def get_efi_disk_part(devices): for disk in devices: (parent, partnum) = block.get_blockdev_for_partition(disk) if partnum: return (parent, partnum) return (None, None)
def get_block_devices(target): """Returns list of block devices for the given target.""" devs = block.get_devices_for_mp(target) blockdevs = set() for maybepart in devs: (blockdev, part) = block.get_blockdev_for_partition(maybepart) blockdevs.add(blockdev) return list(blockdevs)
def wipe_superblock(device): """ Wrapper for block.wipe_volume compatible with shutdown function interface """ blockdev = block.sysfs_to_devpath(device) # when operating on a disk that used to have a dos part table with an # extended partition, attempting to wipe the extended partition will fail try: if not block.is_online(blockdev): LOG.debug("Device is not online (size=0), so skipping:" " '%s'", blockdev) return if block.is_extended_partition(blockdev): LOG.info( "extended partitions do not need wiping, so skipping:" " '%s'", blockdev) return except OSError as e: if util.is_file_not_found_exc(e): LOG.debug('Device to wipe disappeared: %s', e) LOG.debug('/proc/partitions says: %s', util.load_file('/proc/partitions')) (parent, partnum) = block.get_blockdev_for_partition(blockdev) out, _e = util.subp(['sfdisk', '-d', parent], capture=True, combine_capture=True) LOG.debug('Disk partition info:\n%s', out) return else: raise e # gather any partitions partitions = block.get_sysfs_partitions(device) # release zfs member by exporting the pool if zfs.zfs_supported() and block.is_zfs_member(blockdev): poolname = zfs.device_to_poolname(blockdev) # only export pools that have been imported if poolname in zfs.zpool_list(): try: zfs.zpool_export(poolname) except util.ProcessExecutionError as e: LOG.warning('Failed to export zpool "%s": %s', poolname, e) if is_swap_device(blockdev): shutdown_swap(blockdev) # some volumes will be claimed by the bcache layer but do not surface # an actual /dev/bcacheN device which owns the parts (backing, cache) # The result is that some volumes cannot be wiped while bcache claims # the device. Resolve this by stopping bcache layer on those volumes # if present. for bcache_path in ['bcache', 'bcache/set']: stop_path = os.path.join(device, bcache_path) if os.path.exists(stop_path): LOG.debug('Attempting to release bcache layer from device: %s:%s', device, stop_path) if stop_path.endswith('set'): rp = os.path.realpath(stop_path) bcache.stop_cacheset(rp) else: bcache._stop_device(stop_path) _wipe_superblock(blockdev) # if we had partitions, make sure they've been removed if partitions: LOG.debug('%s had partitions, issuing partition reread', device) retries = [.5, .5, 1, 2, 5, 7] for attempt, wait in enumerate(retries): try: # only rereadpt on wiped device block.rescan_block_devices(devices=[blockdev]) # may raise IOError, OSError due to wiped partition table curparts = block.get_sysfs_partitions(device) if len(curparts) == 0: return except (IOError, OSError): if attempt + 1 >= len(retries): raise LOG.debug( "%s partitions still present, rereading pt" " (%s/%s). sleeping %ss before retry", device, attempt + 1, len(retries), wait) time.sleep(wait) # multipath partitions are separate block devices (disks) if multipath.is_mpath_partition(blockdev): multipath.remove_partition(blockdev) # multipath devices must be hidden to utilize a single member (path) elif multipath.is_mpath_device(blockdev): mp_id = multipath.find_mpath_id(blockdev) if mp_id: multipath.remove_map(mp_id) else: raise RuntimeError('Failed to find multipath id for %s' % blockdev)
def detect_and_handle_multipath(cfg, target): DEFAULT_MULTIPATH_PACKAGES = ['multipath-tools-boot'] mpcfg = cfg.get('multipath', {}) mpmode = mpcfg.get('mode', 'auto') mppkgs = mpcfg.get('packages', DEFAULT_MULTIPATH_PACKAGES) mpbindings = mpcfg.get('overwrite_bindings', True) if isinstance(mppkgs, str): mppkgs = [mppkgs] if mpmode == 'disabled': return if mpmode == 'auto' and not block.detect_multipath(target): return LOG.info("Detected multipath devices. Installing support via %s", mppkgs) util.install_packages(mppkgs, target=target) replace_spaces = True try: # check in-target version pkg_ver = util.get_package_version('multipath-tools', target=target) LOG.debug("get_package_version:\n%s", pkg_ver) LOG.debug("multipath version is %s (major=%s minor=%s micro=%s)", pkg_ver['semantic_version'], pkg_ver['major'], pkg_ver['minor'], pkg_ver['micro']) # multipath-tools versions < 0.5.0 do _NOT_ want whitespace replaced # i.e. 0.4.X in Trusty. if pkg_ver['semantic_version'] < 500: replace_spaces = False except Exception as e: LOG.warn( "failed reading multipath-tools version, " "assuming it wants no spaces in wwids: %s", e) multipath_cfg_path = os.path.sep.join([target, '/etc/multipath.conf']) multipath_bind_path = os.path.sep.join([target, '/etc/multipath/bindings']) # We don't want to overwrite multipath.conf file provided by the image. if not os.path.isfile(multipath_cfg_path): # Without user_friendly_names option enabled system fails to boot # if any of the disks has spaces in its name. Package multipath-tools # has bug opened for this issue (LP: 1432062) but it was not fixed yet. multipath_cfg_content = '\n'.join([ '# This file was created by curtin while installing the system.', 'defaults {', ' user_friendly_names yes', '}', '' ]) util.write_file(multipath_cfg_path, content=multipath_cfg_content) if mpbindings or not os.path.isfile(multipath_bind_path): # we do assume that get_devices_for_mp()[0] is / target_dev = block.get_devices_for_mp(target)[0] wwid = block.get_scsi_wwid(target_dev, replace_whitespace=replace_spaces) blockdev, partno = block.get_blockdev_for_partition(target_dev) mpname = "mpath0" grub_dev = "/dev/mapper/" + mpname if partno is not None: grub_dev += "-part%s" % partno LOG.debug("configuring multipath install for root=%s wwid=%s", grub_dev, wwid) multipath_bind_content = '\n'.join([ '# This file was created by curtin while installing the system.', "%s %s" % (mpname, wwid), '# End of content generated by curtin.', '# Everything below is maintained by multipath subsystem.', '' ]) util.write_file(multipath_bind_path, content=multipath_bind_content) grub_cfg = os.path.sep.join( [target, '/etc/default/grub.d/50-curtin-multipath.cfg']) msg = '\n'.join([ '# Written by curtin for multipath device wwid "%s"' % wwid, 'GRUB_DEVICE=%s' % grub_dev, 'GRUB_DISABLE_LINUX_UUID=true', '' ]) util.write_file(grub_cfg, content=msg) else: LOG.warn("Not sure how this will boot") # Initrams needs to be updated to include /etc/multipath.cfg # and /etc/multipath/bindings files. update_initramfs(target, all_kernels=True)
def setup_grub(cfg, target): # target is the path to the mounted filesystem # FIXME: these methods need moving to curtin.block # and using them from there rather than commands.block_meta from curtin.commands.block_meta import (extract_storage_ordered_dict, get_path_to_storage_volume) grubcfg = cfg.get('grub', {}) # copy legacy top level name if 'grub_install_devices' in cfg and 'install_devices' not in grubcfg: grubcfg['install_devices'] = cfg['grub_install_devices'] LOG.debug("setup grub on target %s", target) # if there is storage config, look for devices tagged with 'grub_device' storage_cfg_odict = None try: storage_cfg_odict = extract_storage_ordered_dict(cfg) except ValueError: pass if storage_cfg_odict: storage_grub_devices = [] for item_id, item in storage_cfg_odict.items(): if not item.get('grub_device'): continue LOG.debug("checking: %s", item) storage_grub_devices.append( get_path_to_storage_volume(item_id, storage_cfg_odict)) if len(storage_grub_devices) > 0: grubcfg['install_devices'] = storage_grub_devices LOG.debug("install_devices: %s", grubcfg.get('install_devices')) if 'install_devices' in grubcfg: instdevs = grubcfg.get('install_devices') if isinstance(instdevs, str): instdevs = [instdevs] if instdevs is None: LOG.debug("grub installation disabled by config") else: # If there were no install_devices found then we try to do the right # thing. That right thing is basically installing on all block # devices that are mounted. On powerpc, though it means finding PrEP # partitions. devs = block.get_devices_for_mp(target) blockdevs = set() for maybepart in devs: try: (blockdev, part) = block.get_blockdev_for_partition(maybepart) blockdevs.add(blockdev) except ValueError: # if there is no syspath for this device such as a lvm # or raid device, then a ValueError is raised here. LOG.debug("failed to find block device for %s", maybepart) if platform.machine().startswith("ppc64"): # assume we want partitions that are 4100 (PReP). The snippet here # just prints the partition number partitions of that type. shnip = textwrap.dedent(""" export LANG=C; for d in "$@"; do sgdisk "$d" --print | awk '$6 == prep { print d $1 }' "d=$d" prep=4100 done """) try: out, err = util.subp(['sh', '-c', shnip, '--'] + list(blockdevs), capture=True) instdevs = str(out).splitlines() if not instdevs: LOG.warn("No power grub target partitions found!") instdevs = None except util.ProcessExecutionError as e: LOG.warn("Failed to find power grub partitions: %s", e) instdevs = None else: instdevs = list(blockdevs) # UEFI requires grub-efi-{arch}. If a signed version of that package # exists then it will be installed. if util.is_uefi_bootable(): arch = util.get_architecture() pkgs = ['grub-efi-%s' % arch] # Architecture might support a signed UEFI loader uefi_pkg_signed = 'grub-efi-%s-signed' % arch if util.has_pkg_available(uefi_pkg_signed): pkgs.append(uefi_pkg_signed) # AMD64 has shim-signed for SecureBoot support if arch == "amd64": pkgs.append("shim-signed") # Install the UEFI packages needed for the architecture util.install_packages(pkgs, target=target) env = os.environ.copy() replace_default = grubcfg.get('replace_linux_default', True) if str(replace_default).lower() in ("0", "false"): env['REPLACE_GRUB_LINUX_DEFAULT'] = "0" else: env['REPLACE_GRUB_LINUX_DEFAULT'] = "1" if instdevs: instdevs = [block.get_dev_name_entry(i)[1] for i in instdevs] else: instdevs = ["none"] if util.is_uefi_bootable() and grubcfg.get('update_nvram', True): uefi_remove_old_loaders(grubcfg, target) LOG.debug("installing grub to %s [replace_default=%s]", instdevs, replace_default) with util.ChrootableTarget(target): args = ['install-grub'] if util.is_uefi_bootable(): args.append("--uefi") if grubcfg.get('update_nvram', True): LOG.debug("GRUB UEFI enabling NVRAM updates") args.append("--update-nvram") else: LOG.debug("NOT enabling UEFI nvram updates") LOG.debug("Target system may not boot") args.append(target) # capture stdout and stderr joined. join_stdout_err = ['sh', '-c', 'exec "$0" "$@" 2>&1'] out, _err = util.subp(join_stdout_err + args + instdevs, env=env, capture=True) LOG.debug("%s\n%s\n", args, out) if util.is_uefi_bootable() and grubcfg.get('update_nvram', True): uefi_reorder_loaders(grubcfg, target)