2241 lines
84 KiB
Python
2241 lines
84 KiB
Python
# __init__.py
|
|
# Entry point for anaconda's storage configuration module.
|
|
#
|
|
# Copyright (C) 2009 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>
|
|
#
|
|
|
|
import os
|
|
import time
|
|
import stat
|
|
import errno
|
|
import sys
|
|
import statvfs
|
|
|
|
import nss.nss
|
|
import parted
|
|
|
|
import isys
|
|
import iutil
|
|
from constants import *
|
|
from pykickstart.constants import *
|
|
from flags import flags
|
|
|
|
import storage_log
|
|
from errors import *
|
|
from devices import *
|
|
from devicetree import DeviceTree
|
|
from deviceaction import *
|
|
from formats import getFormat
|
|
from formats import get_device_format_class
|
|
from formats import get_default_filesystem_type
|
|
from devicelibs.lvm import safeLvmName
|
|
from devicelibs.dm import name_from_dm_node
|
|
from devicelibs.crypto import generateBackupPassphrase
|
|
from devicelibs.mpath import MultipathConfigWriter
|
|
from devicelibs.edd import get_edd_dict
|
|
from udev import *
|
|
import iscsi
|
|
import fcoe
|
|
import zfcp
|
|
import dasd
|
|
|
|
import shelve
|
|
import contextlib
|
|
|
|
import gettext
|
|
_ = lambda x: gettext.ldgettext("anaconda", x)
|
|
|
|
import logging
|
|
log = logging.getLogger("storage")
|
|
|
|
def storageInitialize(anaconda):
|
|
storage = anaconda.storage
|
|
|
|
storage.shutdown()
|
|
|
|
if anaconda.dir == DISPATCH_BACK:
|
|
return
|
|
|
|
# touch /dev/.in_sysinit so that /lib/udev/rules.d/65-md-incremental.rules
|
|
# does not mess with any mdraid sets
|
|
open("/dev/.in_sysinit", "w")
|
|
|
|
# XXX I don't understand why I have to do this, but this is needed to
|
|
# populate the udev db
|
|
udev_trigger(subsystem="block", action="change")
|
|
|
|
|
|
anaconda.intf.resetInitializeDiskQuestion()
|
|
anaconda.intf.resetReinitInconsistentLVMQuestion()
|
|
|
|
# Set up the protected partitions list now.
|
|
if anaconda.protected:
|
|
storage.protectedDevSpecs.extend(anaconda.protected)
|
|
storage.reset()
|
|
|
|
if not flags.livecdInstall and not storage.protectedDevices:
|
|
if anaconda.upgrade:
|
|
return
|
|
else:
|
|
anaconda.intf.messageWindow(_("Unknown Device"),
|
|
_("The installation source given by device %s "
|
|
"could not be found. Please check your "
|
|
"parameters and try again.") % anaconda.protected,
|
|
type="custom", custom_buttons = [_("_Exit installer")])
|
|
sys.exit(1)
|
|
else:
|
|
storage.reset()
|
|
|
|
if not storage.disks:
|
|
rc = anaconda.intf.messageWindow(_("No disks found"),
|
|
_("No usable disks have been found."),
|
|
type="custom",
|
|
custom_buttons = [_("Back"), _("_Exit installer")],
|
|
default=0)
|
|
if rc == 0:
|
|
return DISPATCH_BACK
|
|
sys.exit(1)
|
|
|
|
# dispatch.py helper function
|
|
def storageComplete(anaconda):
|
|
if anaconda.dir == DISPATCH_BACK:
|
|
rc = anaconda.intf.messageWindow(_("Installation cannot continue."),
|
|
_("The storage configuration you have "
|
|
"chosen has already been activated. You "
|
|
"can no longer return to the disk editing "
|
|
"screen. Would you like to continue with "
|
|
"the installation process?"),
|
|
type = "yesno")
|
|
if rc == 0:
|
|
sys.exit(0)
|
|
return DISPATCH_FORWARD
|
|
|
|
devs = anaconda.storage.devicetree.getDevicesByType("luks/dm-crypt")
|
|
existing_luks = False
|
|
new_luks = False
|
|
for dev in devs:
|
|
if dev.exists:
|
|
existing_luks = True
|
|
else:
|
|
new_luks = True
|
|
|
|
if (anaconda.storage.encryptedAutoPart or new_luks) and \
|
|
not anaconda.storage.encryptionPassphrase:
|
|
while True:
|
|
(passphrase, retrofit) = anaconda.intf.getLuksPassphrase(preexist=existing_luks)
|
|
if passphrase:
|
|
anaconda.storage.encryptionPassphrase = passphrase
|
|
anaconda.storage.encryptionRetrofit = retrofit
|
|
break
|
|
else:
|
|
rc = anaconda.intf.messageWindow(_("Encrypt device?"),
|
|
_("You specified block device encryption "
|
|
"should be enabled, but you have not "
|
|
"supplied a passphrase. If you do not "
|
|
"go back and provide a passphrase, "
|
|
"block device encryption will be "
|
|
"disabled."),
|
|
type="custom",
|
|
custom_buttons=[_("Back"), _("Continue")],
|
|
default=0)
|
|
if rc == 1:
|
|
log.info("user elected to not encrypt any devices.")
|
|
undoEncryption(anaconda.storage)
|
|
anaconda.storage.encryptedAutoPart = False
|
|
break
|
|
|
|
if anaconda.storage.encryptionPassphrase:
|
|
for dev in anaconda.storage.devices:
|
|
if dev.format.type == "luks" and not dev.format.exists:
|
|
dev.format.passphrase = anaconda.storage.encryptionPassphrase
|
|
|
|
if anaconda.ksdata:
|
|
return
|
|
|
|
rc = anaconda.intf.messageWindow(_("Writing storage configuration to disk"),
|
|
_("The partitioning options you have selected "
|
|
"will now be written to disk. Any "
|
|
"data on deleted or reformatted partitions "
|
|
"will be lost."),
|
|
type = "custom", custom_icon="warning",
|
|
custom_buttons=[_("Go _back"),
|
|
_("_Write changes to disk")],
|
|
default = 0)
|
|
|
|
# Make sure that all is down, even the disks that we setup after popluate.
|
|
anaconda.storage.devicetree.teardownAll()
|
|
|
|
if rc == 0:
|
|
return DISPATCH_BACK
|
|
|
|
def writeEscrowPackets(anaconda):
|
|
escrowDevices = filter(lambda d: d.format.type == "luks" and \
|
|
d.format.escrow_cert,
|
|
anaconda.storage.devices)
|
|
|
|
if not escrowDevices:
|
|
return
|
|
|
|
log.debug("escrow: writeEscrowPackets start")
|
|
|
|
wait_win = anaconda.intf.waitWindow(_("Running..."),
|
|
_("Storing encryption keys"))
|
|
|
|
nss.nss.nss_init_nodb() # Does nothing if NSS is already initialized
|
|
|
|
backupPassphrase = generateBackupPassphrase()
|
|
try:
|
|
for device in escrowDevices:
|
|
log.debug("escrow: device %s: %s" %
|
|
(repr(device.path), repr(device.format.type)))
|
|
device.format.escrow(anaconda.rootPath + "/root",
|
|
backupPassphrase)
|
|
|
|
wait_win.pop()
|
|
except (IOError, RuntimeError) as e:
|
|
wait_win.pop()
|
|
anaconda.intf.messageWindow(_("Error"),
|
|
_("Error storing an encryption key: "
|
|
"%s\n") % str(e), type="custom",
|
|
custom_icon="error",
|
|
custom_buttons=[_("_Exit installer")])
|
|
sys.exit(1)
|
|
|
|
log.debug("escrow: writeEscrowPackets done")
|
|
|
|
|
|
def undoEncryption(storage):
|
|
for device in storage.devicetree.getDevicesByType("luks/dm-crypt"):
|
|
if device.exists:
|
|
continue
|
|
|
|
slave = device.slave
|
|
format = device.format
|
|
|
|
# set any devices that depended on the luks device to now depend on
|
|
# the former slave device
|
|
for child in storage.devicetree.getChildren(device):
|
|
child.parents.remove(device)
|
|
device.removeChild()
|
|
child.parents.append(slave)
|
|
|
|
storage.devicetree.registerAction(ActionDestroyFormat(device))
|
|
storage.devicetree.registerAction(ActionDestroyDevice(device))
|
|
storage.devicetree.registerAction(ActionDestroyFormat(slave))
|
|
storage.devicetree.registerAction(ActionCreateFormat(slave, format))
|
|
|
|
class Storage(object):
|
|
def __init__(self, anaconda):
|
|
self.anaconda = anaconda
|
|
|
|
# storage configuration variables
|
|
self.ignoredDisks = []
|
|
self.exclusiveDisks = []
|
|
self.doAutoPart = False
|
|
self.clearPartType = None
|
|
self.clearPartDisks = []
|
|
self.encryptedAutoPart = True
|
|
self.encryptionPassphrase = None
|
|
self.escrowCertificates = {}
|
|
self.autoPartEscrowCert = None
|
|
self.autoPartAddBackupPassphrase = False
|
|
self.encryptionRetrofit = False
|
|
self.reinitializeDisks = False
|
|
self.zeroMbr = None
|
|
self.protectedDevSpecs = []
|
|
self.autoPartitionRequests = []
|
|
self.eddDict = {}
|
|
|
|
self.__luksDevs = {}
|
|
|
|
self.iscsi = iscsi.iscsi()
|
|
self.fcoe = fcoe.fcoe()
|
|
self.zfcp = zfcp.ZFCP()
|
|
self.dasd = dasd.DASD()
|
|
|
|
self._nextID = 0
|
|
self.defaultFSType = get_default_filesystem_type()
|
|
self.defaultBootFSType = get_default_filesystem_type(boot=True)
|
|
self._dumpFile = "/tmp/storage.state"
|
|
|
|
# these will both be empty until our reset method gets called
|
|
self.devicetree = DeviceTree(intf=self.anaconda.intf,
|
|
ignored=self.ignoredDisks,
|
|
exclusive=self.exclusiveDisks,
|
|
type=self.clearPartType,
|
|
clear=self.clearPartDisks,
|
|
reinitializeDisks=self.reinitializeDisks,
|
|
protected=self.protectedDevSpecs,
|
|
zeroMbr=self.zeroMbr,
|
|
passphrase=self.encryptionPassphrase,
|
|
luksDict=self.__luksDevs,
|
|
iscsi=self.iscsi,
|
|
dasd=self.dasd)
|
|
self.fsset = FSSet(self.devicetree, self.anaconda.rootPath)
|
|
|
|
def doIt(self):
|
|
self.devicetree.processActions()
|
|
self.doEncryptionPassphraseRetrofits()
|
|
|
|
# now set the boot partition's flag
|
|
try:
|
|
boot = self.anaconda.platform.bootDevice()
|
|
if boot.type == "mdarray":
|
|
bootDevs = boot.parents
|
|
else:
|
|
bootDevs = [boot]
|
|
except DeviceError:
|
|
bootDevs = []
|
|
else:
|
|
for dev in bootDevs:
|
|
if hasattr(dev, "bootable"):
|
|
# Dos labels can only have one partition marked as active
|
|
# and unmarking ie the windows partition is not a good idea
|
|
skip = False
|
|
if dev.disk.format.partedDisk.type == "msdos":
|
|
for p in dev.disk.format.partedDisk.partitions:
|
|
if p.type == parted.PARTITION_NORMAL and \
|
|
p.getFlag(parted.PARTITION_BOOT):
|
|
skip = True
|
|
break
|
|
if skip:
|
|
log.info("not setting boot flag on %s as there is"
|
|
"another active partition" % dev.name)
|
|
continue
|
|
log.info("setting boot flag on %s" % dev.name)
|
|
dev.bootable = True
|
|
dev.disk.setup()
|
|
dev.disk.format.commitToDisk()
|
|
|
|
self.dumpState("final")
|
|
|
|
@property
|
|
def nextID(self):
|
|
id = self._nextID
|
|
self._nextID += 1
|
|
return id
|
|
|
|
def shutdown(self):
|
|
try:
|
|
self.devicetree.teardownAll()
|
|
except Exception as e:
|
|
log.error("failure tearing down device tree: %s" % e)
|
|
|
|
self.zfcp.shutdown()
|
|
|
|
# TODO: iscsi.shutdown()
|
|
|
|
def reset(self):
|
|
""" Reset storage configuration to reflect actual system state.
|
|
|
|
This should rescan from scratch but not clobber user-obtained
|
|
information like passphrases, iscsi config, &c
|
|
|
|
"""
|
|
# save passphrases for luks devices so we don't have to reprompt
|
|
self.encryptionPassphrase = None
|
|
for device in self.devices:
|
|
if device.format.type == "luks" and device.format.exists:
|
|
self.__luksDevs[device.format.uuid] = device.format._LUKS__passphrase
|
|
|
|
w = self.anaconda.intf.waitWindow(_("Finding Devices"),
|
|
_("Finding storage devices"))
|
|
self.iscsi.startup(self.anaconda.intf)
|
|
self.fcoe.startup(self.anaconda.intf)
|
|
self.zfcp.startup()
|
|
self.dasd.startup(intf=self.anaconda.intf, zeroMbr=self.zeroMbr)
|
|
if self.anaconda.upgrade:
|
|
clearPartType = CLEARPART_TYPE_NONE
|
|
else:
|
|
clearPartType = self.clearPartType
|
|
|
|
self.devicetree = DeviceTree(intf=self.anaconda.intf,
|
|
ignored=self.ignoredDisks,
|
|
exclusive=self.exclusiveDisks,
|
|
type=clearPartType,
|
|
clear=self.clearPartDisks,
|
|
reinitializeDisks=self.reinitializeDisks,
|
|
protected=self.protectedDevSpecs,
|
|
zeroMbr=self.zeroMbr,
|
|
passphrase=self.encryptionPassphrase,
|
|
luksDict=self.__luksDevs,
|
|
iscsi=self.iscsi,
|
|
dasd=self.dasd)
|
|
self.devicetree.populate()
|
|
self.fsset = FSSet(self.devicetree, self.anaconda.rootPath)
|
|
self.eddDict = get_edd_dict(self.partitioned)
|
|
self.anaconda.rootParts = None
|
|
self.anaconda.upgradeRoot = None
|
|
self.dumpState("initial")
|
|
w.pop()
|
|
|
|
@property
|
|
def devices(self):
|
|
""" A list of all the devices in the device tree. """
|
|
devices = self.devicetree.devices
|
|
devices.sort(key=lambda d: d.name)
|
|
return devices
|
|
|
|
@property
|
|
def disks(self):
|
|
""" A list of the disks in the device tree.
|
|
|
|
Ignored disks are not included, as are disks with no media present.
|
|
|
|
This is based on the current state of the device tree and
|
|
does not necessarily reflect the actual on-disk state of the
|
|
system's disks.
|
|
"""
|
|
disks = []
|
|
for device in self.devicetree.devices:
|
|
if device.isDisk:
|
|
if not device.mediaPresent:
|
|
log.info("Skipping disk: %s: No media present" % device.name)
|
|
continue
|
|
disks.append(device)
|
|
disks.sort(key=lambda d: d.name, cmp=self.compareDisks)
|
|
return disks
|
|
|
|
@property
|
|
def partitioned(self):
|
|
""" A list of the partitioned devices in the device tree.
|
|
|
|
Ignored devices are not included, nor disks with no media present.
|
|
|
|
Devices of types for which partitioning is not supported are also
|
|
not included.
|
|
|
|
This is based on the current state of the device tree and
|
|
does not necessarily reflect the actual on-disk state of the
|
|
system's disks.
|
|
"""
|
|
partitioned = []
|
|
for device in self.devicetree.devices:
|
|
if not device.partitioned:
|
|
continue
|
|
|
|
if not device.mediaPresent:
|
|
log.info("Skipping device: %s: No media present" % device.name)
|
|
continue
|
|
|
|
partitioned.append(device)
|
|
|
|
partitioned.sort(key=lambda d: d.name)
|
|
return partitioned
|
|
|
|
@property
|
|
def partitions(self):
|
|
""" A list of the partitions in the device tree.
|
|
|
|
This is based on the current state of the device tree and
|
|
does not necessarily reflect the actual on-disk state of the
|
|
system's disks.
|
|
"""
|
|
partitions = self.devicetree.getDevicesByInstance(PartitionDevice)
|
|
partitions.sort(key=lambda d: d.name)
|
|
return partitions
|
|
|
|
@property
|
|
def vgs(self):
|
|
""" A list of the LVM Volume Groups in the device tree.
|
|
|
|
This is based on the current state of the device tree and
|
|
does not necessarily reflect the actual on-disk state of the
|
|
system's disks.
|
|
"""
|
|
vgs = self.devicetree.getDevicesByType("lvmvg")
|
|
vgs.sort(key=lambda d: d.name)
|
|
return vgs
|
|
|
|
@property
|
|
def lvs(self):
|
|
""" A list of the LVM Logical Volumes in the device tree.
|
|
|
|
This is based on the current state of the device tree and
|
|
does not necessarily reflect the actual on-disk state of the
|
|
system's disks.
|
|
"""
|
|
lvs = self.devicetree.getDevicesByType("lvmlv")
|
|
lvs.sort(key=lambda d: d.name)
|
|
return lvs
|
|
|
|
@property
|
|
def pvs(self):
|
|
""" A list of the LVM Physical Volumes in the device tree.
|
|
|
|
This is based on the current state of the device tree and
|
|
does not necessarily reflect the actual on-disk state of the
|
|
system's disks.
|
|
"""
|
|
devices = self.devicetree.devices
|
|
pvs = [d for d in devices if d.format.type == "lvmpv"]
|
|
pvs.sort(key=lambda d: d.name)
|
|
return pvs
|
|
|
|
def unusedPVs(self, vg=None):
|
|
unused = []
|
|
for pv in self.pvs:
|
|
used = False
|
|
for _vg in self.vgs:
|
|
if _vg.dependsOn(pv) and _vg != vg:
|
|
used = True
|
|
break
|
|
elif _vg == vg:
|
|
break
|
|
if not used:
|
|
unused.append(pv)
|
|
return unused
|
|
|
|
@property
|
|
def mdarrays(self):
|
|
""" A list of the MD arrays in the device tree.
|
|
|
|
This is based on the current state of the device tree and
|
|
does not necessarily reflect the actual on-disk state of the
|
|
system's disks.
|
|
"""
|
|
arrays = self.devicetree.getDevicesByType("mdarray")
|
|
arrays.sort(key=lambda d: d.name)
|
|
return arrays
|
|
|
|
@property
|
|
def mdcontainers(self):
|
|
""" A list of the MD containers in the device tree. """
|
|
arrays = self.devicetree.getDevicesByType("mdcontainer")
|
|
arrays.sort(key=lambda d: d.name)
|
|
return arrays
|
|
|
|
@property
|
|
def mdmembers(self):
|
|
""" A list of the MD member devices in the device tree.
|
|
|
|
This is based on the current state of the device tree and
|
|
does not necessarily reflect the actual on-disk state of the
|
|
system's disks.
|
|
"""
|
|
devices = self.devicetree.devices
|
|
members = [d for d in devices if d.format.type == "mdmember"]
|
|
members.sort(key=lambda d: d.name)
|
|
return members
|
|
|
|
def unusedMDMembers(self, array=None):
|
|
unused = []
|
|
for member in self.mdmembers:
|
|
used = False
|
|
for _array in self.mdarrays + self.mdcontainers:
|
|
if _array.dependsOn(member) and _array != array:
|
|
used = True
|
|
break
|
|
elif _array == array:
|
|
break
|
|
if not used:
|
|
unused.append(member)
|
|
return unused
|
|
|
|
@property
|
|
def unusedMDMinors(self):
|
|
""" Return a list of unused minors for use in RAID. """
|
|
raidMinors = range(0,32)
|
|
for array in self.mdarrays + self.mdcontainers:
|
|
if array.minor is not None and array.minor in raidMinors:
|
|
raidMinors.remove(array.minor)
|
|
return raidMinors
|
|
|
|
@property
|
|
def swaps(self):
|
|
""" A list of the swap devices in the device tree.
|
|
|
|
This is based on the current state of the device tree and
|
|
does not necessarily reflect the actual on-disk state of the
|
|
system's disks.
|
|
"""
|
|
devices = self.devicetree.devices
|
|
swaps = [d for d in devices if d.format.type == "swap"]
|
|
swaps.sort(key=lambda d: d.name)
|
|
return swaps
|
|
|
|
@property
|
|
def protectedDevices(self):
|
|
devices = self.devicetree.devices
|
|
protected = [d for d in devices if d.protected]
|
|
protected.sort(key=lambda d: d.name)
|
|
return protected
|
|
|
|
def exceptionDisks(self):
|
|
""" Return a list of removable devices to save exceptions to.
|
|
|
|
FIXME: This raises the problem that the device tree can be
|
|
in a state that does not reflect that actual current
|
|
state of the system at any given point.
|
|
|
|
We need a way to provide direct scanning of disks,
|
|
partitions, and filesystems without relying on the
|
|
larger objects' correctness.
|
|
|
|
Also, we need to find devices that have just been made
|
|
available for the purpose of storing the exception
|
|
report.
|
|
"""
|
|
# When a usb is connected from before the start of the installation,
|
|
# it is not correctly detected.
|
|
udev_trigger(subsystem="block", action="change")
|
|
self.reset()
|
|
|
|
dests = []
|
|
|
|
for disk in self.disks:
|
|
if not disk.removable and \
|
|
disk.format is not None and \
|
|
disk.format.mountable:
|
|
dests.append([disk.path, disk.name])
|
|
|
|
for part in self.partitions:
|
|
if not part.disk.removable:
|
|
continue
|
|
|
|
elif part.partedPartition.active and \
|
|
not part.partedPartition.getFlag(parted.PARTITION_RAID) and \
|
|
not part.partedPartition.getFlag(parted.PARTITION_LVM) and \
|
|
part.format is not None and part.format.mountable:
|
|
dests.append([part.path, part.name])
|
|
|
|
return dests
|
|
|
|
def deviceImmutable(self, device, ignoreProtected=False):
|
|
""" Return any reason the device cannot be modified/removed.
|
|
|
|
Return False if the device can be removed.
|
|
|
|
Devices that cannot be removed include:
|
|
|
|
- protected partitions
|
|
- devices that are part of an md array or lvm vg
|
|
- extended partition containing logical partitions that
|
|
meet any of the above criteria
|
|
|
|
"""
|
|
if not isinstance(device, Device):
|
|
raise ValueError("arg1 (%s) must be a Device instance" % device)
|
|
|
|
if not ignoreProtected and device.protected:
|
|
return _("This partition is holding the data for the hard "
|
|
"drive install.")
|
|
elif isinstance(device, PartitionDevice) and device.isProtected:
|
|
# LDL formatted DASDs always have one partition, you'd have to
|
|
# reformat the DASD in CDL mode to get rid of it
|
|
return _("You cannot delete a partition of a LDL formatted "
|
|
"DASD.")
|
|
elif device.format.type == "mdmember":
|
|
for array in self.mdarrays + self.mdcontainers:
|
|
if array.dependsOn(device):
|
|
if array.minor is not None:
|
|
return _("This device is part of the RAID "
|
|
"device %s.") % (array.path,)
|
|
else:
|
|
return _("This device is part of a RAID device.")
|
|
elif device.format.type == "lvmpv":
|
|
for vg in self.vgs:
|
|
if vg.dependsOn(device):
|
|
if vg.name is not None:
|
|
return _("This device is part of the LVM "
|
|
"volume group '%s'.") % (vg.name,)
|
|
else:
|
|
return _("This device is part of a LVM volume "
|
|
"group.")
|
|
elif device.format.type == "luks":
|
|
try:
|
|
luksdev = self.devicetree.getChildren(device)[0]
|
|
except IndexError:
|
|
pass
|
|
else:
|
|
return self.deviceImmutable(luksdev)
|
|
elif isinstance(device, PartitionDevice) and device.isExtended:
|
|
reasons = {}
|
|
for dep in self.deviceDeps(device):
|
|
reason = self.deviceImmutable(dep)
|
|
if reason:
|
|
reasons[dep.path] = reason
|
|
if reasons:
|
|
msg = _("This device is an extended partition which "
|
|
"contains logical partitions that cannot be "
|
|
"deleted:\n\n")
|
|
for dev in reasons:
|
|
msg += "%s: %s" % (dev, reasons[dev])
|
|
return msg
|
|
|
|
for i in self.devicetree.immutableDevices:
|
|
if i[0] == device.name:
|
|
return i[1]
|
|
|
|
return False
|
|
|
|
def deviceDeps(self, device):
|
|
return self.devicetree.getDependentDevices(device)
|
|
|
|
def newPartition(self, *args, **kwargs):
|
|
""" Return a new PartitionDevice instance for configuring. """
|
|
if kwargs.has_key("fmt_type"):
|
|
kwargs["format"] = getFormat(kwargs.pop("fmt_type"),
|
|
mountpoint=kwargs.pop("mountpoint",
|
|
None),
|
|
**kwargs.pop("fmt_args", {}))
|
|
|
|
if kwargs.has_key("disks"):
|
|
parents = kwargs.pop("disks")
|
|
if isinstance(parents, Device):
|
|
kwargs["parents"] = [parents]
|
|
else:
|
|
kwargs["parents"] = parents
|
|
|
|
if kwargs.has_key("name"):
|
|
name = kwargs.pop("name")
|
|
else:
|
|
name = "req%d" % self.nextID
|
|
|
|
return PartitionDevice(name, *args, **kwargs)
|
|
|
|
def newMDArray(self, *args, **kwargs):
|
|
""" Return a new MDRaidArrayDevice instance for configuring. """
|
|
if kwargs.has_key("fmt_type"):
|
|
kwargs["format"] = getFormat(kwargs.pop("fmt_type"),
|
|
mountpoint=kwargs.pop("mountpoint",
|
|
None))
|
|
|
|
if kwargs.has_key("minor"):
|
|
kwargs["minor"] = int(kwargs["minor"])
|
|
else:
|
|
kwargs["minor"] = self.unusedMDMinors[0]
|
|
|
|
if kwargs.has_key("name"):
|
|
name = kwargs.pop("name")
|
|
else:
|
|
name = "md%d" % kwargs["minor"]
|
|
|
|
return MDRaidArrayDevice(name, *args, **kwargs)
|
|
|
|
def newVG(self, *args, **kwargs):
|
|
""" Return a new LVMVolumeGroupDevice instance. """
|
|
pvs = kwargs.pop("pvs", [])
|
|
for pv in pvs:
|
|
if pv not in self.devices:
|
|
raise ValueError("pv is not in the device tree")
|
|
|
|
if kwargs.has_key("name"):
|
|
name = kwargs.pop("name")
|
|
else:
|
|
name = self.createSuggestedVGName(self.anaconda.network)
|
|
|
|
if name in [d.name for d in self.devices]:
|
|
raise ValueError("name already in use")
|
|
|
|
return LVMVolumeGroupDevice(name, pvs, *args, **kwargs)
|
|
|
|
def newLV(self, *args, **kwargs):
|
|
""" Return a new LVMLogicalVolumeDevice instance. """
|
|
if kwargs.has_key("vg"):
|
|
vg = kwargs.pop("vg")
|
|
|
|
mountpoint = kwargs.pop("mountpoint", None)
|
|
if kwargs.has_key("fmt_type"):
|
|
kwargs["format"] = getFormat(kwargs.pop("fmt_type"),
|
|
mountpoint=mountpoint)
|
|
|
|
if kwargs.has_key("name"):
|
|
name = kwargs.pop("name")
|
|
else:
|
|
if kwargs.get("format") and kwargs["format"].type == "swap":
|
|
swap = True
|
|
else:
|
|
swap = False
|
|
name = self.createSuggestedLVName(vg,
|
|
swap=swap,
|
|
mountpoint=mountpoint)
|
|
|
|
if name in [d.name for d in self.devices]:
|
|
raise ValueError("name already in use")
|
|
|
|
return LVMLogicalVolumeDevice(name, vg, *args, **kwargs)
|
|
|
|
def createDevice(self, device):
|
|
""" Schedule creation of a device.
|
|
|
|
TODO: We could do some things here like assign the next
|
|
available raid minor if one isn't already set.
|
|
"""
|
|
self.devicetree.registerAction(ActionCreateDevice(device))
|
|
if device.format.type:
|
|
self.devicetree.registerAction(ActionCreateFormat(device))
|
|
|
|
def destroyDevice(self, device):
|
|
""" Schedule destruction of a device. """
|
|
if device.format.exists and device.format.type:
|
|
# schedule destruction of any formatting while we're at it
|
|
self.devicetree.registerAction(ActionDestroyFormat(device))
|
|
|
|
action = ActionDestroyDevice(device)
|
|
self.devicetree.registerAction(action)
|
|
|
|
def formatDevice(self, device, format):
|
|
""" Schedule formatting of a device. """
|
|
self.devicetree.registerAction(ActionDestroyFormat(device))
|
|
self.devicetree.registerAction(ActionCreateFormat(device, format))
|
|
|
|
def formatByDefault(self, device):
|
|
"""Return whether the device should be reformatted by default."""
|
|
formatlist = ['/boot', '/var', '/tmp', '/usr']
|
|
exceptlist = ['/home', '/usr/local', '/opt', '/var/www']
|
|
|
|
if not device.format.linuxNative:
|
|
return False
|
|
|
|
if device.format.mountable:
|
|
if not device.format.mountpoint:
|
|
return False
|
|
|
|
if device.format.mountpoint == "/" or \
|
|
device.format.mountpoint in formatlist:
|
|
return True
|
|
|
|
for p in formatlist:
|
|
if device.format.mountpoint.startswith(p):
|
|
for q in exceptlist:
|
|
if device.format.mountpoint.startswith(q):
|
|
return False
|
|
return True
|
|
elif device.format.type == "swap":
|
|
return True
|
|
|
|
# be safe for anything else and default to off
|
|
return False
|
|
|
|
def extendedPartitionsSupported(self):
|
|
""" Return whether any disks support extended partitions."""
|
|
for disk in self.partitioned:
|
|
if disk.format.partedDisk.supportsFeature(parted.DISK_TYPE_EXTENDED):
|
|
return True
|
|
return False
|
|
|
|
def createSuggestedVGName(self, network):
|
|
""" Return a reasonable, unused VG name. """
|
|
# try to create a volume group name incorporating the hostname
|
|
hn = network.hostname
|
|
vgnames = [vg.name for vg in self.vgs]
|
|
if hn is not None and hn != '':
|
|
if hn == 'localhost' or hn == 'localhost.localdomain':
|
|
vgtemplate = "VolGroup"
|
|
elif hn.find('.') != -1:
|
|
template = "vg_%s" % (hn.split('.')[0].lower(),)
|
|
vgtemplate = safeLvmName(template)
|
|
else:
|
|
template = "vg_%s" % (hn.lower(),)
|
|
vgtemplate = safeLvmName(template)
|
|
else:
|
|
vgtemplate = "VolGroup"
|
|
|
|
if vgtemplate not in vgnames and \
|
|
vgtemplate not in lvm.lvm_vg_blacklist:
|
|
return vgtemplate
|
|
else:
|
|
i = 0
|
|
while 1:
|
|
tmpname = "%s%02d" % (vgtemplate, i,)
|
|
if not tmpname in vgnames and \
|
|
tmpname not in lvm.lvm_vg_blacklist:
|
|
break
|
|
|
|
i += 1
|
|
if i > 99:
|
|
tmpname = ""
|
|
|
|
return tmpname
|
|
|
|
def createSuggestedLVName(self, vg, swap=None, mountpoint=None):
|
|
""" Return a suitable, unused name for a new logical volume. """
|
|
# FIXME: this is not at all guaranteed to work
|
|
if mountpoint:
|
|
# try to incorporate the mountpoint into the name
|
|
if mountpoint == '/':
|
|
lvtemplate = 'lv_root'
|
|
else:
|
|
if mountpoint.startswith("/"):
|
|
template = "lv_%s" % mountpoint[1:]
|
|
else:
|
|
template = "lv_%s" % (mountpoint,)
|
|
|
|
lvtemplate = safeLvmName(template)
|
|
else:
|
|
if swap:
|
|
if len([s for s in self.swaps if s in vg.lvs]):
|
|
idx = len([s for s in self.swaps if s in vg.lvs])
|
|
while True:
|
|
lvtemplate = "lv_swap%02d" % idx
|
|
if lvtemplate in [lv.lvname for lv in vg.lvs]:
|
|
idx += 1
|
|
else:
|
|
break
|
|
else:
|
|
lvtemplate = "lv_swap"
|
|
else:
|
|
idx = len(vg.lvs)
|
|
while True:
|
|
lvtemplate = "LogVol%02d" % idx
|
|
if lvtemplate in [l.lvname for l in vg.lvs]:
|
|
idx += 1
|
|
else:
|
|
break
|
|
|
|
return lvtemplate
|
|
|
|
def doEncryptionPassphraseRetrofits(self):
|
|
""" Add the global passphrase to all preexisting LUKS devices.
|
|
|
|
This establishes a common passphrase for all encrypted devices
|
|
in the system so that users only have to enter one passphrase
|
|
during system boot.
|
|
"""
|
|
if not self.encryptionRetrofit:
|
|
return
|
|
|
|
for device in self.devices:
|
|
if device.format.type == "luks" and \
|
|
device.format._LUKS__passphrase != self.encryptionPassphrase:
|
|
log.info("adding new passphrase to preexisting encrypted "
|
|
"device %s" % device.path)
|
|
try:
|
|
device.format.addPassphrase(self.encryptionPassphrase)
|
|
except CryptoError:
|
|
log.error("failed to add new passphrase to existing "
|
|
"device %s" % device.path)
|
|
|
|
def sanityCheck(self):
|
|
""" Run a series of tests to verify the storage configuration.
|
|
|
|
This function is called at the end of partitioning so that
|
|
we can make sure you don't have anything silly (like no /,
|
|
a really small /, etc). Returns (errors, warnings) where
|
|
each is a list of strings.
|
|
"""
|
|
checkSizes = [('/usr', 250), ('/tmp', 50), ('/var', 384),
|
|
('/home', 100), ('/boot', 75)]
|
|
warnings = []
|
|
errors = []
|
|
|
|
mustbeonlinuxfs = ['/', '/var', '/tmp', '/usr', '/home', '/usr/share', '/usr/lib']
|
|
mustbeonroot = ['/bin','/dev','/sbin','/etc','/lib','/root', '/mnt', 'lost+found', '/proc']
|
|
|
|
filesystems = self.mountpoints
|
|
root = self.fsset.rootDevice
|
|
swaps = self.fsset.swapDevices
|
|
try:
|
|
boot = self.anaconda.platform.bootDevice()
|
|
except DeviceError:
|
|
boot = None
|
|
|
|
if not root:
|
|
errors.append(_("You have not defined a root partition (/), "
|
|
"which is required for installation of %s "
|
|
"to continue.") % (productName,))
|
|
|
|
if root and root.size < 250:
|
|
warnings.append(_("Your root partition is less than 250 "
|
|
"megabytes which is usually too small to "
|
|
"install %s.") % (productName,))
|
|
|
|
if (root and
|
|
root.size < self.anaconda.backend.getMinimumSizeMB("/")):
|
|
errors.append(_("Your / partition is less than %(min)s "
|
|
"MB which is lower than recommended "
|
|
"for a normal %(productName)s install.")
|
|
% {'min': self.anaconda.backend.getMinimumSizeMB("/"),
|
|
'productName': productName})
|
|
|
|
# livecds have to have the rootfs type match up
|
|
if (root and
|
|
self.anaconda.backend.rootFsType and
|
|
root.format.type != self.anaconda.backend.rootFsType):
|
|
errors.append(_("Your / partition does not match the "
|
|
"the live image you are installing from. "
|
|
"It must be formatted as %s.")
|
|
% (self.anaconda.backend.rootFsType,))
|
|
|
|
for (mount, size) in checkSizes:
|
|
if mount in filesystems and filesystems[mount].size < size:
|
|
warnings.append(_("Your %(mount)s partition is less than "
|
|
"%(size)s megabytes which is lower than "
|
|
"recommended for a normal %(productName)s "
|
|
"install.")
|
|
% {'mount': mount, 'size': size,
|
|
'productName': productName})
|
|
|
|
usb_disks = []
|
|
firewire_disks = []
|
|
for disk in self.disks:
|
|
if isys.driveUsesModule(disk.name, ["usb-storage", "ub"]):
|
|
usb_disks.append(disk)
|
|
elif isys.driveUsesModule(disk.name, ["sbp2", "firewire-sbp2"]):
|
|
firewire_disks.append(disk)
|
|
|
|
uses_usb = False
|
|
uses_firewire = False
|
|
for device in filesystems.values():
|
|
for disk in usb_disks:
|
|
if device.dependsOn(disk):
|
|
uses_usb = True
|
|
break
|
|
|
|
for disk in firewire_disks:
|
|
if device.dependsOn(disk):
|
|
uses_firewire = True
|
|
break
|
|
|
|
if uses_usb:
|
|
warnings.append(_("Installing on a USB device. This may "
|
|
"or may not produce a working system."))
|
|
if uses_firewire:
|
|
warnings.append(_("Installing on a FireWire device. This may "
|
|
"or may not produce a working system."))
|
|
|
|
errors.extend(self.anaconda.platform.checkBootRequest(boot))
|
|
|
|
if not swaps:
|
|
if iutil.memInstalled() < isys.EARLY_SWAP_RAM:
|
|
errors.append(_("You have not specified a swap partition. "
|
|
"Due to the amount of memory present, a "
|
|
"swap partition is required to complete "
|
|
"installation."))
|
|
else:
|
|
warnings.append(_("You have not specified a swap partition. "
|
|
"Although not strictly required in all cases, "
|
|
"it will significantly improve performance "
|
|
"for most installations."))
|
|
|
|
for (mountpoint, dev) in filesystems.items():
|
|
if mountpoint in mustbeonroot:
|
|
errors.append(_("This mount point is invalid. The %s directory must "
|
|
"be on the / file system.") % mountpoint)
|
|
|
|
if mountpoint in mustbeonlinuxfs and (not dev.format.mountable or not dev.format.linuxNative):
|
|
errors.append(_("The mount point %s must be on a linux file system.") % mountpoint)
|
|
|
|
return (errors, warnings)
|
|
|
|
def isProtected(self, device):
|
|
""" Return True is the device is protected. """
|
|
return device.protected
|
|
|
|
def checkNoDisks(self):
|
|
"""Check that there are valid disk devices."""
|
|
if not self.disks:
|
|
self.anaconda.intf.messageWindow(_("No Drives Found"),
|
|
_("An error has occurred - no valid devices were "
|
|
"found on which to create new file systems. "
|
|
"Please check your hardware for the cause "
|
|
"of this problem."))
|
|
return True
|
|
return False
|
|
|
|
def dumpState(self, suffix):
|
|
""" Dump the current device list to the storage shelf. """
|
|
key = "devices.%d.%s" % (time.time(), suffix)
|
|
with contextlib.closing(shelve.open(self._dumpFile)) as shelf:
|
|
shelf[key] = [d.dict for d in self.devices]
|
|
|
|
def write(self, instPath):
|
|
self.fsset.write(instPath)
|
|
self.iscsi.write(instPath, self.anaconda)
|
|
self.fcoe.write(instPath, self.anaconda)
|
|
self.zfcp.write(instPath)
|
|
self.dasd.write(instPath)
|
|
|
|
def writeKS(self, f):
|
|
def useExisting(lst):
|
|
foundCreateDevice = False
|
|
foundCreateFormat = False
|
|
|
|
for l in lst:
|
|
if isinstance(l, ActionCreateDevice):
|
|
foundCreateDevice = True
|
|
elif isinstance(l, ActionCreateFormat):
|
|
foundCreateFormat = True
|
|
|
|
return (foundCreateFormat and not foundCreateDevice)
|
|
|
|
log.warning("Storage.writeKS not completely implemented")
|
|
f.write("# The following is the partition information you requested\n")
|
|
f.write("# Note that any partitions you deleted are not expressed\n")
|
|
f.write("# here so unless you clear all partitions first, this is\n")
|
|
f.write("# not guaranteed to work\n")
|
|
|
|
# clearpart
|
|
if self.clearPartType is None or self.clearPartType == CLEARPART_TYPE_NONE:
|
|
args = ["--none"]
|
|
elif self.clearPartType == CLEARPART_TYPE_LINUX:
|
|
args = ["--linux"]
|
|
else:
|
|
args = ["--all"]
|
|
|
|
if self.clearPartDisks:
|
|
args += ["--drives=%s" % ",".join(self.clearPartDisks)]
|
|
if self.reinitializeDisks:
|
|
args += ["--initlabel"]
|
|
|
|
f.write("#clearpart %s\n" % " ".join(args))
|
|
|
|
# ignoredisks
|
|
if self.ignoredDisks:
|
|
f.write("#ignoredisk --drives=%s\n" % ",".join(self.ignoredDisks))
|
|
elif self.exclusiveDisks:
|
|
f.write("#ignoredisk --only-use=%s\n" % ",".join(self.exclusiveDisks))
|
|
|
|
# the various partitioning commands
|
|
dict = {}
|
|
actions = filter(lambda x: x.device.format.type != "luks",
|
|
self.devicetree.findActions(type="create"))
|
|
|
|
for action in actions:
|
|
if dict.has_key(action.device.path):
|
|
dict[action.device.path].append(action)
|
|
else:
|
|
dict[action.device.path] = [action]
|
|
|
|
for device in self.devices:
|
|
# If there's no action for the given device, it must be one
|
|
# we are reusing.
|
|
if not dict.has_key(device.path):
|
|
noformat = True
|
|
preexisting = True
|
|
else:
|
|
noformat = False
|
|
preexisting = useExisting(dict[device.path])
|
|
|
|
device.writeKS(f, preexisting=preexisting, noformat=noformat)
|
|
f.write("\n")
|
|
|
|
self.iscsi.writeKS(f)
|
|
self.fcoe.writeKS(f)
|
|
self.zfcp.writeKS(f)
|
|
|
|
def turnOnSwap(self, upgrading=None):
|
|
self.fsset.turnOnSwap(self.anaconda, upgrading=upgrading)
|
|
|
|
def mountFilesystems(self, raiseErrors=None, readOnly=None, skipRoot=False):
|
|
self.fsset.mountFilesystems(self.anaconda, raiseErrors=raiseErrors,
|
|
readOnly=readOnly, skipRoot=skipRoot)
|
|
|
|
def umountFilesystems(self, ignoreErrors=True, swapoff=True):
|
|
self.fsset.umountFilesystems(ignoreErrors=ignoreErrors, swapoff=swapoff)
|
|
|
|
def parseFSTab(self):
|
|
self.fsset.parseFSTab()
|
|
|
|
def mkDevRoot(self):
|
|
self.fsset.mkDevRoot()
|
|
|
|
def createSwapFile(self, device, size):
|
|
self.fsset.createSwapFile(device, size)
|
|
|
|
@property
|
|
def fsFreeSpace(self):
|
|
return self.fsset.fsFreeSpace()
|
|
|
|
@property
|
|
def mtab(self):
|
|
return self.fsset.mtab()
|
|
|
|
@property
|
|
def mountpoints(self):
|
|
return self.fsset.mountpoints
|
|
|
|
@property
|
|
def migratableDevices(self):
|
|
return self.fsset.migratableDevices
|
|
|
|
@property
|
|
def rootDevice(self):
|
|
return self.fsset.rootDevice
|
|
|
|
def compareDisks(self, first, second):
|
|
if self.eddDict.has_key(first) and self.eddDict.has_key(second):
|
|
one = self.eddDict[first]
|
|
two = self.eddDict[second]
|
|
if (one < two):
|
|
return -1
|
|
elif (one > two):
|
|
return 1
|
|
|
|
# if one is in the BIOS and the other not prefer the one in the BIOS
|
|
if self.eddDict.has_key(first):
|
|
return -1
|
|
if self.eddDict.has_key(second):
|
|
return 1
|
|
|
|
if first.startswith("hd"):
|
|
type1 = 0
|
|
elif first.startswith("sd"):
|
|
type1 = 1
|
|
elif (first.startswith("vd") or first.startswith("xvd")):
|
|
type1 = -1
|
|
else:
|
|
type1 = 2
|
|
|
|
if second.startswith("hd"):
|
|
type2 = 0
|
|
elif second.startswith("sd"):
|
|
type2 = 1
|
|
elif (second.startswith("vd") or second.startswith("xvd")):
|
|
type2 = -1
|
|
else:
|
|
type2 = 2
|
|
|
|
if (type1 < type2):
|
|
return -1
|
|
elif (type1 > type2):
|
|
return 1
|
|
else:
|
|
len1 = len(first)
|
|
len2 = len(second)
|
|
|
|
if (len1 < len2):
|
|
return -1
|
|
elif (len1 > len2):
|
|
return 1
|
|
else:
|
|
if (first < second):
|
|
return -1
|
|
elif (first > second):
|
|
return 1
|
|
|
|
return 0
|
|
|
|
def getReleaseString(mountpoint):
|
|
relName = None
|
|
relVer = None
|
|
|
|
filename = "%s/etc/redhat-release" % mountpoint
|
|
if os.access(filename, os.R_OK):
|
|
with open(filename) 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 findExistingRootDevices(anaconda, upgradeany=False):
|
|
""" Return a list of all root filesystems in the device tree. """
|
|
rootDevs = []
|
|
|
|
if not os.path.exists(anaconda.rootPath):
|
|
iutil.mkdirChain(anaconda.rootPath)
|
|
|
|
roots = []
|
|
for device in anaconda.storage.devicetree.leaves:
|
|
if not device.format.linuxNative or not device.format.mountable:
|
|
continue
|
|
|
|
if device.protected:
|
|
# can't upgrade the part holding hd: media so why look at it?
|
|
continue
|
|
|
|
try:
|
|
device.setup()
|
|
except Exception as e:
|
|
log.warning("setup of %s failed: %s" % (device.name, e))
|
|
continue
|
|
|
|
try:
|
|
device.format.mount(options="ro", mountpoint=anaconda.rootPath)
|
|
except Exception as e:
|
|
log.warning("mount of %s as %s failed: %s" % (device.name,
|
|
device.format.type,
|
|
e))
|
|
device.teardown()
|
|
continue
|
|
|
|
if os.access(anaconda.rootPath + "/etc/fstab", os.R_OK):
|
|
(product, version) = getReleaseString(anaconda.rootPath)
|
|
if upgradeany or \
|
|
anaconda.instClass.productUpgradable(product, version):
|
|
rootDevs.append((device, "%s %s" % (product, version)))
|
|
else:
|
|
log.info("product %s version %s found on %s is not upgradable"
|
|
% (product, version, device.name))
|
|
|
|
# this handles unmounting the filesystem
|
|
device.teardown(recursive=True)
|
|
|
|
return rootDevs
|
|
|
|
def mountExistingSystem(anaconda, rootEnt,
|
|
allowDirty=None, warnDirty=None,
|
|
readOnly=None):
|
|
""" Mount filesystems specified in rootDevice's /etc/fstab file. """
|
|
rootDevice = rootEnt[0]
|
|
rootPath = anaconda.rootPath
|
|
fsset = anaconda.storage.fsset
|
|
if readOnly:
|
|
readOnly = "ro"
|
|
else:
|
|
readOnly = ""
|
|
|
|
if rootDevice.protected and os.path.ismount("/mnt/isodir"):
|
|
isys.mount("/mnt/isodir",
|
|
rootPath,
|
|
fstype=rootDevice.format.type,
|
|
bindMount=True)
|
|
else:
|
|
rootDevice.setup()
|
|
rootDevice.format.mount(chroot=rootPath,
|
|
mountpoint="/",
|
|
options=readOnly)
|
|
|
|
fsset.parseFSTab()
|
|
|
|
# check for dirty filesystems
|
|
dirtyDevs = []
|
|
for device in fsset.devices:
|
|
if not hasattr(device.format, "isDirty"):
|
|
continue
|
|
|
|
try:
|
|
device.setup()
|
|
except DeviceError as e:
|
|
# we'll catch this in the main loop
|
|
continue
|
|
|
|
if device.format.isDirty:
|
|
log.info("%s contains a dirty %s filesystem" % (device.path,
|
|
device.format.type))
|
|
dirtyDevs.append(device.path)
|
|
|
|
messageWindow = anaconda.intf.messageWindow
|
|
if not allowDirty and dirtyDevs:
|
|
messageWindow(_("Dirty File Systems"),
|
|
_("The following file systems for your Linux system "
|
|
"were not unmounted cleanly. Please boot your "
|
|
"Linux installation, let the file systems be "
|
|
"checked and shut down cleanly to upgrade.\n"
|
|
"%s") % "\n".join(dirtyDevs))
|
|
anaconda.storage.devicetree.teardownAll()
|
|
sys.exit(0)
|
|
elif warnDirty and dirtyDevs:
|
|
rc = messageWindow(_("Dirty File Systems"),
|
|
_("The following file systems for your Linux "
|
|
"system were not unmounted cleanly. Would "
|
|
"you like to mount them anyway?\n"
|
|
"%s") % "\n".join(dirtyDevs),
|
|
type = "yesno")
|
|
if rc == 0:
|
|
return -1
|
|
|
|
fsset.mountFilesystems(anaconda, 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:
|
|
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
|
|
if not options:
|
|
options = ""
|
|
|
|
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:
|
|
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, rootpath):
|
|
self.devicetree = devicetree
|
|
self.rootpath = rootpath
|
|
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.preserveLines = [] # lines we just ignore and preserve
|
|
|
|
@property
|
|
def sysfs(self):
|
|
if not self._sysfs:
|
|
self._sysfs = NoDevice(format=getFormat("sysfs",
|
|
device="sys",
|
|
mountpoint="/sys"))
|
|
return self._sysfs
|
|
|
|
@property
|
|
def dev(self):
|
|
if not self._dev:
|
|
self._dev = DirectoryDevice("/dev", format=getFormat("bind",
|
|
device="/dev",
|
|
mountpoint="/dev",
|
|
exists=True),
|
|
exists=True)
|
|
|
|
return self._dev
|
|
|
|
@property
|
|
def devpts(self):
|
|
if not self._devpts:
|
|
self._devpts = NoDevice(format=getFormat("devpts",
|
|
device="devpts",
|
|
mountpoint="/dev/pts"))
|
|
return self._devpts
|
|
|
|
@property
|
|
def proc(self):
|
|
if not self._proc:
|
|
self._proc = NoDevice(format=getFormat("proc",
|
|
device="proc",
|
|
mountpoint="/proc"))
|
|
return self._proc
|
|
|
|
@property
|
|
def devshm(self):
|
|
if not self._devshm:
|
|
self._devshm = NoDevice(format=getFormat("tmpfs",
|
|
device="tmpfs",
|
|
mountpoint="/dev/shm"))
|
|
return self._devshm
|
|
|
|
@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, passno)):
|
|
# find device in the tree
|
|
device = self.devicetree.resolveDevice(devspec,
|
|
cryptTab=self.cryptTab,
|
|
blkidTab=self.blkidTab)
|
|
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,
|
|
format=getFormat(fstype,
|
|
device=devspec))
|
|
elif devspec.startswith("/") and fstype == "swap":
|
|
# swap file
|
|
device = FileDevice(devspec,
|
|
parents=get_containing_device(devspec, self.devicetree),
|
|
format=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"):
|
|
# drop these now -- we'll recreate later
|
|
return None
|
|
else:
|
|
# nodev filesystem -- preserve or drop completely?
|
|
format = getFormat(fstype)
|
|
if devspec == "none" or \
|
|
isinstance(format, get_device_format_class("nodev")):
|
|
device = NoDevice(format=format)
|
|
else:
|
|
device = StorageDevice(devspec, format=format)
|
|
|
|
if device is None:
|
|
log.error("failed to resolve %s (%s) from fstab" % (devspec,
|
|
fstype))
|
|
raise UnrecognizedFSTabEntryError()
|
|
|
|
if device.format.type is None:
|
|
log.info("Unrecognized filesystem type for %s (%s)"
|
|
% (device.name, fstype))
|
|
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
|
|
fmt = getFormat(fstype, device=device.path)
|
|
if fmt.type != device.format.type:
|
|
raise StorageError("scanned format (%s) differs from fstab "
|
|
"format (%s)" % (device.format.type, fstype))
|
|
|
|
if device.format.mountable:
|
|
device.format.mountpoint = mountpoint
|
|
device.format.mountopts = options
|
|
|
|
# is this useful?
|
|
try:
|
|
device.format.options = options
|
|
except AttributeError:
|
|
pass
|
|
|
|
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 = self.rootpath
|
|
|
|
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" % blkidTab.devices.keys())
|
|
except Exception as e:
|
|
log.info("error parsing blkid.tab: %s" % e)
|
|
blkidTab = None
|
|
|
|
cryptTab = CryptTab(self.devicetree, blkidTab=blkidTab, chroot=chroot)
|
|
try:
|
|
cryptTab.parse(chroot=chroot)
|
|
log.debug("crypttab maps: %s" % cryptTab.mappings.keys())
|
|
except Exception as e:
|
|
log.info("error parsing crypttab: %s" % e)
|
|
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:
|
|
# strip off comments
|
|
(line, pound, comment) = line.partition("#")
|
|
fields = line.split()
|
|
|
|
if not 4 <= len(fields) <= 6:
|
|
continue
|
|
elif len(fields) == 4:
|
|
fields.extend([0, 0])
|
|
elif len(fields) == 5:
|
|
fields.append(0)
|
|
|
|
(devspec, mountpoint, fstype, options, dump, passno) = fields
|
|
|
|
try:
|
|
device = self._parseOneLine((devspec, mountpoint, fstype, options, dump, passno))
|
|
except UnrecognizedFSTabEntryError:
|
|
# just write the line back out as-is after upgrade
|
|
self.preserveLines.append(line)
|
|
continue
|
|
except Exception as e:
|
|
raise Exception("fstab entry %s is malformed: %s" % (devspec, e))
|
|
|
|
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 fsFreeSpace(self, chroot=None):
|
|
if not chroot:
|
|
chroot = self.rootpath
|
|
|
|
space = []
|
|
for device in self.devices:
|
|
if not device.format.mountable or \
|
|
not device.format.mountpoint or \
|
|
not device.format.status:
|
|
continue
|
|
|
|
path = "%s/%s" % (chroot, device.format.mountpoint)
|
|
|
|
ST_RDONLY = 1 # this should be in python's posix module
|
|
if not os.path.exists(path) or os.statvfs(path)[statvfs.F_FLAG] & ST_RDONLY:
|
|
continue
|
|
|
|
try:
|
|
space.append((device.format.mountpoint,
|
|
isys.pathSpaceAvailable(path)))
|
|
except SystemError:
|
|
log.error("failed to calculate free space for %s" % (device.format.mountpoint,))
|
|
|
|
space.sort(key=lambda s: s[1])
|
|
return space
|
|
|
|
def mtab(self):
|
|
format = "%s %s %s %s 0 0\n"
|
|
mtab = ""
|
|
devices = self.mountpoints.values() + self.swapDevices
|
|
devices.extend([self.devshm, self.devpts, self.sysfs, self.proc])
|
|
devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
|
|
for device in devices:
|
|
if not device.format.status:
|
|
continue
|
|
if not device.format.mountable:
|
|
continue
|
|
if device.format.mountpoint:
|
|
options = device.format.mountopts
|
|
if options:
|
|
options = options.replace("defaults,", "")
|
|
options = options.replace("defaults", "")
|
|
|
|
if options:
|
|
options = "rw," + options
|
|
else:
|
|
options = "rw"
|
|
mtab = mtab + format % (device.path,
|
|
device.format.mountpoint,
|
|
device.format.type,
|
|
options)
|
|
return mtab
|
|
|
|
def turnOnSwap(self, anaconda, upgrading=None):
|
|
def swapErrorDialog(msg, device):
|
|
if not anaconda.intf:
|
|
sys.exit(0)
|
|
|
|
buttons = [_("Skip"), _("Format"), _("_Exit")]
|
|
ret = anaconda.intf.messageWindow(_("Error"), msg, type="custom",
|
|
custom_buttons=buttons,
|
|
custom_icon="warning")
|
|
|
|
if ret == 0:
|
|
self.devicetree._removeDevice(device)
|
|
return False
|
|
elif ret == 1:
|
|
device.format.create(force=True)
|
|
return True
|
|
else:
|
|
sys.exit(0)
|
|
|
|
for device in self.swapDevices:
|
|
if isinstance(device, FileDevice):
|
|
# set up FileDevices' parents now that they are accessible
|
|
targetDir = "%s/%s" % (anaconda.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 OldSwapError:
|
|
msg = _("The swap device:\n\n %s\n\n"
|
|
"is an old-style Linux swap partition. If "
|
|
"you want to use this device for swap space, "
|
|
"you must reformat as a new-style Linux swap "
|
|
"partition.") \
|
|
% device.path
|
|
|
|
if swapErrorDialog(msg, device):
|
|
continue
|
|
except SuspendError:
|
|
if upgrading:
|
|
msg = _("The swap device:\n\n %s\n\n"
|
|
"in your /etc/fstab file is currently in "
|
|
"use as a software suspend device, "
|
|
"which means your system is hibernating. "
|
|
"To perform an upgrade, please shut down "
|
|
"your system rather than hibernating it.") \
|
|
% device.path
|
|
else:
|
|
msg = _("The swap device:\n\n %s\n\n"
|
|
"in your /etc/fstab file is currently in "
|
|
"use as a software suspend device, "
|
|
"which means your system is hibernating. "
|
|
"If you are performing a new install, "
|
|
"make sure the installer is set "
|
|
"to format all swap devices.") \
|
|
% device.path
|
|
|
|
if swapErrorDialog(msg, device):
|
|
continue
|
|
except UnknownSwapError:
|
|
msg = _("The swap device:\n\n %s\n\n"
|
|
"does not contain a supported swap volume. In "
|
|
"order to continue installation, you will need "
|
|
"to format the device or skip it.") \
|
|
% device.path
|
|
|
|
if swapErrorDialog(msg, device):
|
|
continue
|
|
except DeviceError as (msg, name):
|
|
if anaconda.intf:
|
|
if upgrading:
|
|
err = _("Error enabling swap device %(name)s: "
|
|
"%(msg)s\n\n"
|
|
"The /etc/fstab on your upgrade partition "
|
|
"does not reference a valid swap "
|
|
"device.\n\nPress OK to exit the "
|
|
"installer") % {'name': name, 'msg': msg}
|
|
else:
|
|
err = _("Error enabling swap device %(name)s: "
|
|
"%(msg)s\n\n"
|
|
"This most likely means this swap "
|
|
"device has not been initialized.\n\n"
|
|
"Press OK to exit the installer.") % \
|
|
{'name': name, 'msg': msg}
|
|
anaconda.intf.messageWindow(_("Error"), err)
|
|
sys.exit(0)
|
|
|
|
break
|
|
|
|
def mountFilesystems(self, anaconda, raiseErrors=None, readOnly=None,
|
|
skipRoot=False):
|
|
intf = anaconda.intf
|
|
devices = self.mountpoints.values() + self.swapDevices
|
|
devices.extend([self.dev, self.devshm, self.devpts, self.sysfs, self.proc])
|
|
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 != self.dev:
|
|
# 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" % (anaconda.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 msg:
|
|
# FIXME: need an error popup
|
|
continue
|
|
|
|
if readOnly:
|
|
options = "%s,%s" % (options, readOnly)
|
|
|
|
try:
|
|
device.format.setup(options=options,
|
|
chroot=anaconda.rootPath)
|
|
except OSError as e:
|
|
log.error("OSError: (%d) %s" % (e.errno, e.strerror))
|
|
|
|
if intf:
|
|
if e.errno == errno.EEXIST:
|
|
intf.messageWindow(_("Invalid mount point"),
|
|
_("An error occurred when trying "
|
|
"to create %s. Some element of "
|
|
"this path is not a directory. "
|
|
"This is a fatal error and the "
|
|
"install cannot continue.\n\n"
|
|
"Press <Enter> to exit the "
|
|
"installer.")
|
|
% (device.format.mountpoint,))
|
|
else:
|
|
na = {'mountpoint': device.format.mountpoint,
|
|
'msg': e.strerror}
|
|
intf.messageWindow(_("Invalid mount point"),
|
|
_("An error occurred when trying "
|
|
"to create %(mountpoint)s: "
|
|
"%(msg)s. This is "
|
|
"a fatal error and the install "
|
|
"cannot continue.\n\n"
|
|
"Press <Enter> to exit the "
|
|
"installer.") % na)
|
|
sys.exit(0)
|
|
except SystemError as (num, msg):
|
|
log.error("SystemError: (%d) %s" % (num, msg) )
|
|
|
|
if raiseErrors:
|
|
raise
|
|
if intf and not device.format.linuxNative:
|
|
na = {'path': device.path,
|
|
'mountpoint': device.format.mountpoint}
|
|
ret = intf.messageWindow(_("Unable to mount filesystem"),
|
|
_("An error occurred mounting "
|
|
"device %(path)s as "
|
|
"%(mountpoint)s. You may "
|
|
"continue installation, but "
|
|
"there may be problems.") % na,
|
|
type="custom",
|
|
custom_icon="warning",
|
|
custom_buttons=[_("_Exit installer"),
|
|
_("_Continue")])
|
|
|
|
if ret == 0:
|
|
sys.exit(0)
|
|
else:
|
|
continue
|
|
|
|
sys.exit(0)
|
|
except FSError as msg:
|
|
log.error("FSError: %s" % msg)
|
|
|
|
if intf:
|
|
na = {'path': device.path,
|
|
'mountpoint': device.format.mountpoint,
|
|
'msg': msg}
|
|
intf.messageWindow(_("Unable to mount filesystem"),
|
|
_("An error occurred mounting "
|
|
"device %(path)s as %(mountpoint)s: "
|
|
"%(msg)s. This is "
|
|
"a fatal error and the install "
|
|
"cannot continue.\n\n"
|
|
"Press <Enter> to exit the "
|
|
"installer.") % na)
|
|
sys.exit(0)
|
|
|
|
self.active = True
|
|
|
|
def umountFilesystems(self, ignoreErrors=True, swapoff=True):
|
|
devices = self.mountpoints.values() + self.swapDevices
|
|
devices.extend([self.dev, self.devshm, self.devpts, self.sysfs, self.proc])
|
|
devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
|
|
devices.reverse()
|
|
for device in devices:
|
|
if not device.format.mountable and \
|
|
(device.format.type != "swap" or swapoff):
|
|
continue
|
|
|
|
device.format.teardown()
|
|
device.teardown()
|
|
|
|
self.active = False
|
|
|
|
def createSwapFile(self, device, size, rootPath=None):
|
|
""" Create and activate a swap file under rootPath. """
|
|
if not rootPath:
|
|
rootPath = self.rootpath
|
|
|
|
filename = "/SWAP"
|
|
count = 0
|
|
basedir = os.path.normpath("%s/%s" % (rootPath,
|
|
device.format.mountpoint))
|
|
while os.path.exists("%s/%s" % (basedir, filename)) or \
|
|
self.devicetree.getDeviceByName(filename):
|
|
file = os.path.normpath("%s/%s" % (basedir, filename))
|
|
count += 1
|
|
filename = "/SWAP-%d" % count
|
|
|
|
dev = FileDevice(filename,
|
|
size=size,
|
|
parents=[device],
|
|
format=getFormat("swap", device=filename))
|
|
dev.create()
|
|
dev.setup()
|
|
dev.format.create()
|
|
dev.format.setup()
|
|
# nasty, nasty
|
|
self.devicetree._addDevice(dev)
|
|
|
|
def mkDevRoot(self, instPath=None):
|
|
if not instPath:
|
|
instPath = self.rootpath
|
|
|
|
root = self.rootDevice
|
|
dev = "%s/%s" % (instPath, root.path)
|
|
if not os.path.exists("%s/dev/root" %(instPath,)) and os.path.exists(dev):
|
|
rdev = os.stat(dev).st_rdev
|
|
os.mknod("%s/dev/root" % (instPath,), stat.S_IFBLK | 0600, 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 ["/", self.rootpath]:
|
|
for device in self.devices:
|
|
try:
|
|
mountpoint = device.format.mountpoint
|
|
except AttributeError:
|
|
mountpoint = None
|
|
|
|
if mountpoint == path:
|
|
return device
|
|
|
|
@property
|
|
def migratableDevices(self):
|
|
""" List of devices whose filesystems can be migrated. """
|
|
migratable = []
|
|
for device in self.devices:
|
|
if device.format.migratable and device.format.exists:
|
|
migratable.append(device)
|
|
|
|
return migratable
|
|
|
|
def write(self, instPath=None):
|
|
""" write out all config files based on the set of filesystems """
|
|
if not instPath:
|
|
instPath = self.rootpath
|
|
|
|
# /etc/fstab
|
|
fstab_path = os.path.normpath("%s/etc/fstab" % instPath)
|
|
fstab = self.fstab()
|
|
open(fstab_path, "w").write(fstab)
|
|
|
|
# /etc/crypttab
|
|
crypttab_path = os.path.normpath("%s/etc/crypttab" % instPath)
|
|
crypttab = self.crypttab()
|
|
open(crypttab_path, "w").write(crypttab)
|
|
|
|
# /etc/mdadm.conf
|
|
mdadm_path = os.path.normpath("%s/etc/mdadm.conf" % instPath)
|
|
mdadm_conf = self.mdadmConf()
|
|
if mdadm_conf:
|
|
open(mdadm_path, "w").write(mdadm_conf)
|
|
|
|
# /etc/multipath.conf
|
|
multipath_path = os.path.normpath("%s/etc/multipath.conf" % instPath)
|
|
multipath_conf = self.multipathConf()
|
|
if multipath_conf:
|
|
open(multipath_path, "w").write(multipath_conf)
|
|
|
|
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 = 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)
|
|
|
|
conf = "# mdadm.conf written out by anaconda\n"
|
|
conf += "MAILADDR root\n"
|
|
conf += "AUTO +imsm +1.x -all\n"
|
|
devices = 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 multipathConf(self):
|
|
""" Return the contents of multipath.conf. """
|
|
mpaths = self.devicetree.getDevicesByType("dm-multipath")
|
|
if not mpaths:
|
|
return None
|
|
mpaths.sort(key=lambda d: d.name)
|
|
config = MultipathConfigWriter()
|
|
whitelist = []
|
|
for mpath in mpaths:
|
|
config.addMultipathDevice(mpath)
|
|
whitelist.append(mpath.name)
|
|
whitelist.extend([d.name for d in mpath.parents])
|
|
|
|
# blacklist everything we're not using and let the
|
|
# sysadmin sort it out.
|
|
for d in self.devicetree.devices:
|
|
if not d.name in whitelist:
|
|
config.addBlacklistDevice(d)
|
|
|
|
return config.write()
|
|
|
|
def fstab (self):
|
|
format = "%-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)
|
|
devices += self.swapDevices
|
|
devices.extend([self.devshm, self.devpts, self.sysfs, self.proc])
|
|
netdevs = self.devicetree.getDevicesByInstance(NetworkStorageDevice)
|
|
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):
|
|
options = options + ",_netdev"
|
|
break
|
|
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 + format % (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
|