assembler/qemu: support generic partition layouts

Introduce two new assembler options `pttype` and `partitions` to
allow fine grained control over how the partition table is created.
The first one controls the partition type, either `mbr` (default,
when the key is missing) or `gpt`; if specified the `partitions`
key must contain a list of objects describing the individual
partitions (`start`, `size`, `type`) together with a `filesystem`
object describing the filesystem (`type`, `uuid`, `mountpoint`) to
be created on that partition.
In the case the `pttype` option is missing, the legacy mode is used
where `root_fs_uuid` and `root_fs_type` need to be specified.
This commit is contained in:
Christian Kellner 2019-12-11 20:17:53 +01:00 committed by Tom Gundersen
parent 9688859acf
commit 5eb4ceff2f

View file

@ -14,11 +14,13 @@ STAGE_DESC = "Assemble a bootable partitioned disk image with qemu-img"
STAGE_INFO = """ STAGE_INFO = """
Assemble a bootable partitioned disk image using `qemu-img`. Assemble a bootable partitioned disk image using `qemu-img`.
Creates a sparse MBR-partitioned disk image of the given `size`, with a single Creates a sparse partitioned disk image of type `pttype` of a given `size`,
bootable partition containing the root filesystem. with a partition table according to `partitions` or a MBR partitioned disk
having a single bootable partition containing the root filesystem if the
`pttype` property is absent.
Installs GRUB2 (using the buildhost's `/usr/lib/grub/i386-pc/boot.img` etc.) as If the partition type is MBR it installs GRUB2 (using the buildhost's
the bootloader. `/usr/lib/grub/i386-pc/boot.img` etc.) as the bootloader.
Copies the tree contents into the root filesystem and then converts the raw Copies the tree contents into the root filesystem and then converts the raw
sparse image into the format requested with the `fmt` option. sparse image into the format requested with the `fmt` option.
@ -27,7 +29,12 @@ Buildhost commands used: `truncate`, `mount`, `umount`, `sfdisk`,
`grub2-mkimage`, `mkfs.ext4` or `mkfs.xfs`, `qemu-img`. `grub2-mkimage`, `mkfs.ext4` or `mkfs.xfs`, `qemu-img`.
""" """
STAGE_OPTS = """ STAGE_OPTS = """
"required": ["format", "filename", "ptuuid", "root_fs_uuid", "size"], "required": ["format", "filename", "ptuuid", "size"],
"oneOf": [{
"required": ["root_fs_uuid"]
},{
"required": ["pttype", "partitions"]
}],
"properties": { "properties": {
"format": { "format": {
"description": "Image file format to use", "description": "Image file format to use",
@ -38,10 +45,61 @@ STAGE_OPTS = """
"description": "Image filename", "description": "Image filename",
"type": "string" "type": "string"
}, },
"partitions": {
"description": "Partition layout ",
"type": "array",
"items": {
"description": "Description of one partition",
"type": "object",
"properties": {
"bootable": {
"description": "Mark the partition as bootable (MBR)",
"type": "boolean"
},
"size": {
"description": "The size of this partition",
"type": "integer"
},
"start": {
"description": "The start offset of this partition",
"type": "integer"
},
"type": {
"description": "The partition type (UUID or identifier)",
"type": "string"
},
"filesystem": {
"description": "Description of the filesystem",
"type": "object",
"required": ["mountpoint", "type", "uuid"],
"properties": {
"mountpoint": {
"description": "Where to mount the partition",
"type": "string"
},
"type": {
"description": "Type of the filesystem",
"type": "string",
"enum": ["ext4", "xfs"]
},
"uuid": {
"description": "UUID for the filesystem",
"type": "string"
}
}
}
}
}
},
"ptuuid": { "ptuuid": {
"description": "UUID for the disk image's partition table", "description": "UUID for the disk image's partition table",
"type": "string" "type": "string"
}, },
"pttype": {
"description": "The type of the partition table",
"type": "string",
"enum": ["mbr", "gpt"]
},
"root_fs_uuid": { "root_fs_uuid": {
"description": "UUID for the root filesystem", "description": "UUID for the root filesystem",
"type": "string" "type": "string"
@ -86,8 +144,7 @@ def mkfs_for_type(device, uuid, fs_type):
maker(device, uuid) maker(device, uuid)
def create_partition_table(image, options): def create_partition_table_legacy(image, options):
"""Set up the partition table of the image"""
ptuuid = options["ptuuid"] ptuuid = options["ptuuid"]
root_fs_uuid = options["root_fs_uuid"] root_fs_uuid = options["root_fs_uuid"]
root_fs_type = options.get("root_fs_type", "ext4") root_fs_type = options.get("root_fs_type", "ext4")
@ -112,6 +169,44 @@ def create_partition_table(image, options):
return partitions return partitions
def create_partition_table(image, options):
"""Set up the partition table of the image"""
ptuuid = options["ptuuid"]
pttype = options.get("pttype")
# if 'pttype' is missing, we are in legacy mode
if pttype is None:
return create_partition_table_legacy(image, options)
# new mode
partitions = options["partitions"]
# generate the command for sfdisk to create the table
command = f"label: {pttype}\nlabel-id: {ptuuid}"
for partition in partitions:
fields = []
for field in ["start", "size", "type"]:
if field in partition:
fields += [f'{field}="{partition[field]}"']
if "bootable" in partition and partition["bootable"]:
fields += ["bootable"]
command += "\n" + ", ".join(fields)
subprocess.run(["sfdisk", "-q", image], input=command, encoding='utf-8', check=True)
# read the actual dimensions back
r = subprocess.run(["sfdisk", "--json", image], stdout=subprocess.PIPE, encoding='utf-8', check=True)
disk_table = json.loads(r.stdout)["partitiontable"]
disk_partitions = disk_table["partitions"]
assert len(disk_partitions) == len(partitions)
for i, partition in enumerate(partitions):
partition["start"] = disk_partitions[i]["start"] * 512
partition["size"] = disk_partitions[i]["size"] * 512
return partitions
def install_grub2(image, partitions): def install_grub2(image, partitions):
"""Install grub2 to image""" """Install grub2 to image"""
grub2_core = "/var/tmp/grub2-core.img" grub2_core = "/var/tmp/grub2-core.img"