Example #1
0
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)
Example #3
0
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)
Example #4
0
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)
Example #5
0
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)