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@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@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@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.