Disk creation improvements

* Support specifying the size of the target disk image for qemu
* For bare metal, only pad the image to the next megabyte
* Use truncate() to extend images, instead of writing zeros (faster)
* Return None from get_disk() with nonexistent name
* Leave 1MiB on non-boot disks, or 1GiB on boot disks, unpartitioned
  (for proper 4K alignment and to help preserve the srcfs or boot
  partition creation)
* Fix qemu invocation when an external.img is not used
* Make -qr work with kernel bootstrap (will need kexec-fiwix fix)
This commit is contained in:
Gábor Stefanik 2023-12-17 21:59:00 +01:00
parent dc62d8242c
commit b4d9c5e7cb
4 changed files with 61 additions and 27 deletions

View File

@ -33,7 +33,7 @@ class Generator():
self.tmp_dir = tmpdir.path self.tmp_dir = tmpdir.path
self.external_dir = os.path.join(self.tmp_dir, 'external') self.external_dir = os.path.join(self.tmp_dir, 'external')
def prepare(self, using_kernel=False, kernel_bootstrap=False): def prepare(self, using_kernel=False, kernel_bootstrap=False, target_size=0):
""" """
Prepare basic media of live-bootstrap. Prepare basic media of live-bootstrap.
/steps -- contains steps to be built /steps -- contains steps to be built
@ -57,9 +57,14 @@ class Generator():
if self.repo_path or self.external_sources: if self.repo_path or self.external_sources:
self.tmpdir.add_disk("external", filesystem="ext3") self.tmpdir.add_disk("external", filesystem="ext3")
self.tmpdir.mount_disk("external", "external") self.tmpdir.mount_disk("external", "external")
else:
self.external_dir = os.path.join(self.tmp_dir, 'external')
elif using_kernel: elif using_kernel:
self.tmp_dir = os.path.join(self.tmp_dir, 'disk') self.tmp_dir = os.path.join(self.tmp_dir, 'disk')
self.tmpdir.add_disk("disk", filesystem="ext3") self.tmpdir.add_disk("disk",
filesystem="ext3",
size=(target_size + "M") if target_size else "16G",
bootable=True)
self.tmpdir.mount_disk("disk", "disk") self.tmpdir.mount_disk("disk", "disk")
self.external_dir = os.path.join(self.tmp_dir, 'external') self.external_dir = os.path.join(self.tmp_dir, 'external')
@ -86,7 +91,7 @@ class Generator():
shutil.copytree(self.repo_path, repo_dir) shutil.copytree(self.repo_path, repo_dir)
if kernel_bootstrap: if kernel_bootstrap:
self.create_builder_hex0_disk_image(os.path.join(self.tmp_dir, 'disk.img')) self.create_builder_hex0_disk_image(os.path.join(self.tmp_dir, 'disk.img'), target_size)
if kernel_bootstrap and (self.external_sources or self.repo_path): if kernel_bootstrap and (self.external_sources or self.repo_path):
self.tmpdir.umount_disk('external') self.tmpdir.umount_disk('external')
@ -149,7 +154,7 @@ class Generator():
def distfiles(self): def distfiles(self):
"""Copy in distfiles""" """Copy in distfiles"""
def copy_no_network_distfiles(out): def copy_no_network_distfiles(out):
# Note that no network == no disk for kernel bootstrap mode # Note that "no disk" implies "no network" for kernel bootstrap mode
pre_src_path = os.path.join(self.git_dir, 'steps', 'pre-network-sources') pre_src_path = os.path.join(self.git_dir, 'steps', 'pre-network-sources')
with open(pre_src_path, 'r', encoding="utf-8") as source_list: with open(pre_src_path, 'r', encoding="utf-8") as source_list:
for file in source_list.readlines(): for file in source_list.readlines():
@ -222,7 +227,7 @@ class Generator():
os.chdir(save_cwd) os.chdir(save_cwd)
def create_builder_hex0_disk_image(self, image_file_name): def create_builder_hex0_disk_image(self, image_file_name, size):
"""Create builder-hex0 disk image""" """Create builder-hex0 disk image"""
shutil.copyfile(os.path.join('seed', 'stage0-posix', 'bootstrap-seeds', shutil.copyfile(os.path.join('seed', 'stage0-posix', 'bootstrap-seeds',
'NATIVE', 'x86', 'builder-hex0-x86-stage1.img'), 'NATIVE', 'x86', 'builder-hex0-x86-stage1.img'),
@ -250,11 +255,10 @@ class Generator():
image_file.write(b'\0' * round_up) image_file.write(b'\0' * round_up)
current_size += round_up current_size += round_up
# fill file with zeros up to desired size, one megabyte at a time # extend file up to desired size
with open(image_file_name, 'ab') as image_file: if current_size < size * megabyte:
while current_size < 16384 * megabyte: with open(image_file_name, 'ab') as image_file:
image_file.write(b'\0' * megabyte) image_file.truncate(size * megabyte)
current_size += megabyte
def check_file(self, file_name, expected_hash): def check_file(self, file_name, expected_hash):
"""Check hash of downloaded source file.""" """Check hash of downloaded source file."""

View File

@ -60,10 +60,21 @@ class Tmpdir:
self._type = TmpType.TMPFS self._type = TmpType.TMPFS
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def add_disk(self, name, size="16G", filesystem="ext4", tabletype="msdos", mkfs_args=None): def add_disk(self,
name,
size="16G",
filesystem="ext4",
tabletype="msdos",
bootable=False,
mkfs_args=None):
"""Add a disk""" """Add a disk"""
disk_path = os.path.join(self.path, f"{name}.img") disk_path = os.path.join(self.path, f"{name}.img")
self._disks[name] = create_disk(disk_path, tabletype, filesystem, size, mkfs_args=mkfs_args) self._disks[name] = create_disk(disk_path,
tabletype,
filesystem,
size,
bootable,
mkfs_args)
self._disk_filesystems[name] = filesystem self._disk_filesystems[name] = filesystem
# Allow executing user to access it # Allow executing user to access it
run_as_root("chown", getpass.getuser(), self._disks[name]) run_as_root("chown", getpass.getuser(), self._disks[name])
@ -87,4 +98,4 @@ class Tmpdir:
def get_disk(self, name): def get_disk(self, name):
"""Get the path to a device of a disk""" """Get the path to a device of a disk"""
return self._disks[name] return self._disks.get(name)

View File

@ -31,7 +31,8 @@ def run_as_root(*args, **kwargs):
return run("sudo", *args, **kwargs) return run("sudo", *args, **kwargs)
return run(*args, **kwargs) return run(*args, **kwargs)
def create_disk(image, disk_type, fs_type, size, mkfs_args=None): # pylint: disable=too-many-arguments
def create_disk(image, disk_type, fs_type, size, bootable=False, mkfs_args=None):
"""Create a disk image, with a filesystem on it""" """Create a disk image, with a filesystem on it"""
if mkfs_args is None: if mkfs_args is None:
mkfs_args = [] mkfs_args = []
@ -42,7 +43,7 @@ def create_disk(image, disk_type, fs_type, size, mkfs_args=None):
# Create the partition # Create the partition
if disk_type != "none": if disk_type != "none":
run_as_root('parted', '--script', image, 'mklabel', disk_type, 'mkpart', run_as_root('parted', '--script', image, 'mklabel', disk_type, 'mkpart',
'primary', fs_type, '0%', '100%') 'primary', fs_type, '1GiB' if bootable else '1MiB', '100%')
run_as_root('partprobe', loop_dev) run_as_root('partprobe', loop_dev)
run_as_root('mkfs.' + fs_type, loop_dev + "p1", *mkfs_args) run_as_root('mkfs.' + fs_type, loop_dev + "p1", *mkfs_args)
return loop_dev return loop_dev

View File

@ -39,7 +39,7 @@ def create_configuration_file(args):
if args.repo or args.external_sources: if args.repo or args.external_sources:
config.write("DISK=sdb1\n") config.write("DISK=sdb1\n")
else: else:
config.write("DISK=sdb\n") config.write("DISK=sda\n")
config.write("KERNEL_BOOTSTRAP=True\n") config.write("KERNEL_BOOTSTRAP=True\n")
else: else:
config.write("DISK=sda1\n") config.write("DISK=sda1\n")
@ -98,6 +98,8 @@ def main():
default="qemu-system-x86_64") default="qemu-system-x86_64")
parser.add_argument("-qr", "--qemu-ram", help="Memory (in megabytes) allocated to QEMU VM", parser.add_argument("-qr", "--qemu-ram", help="Memory (in megabytes) allocated to QEMU VM",
default=4096) default=4096)
parser.add_argument("-qs", "--target-size", help="Size of the target image (for QEMU only)",
default="16G")
parser.add_argument("-qk", "--kernel", help="Custom sysa kernel to use") parser.add_argument("-qk", "--kernel", help="Custom sysa kernel to use")
parser.add_argument("-b", "--bare-metal", help="Build images for bare metal", parser.add_argument("-b", "--bare-metal", help="Build images for bare metal",
@ -136,6 +138,15 @@ def main():
if int(args.cores) < 1: if int(args.cores) < 1:
raise ValueError("Must use one or more cores.") raise ValueError("Must use one or more cores.")
# Target image size validation
if args.qemu:
if int(str(args.target_size).rstrip('gGmM')) < 1:
raise ValueError("Please specify a positive target size for qemu.")
args.target_size = (int(str(args.target_size).rstrip('gGmM')) *
(1024 if str(args.target_size).lower().endswith('g') else 1))
else:
args.target_size = 0
# bootstrap.cfg # bootstrap.cfg
try: try:
os.remove(os.path.join('sysa', 'bootstrap.cfg')) os.remove(os.path.join('sysa', 'bootstrap.cfg'))
@ -158,9 +169,9 @@ def main():
repo_path=args.repo, repo_path=args.repo,
early_preseed=args.early_preseed) early_preseed=args.early_preseed)
bootstrap(args, generator, tmpdir) bootstrap(args, generator, tmpdir, args.target_size)
def bootstrap(args, generator, tmpdir): def bootstrap(args, generator, tmpdir, size):
"""Kick off bootstrap process.""" """Kick off bootstrap process."""
print(f"Bootstrapping {args.arch} -- SysA") print(f"Bootstrapping {args.arch} -- SysA")
if args.chroot: if args.chroot:
@ -224,18 +235,18 @@ print(shutil.which('chroot'))
elif args.bare_metal: elif args.bare_metal:
if args.kernel: if args.kernel:
generator.prepare(using_kernel=True) generator.prepare(using_kernel=True, target_size=size)
print("Please:") print("Please:")
print(" 1. Take tmp/initramfs and your kernel, boot using this.") print(" 1. Take tmp/initramfs and your kernel, boot using this.")
print(" 2. Take tmp/disk.img and put this on a writable storage medium.") print(" 2. Take tmp/disk.img and put this on a writable storage medium.")
else: else:
generator.prepare(kernel_bootstrap=True) generator.prepare(kernel_bootstrap=True, target_size=size)
print("Please:") print("Please:")
print(" 1. Take tmp/disk.img and write it to a boot drive and then boot it.") print(" 1. Take tmp/disk.img and write it to a boot drive and then boot it.")
else: else:
if args.kernel: if args.kernel:
generator.prepare(using_kernel=True) generator.prepare(using_kernel=True, target_size=size)
run(args.qemu_cmd, run(args.qemu_cmd,
'-enable-kvm', '-enable-kvm',
@ -249,17 +260,24 @@ print(shutil.which('chroot'))
'-nographic', '-nographic',
'-append', 'console=ttyS0 root=/dev/sda1 rootfstype=ext3 init=/init rw') '-append', 'console=ttyS0 root=/dev/sda1 rootfstype=ext3 init=/init rw')
else: else:
generator.prepare(kernel_bootstrap=True) generator.prepare(kernel_bootstrap=True, target_size=size)
run(args.qemu_cmd, arg_list = [
'-enable-kvm', '-enable-kvm',
'-m', "4G", '-m', str(args.qemu_ram) + 'M',
'-smp', str(args.cores), '-smp', str(args.cores),
'-no-reboot', '-no-reboot',
'-drive', 'file=' + os.path.join(generator.tmp_dir, 'disk.img') + ',format=raw', '-drive', 'file=' + os.path.join(generator.tmp_dir, 'disk.img') + ',format=raw'
'-drive', 'file=' + tmpdir.get_disk("external") + ',format=raw', ]
if tmpdir.get_disk("external") is not None:
arg_list += [
'-drive', 'file=' + tmpdir.get_disk("external") + ',format=raw',
]
arg_list += [
'-machine', 'kernel-irqchip=split', '-machine', 'kernel-irqchip=split',
'-nic', 'user,ipv6=off,model=e1000', '-nic', 'user,ipv6=off,model=e1000',
'-nographic') '-nographic'
]
run(args.qemu_cmd, *arg_list)
if __name__ == "__main__": if __name__ == "__main__":
main() main()