Snapd tool for building Ubuntu Core images

mborzecki
upcoming

#1

While working on Automatic gadget asset updates snapd has learned to understand the volume and its structures defined in gadget.yaml. As part of the gadget assets update, snapd can deploy structure on a disk, either by writing raw image data at a specific location or by manipulating the files and directories in a mounted filesystem.

It is a natural step to extend the functionality to and have a tool that can create Ubuntu Cure images from scratch, like ubuntu-image does, but with fewer dependencies and as a part of snapd tools suite.

For the lack of better ideas, I have named the work-in-progress version of the tool snap-image and it lives in the snapd tree under cmd/snap-image.

Most of the support code has already landed, except for a single PR. Once it lands, I will be albe to start opening pull requests with the actual tool. For now, the tool is only available in my snapd fork https://github.com/bboozzoo/snapd/tree/bboozzoo/gadget-update-wip/cmd/snap-image. Feel free to clone the repo and built it yourself.

The name and subcommands are only proposals, although very much functional right now. I did not want to make this another snap subcomand, because we already have a plenty of those, and the tool feels specialized enough to keep it standalone.

Dependencies

The tool has no library dependencies and can be used as a standalone binary. There are however dependencies on external tools:

  • mkfs.ext4
  • mkfs.vfat
  • sfdisk
  • mtools (specifically mcopy)

Workflow

The workflow is simple and is based on what we already have.

Step 1 is to prepare an image using snap prepare-image command:

$ snap prepare-image pc-18.model $PWD/image-root-pc --channel edge

Step 2 is building the actual image:

$ snap-image prepare-volume $PWD/image-root-pc pc
image written to: /tmp/snap-image-099515175/output.img

Commands

Volumes are built using prepare-volume subcommand, like shown above. For the reference:

$ snap-image prepare-volume <prepared-image-dir> <volume-name>

Since I found working with ubuntu-image output a bit frustrating, I have added a command to display the layout information. It is my believe that this makes the debugging & image building process better controlled. The subcommand is position-volume:

$ snap-image position-volume <prepared-image-dir> <volume-name>

Example:

$ snap-image position-volume $HOME/work/canonical/image/image-root-pc pc
volume:
  sector-size: 512
  size: 499033088                 # 974674 sectors (475 MB)
  schema: gpt
  structures: 
     #0 ("mbr"):
       type: mbr
       size: 440                  # 0 sectors + 440 bytes (unaligned)
       start-offset: 0            # 0 sectors
       effective-role: mbr
       content: 
       - image: pc-boot.img
         size: 440                # 0 sectors + 440 bytes (unaligned)
         start-offset: 0          # 0 sectors
     #1 ("BIOS Boot"):
       type: DA,21686148-6449-6E6F-744E-656564454649
       size: 1048576              # 2048 sectors (1 MB)
       start-offset: 1048576      # 2048 sectors
       effective-role: <none>
       content: 
       - image: pc-core.img
         size: 182881             # 357 sectors + 97 bytes (unaligned)
         start-offset: 1048576    # 2048 sectors
     #2 ("EFI System"):
       type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
       size: 52428800             # 102400 sectors (50 MB)
       start-offset: 2097152      # 4096 sectors
       effective-role: system-boot
       filesystem: vfat
       filesystem-label: system-boot
     #3 ("writable"):
       type: 
       size: 444489728            # 868144 sectors (423 MB)
       start-offset: 54525952     # 106496 sectors
       effective-role: system-data
       filesystem: ext4
       filesystem-label: writable

Compatibilty with ubuntu-image

The partition/structure layout code is compatible with layouts produced by ubuntu-image and preserves its properties, such as assuming 512 byte block size and starting the first non MBR structure at 1MB offset by default.

A structure labeled writable with system-data role is automatically added to the volume. Its size is estimated using the same du -s -B1 command followed by 1.5 * measured size + 8MB overhead. Because the staged filesystem is not mangled before calculating its size, snap-image creates slightly larger writable structures.

Compare the sfdisk dump output of images for:

PC
  • image created by ubuntu-image
$ sfdisk --dump image-home/pc.img 
label: gpt
label-id: 7E3F472C-BE32-4F7B-B937-852B59B9C143
device: image-home/pc.img
unit: sectors
first-lba: 34
last-lba: 943932

image-home/pc.img1 : start=        2048, size=        2048, type=21686148-6449-6E6F-744E-656564454649, uuid=CECCDF1F-5C15-4408-884D-E0A94180822A, name="BIOS Boot"
image-home/pc.img2 : start=        4096, size=      102400, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=55191A67-A0BB-4E56-A27A-6D5CDCCA4B73, name="EFI System"
image-home/pc.img3 : start=      106496, size=      837436, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=72E689BD-CD3C-4F00-95E8-2085DEB59EA3, name="writable"
  • image created with snap-image:
$ sfdisk --dump output.img 
label: gpt
label-id: 9EED333F-088D-7A4D-883A-6B74A45D23BF
device: output.img
unit: sectors
first-lba: 34
last-lba: 974640

output.img1 : start=        2048, size=        2048, type=21686148-6449-6E6F-744E-656564454649, uuid=91488627-5959-A248-9DA4-1B983193C5BF, name="BIOS Boot"
output.img2 : start=        4096, size=      102400, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=D02309C4-3B07-2E4F-A119-7345916A05D0, name="EFI System"
output.img3 : start=      106496, size=      868144, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=CAB2F72D-951B-7941-B78D-B147639C645F, name="writable"
RPi3
  • image created by ubuntu-image
$ sfdisk --dump image-home/pi.img 
label: dos
label-id: 0x73d36f65
device: image-home/pi.img
unit: sectors

image-home/pi.img1 : start=        2048, size=      262144, type=c, bootable
image-home/pi.img2 : start=      264192, size=      739000, type=83
  • image created with snap-image:
$ sfdisk --dump output.img 
label: dos
label-id: 0x918bf533
device: output.img
unit: sectors

output.img1 : start=        2048, size=      262144, type=c, bootable
output.img2 : start=      264192, size=      768496, type=83

Current status

The subcommands are already implemented and usable as shown above.

At this point the tool was used to generate bootable images for the following models:

  • ubuntu-core-18-amd64
  • ubuntu-core-18-pi3

I have not attempted building any Core 16 images.

There is a known problem that mkfs.ext4 shipped with Ubuntu 16.04 does not support -d switch. Given that the tool is a standalone Go binary, we could deliver it as a core18 snap and make the problem go away.


#2

we have gadgets (for customer devices) that define multiple volumes for i.e. systems that need the u-boot.img in a separate NAND flash (but the rest of the system on eMMC). this works fine today with ubuntu-image by defining a second volume in gadget.yaml. can we make sure this new way of building images does not regress in that area ?


#3

Right, the code should not regress. I think that current gadget YAML parser is likely error out when more than one volume declares a bootloader, but we’ll have to account for what people are doing, should we ever switch from ubuntu-image. I would very much like to take a peek at any elaborate gadget YAML that you are using.


#4

in that case above only one volume declares the botloader …

like:

device-tree: foobar.dtb
device-tree-origin: kernel
volumes:
  foobar-image:
    bootloader: u-boot
    schema: mbr
    structure:
      - name: system-boot
        type: 0C
        filesystem: vfat
        filesystem-label: system-boot
        size: 128M
        role: system-boot
      - name: writable
        type: 83
        filesystem: ext4
        filesystem-label: writable
        size: 420M
        role: system-data
  u-boot-foobar-123:
    structure:
      - name: u-boot
        type: bare
        size: 512000
        offset: 0
        content:
          - image: u-boot-foobar.img
...

(there are more u-boot volumes below for different variants of the device but only the main volume at the top declares bootloader:)