Pass all blockdevices to bootloader

Currently we only export "image-block-device" which is the loopback
device (/dev/loopX) for the underlying image.  This is the device we
install grub to (from inside the chroot ...)

This is ok for x86, but is insufficient for some platforms like PPC
which have a separate boot partition.  They do not want to install to
the loop device, but do things like dd special ELF files into special
boot partitions.

The first problem seems to be that in level1/partitioning.py we have a
whole bunch of different paths that either call partprobe on the loop
device, or kpartx.  We have _all_part_devices_exist() that gates the
kpartx for unknown reasons.  We have detach_loopback() that does not
seem to remove losetup created devices.  I don't think this does
cleanup if it uses kpartx correctly.  It is extremley unclear what's
going to be mapped where.

This moves to us *only* using kpartx to map the partitions of the loop
device.  We will *not* call partprobe and create the /dev/loopXpN
devices and will only have the devicemapper nodes kpartx creates.
This seems to be best.  Cleanup happens inside partitioning.py.
practice.  Deeper thinking about this, and more cleanup of the
variables will be welcome.

This adds "image-block-devices" (note the extra "s") which exports all
the block devices with name and path.  This is in a string format that
can be eval'd to an array (you can't export arrays).

This is then used in a follow-on
(I0918e8df8797d6dbabf7af618989ab7f79ee9580) to pick the right
partition on PPC.

Change-Id: If8e33106b4104da2d56d7941ce96ffcb014907bc
This commit is contained in:
Ian Wienand 2017-06-06 12:09:24 +10:00
parent 1d1e4ccb3e
commit 6c394f5746
6 changed files with 89 additions and 88 deletions

View File

@ -307,18 +307,26 @@ class BlockDevice(object):
# been dumped; i.e. after cmd_create() called. # been dumped; i.e. after cmd_create() called.
state = BlockDeviceState(self.state_json_file_name) state = BlockDeviceState(self.state_json_file_name)
if symbol == 'image-block-partition': # The path to the .raw file for conversion
# 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
if symbol == 'image-path': if symbol == 'image-path':
print("%s" % state['blockdev']['image0']['image']) print("%s" % state['blockdev']['image0']['image'])
return 0 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) logger.error("Invalid symbol [%s] for getval", symbol)
return 1 return 1

View File

@ -65,5 +65,15 @@ class PartitionNode(NodeBase):
edge_from.append(self.prev_partition.name) edge_from.append(self.prev_partition.name)
return (edge_from, edge_to) 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): def create(self):
self.partitioning.create() self.partitioning.create()
def cleanup(self):
self.partitioning.cleanup()

View File

@ -15,8 +15,6 @@
import logging import logging
import os import os
from subprocess import CalledProcessError
from diskimage_builder.block_device.exception import \ from diskimage_builder.block_device.exception import \
BlockDeviceSetupException BlockDeviceSetupException
from diskimage_builder.block_device.level1.mbr import MBR 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 # within one object, there is the need to store a flag if the
# creation of the partitions was already done. # creation of the partitions was already done.
self.already_created = False self.already_created = False
self.already_cleaned = False
# Parameter check # Parameter check
if 'base' not in config: if 'base' not in config:
@ -94,52 +93,10 @@ class Partitioning(PluginBase):
fd.seek(0, 2) fd.seek(0, 2)
return fd.tell() 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])
def create(self):
# not this is NOT a node and this is not called directly! The # not this is NOT a node and this is not called directly! The
# create() calls in the partition nodes this plugin has # create() calls in the partition nodes this plugin has
# created are calling back into this. # created are calling back into this.
image_path = self.state['blockdev'][self.base]['image'] def create(self):
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 # 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 # in the graph, so for every partition we get a create() call
# as the walk happens. But we only need to create the # as the walk happens. But we only need to create the
@ -147,10 +104,16 @@ class Partitioning(PluginBase):
if self.already_created: if self.already_created:
logger.info("Not creating the partitions a second time.") logger.info("Not creating the partitions a second time.")
return 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' assert self.label == 'mbr'
partition_devices = set()
disk_size = self._size_of_block_dev(image_path) disk_size = self._size_of_block_dev(image_path)
with MBR(image_path, disk_size, self.align) as part_impl: with MBR(image_path, disk_size, self.align) as part_impl:
for part_cfg in self.partitions: for part_cfg in self.partitions:
@ -170,11 +133,36 @@ class Partitioning(PluginBase):
part_size, part_type) part_size, part_type)
logger.debug("Create partition [%s] [%d]", logger.debug("Create partition [%s] [%d]",
part_name, part_no) 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] \ self.state['blockdev'][part_name] \
= {'device': partition_device_name} = {'device': partition_device_name}
partition_devices.add(partition_device_name)
self.already_created = True # now all the partitions are created, get device-mapper to
self._notify_os_of_partition_changes(device_path, partition_devices) # 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 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']])

View File

@ -9,8 +9,11 @@ fi
set -eu set -eu
set -o pipefail set -o pipefail
PART_DEV=$IMAGE_BLOCK_DEVICE BOOT_DEV=$IMAGE_BLOCK_DEVICE
BOOT_DEV=$IMAGE_BLOCK_DEVICE_WITHOUT_PART
# All available devices, handy for some bootloaders...
declare -A DEVICES
eval DEVICES=( $IMAGE_BLOCK_DEVICES )
function install_extlinux { function install_extlinux {
install-packages -m bootloader extlinux install-packages -m bootloader extlinux

View File

@ -239,22 +239,6 @@ function run_d() {
check_break after-$1 bash 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() { function arg_to_elements() {
for arg do IMAGE_ELEMENT="$IMAGE_ELEMENT $arg" ; done for arg do IMAGE_ELEMENT="$IMAGE_ELEMENT $arg" ; done

View File

@ -413,21 +413,28 @@ if [ -z ${IMAGE_BLOCK_DEVICE} ] ; then
# values to dib-block-device: using the YAML config and # values to dib-block-device: using the YAML config and
dib-block-device create dib-block-device create
# It's called 'DEVICE' but it's the partition. # This is the device (/dev/loopX). It's where to install the
IMAGE_BLOCK_DEVICE=$(dib-block-device getval image-block-partition) # 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 # Write the fstab
dib-block-device writefstab dib-block-device writefstab
fi fi
export IMAGE_BLOCK_DEVICE
# XXX: needed?
LOOPDEV=${IMAGE_BLOCK_DEVICE} LOOPDEV=${IMAGE_BLOCK_DEVICE}
IMAGE_BLOCK_DEVICE_WITHOUT_PART=$(echo ${IMAGE_BLOCK_DEVICE} \ # At this point, dib-block-device has created the raw image file
| sed -e "s|^\(.*loop[0-9]*\)p[0-9]*$|\1|g") # (IMAGE_BLOCK_DEVICE) and mounted all the partitions under
export IMAGE_BLOCK_DEVICE_WITHOUT_PART # $TMP_BUILD_DIR/mnt for us. We can now copy into the final image.
export EXTRA_DETACH="detach_loopback ${IMAGE_BLOCK_DEVICE_WITHOUT_PART}"
export EXTRA_UNMOUNT="dib-block-device cleanup"
# 'mv' is not usable here - especially when a top level directory # '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' # 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 # Unmount and cleanup the /mnt and /build subdirectories, to save
# space before converting the image to some other format. # space before converting the image to some other format.
# XXX ? needed?
export EXTRA_UNMOUNT="" export EXTRA_UNMOUNT=""
unmount_image unmount_image
TMP_IMAGE_PATH=$(dib-block-device getval image-path) TMP_IMAGE_PATH=$(dib-block-device getval image-path)
export TMP_IMAGE_PATH export TMP_IMAGE_PATH
# remove all mounts
dib-block-device umount dib-block-device umount
dib-block-device cleanup dib-block-device cleanup
cleanup_build_dir cleanup_build_dir