summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Cragg <edward.cragg@codethink.co.uk>2015-08-04 12:21:57 +0100
committerPedro Alvarez <pedro.alvarez@codethink.co.uk>2015-10-24 14:44:00 +0100
commit025e1444551457b2fc3d9a048402e6bdfd6da7e5 (patch)
tree27739b61bccd74db4cb0b3489a59bd7369e28fb1
parentf3937baa7667e41cee5119d056514a8b6f913068 (diff)
downloaddefinitions-025e1444551457b2fc3d9a048402e6bdfd6da7e5.tar.gz
Rawdisk partitioning v2: Add partitioning functions
Add partitioning functions to the rawdisk.write deployment extension Change-Id: I45bcabc191951d086b8f4ae028a248f95a5e6f2e
-rwxr-xr-xextensions/openstack.write2
-rw-r--r--extensions/partitioning.py163
-rwxr-xr-xextensions/rawdisk.write73
-rw-r--r--extensions/writeexts.py323
4 files changed, 490 insertions, 71 deletions
diff --git a/extensions/openstack.write b/extensions/openstack.write
index f1233560..f0d2fc0b 100755
--- a/extensions/openstack.write
+++ b/extensions/openstack.write
@@ -51,7 +51,7 @@ class OpenStackWriteExtension(writeexts.WriteExtension):
def set_extlinux_root_to_virtio(self, raw_disk):
'''Re-configures extlinux to use virtio disks'''
self.status(msg='Updating extlinux.conf')
- with self.mount(raw_disk) as mp:
+ with self.find_and_mount_rootfs(raw_disk) as mp:
path = os.path.join(mp, 'extlinux.conf')
with open(path) as f:
diff --git a/extensions/partitioning.py b/extensions/partitioning.py
new file mode 100644
index 00000000..1c295697
--- /dev/null
+++ b/extensions/partitioning.py
@@ -0,0 +1,163 @@
+#!/usr/bin/python
+# Copyright (C) 2015 Codethink Limited
+#
+# 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; version 2 of the License.
+#
+# 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/>.
+
+
+"""A module providing Baserock-specific partitioning functions"""
+
+import os
+import pyfdisk
+import re
+import subprocess
+import writeexts
+
+def do_partitioning(location, disk_size, temp_root, part_spec):
+ '''Perform partitioning
+
+ Perform partitioning using the pyfdisk.py module. Documentation
+ for this, and guidance on how to create a partition specification can
+ be found in extensions/pyfdisk.README
+
+ This function also validates essential parts of the partition layout
+
+ Args:
+ location: Path to the target device or image
+ temp_root: Location of the unpacked Baserock rootfs
+ part_spec: Path to a YAML formatted partition specification
+ Returns:
+ A pyfdisk.py Device object
+ Raises:
+ writeexts.ExtensionError
+ '''
+ # Create partition table and filesystems
+ try:
+ dev = pyfdisk.load_yaml(location, disk_size, part_spec)
+ writeexts.Extension.status(msg='Loaded partition specification: %s' %
+ part_spec)
+
+ # FIXME: GPT currently not fully supported due to missing tools
+ if dev.partition_table_format.lower() == 'gpt':
+ writeexts.Extension.status(msg='WARNING: GPT partition tables '
+ 'are not currently supported, '
+ 'when using the extlinux '
+ 'bootloader')
+
+ writeexts.Extension.status(msg='Summary:\n' + str(dev.partitionlist))
+ writeexts.Extension.status(msg='Writing partition table')
+ dev.commit()
+ dev.create_filesystems(skip=('/'))
+ except (pyfdisk.PartitioningError, pyfdisk.FdiskError) as e:
+ raise writeexts.ExtensionError(e.msg)
+
+ mountpoints = set(part.mountpoint for part in dev.partitionlist
+ if hasattr(part, 'mountpoint'))
+ if '/' not in mountpoints:
+ raise writeexts.ExtensionError('No partition with root '
+ 'mountpoint, please specify a '
+ 'partition with \'mountpoint: /\' '
+ 'in the partition specification')
+
+ mounted_partitions = set(part for part in dev.partitionlist
+ if hasattr(part, 'mountpoint'))
+
+ for part in mounted_partitions:
+ if not hasattr(part, 'filesystem'):
+ raise writeexts.ExtensionError('Cannot mount a partition '
+ 'without filesystem, please specify one '
+ 'for \'%s\' partition in the partition '
+ 'specification' % part.mountpoint)
+ if part.mountpoint == '/':
+ # Check that bootable flag is set for MBR devices
+ if (hasattr(part, 'boot')
+ and str(part.boot).lower() not in ('yes', 'true')
+ and dev.partition_table_format.lower() == 'mbr'):
+ writeexts.Extension.status(msg='WARNING: Boot partition '
+ 'needs bootable flag set to '
+ 'boot with extlinux/syslinux')
+
+ return dev
+
+def process_raw_files(dev, temp_root):
+ if hasattr(dev, 'raw_files'):
+ write_raw_files(dev.location, temp_root, dev)
+ for part in dev.partitionlist:
+ if hasattr(part, 'raw_files'):
+ # dd seek=n is used, which skips n blocks before writing,
+ # so we must skip n-1 sectors before writing in order to
+ # start writing files to the first block of the partition
+ write_raw_files(dev.location, temp_root, part,
+ (part.extent.start - 1) * dev.sector_size)
+
+def write_raw_files(location, temp_root, dev_or_part, start_offset=0):
+ '''Write files with `dd`'''
+ offset = 0
+ for raw_args in dev_or_part.raw_files:
+ r = RawFile(temp_root, start_offset, offset, **raw_args)
+ offset = r.next_offset
+ r.dd(location)
+
+
+class RawFile(object):
+ '''A class to hold information about a raw file to write to a device'''
+
+ def __init__(self, source_root,
+ start_offset=0, wr_offset=0,
+ sector_size=512, **kwargs):
+ '''Initialisation function
+
+ Args:
+ source_root: Base path for filenames
+ wr_offset: Offset to write to (and offset per-file offsets by)
+ sector_size: Device sector size (default: 512)
+ **kwargs:
+ file: A path to the file to write (combined with source_root)
+ offset_sectors: An offset to write to in sectors (optional)
+ offset_bytes: An offset to write to in bytes (optional)
+ '''
+ if 'file' not in kwargs:
+ raise writeexts.ExtensionError('Missing file name or path')
+ self.path = os.path.join(source_root,
+ re.sub('^/', '', kwargs['file']))
+
+ if not os.path.exists(self.path):
+ raise writeexts.ExtensionError('File not found: %s' % self.path)
+ elif os.path.isdir(self.path):
+ raise writeexts.ExtensionError('Can only dd regular files')
+ else:
+ self.size = os.stat(self.path).st_size
+
+ self.offset = start_offset
+ if 'offset_bytes' in kwargs:
+ self.offset += pyfdisk.human_size(kwargs['offset_bytes'])
+ elif 'offset_sectors' in kwargs:
+ self.offset += kwargs['offset_sectors'] * sector_size
+ else:
+ self.offset += wr_offset
+
+ self.skip = pyfdisk.human_size(kwargs.get('skip_bytes', 0))
+ self.count = pyfdisk.human_size(kwargs.get('count_bytes', self.size))
+
+ # Offset of the first free byte after this file (first byte of next)
+ self.next_offset = self.size + self.offset
+
+ def dd(self, location):
+ writeexts.Extension.status(msg='Writing %s at %d bytes' %
+ (self.path, self.offset))
+ subprocess.check_call(['dd', 'if=%s' % self.path,
+ 'of=%s' % location, 'bs=1',
+ 'seek=%d' % self.offset,
+ 'skip=%d' % self.skip,
+ 'count=%d' % self.count,
+ 'conv=notrunc'])
+ subprocess.check_call('sync')
diff --git a/extensions/rawdisk.write b/extensions/rawdisk.write
index 49d0a1e8..830d06a5 100755
--- a/extensions/rawdisk.write
+++ b/extensions/rawdisk.write
@@ -17,7 +17,10 @@
'''A Morph deployment write extension for raw disk images.'''
+import contextlib
import os
+import pyfdisk
+import re
import subprocess
import sys
import time
@@ -44,54 +47,64 @@ class RawDiskWriteExtension(writeexts.WriteExtension):
try:
if not self.is_device(location):
with self.created_disk_image(location):
- self.format_btrfs(location)
- self.create_system(temp_root, location)
+ self.create_baserock_system(temp_root, location)
self.status(msg='Disk image has been created at %s' %
location)
else:
- self.format_btrfs(location)
- self.create_system(temp_root, location)
+ self.create_baserock_system(temp_root, location)
self.status(msg='System deployed to %s' % location)
except Exception:
self.status(msg='Failure to deploy system to %s' %
location)
raise
- def upgrade_local_system(self, raw_disk, temp_root):
+ def upgrade_local_system(self, location, temp_root):
self.complete_fstab_for_btrfs_layout(temp_root)
- with self.mount(raw_disk) as mp:
- version_label = self.get_version_label(mp)
- self.status(msg='Updating image to a new version with label %s' %
- version_label)
+ try:
+ with self.mount(location) as mp:
+ self.do_upgrade(mp, temp_root)
+ return
+ except subprocess.CalledProcessError:
+ pass
- version_root = os.path.join(mp, 'systems', version_label)
- os.mkdir(version_root)
+ # At this point, we have failed to mount a raw image, so instead
+ # search for a Baserock root filesystem in the device's partitions
+ with self.find_and_mount_rootfs(location) as mp:
+ self.do_upgrade(mp, temp_root)
- old_orig = os.path.join(mp, 'systems', 'default', 'orig')
- new_orig = os.path.join(version_root, 'orig')
- subprocess.check_call(
- ['btrfs', 'subvolume', 'snapshot', old_orig, new_orig])
+ def do_upgrade(self, mp, temp_root):
+ version_label = self.get_version_label(mp)
+ self.status(msg='Updating image to a new version with label %s' %
+ version_label)
- subprocess.check_call(
- ['rsync', '-a', '--checksum', '--numeric-ids', '--delete',
- temp_root + os.path.sep, new_orig])
+ version_root = os.path.join(mp, 'systems', version_label)
+ os.mkdir(version_root)
- self.create_run(version_root)
+ old_orig = os.path.join(mp, 'systems', 'default', 'orig')
+ new_orig = os.path.join(version_root, 'orig')
+ subprocess.check_call(
+ ['btrfs', 'subvolume', 'snapshot', old_orig, new_orig])
- default_path = os.path.join(mp, 'systems', 'default')
- if os.path.exists(default_path):
- os.remove(default_path)
- else:
- # we are upgrading and old system that does
- # not have an updated extlinux config file
- if self.bootloader_config_is_wanted():
- self.generate_bootloader_config(mp)
- self.install_bootloader(mp)
- os.symlink(version_label, default_path)
+ subprocess.check_call(
+ ['rsync', '-a', '--checksum', '--numeric-ids', '--delete',
+ temp_root + os.path.sep, new_orig])
+ self.create_run(version_root)
+
+ default_path = os.path.join(mp, 'systems', 'default')
+ if os.path.exists(default_path):
+ os.remove(default_path)
+ else:
+ # we are upgrading and old system that does
+ # not have an updated extlinux config file
if self.bootloader_config_is_wanted():
- self.install_kernel(version_root, temp_root)
+ self.generate_bootloader_config(mp)
+ self.install_bootloader(mp)
+ os.symlink(version_label, default_path)
+
+ if self.bootloader_config_is_wanted():
+ self.install_kernel(version_root, temp_root)
def get_version_label(self, mp):
version_label = os.environ.get('VERSION_LABEL')
diff --git a/extensions/writeexts.py b/extensions/writeexts.py
index 000c8270..de7c0f2e 100644
--- a/extensions/writeexts.py
+++ b/extensions/writeexts.py
@@ -18,6 +18,8 @@ import errno
import fcntl
import logging
import os
+import partitioning
+import pyfdisk
import re
import select
import shutil
@@ -228,7 +230,8 @@ class Extension(object):
sys.stdout.write('ERROR: %s\n' % e)
sys.exit(1)
- def status(self, **kwargs):
+ @staticmethod
+ def status(**kwargs):
'''Provide status output.
The ``msg`` keyword argument is the actual message,
@@ -264,12 +267,18 @@ class WriteExtension(Extension):
'Error: Btrfs is required for this deployment, but was not '
'detected in the kernel of the machine that is running Morph.')
- def create_local_system(self, temp_root, raw_disk):
+ def create_local_system(self, temp_root, location):
'''Create a raw system image locally.'''
- with self.created_disk_image(raw_disk):
- self.format_btrfs(raw_disk)
- self.create_system(temp_root, raw_disk)
+ with self.created_disk_image(location):
+ self.create_baserock_system(temp_root, location)
+
+ def create_baserock_system(self, temp_root, location):
+ if self.get_environment_boolean('USE_PARTITIONING', 'no'):
+ self.create_partitioned_system(temp_root, location)
+ else:
+ self.format_btrfs(location)
+ self.create_system(temp_root, location)
@contextlib.contextmanager
def created_disk_image(self, location):
@@ -295,7 +304,7 @@ class WriteExtension(Extension):
try:
self.create_btrfs_system_layout(
temp_root, mp, version_label='factory',
- disk_uuid=self.get_uuid(raw_disk))
+ rootfs_uuid=self.get_uuid(raw_disk))
except BaseException as e:
sys.stderr.write('Error creating Btrfs system layout')
raise
@@ -384,11 +393,20 @@ class WriteExtension(Extension):
else:
raise
- def get_uuid(self, location):
- '''Get the UUID of a block device's file system.'''
- # Requires util-linux blkid; busybox one ignores options and
- # lies by exiting successfully.
- return subprocess.check_output(['blkid', '-s', 'UUID', '-o', 'value',
+ def get_uuid(self, location, offset=0):
+ '''Get the filesystem UUID of a block device's file system.
+
+ Requires util-linux blkid; the busybox version ignores options and
+ lies by exiting successfully.
+
+ Args:
+ location: Path of device or image to inspect
+ offset: A byte offset - which should point to the start of a
+ partition containing a filesystem
+ '''
+
+ return subprocess.check_output(['blkid', '-s', 'UUID', '-o',
+ 'value', '-p', '-O', str(offset),
location]).strip()
@contextlib.contextmanager
@@ -413,7 +431,7 @@ class WriteExtension(Extension):
os.rmdir(mount_point)
def create_btrfs_system_layout(self, temp_root, mountpoint, version_label,
- disk_uuid):
+ rootfs_uuid, device=None):
'''Separate base OS versions from state using subvolumes.
'''
@@ -424,11 +442,9 @@ class WriteExtension(Extension):
os.makedirs(version_root)
os.makedirs(state_root)
- self.create_orig(version_root, temp_root)
- system_dir = os.path.join(version_root, 'orig')
-
+ system_dir = self.create_orig(version_root, temp_root)
state_dirs = self.complete_fstab_for_btrfs_layout(system_dir,
- disk_uuid)
+ rootfs_uuid, device)
for state_dir in state_dirs:
self.create_state_subvolume(system_dir, mountpoint, state_dir)
@@ -444,12 +460,53 @@ class WriteExtension(Extension):
self.install_dtb(version_root, temp_root)
self.install_syslinux_menu(mountpoint, version_root)
if initramfs is not None:
+ # Using initramfs - can boot a rootfs with a filesystem UUID
self.install_initramfs(initramfs, version_root)
- self.generate_bootloader_config(mountpoint, disk_uuid)
+ self.generate_bootloader_config(mountpoint,
+ rootfs_uuid=rootfs_uuid)
else:
- self.generate_bootloader_config(mountpoint)
+ if device:
+ # A partitioned disk or image - boot with partition UUID
+ root_part = device.get_partition_by_mountpoint('/')
+ root_guid = device.get_partition_uuid(root_part)
+ self.generate_bootloader_config(mountpoint,
+ root_guid=root_guid)
+ if self.get_bootloader_install() == 'extlinux':
+ self.install_syslinux_blob(device, system_dir)
+ else:
+ # Unpartitioned and no initramfs - cannot boot with a UUID
+ self.generate_bootloader_config(mountpoint)
self.install_bootloader(mountpoint)
+ if device:
+ self.create_partition_mountpoints(device, system_dir)
+
+ def create_partition_mountpoints(self, device, system_dir):
+ '''Create (or empty) partition mountpoints in the root filesystem
+
+ Delete contents of partition mountpoints in the rootfs to leave an
+ empty mount drectory (files are copied to the actual partition in
+ create_partitioned_system()), or create an empty mount directory in
+ the rootfs if the mount path doesn't exist.
+
+ Args:
+ device: A pyfdisk.py Device object describing the partitioning
+ system_dir: A path to the Baserock rootfs to be modified
+ '''
+
+ for part in device.partitionlist:
+ if hasattr(part, 'mountpoint') and part.mountpoint != '/':
+ part_mount_dir = os.path.join(system_dir,
+ re.sub('^/', '', part.mountpoint))
+ if os.path.exists(part_mount_dir):
+ self.status(msg='Deleting files in mountpoint '
+ 'for %s partition' % part.mountpoint)
+ self.empty_dir(part_mount_dir)
+ else:
+ self.status(msg='Creating empty mount directory '
+ 'for %s partition' % part.mountpoint)
+ os.mkdir(part_mount_dir)
+
def create_orig(self, version_root, temp_root):
'''Create the default "factory" system.'''
@@ -460,6 +517,8 @@ class WriteExtension(Extension):
self.status(msg='Copying files to orig subvolume')
subprocess.check_call(['cp', '-a', temp_root + '/.', orig + '/.'])
+ return orig
+
def create_run(self, version_root):
'''Create the 'run' snapshot.'''
@@ -484,16 +543,41 @@ class WriteExtension(Extension):
os.chmod(subvolume, 0o755)
existing_state_dir = os.path.join(system_dir, state_subdir)
+ self.move_dir_contents(existing_state_dir, subvolume)
+
+ def move_dir_contents(self, source_dir, target_dir):
+ '''Move all files source_dir, to target_dir'''
+
+ n = self.__cmd_files_in_dir(['mv'], source_dir, target_dir)
+ if n:
+ self.status(msg='Moved %d files to %s' % (n, target_dir))
+
+ def copy_dir_contents(self, source_dir, target_dir):
+ '''Copy all files source_dir, to target_dir'''
+
+ n = self.__cmd_files_in_dir(['cp', '-a', '-r'], source_dir, target_dir)
+ if n:
+ self.status(msg='Copied %d files to %s' % (n, target_dir))
+
+ def empty_dir(self, directory):
+ '''Empty the contents of a directory, but not the directory itself'''
+
+ n = self.__cmd_files_in_dir(['rm', '-rf'], directory)
+ if n:
+ self.status(msg='Deleted %d files in %s' % (n, directory))
+
+ def __cmd_files_in_dir(self, cmd, source_dir, target_dir=None):
files = []
- if os.path.exists(existing_state_dir):
- files = os.listdir(existing_state_dir)
- if len(files) > 0:
- self.status(msg='Moving existing data to %s subvolume' % subvolume)
+ if os.path.exists(source_dir):
+ files = os.listdir(source_dir)
for filename in files:
- filepath = os.path.join(existing_state_dir, filename)
- subprocess.check_call(['mv', filepath, subvolume])
+ filepath = os.path.join(source_dir, filename)
+ add_params = [filepath, target_dir] if target_dir else [filepath]
+ subprocess.check_call(cmd + add_params)
+ return len(files)
- def complete_fstab_for_btrfs_layout(self, system_dir, rootfs_uuid=None):
+ def complete_fstab_for_btrfs_layout(self, system_dir,
+ rootfs_uuid=None, device=None):
'''Fill in /etc/fstab entries for the default Btrfs disk layout.
In the future we should move this code out of the write extension and
@@ -521,9 +605,33 @@ class WriteExtension(Extension):
'UUID=%s' % rootfs_uuid)
fstab.add_line('%s / btrfs defaults,rw,noatime 0 1' % root_device)
+ # Add fstab entries for partitions
+ if device:
+ mount_parts = set(p for p in device.partitionlist
+ if hasattr(p, 'mountpoint') and p.mountpoint != '/')
+ part_mountpoints = set(p.mountpoint for p in mount_parts)
+ for part in mount_parts:
+ if part.mountpoint not in existing_mounts:
+ # Get filesystem UUID
+ part_uuid = self.get_uuid(device.location,
+ part.extent.start *
+ device.sector_size)
+ self.status(msg='Adding fstab entry for %s '
+ 'partition' % part.mountpoint)
+ fstab.add_line('UUID=%s %s %s defaults,rw,noatime '
+ '0 2' % (part_uuid, part.mountpoint,
+ part.filesystem))
+ else:
+ self.status(msg='WARNING: an entry already exists in '
+ 'fstab for %s partition, skipping' %
+ part.mountpoint)
+
+ # Add entries for state dirs
state_dirs_to_create = set()
for state_dir in shared_state_dirs:
- if '/' + state_dir not in existing_mounts:
+ mp = '/' + state_dir
+ if (mp not in existing_mounts and
+ (device and mp not in part_mountpoints)):
state_dirs_to_create.add(state_dir)
state_subvol = os.path.join('/state', state_dir)
fstab.add_line(
@@ -601,7 +709,7 @@ class WriteExtension(Extension):
def get_root_device(self):
return os.environ.get('ROOT_DEVICE', '/dev/sda')
- def generate_bootloader_config(self, real_root, disk_uuid=None):
+ def generate_bootloader_config(self, *args, **kwargs):
'''Install extlinux on the newly created disk image.'''
config_function_dict = {
'extlinux': self.generate_extlinux_config,
@@ -609,13 +717,22 @@ class WriteExtension(Extension):
config_type = self.get_bootloader_config_format()
if config_type in config_function_dict:
- config_function_dict[config_type](real_root, disk_uuid)
+ config_function_dict[config_type](*args, **kwargs)
else:
raise ExtensionError(
'Invalid BOOTLOADER_CONFIG_FORMAT %s' % config_type)
- def generate_extlinux_config(self, real_root, disk_uuid=None):
- '''Install extlinux on the newly created disk image.'''
+ def generate_extlinux_config(self, real_root,
+ rootfs_uuid=None, root_guid=None):
+ '''Generate the extlinux configuration file
+
+ Args:
+ real_root: Path to the mounted top level of the root filesystem
+ rootfs_uuid: Specify a filesystem UUID which can be loaded using
+ an initramfs aware of filesystems
+ root_guid: Specify a partition GUID, can be used without an
+ initramfs
+ '''
self.status(msg='Creating extlinux.conf')
config = os.path.join(real_root, 'extlinux.conf')
@@ -631,29 +748,40 @@ class WriteExtension(Extension):
'rootfstype=btrfs ' # required when using initramfs, also boots
# faster when specified without initramfs
'rootflags=subvol=systems/default/run ') # boot runtime subvol
- kernel_args += 'root=%s ' % (self.get_root_device()
- if disk_uuid is None
- else 'UUID=%s' % disk_uuid)
+
+ # See init/do_mounts.c:182 in the kernel source, in the comment above
+ # function name_to_dev_t(), for an explanation of the available
+ # options for the kernel parameter 'root', particularly when using
+ # GUID/UUIDs
+ if rootfs_uuid:
+ root_device = 'UUID=%s' % rootfs_uuid
+ elif root_guid:
+ root_device = 'PARTUUID=%s' % root_guid
+ else:
+ # Fall back to the root partition named in the cluster
+ root_device = self.get_root_device()
+ kernel_args += 'root=%s ' % root_device
+
kernel_args += self.get_extra_kernel_args()
with open(config, 'w') as f:
f.write('default linux\n')
f.write('timeout 1\n')
f.write('label linux\n')
f.write('kernel /systems/default/kernel\n')
- if disk_uuid is not None:
+ if rootfs_uuid is not None:
f.write('initrd /systems/default/initramfs\n')
if self.get_dtb_path() != '':
f.write('devicetree /systems/default/dtb\n')
f.write('append %s\n' % kernel_args)
- def install_bootloader(self, real_root):
+ def install_bootloader(self, *args, **kwargs):
install_function_dict = {
'extlinux': self.install_bootloader_extlinux,
}
install_type = self.get_bootloader_install()
if install_type in install_function_dict:
- install_function_dict[install_type](real_root)
+ install_function_dict[install_type](*args, **kwargs)
elif install_type != 'none':
raise ExtensionError(
'Invalid BOOTLOADER_INSTALL %s' % install_type)
@@ -666,6 +794,33 @@ class WriteExtension(Extension):
subprocess.check_call(['sync'])
time.sleep(2)
+ def install_syslinux_blob(self, device, orig_root):
+ '''Install Syslinux MBR blob
+
+ This is the first stage of boot (for partitioned images) on x86
+ machines. It is not required where there is no partition table. The
+ syslinux bootloader is written to the MBR, and is capable of loading
+ extlinux. This only works when the partition is set as bootable (MBR),
+ or the legacy boot flag is set (GPT). The blob is built with extlinux,
+ and found in the rootfs'''
+
+ pt_format = device.partition_table_format.lower()
+ if pt_format in ('gpb', 'mbr'):
+ blob = 'mbr.bin'
+ elif pt_format == 'gpt':
+ blob = 'gptmbr.bin'
+ blob_name = 'usr/share/syslinux/' + blob
+ self.status(msg='Installing syslinux %s blob' % pt_format.upper())
+ blob_location = os.path.join(orig_root, blob_name)
+ if os.path.exists(blob_location):
+ subprocess.check_call(['dd', 'if=%s' % blob_location,
+ 'of=%s' % device.location,
+ 'bs=440', 'count=1', 'conv=notrunc'])
+ else:
+ raise ExtensionError('MBR blob not found. Is this the correct'
+ 'architecture? The MBR blob will only be built for x86'
+ 'systems. You may wish to configure BOOTLOADER_INSTALL')
+
def install_syslinux_menu(self, real_root, version_root):
'''Make syslinux/extlinux menu binary available.
@@ -712,13 +867,13 @@ class WriteExtension(Extension):
return True
- def get_environment_boolean(self, variable):
+ def get_environment_boolean(self, variable, default='no'):
'''Parse a yes/no boolean passed through the environment.'''
- value = os.environ.get(variable, 'no').lower()
- if value in ['no', '0', 'false']:
+ value = os.environ.get(variable, default).lower()
+ if value in ('no', '0', 'false'):
return False
- elif value in ['yes', '1', 'true']:
+ elif value in ('yes', '1', 'true'):
return True
else:
raise ExtensionError('Unexpected value for %s: %s' %
@@ -744,3 +899,91 @@ class WriteExtension(Extension):
if e.errno == errno.ENOENT:
return False
raise
+
+ def create_partitioned_system(self, temp_root, location):
+ '''Create a Baserock system in a partitioned disk image or device'''
+
+ part_spec = os.environ.get('PARTITION_FILE', 'partitioning/default')
+
+ disk_size = self.get_disk_size()
+ if not disk_size:
+ raise writeexts.ExtensionError('DISK_SIZE is not defined')
+
+ dev = partitioning.do_partitioning(location, disk_size,
+ temp_root, part_spec)
+
+ for part in dev.partitionlist:
+ if not hasattr(part, 'mountpoint'):
+ continue
+ if part.mountpoint == '/':
+ # Re-format the rootfs, to include needed extra features
+ with pyfdisk.create_loopback(location,
+ part.extent.start *
+ dev.sector_size, part.size) as l:
+ self.mkfs_btrfs(l)
+
+ self.status(msg='Mounting partition %d' % part.number)
+ offset = part.extent.start * dev.sector_size
+ with self.mount_partition(location,
+ offset, part.size) as part_mount_dir:
+ if part.mountpoint == '/':
+ # Install root filesystem
+ rfs_uuid = self.get_uuid(location, part.extent.start *
+ dev.sector_size)
+ self.create_btrfs_system_layout(temp_root, part_mount_dir,
+ 'factory', rfs_uuid, dev)
+ else:
+ # Copy files to partition from unpacked rootfs
+ src_dir = os.path.join(temp_root,
+ re.sub('^/', '', part.mountpoint))
+ self.status(msg='Copying files to %s partition' %
+ part.mountpoint)
+ self.copy_dir_contents(src_dir, part_mount_dir)
+
+ # Write raw files to disk with dd
+ partitioning.process_raw_files(dev, temp_root)
+
+ @contextlib.contextmanager
+ def mount_partition(self, location, offset_bytes, size_bytes):
+ '''Mount a partition in a partitioned device or image'''
+
+ with pyfdisk.create_loopback(location, offset=offset_bytes,
+ size=size_bytes) as loop:
+ with self.mount(loop) as mountpoint:
+ yield mountpoint
+
+ @contextlib.contextmanager
+ def find_and_mount_rootfs(self, location):
+ '''
+ Mount a Baserock rootfs inside a partitioned device or image
+
+ This function searches a disk image or device, with unknown
+ partitioning scheme, for a Baserock rootfs. This is done by finding
+ offsets and sizes of partitions in the partition table, mounting each
+ partition, and checking whether a known path exists in the mount.
+
+ Args:
+ location: the location of the disk image or device to search
+ Returns:
+ A path to the mount point of the mounted Baserock rootfs
+ '''
+
+ if pyfdisk.get_pt_type(location) == 'none':
+ with self.mount(location) as mountpoint:
+ yield mountpoint
+
+ sector_size = pyfdisk.get_sector_size(location)
+ partn_sizes = pyfdisk.get_partition_sector_sizes(location)
+ for i, offset in enumerate(pyfdisk.get_partition_offsets(location)):
+ try:
+ with self.mount_partition(location, offset * sector_size,
+ partn_sizes[i] * sector_size) as mp:
+ path = os.path.join(mp, 'systems/default/orig/baserock')
+ if os.path.exists(path):
+ self.status(msg='Found a Baserock rootfs at '
+ 'offset %d sectors/%d bytes' %
+ (offset, offset * sector_size))
+ yield mp
+ except BaseException:
+ # Probably a partition without a filesystem, carry on
+ pass