On Mon, 15 Aug 2016 at 13:22:29 +0100, Simon McVittie wrote: > As a follow-up for these fixes, I'm part way through converting the > partition/device handling to be based on a list of named partitions, > so that Filesystem doesn't have to second-guess how the disk would > have been partitioned for a particular combination of options. This > should hopefully make it more robust.
Here's that change. S
>From c059b1be7bf3c2d4658c70ab0bd938c6649d00cb Mon Sep 17 00:00:00 2001 From: Simon McVittie <s...@debian.org> Date: Mon, 15 Aug 2016 10:12:23 +0100 Subject: [PATCH 3/6] Filesystem.make_rootfs_part: remove unused method Signed-off-by: Simon McVittie <s...@debian.org> --- vmdebootstrap/filesystem.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/vmdebootstrap/filesystem.py b/vmdebootstrap/filesystem.py index fa7a575..3db100a 100644 --- a/vmdebootstrap/filesystem.py +++ b/vmdebootstrap/filesystem.py @@ -297,11 +297,6 @@ class Filesystem(Base): except IOError: pass - def make_rootfs_part(self, extent): - bootsize = self.settings['esp-size'] / (1024 * 1024) + 1 - runcmd(['parted', '-s', self.settings['image'], - 'mkpart', 'primary', str(bootsize), extent]) - def convert_image_to_qcow2(self): """ Current images are all prepared as raw -- 2.8.1
>From 8f76ea193732da163a895aae80631979a7381a9d Mon Sep 17 00:00:00 2001 From: Simon McVittie <s...@debian.org> Date: Mon, 15 Aug 2016 11:18:47 +0100 Subject: [PATCH 4/6] Factor out partition creation into a new Partitions handler This is not intended to make any functional difference, except that creating the ESP now respects bootoffset. It seems unlikely that UEFI systems will have a nonzero bootoffset in practice, but it was simpler this way. Signed-off-by: Simon McVittie <s...@debian.org> --- bin/vmdebootstrap | 75 +++++++++---------------- vmdebootstrap/base.py | 20 ------- vmdebootstrap/partitions.py | 132 ++++++++++++++++++++++++++++++++++++++++++++ vmdebootstrap/uefi.py | 26 ++++----- 4 files changed, 169 insertions(+), 84 deletions(-) create mode 100644 vmdebootstrap/partitions.py diff --git a/bin/vmdebootstrap b/bin/vmdebootstrap index cc1efe1..9dd2cae 100755 --- a/bin/vmdebootstrap +++ b/bin/vmdebootstrap @@ -38,6 +38,7 @@ from vmdebootstrap.codenames import Codenames from vmdebootstrap.filesystem import Filesystem from vmdebootstrap.uefi import Uefi from vmdebootstrap.network import Networking +from vmdebootstrap.partitions import Partitions __version__ = '1.6' @@ -62,6 +63,7 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth ExtLinux.name: ExtLinux(), Filesystem.name: Filesystem(), Networking.name: Networking(), + Partitions.name: Partitions(), } def add_settings(self): @@ -402,67 +404,42 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth starts at that offset to allow customisation scripts to put bootloader images into the space, e.g. u-boot. """ - base = self.handlers[Base.name] - base.message('Creating partitions') + partitions = self.handlers[Partitions.name] + partitions.message('Creating partitions') + partitions.create_table() uefi = self.handlers[Uefi.name] - runcmd(['parted', '-s', self.settings['image'], - 'mklabel', self.settings['part-type']]) - partoffset = 0 - extent = base.check_swap_size() + + extent = partitions.check_swap_size() # uefi - uefi.partition_esp() - - # /boot partitioning offset calculation - # returns partoffset - if self.settings['bootoffset'] and self.settings['bootoffset'] is not '0': - # turn v.small offsets into something at least possible to create. - if self.settings['bootoffset'] < 1048576: - partoffset = 1 - logging.info( - "Setting bootoffset %smib to allow for %s bytes", - partoffset, self.settings['bootoffset']) - else: - partoffset = self.settings['bootoffset'] / (1024 * 1024) - base.message( - "Using bootoffset: %smib %s bytes" % - (partoffset, self.settings['bootoffset'])) + esp = uefi.partition_esp(partitions) - # /boot creation - move into base but keep the check - # needs extent, partoffset, bootsize: no return + # /boot creation - move into partitions but keep the check + # needs extent, bootsize: no return if self.settings['bootsize'] and self.settings['bootsize'] is not '0%': - boot_fs_type = 'ext2' + boot = partitions.new('boot') + + boot.type = self.settings['boottype'] + + # FIXME: what if it is neither ext2 nor FAT? + boot.parted_type = 'ext2' if self.settings['boottype'] in ('vfat', 'msdos'): - boot_fs_type = 'fat16' - if self.settings['grub'] and not partoffset: - partoffset = 1 + boot.parted_type = 'fat16' + bootsize = self.settings['bootsize'] / (1024 * 1024) - bootsize += partoffset - base.message("Using bootsize %smib: %s bytes" % (bootsize, self.settings['bootsize'])) - logging.debug("Starting boot partition at %sMB", bootsize) - runcmd(['parted', '-s', self.settings['image'], - 'mkpart', 'primary', boot_fs_type, str(partoffset), - str(bootsize)]) - logging.debug("Starting root partition at %sMB", partoffset) - runcmd(['parted', '-s', self.settings['image'], - 'mkpart', 'primary', str(bootsize), extent]) + partitions.message("Using bootsize %smib: %s bytes" % (bootsize, self.settings['bootsize'])) + boot.end = boot.start + bootsize - # uefi - make rootfs partition after end of ESP - # needs extent - elif self.settings['use-uefi']: - uefi.make_root(extent) + partitions.commit(boot) - # no boot partition - else: - runcmd(['parted', '-s', self.settings['image'], - 'mkpart', 'primary', '0%', extent]) + root = partitions.new('root') + root.end = extent + partitions.commit(root) - # whatever we create, something needs the boot flag - runcmd(['parted', '-s', self.settings['image'], - 'set', '1', 'boot', 'on']) + partitions.ensure_something_bootable() # return to doing swap setup - base.make_swap(extent) + partitions.make_swap(extent) def _bootstrap_packages(self): base = self.handlers[Base.name] diff --git a/vmdebootstrap/base.py b/vmdebootstrap/base.py index d090f0e..ea8ebe0 100644 --- a/vmdebootstrap/base.py +++ b/vmdebootstrap/base.py @@ -175,26 +175,6 @@ class Base(object): with open(inittab, 'a') as ftab: ftab.write('\nS0:23:respawn:%s\n' % serial_command) - def check_swap_size(self): - # swap - modifies extent - extent = '100%' - swap = 256 * 1024 * 1024 - if self.settings['swap'] > 0: - if self.settings['swap'] > swap: - swap = self.settings['swap'] - else: - # minimum 256MB as default qemu ram is 128MB - logging.debug("Setting minimum 256MB swap space") - extent = "%s%%" % int(100 * (self.settings['size'] - swap) / self.settings['size']) - return extent - - def make_swap(self, extent): - if self.settings['swap'] > 0: - logging.debug("Creating swap partition") - runcmd([ - 'parted', '-s', self.settings['image'], - 'mkpart', 'primary', 'linux-swap', extent, '100%']) - def base_packages(self): packages = [] if not self.settings['foreign'] and not self.settings['no-acpid']: diff --git a/vmdebootstrap/partitions.py b/vmdebootstrap/partitions.py new file mode 100644 index 0000000..4756c43 --- /dev/null +++ b/vmdebootstrap/partitions.py @@ -0,0 +1,132 @@ +""" + Partitioning +""" +# -*- coding: utf-8 -*- +# +# partitions.py +# +# Copyright 2015 Neil Williams +# Copyright 2016 Simon McVittie +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging + +from vmdebootstrap.base import ( + Base, + runcmd, +) + +class Partition(object): + def __init__(self, name, index): + self.name = name + self.index = index + self.parted_type = None + self.type = None + self.flags = set() + # in MiB + self.start = None + self.end = None + +class Partitions(Base): + name = 'partitions' + + def __init__(self): + super(Partitions, self).__init__() + + self.partitions = [] + # in MiB + self.start_space = 0 + + def create_table(self): + partoffset = 0 + + runcmd(['parted', '-s', self.settings['image'], + 'mklabel', self.settings['part-type']]) + + # /boot partitioning offset calculation + # returns partoffset + if self.settings['bootoffset'] and self.settings['bootoffset'] is not '0': + # turn v.small offsets into something at least possible to create. + if self.settings['bootoffset'] < 1048576: + partoffset = 1 + logging.info( + "Setting bootoffset %smib to allow for %s bytes", + partoffset, self.settings['bootoffset']) + else: + partoffset = self.settings['bootoffset'] / (1024 * 1024) + self.message( + "Using bootoffset: %smib %s bytes" % + (partoffset, self.settings['bootoffset'])) + + self.start_space = partoffset + + def new(self, name): + part = Partition(name, len(self.partitions) + 1) + self.partitions.append(part) + part.start = self.start_space + return part + + def commit(self, part): + parted_type = part.parted_type + + if parted_type is None: + parted_type = part.type + + if parted_type in ('vfat', 'msdos'): + parted_type = 'fat16' + + if parted_type is None: + maybe_type = [] + else: + maybe_type = [parted_type] + + runcmd(['parted', '-s', self.settings['image'], + 'mkpart', 'primary'] + maybe_type + [ + str(part.start), str(part.end)]) + for f in part.flags: + runcmd(['parted', '-s', self.settings['image'], + 'set', str(part.index), f, 'on']) + + self.start_space = part.end + + def ensure_something_bootable(self): + # whatever we create, something needs the boot flag + for p in self.partitions: + if 'boot' in p.flags: + break + else: + # assume the first partition is a good choice... + runcmd(['parted', '-s', self.settings['image'], + 'set', '1', 'boot', 'on']) + + def check_swap_size(self): + # swap - modifies extent + extent = '100%' + swap = 256 * 1024 * 1024 + if self.settings['swap'] > 0: + if self.settings['swap'] > swap: + swap = self.settings['swap'] + else: + # minimum 256MB as default qemu ram is 128MB + logging.debug("Setting minimum 256MB swap space") + extent = "%s%%" % int(100 * (self.settings['size'] - swap) / self.settings['size']) + return extent + + def make_swap(self, extent): + if self.settings['swap'] > 0: + logging.debug("Creating swap partition") + runcmd([ + 'parted', '-s', self.settings['image'], + 'mkpart', 'primary', 'linux-swap', extent, '100%']) diff --git a/vmdebootstrap/uefi.py b/vmdebootstrap/uefi.py index b2ca410..362b9b2 100644 --- a/vmdebootstrap/uefi.py +++ b/vmdebootstrap/uefi.py @@ -29,7 +29,6 @@ import logging import cliapp from vmdebootstrap.base import ( Base, - runcmd, mount_wrapper, umount_wrapper, ) @@ -141,18 +140,20 @@ class Uefi(Base): finally: umount_wrapper(rootdir) - def partition_esp(self): + def partition_esp(self, partitions): if not self.settings['use-uefi']: - return + return None + espsize = self.settings['esp-size'] / (1024 * 1024) self.message("Using ESP size: %smib %s bytes" % (espsize, self.settings['esp-size'])) - runcmd(['parted', '-s', self.settings['image'], - 'mkpart', 'primary', 'fat32', - '1', str(espsize)]) - runcmd(['parted', '-s', self.settings['image'], - 'set', '1', 'boot', 'on']) - runcmd(['parted', '-s', self.settings['image'], - 'set', '1', 'esp', 'on']) + + esp = partitions.new('esp') + esp.parted_type = 'fat32' + esp.type = 'vfat' + esp.flags = set(['boot', 'esp']) + esp.end = esp.start + espsize + partitions.commit(esp) + return esp def prepare_esp(self, rootdir, bootdev): self.bootdir = '%s/%s/%s' % (rootdir, 'boot', 'efi') @@ -160,8 +161,3 @@ class Uefi(Base): self.mkfs(bootdev, fstype='vfat') os.makedirs(self.bootdir) return self.bootdir - - def make_root(self, extent): - bootsize = self.settings['esp-size'] / (1024 * 1024) + 1 - runcmd(['parted', '-s', self.settings['image'], - 'mkpart', 'primary', str(bootsize), extent]) -- 2.8.1
>From ecd29b08c2db4d9962a1d92ee3016ce547c3f492 Mon Sep 17 00:00:00 2001 From: Simon McVittie <s...@debian.org> Date: Mon, 15 Aug 2016 11:28:03 +0100 Subject: [PATCH 5/6] Partitions: do swap creation in terms of 1 MiB offsets, not percent Signed-off-by: Simon McVittie <s...@debian.org> --- bin/vmdebootstrap | 2 +- vmdebootstrap/partitions.py | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/bin/vmdebootstrap b/bin/vmdebootstrap index 9dd2cae..5c554b2 100755 --- a/bin/vmdebootstrap +++ b/bin/vmdebootstrap @@ -439,7 +439,7 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth partitions.ensure_something_bootable() # return to doing swap setup - partitions.make_swap(extent) + partitions.make_swap() def _bootstrap_packages(self): base = self.handlers[Base.name] diff --git a/vmdebootstrap/partitions.py b/vmdebootstrap/partitions.py index 4756c43..4d4a036 100644 --- a/vmdebootstrap/partitions.py +++ b/vmdebootstrap/partitions.py @@ -48,6 +48,7 @@ class Partitions(Base): self.partitions = [] # in MiB self.start_space = 0 + self.end_space = 0 def create_table(self): partoffset = 0 @@ -71,6 +72,9 @@ class Partitions(Base): (partoffset, self.settings['bootoffset'])) self.start_space = partoffset + self.end_space = self.settings['size'] / (1024 * 1024) + # Allow 1 MiB of space for the backup GPT + self.end_space -= 1 def new(self, name): part = Partition(name, len(self.partitions) + 1) @@ -113,7 +117,7 @@ class Partitions(Base): def check_swap_size(self): # swap - modifies extent - extent = '100%' + extent = self.end_space swap = 256 * 1024 * 1024 if self.settings['swap'] > 0: if self.settings['swap'] > swap: @@ -121,12 +125,13 @@ class Partitions(Base): else: # minimum 256MB as default qemu ram is 128MB logging.debug("Setting minimum 256MB swap space") - extent = "%s%%" % int(100 * (self.settings['size'] - swap) / self.settings['size']) + extent = self.end_space - (swap / (1024 * 1024)) return extent - def make_swap(self, extent): + def make_swap(self): if self.settings['swap'] > 0: logging.debug("Creating swap partition") - runcmd([ - 'parted', '-s', self.settings['image'], - 'mkpart', 'primary', 'linux-swap', extent, '100%']) + swap = self.new('swap') + swap.parted_type = 'linux-swap' + swap.end = self.end_space + self.commit(swap) -- 2.8.1
>From 304b4145a75aad53042ee364c0908d889d1bbdfc Mon Sep 17 00:00:00 2001 From: Simon McVittie <s...@debian.org> Date: Mon, 15 Aug 2016 13:46:40 +0100 Subject: [PATCH 6/6] Filesystem: use Partitions to set up kpartx This avoids second-guessing which partition corresponds to which index. Signed-off-by: Simon McVittie <s...@debian.org> --- bin/vmdebootstrap | 3 ++- vmdebootstrap/filesystem.py | 37 +++++++++++-------------------------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/bin/vmdebootstrap b/bin/vmdebootstrap index 5c554b2..cbeb185 100755 --- a/bin/vmdebootstrap +++ b/bin/vmdebootstrap @@ -220,10 +220,11 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth filesystem = self.handlers[Filesystem.name] extlinux = self.handlers[ExtLinux.name] distro = self.handlers[Codenames.name] + partitions = self.handlers[Partitions.name] base.create_empty_image() self.partition_image() extlinux.install_mbr() - filesystem.setup_kpartx() + filesystem.setup_kpartx(partitions) rootdev = filesystem.devices['rootdev'] roottype = filesystem.devices['roottype'] bootdev = filesystem.devices['bootdev'] diff --git a/vmdebootstrap/filesystem.py b/vmdebootstrap/filesystem.py index 3db100a..20749ab 100644 --- a/vmdebootstrap/filesystem.py +++ b/vmdebootstrap/filesystem.py @@ -85,35 +85,20 @@ class Filesystem(Base): self.message("Updating the initramfs") runcmd(['chroot', rootdir, cmd, '-u']) - def setup_kpartx(self): + def setup_kpartx(self, partitions): bootindex = None + rootindex = None swapindex = None out = runcmd(['kpartx', '-avs', self.settings['image']]) - if self.settings['bootsize'] and self.settings['swap'] > 0: - bootindex = 0 - rootindex = 1 - swapindex = 2 - parts = 3 - elif self.settings['use-uefi'] and self.settings['swap'] > 0: - bootindex = 0 - rootindex = 1 - swapindex = 2 - parts = 3 - elif self.settings['use-uefi']: - bootindex = 0 - rootindex = 1 - parts = 2 - elif self.settings['bootsize']: - bootindex = 0 - rootindex = 1 - parts = 2 - elif self.settings['swap'] > 0: - rootindex = 0 - swapindex = 1 - parts = 2 - else: - rootindex = 0 - parts = 1 + parts = len(partitions.partitions) + for part in partitions.partitions: + if part.name in ('boot', 'esp'): + bootindex = part.index - 1 + elif part.name == 'root': + rootindex = part.index - 1 + elif part.name == 'swap': + swapindex = part.index - 1 + assert rootindex is not None boot = None swap = None devices = [line.decode('utf-8').split()[2] -- 2.8.1