debian-forge/assemblers/org.osbuild.qcow2
Tom Gundersen 53fe311bcd assembler/qcow2: copy the tree into the target image
We used to let mkfs.ext4 initialize the filesystem for us, but it
turns out that the metadata attributes of the root directory were
not being initialized from the source tree. In particular, this
meant that the SELinu labels were left as unconfined_t, rather
than root_t, which would not allow us to boot in enforcing mode.

An alternative approach might be to fixup the root inode manually,
while still doing the rest using mkfs.ext4, but let's leave that
for the future if it turns out to be worth it.

Signed-off-by: Tom Gundersen <teg@jklm.no>
2019-08-26 09:25:42 +03:00

96 lines
3.8 KiB
Python
Executable file

#!/usr/bin/python3
import contextlib
import json
import math
import os
import socket
import subprocess
import sys
import tempfile
import osbuild.remoteloop as remoteloop
def tree_size(tree):
size = 0
for root, dirs, files in os.walk(tree):
for entry in files + dirs:
path = os.path.join(root, entry)
size += os.stat(path, follow_symlinks=False).st_size
return size
@contextlib.contextmanager
def mount(source, dest, *options):
os.makedirs(dest, 0o755, True)
subprocess.run(["mount", *options, source, dest], check=True)
try:
yield
finally:
subprocess.run(["umount", "-R", dest], check=True)
@contextlib.contextmanager
def mount_api(dest):
with mount("/dev", f"{dest}/dev", "-o", "rbind"), \
mount("/proc", f"{dest}/proc", "-o", "rbind"), \
mount("/sys", f"{dest}/sys", "-o", "rbind"), \
mount("none", f"{dest}/run", "-t", "tmpfs"):
yield
@contextlib.contextmanager
def loop_device(loop_client, image, size, offset=0):
fd = os.open(image, os.O_RDWR)
devname = loop_client.create_device(fd, offset=offset, sizelimit=size)
os.close(fd)
path = f"/dev/{devname}"
try:
yield path
finally:
os.unlink(path)
def main(tree, output_dir, options, loop_client):
filename = options["filename"]
root_fs_uuid = options["root_fs_uuid"]
# Create a working directory on a tmpfs, maybe we should implicitly
# always do this.
with tempfile.TemporaryDirectory() as workdir:
image = f"{workdir}/image.raw"
mountpoint = f"{workdir}/mnt"
# Create an empty image file of the right size
size = int(math.ceil(tree_size(tree) * 1.2 / 512) * 512)
subprocess.run(["truncate", "--size", str(size), image], check=True)
# Set up the partition table of the image
partition_table = "label: mbr\nbootable, type=83"
subprocess.run(["sfdisk", "-q", image], input=partition_table, encoding='utf-8', check=True)
r = subprocess.run(["sfdisk", "--json", image], stdout=subprocess.PIPE, encoding='utf-8', check=True)
partition_table = json.loads(r.stdout)
partition = partition_table["partitiontable"]["partitions"][0]
partition_offset = partition["start"] * 512
partition_size = partition["size"] * 512
# Populate the first partition of the image with an ext4 fs and fill it with the contents of the
# tree we are operating on.
subprocess.run(["mkfs.ext4", "-U", root_fs_uuid, "-E", f"offset={partition_offset}", image,
f"{int(partition_size / 1024)}k"], input="y", encoding='utf-8', check=True)
# Mount the created image as a loopback device
with loop_device(loop_client, image, partition_offset) as loop_block, \
loop_device(loop_client, image, partition_size, partition_offset) as loop_part, \
mount(loop_part, mountpoint):
# Copy the tree into the target image
subprocess.run(["cp", "-a", f"{tree}/.", mountpoint], check=True)
# Install grub2 into the boot sector of the image, and copy the grub2 imagise into /boot/grub2
with mount_api(mountpoint):
subprocess.run(["chroot", mountpoint, "grub2-install", "--no-floppy",
"--modules=part_msdos", "--target=i386-pc", loop_block], check=True)
subprocess.run(["qemu-img", "convert", "-O", "qcow2", "-c", image, f"{output_dir}/{filename}"], check=True)
if __name__ == '__main__':
args = json.load(sys.stdin)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1)
sock.connect("/run/osbuild/api/remoteloop")
ret = main(args["tree"], args["output_dir"], args["options"], remoteloop.LoopClient(sock))
sys.exit(ret)