diff --git a/diskimage_builder/block_device/blockdevice.py b/diskimage_builder/block_device/blockdevice.py index 30c2172d2..4e404bddf 100644 --- a/diskimage_builder/block_device/blockdevice.py +++ b/diskimage_builder/block_device/blockdevice.py @@ -307,18 +307,26 @@ class BlockDevice(object): # been dumped; i.e. after cmd_create() called. state = BlockDeviceState(self.state_json_file_name) - if symbol == 'image-block-partition': - # If there is no partition needed, pass back directly the - # image. - if 'root' in state['blockdev']: - print("%s" % state['blockdev']['root']['device']) - else: - print("%s" % state['blockdev']['image0']['device']) - return 0 + # The path to the .raw file for conversion if symbol == 'image-path': print("%s" % state['blockdev']['image0']['image']) return 0 + # This is the loopback device where the above image is setup + if symbol == 'image-block-device': + print("%s" % state['blockdev']['image0']['device']) + return 0 + + # Full list of created devices by name. Some bootloaders, for + # example, want to be able to see their boot partitions to + # copy things in. Intended to be read into a bash array + if symbol == 'image-block-devices': + out = "" + for k, v in state['blockdev'].items(): + out += " [%s]=%s " % (k, v['device']) + print(out) + return 0 + logger.error("Invalid symbol [%s] for getval", symbol) return 1 diff --git a/diskimage_builder/block_device/level1/partition.py b/diskimage_builder/block_device/level1/partition.py index c6f812c5d..e91e1bf98 100644 --- a/diskimage_builder/block_device/level1/partition.py +++ b/diskimage_builder/block_device/level1/partition.py @@ -65,5 +65,15 @@ class PartitionNode(NodeBase): edge_from.append(self.prev_partition.name) return (edge_from, edge_to) + # These all call back to the parent "partitioning" object to do + # the real work. Every node calls it, but only one will succeed; + # see the gating we do in the parent function. + # + # XXX: A better model here would be for the parent object to a + # real node in the config graph, so it's create() gets called. + # These can then just be stubs. def create(self): self.partitioning.create() + + def cleanup(self): + self.partitioning.cleanup() diff --git a/diskimage_builder/block_device/level1/partitioning.py b/diskimage_builder/block_device/level1/partitioning.py index ff2b2bc74..f7fd07ced 100644 --- a/diskimage_builder/block_device/level1/partitioning.py +++ b/diskimage_builder/block_device/level1/partitioning.py @@ -15,8 +15,6 @@ import logging import os -from subprocess import CalledProcessError - from diskimage_builder.block_device.exception import \ BlockDeviceSetupException from diskimage_builder.block_device.level1.mbr import MBR @@ -45,6 +43,7 @@ class Partitioning(PluginBase): # within one object, there is the need to store a flag if the # creation of the partitions was already done. self.already_created = False + self.already_cleaned = False # Parameter check if 'base' not in config: @@ -94,52 +93,10 @@ class Partitioning(PluginBase): fd.seek(0, 2) return fd.tell() - def _all_part_devices_exist(self, expected_part_devices): - for part_device in expected_part_devices: - logger.debug("Checking if partition device [%s] exists", - part_device) - if not os.path.exists(part_device): - logger.info("Partition device [%s] does not exists", - part_device) - return False - logger.debug("Partition already exists [%s]", part_device) - return True - - def _notify_os_of_partition_changes(self, device_path, partition_devices): - """Notify of of partition table changes - - There is the need to call some programs to inform the operating - system of partition tables changes. - These calls are highly distribution and version specific. Here - a couple of different methods are used to get the best result. - """ - try: - exec_sudo(["partprobe", device_path]) - exec_sudo(["udevadm", "settle"]) - except CalledProcessError as e: - logger.info("Ignoring settling failure: %s", e) - pass - - if self._all_part_devices_exist(partition_devices): - return - # If running inside Docker, make our nodes manually, because udev - # will not be working. - if os.path.exists("/.dockerenv"): - # kpartx cannot run in sync mode in docker. - exec_sudo(["kpartx", "-av", device_path]) - exec_sudo(["dmsetup", "--noudevsync", "mknodes"]) - return - - exec_sudo(["kpartx", "-avs", device_path]) - + # not this is NOT a node and this is not called directly! The + # create() calls in the partition nodes this plugin has + # created are calling back into this. def create(self): - # not this is NOT a node and this is not called directly! The - # create() calls in the partition nodes this plugin has - # created are calling back into this. - image_path = self.state['blockdev'][self.base]['image'] - device_path = self.state['blockdev'][self.base]['device'] - logger.info("Creating partition on [%s] [%s]", self.base, image_path) - # This is a bit of a hack. Each of the partitions is actually # in the graph, so for every partition we get a create() call # as the walk happens. But we only need to create the @@ -147,10 +104,16 @@ class Partitioning(PluginBase): if self.already_created: logger.info("Not creating the partitions a second time.") return + self.already_created = True + + # the raw file on disk + image_path = self.state['blockdev'][self.base]['image'] + # the /dev/loopX device of the parent + device_path = self.state['blockdev'][self.base]['device'] + logger.info("Creating partition on [%s] [%s]", self.base, image_path) assert self.label == 'mbr' - partition_devices = set() disk_size = self._size_of_block_dev(image_path) with MBR(image_path, disk_size, self.align) as part_impl: for part_cfg in self.partitions: @@ -170,11 +133,36 @@ class Partitioning(PluginBase): part_size, part_type) logger.debug("Create partition [%s] [%d]", part_name, part_no) - partition_device_name = device_path + "p%d" % part_no + + # We're going to mount all partitions with kpartx + # below once we're done. So the device this partition + # will be seen at becomes "/dev/mapper/loop0pX" + assert device_path[:5] == "/dev/" + partition_device_name = "/dev/mapper/%sp%d" % \ + (device_path[5:], part_no) self.state['blockdev'][part_name] \ = {'device': partition_device_name} - partition_devices.add(partition_device_name) - self.already_created = True - self._notify_os_of_partition_changes(device_path, partition_devices) + # now all the partitions are created, get device-mapper to + # mount them + if not os.path.exists("/.dockerenv"): + exec_sudo(["kpartx", "-avs", device_path]) + else: + # If running inside Docker, make our nodes manually, + # because udev will not be working. kpartx cannot run in + # sync mode in docker. + exec_sudo(["kpartx", "-av", device_path]) + exec_sudo(["dmsetup", "--noudevsync", "mknodes"]) + return + + def cleanup(self): + # remove the partition mappings made for the parent + # block-device by create() above. this is called from the + # child PartitionNode umount/delete/cleanup. Thus every + # partition calls it, but we only want to do it once and our + # gate. + if not self.already_cleaned: + self.already_cleaned = True + exec_sudo(["kpartx", "-d", + self.state['blockdev'][self.base]['device']]) diff --git a/diskimage_builder/elements/bootloader/finalise.d/50-bootloader b/diskimage_builder/elements/bootloader/finalise.d/50-bootloader index 6b956f80d..80472156a 100755 --- a/diskimage_builder/elements/bootloader/finalise.d/50-bootloader +++ b/diskimage_builder/elements/bootloader/finalise.d/50-bootloader @@ -9,8 +9,11 @@ fi set -eu set -o pipefail -PART_DEV=$IMAGE_BLOCK_DEVICE -BOOT_DEV=$IMAGE_BLOCK_DEVICE_WITHOUT_PART +BOOT_DEV=$IMAGE_BLOCK_DEVICE + +# All available devices, handy for some bootloaders... +declare -A DEVICES +eval DEVICES=( $IMAGE_BLOCK_DEVICES ) function install_extlinux { install-packages -m bootloader extlinux diff --git a/diskimage_builder/lib/common-functions b/diskimage_builder/lib/common-functions index c99c4747a..4be4bb675 100644 --- a/diskimage_builder/lib/common-functions +++ b/diskimage_builder/lib/common-functions @@ -239,22 +239,6 @@ function run_d() { check_break after-$1 bash } -function detach_loopback() { - local loopdev=$1 - - # Remove the map if it exists - # If setup on a rhel or derivative the map was created with kpartx not losetup - # and subsequently needs to be removed. - loopdev_name=$(echo $loopdev | sed 's/\/dev\///g') - - if sudo dmsetup ls | grep $loopdev_name; then - mapper_name=$(sudo dmsetup ls | grep $loopdev_name | awk '{ print $1 }') - sudo dmsetup --noudevsync remove $mapper_name - fi - - return 0 -} - function arg_to_elements() { for arg do IMAGE_ELEMENT="$IMAGE_ELEMENT $arg" ; done diff --git a/diskimage_builder/lib/disk-image-create b/diskimage_builder/lib/disk-image-create index 88c64ed32..ff3dcebec 100644 --- a/diskimage_builder/lib/disk-image-create +++ b/diskimage_builder/lib/disk-image-create @@ -413,21 +413,28 @@ if [ -z ${IMAGE_BLOCK_DEVICE} ] ; then # values to dib-block-device: using the YAML config and dib-block-device create - # It's called 'DEVICE' but it's the partition. - IMAGE_BLOCK_DEVICE=$(dib-block-device getval image-block-partition) + # This is the device (/dev/loopX). It's where to install the + # bootloader. + IMAGE_BLOCK_DEVICE=$(dib-block-device getval image-block-device) + export IMAGE_BLOCK_DEVICE + + # Similar to above, but all mounted devices. This is handy for + # some bootloaders that have multi-partition layouts and want to + # copy things to different places other than just + # IMAGE_BLOCK_DEVICE. "eval" this into an array as needed + IMAGE_BLOCK_DEVICES=$(dib-block-device getval image-block-devices) + export IMAGE_BLOCK_DEVICES # Write the fstab dib-block-device writefstab fi -export IMAGE_BLOCK_DEVICE + +# XXX: needed? LOOPDEV=${IMAGE_BLOCK_DEVICE} -IMAGE_BLOCK_DEVICE_WITHOUT_PART=$(echo ${IMAGE_BLOCK_DEVICE} \ - | sed -e "s|^\(.*loop[0-9]*\)p[0-9]*$|\1|g") -export IMAGE_BLOCK_DEVICE_WITHOUT_PART - -export EXTRA_DETACH="detach_loopback ${IMAGE_BLOCK_DEVICE_WITHOUT_PART}" -export EXTRA_UNMOUNT="dib-block-device cleanup" +# At this point, dib-block-device has created the raw image file +# (IMAGE_BLOCK_DEVICE) and mounted all the partitions under +# $TMP_BUILD_DIR/mnt for us. We can now copy into the final image. # 'mv' is not usable here - especially when a top level directory # has the same name as a mount point of a partition. If so, 'mv' @@ -480,14 +487,15 @@ fi # Unmount and cleanup the /mnt and /build subdirectories, to save # space before converting the image to some other format. +# XXX ? needed? export EXTRA_UNMOUNT="" unmount_image TMP_IMAGE_PATH=$(dib-block-device getval image-path) export TMP_IMAGE_PATH +# remove all mounts dib-block-device umount - dib-block-device cleanup cleanup_build_dir