[PATCHv2] Move autopart and installation-specific code outside of __init__.py
David Lehman
dlehman at redhat.com
Fri Feb 13 18:32:49 UTC 2015
On 02/13/2015 03:39 AM, Vratislav Podzimek wrote:
> This code belongs to separate modules.
>
> <not-in-commit-msg>
> The difference from the first version of the patch is that the
> turnOnFilesystems and writeEscrowPackets functions were moved to the
> blivet/osinstall.py module and both osinstall.py and autopart.py now
> have the right author mentioned in the header.
>
> I'm not sending a new version of the related anaconda as it only has a
> single change of import from blivet (turnOnFilesystems) added on top
> of the old patch.
>
> Both pylint and tests are okay in both anaconda and blivet.
> </not-in-commit-msg>
>
> Signed-off-by: Vratislav Podzimek <vpodzime at redhat.com>
ACK.
> ---
> blivet/__init__.py | 1083 +-------------------------------------------
> blivet/autopart.py | 483 ++++++++++++++++++++
> blivet/devicelibs/swap.py | 67 ---
> blivet/osinstall.py | 1107 +++++++++++++++++++++++++++++++++++++++++++++
> blivet/partitioning.py | 386 +---------------
> 5 files changed, 1602 insertions(+), 1524 deletions(-)
> create mode 100644 blivet/autopart.py
> create mode 100644 blivet/osinstall.py
>
> diff --git a/blivet/__init__.py b/blivet/__init__.py
> index 3c12afd..8e1c0bd 100644
> --- a/blivet/__init__.py
> +++ b/blivet/__init__.py
> @@ -50,32 +50,22 @@ get_bootloader = lambda: None
>
> import os
> import time
> -import stat
> import copy
> import tempfile
> -import shlex
> import re
>
> -try:
> - import nss.nss
> -except ImportError:
> - nss = None
> -
> import parted
>
> from pykickstart.constants import AUTOPART_TYPE_LVM, CLEARPART_TYPE_ALL, CLEARPART_TYPE_LINUX, CLEARPART_TYPE_LIST, CLEARPART_TYPE_NONE
>
> from .storage_log import log_exception_info, log_method_call
> -from .errors import DeviceError, FSResizeError, FSTabTypeMismatchError, UnknownSourceDeviceError, StorageError, UnrecognizedFSTabEntryError
> -from .devices import BTRFSDevice, BTRFSSubVolumeDevice, BTRFSVolumeDevice, DirectoryDevice, FileDevice, LVMLogicalVolumeDevice, LVMThinLogicalVolumeDevice, LVMThinPoolDevice, LVMVolumeGroupDevice, MDRaidArrayDevice, NetworkStorageDevice, NFSDevice, NoDevice, OpticalDevice, PartitionDevice, TmpFSDevice, devicePathToName
> +from .errors import UnknownSourceDeviceError, StorageError
> +from .devices import BTRFSDevice, BTRFSSubVolumeDevice, BTRFSVolumeDevice, LVMLogicalVolumeDevice, LVMThinLogicalVolumeDevice, LVMThinPoolDevice, LVMVolumeGroupDevice, MDRaidArrayDevice, PartitionDevice, TmpFSDevice, devicePathToName
> from .devicetree import DeviceTree
> from .deviceaction import ActionCreateDevice, ActionCreateFormat, ActionDestroyDevice, ActionDestroyFormat, ActionResizeDevice, ActionResizeFormat
> from .formats import getFormat
> -from .formats import get_device_format_class
> from .formats import get_default_filesystem_type
> from . import devicefactory
> -from .devicelibs.dm import name_from_dm_node
> -from .devicelibs.crypto import generateBackupPassphrase
> from .devicelibs.edd import get_edd_dict
> from .devicelibs.dasd import make_dasd_list, write_dasd_conf
> from . import iscsi
> @@ -184,73 +174,6 @@ def storageInitialize(storage, ksdata, protected):
> if d.name not in ksdata.ignoredisk.ignoredisk]
> log.debug("onlyuse is now: %s", ",".join(ksdata.ignoredisk.onlyuse))
>
> -def turnOnFilesystems(storage, mountOnly=False, callbacks=None):
> - """
> - Perform installer-specific activation of storage configuration.
> -
> - :param callbacks: callbacks to be invoked when actions are executed
> - :type callbacks: return value of the :func:`~.callbacks.create_new_callbacks_register`
> -
> - """
> -
> - if not flags.installer_mode:
> - return
> -
> - if not mountOnly:
> - if (flags.live_install and not flags.image_install and not storage.fsset.active):
> - # turn off any swaps that we didn't turn on
> - # needed for live installs
> - util.run_program(["swapoff", "-a"])
> - storage.devicetree.teardownAll()
> -
> - try:
> - storage.doIt(callbacks)
> - except FSResizeError as e:
> - if errorHandler.cb(e) == ERROR_RAISE:
> - raise
> - except Exception as e:
> - raise
> -
> - storage.turnOnSwap()
> - # FIXME: For livecd, skipRoot needs to be True.
> - storage.mountFilesystems()
> -
> - if not mountOnly:
> - writeEscrowPackets(storage)
> -
> -def writeEscrowPackets(storage):
> - escrowDevices = [d for d in storage.devices if d.format.type == 'luks' and
> - d.format.escrow_cert]
> -
> - if not escrowDevices:
> - return
> -
> - log.debug("escrow: writeEscrowPackets start")
> -
> - if not nss:
> - log.error("escrow: no nss python module -- aborting")
> - return
> -
> - nss.nss.nss_init_nodb() # Does nothing if NSS is already initialized
> -
> - backupPassphrase = generateBackupPassphrase()
> -
> - try:
> - escrowDir = _sysroot + "/root"
> - log.debug("escrow: writing escrow packets to %s", escrowDir)
> - util.makedirs(escrowDir)
> - for device in escrowDevices:
> - log.debug("escrow: device %s: %s",
> - repr(device.path), repr(device.format.type))
> - device.format.escrow(escrowDir,
> - backupPassphrase)
> -
> - except (IOError, RuntimeError) as e:
> - # TODO: real error handling
> - log.error("failed to store encryption key: %s", e)
> -
> - log.debug("escrow: writeEscrowPackets done")
> -
> def empty_device(device, devicetree):
> empty = True
> if device.partitioned:
> @@ -302,6 +225,10 @@ class Blivet(object):
> :keyword ksdata: kickstart data store
> :type ksdata: :class:`pykickstart.Handler`
> """
> + # XXX: this cannot be imported globally as it would create a dependency
> + # cycle in the imports
> + from .osinstall import FSSet
> +
> self.ksdata = ksdata
> self._bootloader = None
>
> @@ -442,6 +369,11 @@ class Blivet(object):
> See :meth:`devicetree.Devicetree.populate` for more information
> about the cleanupOnly keyword argument.
> """
> +
> + # XXX: this cannot be imported globally as it would create a dependency
> + # cycle in the imports
> + from .osinstall import findExistingInstallations, FSSet
> +
> log.info("resetting Blivet (version %s) instance %s", __version__, self)
> if flags.installer_mode:
> # save passphrases for luks devices so we don't have to reprompt
> @@ -2072,996 +2004,3 @@ def mountExistingSystem(fsset, rootDevice, readOnly=None):
>
> fsset.parseFSTab()
> fsset.mountFilesystems(rootPath=_sysroot, readOnly=readOnly, skipRoot=True)
> -
> -
> -class BlkidTab(object):
> - """ Dictionary-like interface to blkid.tab with device path keys """
> - def __init__(self, chroot=""):
> - self.chroot = chroot
> - self.devices = {}
> -
> - def parse(self):
> - path = "%s/etc/blkid/blkid.tab" % self.chroot
> - log.debug("parsing %s", path)
> - with open(path) as f:
> - for line in f.readlines():
> - # this is pretty ugly, but an XML parser is more work than
> - # is justifiable for this purpose
> - if not line.startswith("<device "):
> - continue
> -
> - line = line[len("<device "):-len("</device>\n")]
> -
> - (data, _sep, device) = line.partition(">")
> - if not device:
> - continue
> -
> - self.devices[device] = {}
> - for pair in data.split():
> - try:
> - (key, value) = pair.split("=")
> - except ValueError:
> - continue
> -
> - self.devices[device][key] = value[1:-1] # strip off quotes
> -
> - def __getitem__(self, key):
> - return self.devices[key]
> -
> - def get(self, key, default=None):
> - return self.devices.get(key, default)
> -
> -
> -class CryptTab(object):
> - """ Dictionary-like interface to crypttab entries with map name keys """
> - def __init__(self, devicetree, blkidTab=None, chroot=""):
> - self.devicetree = devicetree
> - self.blkidTab = blkidTab
> - self.chroot = chroot
> - self.mappings = {}
> -
> - def parse(self, chroot=""):
> - """ Parse /etc/crypttab from an existing installation. """
> - if not chroot or not os.path.isdir(chroot):
> - chroot = ""
> -
> - path = "%s/etc/crypttab" % chroot
> - log.debug("parsing %s", path)
> - with open(path) as f:
> - if not self.blkidTab:
> - try:
> - self.blkidTab = BlkidTab(chroot=chroot)
> - self.blkidTab.parse()
> - except Exception: # pylint: disable=broad-except
> - log_exception_info(fmt_str="failed to parse blkid.tab")
> - self.blkidTab = None
> -
> - for line in f.readlines():
> - (line, _pound, _comment) = line.partition("#")
> - fields = line.split()
> - if not 2 <= len(fields) <= 4:
> - continue
> - elif len(fields) == 2:
> - fields.extend(['none', ''])
> - elif len(fields) == 3:
> - fields.append('')
> -
> - (name, devspec, keyfile, options) = fields
> -
> - # resolve devspec to a device in the tree
> - device = self.devicetree.resolveDevice(devspec,
> - blkidTab=self.blkidTab)
> - if device:
> - self.mappings[name] = {"device": device,
> - "keyfile": keyfile,
> - "options": options}
> -
> - def populate(self):
> - """ Populate the instance based on the device tree's contents. """
> - for device in self.devicetree.devices:
> - # XXX should we put them all in there or just the ones that
> - # are part of a device containing swap or a filesystem?
> - #
> - # Put them all in here -- we can filter from FSSet
> - if device.format.type != "luks":
> - continue
> -
> - key_file = device.format.keyFile
> - if not key_file:
> - key_file = "none"
> -
> - options = device.format.options or ""
> -
> - self.mappings[device.format.mapName] = {"device": device,
> - "keyfile": key_file,
> - "options": options}
> -
> - def crypttab(self):
> - """ Write out /etc/crypttab """
> - crypttab = ""
> - for name in self.mappings:
> - entry = self[name]
> - crypttab += "%s UUID=%s %s %s\n" % (name,
> - entry['device'].format.uuid,
> - entry['keyfile'],
> - entry['options'])
> - return crypttab
> -
> - def __getitem__(self, key):
> - return self.mappings[key]
> -
> - def get(self, key, default=None):
> - return self.mappings.get(key, default)
> -
> -def get_containing_device(path, devicetree):
> - """ Return the device that a path resides on. """
> - if not os.path.exists(path):
> - return None
> -
> - st = os.stat(path)
> - major = os.major(st.st_dev)
> - minor = os.minor(st.st_dev)
> - link = "/sys/dev/block/%s:%s" % (major, minor)
> - if not os.path.exists(link):
> - return None
> -
> - try:
> - device_name = os.path.basename(os.readlink(link))
> - except Exception: # pylint: disable=broad-except
> - log_exception_info(fmt_str="failed to find device name for path %s", fmt_args=[path])
> - return None
> -
> - if device_name.startswith("dm-"):
> - # have I told you lately that I love you, device-mapper?
> - device_name = name_from_dm_node(device_name)
> -
> - return devicetree.getDeviceByName(device_name)
> -
> -
> -class FSSet(object):
> - """ A class to represent a set of filesystems. """
> - def __init__(self, devicetree):
> - self.devicetree = devicetree
> - self.cryptTab = None
> - self.blkidTab = None
> - self.origFStab = None
> - self.active = False
> - self._dev = None
> - self._devpts = None
> - self._sysfs = None
> - self._proc = None
> - self._devshm = None
> - self._usb = None
> - self._selinux = None
> - self._run = None
> - self._fstab_swaps = set()
> - self.preserveLines = [] # lines we just ignore and preserve
> -
> - @property
> - def sysfs(self):
> - if not self._sysfs:
> - self._sysfs = NoDevice(fmt=getFormat("sysfs", device="sysfs", mountpoint="/sys"))
> - return self._sysfs
> -
> - @property
> - def dev(self):
> - if not self._dev:
> - self._dev = DirectoryDevice("/dev",
> - fmt=getFormat("bind", device="/dev", mountpoint="/dev", exists=True),
> - exists=True)
> -
> - return self._dev
> -
> - @property
> - def devpts(self):
> - if not self._devpts:
> - self._devpts = NoDevice(fmt=getFormat("devpts", device="devpts", mountpoint="/dev/pts"))
> - return self._devpts
> -
> - @property
> - def proc(self):
> - if not self._proc:
> - self._proc = NoDevice(fmt=getFormat("proc", device="proc", mountpoint="/proc"))
> - return self._proc
> -
> - @property
> - def devshm(self):
> - if not self._devshm:
> - self._devshm = NoDevice(fmt=getFormat("tmpfs", device="tmpfs", mountpoint="/dev/shm"))
> - return self._devshm
> -
> - @property
> - def usb(self):
> - if not self._usb:
> - self._usb = NoDevice(fmt=getFormat("usbfs", device="usbfs", mountpoint="/proc/bus/usb"))
> - return self._usb
> -
> - @property
> - def selinux(self):
> - if not self._selinux:
> - self._selinux = NoDevice(fmt=getFormat("selinuxfs", device="selinuxfs", mountpoint="/sys/fs/selinux"))
> - return self._selinux
> -
> - @property
> - def run(self):
> - if not self._run:
> - self._run = DirectoryDevice("/run",
> - fmt=getFormat("bind", device="/run", mountpoint="/run", exists=True),
> - exists=True)
> -
> - return self._run
> -
> - @property
> - def devices(self):
> - return sorted(self.devicetree.devices, key=lambda d: d.path)
> -
> - @property
> - def mountpoints(self):
> - filesystems = {}
> - for device in self.devices:
> - if device.format.mountable and device.format.mountpoint:
> - filesystems[device.format.mountpoint] = device
> - return filesystems
> -
> - def _parseOneLine(self, devspec, mountpoint, fstype, options, _dump="0", _passno="0"):
> - """Parse an fstab entry for a device, return the corresponding device.
> -
> - The parameters correspond to the items in a single entry in the
> - order in which they occur in the entry.
> -
> - :returns: the device corresponding to the entry
> - :rtype: :class:`devices.Device`
> - """
> -
> - # no sense in doing any legwork for a noauto entry
> - if "noauto" in options.split(","):
> - log.info("ignoring noauto entry")
> - raise UnrecognizedFSTabEntryError()
> -
> - # find device in the tree
> - device = self.devicetree.resolveDevice(devspec,
> - cryptTab=self.cryptTab,
> - blkidTab=self.blkidTab,
> - options=options)
> -
> - if device:
> - # fall through to the bottom of this block
> - pass
> - elif devspec.startswith("/dev/loop"):
> - # FIXME: create devices.LoopDevice
> - log.warning("completely ignoring your loop mount")
> - elif ":" in devspec and fstype.startswith("nfs"):
> - # NFS -- preserve but otherwise ignore
> - device = NFSDevice(devspec,
> - fmt=getFormat(fstype,
> - exists=True,
> - device=devspec))
> - elif devspec.startswith("/") and fstype == "swap":
> - # swap file
> - device = FileDevice(devspec,
> - parents=get_containing_device(devspec, self.devicetree),
> - fmt=getFormat(fstype,
> - device=devspec,
> - exists=True),
> - exists=True)
> - elif fstype == "bind" or "bind" in options:
> - # bind mount... set fstype so later comparison won't
> - # turn up false positives
> - fstype = "bind"
> -
> - # This is probably not going to do anything useful, so we'll
> - # make sure to try again from FSSet.mountFilesystems. The bind
> - # mount targets should be accessible by the time we try to do
> - # the bind mount from there.
> - parents = get_containing_device(devspec, self.devicetree)
> - device = DirectoryDevice(devspec, parents=parents, exists=True)
> - device.format = getFormat("bind",
> - device=device.path,
> - exists=True)
> - elif mountpoint in ("/proc", "/sys", "/dev/shm", "/dev/pts",
> - "/sys/fs/selinux", "/proc/bus/usb"):
> - # drop these now -- we'll recreate later
> - return None
> - else:
> - # nodev filesystem -- preserve or drop completely?
> - fmt = getFormat(fstype)
> - fmt_class = get_device_format_class("nodev")
> - if devspec == "none" or \
> - (fmt_class and isinstance(fmt, fmt_class)):
> - device = NoDevice(fmt=fmt)
> -
> - if device is None:
> - log.error("failed to resolve %s (%s) from fstab", devspec,
> - fstype)
> - raise UnrecognizedFSTabEntryError()
> -
> - device.setup()
> - fmt = getFormat(fstype, device=device.path, exists=True)
> - if fstype != "auto" and None in (device.format.type, fmt.type):
> - log.info("Unrecognized filesystem type for %s (%s)",
> - device.name, fstype)
> - device.teardown()
> - raise UnrecognizedFSTabEntryError()
> -
> - # make sure, if we're using a device from the tree, that
> - # the device's format we found matches what's in the fstab
> - ftype = getattr(fmt, "mountType", fmt.type)
> - dtype = getattr(device.format, "mountType", device.format.type)
> - if fstype != "auto" and ftype != dtype:
> - log.info("fstab says %s at %s is %s", dtype, mountpoint, ftype)
> - if fmt.testMount():
> - device.format = fmt
> - else:
> - device.teardown()
> - raise FSTabTypeMismatchError("%s: detected as %s, fstab says %s"
> - % (mountpoint, dtype, ftype))
> - del ftype
> - del dtype
> -
> - if hasattr(device.format, "mountpoint"):
> - device.format.mountpoint = mountpoint
> -
> - device.format.options = options
> -
> - return device
> -
> - def parseFSTab(self, chroot=None):
> - """ parse /etc/fstab
> -
> - preconditions:
> - all storage devices have been scanned, including filesystems
> - postconditions:
> -
> - FIXME: control which exceptions we raise
> -
> - XXX do we care about bind mounts?
> - how about nodev mounts?
> - loop mounts?
> - """
> - if not chroot or not os.path.isdir(chroot):
> - chroot = _sysroot
> -
> - path = "%s/etc/fstab" % chroot
> - if not os.access(path, os.R_OK):
> - # XXX should we raise an exception instead?
> - log.info("cannot open %s for read", path)
> - return
> -
> - blkidTab = BlkidTab(chroot=chroot)
> - try:
> - blkidTab.parse()
> - log.debug("blkid.tab devs: %s", list(blkidTab.devices.keys()))
> - except Exception: # pylint: disable=broad-except
> - log_exception_info(log.info, "error parsing blkid.tab")
> - blkidTab = None
> -
> - cryptTab = CryptTab(self.devicetree, blkidTab=blkidTab, chroot=chroot)
> - try:
> - cryptTab.parse(chroot=chroot)
> - log.debug("crypttab maps: %s", list(cryptTab.mappings.keys()))
> - except Exception: # pylint: disable=broad-except
> - log_exception_info(log.info, "error parsing crypttab")
> - cryptTab = None
> -
> - self.blkidTab = blkidTab
> - self.cryptTab = cryptTab
> -
> - with open(path) as f:
> - log.debug("parsing %s", path)
> -
> - lines = f.readlines()
> -
> - # save the original file
> - self.origFStab = ''.join(lines)
> -
> - for line in lines:
> -
> - (line, _pound, _comment) = line.partition("#")
> - fields = line.split()
> -
> - if not 4 <= len(fields) <= 6:
> - continue
> -
> - try:
> - device = self._parseOneLine(*fields)
> - except UnrecognizedFSTabEntryError:
> - # just write the line back out as-is after upgrade
> - self.preserveLines.append(line)
> - continue
> -
> - if not device:
> - continue
> -
> - if device not in self.devicetree.devices:
> - try:
> - self.devicetree._addDevice(device)
> - except ValueError:
> - # just write duplicates back out post-install
> - self.preserveLines.append(line)
> -
> - def turnOnSwap(self, rootPath=""):
> - """ Activate the system's swap space. """
> - if not flags.installer_mode:
> - return
> -
> - for device in self.swapDevices:
> - if isinstance(device, FileDevice):
> - # set up FileDevices' parents now that they are accessible
> - targetDir = "%s/%s" % (rootPath, device.path)
> - parent = get_containing_device(targetDir, self.devicetree)
> - if not parent:
> - log.error("cannot determine which device contains "
> - "directory %s", device.path)
> - device.parents = []
> - self.devicetree._removeDevice(device)
> - continue
> - else:
> - device.parents = [parent]
> -
> - while True:
> - try:
> - device.setup()
> - device.format.setup()
> - except StorageError as e:
> - if errorHandler.cb(e) == ERROR_RAISE:
> - raise
> - else:
> - break
> -
> - def mountFilesystems(self, rootPath="", readOnly=None, skipRoot=False):
> - """ Mount the system's filesystems.
> -
> - :param str rootPath: the root directory for this filesystem
> - :param readOnly: read only option str for this filesystem
> - :type readOnly: str or None
> - :param bool skipRoot: whether to skip mounting the root filesystem
> - """
> - if not flags.installer_mode:
> - return
> -
> - devices = list(self.mountpoints.values()) + self.swapDevices
> - devices.extend([self.dev, self.devshm, self.devpts, self.sysfs,
> - self.proc, self.selinux, self.usb, self.run])
> - devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
> -
> - for device in devices:
> - if not device.format.mountable or not device.format.mountpoint:
> - continue
> -
> - if skipRoot and device.format.mountpoint == "/":
> - continue
> -
> - options = device.format.options
> - if "noauto" in options.split(","):
> - continue
> -
> - if device.format.type == "bind" and device not in [self.dev, self.run]:
> - # set up the DirectoryDevice's parents now that they are
> - # accessible
> - #
> - # -- bind formats' device and mountpoint are always both
> - # under the chroot. no exceptions. none, damn it.
> - targetDir = "%s/%s" % (rootPath, device.path)
> - parent = get_containing_device(targetDir, self.devicetree)
> - if not parent:
> - log.error("cannot determine which device contains "
> - "directory %s", device.path)
> - device.parents = []
> - self.devicetree._removeDevice(device)
> - continue
> - else:
> - device.parents = [parent]
> -
> - try:
> - device.setup()
> - except Exception as e: # pylint: disable=broad-except
> - log_exception_info(fmt_str="unable to set up device %s", fmt_args=[device])
> - if errorHandler.cb(e) == ERROR_RAISE:
> - raise
> - else:
> - continue
> -
> - if readOnly:
> - options = "%s,%s" % (options, readOnly)
> -
> - try:
> - device.format.setup(options=options,
> - chroot=rootPath)
> - except Exception as e: # pylint: disable=broad-except
> - log_exception_info(log.error, "error mounting %s on %s", [device.path, device.format.mountpoint])
> - if errorHandler.cb(e) == ERROR_RAISE:
> - raise
> -
> - self.active = True
> -
> - def umountFilesystems(self, swapoff=True):
> - """ unmount filesystems, except swap if swapoff == False """
> - devices = list(self.mountpoints.values()) + self.swapDevices
> - devices.extend([self.dev, self.devshm, self.devpts, self.sysfs,
> - self.proc, self.usb, self.selinux, self.run])
> - devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
> - devices.reverse()
> - for device in devices:
> - if (not device.format.mountable) or \
> - (device.format.type == "swap" and not swapoff):
> - continue
> -
> - device.format.teardown()
> - device.teardown()
> -
> - self.active = False
> -
> - def createSwapFile(self, device, size):
> - """ Create and activate a swap file under storage root. """
> - filename = "/SWAP"
> - count = 0
> - basedir = os.path.normpath("%s/%s" % (getTargetPhysicalRoot(),
> - device.format.mountpoint))
> - while os.path.exists("%s/%s" % (basedir, filename)) or \
> - self.devicetree.getDeviceByName(filename):
> - count += 1
> - filename = "/SWAP-%d" % count
> -
> - dev = FileDevice(filename,
> - size=size,
> - parents=[device],
> - fmt=getFormat("swap", device=filename))
> - dev.create()
> - dev.setup()
> - dev.format.create()
> - dev.format.setup()
> - # nasty, nasty
> - self.devicetree._addDevice(dev)
> -
> - def mkDevRoot(self):
> - root = self.rootDevice
> - dev = "%s/%s" % (_sysroot, root.path)
> - if not os.path.exists("%s/dev/root" %(_sysroot,)) and os.path.exists(dev):
> - rdev = os.stat(dev).st_rdev
> - os.mknod("%s/dev/root" % (_sysroot,), stat.S_IFBLK | 0o600, rdev)
> -
> - @property
> - def swapDevices(self):
> - swaps = []
> - for device in self.devices:
> - if device.format.type == "swap":
> - swaps.append(device)
> - return swaps
> -
> - @property
> - def rootDevice(self):
> - for path in ["/", getTargetPhysicalRoot()]:
> - for device in self.devices:
> - try:
> - mountpoint = device.format.mountpoint
> - except AttributeError:
> - mountpoint = None
> -
> - if mountpoint == path:
> - return device
> -
> - def write(self):
> - """ write out all config files based on the set of filesystems """
> - # /etc/fstab
> - fstab_path = os.path.normpath("%s/etc/fstab" % _sysroot)
> - fstab = self.fstab()
> - open(fstab_path, "w").write(fstab)
> -
> - # /etc/crypttab
> - crypttab_path = os.path.normpath("%s/etc/crypttab" % _sysroot)
> - crypttab = self.crypttab()
> - origmask = os.umask(0o077)
> - open(crypttab_path, "w").write(crypttab)
> - os.umask(origmask)
> -
> - # /etc/mdadm.conf
> - mdadm_path = os.path.normpath("%s/etc/mdadm.conf" % _sysroot)
> - mdadm_conf = self.mdadmConf()
> - if mdadm_conf:
> - open(mdadm_path, "w").write(mdadm_conf)
> -
> - # /etc/multipath.conf
> - if self.devicetree.getDevicesByType("dm-multipath"):
> - util.copy_to_system("/etc/multipath.conf")
> - util.copy_to_system("/etc/multipath/wwids")
> - util.copy_to_system("/etc/multipath/bindings")
> - else:
> - log.info("not writing out mpath configuration")
> -
> - def crypttab(self):
> - # if we are upgrading, do we want to update crypttab?
> - # gut reaction says no, but plymouth needs the names to be very
> - # specific for passphrase prompting
> - if not self.cryptTab:
> - self.cryptTab = CryptTab(self.devicetree)
> - self.cryptTab.populate()
> -
> - devices = list(self.mountpoints.values()) + self.swapDevices
> -
> - # prune crypttab -- only mappings required by one or more entries
> - for name in self.cryptTab.mappings.keys():
> - keep = False
> - mapInfo = self.cryptTab[name]
> - cryptoDev = mapInfo['device']
> - for device in devices:
> - if device == cryptoDev or device.dependsOn(cryptoDev):
> - keep = True
> - break
> -
> - if not keep:
> - del self.cryptTab.mappings[name]
> -
> - return self.cryptTab.crypttab()
> -
> - def mdadmConf(self):
> - """ Return the contents of mdadm.conf. """
> - arrays = self.devicetree.getDevicesByType("mdarray")
> - arrays.extend(self.devicetree.getDevicesByType("mdbiosraidarray"))
> - arrays.extend(self.devicetree.getDevicesByType("mdcontainer"))
> - # Sort it, this not only looks nicer, but this will also put
> - # containers (which get md0, md1, etc.) before their members
> - # (which get md127, md126, etc.). and lame as it is mdadm will not
> - # assemble the whole stack in one go unless listed in the proper order
> - # in mdadm.conf
> - arrays.sort(key=lambda d: d.path)
> - if not arrays:
> - return ""
> -
> - conf = "# mdadm.conf written out by anaconda\n"
> - conf += "MAILADDR root\n"
> - conf += "AUTO +imsm +1.x -all\n"
> - devices = list(self.mountpoints.values()) + self.swapDevices
> - for array in arrays:
> - for device in devices:
> - if device == array or device.dependsOn(array):
> - conf += array.mdadmConfEntry
> - break
> -
> - return conf
> -
> - def fstab (self):
> - fmt_str = "%-23s %-23s %-7s %-15s %d %d\n"
> - fstab = """
> -#
> -# /etc/fstab
> -# Created by anaconda on %s
> -#
> -# Accessible filesystems, by reference, are maintained under '/dev/disk'
> -# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
> -#
> -""" % time.asctime()
> -
> - devices = sorted(self.mountpoints.values(),
> - key=lambda d: d.format.mountpoint)
> -
> - # filter swaps only in installer mode
> - if flags.installer_mode:
> - devices += [dev for dev in self.swapDevices
> - if dev in self._fstab_swaps]
> - else:
> - devices += self.swapDevices
> -
> - netdevs = self.devicetree.getDevicesByInstance(NetworkStorageDevice)
> -
> - rootdev = devices[0]
> - root_on_netdev = any(rootdev.dependsOn(netdev) for netdev in netdevs)
> -
> - for device in devices:
> - # why the hell do we put swap in the fstab, anyway?
> - if not device.format.mountable and device.format.type != "swap":
> - continue
> -
> - # Don't write out lines for optical devices, either.
> - if isinstance(device, OpticalDevice):
> - continue
> -
> - fstype = getattr(device.format, "mountType", device.format.type)
> - if fstype == "swap":
> - mountpoint = "swap"
> - options = device.format.options
> - else:
> - mountpoint = device.format.mountpoint
> - options = device.format.options
> - if not mountpoint:
> - log.warning("%s filesystem on %s has no mountpoint",
> - fstype,
> - device.path)
> - continue
> -
> - options = options or "defaults"
> - for netdev in netdevs:
> - if device.dependsOn(netdev):
> - if root_on_netdev and mountpoint not in ["/", "/usr"]:
> - options = options + ",x-initrd.mount"
> - break
> - if device.encrypted:
> - options += ",x-systemd.device-timeout=0"
> - devspec = device.fstabSpec
> - dump = device.format.dump
> - if device.format.check and mountpoint == "/":
> - passno = 1
> - elif device.format.check:
> - passno = 2
> - else:
> - passno = 0
> - fstab = fstab + device.fstabComment
> - fstab = fstab + fmt_str % (devspec, mountpoint, fstype,
> - options, dump, passno)
> -
> - # now, write out any lines we were unable to process because of
> - # unrecognized filesystems or unresolveable device specifications
> - for line in self.preserveLines:
> - fstab += line
> -
> - return fstab
> -
> - def addFstabSwap(self, device):
> - """
> - Add swap device to the list of swaps that should appear in the fstab.
> -
> - :param device: swap device that should be added to the list
> - :type device: blivet.devices.StorageDevice instance holding a swap format
> -
> - """
> -
> - self._fstab_swaps.add(device)
> -
> - def removeFstabSwap(self, device):
> - """
> - Remove swap device from the list of swaps that should appear in the fstab.
> -
> - :param device: swap device that should be removed from the list
> - :type device: blivet.devices.StorageDevice instance holding a swap format
> -
> - """
> -
> - try:
> - self._fstab_swaps.remove(device)
> - except KeyError:
> - pass
> -
> - def setFstabSwaps(self, devices):
> - """
> - Set swap devices that should appear in the fstab.
> -
> - :param devices: iterable providing devices that should appear in the fstab
> - :type devices: iterable providing blivet.devices.StorageDevice instances holding
> - a swap format
> -
> - """
> -
> - self._fstab_swaps = set(devices)
> -
> -def releaseFromRedhatRelease(fn):
> - """
> - Attempt to identify the installation of a Linux distribution via
> - /etc/redhat-release. This file must already have been verified to exist
> - and be readable.
> -
> - :param fn: an open filehandle on /etc/redhat-release
> - :type fn: filehandle
> - :returns: The distribution's name and version, or None for either or both
> - if they cannot be determined
> - :rtype: (string, string)
> - """
> - relName = None
> - relVer = None
> -
> - with open(fn) as f:
> - try:
> - relstr = f.readline().strip()
> - except (IOError, AttributeError):
> - relstr = ""
> -
> - # get the release name and version
> - # assumes that form is something
> - # like "Red Hat Linux release 6.2 (Zoot)"
> - (product, sep, version) = relstr.partition(" release ")
> - if sep:
> - relName = product
> - relVer = version.split()[0]
> -
> - return (relName, relVer)
> -
> -def releaseFromOsRelease(fn):
> - """
> - Attempt to identify the installation of a Linux distribution via
> - /etc/os-release. This file must already have been verified to exist
> - and be readable.
> -
> - :param fn: an open filehandle on /etc/os-release
> - :type fn: filehandle
> - :returns: The distribution's name and version, or None for either or both
> - if they cannot be determined
> - :rtype: (string, string)
> - """
> - relName = None
> - relVer = None
> -
> - with open(fn, "r") as f:
> - parser = shlex.shlex(f)
> -
> - while True:
> - key = parser.get_token()
> - if key == parser.eof:
> - break
> - elif key == "NAME":
> - # Throw away the "=".
> - parser.get_token()
> - relName = parser.get_token().strip("'\"")
> - elif key == "VERSION_ID":
> - # Throw away the "=".
> - parser.get_token()
> - relVer = parser.get_token().strip("'\"")
> -
> - return (relName, relVer)
> -
> -def getReleaseString():
> - """
> - Attempt to identify the installation of a Linux distribution by checking
> - a previously mounted filesystem for several files. The filesystem must
> - be mounted under the target physical root.
> -
> - :returns: The machine's arch, distribution name, and distribution version
> - or None for any parts that cannot be determined
> - :rtype: (string, string, string)
> - """
> - relName = None
> - relVer = None
> -
> - try:
> - relArch = util.capture_output(["arch"], root=_sysroot).strip()
> - except OSError:
> - relArch = None
> -
> - filename = "%s/etc/redhat-release" % getSysroot()
> - if os.access(filename, os.R_OK):
> - (relName, relVer) = releaseFromRedhatRelease(filename)
> - else:
> - filename = "%s/etc/os-release" % getSysroot()
> - if os.access(filename, os.R_OK):
> - (relName, relVer) = releaseFromOsRelease(filename)
> -
> - return (relArch, relName, relVer)
> -
> -def findExistingInstallations(devicetree):
> - if not os.path.exists(getTargetPhysicalRoot()):
> - util.makedirs(getTargetPhysicalRoot())
> -
> - roots = []
> - for device in devicetree.leaves:
> - if not device.format.linuxNative or not device.format.mountable or \
> - not device.controllable:
> - continue
> -
> - try:
> - device.setup()
> - except Exception: # pylint: disable=broad-except
> - log_exception_info(log.warning, "setup of %s failed", [device.name])
> - continue
> -
> - options = device.format.options + ",ro"
> - try:
> - device.format.mount(options=options, mountpoint=getSysroot())
> - except Exception: # pylint: disable=broad-except
> - log_exception_info(log.warning, "mount of %s as %s failed", [device.name, device.format.type])
> - device.teardown()
> - continue
> -
> - if not os.access(getSysroot() + "/etc/fstab", os.R_OK):
> - device.teardown(recursive=True)
> - continue
> -
> - try:
> - (architecture, product, version) = getReleaseString()
> - except ValueError:
> - name = _("Linux on %s") % device.name
> - else:
> - # I'd like to make this finer grained, but it'd be very difficult
> - # to translate.
> - if not product or not version or not architecture:
> - name = _("Unknown Linux")
> - elif "linux" in product.lower():
> - name = _("%(product)s %(version)s for %(arch)s") % \
> - {"product": product, "version": version, "arch": architecture}
> - else:
> - name = _("%(product)s Linux %(version)s for %(arch)s") % \
> - {"product": product, "version": version, "arch": architecture}
> -
> - (mounts, swaps) = parseFSTab(devicetree, chroot=_sysroot)
> - device.teardown()
> - if not mounts and not swaps:
> - # empty /etc/fstab. weird, but I've seen it happen.
> - continue
> - roots.append(Root(mounts=mounts, swaps=swaps, name=name))
> -
> - return roots
> -
> -class Root(object):
> - """ A Root represents an existing OS installation. """
> - def __init__(self, mounts=None, swaps=None, name=None):
> - """
> - :keyword mounts: mountpoint dict
> - :type mounts: dict (mountpoint keys and :class:`~.devices.StorageDevice` values)
> - :keyword swaps: swap device list
> - :type swaps: list of :class:`~.devices.StorageDevice`
> - :keyword name: name for this installed OS
> - :type name: str
> - """
> - # mountpoint key, StorageDevice value
> - if not mounts:
> - self.mounts = {}
> - else:
> - self.mounts = mounts
> -
> - # StorageDevice
> - if not swaps:
> - self.swaps = []
> - else:
> - self.swaps = swaps
> -
> - self.name = name # eg: "Fedora Linux 16 for x86_64", "Linux on sda2"
> -
> - if not self.name and "/" in self.mounts:
> - self.name = self.mounts["/"].format.uuid
> -
> - @property
> - def device(self):
> - return self.mounts.get("/")
> -
> -def parseFSTab(devicetree, chroot=None):
> - """ parse /etc/fstab and return a tuple of a mount dict and swap list """
> - if not chroot or not os.path.isdir(chroot):
> - chroot = _sysroot
> -
> - mounts = {}
> - swaps = []
> - path = "%s/etc/fstab" % chroot
> - if not os.access(path, os.R_OK):
> - # XXX should we raise an exception instead?
> - log.info("cannot open %s for read", path)
> - return (mounts, swaps)
> -
> - blkidTab = BlkidTab(chroot=chroot)
> - try:
> - blkidTab.parse()
> - log.debug("blkid.tab devs: %s", list(blkidTab.devices.keys()))
> - except Exception: # pylint: disable=broad-except
> - log_exception_info(log.info, "error parsing blkid.tab")
> - blkidTab = None
> -
> - cryptTab = CryptTab(devicetree, blkidTab=blkidTab, chroot=chroot)
> - try:
> - cryptTab.parse(chroot=chroot)
> - log.debug("crypttab maps: %s", list(cryptTab.mappings.keys()))
> - except Exception: # pylint: disable=broad-except
> - log_exception_info(log.info, "error parsing crypttab")
> - cryptTab = None
> -
> - with open(path) as f:
> - log.debug("parsing %s", path)
> - for line in f.readlines():
> -
> - (line, _pound, _comment) = line.partition("#")
> - fields = line.split(None, 4)
> -
> - if len(fields) < 5:
> - continue
> -
> - (devspec, mountpoint, fstype, options, _rest) = fields
> -
> - # find device in the tree
> - device = devicetree.resolveDevice(devspec,
> - cryptTab=cryptTab,
> - blkidTab=blkidTab,
> - options=options)
> -
> - if device is None:
> - continue
> -
> - if fstype != "swap":
> - mounts[mountpoint] = device
> - else:
> - swaps.append(device)
> -
> - return (mounts, swaps)
> diff --git a/blivet/autopart.py b/blivet/autopart.py
> new file mode 100644
> index 0000000..f35cc88
> --- /dev/null
> +++ b/blivet/autopart.py
> @@ -0,0 +1,483 @@
> +#
> +# Copyright (C) 2009-2015 Red Hat, Inc.
> +#
> +# This copyrighted material is made available to anyone wishing to use,
> +# modify, copy, or redistribute it subject to the terms and conditions of
> +# the GNU General Public License v.2, or (at your option) any later version.
> +# This program is distributed in the hope that it will be useful, but WITHOUT
> +# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
> +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
> +# source code or documentation are not subject to the GNU General Public
> +# License and may only be used or replicated with the express permission of
> +# Red Hat, Inc.
> +#
> +# Red Hat Author(s): Dave Lehman <dlehman at redhat.com>
> +#
> +
> +"""This module provides functions related to autopartitioning."""
> +
> +import parted
> +from decimal import Decimal
> +
> +from . import util
> +from .size import Size
> +from .devices.partition import PartitionDevice, FALLBACK_DEFAULT_PART_SIZE
> +from .devices.luks import LUKSDevice
> +from .errors import NoDisksError, NotEnoughFreeSpaceError
> +from .formats import getFormat
> +from .partitioning import doPartitioning, getFreeRegions, growLVM
> +from . import _
> +
> +from pykickstart.constants import AUTOPART_TYPE_BTRFS, AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP, AUTOPART_TYPE_PLAIN
> +
> +import logging
> +log = logging.getLogger("anaconda")
> +
> +# maximum ratio of swap size to disk size (10 %)
> +MAX_SWAP_DISK_RATIO = Decimal('0.1')
> +
> +def swapSuggestion(quiet=False, hibernation=False, disk_space=None):
> + """
> + Suggest the size of the swap partition that will be created.
> +
> + :param quiet: whether to log size information or not
> + :type quiet: bool
> + :param hibernation: calculate swap size big enough for hibernation
> + :type hibernation: bool
> + :param disk_space: how much disk space is available
> + :type disk_space: :class:`~.size.Size`
> + :return: calculated swap size
> +
> + """
> +
> + mem = util.total_memory()
> + mem = ((mem / 16) + 1) * 16
> + if not quiet:
> + log.info("Detected %s of memory", mem)
> +
> + sixtyfour_GiB = Size("64 GiB")
> +
> + # the succeeding if-statement implements the following formula for
> + # suggested swap size.
> + #
> + # swap(mem) = 2 * mem, if mem < 2 GiB
> + # = mem, if 2 GiB <= mem < 8 GiB
> + # = mem / 2, if 8 GIB <= mem < 64 GiB
> + # = 4 GiB, if mem >= 64 GiB
> + if mem < Size("2 GiB"):
> + swap = 2 * mem
> +
> + elif mem < Size("8 GiB"):
> + swap = mem
> +
> + elif mem < sixtyfour_GiB:
> + swap = mem / 2
> +
> + else:
> + swap = Size("4 GiB")
> +
> + if hibernation:
> + if mem <= sixtyfour_GiB:
> + swap = mem + swap
> + else:
> + log.info("Ignoring --hibernation option on systems with %s of RAM or more", sixtyfour_GiB)
> +
> + if disk_space is not None and not hibernation:
> + max_swap = disk_space * MAX_SWAP_DISK_RATIO
> + if swap > max_swap:
> + log.info("Suggested swap size (%(swap)s) exceeds %(percent)d %% of "
> + "disk space, using %(percent)d %% of disk space (%(size)s) "
> + "instead.", {"percent": MAX_SWAP_DISK_RATIO*100,
> + "swap": swap,
> + "size": max_swap})
> + swap = max_swap
> +
> + if not quiet:
> + log.info("Swap attempt of %s", swap)
> +
> + return swap
> +
> +def _getCandidateDisks(storage):
> + """ Return a list of disks to be used for autopart.
> +
> + Disks must be partitioned and have a single free region large enough
> + for a default-sized (500MiB) partition. They must also be in
> + :attr:`StorageDiscoveryConfig.clearPartDisks` if it is non-empty.
> +
> + :param storage: a Blivet instance
> + :type storage: :class:`~.Blivet`
> + :returns: a list of partitioned disks with at least 500MiB of free space
> + :rtype: list of :class:`~.devices.StorageDevice`
> + """
> + disks = []
> + for disk in storage.partitioned:
> + if storage.config.clearPartDisks and \
> + (disk.name not in storage.config.clearPartDisks):
> + continue
> +
> + part = disk.format.firstPartition
> + while part:
> + if not part.type & parted.PARTITION_FREESPACE:
> + part = part.nextPartition()
> + continue
> +
> + if Size(part.getLength(unit="B")) > PartitionDevice.defaultSize:
> + disks.append(disk)
> + break
> +
> + part = part.nextPartition()
> +
> + return disks
> +
> +def _scheduleImplicitPartitions(storage, disks, min_luks_entropy=0):
> + """ Schedule creation of a lvm/btrfs member partitions for autopart.
> +
> + We create one such partition on each disk. They are not allocated until
> + later (in :func:`doPartitioning`).
> +
> + :param storage: a :class:`~.Blivet` instance
> + :type storage: :class:`~.Blivet`
> + :param disks: list of partitioned disks with free space
> + :type disks: list of :class:`~.devices.StorageDevice`
> + :param min_luks_entropy: minimum entropy in bits required for
> + luks format creation
> + :type min_luks_entropy: int
> + :returns: list of newly created (unallocated) partitions
> + :rtype: list of :class:`~.devices.PartitionDevice`
> + """
> + # create a separate pv or btrfs partition for each disk with free space
> + devs = []
> +
> + # only schedule the partitions if either lvm or btrfs autopart was chosen
> + if storage.autoPartType == AUTOPART_TYPE_PLAIN:
> + return devs
> +
> + for disk in disks:
> + if storage.encryptedAutoPart:
> + fmt_type = "luks"
> + fmt_args = {"passphrase": storage.encryptionPassphrase,
> + "cipher": storage.encryptionCipher,
> + "escrow_cert": storage.autoPartEscrowCert,
> + "add_backup_passphrase": storage.autoPartAddBackupPassphrase,
> + "min_luks_entropy": min_luks_entropy}
> + else:
> + if storage.autoPartType in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP):
> + fmt_type = "lvmpv"
> + else:
> + fmt_type = "btrfs"
> + fmt_args = {}
> + part = storage.newPartition(fmt_type=fmt_type,
> + fmt_args=fmt_args,
> + grow=True,
> + parents=[disk])
> + storage.createDevice(part)
> + devs.append(part)
> +
> + return devs
> +
> +def _schedulePartitions(storage, disks, implicit_devices, min_luks_entropy=0):
> + """ Schedule creation of autopart partitions.
> +
> + This only schedules the requests for actual partitions.
> +
> + :param storage: a :class:`~.Blivet` instance
> + :type storage: :class:`~.Blivet`
> + :param disks: list of partitioned disks with free space
> + :type disks: list of :class:`~.devices.StorageDevice`
> + :param min_luks_entropy: minimum entropy in bits required for
> + luks format creation
> + :type min_luks_entropy: int
> + :returns: None
> + :rtype: None
> + """
> + # basis for requests with requiredSpace is the sum of the sizes of the
> + # two largest free regions
> + all_free = (Size(reg.getLength(unit="B")) for reg in getFreeRegions(disks))
> + all_free = sorted(all_free, reverse=True)
> + if not all_free:
> + # this should never happen since we've already filtered the disks
> + # to those with at least 500MiB free
> + log.error("no free space on disks %s", [d.name for d in disks])
> + return
> +
> + free = all_free[0]
> + if len(all_free) > 1:
> + free += all_free[1]
> +
> + # The boot disk must be set at this point. See if any platform-specific
> + # stage1 device we might allocate already exists on the boot disk.
> + stage1_device = None
> + for device in storage.devices:
> + if storage.bootloader.stage1_disk not in device.disks:
> + continue
> +
> + if storage.bootloader.is_valid_stage1_device(device, early=True):
> + stage1_device = device
> + break
> +
> + #
> + # First pass is for partitions only. We'll do LVs later.
> + #
> + for request in storage.autoPartitionRequests:
> + if ((request.lv and
> + storage.autoPartType in (AUTOPART_TYPE_LVM,
> + AUTOPART_TYPE_LVM_THINP)) or
> + (request.btr and storage.autoPartType == AUTOPART_TYPE_BTRFS)):
> + continue
> +
> + if request.requiredSpace and request.requiredSpace > free:
> + continue
> +
> + elif request.fstype in ("prepboot", "efi", "macefi", "hfs+") and \
> + (storage.bootloader.skip_bootloader or stage1_device):
> + # there should never be a need for more than one of these
> + # partitions, so skip them.
> + log.info("skipping unneeded stage1 %s request", request.fstype)
> + log.debug("%s", request)
> +
> + if request.fstype in ["efi", "macefi"] and stage1_device:
> + # Set the mountpoint for the existing EFI boot partition
> + stage1_device.format.mountpoint = "/boot/efi"
> +
> + log.debug("%s", stage1_device)
> + continue
> + elif request.fstype == "biosboot":
> + is_gpt = (stage1_device and
> + getattr(stage1_device.format, "labelType", None) == "gpt")
> + has_bios_boot = (stage1_device and
> + any([p.format.type == "biosboot"
> + for p in storage.partitions
> + if p.disk == stage1_device]))
> + if (storage.bootloader.skip_bootloader or
> + not (stage1_device and stage1_device.isDisk and
> + is_gpt and not has_bios_boot)):
> + # there should never be a need for more than one of these
> + # partitions, so skip them.
> + log.info("skipping unneeded stage1 %s request", request.fstype)
> + log.debug("%s", request)
> + log.debug("%s", stage1_device)
> + continue
> +
> + if request.size > all_free[0]:
> + # no big enough free space for the requested partition
> + raise NotEnoughFreeSpaceError(_("No big enough free space on disks for "
> + "automatic partitioning"))
> +
> + if request.encrypted and storage.encryptedAutoPart:
> + fmt_type = "luks"
> + fmt_args = {"passphrase": storage.encryptionPassphrase,
> + "cipher": storage.encryptionCipher,
> + "escrow_cert": storage.autoPartEscrowCert,
> + "add_backup_passphrase": storage.autoPartAddBackupPassphrase,
> + "min_luks_entropy": min_luks_entropy}
> + else:
> + fmt_type = request.fstype
> + fmt_args = {}
> +
> + dev = storage.newPartition(fmt_type=fmt_type,
> + fmt_args=fmt_args,
> + size=request.size,
> + grow=request.grow,
> + maxsize=request.maxSize,
> + mountpoint=request.mountpoint,
> + parents=disks,
> + weight=request.weight)
> +
> + # schedule the device for creation
> + storage.createDevice(dev)
> +
> + if request.encrypted and storage.encryptedAutoPart:
> + luks_fmt = getFormat(request.fstype,
> + device=dev.path,
> + mountpoint=request.mountpoint)
> + luks_dev = LUKSDevice("luks-%s" % dev.name,
> + fmt=luks_fmt,
> + size=dev.size,
> + parents=dev)
> + storage.createDevice(luks_dev)
> +
> + if storage.autoPartType in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP,
> + AUTOPART_TYPE_BTRFS):
> + # doing LVM/BTRFS -- make sure the newly created partition fits in some
> + # free space together with one of the implicitly requested partitions
> + smallest_implicit = sorted(implicit_devices, key=lambda d: d.size)[0]
> + if (request.size + smallest_implicit.size) > all_free[0]:
> + # not enough space to allocate the smallest implicit partition
> + # and the request, make the implicit partitions smaller in
> + # attempt to make space for the request
> + for implicit_req in implicit_devices:
> + implicit_req.size = FALLBACK_DEFAULT_PART_SIZE
> +
> + return implicit_devices
> +
> +def _scheduleVolumes(storage, devs):
> + """ Schedule creation of autopart lvm/btrfs volumes.
> +
> + Schedules encryption of member devices if requested, schedules creation
> + of the container (:class:`~.devices.LVMVolumeGroupDevice` or
> + :class:`~.devices.BTRFSVolumeDevice`) then schedules creation of the
> + autopart volume requests.
> +
> + :param storage: a :class:`~.Blivet` instance
> + :type storage: :class:`~.Blivet`
> + :param devs: list of member partitions
> + :type devs: list of :class:`~.devices.PartitionDevice`
> + :returns: None
> + :rtype: None
> +
> + If an appropriate bootloader stage1 device exists on the boot drive, any
> + autopart request to create another one will be skipped/discarded.
> + """
> + if not devs:
> + return
> +
> + if storage.autoPartType in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP):
> + new_container = storage.newVG
> + new_volume = storage.newLV
> + format_name = "lvmpv"
> + else:
> + new_container = storage.newBTRFS
> + new_volume = storage.newBTRFS
> + format_name = "btrfs"
> +
> + if storage.encryptedAutoPart:
> + pvs = []
> + for dev in devs:
> + pv = LUKSDevice("luks-%s" % dev.name,
> + fmt=getFormat(format_name, device=dev.path),
> + size=dev.size,
> + parents=dev)
> + pvs.append(pv)
> + storage.createDevice(pv)
> + else:
> + pvs = devs
> +
> + # create a vg containing all of the autopart pvs
> + container = new_container(parents=pvs)
> + storage.createDevice(container)
> +
> + #
> + # Convert storage.autoPartitionRequests into Device instances and
> + # schedule them for creation.
> + #
> + # Second pass, for LVs only.
> + pool = None
> + for request in storage.autoPartitionRequests:
> + btr = storage.autoPartType == AUTOPART_TYPE_BTRFS and request.btr
> + lv = (storage.autoPartType in (AUTOPART_TYPE_LVM,
> + AUTOPART_TYPE_LVM_THINP) and request.lv)
> + thinlv = (storage.autoPartType == AUTOPART_TYPE_LVM_THINP and
> + request.lv and request.thin)
> + if thinlv and pool is None:
> + # create a single thin pool in the vg
> + pool = storage.newLV(parents=[container], thin_pool=True, grow=True)
> + storage.createDevice(pool)
> +
> + if not btr and not lv and not thinlv:
> + continue
> +
> + # required space isn't relevant on btrfs
> + if (lv or thinlv) and \
> + request.requiredSpace and request.requiredSpace > container.size:
> + continue
> +
> + if request.fstype is None:
> + if btr:
> + # btrfs volumes can only contain btrfs filesystems
> + request.fstype = "btrfs"
> + else:
> + request.fstype = storage.defaultFSType
> +
> + kwargs = {"mountpoint": request.mountpoint,
> + "fmt_type": request.fstype}
> + if lv or thinlv:
> + if thinlv:
> + parents = [pool]
> + else:
> + parents = [container]
> +
> + kwargs.update({"parents": parents,
> + "grow": request.grow,
> + "maxsize": request.maxSize,
> + "size": request.size,
> + "thin_volume": thinlv})
> + else:
> + kwargs.update({"parents": [container],
> + "size": request.size,
> + "subvol": True})
> +
> + dev = new_volume(**kwargs)
> +
> + # schedule the device for creation
> + storage.createDevice(dev)
> +
> +def doAutoPartition(storage, data, min_luks_entropy=0):
> + """ Perform automatic partitioning.
> +
> + :param storage: a :class:`~.Blivet` instance
> + :type storage: :class:`~.Blivet`
> + :param data: kickstart data
> + :type data: :class:`pykickstart.BaseHandler`
> + :param min_luks_entropy: minimum entropy in bits required for
> + luks format creation
> + :type min_luks_entropy: int
> +
> + :attr:`Blivet.doAutoPart` controls whether this method creates the
> + automatic partitioning layout. :attr:`Blivet.autoPartType` controls
> + which variant of autopart used. It uses one of the pykickstart
> + AUTOPART_TYPE_* constants. The set of eligible disks is defined in
> + :attr:`StorageDiscoveryConfig.clearPartDisks`.
> +
> + .. note::
> +
> + Clearing of partitions is handled separately, in
> + :meth:`~.Blivet.clearPartitions`.
> + """
> + # pylint: disable=unused-argument
> + log.debug("doAutoPart: %s", storage.doAutoPart)
> + log.debug("encryptedAutoPart: %s", storage.encryptedAutoPart)
> + log.debug("autoPartType: %s", storage.autoPartType)
> + log.debug("clearPartType: %s", storage.config.clearPartType)
> + log.debug("clearPartDisks: %s", storage.config.clearPartDisks)
> + log.debug("autoPartitionRequests:\n%s", "".join([str(p) for p in storage.autoPartitionRequests]))
> + log.debug("storage.disks: %s", [d.name for d in storage.disks])
> + log.debug("storage.partitioned: %s", [d.name for d in storage.partitioned])
> + log.debug("all names: %s", [d.name for d in storage.devices])
> + log.debug("boot disk: %s", getattr(storage.bootDisk, "name", None))
> +
> + disks = []
> + devs = []
> +
> + if not storage.doAutoPart:
> + return
> +
> + if not storage.partitioned:
> + raise NoDisksError(_("No usable disks selected"))
> +
> + disks = _getCandidateDisks(storage)
> + devs = _scheduleImplicitPartitions(storage, disks, min_luks_entropy)
> + log.debug("candidate disks: %s", disks)
> + log.debug("devs: %s", devs)
> +
> + if disks == []:
> + raise NotEnoughFreeSpaceError(_("Not enough free space on disks for "
> + "automatic partitioning"))
> +
> + devs = _schedulePartitions(storage, disks, devs, min_luks_entropy=min_luks_entropy)
> +
> + # run the autopart function to allocate and grow partitions
> + doPartitioning(storage)
> + _scheduleVolumes(storage, devs)
> +
> + # grow LVs
> + growLVM(storage)
> +
> + storage.setUpBootLoader()
> +
> + # only newly added swaps should appear in the fstab
> + new_swaps = (dev for dev in storage.swaps if not dev.format.exists)
> + storage.setFstabSwaps(new_swaps)
> diff --git a/blivet/devicelibs/swap.py b/blivet/devicelibs/swap.py
> index 63e0dec9..5314810 100644
> --- a/blivet/devicelibs/swap.py
> +++ b/blivet/devicelibs/swap.py
> @@ -22,19 +22,14 @@
>
> import resource
> import os
> -from decimal import Decimal
>
> from ..errors import DMError, OldSwapError, SuspendError, SwapError, UnknownSwapError
> from .. import util
> from . import dm
> -from ..size import Size
>
> import logging
> log = logging.getLogger("blivet")
>
> -# maximum ratio of swap size to disk size (10 %)
> -MAX_SWAP_DISK_RATIO = Decimal('0.1')
> -
> def mkswap(device, label=None):
> # We use -f to force since mkswap tends to refuse creation on lvs with
> # a message about erasing bootbits sectors on whole disks. Bah.
> @@ -117,65 +112,3 @@ def swapstatus(device):
> break
>
> return status
> -
> -def swapSuggestion(quiet=False, hibernation=False, disk_space=None):
> - """
> - Suggest the size of the swap partition that will be created.
> -
> - :param quiet: whether to log size information or not
> - :type quiet: bool
> - :param hibernation: calculate swap size big enough for hibernation
> - :type hibernation: bool
> - :param disk_space: how much disk space is available
> - :type disk_space: :class:`~.size.Size`
> - :return: calculated swap size
> -
> - """
> -
> - mem = util.total_memory()
> - mem = ((mem / 16) + 1) * 16
> - if not quiet:
> - log.info("Detected %s of memory", mem)
> -
> - sixtyfour_GiB = Size("64 GiB")
> -
> - # the succeeding if-statement implements the following formula for
> - # suggested swap size.
> - #
> - # swap(mem) = 2 * mem, if mem < 2 GiB
> - # = mem, if 2 GiB <= mem < 8 GiB
> - # = mem / 2, if 8 GIB <= mem < 64 GiB
> - # = 4 GiB, if mem >= 64 GiB
> - if mem < Size("2 GiB"):
> - swap = 2 * mem
> -
> - elif mem < Size("8 GiB"):
> - swap = mem
> -
> - elif mem < sixtyfour_GiB:
> - swap = mem / 2
> -
> - else:
> - swap = Size("4 GiB")
> -
> - if hibernation:
> - if mem <= sixtyfour_GiB:
> - swap = mem + swap
> - else:
> - log.info("Ignoring --hibernation option on systems with %s of RAM or more", sixtyfour_GiB)
> -
> - if disk_space is not None and not hibernation:
> - max_swap = disk_space * MAX_SWAP_DISK_RATIO
> - if swap > max_swap:
> - log.info("Suggested swap size (%(swap)s) exceeds %(percent)d %% of "
> - "disk space, using %(percent)d %% of disk space (%(size)s) "
> - "instead.", {"percent": MAX_SWAP_DISK_RATIO*100,
> - "swap": swap,
> - "size": max_swap})
> - swap = max_swap
> -
> - if not quiet:
> - log.info("Swap attempt of %s", swap)
> -
> - return swap
> -
> diff --git a/blivet/osinstall.py b/blivet/osinstall.py
> new file mode 100644
> index 0000000..3729246
> --- /dev/null
> +++ b/blivet/osinstall.py
> @@ -0,0 +1,1107 @@
> +#
> +# Copyright (C) 2009-2015 Red Hat, Inc.
> +#
> +# This copyrighted material is made available to anyone wishing to use,
> +# modify, copy, or redistribute it subject to the terms and conditions of
> +# the GNU General Public License v.2, or (at your option) any later version.
> +# This program is distributed in the hope that it will be useful, but WITHOUT
> +# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
> +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
> +# source code or documentation are not subject to the GNU General Public
> +# License and may only be used or replicated with the express permission of
> +# Red Hat, Inc.
> +#
> +# Red Hat Author(s): Dave Lehman <dlehman at redhat.com>
> +#
> +
> +"""This module provides functions related to OS installation."""
> +
> +import shlex
> +import os
> +import stat
> +import time
> +
> +from . import util
> +from . import getSysroot, getTargetPhysicalRoot, errorHandler, ERROR_RAISE
> +
> +from .storage_log import log_exception_info
> +from .devices import FileDevice, NFSDevice, NoDevice, OpticalDevice, NetworkStorageDevice, DirectoryDevice
> +from .errors import FSTabTypeMismatchError, UnrecognizedFSTabEntryError, StorageError, FSResizeError
> +from .formats import get_device_format_class
> +from .devicelibs.dm import name_from_dm_node
> +from .devicelibs.crypto import generateBackupPassphrase
> +from .formats import getFormat
> +from .flags import flags
> +
> +from .i18n import _
> +
> +import logging
> +log = logging.getLogger("blivet")
> +
> +try:
> + import nss.nss
> +except ImportError:
> + nss = None
> +
> +def releaseFromRedhatRelease(fn):
> + """
> + Attempt to identify the installation of a Linux distribution via
> + /etc/redhat-release. This file must already have been verified to exist
> + and be readable.
> +
> + :param fn: an open filehandle on /etc/redhat-release
> + :type fn: filehandle
> + :returns: The distribution's name and version, or None for either or both
> + if they cannot be determined
> + :rtype: (string, string)
> + """
> + relName = None
> + relVer = None
> +
> + with open(fn) as f:
> + try:
> + relstr = f.readline().strip()
> + except (IOError, AttributeError):
> + relstr = ""
> +
> + # get the release name and version
> + # assumes that form is something
> + # like "Red Hat Linux release 6.2 (Zoot)"
> + (product, sep, version) = relstr.partition(" release ")
> + if sep:
> + relName = product
> + relVer = version.split()[0]
> +
> + return (relName, relVer)
> +
> +def releaseFromOsRelease(fn):
> + """
> + Attempt to identify the installation of a Linux distribution via
> + /etc/os-release. This file must already have been verified to exist
> + and be readable.
> +
> + :param fn: an open filehandle on /etc/os-release
> + :type fn: filehandle
> + :returns: The distribution's name and version, or None for either or both
> + if they cannot be determined
> + :rtype: (string, string)
> + """
> + relName = None
> + relVer = None
> +
> + with open(fn, "r") as f:
> + parser = shlex.shlex(f)
> +
> + while True:
> + key = parser.get_token()
> + if key == parser.eof:
> + break
> + elif key == "NAME":
> + # Throw away the "=".
> + parser.get_token()
> + relName = parser.get_token().strip("'\"")
> + elif key == "VERSION_ID":
> + # Throw away the "=".
> + parser.get_token()
> + relVer = parser.get_token().strip("'\"")
> +
> + return (relName, relVer)
> +
> +def getReleaseString():
> + """
> + Attempt to identify the installation of a Linux distribution by checking
> + a previously mounted filesystem for several files. The filesystem must
> + be mounted under the target physical root.
> +
> + :returns: The machine's arch, distribution name, and distribution version
> + or None for any parts that cannot be determined
> + :rtype: (string, string, string)
> + """
> + relName = None
> + relVer = None
> +
> + try:
> + relArch = util.capture_output(["arch"], root=getSysroot()).strip()
> + except OSError:
> + relArch = None
> +
> + filename = "%s/etc/redhat-release" % getSysroot()
> + if os.access(filename, os.R_OK):
> + (relName, relVer) = releaseFromRedhatRelease(filename)
> + else:
> + filename = "%s/etc/os-release" % getSysroot()
> + if os.access(filename, os.R_OK):
> + (relName, relVer) = releaseFromOsRelease(filename)
> +
> + return (relArch, relName, relVer)
> +
> +def parseFSTab(devicetree, chroot=None):
> + """ parse /etc/fstab and return a tuple of a mount dict and swap list """
> + if not chroot or not os.path.isdir(chroot):
> + chroot = getSysroot()
> +
> + mounts = {}
> + swaps = []
> + path = "%s/etc/fstab" % chroot
> + if not os.access(path, os.R_OK):
> + # XXX should we raise an exception instead?
> + log.info("cannot open %s for read", path)
> + return (mounts, swaps)
> +
> + blkidTab = BlkidTab(chroot=chroot)
> + try:
> + blkidTab.parse()
> + log.debug("blkid.tab devs: %s", list(blkidTab.devices.keys()))
> + except Exception: # pylint: disable=broad-except
> + log_exception_info(log.info, "error parsing blkid.tab")
> + blkidTab = None
> +
> + cryptTab = CryptTab(devicetree, blkidTab=blkidTab, chroot=chroot)
> + try:
> + cryptTab.parse(chroot=chroot)
> + log.debug("crypttab maps: %s", list(cryptTab.mappings.keys()))
> + except Exception: # pylint: disable=broad-except
> + log_exception_info(log.info, "error parsing crypttab")
> + cryptTab = None
> +
> + with open(path) as f:
> + log.debug("parsing %s", path)
> + for line in f.readlines():
> +
> + (line, _pound, _comment) = line.partition("#")
> + fields = line.split(None, 4)
> +
> + if len(fields) < 5:
> + continue
> +
> + (devspec, mountpoint, fstype, options, _rest) = fields
> +
> + # find device in the tree
> + device = devicetree.resolveDevice(devspec,
> + cryptTab=cryptTab,
> + blkidTab=blkidTab,
> + options=options)
> +
> + if device is None:
> + continue
> +
> + if fstype != "swap":
> + mounts[mountpoint] = device
> + else:
> + swaps.append(device)
> +
> + return (mounts, swaps)
> +
> +def findExistingInstallations(devicetree):
> + if not os.path.exists(getTargetPhysicalRoot()):
> + util.makedirs(getTargetPhysicalRoot())
> +
> + roots = []
> + for device in devicetree.leaves:
> + if not device.format.linuxNative or not device.format.mountable or \
> + not device.controllable:
> + continue
> +
> + try:
> + device.setup()
> + except Exception: # pylint: disable=broad-except
> + log_exception_info(log.warning, "setup of %s failed", [device.name])
> + continue
> +
> + options = device.format.options + ",ro"
> + try:
> + device.format.mount(options=options, mountpoint=getSysroot())
> + except Exception: # pylint: disable=broad-except
> + log_exception_info(log.warning, "mount of %s as %s failed", [device.name, device.format.type])
> + device.teardown()
> + continue
> +
> + if not os.access(getSysroot() + "/etc/fstab", os.R_OK):
> + device.teardown(recursive=True)
> + continue
> +
> + try:
> + (architecture, product, version) = getReleaseString()
> + except ValueError:
> + name = _("Linux on %s") % device.name
> + else:
> + # I'd like to make this finer grained, but it'd be very difficult
> + # to translate.
> + if not product or not version or not architecture:
> + name = _("Unknown Linux")
> + elif "linux" in product.lower():
> + name = _("%(product)s %(version)s for %(arch)s") % \
> + {"product": product, "version": version, "arch": architecture}
> + else:
> + name = _("%(product)s Linux %(version)s for %(arch)s") % \
> + {"product": product, "version": version, "arch": architecture}
> +
> + (mounts, swaps) = parseFSTab(devicetree, chroot=getSysroot())
> + device.teardown()
> + if not mounts and not swaps:
> + # empty /etc/fstab. weird, but I've seen it happen.
> + continue
> + roots.append(Root(mounts=mounts, swaps=swaps, name=name))
> +
> + return roots
> +
> +class FSSet(object):
> + """ A class to represent a set of filesystems. """
> + def __init__(self, devicetree):
> + self.devicetree = devicetree
> + self.cryptTab = None
> + self.blkidTab = None
> + self.origFStab = None
> + self.active = False
> + self._dev = None
> + self._devpts = None
> + self._sysfs = None
> + self._proc = None
> + self._devshm = None
> + self._usb = None
> + self._selinux = None
> + self._run = None
> + self._fstab_swaps = set()
> + self.preserveLines = [] # lines we just ignore and preserve
> +
> + @property
> + def sysfs(self):
> + if not self._sysfs:
> + self._sysfs = NoDevice(fmt=getFormat("sysfs", device="sysfs", mountpoint="/sys"))
> + return self._sysfs
> +
> + @property
> + def dev(self):
> + if not self._dev:
> + self._dev = DirectoryDevice("/dev",
> + fmt=getFormat("bind", device="/dev", mountpoint="/dev", exists=True),
> + exists=True)
> +
> + return self._dev
> +
> + @property
> + def devpts(self):
> + if not self._devpts:
> + self._devpts = NoDevice(fmt=getFormat("devpts", device="devpts", mountpoint="/dev/pts"))
> + return self._devpts
> +
> + @property
> + def proc(self):
> + if not self._proc:
> + self._proc = NoDevice(fmt=getFormat("proc", device="proc", mountpoint="/proc"))
> + return self._proc
> +
> + @property
> + def devshm(self):
> + if not self._devshm:
> + self._devshm = NoDevice(fmt=getFormat("tmpfs", device="tmpfs", mountpoint="/dev/shm"))
> + return self._devshm
> +
> + @property
> + def usb(self):
> + if not self._usb:
> + self._usb = NoDevice(fmt=getFormat("usbfs", device="usbfs", mountpoint="/proc/bus/usb"))
> + return self._usb
> +
> + @property
> + def selinux(self):
> + if not self._selinux:
> + self._selinux = NoDevice(fmt=getFormat("selinuxfs", device="selinuxfs", mountpoint="/sys/fs/selinux"))
> + return self._selinux
> +
> + @property
> + def run(self):
> + if not self._run:
> + self._run = DirectoryDevice("/run",
> + fmt=getFormat("bind", device="/run", mountpoint="/run", exists=True),
> + exists=True)
> +
> + return self._run
> +
> + @property
> + def devices(self):
> + return sorted(self.devicetree.devices, key=lambda d: d.path)
> +
> + @property
> + def mountpoints(self):
> + filesystems = {}
> + for device in self.devices:
> + if device.format.mountable and device.format.mountpoint:
> + filesystems[device.format.mountpoint] = device
> + return filesystems
> +
> + def _parseOneLine(self, devspec, mountpoint, fstype, options, _dump="0", _passno="0"):
> + """Parse an fstab entry for a device, return the corresponding device.
> +
> + The parameters correspond to the items in a single entry in the
> + order in which they occur in the entry.
> +
> + :returns: the device corresponding to the entry
> + :rtype: :class:`devices.Device`
> + """
> +
> + # no sense in doing any legwork for a noauto entry
> + if "noauto" in options.split(","):
> + log.info("ignoring noauto entry")
> + raise UnrecognizedFSTabEntryError()
> +
> + # find device in the tree
> + device = self.devicetree.resolveDevice(devspec,
> + cryptTab=self.cryptTab,
> + blkidTab=self.blkidTab,
> + options=options)
> +
> + if device:
> + # fall through to the bottom of this block
> + pass
> + elif devspec.startswith("/dev/loop"):
> + # FIXME: create devices.LoopDevice
> + log.warning("completely ignoring your loop mount")
> + elif ":" in devspec and fstype.startswith("nfs"):
> + # NFS -- preserve but otherwise ignore
> + device = NFSDevice(devspec,
> + fmt=getFormat(fstype,
> + exists=True,
> + device=devspec))
> + elif devspec.startswith("/") and fstype == "swap":
> + # swap file
> + device = FileDevice(devspec,
> + parents=get_containing_device(devspec, self.devicetree),
> + fmt=getFormat(fstype,
> + device=devspec,
> + exists=True),
> + exists=True)
> + elif fstype == "bind" or "bind" in options:
> + # bind mount... set fstype so later comparison won't
> + # turn up false positives
> + fstype = "bind"
> +
> + # This is probably not going to do anything useful, so we'll
> + # make sure to try again from FSSet.mountFilesystems. The bind
> + # mount targets should be accessible by the time we try to do
> + # the bind mount from there.
> + parents = get_containing_device(devspec, self.devicetree)
> + device = DirectoryDevice(devspec, parents=parents, exists=True)
> + device.format = getFormat("bind",
> + device=device.path,
> + exists=True)
> + elif mountpoint in ("/proc", "/sys", "/dev/shm", "/dev/pts",
> + "/sys/fs/selinux", "/proc/bus/usb"):
> + # drop these now -- we'll recreate later
> + return None
> + else:
> + # nodev filesystem -- preserve or drop completely?
> + fmt = getFormat(fstype)
> + fmt_class = get_device_format_class("nodev")
> + if devspec == "none" or \
> + (fmt_class and isinstance(fmt, fmt_class)):
> + device = NoDevice(fmt=fmt)
> +
> + if device is None:
> + log.error("failed to resolve %s (%s) from fstab", devspec,
> + fstype)
> + raise UnrecognizedFSTabEntryError()
> +
> + device.setup()
> + fmt = getFormat(fstype, device=device.path, exists=True)
> + if fstype != "auto" and None in (device.format.type, fmt.type):
> + log.info("Unrecognized filesystem type for %s (%s)",
> + device.name, fstype)
> + device.teardown()
> + raise UnrecognizedFSTabEntryError()
> +
> + # make sure, if we're using a device from the tree, that
> + # the device's format we found matches what's in the fstab
> + ftype = getattr(fmt, "mountType", fmt.type)
> + dtype = getattr(device.format, "mountType", device.format.type)
> + if fstype != "auto" and ftype != dtype:
> + log.info("fstab says %s at %s is %s", dtype, mountpoint, ftype)
> + if fmt.testMount():
> + device.format = fmt
> + else:
> + device.teardown()
> + raise FSTabTypeMismatchError("%s: detected as %s, fstab says %s"
> + % (mountpoint, dtype, ftype))
> + del ftype
> + del dtype
> +
> + if hasattr(device.format, "mountpoint"):
> + device.format.mountpoint = mountpoint
> +
> + device.format.options = options
> +
> + return device
> +
> + def parseFSTab(self, chroot=None):
> + """ parse /etc/fstab
> +
> + preconditions:
> + all storage devices have been scanned, including filesystems
> + postconditions:
> +
> + FIXME: control which exceptions we raise
> +
> + XXX do we care about bind mounts?
> + how about nodev mounts?
> + loop mounts?
> + """
> + if not chroot or not os.path.isdir(chroot):
> + chroot = getSysroot()
> +
> + path = "%s/etc/fstab" % chroot
> + if not os.access(path, os.R_OK):
> + # XXX should we raise an exception instead?
> + log.info("cannot open %s for read", path)
> + return
> +
> + blkidTab = BlkidTab(chroot=chroot)
> + try:
> + blkidTab.parse()
> + log.debug("blkid.tab devs: %s", list(blkidTab.devices.keys()))
> + except Exception: # pylint: disable=broad-except
> + log_exception_info(log.info, "error parsing blkid.tab")
> + blkidTab = None
> +
> + cryptTab = CryptTab(self.devicetree, blkidTab=blkidTab, chroot=chroot)
> + try:
> + cryptTab.parse(chroot=chroot)
> + log.debug("crypttab maps: %s", list(cryptTab.mappings.keys()))
> + except Exception: # pylint: disable=broad-except
> + log_exception_info(log.info, "error parsing crypttab")
> + cryptTab = None
> +
> + self.blkidTab = blkidTab
> + self.cryptTab = cryptTab
> +
> + with open(path) as f:
> + log.debug("parsing %s", path)
> +
> + lines = f.readlines()
> +
> + # save the original file
> + self.origFStab = ''.join(lines)
> +
> + for line in lines:
> +
> + (line, _pound, _comment) = line.partition("#")
> + fields = line.split()
> +
> + if not 4 <= len(fields) <= 6:
> + continue
> +
> + try:
> + device = self._parseOneLine(*fields)
> + except UnrecognizedFSTabEntryError:
> + # just write the line back out as-is after upgrade
> + self.preserveLines.append(line)
> + continue
> +
> + if not device:
> + continue
> +
> + if device not in self.devicetree.devices:
> + try:
> + self.devicetree._addDevice(device)
> + except ValueError:
> + # just write duplicates back out post-install
> + self.preserveLines.append(line)
> +
> + def turnOnSwap(self, rootPath=""):
> + """ Activate the system's swap space. """
> + if not flags.installer_mode:
> + return
> +
> + for device in self.swapDevices:
> + if isinstance(device, FileDevice):
> + # set up FileDevices' parents now that they are accessible
> + targetDir = "%s/%s" % (rootPath, device.path)
> + parent = get_containing_device(targetDir, self.devicetree)
> + if not parent:
> + log.error("cannot determine which device contains "
> + "directory %s", device.path)
> + device.parents = []
> + self.devicetree._removeDevice(device)
> + continue
> + else:
> + device.parents = [parent]
> +
> + while True:
> + try:
> + device.setup()
> + device.format.setup()
> + except StorageError as e:
> + if errorHandler.cb(e) == ERROR_RAISE:
> + raise
> + else:
> + break
> +
> + def mountFilesystems(self, rootPath="", readOnly=None, skipRoot=False):
> + """ Mount the system's filesystems.
> +
> + :param str rootPath: the root directory for this filesystem
> + :param readOnly: read only option str for this filesystem
> + :type readOnly: str or None
> + :param bool skipRoot: whether to skip mounting the root filesystem
> + """
> + if not flags.installer_mode:
> + return
> +
> + devices = list(self.mountpoints.values()) + self.swapDevices
> + devices.extend([self.dev, self.devshm, self.devpts, self.sysfs,
> + self.proc, self.selinux, self.usb, self.run])
> + devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
> +
> + for device in devices:
> + if not device.format.mountable or not device.format.mountpoint:
> + continue
> +
> + if skipRoot and device.format.mountpoint == "/":
> + continue
> +
> + options = device.format.options
> + if "noauto" in options.split(","):
> + continue
> +
> + if device.format.type == "bind" and device not in [self.dev, self.run]:
> + # set up the DirectoryDevice's parents now that they are
> + # accessible
> + #
> + # -- bind formats' device and mountpoint are always both
> + # under the chroot. no exceptions. none, damn it.
> + targetDir = "%s/%s" % (rootPath, device.path)
> + parent = get_containing_device(targetDir, self.devicetree)
> + if not parent:
> + log.error("cannot determine which device contains "
> + "directory %s", device.path)
> + device.parents = []
> + self.devicetree._removeDevice(device)
> + continue
> + else:
> + device.parents = [parent]
> +
> + try:
> + device.setup()
> + except Exception as e: # pylint: disable=broad-except
> + log_exception_info(fmt_str="unable to set up device %s", fmt_args=[device])
> + if errorHandler.cb(e) == ERROR_RAISE:
> + raise
> + else:
> + continue
> +
> + if readOnly:
> + options = "%s,%s" % (options, readOnly)
> +
> + try:
> + device.format.setup(options=options,
> + chroot=rootPath)
> + except Exception as e: # pylint: disable=broad-except
> + log_exception_info(log.error, "error mounting %s on %s", [device.path, device.format.mountpoint])
> + if errorHandler.cb(e) == ERROR_RAISE:
> + raise
> +
> + self.active = True
> +
> + def umountFilesystems(self, swapoff=True):
> + """ unmount filesystems, except swap if swapoff == False """
> + devices = list(self.mountpoints.values()) + self.swapDevices
> + devices.extend([self.dev, self.devshm, self.devpts, self.sysfs,
> + self.proc, self.usb, self.selinux, self.run])
> + devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
> + devices.reverse()
> + for device in devices:
> + if (not device.format.mountable) or \
> + (device.format.type == "swap" and not swapoff):
> + continue
> +
> + device.format.teardown()
> + device.teardown()
> +
> + self.active = False
> +
> + def createSwapFile(self, device, size):
> + """ Create and activate a swap file under storage root. """
> + filename = "/SWAP"
> + count = 0
> + basedir = os.path.normpath("%s/%s" % (getTargetPhysicalRoot(),
> + device.format.mountpoint))
> + while os.path.exists("%s/%s" % (basedir, filename)) or \
> + self.devicetree.getDeviceByName(filename):
> + count += 1
> + filename = "/SWAP-%d" % count
> +
> + dev = FileDevice(filename,
> + size=size,
> + parents=[device],
> + fmt=getFormat("swap", device=filename))
> + dev.create()
> + dev.setup()
> + dev.format.create()
> + dev.format.setup()
> + # nasty, nasty
> + self.devicetree._addDevice(dev)
> +
> + def mkDevRoot(self):
> + root = self.rootDevice
> + dev = "%s/%s" % (getSysroot(), root.path)
> + if not os.path.exists("%s/dev/root" %(getSysroot(),)) and os.path.exists(dev):
> + rdev = os.stat(dev).st_rdev
> + os.mknod("%s/dev/root" % (getSysroot(),), stat.S_IFBLK | 0o600, rdev)
> +
> + @property
> + def swapDevices(self):
> + swaps = []
> + for device in self.devices:
> + if device.format.type == "swap":
> + swaps.append(device)
> + return swaps
> +
> + @property
> + def rootDevice(self):
> + for path in ["/", getTargetPhysicalRoot()]:
> + for device in self.devices:
> + try:
> + mountpoint = device.format.mountpoint
> + except AttributeError:
> + mountpoint = None
> +
> + if mountpoint == path:
> + return device
> +
> + def write(self):
> + """ write out all config files based on the set of filesystems """
> + # /etc/fstab
> + fstab_path = os.path.normpath("%s/etc/fstab" % getSysroot())
> + fstab = self.fstab()
> + open(fstab_path, "w").write(fstab)
> +
> + # /etc/crypttab
> + crypttab_path = os.path.normpath("%s/etc/crypttab" % getSysroot())
> + crypttab = self.crypttab()
> + origmask = os.umask(0o077)
> + open(crypttab_path, "w").write(crypttab)
> + os.umask(origmask)
> +
> + # /etc/mdadm.conf
> + mdadm_path = os.path.normpath("%s/etc/mdadm.conf" % getSysroot())
> + mdadm_conf = self.mdadmConf()
> + if mdadm_conf:
> + open(mdadm_path, "w").write(mdadm_conf)
> +
> + # /etc/multipath.conf
> + if self.devicetree.getDevicesByType("dm-multipath"):
> + util.copy_to_system("/etc/multipath.conf")
> + util.copy_to_system("/etc/multipath/wwids")
> + util.copy_to_system("/etc/multipath/bindings")
> + else:
> + log.info("not writing out mpath configuration")
> +
> + def crypttab(self):
> + # if we are upgrading, do we want to update crypttab?
> + # gut reaction says no, but plymouth needs the names to be very
> + # specific for passphrase prompting
> + if not self.cryptTab:
> + self.cryptTab = CryptTab(self.devicetree)
> + self.cryptTab.populate()
> +
> + devices = list(self.mountpoints.values()) + self.swapDevices
> +
> + # prune crypttab -- only mappings required by one or more entries
> + for name in self.cryptTab.mappings.keys():
> + keep = False
> + mapInfo = self.cryptTab[name]
> + cryptoDev = mapInfo['device']
> + for device in devices:
> + if device == cryptoDev or device.dependsOn(cryptoDev):
> + keep = True
> + break
> +
> + if not keep:
> + del self.cryptTab.mappings[name]
> +
> + return self.cryptTab.crypttab()
> +
> + def mdadmConf(self):
> + """ Return the contents of mdadm.conf. """
> + arrays = self.devicetree.getDevicesByType("mdarray")
> + arrays.extend(self.devicetree.getDevicesByType("mdbiosraidarray"))
> + arrays.extend(self.devicetree.getDevicesByType("mdcontainer"))
> + # Sort it, this not only looks nicer, but this will also put
> + # containers (which get md0, md1, etc.) before their members
> + # (which get md127, md126, etc.). and lame as it is mdadm will not
> + # assemble the whole stack in one go unless listed in the proper order
> + # in mdadm.conf
> + arrays.sort(key=lambda d: d.path)
> + if not arrays:
> + return ""
> +
> + conf = "# mdadm.conf written out by anaconda\n"
> + conf += "MAILADDR root\n"
> + conf += "AUTO +imsm +1.x -all\n"
> + devices = list(self.mountpoints.values()) + self.swapDevices
> + for array in arrays:
> + for device in devices:
> + if device == array or device.dependsOn(array):
> + conf += array.mdadmConfEntry
> + break
> +
> + return conf
> +
> + def fstab (self):
> + fmt_str = "%-23s %-23s %-7s %-15s %d %d\n"
> + fstab = """
> +#
> +# /etc/fstab
> +# Created by anaconda on %s
> +#
> +# Accessible filesystems, by reference, are maintained under '/dev/disk'
> +# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
> +#
> +""" % time.asctime()
> +
> + devices = sorted(self.mountpoints.values(),
> + key=lambda d: d.format.mountpoint)
> +
> + # filter swaps only in installer mode
> + if flags.installer_mode:
> + devices += [dev for dev in self.swapDevices
> + if dev in self._fstab_swaps]
> + else:
> + devices += self.swapDevices
> +
> + netdevs = self.devicetree.getDevicesByInstance(NetworkStorageDevice)
> +
> + rootdev = devices[0]
> + root_on_netdev = any(rootdev.dependsOn(netdev) for netdev in netdevs)
> +
> + for device in devices:
> + # why the hell do we put swap in the fstab, anyway?
> + if not device.format.mountable and device.format.type != "swap":
> + continue
> +
> + # Don't write out lines for optical devices, either.
> + if isinstance(device, OpticalDevice):
> + continue
> +
> + fstype = getattr(device.format, "mountType", device.format.type)
> + if fstype == "swap":
> + mountpoint = "swap"
> + options = device.format.options
> + else:
> + mountpoint = device.format.mountpoint
> + options = device.format.options
> + if not mountpoint:
> + log.warning("%s filesystem on %s has no mountpoint",
> + fstype,
> + device.path)
> + continue
> +
> + options = options or "defaults"
> + for netdev in netdevs:
> + if device.dependsOn(netdev):
> + if root_on_netdev and mountpoint not in ["/", "/usr"]:
> + options = options + ",x-initrd.mount"
> + break
> + if device.encrypted:
> + options += ",x-systemd.device-timeout=0"
> + devspec = device.fstabSpec
> + dump = device.format.dump
> + if device.format.check and mountpoint == "/":
> + passno = 1
> + elif device.format.check:
> + passno = 2
> + else:
> + passno = 0
> + fstab = fstab + device.fstabComment
> + fstab = fstab + fmt_str % (devspec, mountpoint, fstype,
> + options, dump, passno)
> +
> + # now, write out any lines we were unable to process because of
> + # unrecognized filesystems or unresolveable device specifications
> + for line in self.preserveLines:
> + fstab += line
> +
> + return fstab
> +
> + def addFstabSwap(self, device):
> + """
> + Add swap device to the list of swaps that should appear in the fstab.
> +
> + :param device: swap device that should be added to the list
> + :type device: blivet.devices.StorageDevice instance holding a swap format
> +
> + """
> +
> + self._fstab_swaps.add(device)
> +
> + def removeFstabSwap(self, device):
> + """
> + Remove swap device from the list of swaps that should appear in the fstab.
> +
> + :param device: swap device that should be removed from the list
> + :type device: blivet.devices.StorageDevice instance holding a swap format
> +
> + """
> +
> + try:
> + self._fstab_swaps.remove(device)
> + except KeyError:
> + pass
> +
> + def setFstabSwaps(self, devices):
> + """
> + Set swap devices that should appear in the fstab.
> +
> + :param devices: iterable providing devices that should appear in the fstab
> + :type devices: iterable providing blivet.devices.StorageDevice instances holding
> + a swap format
> +
> + """
> +
> + self._fstab_swaps = set(devices)
> +
> +class Root(object):
> + """ A Root represents an existing OS installation. """
> + def __init__(self, mounts=None, swaps=None, name=None):
> + """
> + :keyword mounts: mountpoint dict
> + :type mounts: dict (mountpoint keys and :class:`~.devices.StorageDevice` values)
> + :keyword swaps: swap device list
> + :type swaps: list of :class:`~.devices.StorageDevice`
> + :keyword name: name for this installed OS
> + :type name: str
> + """
> + # mountpoint key, StorageDevice value
> + if not mounts:
> + self.mounts = {}
> + else:
> + self.mounts = mounts
> +
> + # StorageDevice
> + if not swaps:
> + self.swaps = []
> + else:
> + self.swaps = swaps
> +
> + self.name = name # eg: "Fedora Linux 16 for x86_64", "Linux on sda2"
> +
> + if not self.name and "/" in self.mounts:
> + self.name = self.mounts["/"].format.uuid
> +
> + @property
> + def device(self):
> + return self.mounts.get("/")
> +
> +
> +class BlkidTab(object):
> + """ Dictionary-like interface to blkid.tab with device path keys """
> + def __init__(self, chroot=""):
> + self.chroot = chroot
> + self.devices = {}
> +
> + def parse(self):
> + path = "%s/etc/blkid/blkid.tab" % self.chroot
> + log.debug("parsing %s", path)
> + with open(path) as f:
> + for line in f.readlines():
> + # this is pretty ugly, but an XML parser is more work than
> + # is justifiable for this purpose
> + if not line.startswith("<device "):
> + continue
> +
> + line = line[len("<device "):-len("</device>\n")]
> +
> + (data, _sep, device) = line.partition(">")
> + if not device:
> + continue
> +
> + self.devices[device] = {}
> + for pair in data.split():
> + try:
> + (key, value) = pair.split("=")
> + except ValueError:
> + continue
> +
> + self.devices[device][key] = value[1:-1] # strip off quotes
> +
> + def __getitem__(self, key):
> + return self.devices[key]
> +
> + def get(self, key, default=None):
> + return self.devices.get(key, default)
> +
> +
> +class CryptTab(object):
> + """ Dictionary-like interface to crypttab entries with map name keys """
> + def __init__(self, devicetree, blkidTab=None, chroot=""):
> + self.devicetree = devicetree
> + self.blkidTab = blkidTab
> + self.chroot = chroot
> + self.mappings = {}
> +
> + def parse(self, chroot=""):
> + """ Parse /etc/crypttab from an existing installation. """
> + if not chroot or not os.path.isdir(chroot):
> + chroot = ""
> +
> + path = "%s/etc/crypttab" % chroot
> + log.debug("parsing %s", path)
> + with open(path) as f:
> + if not self.blkidTab:
> + try:
> + self.blkidTab = BlkidTab(chroot=chroot)
> + self.blkidTab.parse()
> + except Exception: # pylint: disable=broad-except
> + log_exception_info(fmt_str="failed to parse blkid.tab")
> + self.blkidTab = None
> +
> + for line in f.readlines():
> + (line, _pound, _comment) = line.partition("#")
> + fields = line.split()
> + if not 2 <= len(fields) <= 4:
> + continue
> + elif len(fields) == 2:
> + fields.extend(['none', ''])
> + elif len(fields) == 3:
> + fields.append('')
> +
> + (name, devspec, keyfile, options) = fields
> +
> + # resolve devspec to a device in the tree
> + device = self.devicetree.resolveDevice(devspec,
> + blkidTab=self.blkidTab)
> + if device:
> + self.mappings[name] = {"device": device,
> + "keyfile": keyfile,
> + "options": options}
> +
> + def populate(self):
> + """ Populate the instance based on the device tree's contents. """
> + for device in self.devicetree.devices:
> + # XXX should we put them all in there or just the ones that
> + # are part of a device containing swap or a filesystem?
> + #
> + # Put them all in here -- we can filter from FSSet
> + if device.format.type != "luks":
> + continue
> +
> + key_file = device.format.keyFile
> + if not key_file:
> + key_file = "none"
> +
> + options = device.format.options or ""
> +
> + self.mappings[device.format.mapName] = {"device": device,
> + "keyfile": key_file,
> + "options": options}
> +
> + def crypttab(self):
> + """ Write out /etc/crypttab """
> + crypttab = ""
> + for name in self.mappings:
> + entry = self[name]
> + crypttab += "%s UUID=%s %s %s\n" % (name,
> + entry['device'].format.uuid,
> + entry['keyfile'],
> + entry['options'])
> + return crypttab
> +
> + def __getitem__(self, key):
> + return self.mappings[key]
> +
> + def get(self, key, default=None):
> + return self.mappings.get(key, default)
> +
> +def get_containing_device(path, devicetree):
> + """ Return the device that a path resides on. """
> + if not os.path.exists(path):
> + return None
> +
> + st = os.stat(path)
> + major = os.major(st.st_dev)
> + minor = os.minor(st.st_dev)
> + link = "/sys/dev/block/%s:%s" % (major, minor)
> + if not os.path.exists(link):
> + return None
> +
> + try:
> + device_name = os.path.basename(os.readlink(link))
> + except Exception: # pylint: disable=broad-except
> + log_exception_info(fmt_str="failed to find device name for path %s", fmt_args=[path])
> + return None
> +
> + if device_name.startswith("dm-"):
> + # have I told you lately that I love you, device-mapper?
> + device_name = name_from_dm_node(device_name)
> +
> + return devicetree.getDeviceByName(device_name)
> +
> +def turnOnFilesystems(storage, mountOnly=False, callbacks=None):
> + """
> + Perform installer-specific activation of storage configuration.
> +
> + :param callbacks: callbacks to be invoked when actions are executed
> + :type callbacks: return value of the :func:`~.callbacks.create_new_callbacks_register`
> +
> + """
> +
> + if not flags.installer_mode:
> + return
> +
> + if not mountOnly:
> + if (flags.live_install and not flags.image_install and not storage.fsset.active):
> + # turn off any swaps that we didn't turn on
> + # needed for live installs
> + util.run_program(["swapoff", "-a"])
> + storage.devicetree.teardownAll()
> +
> + try:
> + storage.doIt(callbacks)
> + except FSResizeError as e:
> + if errorHandler.cb(e) == ERROR_RAISE:
> + raise
> + except Exception as e:
> + raise
> +
> + storage.turnOnSwap()
> + # FIXME: For livecd, skipRoot needs to be True.
> + storage.mountFilesystems()
> +
> + if not mountOnly:
> + writeEscrowPackets(storage)
> +
> +def writeEscrowPackets(storage):
> + escrowDevices = [d for d in storage.devices if d.format.type == 'luks' and
> + d.format.escrow_cert]
> +
> + if not escrowDevices:
> + return
> +
> + log.debug("escrow: writeEscrowPackets start")
> +
> + if not nss:
> + log.error("escrow: no nss python module -- aborting")
> + return
> +
> + nss.nss.nss_init_nodb() # Does nothing if NSS is already initialized
> +
> + backupPassphrase = generateBackupPassphrase()
> +
> + try:
> + escrowDir = getSysroot() + "/root"
> + log.debug("escrow: writing escrow packets to %s", escrowDir)
> + util.makedirs(escrowDir)
> + for device in escrowDevices:
> + log.debug("escrow: device %s: %s",
> + repr(device.path), repr(device.format.type))
> + device.format.escrow(escrowDir,
> + backupPassphrase)
> +
> + except (IOError, RuntimeError) as e:
> + # TODO: real error handling
> + log.error("failed to store encryption key: %s", e)
> +
> + log.debug("escrow: writeEscrowPackets done")
> diff --git a/blivet/partitioning.py b/blivet/partitioning.py
> index f59f015..17da6e0 100644
> --- a/blivet/partitioning.py
> +++ b/blivet/partitioning.py
> @@ -24,13 +24,10 @@ from operator import gt, lt
> from decimal import Decimal
>
> import parted
> -from pykickstart.constants import AUTOPART_TYPE_BTRFS, AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP, AUTOPART_TYPE_PLAIN
>
> -from .errors import DeviceError, NoDisksError, NotEnoughFreeSpaceError, PartitioningError
> +from .errors import DeviceError, PartitioningError
> from .flags import flags
> from .devices import PartitionDevice, LUKSDevice, devicePathToName
> -from .devices.partition import FALLBACK_DEFAULT_PART_SIZE
> -from .formats import getFormat
> from .devicelibs.lvm import get_pool_padding
> from .size import Size
> from .i18n import _
> @@ -39,387 +36,6 @@ from .util import stringize, unicodeize
> import logging
> log = logging.getLogger("blivet")
>
> -def _getCandidateDisks(storage):
> - """ Return a list of disks to be used for autopart.
> -
> - Disks must be partitioned and have a single free region large enough
> - for a default-sized (500MiB) partition. They must also be in
> - :attr:`StorageDiscoveryConfig.clearPartDisks` if it is non-empty.
> -
> - :param storage: a Blivet instance
> - :type storage: :class:`~.Blivet`
> - :returns: a list of partitioned disks with at least 500MiB of free space
> - :rtype: list of :class:`~.devices.StorageDevice`
> - """
> - disks = []
> - for disk in storage.partitioned:
> - if storage.config.clearPartDisks and \
> - (disk.name not in storage.config.clearPartDisks):
> - continue
> -
> - part = disk.format.firstPartition
> - while part:
> - if not part.type & parted.PARTITION_FREESPACE:
> - part = part.nextPartition()
> - continue
> -
> - if Size(part.getLength(unit="B")) > PartitionDevice.defaultSize:
> - disks.append(disk)
> - break
> -
> - part = part.nextPartition()
> -
> - return disks
> -
> -def _scheduleImplicitPartitions(storage, disks, min_luks_entropy=0):
> - """ Schedule creation of a lvm/btrfs member partitions for autopart.
> -
> - We create one such partition on each disk. They are not allocated until
> - later (in :func:`doPartitioning`).
> -
> - :param storage: a :class:`~.Blivet` instance
> - :type storage: :class:`~.Blivet`
> - :param disks: list of partitioned disks with free space
> - :type disks: list of :class:`~.devices.StorageDevice`
> - :param min_luks_entropy: minimum entropy in bits required for
> - luks format creation
> - :type min_luks_entropy: int
> - :returns: list of newly created (unallocated) partitions
> - :rtype: list of :class:`~.devices.PartitionDevice`
> - """
> - # create a separate pv or btrfs partition for each disk with free space
> - devs = []
> -
> - # only schedule the partitions if either lvm or btrfs autopart was chosen
> - if storage.autoPartType == AUTOPART_TYPE_PLAIN:
> - return devs
> -
> - for disk in disks:
> - if storage.encryptedAutoPart:
> - fmt_type = "luks"
> - fmt_args = {"passphrase": storage.encryptionPassphrase,
> - "cipher": storage.encryptionCipher,
> - "escrow_cert": storage.autoPartEscrowCert,
> - "add_backup_passphrase": storage.autoPartAddBackupPassphrase,
> - "min_luks_entropy": min_luks_entropy}
> - else:
> - if storage.autoPartType in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP):
> - fmt_type = "lvmpv"
> - else:
> - fmt_type = "btrfs"
> - fmt_args = {}
> - part = storage.newPartition(fmt_type=fmt_type,
> - fmt_args=fmt_args,
> - grow=True,
> - parents=[disk])
> - storage.createDevice(part)
> - devs.append(part)
> -
> - return devs
> -
> -def _schedulePartitions(storage, disks, implicit_devices, min_luks_entropy=0):
> - """ Schedule creation of autopart partitions.
> -
> - This only schedules the requests for actual partitions.
> -
> - :param storage: a :class:`~.Blivet` instance
> - :type storage: :class:`~.Blivet`
> - :param disks: list of partitioned disks with free space
> - :type disks: list of :class:`~.devices.StorageDevice`
> - :param min_luks_entropy: minimum entropy in bits required for
> - luks format creation
> - :type min_luks_entropy: int
> - :returns: None
> - :rtype: None
> - """
> - # basis for requests with requiredSpace is the sum of the sizes of the
> - # two largest free regions
> - all_free = (Size(reg.getLength(unit="B")) for reg in getFreeRegions(disks))
> - all_free = sorted(all_free, reverse=True)
> - if not all_free:
> - # this should never happen since we've already filtered the disks
> - # to those with at least 500MiB free
> - log.error("no free space on disks %s", [d.name for d in disks])
> - return
> -
> - free = all_free[0]
> - if len(all_free) > 1:
> - free += all_free[1]
> -
> - # The boot disk must be set at this point. See if any platform-specific
> - # stage1 device we might allocate already exists on the boot disk.
> - stage1_device = None
> - for device in storage.devices:
> - if storage.bootloader.stage1_disk not in device.disks:
> - continue
> -
> - if storage.bootloader.is_valid_stage1_device(device, early=True):
> - stage1_device = device
> - break
> -
> - #
> - # First pass is for partitions only. We'll do LVs later.
> - #
> - for request in storage.autoPartitionRequests:
> - if ((request.lv and
> - storage.autoPartType in (AUTOPART_TYPE_LVM,
> - AUTOPART_TYPE_LVM_THINP)) or
> - (request.btr and storage.autoPartType == AUTOPART_TYPE_BTRFS)):
> - continue
> -
> - if request.requiredSpace and request.requiredSpace > free:
> - continue
> -
> - elif request.fstype in ("prepboot", "efi", "macefi", "hfs+") and \
> - (storage.bootloader.skip_bootloader or stage1_device):
> - # there should never be a need for more than one of these
> - # partitions, so skip them.
> - log.info("skipping unneeded stage1 %s request", request.fstype)
> - log.debug("%s", request)
> -
> - if request.fstype in ["efi", "macefi"] and stage1_device:
> - # Set the mountpoint for the existing EFI boot partition
> - stage1_device.format.mountpoint = "/boot/efi"
> -
> - log.debug("%s", stage1_device)
> - continue
> - elif request.fstype == "biosboot":
> - is_gpt = (stage1_device and
> - getattr(stage1_device.format, "labelType", None) == "gpt")
> - has_bios_boot = (stage1_device and
> - any([p.format.type == "biosboot"
> - for p in storage.partitions
> - if p.disk == stage1_device]))
> - if (storage.bootloader.skip_bootloader or
> - not (stage1_device and stage1_device.isDisk and
> - is_gpt and not has_bios_boot)):
> - # there should never be a need for more than one of these
> - # partitions, so skip them.
> - log.info("skipping unneeded stage1 %s request", request.fstype)
> - log.debug("%s", request)
> - log.debug("%s", stage1_device)
> - continue
> -
> - if request.size > all_free[0]:
> - # no big enough free space for the requested partition
> - raise NotEnoughFreeSpaceError(_("No big enough free space on disks for "
> - "automatic partitioning"))
> -
> - if request.encrypted and storage.encryptedAutoPart:
> - fmt_type = "luks"
> - fmt_args = {"passphrase": storage.encryptionPassphrase,
> - "cipher": storage.encryptionCipher,
> - "escrow_cert": storage.autoPartEscrowCert,
> - "add_backup_passphrase": storage.autoPartAddBackupPassphrase,
> - "min_luks_entropy": min_luks_entropy}
> - else:
> - fmt_type = request.fstype
> - fmt_args = {}
> -
> - dev = storage.newPartition(fmt_type=fmt_type,
> - fmt_args=fmt_args,
> - size=request.size,
> - grow=request.grow,
> - maxsize=request.maxSize,
> - mountpoint=request.mountpoint,
> - parents=disks,
> - weight=request.weight)
> -
> - # schedule the device for creation
> - storage.createDevice(dev)
> -
> - if request.encrypted and storage.encryptedAutoPart:
> - luks_fmt = getFormat(request.fstype,
> - device=dev.path,
> - mountpoint=request.mountpoint)
> - luks_dev = LUKSDevice("luks-%s" % dev.name,
> - fmt=luks_fmt,
> - size=dev.size,
> - parents=dev)
> - storage.createDevice(luks_dev)
> -
> - if storage.autoPartType in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP,
> - AUTOPART_TYPE_BTRFS):
> - # doing LVM/BTRFS -- make sure the newly created partition fits in some
> - # free space together with one of the implicitly requested partitions
> - smallest_implicit = sorted(implicit_devices, key=lambda d: d.size)[0]
> - if (request.size + smallest_implicit.size) > all_free[0]:
> - # not enough space to allocate the smallest implicit partition
> - # and the request, make the implicit partitions smaller in
> - # attempt to make space for the request
> - for implicit_req in implicit_devices:
> - implicit_req.size = FALLBACK_DEFAULT_PART_SIZE
> -
> - return implicit_devices
> -
> -def _scheduleVolumes(storage, devs):
> - """ Schedule creation of autopart lvm/btrfs volumes.
> -
> - Schedules encryption of member devices if requested, schedules creation
> - of the container (:class:`~.devices.LVMVolumeGroupDevice` or
> - :class:`~.devices.BTRFSVolumeDevice`) then schedules creation of the
> - autopart volume requests.
> -
> - :param storage: a :class:`~.Blivet` instance
> - :type storage: :class:`~.Blivet`
> - :param devs: list of member partitions
> - :type devs: list of :class:`~.devices.PartitionDevice`
> - :returns: None
> - :rtype: None
> -
> - If an appropriate bootloader stage1 device exists on the boot drive, any
> - autopart request to create another one will be skipped/discarded.
> - """
> - if not devs:
> - return
> -
> - if storage.autoPartType in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP):
> - new_container = storage.newVG
> - new_volume = storage.newLV
> - format_name = "lvmpv"
> - else:
> - new_container = storage.newBTRFS
> - new_volume = storage.newBTRFS
> - format_name = "btrfs"
> -
> - if storage.encryptedAutoPart:
> - pvs = []
> - for dev in devs:
> - pv = LUKSDevice("luks-%s" % dev.name,
> - fmt=getFormat(format_name, device=dev.path),
> - size=dev.size,
> - parents=dev)
> - pvs.append(pv)
> - storage.createDevice(pv)
> - else:
> - pvs = devs
> -
> - # create a vg containing all of the autopart pvs
> - container = new_container(parents=pvs)
> - storage.createDevice(container)
> -
> - #
> - # Convert storage.autoPartitionRequests into Device instances and
> - # schedule them for creation.
> - #
> - # Second pass, for LVs only.
> - pool = None
> - for request in storage.autoPartitionRequests:
> - btr = storage.autoPartType == AUTOPART_TYPE_BTRFS and request.btr
> - lv = (storage.autoPartType in (AUTOPART_TYPE_LVM,
> - AUTOPART_TYPE_LVM_THINP) and request.lv)
> - thinlv = (storage.autoPartType == AUTOPART_TYPE_LVM_THINP and
> - request.lv and request.thin)
> - if thinlv and pool is None:
> - # create a single thin pool in the vg
> - pool = storage.newLV(parents=[container], thin_pool=True, grow=True)
> - storage.createDevice(pool)
> -
> - if not btr and not lv and not thinlv:
> - continue
> -
> - # required space isn't relevant on btrfs
> - if (lv or thinlv) and \
> - request.requiredSpace and request.requiredSpace > container.size:
> - continue
> -
> - if request.fstype is None:
> - if btr:
> - # btrfs volumes can only contain btrfs filesystems
> - request.fstype = "btrfs"
> - else:
> - request.fstype = storage.defaultFSType
> -
> - kwargs = {"mountpoint": request.mountpoint,
> - "fmt_type": request.fstype}
> - if lv or thinlv:
> - if thinlv:
> - parents = [pool]
> - else:
> - parents = [container]
> -
> - kwargs.update({"parents": parents,
> - "grow": request.grow,
> - "maxsize": request.maxSize,
> - "size": request.size,
> - "thin_volume": thinlv})
> - else:
> - kwargs.update({"parents": [container],
> - "size": request.size,
> - "subvol": True})
> -
> - dev = new_volume(**kwargs)
> -
> - # schedule the device for creation
> - storage.createDevice(dev)
> -
> -def doAutoPartition(storage, data, min_luks_entropy=0):
> - """ Perform automatic partitioning.
> -
> - :param storage: a :class:`~.Blivet` instance
> - :type storage: :class:`~.Blivet`
> - :param data: kickstart data
> - :type data: :class:`pykickstart.BaseHandler`
> - :param min_luks_entropy: minimum entropy in bits required for
> - luks format creation
> - :type min_luks_entropy: int
> -
> - :attr:`Blivet.doAutoPart` controls whether this method creates the
> - automatic partitioning layout. :attr:`Blivet.autoPartType` controls
> - which variant of autopart used. It uses one of the pykickstart
> - AUTOPART_TYPE_* constants. The set of eligible disks is defined in
> - :attr:`StorageDiscoveryConfig.clearPartDisks`.
> -
> - .. note::
> -
> - Clearing of partitions is handled separately, in
> - :meth:`~.Blivet.clearPartitions`.
> - """
> - # pylint: disable=unused-argument
> - log.debug("doAutoPart: %s", storage.doAutoPart)
> - log.debug("encryptedAutoPart: %s", storage.encryptedAutoPart)
> - log.debug("autoPartType: %s", storage.autoPartType)
> - log.debug("clearPartType: %s", storage.config.clearPartType)
> - log.debug("clearPartDisks: %s", storage.config.clearPartDisks)
> - log.debug("autoPartitionRequests:\n%s", "".join([str(p) for p in storage.autoPartitionRequests]))
> - log.debug("storage.disks: %s", [d.name for d in storage.disks])
> - log.debug("storage.partitioned: %s", [d.name for d in storage.partitioned])
> - log.debug("all names: %s", [d.name for d in storage.devices])
> - log.debug("boot disk: %s", getattr(storage.bootDisk, "name", None))
> -
> - disks = []
> - devs = []
> -
> - if not storage.doAutoPart:
> - return
> -
> - if not storage.partitioned:
> - raise NoDisksError(_("No usable disks selected"))
> -
> - disks = _getCandidateDisks(storage)
> - devs = _scheduleImplicitPartitions(storage, disks, min_luks_entropy)
> - log.debug("candidate disks: %s", disks)
> - log.debug("devs: %s", devs)
> -
> - if disks == []:
> - raise NotEnoughFreeSpaceError(_("Not enough free space on disks for "
> - "automatic partitioning"))
> -
> - devs = _schedulePartitions(storage, disks, devs, min_luks_entropy=min_luks_entropy)
> -
> - # run the autopart function to allocate and grow partitions
> - doPartitioning(storage)
> - _scheduleVolumes(storage, devs)
> -
> - # grow LVs
> - growLVM(storage)
> -
> - storage.setUpBootLoader()
> -
> - # only newly added swaps should appear in the fstab
> - new_swaps = (dev for dev in storage.swaps if not dev.format.exists)
> - storage.setFstabSwaps(new_swaps)
> -
> def partitionCompare(part1, part2):
> """ More specifically defined partitions come first.
>
>
More information about the anaconda-patches
mailing list