812 lines
28 KiB
Python
812 lines
28 KiB
Python
|
#
|
||
|
# Copyright (C) 2019 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.
|
||
|
#
|
||
|
import os
|
||
|
import shutil
|
||
|
import time
|
||
|
|
||
|
from blivet import blockdev
|
||
|
from blivet.devices import NoDevice, DirectoryDevice, NFSDevice, FileDevice, MDRaidArrayDevice, \
|
||
|
NetworkStorageDevice, OpticalDevice
|
||
|
from blivet.errors import UnrecognizedFSTabEntryError, FSTabTypeMismatchError, SwapSpaceError, \
|
||
|
StorageError
|
||
|
from blivet.formats import get_format, get_device_format_class
|
||
|
from blivet.storage_log import log_exception_info
|
||
|
|
||
|
from pyanaconda.core.configuration.anaconda import conf
|
||
|
from pyanaconda.core.i18n import _
|
||
|
from pyanaconda.modules.storage.platform import platform, EFI
|
||
|
|
||
|
from pyanaconda.anaconda_loggers import get_module_logger
|
||
|
log = get_module_logger(__name__)
|
||
|
|
||
|
__all__ = ["BlkidTab", "CryptTab", "FSSet"]
|
||
|
|
||
|
|
||
|
def copy_to_system(source):
|
||
|
""" Copy the source file the target OS installation. """
|
||
|
if not os.access(source, os.R_OK):
|
||
|
log.info("copy_to_system: source '%s' does not exist.", source)
|
||
|
return False
|
||
|
|
||
|
target = conf.target.system_root + source
|
||
|
target_dir = os.path.dirname(target)
|
||
|
log.debug("copy_to_system: '%s' -> '%s'.", source, target)
|
||
|
if not os.path.isdir(target_dir):
|
||
|
os.makedirs(target_dir)
|
||
|
shutil.copy(source, target)
|
||
|
return True
|
||
|
|
||
|
|
||
|
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 = blockdev.dm.name_from_node(device_name)
|
||
|
|
||
|
return devicetree.get_device_by_name(device_name)
|
||
|
|
||
|
|
||
|
def get_system_filesystems(devicetree):
|
||
|
"""Get system filesystems.
|
||
|
|
||
|
:param devicetree: a model of the storage
|
||
|
:return: a list of new devices
|
||
|
"""
|
||
|
devices = [
|
||
|
DirectoryDevice(
|
||
|
"/dev",
|
||
|
exists=True,
|
||
|
fmt=get_format(
|
||
|
"bind",
|
||
|
device="/dev",
|
||
|
mountpoint="/dev",
|
||
|
exists=True
|
||
|
),
|
||
|
),
|
||
|
NoDevice(
|
||
|
fmt=get_format(
|
||
|
"tmpfs",
|
||
|
device="tmpfs",
|
||
|
mountpoint="/dev/shm"
|
||
|
)
|
||
|
),
|
||
|
NoDevice(
|
||
|
fmt=get_format(
|
||
|
"devpts",
|
||
|
device="devpts",
|
||
|
mountpoint="/dev/pts"
|
||
|
)
|
||
|
),
|
||
|
NoDevice(
|
||
|
fmt=get_format(
|
||
|
"sysfs",
|
||
|
device="sysfs",
|
||
|
mountpoint="/sys"
|
||
|
)
|
||
|
),
|
||
|
NoDevice(
|
||
|
fmt=get_format(
|
||
|
"proc",
|
||
|
device="proc",
|
||
|
mountpoint="/proc"
|
||
|
)
|
||
|
),
|
||
|
NoDevice(
|
||
|
fmt=get_format(
|
||
|
"selinuxfs",
|
||
|
device="selinuxfs",
|
||
|
mountpoint="/sys/fs/selinux"
|
||
|
)
|
||
|
),
|
||
|
NoDevice(
|
||
|
fmt=get_format(
|
||
|
"usbfs",
|
||
|
device="usbfs",
|
||
|
mountpoint="/proc/bus/usb"
|
||
|
)
|
||
|
),
|
||
|
DirectoryDevice(
|
||
|
"/run",
|
||
|
exists=True,
|
||
|
fmt=get_format(
|
||
|
"bind",
|
||
|
device="/run",
|
||
|
mountpoint="/run",
|
||
|
exists=True
|
||
|
)
|
||
|
)
|
||
|
]
|
||
|
|
||
|
if isinstance(platform, EFI):
|
||
|
device = NoDevice(
|
||
|
fmt=get_format(
|
||
|
"efivarfs",
|
||
|
device="efivarfs",
|
||
|
mountpoint="/sys/firmware/efi/efivars"
|
||
|
)
|
||
|
)
|
||
|
devices.append(device)
|
||
|
|
||
|
if "/tmp" not in devicetree.mountpoints:
|
||
|
device = NoDevice(
|
||
|
fmt=get_format(
|
||
|
"tmpfs",
|
||
|
device="tmpfs",
|
||
|
mountpoint="/tmp"
|
||
|
)
|
||
|
)
|
||
|
devices.append(device)
|
||
|
|
||
|
return devices
|
||
|
|
||
|
|
||
|
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
|
||
|
if not os.access(path, os.R_OK):
|
||
|
return
|
||
|
|
||
|
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, blkid_tab=None, chroot=""):
|
||
|
self.devicetree = devicetree
|
||
|
self.blkid_tab = blkid_tab
|
||
|
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
|
||
|
if not os.access(path, os.R_OK):
|
||
|
return
|
||
|
|
||
|
log.debug("parsing %s", path)
|
||
|
with open(path) as f:
|
||
|
if not self.blkid_tab:
|
||
|
try:
|
||
|
self.blkid_tab = BlkidTab(chroot=chroot)
|
||
|
self.blkid_tab.parse()
|
||
|
except Exception: # pylint: disable=broad-except
|
||
|
log_exception_info(fmt_str="failed to parse blkid.tab")
|
||
|
self.blkid_tab = 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.resolve_device(devspec,
|
||
|
blkid_tab=self.blkid_tab)
|
||
|
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.key_file
|
||
|
if not key_file:
|
||
|
key_file = "none"
|
||
|
|
||
|
options = device.format.options or ""
|
||
|
|
||
|
self.mappings[device.format.map_name] = {"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)
|
||
|
|
||
|
|
||
|
class FSSet(object):
|
||
|
"""A class to represent a set of filesystems."""
|
||
|
|
||
|
def __init__(self, devicetree):
|
||
|
self.devicetree = devicetree
|
||
|
self.crypt_tab = None
|
||
|
self.blkid_tab = None
|
||
|
self._fstab_swaps = set()
|
||
|
self._system_filesystems = []
|
||
|
self.preserve_lines = [] # lines we just ignore and preserve
|
||
|
|
||
|
@property
|
||
|
def system_filesystems(self):
|
||
|
if not self._system_filesystems:
|
||
|
self._system_filesystems = get_system_filesystems(
|
||
|
self.devicetree
|
||
|
)
|
||
|
|
||
|
return self._system_filesystems
|
||
|
|
||
|
@property
|
||
|
def devices(self):
|
||
|
return sorted(self.devicetree.devices, key=lambda d: d.path)
|
||
|
|
||
|
@property
|
||
|
def mountpoints(self):
|
||
|
return self.devicetree.mountpoints
|
||
|
|
||
|
def _parse_one_line(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.
|
||
|
|
||
|
:return: the device corresponding to the entry
|
||
|
:rtype: :class:`blivet.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.resolve_device(devspec,
|
||
|
crypt_tab=self.crypt_tab,
|
||
|
blkid_tab=self.blkid_tab,
|
||
|
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=get_format(fstype,
|
||
|
exists=True,
|
||
|
device=devspec))
|
||
|
elif devspec.startswith("/") and fstype == "swap":
|
||
|
# swap file
|
||
|
device = FileDevice(devspec,
|
||
|
parents=get_containing_device(devspec, self.devicetree),
|
||
|
fmt=get_format(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.mount_filesystems. 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 = get_format("bind",
|
||
|
device=device.path,
|
||
|
exists=True)
|
||
|
elif mountpoint in ("/proc", "/sys", "/dev/shm", "/dev/pts",
|
||
|
"/sys/fs/selinux", "/proc/bus/usb", "/sys/firmware/efi/efivars"):
|
||
|
# drop these now -- we'll recreate later
|
||
|
return None
|
||
|
else:
|
||
|
# nodev filesystem -- preserve or drop completely?
|
||
|
fmt = get_format(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 = get_format(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, "mount_type", fmt.type)
|
||
|
dtype = getattr(device.format, "mount_type", device.format.type)
|
||
|
if hasattr(fmt, "test_mount") and fstype != "auto" and ftype != dtype:
|
||
|
log.info("fstab says %s at %s is %s", dtype, mountpoint, ftype)
|
||
|
if fmt.test_mount(): # pylint: disable=no-member
|
||
|
device.format = fmt
|
||
|
else:
|
||
|
device.teardown()
|
||
|
raise FSTabTypeMismatchError(_(
|
||
|
"There is an entry in your /etc/fstab file that contains "
|
||
|
"an invalid or incorrect file system type. The file says that "
|
||
|
"{detected_type} at {mount_point} is {fstab_type}.").format(
|
||
|
detected_type=dtype,
|
||
|
mount_point=mountpoint,
|
||
|
fstab_type=ftype
|
||
|
))
|
||
|
|
||
|
del ftype
|
||
|
del dtype
|
||
|
|
||
|
if hasattr(device.format, "mountpoint"):
|
||
|
device.format.mountpoint = mountpoint
|
||
|
|
||
|
device.format.options = options
|
||
|
|
||
|
return device
|
||
|
|
||
|
def parse_fstab(self, chroot=None):
|
||
|
"""Parse /etc/fstab.
|
||
|
|
||
|
preconditions:
|
||
|
all storage devices have been scanned, including filesystems
|
||
|
|
||
|
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 = conf.target.system_root
|
||
|
|
||
|
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
|
||
|
|
||
|
blkid_tab = BlkidTab(chroot=chroot)
|
||
|
try:
|
||
|
blkid_tab.parse()
|
||
|
log.debug("blkid.tab devs: %s", list(blkid_tab.devices.keys()))
|
||
|
except Exception: # pylint: disable=broad-except
|
||
|
log_exception_info(log.info, "error parsing blkid.tab")
|
||
|
blkid_tab = None
|
||
|
|
||
|
crypt_tab = CryptTab(self.devicetree, blkid_tab=blkid_tab, chroot=chroot)
|
||
|
try:
|
||
|
crypt_tab.parse(chroot=chroot)
|
||
|
log.debug("crypttab maps: %s", list(crypt_tab.mappings.keys()))
|
||
|
except Exception: # pylint: disable=broad-except
|
||
|
log_exception_info(log.info, "error parsing crypttab")
|
||
|
crypt_tab = None
|
||
|
|
||
|
self.blkid_tab = blkid_tab
|
||
|
self.crypt_tab = crypt_tab
|
||
|
|
||
|
with open(path) as f:
|
||
|
log.debug("parsing %s", path)
|
||
|
|
||
|
lines = f.readlines()
|
||
|
|
||
|
for line in lines:
|
||
|
|
||
|
(line, _pound, _comment) = line.partition("#")
|
||
|
fields = line.split()
|
||
|
|
||
|
if not 4 <= len(fields) <= 6:
|
||
|
continue
|
||
|
|
||
|
try:
|
||
|
device = self._parse_one_line(*fields)
|
||
|
except UnrecognizedFSTabEntryError:
|
||
|
# just write the line back out as-is after upgrade
|
||
|
self.preserve_lines.append(line)
|
||
|
continue
|
||
|
|
||
|
if not device:
|
||
|
continue
|
||
|
|
||
|
if device not in self.devicetree.devices:
|
||
|
try:
|
||
|
self.devicetree._add_device(device)
|
||
|
except ValueError:
|
||
|
# just write duplicates back out post-install
|
||
|
self.preserve_lines.append(line)
|
||
|
|
||
|
def turn_on_swap(self, root_path=""):
|
||
|
"""Activate the system's swap space."""
|
||
|
for device in self.swap_devices:
|
||
|
if isinstance(device, FileDevice):
|
||
|
# set up FileDevices' parents now that they are accessible
|
||
|
target_dir = "%s/%s" % (root_path, device.path)
|
||
|
parent = get_containing_device(target_dir, self.devicetree)
|
||
|
if not parent:
|
||
|
log.error("cannot determine which device contains "
|
||
|
"directory %s", device.path)
|
||
|
device.parents = []
|
||
|
self.devicetree._remove_device(device)
|
||
|
continue
|
||
|
else:
|
||
|
device.parents = [parent]
|
||
|
|
||
|
while True:
|
||
|
if device.status and device.format.status:
|
||
|
break
|
||
|
try:
|
||
|
device.setup()
|
||
|
device.format.setup()
|
||
|
except (SwapSpaceError, blockdev.SwapActivateError,
|
||
|
StorageError, blockdev.BlockDevError) as e:
|
||
|
log.error("Failed to activate swap on '%s': %s", device.name, str(e))
|
||
|
break
|
||
|
else:
|
||
|
break
|
||
|
|
||
|
def collect_filesystems(self):
|
||
|
"""Collect the system's filesystems.
|
||
|
|
||
|
:return: a list of devices
|
||
|
"""
|
||
|
devices = \
|
||
|
list(self.mountpoints.values()) + \
|
||
|
self.swap_devices + \
|
||
|
self.system_filesystems
|
||
|
|
||
|
devices.sort(key=lambda d: getattr(d.format, "mountpoint", ""))
|
||
|
return devices
|
||
|
|
||
|
def mount_filesystems(self, root_path="", read_only=None, skip_root=False):
|
||
|
"""Mount the system's filesystems.
|
||
|
|
||
|
:param str root_path: the root directory for this filesystem
|
||
|
:param read_only: read only option str for this filesystem
|
||
|
:type read_only: str or None
|
||
|
:param bool skip_root: whether to skip mounting the root filesystem
|
||
|
"""
|
||
|
devices = self.collect_filesystems()
|
||
|
|
||
|
for device in devices:
|
||
|
if not device.format.mountable or not device.format.mountpoint:
|
||
|
continue
|
||
|
|
||
|
if skip_root and device.format.mountpoint == "/":
|
||
|
continue
|
||
|
|
||
|
options = device.format.options
|
||
|
if "noauto" in options.split(","):
|
||
|
continue
|
||
|
|
||
|
if device.format.type == "bind" and device.name not in ["/dev", "/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.
|
||
|
target_dir = "%s/%s" % (root_path, device.path)
|
||
|
parent = get_containing_device(target_dir, self.devicetree)
|
||
|
if not parent:
|
||
|
log.error("cannot determine which device contains "
|
||
|
"directory %s", device.path)
|
||
|
device.parents = []
|
||
|
self.devicetree._remove_device(device)
|
||
|
continue
|
||
|
else:
|
||
|
device.parents = [parent]
|
||
|
|
||
|
if read_only:
|
||
|
options = "%s,%s" % (options, read_only)
|
||
|
|
||
|
# Create /tmp with the right permissions (rhbz#1937626).
|
||
|
# It needs to be created right before we mount anything.
|
||
|
# Call chmod to enforce the mode.
|
||
|
if device.format.mountpoint == "/tmp":
|
||
|
path = os.path.join(root_path, "tmp")
|
||
|
|
||
|
if not os.path.exists(path):
|
||
|
os.makedirs(path)
|
||
|
os.chmod(path, 0o1777)
|
||
|
|
||
|
device.setup()
|
||
|
device.format.setup(
|
||
|
options=options,
|
||
|
chroot=root_path
|
||
|
)
|
||
|
|
||
|
def umount_filesystems(self, swapoff=True):
|
||
|
"""Unmount filesystems.
|
||
|
|
||
|
Exclude swap if swapoff is False.
|
||
|
"""
|
||
|
devices = self.collect_filesystems()
|
||
|
devices.reverse()
|
||
|
|
||
|
for device in devices:
|
||
|
if (not device.format.mountable) or \
|
||
|
(device.format.type == "swap" and not swapoff):
|
||
|
continue
|
||
|
|
||
|
# Unmount the devices
|
||
|
device.format.teardown()
|
||
|
|
||
|
@property
|
||
|
def swap_devices(self):
|
||
|
swaps = []
|
||
|
for device in self.devices:
|
||
|
if device.format.type == "swap":
|
||
|
swaps.append(device)
|
||
|
return swaps
|
||
|
|
||
|
@property
|
||
|
def root_device(self):
|
||
|
for path in ["/", conf.target.physical_root]:
|
||
|
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."""
|
||
|
sysroot = conf.target.system_root
|
||
|
# /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.mdadm_conf()
|
||
|
if mdadm_conf:
|
||
|
open(mdadm_path, "w").write(mdadm_conf)
|
||
|
|
||
|
# /etc/multipath.conf
|
||
|
if any(d for d in self.devices if d.type == "dm-multipath"):
|
||
|
copy_to_system("/etc/multipath.conf")
|
||
|
copy_to_system("/etc/multipath/wwids")
|
||
|
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.crypt_tab:
|
||
|
self.crypt_tab = CryptTab(self.devicetree)
|
||
|
self.crypt_tab.populate()
|
||
|
|
||
|
devices = list(self.mountpoints.values()) + self.swap_devices
|
||
|
|
||
|
# prune crypttab -- only mappings required by one or more entries
|
||
|
for name in list(self.crypt_tab.mappings.keys()):
|
||
|
keep = False
|
||
|
map_info = self.crypt_tab[name]
|
||
|
crypto_dev = map_info['device']
|
||
|
for device in devices:
|
||
|
if device == crypto_dev or device.depends_on(crypto_dev):
|
||
|
keep = True
|
||
|
break
|
||
|
|
||
|
if not keep:
|
||
|
del self.crypt_tab.mappings[name]
|
||
|
|
||
|
return self.crypt_tab.crypttab()
|
||
|
|
||
|
def mdadm_conf(self):
|
||
|
"""Return the contents of mdadm.conf."""
|
||
|
arrays = [d for d in self.devices if isinstance(d, MDRaidArrayDevice)]
|
||
|
# 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 ""
|
||
|
|
||
|
content = "# mdadm.conf written out by anaconda\n"
|
||
|
content += "MAILADDR root\n"
|
||
|
content += "AUTO +imsm +1.x -all\n"
|
||
|
devices = list(self.mountpoints.values()) + self.swap_devices
|
||
|
for array in arrays:
|
||
|
for device in devices:
|
||
|
if device == array or device.depends_on(array):
|
||
|
content += array.mdadm_conf_entry
|
||
|
break
|
||
|
|
||
|
return content
|
||
|
|
||
|
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.
|
||
|
#
|
||
|
# After editing this file, run 'systemctl daemon-reload' to update systemd
|
||
|
# units generated from this file.
|
||
|
#
|
||
|
""" % time.asctime()
|
||
|
|
||
|
devices = sorted(self.mountpoints.values(),
|
||
|
key=lambda d: d.format.mountpoint)
|
||
|
|
||
|
# filter swaps only in installer mode
|
||
|
devices += [dev for dev in self.swap_devices if dev in self._fstab_swaps]
|
||
|
|
||
|
netdevs = [d for d in self.devices if isinstance(d, NetworkStorageDevice)]
|
||
|
|
||
|
rootdev = devices[0]
|
||
|
root_on_netdev = any(rootdev.depends_on(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, "mount_type", device.format.type)
|
||
|
if fstype == "swap":
|
||
|
mountpoint = "none"
|
||
|
options = device.format.options
|
||
|
else:
|
||
|
mountpoint = device.format.mountpoint
|
||
|
options = device.format.options
|
||
|
if not mountpoint:
|
||
|
log.warning("%s filesystem on %s has no mount point",
|
||
|
fstype,
|
||
|
device.path)
|
||
|
continue
|
||
|
|
||
|
options = options or "defaults"
|
||
|
for netdev in netdevs:
|
||
|
if device.depends_on(netdev):
|
||
|
if root_on_netdev and mountpoint == "/var":
|
||
|
options = options + ",x-initrd.mount"
|
||
|
break
|
||
|
if device.encrypted:
|
||
|
options += ",x-systemd.device-timeout=0"
|
||
|
devspec = device.fstab_spec
|
||
|
dump = device.format.dump
|
||
|
if device.format.check and mountpoint == "/":
|
||
|
passno = 1
|
||
|
elif device.format.check:
|
||
|
passno = 2
|
||
|
else:
|
||
|
passno = 0
|
||
|
fstab = fstab + device.fstab_comment
|
||
|
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 unresolvable device specifications
|
||
|
for line in self.preserve_lines:
|
||
|
fstab += line
|
||
|
|
||
|
return fstab
|
||
|
|
||
|
def add_fstab_swap(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: StorageDevice instance holding a swap format
|
||
|
"""
|
||
|
self._fstab_swaps.add(device)
|
||
|
|
||
|
def set_fstab_swaps(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 StorageDevice instances holding a swap format
|
||
|
"""
|
||
|
self._fstab_swaps = set(devices)
|