aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwiktor w brodlo <wiktor@brodlo.net>2011-06-15 16:59:54 +0000
committerwiktor w brodlo <wiktor@brodlo.net>2011-06-15 16:59:54 +0000
commit2590d96369d0217e31dc2812690dde61dac417b5 (patch)
tree82276f787b08a28548e342c7921486f1acefab9f /storage
parentfirst commit (diff)
downloadanaconda-2590d96369d0217e31dc2812690dde61dac417b5.tar.gz
anaconda-2590d96369d0217e31dc2812690dde61dac417b5.tar.bz2
anaconda-2590d96369d0217e31dc2812690dde61dac417b5.zip
Initial import from Sabayon (ver 0.9.9.56)
Diffstat (limited to 'storage')
-rw-r--r--storage/Makefile.am26
-rw-r--r--storage/__init__.py2238
-rw-r--r--storage/dasd.py220
-rw-r--r--storage/deviceaction.py376
-rw-r--r--storage/devicelibs/Makefile.am24
-rw-r--r--storage/devicelibs/__init__.py0
-rw-r--r--storage/devicelibs/crypto.py193
-rw-r--r--storage/devicelibs/dm.py130
-rw-r--r--storage/devicelibs/edd.py97
-rw-r--r--storage/devicelibs/lvm.py419
-rw-r--r--storage/devicelibs/mdraid.py234
-rw-r--r--storage/devicelibs/mpath.py228
-rw-r--r--storage/devicelibs/swap.py125
-rw-r--r--storage/devices.py3576
-rw-r--r--storage/devicetree.py2259
-rw-r--r--storage/errors.py150
-rw-r--r--storage/fcoe.py172
-rw-r--r--storage/formats/Makefile.am24
-rw-r--r--storage/formats/__init__.py403
-rw-r--r--storage/formats/disklabel.py359
-rw-r--r--storage/formats/dmraid.py114
-rw-r--r--storage/formats/fs.py1476
-rw-r--r--storage/formats/luks.py352
-rw-r--r--storage/formats/lvmpv.py156
-rw-r--r--storage/formats/mdraid.py124
-rw-r--r--storage/formats/multipath.py95
-rw-r--r--storage/formats/prepboot.py64
-rw-r--r--storage/formats/swap.py186
-rw-r--r--storage/iscsi.py333
-rw-r--r--storage/miscutils.py57
-rw-r--r--storage/partitioning.py1647
-rw-r--r--storage/partspec.py66
-rw-r--r--storage/storage_log.py32
-rw-r--r--storage/udev.py515
-rw-r--r--storage/zfcp.py441
35 files changed, 16911 insertions, 0 deletions
diff --git a/storage/Makefile.am b/storage/Makefile.am
new file mode 100644
index 0000000..7347694
--- /dev/null
+++ b/storage/Makefile.am
@@ -0,0 +1,26 @@
+# storage/Makefile.am for anaconda
+#
+# Copyright (C) 2009 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: David Cantrell <dcantrell@redhat.com>
+
+pkgpyexecdir = $(pyexecdir)/py$(PACKAGE_NAME)
+storagedir = $(pkgpyexecdir)/storage
+storage_PYTHON = *.py
+
+SUBDIRS = devicelibs formats
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/storage/__init__.py b/storage/__init__.py
new file mode 100644
index 0000000..5e35958
--- /dev/null
+++ b/storage/__init__.py
@@ -0,0 +1,2238 @@
+# __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")
+
+ # Before we set up the storage system, we need to know which disks to
+ # ignore, etc. Luckily that's all in the kickstart data.
+ if anaconda.ksdata:
+ anaconda.storage.zeroMbr = anaconda.ksdata.zerombr.zerombr
+ anaconda.storage.ignoredDisks = anaconda.ksdata.ignoredisk.ignoredisk
+ anaconda.storage.exclusiveDisks = anaconda.ksdata.ignoredisk.onlyuse
+
+ if anaconda.ksdata.clearpart.type is not None:
+ anaconda.storage.clearPartType = anaconda.ksdata.clearpart.type
+ anaconda.storage.clearPartDisks = anaconda.ksdata.clearpart.drives
+ if anaconda.ksdata.clearpart.initAll:
+ anaconda.storage.reinitializeDisks = anaconda.ksdata.clearpart.initAll
+
+ 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.") % devspec,
+ type="custom", custom_buttons = [_("_Exit installer")])
+ sys.exit(1)
+ else:
+ storage.reset()
+
+# 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 = False
+ 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/system-release" % mountpoint
+ if os.access(filename, os.R_OK):
+ with open(filename) as f:
+ relstr = f.readline().strip()
+ relName = ' '.join(relstr.split(" ")[:2])
+ relVer = relstr.split()[-1]
+
+ 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 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
+ if root is None:
+ # cannot create root device !
+ return
+ 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:
+ with open(mdadm_path, "w") as md_f:
+ md_f.write(mdadm_conf)
+
+ # /etc/multipath.conf
+ multipath_path = os.path.normpath("%s/etc/multipath.conf" % instPath)
+ multipath_conf = self.multipathConf()
+ if multipath_conf:
+ with open(multipath_path, "w") as mp_f:
+ mp_f.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
diff --git a/storage/dasd.py b/storage/dasd.py
new file mode 100644
index 0000000..2052843
--- /dev/null
+++ b/storage/dasd.py
@@ -0,0 +1,220 @@
+#
+# dasd.py - DASD class
+#
+# Copyright (C) 2009 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
+#
+# Red Hat Author(s): David Cantrell <dcantrell@redhat.com>
+#
+
+import iutil
+import sys
+import os
+from storage.devices import deviceNameToDiskByPath
+from constants import *
+from flags import flags
+
+import logging
+log = logging.getLogger("anaconda")
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z)
+
+def getDasdPorts():
+ """ Return comma delimited string of valid DASD ports. """
+ ports = []
+
+ f = open("/proc/dasd/devices", "r")
+ lines = map(lambda x: x.strip(), f.readlines())
+ f.close()
+
+ for line in lines:
+ if "unknown" in line:
+ continue
+
+ if "(FBA )" in line or "(ECKD)" in line:
+ ports.append(line.split('(')[0])
+
+ return ','.join(ports)
+
+class DASD:
+ """ Controlling class for DASD interaction before the storage code in
+ anaconda has initialized.
+
+ The DASD class can determine if any DASD devices on the system are
+ unformatted and can perform a dasdfmt on them.
+ """
+
+ def __init__(self):
+ self._dasdlist = []
+ self._devices = [] # list of DASDDevice objects
+ self._totalCylinders = 0
+ self._completedCylinders = 0.0
+ self._maxFormatJobs = 0
+ self.started = False
+
+ def startup(self, *args, **kwargs):
+ """ Look for any unformatted DASDs in the system and offer the user
+ the option for format them with dasdfmt or exit the installer.
+ """
+ if self.started:
+ return
+
+ self.started = True
+
+ if not iutil.isS390():
+ return
+
+ intf = kwargs.get("intf")
+ zeroMbr = kwargs.get("zeroMbr")
+
+ log.info("Checking for unformatted DASD devices:")
+
+ for device in os.listdir("/sys/block"):
+ if not device.startswith("dasd"):
+ continue
+
+ statusfile = "/sys/block/%s/device/status" % (device,)
+ if not os.path.isfile(statusfile):
+ continue
+
+ f = open(statusfile, "r")
+ status = f.read().strip()
+ f.close()
+
+ if status == "unformatted":
+ log.info(" %s is an unformatted DASD" % (device,))
+ self._dasdlist.append(device)
+
+ if not len(self._dasdlist):
+ log.info(" no unformatted DASD devices found")
+ return
+
+ askUser = True
+
+ if zeroMbr:
+ askUser = False
+ elif not intf and not zeroMbr:
+ log.info(" non-interactive kickstart install without zerombr "
+ "command, unable to run dasdfmt, exiting installer")
+ sys.exit(0)
+
+ tmplist = map(lambda s: "/dev/" + s, self._dasdlist)
+ self._dasdlist = map(lambda s: deviceNameToDiskByPath(s), tmplist)
+ c = len(self._dasdlist)
+
+ if intf and askUser:
+ title = P_("Unformatted DASD Device Found",
+ "Unformatted DASD Devices Found", c)
+ msg = P_("Format uninitialized DASD device?\n\n"
+ "There is %d uninitialized DASD device on this "
+ "system. To continue installation, the device must "
+ "be formatted. Formatting will remove any data on "
+ "this device." % c,
+ "Format uninitialized DASD devices?\n\n"
+ "There are %d uninitialized DASD devices on this "
+ "system. To continue installation, the devices must "
+ "be formatted. Formatting will remove any data on "
+ "these devices." % c,
+ c)
+
+ devs = ''
+ for dasd in self._dasdlist:
+ devs += "%s\n" % (dasd,)
+
+ icon = "/usr/share/icons/gnome/32x32/status/dialog-error.png"
+ buttons = [_("_Format"), _("_Exit installer")]
+ rc = intf.detailedMessageWindow(title, msg, devs.strip(),
+ type="custom",
+ custom_icon=icon,
+ custom_buttons=buttons)
+ if rc == 1:
+ log.info(" not running dasdfmt, exiting installer")
+ sys.exit(0)
+
+ argv = ["-y", "-P", "-d", "cdl", "-b", "4096"]
+
+ if intf:
+ title = P_("Formatting DASD Device", "Formatting DASD Devices", c)
+ msg = P_("Preparing %d DASD device for use with Linux..." % c,
+ "Preparing %d DASD devices for use with Linux..." % c, c)
+ pw = intf.progressWindow(title, msg, 1.0)
+
+ for dasd in self._dasdlist:
+ log.info("Running dasdfmt on %s" % (dasd,))
+ iutil.execWithCallback("/sbin/dasdfmt", argv + [dasd],
+ stdout="/dev/tty5", stderr="/dev/tty5",
+ callback=self._updateProgressWindow,
+ callback_data=pw, echo=False)
+
+ pw.pop()
+ else:
+ for dasd in self._dasdlist:
+ log.info("Running dasdfmt on %s" % (dasd,))
+ iutil.execWithRedirect("/sbin/dasdfmt", argv + [dasd],
+ stdout="/dev/tty5", stderr="/dev/tty5")
+
+ def addDASD(self, dasd):
+ """ Adds a DASDDevice to the internal list of DASDs. """
+ if dasd:
+ self._devices.append(dasd)
+
+ def write(self, instPath):
+ """ Write /etc/dasd.conf to target system for all DASD devices
+ configured during installation.
+ """
+ if self._devices == []:
+ return
+
+ f = open(os.path.realpath(instPath + "/etc/dasd.conf"), "w")
+ for dasd in self._devices:
+ fields = [dasd.busid] + dasd.getOpts()
+ f.write("%s\n" % (" ".join(fields),))
+ f.close()
+
+ def _updateProgressWindow(self, data, callback_data=None):
+ """ Reads progress output from dasdfmt and collects the number of
+ cylinders completed so the progress window can update.
+ """
+ if not callback_data:
+ return
+
+ if data == '\n':
+ # each newline we see in this output means one more cylinder done
+ self._completedCylinders += 1.0
+ callback_data.set(self._completedCylinders / self.totalCylinders)
+
+ @property
+ def totalCylinders(self):
+ """ Total number of cylinders of all unformatted DASD devices. """
+ if self._totalCylinders:
+ return self._totalCylinders
+
+ argv = ["-t", "-v", "-y", "-d", "cdl", "-b", "4096"]
+ for dasd in self._dasdlist:
+ buf = iutil.execWithCapture("/sbin/dasdfmt", argv + [dasd],
+ stderr="/dev/tty5")
+ for line in buf.splitlines():
+ if line.startswith("Drive Geometry: "):
+ # line will look like this:
+ # Drive Geometry: 3339 Cylinders * 15 Heads = 50085 Tracks
+ cyls = long(filter(lambda s: s, line.split(' '))[2])
+ self._totalCylinders += cyls
+ break
+
+ return self._totalCylinders
+
+# vim:tw=78:ts=4:et:sw=4
diff --git a/storage/deviceaction.py b/storage/deviceaction.py
new file mode 100644
index 0000000..4524e7c
--- /dev/null
+++ b/storage/deviceaction.py
@@ -0,0 +1,376 @@
+# deviceaction.py
+# Device modification action classes 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>
+#
+
+from udev import *
+
+from devices import StorageDevice, PartitionDevice
+from formats import getFormat
+from errors import *
+from parted import partitionFlag, PARTITION_LBA
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+
+# The values are just hints as to the ordering.
+# Eg: fsmod and devmod ordering depends on the mod (shrink -v- grow)
+ACTION_TYPE_NONE = 0
+ACTION_TYPE_DESTROY = 1000
+ACTION_TYPE_RESIZE = 500
+ACTION_TYPE_MIGRATE = 250
+ACTION_TYPE_CREATE = 100
+
+action_strings = {ACTION_TYPE_NONE: "None",
+ ACTION_TYPE_DESTROY: "Destroy",
+ ACTION_TYPE_RESIZE: "Resize",
+ ACTION_TYPE_MIGRATE: "Migrate",
+ ACTION_TYPE_CREATE: "Create"}
+
+ACTION_OBJECT_NONE = 0
+ACTION_OBJECT_FORMAT = 1
+ACTION_OBJECT_DEVICE = 2
+
+object_strings = {ACTION_OBJECT_NONE: "None",
+ ACTION_OBJECT_FORMAT: "Format",
+ ACTION_OBJECT_DEVICE: "Device"}
+
+RESIZE_SHRINK = 88
+RESIZE_GROW = 89
+
+resize_strings = {RESIZE_SHRINK: "Shrink",
+ RESIZE_GROW: "Grow"}
+
+def action_type_from_string(type_string):
+ if type_string is None:
+ return None
+
+ for (k,v) in action_strings.items():
+ if v.lower() == type_string.lower():
+ return k
+
+ return resize_type_from_string(type_string)
+
+def action_object_from_string(type_string):
+ if type_string is None:
+ return None
+
+ for (k,v) in object_strings.items():
+ if v.lower() == type_string.lower():
+ return k
+
+def resize_type_from_string(type_string):
+ if type_string is None:
+ return None
+
+ for (k,v) in resize_strings.items():
+ if v.lower() == type_string.lower():
+ return k
+
+class DeviceAction(object):
+ """ An action that will be carried out in the future on a Device.
+
+ These classes represent actions to be performed on devices or
+ filesystems.
+
+ The operand Device instance will be modified according to the
+ action, but no changes will be made to the underlying device or
+ filesystem until the DeviceAction instance's execute method is
+ called. The DeviceAction instance's cancel method should reverse
+ any modifications made to the Device instance's attributes.
+
+ If the Device instance represents a pre-existing device, the
+ constructor should call any methods or set any attributes that the
+ action will eventually change. Device/DeviceFormat classes should verify
+ that the requested modifications are reasonable and raise an
+ exception if not.
+
+ Only one action of any given type/object pair can exist for any
+ given device at any given time. This is enforced by the
+ DeviceTree.
+
+ Basic usage:
+
+ a = DeviceAction(dev)
+ a.execute()
+
+ OR
+
+ a = DeviceAction(dev)
+ a.cancel()
+
+
+ XXX should we back up the device with a deep copy for forcibly
+ cancelling actions?
+
+ The downside is that we lose any checking or verification that
+ would get done when resetting the Device instance's attributes to
+ their original values.
+
+ The upside is that we would be guaranteed to achieve a total
+ reversal. No chance of, eg: resizes ending up altering Device
+ size due to rounding or other miscalculation.
+"""
+ type = ACTION_TYPE_NONE
+ obj = ACTION_OBJECT_NONE
+
+ def __init__(self, device):
+ if not isinstance(device, StorageDevice):
+ raise ValueError("arg 1 must be a StorageDevice instance")
+ self.device = device
+
+
+ def execute(self, intf=None):
+ """ perform the action """
+ pass
+
+ def cancel(self):
+ """ cancel the action """
+ pass
+
+ def isDestroy(self):
+ return self.type == ACTION_TYPE_DESTROY
+
+ def isCreate(self):
+ return self.type == ACTION_TYPE_CREATE
+
+ def isMigrate(self):
+ return self.type == ACTION_TYPE_MIGRATE
+
+ def isResize(self):
+ return self.type == ACTION_TYPE_RESIZE
+
+ def isShrink(self):
+ return (self.type == ACTION_TYPE_RESIZE and self.dir == RESIZE_SHRINK)
+
+ def isGrow(self):
+ return (self.type == ACTION_TYPE_RESIZE and self.dir == RESIZE_GROW)
+
+ def isDevice(self):
+ return self.obj == ACTION_OBJECT_DEVICE
+
+ def isFormat(self):
+ return self.obj == ACTION_OBJECT_FORMAT
+
+ @property
+ def format(self):
+ return self.device.format
+
+ def __str__(self):
+ s = "%s %s" % (action_strings[self.type], object_strings[self.obj])
+ if self.isResize():
+ s += " (%s)" % resize_strings[self.dir]
+ if self.isFormat():
+ s += " %s on" % self.format.type
+ if self.isMigrate():
+ s += " to %s" % self.format.migrationTarget
+ s += " %s %s (id %d)" % (self.device.type, self.device.name,
+ self.device.id)
+ return s
+
+class ActionCreateDevice(DeviceAction):
+ """ Action representing the creation of a new device. """
+ type = ACTION_TYPE_CREATE
+ obj = ACTION_OBJECT_DEVICE
+
+ def __init__(self, device):
+ # FIXME: assert device.fs is None
+ DeviceAction.__init__(self, device)
+
+ def execute(self, intf=None):
+ self.device.create(intf=intf)
+
+
+class ActionDestroyDevice(DeviceAction):
+ """ An action representing the deletion of an existing device. """
+ type = ACTION_TYPE_DESTROY
+ obj = ACTION_OBJECT_DEVICE
+
+ def __init__(self, device):
+ # XXX should we insist that device.fs be None?
+ DeviceAction.__init__(self, device)
+ if device.exists:
+ device.teardown()
+
+ def execute(self, intf=None):
+ self.device.destroy()
+
+ # Make sure libparted does not keep cached info for this device
+ # and returns it when we create a new device with the same name
+ if self.device.partedDevice:
+ self.device.partedDevice.removeFromCache()
+
+
+class ActionResizeDevice(DeviceAction):
+ """ An action representing the resizing of an existing device. """
+ type = ACTION_TYPE_RESIZE
+ obj = ACTION_OBJECT_DEVICE
+
+ def __init__(self, device, newsize):
+ if device.currentSize == newsize:
+ raise ValueError("new size same as old size")
+
+ if not device.resizable:
+ raise ValueError("device is not resizable")
+
+ DeviceAction.__init__(self, device)
+ if newsize > device.currentSize:
+ self.dir = RESIZE_GROW
+ else:
+ self.dir = RESIZE_SHRINK
+ self.origsize = device.targetSize
+ self.device.targetSize = newsize
+
+ def execute(self, intf=None):
+ self.device.resize(intf=intf)
+
+ def cancel(self):
+ self.device.targetSize = self.origsize
+
+
+class ActionCreateFormat(DeviceAction):
+ """ An action representing creation of a new filesystem. """
+ type = ACTION_TYPE_CREATE
+ obj = ACTION_OBJECT_FORMAT
+
+ def __init__(self, device, format=None):
+ DeviceAction.__init__(self, device)
+ if format:
+ self.origFormat = device.format
+ if self.device.format.exists:
+ self.device.format.teardown()
+ self.device.format = format
+ else:
+ self.origFormat = getFormat(None)
+
+ def execute(self, intf=None):
+ self.device.setup()
+
+ if isinstance(self.device, PartitionDevice):
+ for flag in partitionFlag.keys():
+ # Keep the LBA flag on pre-existing partitions
+ if flag in [ PARTITION_LBA, self.format.partedFlag ]:
+ continue
+ self.device.unsetFlag(flag)
+
+ if self.format.partedFlag is not None:
+ self.device.setFlag(self.format.partedFlag)
+
+ if self.format.partedSystem is not None:
+ self.device.partedPartition.system = self.format.partedSystem
+
+ self.device.disk.format.commitToDisk()
+
+ self.device.format.create(intf=intf,
+ device=self.device.path,
+ options=self.device.formatArgs)
+ # Get the UUID now that the format is created
+ udev_settle()
+ self.device.updateSysfsPath()
+ info = udev_get_block_device(self.device.sysfsPath)
+ self.device.format.uuid = udev_device_get_uuid(info)
+
+ def cancel(self):
+ self.device.format = self.origFormat
+
+
+class ActionDestroyFormat(DeviceAction):
+ """ An action representing the removal of an existing filesystem.
+
+ XXX this seems unnecessary
+ """
+ type = ACTION_TYPE_DESTROY
+ obj = ACTION_OBJECT_FORMAT
+
+ def __init__(self, device):
+ DeviceAction.__init__(self, device)
+ self.origFormat = self.device.format
+ if device.format.exists:
+ device.format.teardown()
+ self.device.format = None
+
+ def execute(self, intf=None):
+ """ wipe the filesystem signature from the device """
+ if self.origFormat:
+ self.device.setup(orig=True)
+ self.origFormat.destroy()
+ udev_settle()
+ self.device.teardown()
+
+ def cancel(self):
+ self.device.format = self.origFormat
+
+ @property
+ def format(self):
+ return self.origFormat
+
+
+class ActionResizeFormat(DeviceAction):
+ """ An action representing the resizing of an existing filesystem.
+
+ XXX Do we even want to support resizing of a filesystem without
+ also resizing the device it resides on?
+ """
+ type = ACTION_TYPE_RESIZE
+ obj = ACTION_OBJECT_FORMAT
+
+ def __init__(self, device, newsize):
+ if device.format.targetSize == newsize:
+ raise ValueError("new size same as old size")
+
+ DeviceAction.__init__(self, device)
+ if newsize > device.format.currentSize:
+ self.dir = RESIZE_GROW
+ else:
+ self.dir = RESIZE_SHRINK
+ self.origSize = self.device.format.targetSize
+ self.device.format.targetSize = newsize
+
+ def execute(self, intf=None):
+ self.device.setup(orig=True)
+ self.device.format.doResize(intf=intf)
+
+ def cancel(self):
+ self.device.format.targetSize = self.origSize
+
+class ActionMigrateFormat(DeviceAction):
+ """ An action representing the migration of an existing filesystem. """
+ type = ACTION_TYPE_MIGRATE
+ obj = ACTION_OBJECT_FORMAT
+
+ def __init__(self, device):
+ if not device.format.migratable or not device.format.exists:
+ raise ValueError("device format is not migratable")
+
+ DeviceAction.__init__(self, device)
+ self.device.format.migrate = True
+
+ def execute(self, intf=None):
+ self.device.setup(orig=True)
+ self.device.format.doMigrate(intf=intf)
+
+ def cancel(self):
+ self.device.format.migrate = False
+
diff --git a/storage/devicelibs/Makefile.am b/storage/devicelibs/Makefile.am
new file mode 100644
index 0000000..86a7d5e
--- /dev/null
+++ b/storage/devicelibs/Makefile.am
@@ -0,0 +1,24 @@
+# storage/devicelibs/Makefile.am for anaconda
+#
+# Copyright (C) 2009 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: David Cantrell <dcantrell@redhat.com>
+
+pkgpyexecdir = $(pyexecdir)/py$(PACKAGE_NAME)
+storagedevicelibsdir = $(pkgpyexecdir)/storage/devicelibs
+storagedevicelibs_PYTHON = *.py
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/storage/devicelibs/__init__.py b/storage/devicelibs/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/storage/devicelibs/__init__.py
diff --git a/storage/devicelibs/crypto.py b/storage/devicelibs/crypto.py
new file mode 100644
index 0000000..136435d
--- /dev/null
+++ b/storage/devicelibs/crypto.py
@@ -0,0 +1,193 @@
+#
+# crypto.py
+#
+# Copyright (C) 2009 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
+#
+# Author(s): Dave Lehman <dlehman@redhat.com>
+# Martin Sivak <msivak@redhat.com>
+#
+
+import os
+from pycryptsetup import CryptSetup
+import iutil
+
+from ..errors import *
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+# Keep the character set size a power of two to make sure all characters are
+# equally likely
+GENERATED_PASSPHRASE_CHARSET = ("0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "./")
+# 20 chars * 6 bits per char = 120 "bits of security"
+GENERATED_PASSPHRASE_LENGTH = 20
+
+def generateBackupPassphrase():
+ rnd = os.urandom(GENERATED_PASSPHRASE_LENGTH)
+ cs = GENERATED_PASSPHRASE_CHARSET
+ raw = "".join([cs[ord(c) % len(cs)] for c in rnd])
+
+ # Make the result easier to read
+ parts = []
+ for i in xrange(0, len(raw), 5):
+ parts.append(raw[i : i + 5])
+ return "-".join(parts)
+
+def askyes(question):
+ return True
+
+def dolog(priority, text):
+ pass
+
+def is_luks(device):
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ return cs.isLuks(device)
+
+def luks_uuid(device):
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ return cs.luksUUID(device).strip()
+
+def luks_status(name):
+ """True means active, False means inactive (or non-existent)"""
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ return cs.luksStatus(name)!=0
+
+def luks_format(device,
+ passphrase=None, key_file=None,
+ cipher=None, key_size=None):
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ key_file_unlink = False
+
+ if passphrase:
+ key_file = cs.prepare_passphrase_file(passphrase)
+ key_file_unlink = True
+ elif key_file and os.path.isfile(key_file):
+ pass
+ else:
+ raise ValueError("luks_format requires either a passphrase or a key file")
+
+ #None is not considered as default value and pycryptsetup doesn't accept it
+ #so we need to filter out all Nones
+ kwargs = {}
+ kwargs["device"] = device
+ if cipher: kwargs["cipher"] = cipher
+ if key_file: kwargs["keyfile"] = key_file
+ if key_size: kwargs["keysize"] = key_size
+
+ rc = cs.luksFormat(**kwargs)
+ if key_file_unlink: os.unlink(key_file)
+
+ if rc:
+ raise CryptoError("luks_format failed for '%s'" % device)
+
+def luks_open(device, name, passphrase=None, key_file=None):
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ key_file_unlink = False
+
+ if passphrase:
+ key_file = cs.prepare_passphrase_file(passphrase)
+ key_file_unlink = True
+ elif key_file and os.path.isfile(key_file):
+ pass
+ else:
+ raise ValueError("luks_open requires either a passphrase or a key file")
+
+ rc = cs.luksOpen(device = device, name = name, keyfile = key_file)
+ if key_file_unlink: os.unlink(key_file)
+ if rc:
+ raise CryptoError("luks_open failed for %s (%s)" % (device, name))
+
+def luks_close(name):
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ rc = cs.luksClose(name)
+ if rc:
+ raise CryptoError("luks_close failed for %s" % name)
+
+def luks_add_key(device,
+ new_passphrase=None, new_key_file=None,
+ passphrase=None, key_file=None):
+
+ params = ["-q"]
+
+ p = os.pipe()
+ if passphrase:
+ os.write(p[1], "%s\n" % passphrase)
+ elif key_file and os.path.isfile(key_file):
+ params.extend(["--key-file", key_file])
+ else:
+ raise CryptoError("luks_add_key requires either a passphrase or a key file")
+
+ params.extend(["luksAddKey", device])
+
+ if new_passphrase:
+ os.write(p[1], "%s\n" % new_passphrase)
+ elif new_key_file and os.path.isfile(new_key_file):
+ params.append("%s" % new_key_file)
+ else:
+ raise CryptoError("luks_add_key requires either a passphrase or a key file to add")
+
+ os.close(p[1])
+
+ rc = iutil.execWithRedirect("cryptsetup", params,
+ stdin = p[0],
+ stdout = "/dev/tty5",
+ stderr = "/dev/tty5")
+
+ os.close(p[0])
+ if rc:
+ raise CryptoError("luks add key failed with errcode %d" % (rc,))
+
+def luks_remove_key(device,
+ del_passphrase=None, del_key_file=None,
+ passphrase=None, key_file=None):
+
+ params = []
+
+ p = os.pipe()
+ if del_passphrase: #the first question is about the key we want to remove
+ os.write(p[1], "%s\n" % del_passphrase)
+
+ if passphrase:
+ os.write(p[1], "%s\n" % passphrase)
+ elif key_file and os.path.isfile(key_file):
+ params.extend(["--key-file", key_file])
+ else:
+ raise CryptoError("luks_remove_key requires either a passphrase or a key file")
+
+ params.extend(["luksRemoveKey", device])
+
+ if del_passphrase:
+ pass
+ elif del_key_file and os.path.isfile(del_key_file):
+ params.append("%s" % del_key_file)
+ else:
+ raise CryptoError("luks_remove_key requires either a passphrase or a key file to remove")
+
+ os.close(p[1])
+
+ rc = iutil.execWithRedirect("cryptsetup", params,
+ stdin = p[0],
+ stdout = "/dev/tty5",
+ stderr = "/dev/tty5")
+
+ os.close(p[0])
+ if rc:
+ raise CryptoError("luks_remove_key failed with errcode %d" % (rc,))
+
+
diff --git a/storage/devicelibs/dm.py b/storage/devicelibs/dm.py
new file mode 100644
index 0000000..02745e0
--- /dev/null
+++ b/storage/devicelibs/dm.py
@@ -0,0 +1,130 @@
+#
+# dm.py
+# device-mapper functions
+#
+# Copyright (C) 2009 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
+#
+# Author(s): Dave Lehman <dlehman@redhat.com>
+#
+
+import os
+
+import block
+import iutil
+from ..errors import *
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+def name_from_dm_node(dm_node):
+ name = block.getNameFromDmNode(dm_node)
+ if name is not None:
+ return name
+
+ st = os.stat("/dev/%s" % dm_node)
+ major = os.major(st.st_rdev)
+ minor = os.minor(st.st_rdev)
+ name = iutil.execWithCapture("dmsetup",
+ ["info", "--columns",
+ "--noheadings", "-o", "name",
+ "-j", str(major), "-m", str(minor)],
+ stderr="/dev/tty5")
+ log.debug("name_from_dm(%s) returning '%s'" % (dm_node, name.strip()))
+ return name.strip()
+
+def dm_node_from_name(map_name):
+ dm_node = block.getDmNodeFromName(map_name)
+ if dm_node is not None:
+ return dm_node
+
+ devnum = iutil.execWithCapture("dmsetup",
+ ["info", "--columns",
+ "--noheadings",
+ "-o", "devno",
+ map_name],
+ stderr="/dev/tty5")
+ (major, sep, minor) = devnum.strip().partition(":")
+ if not sep:
+ raise DMError("dm device does not exist")
+
+ dm_node = "dm-%d" % int(minor)
+ log.debug("dm_node_from_name(%s) returning '%s'" % (map_name, dm_node))
+ return dm_node
+
+def dm_is_multipath(info):
+ major = None
+ minor = None
+
+ if info.has_key('MAJOR'):
+ major = info['MAJOR']
+ elif info.has_key('DM_MAJOR'):
+ major = info['DM_MAJOR']
+ if info.has_key('MINOR'):
+ minor = info['MINOR']
+ elif info.has_key('DM_MINOR'):
+ minor = info['DM_MINOR']
+
+ if major is None or minor is None:
+ return False
+
+ for map in block.dm.maps():
+ dev = map.dev
+ if dev.major == int(major) and dev.minor == int(minor):
+ for table in map.table:
+ if table.type == 'multipath':
+ return True
+
+def _get_backing_devnums_from_map(map_name):
+ ret = []
+ buf = iutil.execWithCapture("dmsetup",
+ ["info", "--columns",
+ "--noheadings",
+ "-o", "devnos_used",
+ map_name],
+ stderr="/dev/tty5")
+ dev_nums = buf.split()
+ for dev_num in dev_nums:
+ (major, colon, minor) = dev_num.partition(":")
+ ret.append((int(major), int(minor)))
+
+ return ret
+
+def get_backing_devnums(dm_node):
+ #dm_node = dm_node_from_name(map_name)
+ if not dm_node:
+ return None
+
+ top_dir = "/sys/block"
+ backing_devs = os.listdir("%s/%s/slaves/" % (top_dir, dm_node))
+ dev_nums = []
+ for backing_dev in backing_devs:
+ dev_num = open("%s/%s/dev" % (top_dir, backing_dev)).read().strip()
+ (_major, _minor) = dev_num.split(":")
+ dev_nums.append((int(_major), int(_minor)))
+
+ return dev_nums
+
+def get_backing_devs_from_name(map_name):
+ dm_node = dm_node_from_name(map_name)
+ if not dm_node:
+ return None
+
+ slave_devs = os.listdir("/sys/block/virtual/%s" % dm_node)
+ return slave_devs
+
diff --git a/storage/devicelibs/edd.py b/storage/devicelibs/edd.py
new file mode 100644
index 0000000..da03914
--- /dev/null
+++ b/storage/devicelibs/edd.py
@@ -0,0 +1,97 @@
+#
+# edd.py
+# BIOS EDD data parsing functions
+#
+# Copyright (C) 2010 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
+#
+# Author(s): Hans de Goede <hdegoede@redhat.com>
+#
+
+import os
+import struct
+
+import logging
+log = logging.getLogger("storage")
+
+def get_edd_dict(devices):
+ """Given an array of devices return a dict with the BIOS ID for them."""
+ edd_dict = {}
+
+ for biosdev in range(80, 80 + 15):
+ sysfspath = "/sys/firmware/edd/int13_dev%d" % biosdev
+ if not os.path.exists(sysfspath):
+ break # We are done
+
+ sysfspath = "/sys/firmware/edd/int13_dev%d/mbr_signature" % biosdev
+ if not os.path.exists(sysfspath):
+ log.warning("No mbrsig for biosdev: %d" % biosdev)
+ continue
+
+ try:
+ file = open(sysfspath, "r")
+ eddsig = file.read()
+ file.close()
+ except (IOError, OSError) as e:
+ log.warning("Error reading EDD mbrsig for %d: %s" %
+ (biosdev, str(e)))
+ continue
+
+ sysfspath = "/sys/firmware/edd/int13_dev%d/sectors" % biosdev
+ try:
+ file = open(sysfspath, "r")
+ eddsize = file.read()
+ file.close()
+ except (IOError, OSError) as e:
+ eddsize = None
+
+ found = []
+ for dev in devices:
+ try:
+ fd = os.open(dev.path, os.O_RDONLY)
+ os.lseek(fd, 440, 0)
+ mbrsig = struct.unpack('I', os.read(fd, 4))
+ os.close(fd)
+ except OSError as e:
+ log.warning("Error reading mbrsig from disk %s: %s" %
+ (dev.name, str(e)))
+ continue
+
+ mbrsigStr = "0x%08x\n" % mbrsig
+ if mbrsigStr == eddsig:
+ if eddsize:
+ sysfspath = "/sys%s/size" % dev.sysfsPath
+ try:
+ file = open(sysfspath, "r")
+ size = file.read()
+ file.close()
+ except (IOError, OSError) as e:
+ log.warning("Error getting size for: %s" % dev.name)
+ continue
+ if eddsize != size:
+ continue
+ found.append(dev.name)
+
+ if not found:
+ log.error("No matching mbr signature found for biosdev %d" %
+ biosdev)
+ elif len(found) > 1:
+ log.error("Multiple signature matches found for biosdev %d: %s" %
+ (biosdev, str(found)))
+ else:
+ log.info("Found %s for biosdev %d" %(found[0], biosdev))
+ edd_dict[found[0]] = biosdev
+
+ return edd_dict
diff --git a/storage/devicelibs/lvm.py b/storage/devicelibs/lvm.py
new file mode 100644
index 0000000..fd74f56
--- /dev/null
+++ b/storage/devicelibs/lvm.py
@@ -0,0 +1,419 @@
+#
+# lvm.py
+# lvm functions
+#
+# Copyright (C) 2009 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
+#
+# Author(s): Dave Lehman <dlehman@redhat.com>
+#
+
+import os
+import math
+import re
+
+import iutil
+
+from ..errors import *
+from constants import *
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+MAX_LV_SLOTS = 256
+
+def has_lvm():
+ has_lvm = False
+ for path in os.environ["PATH"].split(":"):
+ if os.access("%s/lvm" % path, os.X_OK):
+ has_lvm = True
+ break
+
+ if has_lvm:
+ has_lvm = False
+ for line in open("/proc/devices").readlines():
+ if "device-mapper" in line.split():
+ has_lvm = True
+ break
+
+ return has_lvm
+
+# Start config_args handling code
+#
+# Theoretically we can handle all that can be handled with the LVM --config
+# argument. For every time we call an lvm_cc (lvm compose config) funciton
+# we regenerate the config_args with all global info.
+config_args = [] # Holds the final argument list
+config_args_data = { "filterRejects": [], # regular expressions to reject.
+ "filterAccepts": [] } # regexp to accept
+
+def _composeConfig():
+ """lvm command accepts lvm.conf type arguments preceded by --config. """
+ global config_args, config_args_data
+ config_args = []
+
+ filter_string = ""
+ rejects = config_args_data["filterRejects"]
+ # we don't need the accept for now.
+ # accepts = config_args_data["filterAccepts"]
+ # if len(accepts) > 0:
+ # for i in range(len(rejects)):
+ # filter_string = filter_string + ("\"a|%s|\", " % accpets[i])
+
+ if len(rejects) > 0:
+ for i in range(len(rejects)):
+ filter_string = filter_string + ("\"r|%s|\"," % rejects[i])
+
+
+ filter_string = " filter=[%s] " % filter_string.strip(",")
+
+ # As we add config strings we should check them all.
+ if filter_string == "":
+ # Nothing was really done.
+ return
+
+ # devices_string can have (inside the brackets) "dir", "scan",
+ # "preferred_names", "filter", "cache_dir", "write_cache_state",
+ # "types", "sysfs_scan", "md_component_detection". see man lvm.conf.
+ devices_string = " devices {%s} " % (filter_string) # strings can be added
+ config_string = devices_string # more strings can be added.
+ config_args = ["--config", config_string]
+
+def lvm_cc_addFilterRejectRegexp(regexp):
+ """ Add a regular expression to the --config string."""
+ global config_args_data
+ config_args_data["filterRejects"].append(regexp)
+
+ # compoes config once more.
+ _composeConfig()
+
+def lvm_cc_resetFilter():
+ global config_args, config_args_data
+ config_args_data["filterRejects"] = []
+ config_args_data["filterAccepts"] = []
+ config_args = []
+# End config_args handling code.
+
+# Names that should not be used int the creation of VGs
+lvm_vg_blacklist = []
+def blacklistVG(name):
+ global lvm_vg_blacklist
+ lvm_vg_blacklist.append(name)
+
+def getPossiblePhysicalExtents(floor=0):
+ """Returns a list of integers representing the possible values for
+ the physical extent of a volume group. Value is in KB.
+
+ floor - size (in KB) of smallest PE we care about.
+ """
+
+ possiblePE = []
+ curpe = 8
+ while curpe <= 16384*1024:
+ if curpe >= floor:
+ possiblePE.append(curpe)
+ curpe = curpe * 2
+
+ return possiblePE
+
+def getMaxLVSize():
+ """ Return the maximum size (in MB) of a logical volume. """
+ if iutil.getArch() in ("x86_64", "ppc64", "alpha", "ia64", "s390", "sparc"): #64bit architectures
+ return (8*1024*1024*1024*1024) #Max is 8EiB (very large number..)
+ else:
+ return (16*1024*1024) #Max is 16TiB
+
+# LVM sources set the maximum length limit on VG and LV names at 128. Set
+# our default to 2 below that to account for 0 through 99 entries we may
+# make with this name as a prefix. LVM doesn't seem to impose a limit of
+# 99, but we do in anaconda.
+def safeLvmName(name, maxlen=126):
+ tmp = name.strip()
+ tmp = tmp.replace("/", "_")
+ tmp = re.sub("[^0-9a-zA-Z._]", "", tmp)
+ tmp = tmp.lstrip("_")
+
+ if len(tmp) > maxlen:
+ tmp = tmp[:maxlen]
+
+ return tmp
+
+def clampSize(size, pesize, roundup=None):
+ if roundup:
+ round = math.ceil
+ else:
+ round = math.floor
+
+ return long(round(float(size)/float(pesize)) * pesize)
+
+def lvm(args, progress=None):
+ rc = iutil.execWithPulseProgress("lvm", args,
+ stdout = "/dev/tty5",
+ stderr = "/dev/tty5",
+ progress=progress)
+ if not rc:
+ return
+
+ try:
+ # grab the last line of program.log and strip off the timestamp
+ msg = open("/tmp/program.log").readlines()[-1]
+ msg = msg.split("program: ", 1)[1].strip()
+ except Exception:
+ msg = ""
+
+ raise LVMError(msg)
+
+def pvcreate(device, progress=None):
+ args = ["pvcreate"] + \
+ config_args + \
+ [device]
+
+ try:
+ lvm(args, progress=progress)
+ except LVMError as msg:
+ raise LVMError("pvcreate failed for %s: %s" % (device, msg))
+
+def pvresize(device, size):
+ args = ["pvresize"] + \
+ ["--setphysicalvolumesize", ("%dm" % size)] + \
+ config_args + \
+ [device]
+
+ try:
+ lvm(args)
+ except LVMError as msg:
+ raise LVMError("pvresize failed for %s: %s" % (device, msg))
+
+def pvremove(device):
+ args = ["pvremove"] + \
+ config_args + \
+ [device]
+
+ try:
+ lvm(args)
+ except LVMError as msg:
+ raise LVMError("pvremove failed for %s: %s" % (device, msg))
+
+def pvinfo(device):
+ """
+ If the PV was created with '--metadacopies 0', lvm will do some
+ scanning of devices to determine from their metadata which VG
+ this PV belongs to.
+
+ pvs -o pv_name,pv_mda_count,vg_name,vg_uuid --config \
+ 'devices { scan = "/dev" filter = ["a/loop0/", "r/.*/"] }'
+ """
+ #cfg = "'devices { scan = \"/dev\" filter = [\"a/%s/\", \"r/.*/\"] }'"
+ args = ["pvs", "--noheadings"] + \
+ ["--units", "m"] + \
+ ["-o", "pv_name,pv_mda_count,vg_name,vg_uuid"] + \
+ config_args + \
+ [device]
+
+ rc = iutil.execWithCapture("lvm", args,
+ stderr = "/dev/tty5")
+ vals = rc.split()
+ if not vals:
+ raise LVMError("pvinfo failed for %s" % device)
+
+ # don't raise an exception if pv is not a part of any vg
+ pv_name = vals[0]
+ try:
+ vg_name, vg_uuid = vals[2], vals[3]
+ except IndexError:
+ vg_name, vg_uuid = "", ""
+
+ info = {'pv_name': pv_name,
+ 'vg_name': vg_name,
+ 'vg_uuid': vg_uuid}
+
+ return info
+
+def vgcreate(vg_name, pv_list, pe_size, progress=None):
+ argv = ["vgcreate"]
+ if pe_size:
+ argv.extend(["-s", "%dm" % pe_size])
+ argv.extend(config_args)
+ argv.append(vg_name)
+ argv.extend(pv_list)
+
+ try:
+ lvm(argv, progress=progress)
+ except LVMError as msg:
+ raise LVMError("vgcreate failed for %s: %s" % (vg_name, msg))
+
+def vgremove(vg_name):
+ args = ["vgremove", "--force"] + \
+ config_args +\
+ [vg_name]
+
+ try:
+ lvm(args)
+ except LVMError as msg:
+ raise LVMError("vgremove failed for %s: %s" % (vg_name, msg))
+
+def vgactivate(vg_name):
+ args = ["vgchange", "-a", "y"] + \
+ config_args + \
+ [vg_name]
+
+ try:
+ lvm(args)
+ except LVMError as msg:
+ raise LVMError("vgactivate failed for %s: %s" % (vg_name, msg))
+
+def vgdeactivate(vg_name):
+ args = ["vgchange", "-a", "n"] + \
+ config_args + \
+ [vg_name]
+
+ try:
+ lvm(args)
+ except LVMError as msg:
+ raise LVMError("vgdeactivate failed for %s: %s" % (vg_name, msg))
+
+def vgreduce(vg_name, pv_list, rm=False):
+ """ Reduce a VG.
+
+ rm -> with RemoveMissing option.
+ Use pv_list when rm=False, otherwise ignore pv_list and call vgreduce with
+ the --removemissing option.
+ """
+ args = ["vgreduce"]
+ if rm:
+ args.extend(["--removemissing", vg_name])
+ else:
+ args.extend([vg_name] + pv_list)
+
+ try:
+ lvm(args)
+ except LVMError as msg:
+ raise LVMError("vgreduce failed for %s: %s" % (vg_name, msg))
+
+def vginfo(vg_name):
+ args = ["vgs", "--noheadings", "--nosuffix"] + \
+ ["--units", "m"] + \
+ ["-o", "uuid,size,free,extent_size,extent_count,free_count,pv_count"] + \
+ config_args + \
+ [vg_name]
+
+ buf = iutil.execWithCapture("lvm",
+ args,
+ stderr="/dev/tty5")
+ info = buf.split()
+ if len(info) != 7:
+ raise LVMError(_("vginfo failed for %s" % vg_name))
+
+ d = {}
+ (d['uuid'],d['size'],d['free'],d['pe_size'],
+ d['pe_count'],d['pe_free'],d['pv_count']) = info
+ return d
+
+def lvs(vg_name):
+ args = ["lvs", "--noheadings", "--nosuffix"] + \
+ ["--units", "m"] + \
+ ["-o", "lv_name,lv_uuid,lv_size,lv_attr"] + \
+ config_args + \
+ [vg_name]
+
+ buf = iutil.execWithCapture("lvm",
+ args,
+ stderr="/dev/tty5")
+
+ lvs = {}
+ for line in buf.splitlines():
+ line = line.strip()
+ if not line:
+ continue
+ (name, uuid, size, attr) = line.split()
+ lvs[name] = {"size": size,
+ "uuid": uuid,
+ "attr": attr}
+
+ if not lvs:
+ raise LVMError(_("lvs failed for %s" % vg_name))
+
+ return lvs
+
+def lvorigin(vg_name, lv_name):
+ args = ["lvs", "--noheadings", "-o", "origin"] + \
+ config_args + \
+ ["%s/%s" % (vg_name, lv_name)]
+
+ buf = iutil.execWithCapture("lvm",
+ args,
+ stderr="/dev/tty5")
+
+ try:
+ origin = buf.splitlines()[0].strip()
+ except IndexError:
+ origin = ''
+
+ return origin
+
+def lvcreate(vg_name, lv_name, size, progress=None):
+ args = ["lvcreate"] + \
+ ["-L", "%dm" % size] + \
+ ["-n", lv_name] + \
+ config_args + \
+ [vg_name]
+
+ try:
+ lvm(args, progress=progress)
+ except LVMError as msg:
+ raise LVMError("lvcreate failed for %s/%s: %s" % (vg_name, lv_name, msg))
+
+def lvremove(vg_name, lv_name):
+ args = ["lvremove"] + \
+ config_args + \
+ ["%s/%s" % (vg_name, lv_name)]
+
+ try:
+ lvm(args)
+ except LVMError as msg:
+ raise LVMError("lvremove failed for %s: %s" % (lv_name, msg))
+
+def lvresize(vg_name, lv_name, size):
+ args = ["lvresize"] + \
+ ["--force", "-L", "%dm" % size] + \
+ config_args + \
+ ["%s/%s" % (vg_name, lv_name)]
+
+ try:
+ lvm(args)
+ except LVMError as msg:
+ raise LVMError("lvresize failed for %s: %s" % (lv_name, msg))
+
+def lvactivate(vg_name, lv_name):
+ # see if lvchange accepts paths of the form 'mapper/$vg-$lv'
+ args = ["lvchange", "-a", "y"] + \
+ config_args + \
+ ["%s/%s" % (vg_name, lv_name)]
+
+ try:
+ lvm(args)
+ except LVMError as msg:
+ raise LVMError("lvactivate failed for %s: %s" % (lv_name, msg))
+
+def lvdeactivate(vg_name, lv_name):
+ args = ["lvchange", "-a", "n"] + \
+ config_args + \
+ ["%s/%s" % (vg_name, lv_name)]
+
+ try:
+ lvm(args)
+ except LVMError as msg:
+ raise LVMError("lvdeactivate failed for %s: %s" % (lv_name, msg))
+
diff --git a/storage/devicelibs/mdraid.py b/storage/devicelibs/mdraid.py
new file mode 100644
index 0000000..4e5a5f7
--- /dev/null
+++ b/storage/devicelibs/mdraid.py
@@ -0,0 +1,234 @@
+#
+# mdraid.py
+# mdraid functions
+#
+# Copyright (C) 2009 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
+#
+# Author(s): Dave Lehman <dlehman@redhat.com>
+#
+
+import os
+
+import iutil
+from ..errors import *
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+# raidlevels constants
+RAID10 = 10
+RAID6 = 6
+RAID5 = 5
+RAID4 = 4
+RAID1 = 1
+RAID0 = 0
+
+def getRaidLevels():
+ mdstat_descriptors = {
+ RAID10: ("[RAID10]", "[raid10]"),
+ RAID6: ("[RAID6]", "[raid6]"),
+ RAID5: ("[RAID5]", "[raid5]"),
+ RAID4: ("[RAID4]", "[raid4]"),
+ RAID1: ("[RAID1]", "[raid1]"),
+ RAID0: ("[RAID0]", "[raid0]"),
+ }
+ avail = []
+ try:
+ f = open("/proc/mdstat", "r")
+ except IOError:
+ pass
+ else:
+ for l in f.readlines():
+ if not l.startswith("Personalities"):
+ continue
+
+ lst = l.split()
+
+ for level in mdstat_descriptors:
+ for d in mdstat_descriptors[level]:
+ if d in lst:
+ avail.append(level)
+ break
+
+ f.close()
+
+ avail.sort()
+ return avail
+
+raid_levels = getRaidLevels()
+
+def raidLevel(descriptor):
+ for level in raid_levels:
+ if isRaid(level, descriptor):
+ return level
+ else:
+ raise ValueError, "invalid raid level descriptor %s" % descriptor
+
+def isRaid(raid, raidlevel):
+ """Return whether raidlevel is a valid descriptor of raid"""
+ raid_descriptors = {RAID10: ("RAID10", "raid10", "10", 10),
+ RAID6: ("RAID6", "raid6", "6", 6),
+ RAID5: ("RAID5", "raid5", "5", 5),
+ RAID4: ("RAID4", "raid4", "4", 4),
+ RAID1: ("mirror", "RAID1", "raid1", "1", 1),
+ RAID0: ("stripe", "RAID0", "raid0", "0", 0)}
+
+ if raid in raid_descriptors:
+ return raidlevel in raid_descriptors[raid]
+ else:
+ raise ValueError, "invalid raid level %d" % raid
+
+def get_raid_min_members(raidlevel):
+ """Return the minimum number of raid members required for raid level"""
+ raid_min_members = {RAID10: 2,
+ RAID6: 4,
+ RAID5: 3,
+ RAID4: 3,
+ RAID1: 2,
+ RAID0: 2}
+
+ for raid, min_members in raid_min_members.items():
+ if isRaid(raid, raidlevel):
+ return min_members
+
+ raise ValueError, "invalid raid level %d" % raidlevel
+
+def get_raid_max_spares(raidlevel, nummembers):
+ """Return the maximum number of raid spares for raidlevel."""
+ raid_max_spares = {RAID10: lambda: max(0, nummembers - get_raid_min_members(RAID10)),
+ RAID6: lambda: max(0, nummembers - get_raid_min_members(RAID6)),
+ RAID5: lambda: max(0, nummembers - get_raid_min_members(RAID5)),
+ RAID4: lambda: max(0, nummembers - get_raid_min_members(RAID4)),
+ RAID1: lambda: max(0, nummembers - get_raid_min_members(RAID1)),
+ RAID0: lambda: 0}
+
+ for raid, max_spares_func in raid_max_spares.items():
+ if isRaid(raid, raidlevel):
+ return max_spares_func()
+
+ raise ValueError, "invalid raid level %d" % raidlevel
+
+def mdadm(args, progress=None):
+ rc = iutil.execWithPulseProgress("mdadm", args,
+ stdout = "/dev/tty5",
+ stderr = "/dev/tty5",
+ progress=progress)
+ if not rc:
+ return
+
+ try:
+ # grab the last line of program.log and strip off the timestamp
+ msg = open("/tmp/program.log").readlines()[-1]
+ msg = msg.split("program: ", 1)[1].strip()
+ except Exception:
+ msg = ""
+
+ raise MDRaidError(msg)
+
+def mdcreate(device, level, disks, spares=0, metadataVer=None, bitmap=False,
+ progress=None):
+ argv = ["--create", device, "--run", "--level=%s" % level]
+ raid_devs = len(disks) - spares
+ argv.append("--raid-devices=%d" % raid_devs)
+ if spares:
+ argv.append("--spare-devices=%d" % spares)
+ if metadataVer:
+ argv.append("--metadata=%s" % metadataVer)
+ if bitmap:
+ argv.append("--bitmap=internal")
+ argv.extend(disks)
+
+ try:
+ mdadm(argv, progress=progress)
+ except MDRaidError as msg:
+ raise MDRaidError("mdcreate failed for %s: %s" % (device, msg))
+
+def mddestroy(device):
+ args = ["--zero-superblock", device]
+
+ try:
+ mdadm(args)
+ except MDRaidError as msg:
+ raise MDRaidError("mddestroy failed for %s: %s" % (device, msg))
+
+def mdadd(device):
+ args = ["--incremental", "--quiet"]
+ args.append(device)
+
+ try:
+ mdadm(args)
+ except MDRaidError as msg:
+ raise MDRaidError("mdadd failed for %s: %s" % (device, msg))
+
+def mdactivate(device, members=[], super_minor=None, update_super_minor=False,
+ uuid=None):
+ if super_minor is None and not uuid:
+ raise ValueError("mdactivate requires either a uuid or a super-minor")
+
+ if uuid:
+ identifier = "--uuid=%s" % uuid
+ elif super_minor is not None:
+ identifier = "--super-minor=%d" % super_minor
+ else:
+ identifier = ""
+
+ if update_super_minor:
+ extra_args = ["--update=super-minor"]
+ else:
+ extra_args = [ ]
+
+ args = ["--assemble", device, identifier, "--run", "--auto=md"]
+ args += extra_args
+ args += members
+
+ try:
+ mdadm(args)
+ except MDRaidError as msg:
+ raise MDRaidError("mdactivate failed for %s: %s" % (device, msg))
+
+def mddeactivate(device):
+ args = ["--stop", device]
+
+ try:
+ mdadm(args)
+ except MDRaidError as msg:
+ raise MDRaidError("mddeactivate failed for %s: %s" % (device, msg))
+
+def mdexamine(device):
+ vars = iutil.execWithCapture("mdadm",
+ ["--examine", "--brief", device],
+ stderr="/dev/tty5").split()
+
+ info = {}
+ if vars:
+ try:
+ info["device"] = vars[1]
+ vars = vars[2:]
+ except IndexError:
+ return {}
+
+ for var in vars:
+ (name, equals, value) = var.partition("=")
+ if not equals:
+ continue
+
+ info[name.lower()] = value.strip()
+
+ return info
+
diff --git a/storage/devicelibs/mpath.py b/storage/devicelibs/mpath.py
new file mode 100644
index 0000000..adb2644
--- /dev/null
+++ b/storage/devicelibs/mpath.py
@@ -0,0 +1,228 @@
+from ..udev import *
+import iutil
+
+def parseMultipathOutput(output):
+ # this function parses output from "multipath -d", so we can use its
+ # logic for our topology.
+ # The input looks like:
+ # create: mpathb (1ATA ST3120026AS 5M) undef ATA,ST3120026AS
+ # size=112G features='0' hwhandler='0' wp=undef
+ # `-+- policy='round-robin 0' prio=1 status=undef
+ # `- 2:0:0:0 sda 8:0 undef ready running
+ # create: mpatha (36006016092d21800703762872c60db11) undef DGC,RAID 5
+ # size=10G features='1 queue_if_no_path' hwhandler='1 emc' wp=undef
+ # `-+- policy='round-robin 0' prio=2 status=undef
+ # |- 6:0:0:0 sdb 8:16 undef ready running
+ # `- 7:0:0:0 sdc 8:32 undef ready running
+ #
+ # (In anaconda, the first one there won't be included because we blacklist
+ # "ATA" as a vendor.)
+ #
+ # It returns a structure like:
+ # [ {'mpatha':['sdb','sdc']}, ... ]
+ mpaths = {}
+ if output is None:
+ return mpaths
+
+ name = None
+ devices = []
+
+ lines = output.split('\n')
+ for line in lines:
+ lexemes = line.split()
+ if not lexemes:
+ break
+ if lexemes[0] == 'create:':
+ if name and devices:
+ mpaths[name] = devices
+ name = None
+ devices = []
+ name = lexemes[1]
+ elif lexemes[0].startswith('size='):
+ pass
+ elif lexemes[0] == '`-+-':
+ pass
+ elif lexemes[0] in ['|-','`-']:
+ devices.append(lexemes[2].replace('!', '/'))
+
+ if name and devices:
+ mpaths[name] = devices
+
+ return mpaths
+
+def identifyMultipaths(devices):
+ # this function does a couple of things
+ # 1) identifies multipath disks
+ # 2) sets their ID_FS_TYPE to multipath_member
+ # 3) removes the individual members of an mpath's partitions
+ # sample input with multipath pair [sdb,sdc]
+ # [sr0, sda, sda1, sdb, sdb1, sdb2, sdc, sdc1, sdd, sdd1, sdd2]
+ # sample output:
+ # [sda, sdd], [[sdb, sdc]], [sr0, sda1, sdd1, sdd2]]
+ log.info("devices to scan for multipath: %s" % [d['name'] for d in devices])
+
+ topology = parseMultipathOutput(iutil.execWithCapture("multipath", ["-d",]))
+ # find the devices that aren't in topology, and add them into it...
+ topodevs = reduce(lambda x,y: x.union(y), topology.values(), set())
+ for name in set([d['name'] for d in devices]).difference(topodevs):
+ topology[name] = [name]
+
+ devmap = {}
+ non_disk_devices = {}
+ for d in devices:
+ if not udev_device_is_disk(d):
+ non_disk_devices[d['name']] = d
+ log.info("adding %s to non_disk_device list" % (d['name'],))
+ continue
+ devmap[d['name']] = d
+
+ singlepath_disks = []
+ multipaths = []
+
+ for name, disks in topology.items():
+ if len(disks) == 1:
+ if not non_disk_devices.has_key(disks[0]):
+ log.info("adding %s to singlepath_disks" % (disks[0],))
+ singlepath_disks.append(devmap[disks[0]])
+ else:
+ # some usb cardreaders use multiple lun's (for different slots)
+ # and report a fake disk serial which is the same for all the
+ # lun's (#517603)
+ all_usb = True
+ # see if we've got any non-disk devices on our mpath list.
+ # If so, they're probably false-positives.
+ non_disks = False
+ for disk in disks:
+ d = devmap[disk]
+ if d.get("ID_USB_DRIVER") != "usb-storage":
+ all_usb = False
+ if (not devmap.has_key(disk)) and non_disk_devices.has_key(disk):
+ log.warning("non-disk device %s is part of an mpath" %
+ (disk,))
+ non_disks = True
+
+ if all_usb:
+ log.info("adding multi lun usb mass storage device to singlepath_disks: %s" %
+ (disks,))
+ singlepath_disks.extend([devmap[d] for d in disks])
+ continue
+
+ if non_disks:
+ for disk in disks:
+ if devmap.has_key(disk):
+ del devmap[disk]
+ if topology.has_key(disk):
+ del topology[disk]
+ continue
+
+ log.info("found multipath set: %s" % (disks,))
+ for disk in disks:
+ d = devmap[disk]
+ log.info("adding %s to multipath_disks" % (disk,))
+ d["ID_FS_TYPE"] = "multipath_member"
+ d["ID_MPATH_NAME"] = name
+
+ multipaths.append([devmap[d] for d in disks])
+
+ non_disk_serials = {}
+ for name,device in non_disk_devices.items():
+ serial = udev_device_get_serial(device)
+ non_disk_serials.setdefault(serial, [])
+ non_disk_serials[serial].append(device)
+
+ for mpath in multipaths:
+ for serial in [d.get('ID_SERIAL_SHORT') for d in mpath]:
+ if non_disk_serials.has_key(serial):
+ log.info("filtering out non disk devices [%s]" % [d['name'] for d in non_disk_serials[serial]])
+ for name in [d['name'] for d in non_disk_serials[serial]]:
+ if non_disk_devices.has_key(name):
+ del non_disk_devices[name]
+
+ partition_devices = []
+ for device in non_disk_devices.values():
+ partition_devices.append(device)
+
+ # this is the list of devices we want to keep from the original
+ # device list, but we want to maintain its original order.
+ singlepath_disks = filter(lambda d: d in devices, singlepath_disks)
+ #multipaths = filter(lambda d: d in devices, multipaths)
+ partition_devices = filter(lambda d: d in devices, partition_devices)
+
+ mpathStr = "["
+ for mpath in multipaths:
+ mpathStr += str([d['name'] for d in mpath])
+ mpathStr += "]"
+
+ s = "(%s, %s, %s)" % ([d['name'] for d in singlepath_disks], \
+ mpathStr, \
+ [d['name'] for d in partition_devices])
+ log.info("devices post multipath scan: %s" % s)
+ return (singlepath_disks, multipaths, partition_devices)
+
+class MultipathConfigWriter:
+ def __init__(self):
+ self.blacklist_devices = []
+ self.mpaths = []
+
+ def addBlacklistDevice(self, device):
+ self.blacklist_devices.append(device)
+
+ def addMultipathDevice(self, mpath):
+ self.mpaths.append(mpath)
+
+ def write(self):
+ # if you add anything here, be sure and also add it to anaconda's
+ # multipath.conf
+ ret = ''
+ ret += """\
+# multipath.conf written by anaconda
+
+defaults {
+ user_friendly_names yes
+}
+blacklist {
+ devnode "^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*"
+ devnode "^hd[a-z]"
+ devnode "^dcssblk[0-9]*"
+ device {
+ vendor "DGC"
+ product "LUNZ"
+ }
+ device {
+ vendor "IBM"
+ product "S/390.*"
+ }
+ # don't count normal SATA devices as multipaths
+ device {
+ vendor "ATA"
+ }
+ # don't count 3ware devices as multipaths
+ device {
+ vendor "3ware"
+ }
+ device {
+ vendor "AMCC"
+ }
+ # nor highpoint devices
+ device {
+ vendor "HPT"
+ }
+"""
+ for device in self.blacklist_devices:
+ if device.serial:
+ ret += '\twwid %s\n' % device.serial
+ elif device.vendor and device.model:
+ ret += '\tdevice {\n'
+ ret += '\t\tvendor %s\n' % device.vendor
+ ret += '\t\tproduct %s\n' % device.model
+ ret += '\t}\n'
+ ret += '}\n'
+ ret += 'multipaths {\n'
+ for mpath in self.mpaths:
+ ret += '\tmultipath {\n'
+ for k,v in mpath.config.items():
+ ret += '\t\t%s %s\n' % (k, v)
+ ret += '\t}\n'
+ ret += '}\n'
+
+ return ret
diff --git a/storage/devicelibs/swap.py b/storage/devicelibs/swap.py
new file mode 100644
index 0000000..92dfe93
--- /dev/null
+++ b/storage/devicelibs/swap.py
@@ -0,0 +1,125 @@
+# swap.py
+# Python module for managing swap devices.
+#
+# 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 resource
+
+import iutil
+import os
+
+from ..errors import *
+from . import dm
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+
+def mkswap(device, label='', progress=None):
+ # We use -f to force since mkswap tends to refuse creation on lvs with
+ # a message about erasing bootbits sectors on whole disks. Bah.
+ argv = ["-f"]
+ if label:
+ argv.extend(["-L", label])
+ argv.append(device)
+
+ rc = iutil.execWithPulseProgress("mkswap", argv,
+ stderr = "/dev/tty5",
+ stdout = "/dev/tty5",
+ progress=progress)
+
+ if rc:
+ raise SwapError("mkswap failed for '%s'" % device)
+
+def swapon(device, priority=None):
+ pagesize = resource.getpagesize()
+ buf = None
+ sig = None
+
+ if pagesize > 2048:
+ num = pagesize
+ else:
+ num = 2048
+
+ try:
+ fd = os.open(device, os.O_RDONLY)
+ buf = os.read(fd, num)
+ except OSError:
+ pass
+ finally:
+ try:
+ os.close(fd)
+ except (OSError, UnboundLocalError):
+ pass
+
+ if buf is not None and len(buf) == pagesize:
+ sig = buf[pagesize - 10:]
+ if sig == 'SWAP-SPACE':
+ raise OldSwapError
+ if sig == 'S1SUSPEND\x00' or sig == 'S2SUSPEND\x00':
+ raise SuspendError
+
+ if sig != 'SWAPSPACE2':
+ raise UnknownSwapError
+
+ argv = []
+ if isinstance(priority, int) and 0 <= priority <= 32767:
+ argv.extend(["-p", "%d" % priority])
+ argv.append(device)
+
+ rc = iutil.execWithRedirect("swapon",
+ argv,
+ stderr = "/dev/tty5",
+ stdout = "/dev/tty5")
+
+ if rc:
+ raise SwapError("swapon failed for '%s'" % device)
+
+def swapoff(device):
+ rc = iutil.execWithRedirect("swapoff", [device],
+ stderr = "/dev/tty5",
+ stdout = "/dev/tty5")
+
+ if rc:
+ raise SwapError("swapoff failed for '%s'" % device)
+
+def swapstatus(device):
+ alt_dev = None
+ if device.startswith("/dev/mapper/"):
+ # get the real device node for device-mapper devices since the ones
+ # with meaningful names are just symlinks
+ try:
+ alt_dev = "/dev/%s" % dm.dm_node_from_name(device.split("/")[-1])
+ except DMError:
+ alt_dev = None
+
+ lines = open("/proc/swaps").readlines()
+ status = False
+ for line in lines:
+ if not line.strip():
+ continue
+
+ swap_dev = line.split()[0]
+ if swap_dev in [device, alt_dev]:
+ status = True
+ break
+
+ return status
+
diff --git a/storage/devices.py b/storage/devices.py
new file mode 100644
index 0000000..f310cef
--- /dev/null
+++ b/storage/devices.py
@@ -0,0 +1,3576 @@
+# devices.py
+# Device classes 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>
+#
+
+
+"""
+ Device classes for use by anaconda.
+
+ This is the hierarchy of device objects that anaconda will use for
+ managing storage devices in the system. These classes will
+ individually make use of external support modules as needed to
+ perform operations specific to the type of device they represent.
+
+ TODO:
+ - see how to do network devices (NetworkManager may help)
+ - perhaps just a wrapper here
+ - document return values of all methods/functions
+ - find out what other kinds of wild and crazy devices we need to
+ represent here (iseries? xen? more mainframe? mac? ps?)
+ - PReP
+ - this is a prime candidate for a PseudoDevice
+ - DASD
+ - ZFCP
+ - XEN
+
+ What specifications do we allow? new existing
+ partitions
+ usage + +
+ filesystem, partition type are implicit
+ mountpoint + +
+ size
+ exact + -
+ range + -
+ resize - +
+ format - +
+ encryption + +
+
+ disk
+ exact + -
+ set + -
+ how will we specify this?
+ partition w/ multiple parents cannot otherwise occur
+ primary + -
+
+ mdraid sets
+ filesystem (*) + +
+ mountpoint + +
+ size?
+ format - +
+ encryption + +
+
+ level + ?
+ device minor + ?
+ member devices + ?
+ spares + ?
+ name?
+ bitmap? (boolean) + -
+
+ volume groups
+ name + -
+ member pvs + +
+ pesize + ?
+
+ logical volumes
+ filesystem + +
+ mountpoint + +
+ size
+ exact + ?
+ format - +
+ encryption + +
+
+ name + ?
+ vgname + ?
+
+
+"""
+
+import os
+import math
+import copy
+import time
+
+# device backend modules
+from devicelibs import mdraid
+from devicelibs import lvm
+from devicelibs import dm
+import parted
+import _ped
+import block
+
+from errors import *
+from iutil import notify_kernel, numeric_type
+from .storage_log import log_method_call
+from udev import *
+from formats import get_device_format_class, getFormat, DeviceFormat
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+def get_device_majors():
+ majors = {}
+ for line in open("/proc/devices").readlines():
+ try:
+ (major, device) = line.split()
+ except ValueError:
+ continue
+ try:
+ majors[int(major)] = device
+ except ValueError:
+ continue
+ return majors
+device_majors = get_device_majors()
+
+
+def devicePathToName(devicePath):
+ if devicePath.startswith("/dev/"):
+ name = devicePath[5:]
+ else:
+ name = devicePath
+
+ if name.startswith("mapper/"):
+ name = name[7:]
+
+ return name
+
+
+def deviceNameToDiskByPath(deviceName=None):
+ bypath = '/dev/disk/by-path'
+
+ if not deviceName:
+ return ""
+
+ if not os.path.isdir(bypath):
+ return ""
+
+ deviceName = os.path.basename(deviceName)
+
+ for path in os.listdir(bypath):
+ entry = bypath + '/' + path
+
+ if os.path.islink(entry):
+ target = os.path.basename(os.readlink(entry))
+ else:
+ target = os.path.basename(entry)
+
+ if target == deviceName:
+ return entry
+
+ return ""
+
+
+class Device(object):
+ """ A generic device.
+
+ Device instances know which devices they depend upon (parents
+ attribute). They do not know which devices depend upon them, but
+ they do know whether or not they have any dependent devices
+ (isleaf attribute).
+
+ A Device's setup method should set up all parent devices as well
+ as the device itself. It should not run the resident format's
+ setup method.
+
+ Which Device types rely on their parents' formats being active?
+ DMCryptDevice
+
+ A Device's teardown method should accept the keyword argument
+ recursive, which takes a boolean value and indicates whether or
+ not to recursively close parent devices.
+
+ A Device's create method should create all parent devices as well
+ as the device itself. It should also run the Device's setup method
+ after creating the device. The create method should not create a
+ device's resident format.
+
+ Which device type rely on their parents' formats to be created
+ before they can be created/assembled?
+ VolumeGroup
+ DMCryptDevice
+
+ A Device's destroy method should destroy any resident format
+ before destroying the device itself.
+
+ """
+
+ # This is a counter for generating unique ids for Devices.
+ _id = 0
+
+ _type = "generic device"
+ _packages = []
+
+ def __init__(self, name, parents=None):
+ """ Create a Device instance.
+
+ Arguments:
+
+ name -- the device name (generally a device node's basename)
+
+ Keyword Arguments:
+
+ parents -- a list of required Device instances
+
+ """
+ self._name = name
+ if parents is None:
+ parents = []
+ elif not isinstance(parents, list):
+ raise ValueError("parents must be a list of Device instances")
+ self.parents = parents
+ self.kids = 0
+
+ # Set this instance's id and increment the counter.
+ self.id = Device._id
+ Device._id += 1
+
+ for parent in self.parents:
+ parent.addChild()
+
+ def __deepcopy__(self, memo):
+ """ Create a deep copy of a Device instance.
+
+ We can't do copy.deepcopy on parted objects, which is okay.
+ For these parted objects, we just do a shallow copy.
+ """
+ new = self.__class__.__new__(self.__class__)
+ memo[id(self)] = new
+ dont_copy_attrs = ('_raidSet',)
+ shallow_copy_attrs = ('_partedDevice', '_partedPartition')
+ for (attr, value) in self.__dict__.items():
+ if attr in dont_copy_attrs:
+ setattr(new, attr, value)
+ elif attr in shallow_copy_attrs:
+ setattr(new, attr, copy.copy(value))
+ else:
+ setattr(new, attr, copy.deepcopy(value, memo))
+
+ return new
+
+ def __str__(self):
+ s = ("%(type)s instance (%(id)s) --\n"
+ " name = %(name)s status = %(status)s"
+ " parents = %(parents)s\n"
+ " kids = %(kids)s\n"
+ " id = %(dev_id)s\n" %
+ {"type": self.__class__.__name__, "id": "%#x" % id(self),
+ "name": self.name, "parents": self.parents, "kids": self.kids,
+ "status": self.status, "dev_id": self.id})
+ return s
+
+ @property
+ def dict(self):
+ d = {"type": self.type, "name": self.name,
+ "parents": [p.name for p in self.parents]}
+ return d
+
+ def writeKS(self, f, preexisting=False, noformat=False, s=None):
+ return
+
+ def removeChild(self):
+ log_method_call(self, name=self.name, kids=self.kids)
+ self.kids -= 1
+
+ def addChild(self):
+ log_method_call(self, name=self.name, kids=self.kids)
+ self.kids += 1
+
+ def setup(self, intf=None):
+ """ Open, or set up, a device. """
+ raise NotImplementedError("setup method not defined for Device")
+
+ def teardown(self, recursive=None):
+ """ Close, or tear down, a device. """
+ raise NotImplementedError("teardown method not defined for Device")
+
+ def create(self, intf=None):
+ """ Create the device. """
+ raise NotImplementedError("create method not defined for Device")
+
+ def destroy(self):
+ """ Destroy the device. """
+ raise NotImplementedError("destroy method not defined for Device")
+
+ def setupParents(self, orig=False):
+ """ Run setup method of all parent devices. """
+ log_method_call(self, name=self.name, orig=orig, kids=self.kids)
+ for parent in self.parents:
+ parent.setup(orig=orig)
+
+ def teardownParents(self, recursive=None):
+ """ Run teardown method of all parent devices. """
+ for parent in self.parents:
+ parent.teardown(recursive=recursive)
+
+ def createParents(self):
+ """ Run create method of all parent devices. """
+ log.info("NOTE: recursive device creation disabled")
+ for parent in self.parents:
+ if not parent.exists:
+ raise DeviceError("parent device does not exist", self.name)
+ #parent.create()
+
+ def dependsOn(self, dep):
+ """ Return True if this device depends on dep. """
+ # XXX does a device depend on itself?
+ if dep in self.parents:
+ return True
+
+ for parent in self.parents:
+ if parent.dependsOn(dep):
+ return True
+
+ return False
+
+ def dracutSetupString(self):
+ return ""
+
+ @property
+ def status(self):
+ """ This device's status.
+
+ For now, this should return a boolean:
+ True the device is open and ready for use
+ False the device is not open
+ """
+ return False
+
+ @property
+ def name(self):
+ """ This device's name. """
+ return self._name
+
+ @property
+ def isleaf(self):
+ """ True if this device has no children. """
+ return self.kids == 0
+
+ @property
+ def typeDescription(self):
+ """ String describing the device type. """
+ return self._type
+
+ @property
+ def type(self):
+ """ Device type. """
+ return self._type
+
+ @property
+ def packages(self):
+ """ List of packages required to manage devices of this type.
+
+ This list includes the packages required by its parent devices.
+ """
+ packages = self._packages
+ for parent in self.parents:
+ for package in parent.packages:
+ if package not in packages:
+ packages.append(package)
+
+ return packages
+
+ @property
+ def mediaPresent(self):
+ return True
+
+
+class NetworkStorageDevice(object):
+ """ Virtual base class for network backed storage devices """
+
+ def __init__(self, host_address=None, nic=None):
+ """ Create a NetworkStorage Device instance. Note this class is only
+ to be used as a baseclass and then only with multiple inheritance.
+ The only correct use is:
+ class MyStorageDevice(StorageDevice, NetworkStorageDevice):
+
+ The sole purpose of this class is to:
+ 1) Be able to check if a StorageDevice is network backed
+ (using isinstance).
+ 2) To be able to get the host address of the host (server) backing
+ the storage *or* the NIC through which the storage is connected
+
+ Arguments:
+
+ host_address -- host address of the backing server
+ nic -- nic to which the storage is bound
+ """
+ self.host_address = host_address
+ self.nic = nic
+
+
+class StorageDevice(Device):
+ """ A generic storage device.
+
+ A fully qualified path to the device node can be obtained via the
+ path attribute, although it is not guaranteed to be useful, or
+ even present, unless the StorageDevice's setup method has been
+ run.
+
+ StorageDevice instances can optionally contain a filesystem,
+ represented by an FS instance. A StorageDevice's create method
+ should create a filesystem if one has been specified.
+ """
+ _type = "storage device"
+ _devDir = "/dev"
+ sysfsBlockDir = "class/block"
+ _resizable = False
+ _partitionable = False
+ _isDisk = False
+
+ def __init__(self, device, format=None,
+ size=None, major=None, minor=None,
+ sysfsPath='', parents=None, exists=None, serial=None,
+ vendor="", model="", bus=""):
+ """ Create a StorageDevice instance.
+
+ Arguments:
+
+ device -- the device name (generally a device node's basename)
+
+ Keyword Arguments:
+
+ size -- the device's size (units/format TBD)
+ major -- the device major
+ minor -- the device minor
+ sysfsPath -- sysfs device path
+ format -- a DeviceFormat instance
+ parents -- a list of required Device instances
+ serial -- the ID_SERIAL_SHORT for this device
+ vendor -- the manufacturer of this Device
+ model -- manufacturer's device model string
+ bus -- the interconnect this device uses
+
+ """
+ # allow specification of individual parents
+ if isinstance(parents, Device):
+ parents = [parents]
+
+ self.exists = exists
+ Device.__init__(self, device, parents=parents)
+
+ self.uuid = None
+ self._format = None
+ self._size = numeric_type(size)
+ self.major = numeric_type(major)
+ self.minor = numeric_type(minor)
+ self.sysfsPath = sysfsPath
+ self._serial = serial
+ self._vendor = vendor
+ self._model = model
+ self.bus = bus
+
+ self.protected = False
+
+ self.format = format
+ self.originalFormat = self.format
+ self.fstabComment = ""
+ self._targetSize = self._size
+
+ self._partedDevice = None
+
+ @property
+ def packages(self):
+ """ List of packages required to manage devices of this type.
+
+ This list includes the packages required by this device's
+ format type as well those required by all of its parent
+ devices.
+ """
+ packages = super(StorageDevice, self).packages
+ packages.extend(self.format.packages)
+ for parent in self.parents:
+ for package in parent.format.packages:
+ if package not in packages:
+ packages.append(package)
+
+ return packages
+
+ @property
+ def partedDevice(self):
+ if self.exists and self.status and not self._partedDevice:
+ log.debug("looking up parted Device: %s" % self.path)
+
+ # We aren't guaranteed to be able to get a device. In
+ # particular, built-in USB flash readers show up as devices but
+ # do not always have any media present, so parted won't be able
+ # to find a device.
+ try:
+ self._partedDevice = parted.Device(path=self.path)
+ except (_ped.IOException, _ped.DeviceException):
+ pass
+
+ return self._partedDevice
+
+ def _getTargetSize(self):
+ return self._targetSize
+
+ def _setTargetSize(self, newsize):
+ self._targetSize = newsize
+
+ targetSize = property(lambda s: s._getTargetSize(),
+ lambda s, v: s._setTargetSize(v),
+ doc="Target size of this device")
+
+ def __str__(self):
+ s = Device.__str__(self)
+ s += (" uuid = %(uuid)s format = %(format)r size = %(size)s\n"
+ " major = %(major)s minor = %(minor)r exists = %(exists)s\n"
+ " sysfs path = %(sysfs)s partedDevice = %(partedDevice)r\n"
+ " target size = %(targetSize)s path = %(path)s\n"
+ " format args = %(formatArgs)s originalFormat = %(origFmt)s" %
+ {"uuid": self.uuid, "format": self.format, "size": self.size,
+ "major": self.major, "minor": self.minor, "exists": self.exists,
+ "sysfs": self.sysfsPath, "partedDevice": self.partedDevice,
+ "targetSize": self.targetSize, "path": self.path,
+ "formatArgs": self.formatArgs, "origFmt": self.originalFormat})
+ return s
+
+ @property
+ def dict(self):
+ d = super(StorageDevice, self).dict
+ d.update({"uuid": self.uuid, "size": self.size,
+ "format": self.format.dict, "removable": self.removable,
+ "major": self.major, "minor": self.minor,
+ "exists": self.exists, "sysfs": self.sysfsPath,
+ "targetSize": self.targetSize, "path": self.path})
+ return d
+
+ @property
+ def path(self):
+ """ Device node representing this device. """
+ return "%s/%s" % (self._devDir, self.name)
+
+ def updateSysfsPath(self):
+ """ Update this device's sysfs path. """
+ log_method_call(self, self.name, status=self.status)
+ sysfsName = self.name.replace("/", "!")
+ path = os.path.join("/sys", self.sysfsBlockDir, sysfsName)
+ self.sysfsPath = os.path.realpath(path)[4:]
+ log.debug("%s sysfsPath set to %s" % (self.name, self.sysfsPath))
+
+ @property
+ def formatArgs(self):
+ """ Device-specific arguments to format creation program. """
+ return []
+
+ @property
+ def resizable(self):
+ """ Can this type of device be resized? """
+ return self._resizable and self.exists and \
+ ((self.format and self.format.resizable) or not self.format)
+
+ def notifyKernel(self):
+ """ Send a 'change' uevent to the kernel for this device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ log.debug("not sending change uevent for non-existent device")
+ return
+
+ if not self.status:
+ log.debug("not sending change uevent for inactive device")
+ return
+
+ path = os.path.normpath("/sys/%s" % self.sysfsPath)
+ try:
+ notify_kernel(path, action="change")
+ except Exception, e:
+ log.warning("failed to notify kernel of change: %s" % e)
+
+ @property
+ def fstabSpec(self):
+ spec = self.path
+ if self.format and self.format.uuid:
+ spec = "UUID=%s" % self.format.uuid
+ return spec
+
+ def resize(self, intf=None):
+ """ Resize the device.
+
+ New size should already be set.
+ """
+ raise NotImplementedError("resize method not defined for StorageDevice")
+
+ def setup(self, intf=None, orig=False):
+ """ Open, or set up, a device. """
+ log_method_call(self, self.name, orig=orig, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ self.setupParents(orig=orig)
+ for parent in self.parents:
+ if orig:
+ parent.originalFormat.setup()
+ else:
+ parent.format.setup()
+
+ def teardown(self, recursive=None):
+ """ Close, or tear down, a device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists and not recursive:
+ raise DeviceError("device has not been created", self.name)
+
+ if self.status:
+ if self.originalFormat.exists:
+ self.originalFormat.teardown()
+ if self.format.exists:
+ self.format.teardown()
+ udev_settle()
+
+ if recursive:
+ self.teardownParents(recursive=recursive)
+
+ def _getSize(self):
+ """ Get the device's size in MB, accounting for pending changes. """
+ if self.exists and not self.mediaPresent:
+ return 0
+
+ if self.exists and self.partedDevice:
+ self._size = self.currentSize
+
+ size = self._size
+ if self.exists and self.resizable and self.targetSize != size:
+ size = self.targetSize
+
+ return size
+
+ def _setSize(self, newsize):
+ """ Set the device's size to a new value. """
+ if newsize > self.maxSize:
+ raise DeviceError("device cannot be larger than %s MB" %
+ (self.maxSize(),), self.name)
+ self._size = newsize
+
+ size = property(lambda x: x._getSize(),
+ lambda x, y: x._setSize(y),
+ doc="The device's size in MB, accounting for pending changes")
+
+ @property
+ def currentSize(self):
+ """ The device's actual size. """
+ size = 0
+ if self.exists and self.partedDevice:
+ size = self.partedDevice.getSize()
+ elif self.exists:
+ size = self._size
+ return size
+
+ @property
+ def minSize(self):
+ """ The minimum size this device can be. """
+ if self.format.minSize:
+ return self.format.minSize
+ else:
+ return self.size
+
+ @property
+ def maxSize(self):
+ """ The maximum size this device can be. """
+ if self.format.maxSize > self.currentSize:
+ return self.currentSize
+ else:
+ return self.format.maxSize
+
+ @property
+ def status(self):
+ """ This device's status.
+
+ For now, this should return a boolean:
+ True the device is open and ready for use
+ False the device is not open
+ """
+ if not self.exists:
+ return False
+ return os.access(self.path, os.W_OK)
+
+ def _setFormat(self, format):
+ """ Set the Device's format. """
+ if not format:
+ format = getFormat(None, device=self.path, exists=self.exists)
+ log_method_call(self, self.name, type=format.type,
+ current=getattr(self._format, "type", None))
+ if self._format and self._format.status:
+ # FIXME: self.format.status doesn't mean much
+ raise DeviceError("cannot replace active format", self.name)
+
+ self._format = format
+
+ def _getFormat(self):
+ return self._format
+
+ format = property(lambda d: d._getFormat(),
+ lambda d,f: d._setFormat(f),
+ doc="The device's formatting.")
+
+ def create(self, intf=None):
+ """ Create the device. """
+ log_method_call(self, self.name, status=self.status)
+ if self.exists:
+ raise DeviceError("device has already been created", self.name)
+
+ self.createParents()
+ self.setupParents()
+ self.exists = True
+ self.setup()
+
+ def destroy(self):
+ """ Destroy the device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ if not self.isleaf:
+ raise DeviceError("Cannot destroy non-leaf device", self.name)
+
+ self.exists = False
+ # we already did this in DeviceTree._removeDevice
+ #for parent in self.parents:
+ # parent.removeChild()
+
+ @property
+ def removable(self):
+ devpath = os.path.normpath("/sys/%s" % self.sysfsPath)
+ remfile = os.path.normpath("%s/removable" % devpath)
+ rem_f = None
+ try:
+ rem_f = open(remfile, "r")
+ except IOError as err:
+ if err.errno != 2:
+ raise
+ return False
+ try:
+ return (self.sysfsPath and os.path.exists(devpath) and
+ os.access(remfile, os.R_OK) and
+ rem_f.readline().strip() == "1")
+ finally:
+ rem_f.close()
+
+ @property
+ def isDisk(self):
+ return self._isDisk
+
+ @property
+ def partitionable(self):
+ return self._partitionable
+
+ @property
+ def partitioned(self):
+ return self.format.type == "disklabel" and self.partitionable
+
+ @property
+ def serial(self):
+ return self._serial
+
+ @property
+ def model(self):
+ if not self._model:
+ self._model = getattr(self.partedDevice, "model", "")
+ return self._model
+
+ @property
+ def vendor(self):
+ return self._vendor
+
+class DiskDevice(StorageDevice):
+ """ A disk """
+ _type = "disk"
+ _partitionable = True
+ _isDisk = True
+
+ def __init__(self, device, format=None,
+ size=None, major=None, minor=None, sysfsPath='',
+ parents=None, serial=None, vendor="", model="", bus="",
+ exists=True):
+ """ Create a DiskDevice instance.
+
+ Arguments:
+
+ device -- the device name (generally a device node's basename)
+
+ Keyword Arguments:
+
+ size -- the device's size (units/format TBD)
+ major -- the device major
+ minor -- the device minor
+ sysfsPath -- sysfs device path
+ format -- a DeviceFormat instance
+ parents -- a list of required Device instances
+ removable -- whether or not this is a removable device
+ serial -- the ID_SERIAL_SHORT for this device
+ vendor -- the manufacturer of this Device
+ model -- manufacturer's device model string
+ bus -- the interconnect this device uses
+
+
+ DiskDevices always exist.
+ """
+ StorageDevice.__init__(self, device, format=format, size=size,
+ major=major, minor=minor, exists=exists,
+ sysfsPath=sysfsPath, parents=parents,
+ serial=serial, model=model,
+ vendor=vendor, bus=bus)
+
+ def __str__(self):
+ s = StorageDevice.__str__(self)
+ s += (" removable = %(removable)s partedDevice = %(partedDevice)r" %
+ {"removable": self.removable, "partedDevice": self.partedDevice})
+ return s
+
+ @property
+ def mediaPresent(self):
+ if not self.partedDevice:
+ return False
+
+ # Some drivers (cpqarray <blegh>) make block device nodes for
+ # controllers with no disks attached and then report a 0 size,
+ # treat this as no media present
+ return self.partedDevice.getSize() != 0
+
+ @property
+ def description(self):
+ return self.model
+
+ @property
+ def size(self):
+ """ The disk's size in MB """
+ return super(DiskDevice, self).size
+ #size = property(StorageDevice._getSize)
+
+ def probe(self):
+ """ Probe for any missing information about this device.
+
+ pyparted should be able to tell us anything we want to know.
+ size, disklabel type, maybe even partition layout
+ """
+ log_method_call(self, self.name, size=self.size, partedDevice=self.partedDevice)
+
+ def destroy(self):
+ """ Destroy the device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.mediaPresent:
+ raise DeviceError("cannot destroy disk with no media", self.name)
+
+ self.teardown()
+
+ def setup(self, intf=None, orig=False):
+ """ Open, or set up, a device. """
+ log_method_call(self, self.name, orig=orig, status=self.status)
+ if not os.path.exists(self.path):
+ raise DeviceError("device does not exist", self.name)
+
+
+class PartitionDevice(StorageDevice):
+ """ A disk partition.
+
+ On types and flags...
+
+ We don't need to deal with numerical partition types at all. The
+ only type we are concerned with is primary/logical/extended. Usage
+ specification is accomplished through the use of flags, which we
+ will set according to the partition's format.
+ """
+ _type = "partition"
+ _resizable = True
+ defaultSize = 500
+
+ def __init__(self, name, format=None,
+ size=None, grow=False, maxsize=None,
+ major=None, minor=None, bootable=None,
+ sysfsPath='', parents=None, exists=None,
+ partType=None, primary=False, weight=0):
+ """ Create a PartitionDevice instance.
+
+ Arguments:
+
+ name -- the device name (generally a device node's basename)
+
+ Keyword Arguments:
+
+ exists -- indicates whether this is an existing device
+ format -- the device's format (DeviceFormat instance)
+
+ For existing partitions:
+
+ parents -- the disk that contains this partition
+ major -- the device major
+ minor -- the device minor
+ sysfsPath -- sysfs device path
+
+ For new partitions:
+
+ partType -- primary,extended,&c (as parted constant)
+ grow -- whether or not to grow the partition
+ maxsize -- max size for growable partitions (in MB)
+ size -- the device's size (in MB)
+ bootable -- whether the partition is bootable
+ parents -- a list of potential containing disks
+ weight -- an initial sorting weight to assign
+ """
+ self.req_disks = []
+ self.req_partType = None
+ self.req_primary = None
+ self.req_grow = None
+ self.req_bootable = None
+ self.req_size = 0
+ self.req_base_size = 0
+ self.req_max_size = 0
+ self.req_base_weight = 0
+
+ self._bootable = False
+
+ StorageDevice.__init__(self, name, format=format, size=size,
+ major=major, minor=minor, exists=exists,
+ sysfsPath=sysfsPath, parents=parents)
+
+ if not exists:
+ # this is a request, not a partition -- it has no parents
+ self.req_disks = self.parents[:]
+ for dev in self.parents:
+ dev.removeChild()
+ self.parents = []
+
+ # FIXME: Validate partType, but only if this is a new partition
+ # Otherwise, overwrite it with the partition's type.
+ self._partType = None
+ self.partedFlags = {}
+ self._partedPartition = None
+ self._origPath = None
+ self._currentSize = 0
+
+ # FIXME: Validate size, but only if this is a new partition.
+ # For existing partitions we will get the size from
+ # parted.
+
+ if self.exists:
+ log.debug("looking up parted Partition: %s" % self.path)
+ self._partedPartition = self.disk.format.partedDisk.getPartitionByPath(self.path)
+ if not self._partedPartition:
+ raise DeviceError("cannot find parted partition instance", self.name)
+
+ self._origPath = self.path
+ # collect information about the partition from parted
+ self.probe()
+ if self.getFlag(parted.PARTITION_PREP):
+ # the only way to identify a PPC PReP Boot partition is to
+ # check the partition type/flags, so do it here.
+ self.format = getFormat("prepboot", device=self.path, exists=True)
+ else:
+ # XXX It might be worthwhile to create a shit-simple
+ # PartitionRequest class and pass one to this constructor
+ # for new partitions.
+ if not self._size:
+ # default size for new partition requests
+ self._size = self.defaultSize
+ self.req_name = name
+ self.req_partType = partType
+ self.req_primary = primary
+ self.req_max_size = numeric_type(maxsize)
+ self.req_grow = grow
+ self.req_bootable = bootable
+
+ # req_size may be manipulated in the course of partitioning
+ self.req_size = self._size
+
+ # req_base_size will always remain constant
+ self.req_base_size = self._size
+
+ self.req_base_weight = weight
+
+ def __str__(self):
+ s = StorageDevice.__str__(self)
+ s += (" grow = %(grow)s max size = %(maxsize)s bootable = %(bootable)s\n"
+ " part type = %(partType)s primary = %(primary)s\n"
+ " partedPartition = %(partedPart)r disk = %(disk)r\n" %
+ {"grow": self.req_grow, "maxsize": self.req_max_size,
+ "bootable": self.bootable, "partType": self.partType,
+ "primary": self.req_primary,
+ "partedPart": self.partedPartition, "disk": self.disk})
+
+ if self.partedPartition:
+ s += (" start = %(start)s end = %(end)s length = %(length)s\n"
+ " flags = %(flags)s" %
+ {"length": self.partedPartition.geometry.length,
+ "start": self.partedPartition.geometry.start,
+ "end": self.partedPartition.geometry.end,
+ "flags": self.partedPartition.getFlagsAsString()})
+
+ return s
+
+ @property
+ def dict(self):
+ d = super(PartitionDevice, self).dict
+ d.update({"type": self.partType})
+ if not self.exists:
+ d.update({"grow": self.req_grow, "maxsize": self.req_max_size,
+ "bootable": self.bootable,
+ "primary": self.req_primary})
+
+ if self.partedPartition:
+ d.update({"length": self.partedPartition.geometry.length,
+ "start": self.partedPartition.geometry.start,
+ "end": self.partedPartition.geometry.end,
+ "flags": self.partedPartition.getFlagsAsString()})
+ return d
+
+ def writeKS(self, f, preexisting=False, noformat=False, s=None):
+ args = []
+
+ if self.isExtended:
+ return
+
+ if self.req_grow:
+ args.append("--grow")
+ if self.req_max_size:
+ args.append("--maxsize=%s" % self.req_max_size)
+ if self.req_primary:
+ args.append("--asprimary")
+ if self.req_size:
+ args.append("--size=%s" % (self.req_size or self.defaultSize))
+ if preexisting:
+ if len(self.req_disks) == 1:
+ args.append("--ondisk=%s" % self.req_disks[0].name)
+ else:
+ args.append("--onpart=%s" % self.name)
+ if noformat:
+ args.append("--noformat")
+
+ f.write("#part ")
+ self.format.writeKS(f)
+ f.write(" %s" % " ".join(args))
+ if s:
+ f.write(" %s" % s)
+
+ def _setTargetSize(self, newsize):
+ if newsize != self.currentSize:
+ # change this partition's geometry in-memory so that other
+ # partitioning operations can complete (e.g., autopart)
+ self._targetSize = newsize
+ disk = self.disk.format.partedDisk
+
+ # resize the partition's geometry in memory
+ (constraint, geometry) = self._computeResize(self.partedPartition)
+ disk.setPartitionGeometry(partition=self.partedPartition,
+ constraint=constraint,
+ start=geometry.start, end=geometry.end)
+
+ @property
+ def path(self):
+ """ Device node representing this device. """
+ if not self.parents:
+ # Bogus, but code in various places compares devices by path
+ # So we must return something unique
+ return self.name
+
+ return "%s/%s" % (self.parents[0]._devDir, self.name)
+
+ @property
+ def partType(self):
+ """ Get the partition's type (as parted constant). """
+ try:
+ ptype = self.partedPartition.type
+ except AttributeError:
+ ptype = self._partType
+
+ if not self.exists and ptype is None:
+ ptype = self.req_partType
+
+ return ptype
+
+ @property
+ def isExtended(self):
+ return (self.partType is not None and
+ self.partType & parted.PARTITION_EXTENDED)
+
+ @property
+ def isLogical(self):
+ return (self.partType is not None and
+ self.partType & parted.PARTITION_LOGICAL)
+
+ @property
+ def isPrimary(self):
+ return (self.partType is not None and
+ self.partType == parted.PARTITION_NORMAL)
+
+ @property
+ def isProtected(self):
+ return (self.partType is not None and
+ self.partType & parted.PARTITION_PROTECTED)
+
+ @property
+ def fstabSpec(self):
+ spec = self.path
+ if self.disk and self.disk.type == 'dasd':
+ spec = deviceNameToDiskByPath(self.path)
+ elif self.format and self.format.uuid:
+ spec = "UUID=%s" % self.format.uuid
+ return spec
+
+ def _getPartedPartition(self):
+ return self._partedPartition
+
+ def _setPartedPartition(self, partition):
+ """ Set this PartitionDevice's parted Partition instance. """
+ log_method_call(self, self.name)
+ if partition is None:
+ path = None
+ elif isinstance(partition, parted.Partition):
+ path = partition.path
+ else:
+ raise ValueError("partition must be a parted.Partition instance")
+
+ log.debug("device %s new partedPartition %s has path %s" % (self.name,
+ partition,
+ path))
+ self._partedPartition = partition
+ self.updateName()
+
+ partedPartition = property(lambda d: d._getPartedPartition(),
+ lambda d,p: d._setPartedPartition(p))
+
+ def resetPartedPartition(self):
+ """ Re-get self.partedPartition from the original disklabel. """
+ log_method_call(self, self.name)
+ if not self.exists:
+ return
+
+ # find the correct partition on the original parted.Disk since the
+ # name/number we're now using may no longer match
+ _disklabel = self.disk.originalFormat
+
+ if self.isExtended:
+ # getPartitionBySector doesn't work on extended partitions
+ _partition = _disklabel.extendedPartition
+ log.debug("extended lookup found partition %s"
+ % devicePathToName(getattr(_partition, "path", None)))
+ else:
+ # lookup the partition by sector to avoid the renumbering
+ # nonsense entirely
+ _sector = self.partedPartition.geometry.start
+ _partition = _disklabel.partedDisk.getPartitionBySector(_sector)
+ log.debug("sector-based lookup found partition %s"
+ % devicePathToName(getattr(_partition, "path", None)))
+
+ self.partedPartition = _partition
+
+ def _getWeight(self):
+ return self.req_base_weight
+
+ def _setWeight(self, weight):
+ self.req_base_weight = weight
+
+ weight = property(lambda d: d._getWeight(),
+ lambda d,w: d._setWeight(w))
+
+ def updateSysfsPath(self):
+ """ Update this device's sysfs path. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.parents:
+ self.sysfsPath = ''
+
+ elif self.parents[0]._devDir == "/dev/mapper":
+ dm_node = dm.dm_node_from_name(self.name)
+ path = os.path.join("/sys", self.sysfsBlockDir, dm_node)
+ self.sysfsPath = os.path.realpath(path)[4:]
+
+ else:
+ StorageDevice.updateSysfsPath(self)
+
+ def updateName(self):
+ if self.partedPartition is None:
+ self._name = self.req_name
+ else:
+ self._name = \
+ devicePathToName(self.partedPartition.getDeviceNodeName())
+
+ def dependsOn(self, dep):
+ """ Return True if this device depends on dep. """
+ if isinstance(dep, PartitionDevice) and dep.isExtended and \
+ self.isLogical and self.disk == dep.disk:
+ return True
+
+ return Device.dependsOn(self, dep)
+
+ def _setFormat(self, format):
+ """ Set the Device's format. """
+ log_method_call(self, self.name)
+ StorageDevice._setFormat(self, format)
+
+ def _setBootable(self, bootable):
+ """ Set the bootable flag for this partition. """
+ if self.partedPartition:
+ if iutil.isS390():
+ return
+ if self.flagAvailable(parted.PARTITION_BOOT):
+ if bootable:
+ self.setFlag(parted.PARTITION_BOOT)
+ else:
+ self.unsetFlag(parted.PARTITION_BOOT)
+ else:
+ raise DeviceError("boot flag not available for this partition", self.name)
+
+ self._bootable = bootable
+ else:
+ self.req_bootable = bootable
+
+ def _getBootable(self):
+ return self._bootable or self.req_bootable
+
+ bootable = property(_getBootable, _setBootable)
+
+ def flagAvailable(self, flag):
+ log_method_call(self, path=self.path, flag=flag)
+ if not self.partedPartition:
+ return
+
+ return self.partedPartition.isFlagAvailable(flag)
+
+ def getFlag(self, flag):
+ log_method_call(self, path=self.path, flag=flag)
+ if not self.partedPartition or not self.flagAvailable(flag):
+ return
+
+ return self.partedPartition.getFlag(flag)
+
+ def setFlag(self, flag):
+ log_method_call(self, path=self.path, flag=flag)
+ if not self.partedPartition or not self.flagAvailable(flag):
+ return
+
+ self.partedPartition.setFlag(flag)
+
+ def unsetFlag(self, flag):
+ log_method_call(self, path=self.path, flag=flag)
+ if not self.partedPartition or not self.flagAvailable(flag):
+ return
+
+ self.partedPartition.unsetFlag(flag)
+
+ def probe(self):
+ """ Probe for any missing information about this device.
+
+ size, partition type, flags
+ """
+ log_method_call(self, self.name, exists=self.exists)
+ if not self.exists:
+ return
+
+ # this is in MB
+ self._size = self.partedPartition.getSize()
+ self._currentSize = self._size
+ self.targetSize = self._size
+
+ self._partType = self.partedPartition.type
+
+ self._bootable = self.getFlag(parted.PARTITION_BOOT)
+
+ def create(self, intf=None):
+ """ Create the device. """
+ log_method_call(self, self.name, status=self.status)
+ if self.exists:
+ raise DeviceError("device already exists", self.name)
+
+ w = None
+ if intf:
+ w = intf.waitWindow(_("Creating"),
+ _("Creating device %s") % (self.path,))
+
+ try:
+ self.createParents()
+ self.setupParents()
+
+ self.disk.format.addPartition(self.partedPartition)
+
+ try:
+ self.disk.format.commit()
+ except DiskLabelCommitError:
+ part = self.disk.format.partedDisk.getPartitionByPath(self.path)
+ self.disk.format.removePartition(part)
+ raise
+
+ # Ensure old metadata which lived in freespace so did not get
+ # explictly destroyed by a destroyformat action gets wiped
+ DeviceFormat(device=self.path, exists=True).destroy()
+ except Exception:
+ raise
+ else:
+ self.partedPartition = self.disk.format.partedDisk.getPartitionByPath(self.path)
+
+ self.exists = True
+ self._currentSize = self.partedPartition.getSize()
+ self.setup()
+ finally:
+ if w:
+ w.pop()
+
+ def _computeResize(self, partition):
+ log_method_call(self, self.name, status=self.status)
+
+ # compute new size for partition
+ currentGeom = partition.geometry
+ currentDev = currentGeom.device
+ newLen = long(self.targetSize * 1024 * 1024) / currentDev.sectorSize
+ newGeometry = parted.Geometry(device=currentDev,
+ start=currentGeom.start,
+ length=newLen)
+ # and align the end sector
+ newGeometry.end = self.disk.format.endAlignment.alignDown(newGeometry,
+ newGeometry.end)
+ constraint = parted.Constraint(exactGeom=newGeometry)
+
+ return (constraint, newGeometry)
+
+ def resize(self, intf=None):
+ """ Resize the device.
+
+ self.targetSize must be set to the new size.
+ """
+ log_method_call(self, self.name, status=self.status)
+
+ if self.targetSize != self.currentSize:
+ # partedDisk has been restored to _origPartedDisk, so
+ # recalculate resize geometry because we may have new
+ # partitions on the disk, which could change constraints
+ partedDisk = self.disk.format.partedDisk
+ partition = partedDisk.getPartitionByPath(self.path)
+ (constraint, geometry) = self._computeResize(partition)
+
+ partedDisk.setPartitionGeometry(partition=partition,
+ constraint=constraint,
+ start=geometry.start,
+ end=geometry.end)
+
+ self.disk.format.commit()
+ self._currentSize = partition.getSize()
+
+ def destroy(self):
+ """ Destroy the device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ if not self.sysfsPath:
+ return
+
+ if not self.isleaf:
+ raise DeviceError("Cannot destroy non-leaf device", self.name)
+
+ self.setupParents(orig=True)
+ # we should have already set self.partedPartition to point to the
+ # partition on the original disklabel
+ self.disk.originalFormat.removePartition(self.partedPartition)
+ try:
+ self.disk.originalFormat.commit()
+ except DiskLabelCommitError:
+ self.disk.originalFormat.addPartition(self.partedPartition)
+ self.partedPartition = self.disk.originalFormat.partedDisk.getPartitionByPath(self.path)
+ raise
+
+ self.exists = False
+
+ def teardown(self, recursive=None):
+ """ Close, or tear down, a device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists and not recursive:
+ raise DeviceError("device has not been created", self.name)
+
+ if self.status:
+ if self.originalFormat.exists:
+ self.originalFormat.teardown()
+ if self.format.exists:
+ self.format.teardown()
+ if self.parents[0].type == 'dm-multipath':
+ devmap = block.getMap(major=self.major, minor=self.minor)
+ if devmap:
+ try:
+ block.removeDeviceMap(devmap)
+ except Exception as e:
+ raise DeviceTeardownError("failed to tear down device-mapper partition %s: %s" % (self.name, e))
+ udev_settle()
+
+ StorageDevice.teardown(self, recursive=recursive)
+
+ def _getSize(self):
+ """ Get the device's size. """
+ size = self._size
+ if self.partedPartition:
+ # this defaults to MB
+ size = self.partedPartition.getSize()
+ return size
+
+ def _setSize(self, newsize):
+ """ Set the device's size (for resize, not creation).
+
+ Arguments:
+
+ newsize -- the new size (in MB)
+
+ """
+ log_method_call(self, self.name,
+ status=self.status, size=self._size, newsize=newsize)
+ if not self.exists:
+ raise DeviceError("device does not exist", self.name)
+
+ if newsize > self.disk.size:
+ raise ValueError("partition size would exceed disk size")
+
+ # this defaults to MB
+ maxAvailableSize = self.partedPartition.getMaxAvailableSize()
+
+ if newsize > maxAvailableSize:
+ raise ValueError("new size is greater than available space")
+
+ # now convert the size to sectors and update the geometry
+ geometry = self.partedPartition.geometry
+ physicalSectorSize = geometry.device.physicalSectorSize
+
+ new_length = (newsize * (1024 * 1024)) / physicalSectorSize
+ geometry.length = new_length
+
+ def _getDisk(self):
+ """ The disk that contains this partition."""
+ try:
+ disk = self.parents[0]
+ except IndexError:
+ disk = None
+ return disk
+
+ def _setDisk(self, disk):
+ """Change the parent.
+
+ Setting up a disk is not trivial. It has the potential to change
+ the underlying object. If necessary we must also change this object.
+ """
+ log_method_call(self, self.name, old=getattr(self.disk, "name", None),
+ new=getattr(disk, "name", None))
+ if self.disk:
+ self.disk.removeChild()
+
+ if disk:
+ self.parents = [disk]
+ disk.addChild()
+ else:
+ self.parents = []
+
+ disk = property(lambda p: p._getDisk(), lambda p,d: p._setDisk(d))
+
+ @property
+ def maxSize(self):
+ """ The maximum size this partition can be. """
+ # XXX: this is MB by default
+ maxPartSize = self.partedPartition.getMaxAvailableSize()
+
+ if self.format.maxSize > maxPartSize:
+ return maxPartSize
+ else:
+ return self.format.maxSize
+
+ @property
+ def currentSize(self):
+ """ The device's actual size. """
+ if self.exists:
+ return self._currentSize
+ else:
+ return 0
+
+
+class DMDevice(StorageDevice):
+ """ A device-mapper device """
+ _type = "dm"
+ _devDir = "/dev/mapper"
+
+ def __init__(self, name, format=None, size=None, dmUuid=None,
+ target=None, exists=None, parents=None, sysfsPath=''):
+ """ Create a DMDevice instance.
+
+ Arguments:
+
+ name -- the device name (generally a device node's basename)
+
+ Keyword Arguments:
+
+ target -- the device-mapper target type (string)
+ size -- the device's size (units/format TBD)
+ dmUuid -- the device's device-mapper UUID
+ sysfsPath -- sysfs device path
+ format -- a DeviceFormat instance
+ parents -- a list of required Device instances
+ exists -- indicates whether this is an existing device
+ """
+ StorageDevice.__init__(self, name, format=format, size=size,
+ exists=exists,
+ parents=parents, sysfsPath=sysfsPath)
+ self.target = target
+ self.dmUuid = dmUuid
+
+ def __str__(self):
+ s = StorageDevice.__str__(self)
+ s += (" target = %(target)s dmUuid = %(dmUuid)s" %
+ {"target": self.target, "dmUuid": self.dmUuid})
+ return s
+
+ @property
+ def dict(self):
+ d = super(DMDevice, self).dict
+ d.update({"target": self.target, "dmUuid": self.dmUuid})
+ return d
+
+ @property
+ def fstabSpec(self):
+ """ Return the device specifier for use in /etc/fstab. """
+ return self.path
+
+ @property
+ def mapName(self):
+ """ This device's device-mapper map name """
+ return self.name
+
+ @property
+ def status(self):
+ _status = False
+ for map in block.dm.maps():
+ if map.name == self.mapName:
+ _status = map.live_table and not map.suspended
+ break
+
+ return _status
+
+ def updateSysfsPath(self):
+ """ Update this device's sysfs path. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ if self.status:
+ dm_node = self.getDMNode()
+ path = os.path.join("/sys", self.sysfsBlockDir, dm_node)
+ self.sysfsPath = os.path.realpath(path)[4:]
+ else:
+ self.sysfsPath = ''
+
+ #def getTargetType(self):
+ # return dm.getDmTarget(name=self.name)
+
+ def getDMNode(self):
+ """ Return the dm-X (eg: dm-0) device node for this device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ return dm.dm_node_from_name(self.name)
+
+ def _setName(self, name):
+ """ Set the device's map name. """
+ log_method_call(self, self.name, status=self.status)
+ if self.status:
+ raise DeviceError("cannot rename active device", self.name)
+
+ self._name = name
+ #self.sysfsPath = "/dev/disk/by-id/dm-name-%s" % self.name
+
+ name = property(lambda d: d._name,
+ lambda d,n: d._setName(n))
+
+
+class DMCryptDevice(DMDevice):
+ """ A dm-crypt device """
+ _type = "dm-crypt"
+
+ def __init__(self, name, format=None, size=None, uuid=None,
+ exists=None, sysfsPath='', parents=None):
+ """ Create a DMCryptDevice instance.
+
+ Arguments:
+
+ name -- the device name (generally a device node's basename)
+
+ Keyword Arguments:
+
+ size -- the device's size (units/format TBD)
+ sysfsPath -- sysfs device path
+ format -- a DeviceFormat instance
+ parents -- a list of required Device instances
+ exists -- indicates whether this is an existing device
+ """
+ DMDevice.__init__(self, name, format=format, size=size,
+ parents=parents, sysfsPath=sysfsPath,
+ exists=exists, target="crypt")
+
+class LUKSDevice(DMCryptDevice):
+ """ A mapped LUKS device. """
+ _type = "luks/dm-crypt"
+
+ def __init__(self, name, format=None, size=None, uuid=None,
+ exists=None, sysfsPath='', parents=None):
+ """ Create a LUKSDevice instance.
+
+ Arguments:
+
+ name -- the device name
+
+ Keyword Arguments:
+
+ size -- the device's size in MB
+ uuid -- the device's UUID
+ sysfsPath -- sysfs device path
+ format -- a DeviceFormat instance
+ parents -- a list of required Device instances
+ exists -- indicates whether this is an existing device
+ """
+ DMCryptDevice.__init__(self, name, format=format, size=size,
+ parents=parents, sysfsPath=sysfsPath,
+ uuid=None, exists=exists)
+
+ def writeKS(self, f, preexisting=False, noformat=False, s=None):
+ self.slave.writeKS(f, preexisting=preexisting, noformat=noformat, s=s)
+ self.format.writeKS(f)
+ if s:
+ f.write(" %s" % s)
+
+ @property
+ def size(self):
+ if not self.exists or not self.partedDevice:
+ # the LUKS header takes up 4040 512-byte sectors w/ a 512-bit key
+ size = float(self.slave.size) - ((4040 * 2.0) / 1024)
+ else:
+ size = self.partedDevice.getSize()
+ return size
+
+ def create(self, intf=None):
+ """ Create the device. """
+ log_method_call(self, self.name, status=self.status)
+ if self.exists:
+ raise DeviceError("device already exists", self.name)
+
+ self.createParents()
+ self.setupParents()
+
+ #if not self.slave.format.exists:
+ # self.slave.format.create()
+ self._name = self.slave.format.mapName
+ self.exists = True
+ self.setup()
+
+ def setup(self, intf=None, orig=False):
+ """ Open, or set up, a device. """
+ log_method_call(self, self.name, orig=orig, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ self.slave.setup(orig=orig)
+ if orig:
+ self.slave.originalFormat.setup()
+ else:
+ self.slave.format.setup()
+ udev_settle()
+
+ # we always probe since the device may not be set up when we want
+ # information about it
+ self._size = self.currentSize
+
+ def teardown(self, recursive=False):
+ """ Close, or tear down, a device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists and not recursive:
+ raise DeviceError("device has not been created", self.name)
+
+ if self.status:
+ if self.originalFormat.exists:
+ self.originalFormat.teardown()
+ if self.format.exists:
+ self.format.teardown()
+ udev_settle()
+
+ if self.slave.originalFormat.exists:
+ self.slave.originalFormat.teardown()
+ udev_settle()
+
+ if self.slave.format.exists:
+ self.slave.format.teardown()
+ udev_settle()
+
+ if recursive:
+ self.teardownParents(recursive=recursive)
+
+ def destroy(self):
+ log_method_call(self, self.name, status=self.status)
+ self.format.teardown()
+ udev_settle()
+ self.teardown()
+
+ @property
+ def slave(self):
+ """ This device's backing device. """
+ return self.parents[0]
+
+ def dracutSetupString(self):
+ return "rd_LUKS_UUID=luks-%s" % self.slave.format.uuid
+
+
+class LVMVolumeGroupDevice(DMDevice):
+ """ An LVM Volume Group
+
+ XXX Maybe this should inherit from StorageDevice instead of
+ DMDevice since there's no actual device.
+ """
+ _type = "lvmvg"
+
+ def __init__(self, name, parents, size=None, free=None,
+ peSize=None, peCount=None, peFree=None, pvCount=None,
+ lvNames=[], uuid=None, exists=None, sysfsPath=''):
+ """ Create a LVMVolumeGroupDevice instance.
+
+ Arguments:
+
+ name -- the device name (generally a device node's basename)
+ parents -- a list of physical volumes (StorageDevice)
+
+ Keyword Arguments:
+
+ peSize -- physical extent size (in MB)
+ exists -- indicates whether this is an existing device
+ sysfsPath -- sysfs device path
+
+ For existing VG's only:
+
+ size -- the VG's size (in MB)
+ free -- amount of free space in the VG
+ peFree -- number of free extents
+ peCount -- total number of extents
+ pvCount -- number of PVs in this VG
+ lvNames -- the names of this VG's LVs
+ uuid -- the VG's UUID
+
+ """
+ self.pvClass = get_device_format_class("lvmpv")
+ if not self.pvClass:
+ raise StorageError("cannot find 'lvmpv' class")
+
+ if isinstance(parents, list):
+ for dev in parents:
+ if not isinstance(dev.format, self.pvClass):
+ raise ValueError("constructor requires a list of PVs")
+ elif not isinstance(parents.format, self.pvClass):
+ raise ValueError("constructor requires a list of PVs")
+
+ DMDevice.__init__(self, name, parents=parents,
+ exists=exists, sysfsPath=sysfsPath)
+
+ self.uuid = uuid
+ self.free = numeric_type(free)
+ self.peSize = numeric_type(peSize)
+ self.peCount = numeric_type(peCount)
+ self.peFree = numeric_type(peFree)
+ self.pvCount = numeric_type(pvCount)
+ self.lvNames = lvNames
+
+ # circular references, here I come
+ self._lvs = []
+
+ # TODO: validate peSize if given
+ if not self.peSize:
+ self.peSize = 32.0 # MB
+
+ #self.probe()
+
+ def __str__(self):
+ s = DMDevice.__str__(self)
+ s += (" free = %(free)s PE Size = %(peSize)s PE Count = %(peCount)s\n"
+ " PE Free = %(peFree)s PV Count = %(pvCount)s\n"
+ " LV Names = %(lvNames)s modified = %(modified)s\n"
+ " extents = %(extents)s free space = %(freeSpace)s\n"
+ " free extents = %(freeExtents)s\n"
+ " PVs = %(pvs)s\n"
+ " LVs = %(lvs)s" %
+ {"free": self.free, "peSize": self.peSize, "peCount": self.peCount,
+ "peFree": self.peFree, "pvCount": self.pvCount,
+ "lvNames": self.lvNames, "modified": self.isModified,
+ "extents": self.extents, "freeSpace": self.freeSpace,
+ "freeExtents": self.freeExtents, "pvs": self.pvs, "lvs": self.lvs})
+ return s
+
+ @property
+ def dict(self):
+ d = super(LVMVolumeGroupDevice, self).dict
+ d.update({"free": self.free, "peSize": self.peSize,
+ "peCount": self.peCount, "peFree": self.peFree,
+ "pvCount": self.pvCount, "extents": self.extents,
+ "freeSpace": self.freeSpace,
+ "freeExtents": self.freeExtents,
+ "lvNames": [lv.name for lv in self.lvs]})
+ return d
+
+ def writeKS(self, f, preexisting=False, noformat=False, s=None):
+ args = ["--pesize=%s" % int(self.peSize * 1024)]
+ pvs = []
+
+ for pv in self.pvs:
+ pvs.append("pv.%s" % pv.format.uuid)
+
+ if preexisting:
+ args.append("--useexisting")
+ if noformat:
+ args.append("--noformat")
+
+ f.write("#volgroup %s %s %s" % (self.name, " ".join(args), " ".join(pvs)))
+ if s:
+ f.write(" %s" % s)
+
+ def probe(self):
+ """ Probe for any information about this device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ @property
+ def mapName(self):
+ """ This device's device-mapper map name """
+ # Thank you lvm for this lovely hack.
+ return self.name.replace("-","--")
+
+ @property
+ def path(self):
+ """ Device node representing this device. """
+ return "%s/%s" % (self._devDir, self.mapName)
+
+ def updateSysfsPath(self):
+ """ Update this device's sysfs path. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ self.sysfsPath = ''
+
+ @property
+ def status(self):
+ """ The device's status (True means active). """
+ if not self.exists:
+ return False
+
+ # certainly if any of this VG's LVs are active then so are we
+ for lv in self.lvs:
+ if lv.status:
+ return True
+
+ # if any of our PVs are not active then we cannot be
+ for pv in self.pvs:
+ if not pv.status:
+ return False
+
+ # if we are missing some of our PVs we cannot be active
+ if len(self.pvs) != self.pvCount:
+ return False
+
+ return True
+
+ def _addDevice(self, device):
+ """ Add a new physical volume device to the volume group.
+
+ XXX This is for use by device probing routines and is not
+ intended for modification of the VG.
+ """
+ log_method_call(self,
+ self.name,
+ device=device.name,
+ status=self.status)
+ if not self.exists:
+ raise DeviceError("device does not exist", self.name)
+
+ if not isinstance(device.format, self.pvClass):
+ raise ValueError("addDevice requires a PV arg")
+
+ if self.uuid and device.format.vgUuid != self.uuid:
+ raise ValueError("UUID mismatch")
+
+ if device in self.pvs:
+ raise ValueError("device is already a member of this VG")
+
+ self.parents.append(device)
+ device.addChild()
+
+ # now see if the VG can be activated
+ if len(self.parents) == self.pvCount:
+ self.setup()
+
+ def _removeDevice(self, device):
+ """ Remove a physical volume from the volume group.
+
+ This is for cases like clearing of preexisting partitions.
+ """
+ log_method_call(self,
+ self.name,
+ device=device.name,
+ status=self.status)
+ try:
+ self.parents.remove(device)
+ except ValueError, e:
+ raise ValueError("cannot remove non-member PV device from VG")
+
+ device.removeChild()
+
+ def setup(self, intf=None, orig=False):
+ """ Open, or set up, a device.
+
+ XXX we don't do anything like "vgchange -ay" because we don't
+ want all of the LVs activated, just the VG itself.
+ """
+ log_method_call(self, self.name, orig=orig, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ if self.status:
+ return
+
+ if len(self.parents) < self.pvCount:
+ raise DeviceError("cannot activate VG with missing PV(s)", self.name)
+
+ self.setupParents(orig=orig)
+
+ def teardown(self, recursive=None):
+ """ Close, or tear down, a device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists and not recursive:
+ raise DeviceError("device has not been created", self.name)
+
+ if self.status:
+ lvm.vgdeactivate(self.name)
+
+ if recursive:
+ self.teardownParents(recursive=recursive)
+
+ def create(self, intf=None):
+ """ Create the device. """
+ log_method_call(self, self.name, status=self.status)
+ if self.exists:
+ raise DeviceError("device already exists", self.name)
+
+ w = None
+ if intf:
+ w = intf.progressWindow(_("Creating"),
+ _("Creating device %s")
+ % (self.path,),
+ 100, pulse = True)
+ try:
+ self.createParents()
+ self.setupParents()
+
+ pv_list = [pv.path for pv in self.parents]
+ lvm.vgcreate(self.name, pv_list, self.peSize, progress=w)
+ except Exception:
+ raise
+ else:
+ # FIXME set / update self.uuid here
+ self.exists = True
+ self.setup()
+ finally:
+ if w:
+ w.pop()
+
+ def destroy(self):
+ """ Destroy the device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ # set up the pvs since lvm needs access to them to do the vgremove
+ self.setupParents(orig=True)
+
+ # this sometimes fails for some reason.
+ try:
+ lvm.vgreduce(self.name, [], rm=True)
+ lvm.vgremove(self.name)
+ except lvm.LVMError:
+ raise DeviceError("Could not completely remove VG", self.name)
+ finally:
+ self.exists = False
+
+ def reduce(self, pv_list):
+ """ Remove the listed PVs from the VG. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ lvm.vgreduce(self.name, pv_list)
+ # XXX do we need to notify the kernel?
+
+ def _addLogVol(self, lv):
+ """ Add an LV to this VG. """
+ if lv in self._lvs:
+ raise ValueError("lv is already part of this vg")
+
+ # verify we have the space, then add it
+ # do not verify for growing vg (because of ks)
+ if not lv.exists and \
+ not [pv for pv in self.pvs if getattr(pv, "req_grow", None)] and \
+ lv.size > self.freeSpace:
+ raise DeviceError("new lv is too large to fit in free space", self.name)
+
+ self._lvs.append(lv)
+
+ def _removeLogVol(self, lv):
+ """ Remove an LV from this VG. """
+ if lv not in self.lvs:
+ raise ValueError("specified lv is not part of this vg")
+
+ self._lvs.remove(lv)
+
+ def _addPV(self, pv):
+ """ Add a PV to this VG. """
+ if pv in self.pvs:
+ raise ValueError("pv is already part of this vg")
+
+ # for the time being we will not allow vgextend
+ if self.exists:
+ raise DeviceError("cannot add pv to existing vg", self.name)
+
+ self.parents.append(pv)
+ pv.addChild()
+
+ def _removePV(self, pv):
+ """ Remove an PV from this VG. """
+ if not pv in self.pvs:
+ raise ValueError("specified pv is not part of this vg")
+
+ # for the time being we will not allow vgreduce
+ if self.exists:
+ raise DeviceError("cannot remove pv from existing vg", self.name)
+
+ self.parents.remove(pv)
+ pv.removeChild()
+
+ # We can't rely on lvm to tell us about our size, free space, &c
+ # since we could have modifications queued, unless the VG and all of
+ # its PVs already exist.
+ #
+ # -- liblvm may contain support for in-memory devices
+
+ @property
+ def isModified(self):
+ """ Return True if the VG has changes queued that LVM is unaware of. """
+ modified = True
+ if self.exists and not filter(lambda d: not d.exists, self.pvs):
+ modified = False
+
+ return modified
+
+ @property
+ def size(self):
+ """ The size of this VG """
+ # TODO: just ask lvm if isModified returns False
+
+ # sum up the sizes of the PVs and align to pesize
+ size = 0
+ for pv in self.pvs:
+ size += max(0, self.align(pv.size - pv.format.peStart))
+
+ return size
+
+ @property
+ def extents(self):
+ """ Number of extents in this VG """
+ # TODO: just ask lvm if isModified returns False
+
+ return self.size / self.peSize
+
+ @property
+ def freeSpace(self):
+ """ The amount of free space in this VG (in MB). """
+ # TODO: just ask lvm if isModified returns False
+
+ # total the sizes of any LVs
+ used = 0
+ size = self.size
+ log.debug("%s size is %dMB" % (self.name, size))
+ for lv in self.lvs:
+ log.debug("lv %s uses %dMB" % (lv.name, lv.vgSpaceUsed))
+ used += self.align(lv.vgSpaceUsed, roundup=True)
+
+ free = self.size - used
+ log.debug("vg %s has %dMB free" % (self.name, free))
+ return free
+
+ @property
+ def freeExtents(self):
+ """ The number of free extents in this VG. """
+ # TODO: just ask lvm if isModified returns False
+ return self.freeSpace / self.peSize
+
+ def align(self, size, roundup=None):
+ """ Align a size to a multiple of physical extent size. """
+ size = numeric_type(size)
+
+ if roundup:
+ round = math.ceil
+ else:
+ round = math.floor
+
+ # we want Kbytes as a float for our math
+ size *= 1024.0
+ pesize = self.peSize * 1024.0
+ return long((round(size / pesize) * pesize) / 1024)
+
+ @property
+ def pvs(self):
+ """ A list of this VG's PVs """
+ return self.parents[:] # we don't want folks changing our list
+
+ @property
+ def lvs(self):
+ """ A list of this VG's LVs """
+ return self._lvs[:] # we don't want folks changing our list
+
+ @property
+ def complete(self):
+ """Check if the vg has all its pvs in the system
+ Return True if complete.
+ """
+ return len(self.pvs) == self.pvCount or not self.exists
+
+
+class LVMLogicalVolumeDevice(DMDevice):
+ """ An LVM Logical Volume """
+ _type = "lvmlv"
+ _resizable = True
+
+ def __init__(self, name, vgdev, size=None, uuid=None,
+ stripes=1, logSize=0, snapshotSpace=0,
+ format=None, exists=None, sysfsPath='',
+ grow=None, maxsize=None, percent=None):
+ """ Create a LVMLogicalVolumeDevice instance.
+
+ Arguments:
+
+ name -- the device name (generally a device node's basename)
+ vgdev -- volume group (LVMVolumeGroupDevice instance)
+
+ Keyword Arguments:
+
+ size -- the device's size (in MB)
+ uuid -- the device's UUID
+ stripes -- number of copies in the vg (>1 for mirrored lvs)
+ logSize -- size of log volume (for mirrored lvs)
+ snapshotSpace -- sum of sizes of snapshots of this lv
+ sysfsPath -- sysfs device path
+ format -- a DeviceFormat instance
+ exists -- indicates whether this is an existing device
+
+ For new (non-existent) LVs only:
+
+ grow -- whether to grow this LV
+ maxsize -- maximum size for growable LV (in MB)
+ percent -- percent of VG space to take
+
+ """
+ if isinstance(vgdev, list):
+ if len(vgdev) != 1:
+ raise ValueError("constructor requires a single LVMVolumeGroupDevice instance")
+ elif not isinstance(vgdev[0], LVMVolumeGroupDevice):
+ raise ValueError("constructor requires a LVMVolumeGroupDevice instance")
+ elif not isinstance(vgdev, LVMVolumeGroupDevice):
+ raise ValueError("constructor requires a LVMVolumeGroupDevice instance")
+ DMDevice.__init__(self, name, size=size, format=format,
+ sysfsPath=sysfsPath, parents=vgdev,
+ exists=exists)
+
+ self.uuid = uuid
+ self.snapshotSpace = snapshotSpace
+ self.stripes = stripes
+ self.logSize = logSize
+
+ self.req_grow = None
+ self.req_max_size = 0
+ self.req_size = 0
+ self.req_percent = 0
+
+ if not self.exists:
+ self.req_grow = grow
+ self.req_max_size = numeric_type(maxsize)
+ # XXX should we enforce that req_size be pe-aligned?
+ self.req_size = self._size
+ self.req_percent = numeric_type(percent)
+
+ # here we go with the circular references
+ self.vg._addLogVol(self)
+
+ def __str__(self):
+ s = DMDevice.__str__(self)
+ s += (" VG device = %(vgdev)r percent = %(percent)s\n"
+ " mirrored = %(mirrored)s stripes = %(stripes)d"
+ " snapshot total = %(snapshots)dMB\n"
+ " VG space used = %(vgspace)dMB" %
+ {"vgdev": self.vg, "percent": self.req_percent,
+ "mirrored": self.mirrored, "stripes": self.stripes,
+ "snapshots": self.snapshotSpace, "vgspace": self.vgSpaceUsed })
+ return s
+
+ @property
+ def dict(self):
+ d = super(LVMLogicalVolumeDevice, self).dict
+ if self.exists:
+ d.update({"mirrored": self.mirrored, "stripes": self.stripes,
+ "snapshots": self.snapshotSpace,
+ "vgspace": self.vgSpaceUsed})
+ else:
+ d.update({"percent": self.req_percent})
+
+ return d
+
+ def writeKS(self, f, preexisting=False, noformat=False, s=None):
+ args = ["--name=%s" % self.lvname,
+ "--vgname=%s" % self.vg.name]
+
+ if self.req_grow:
+ args.extend(["--grow", "--size=%s" % (self.req_size or 1)])
+
+ if self.req_max_size > 0:
+ args.append("--maxsize=%s" % self.req_max_size)
+ else:
+ if self.req_percent > 0:
+ args.append("--percent=%s" % self.req_percent)
+ elif self.req_size > 0:
+ args.append("--size=%s" % self.req_size)
+
+ if preexisting:
+ args.append("--useexisting")
+ if noformat:
+ args.append("--noformat")
+
+ f.write("#logvol ")
+ self.format.writeKS(f)
+ f.write(" %s" % " ".join(args))
+ if s:
+ f.write(" %s" % s)
+
+ @property
+ def mirrored(self):
+ return self.stripes > 1
+
+ def _setSize(self, size):
+ size = self.vg.align(numeric_type(size))
+ log.debug("trying to set lv %s size to %dMB" % (self.name, size))
+ if size <= (self.vg.freeSpace + self._size):
+ self._size = size
+ self.targetSize = size
+ else:
+ log.debug("failed to set size: %dMB short" % (size - (self.vg.freeSpace + self._size),))
+ raise ValueError("not enough free space in volume group")
+
+ size = property(StorageDevice._getSize, _setSize)
+
+ @property
+ def vgSpaceUsed(self):
+ return self.size * self.stripes + self.logSize + self.snapshotSpace
+
+ @property
+ def vg(self):
+ """ This Logical Volume's Volume Group. """
+ return self.parents[0]
+
+ @property
+ def mapName(self):
+ """ This device's device-mapper map name """
+ # Thank you lvm for this lovely hack.
+ return "%s-%s" % (self.vg.mapName, self._name.replace("-","--"))
+
+ @property
+ def path(self):
+ """ Device node representing this device. """
+ return "%s/%s" % (self._devDir, self.mapName)
+
+ def getDMNode(self):
+ """ Return the dm-X (eg: dm-0) device node for this device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ return dm.dm_node_from_name(self.mapName)
+
+ @property
+ def name(self):
+ """ This device's name. """
+ return "%s-%s" % (self.vg.name, self._name)
+
+ @property
+ def lvname(self):
+ """ The LV's name (not including VG name). """
+ return self._name
+
+ @property
+ def complete(self):
+ """ Test if vg exits and if it has all pvs. """
+ return self.vg.complete
+
+ def setup(self, intf=None, orig=False):
+ """ Open, or set up, a device. """
+ log_method_call(self, self.name, orig=orig, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ if self.status:
+ return
+
+ self.vg.setup(orig=orig)
+ lvm.lvactivate(self.vg.name, self._name)
+
+ # we always probe since the device may not be set up when we want
+ # information about it
+ self._size = self.currentSize
+
+ def teardown(self, recursive=None):
+ """ Close, or tear down, a device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists and not recursive:
+ raise DeviceError("device has not been created", self.name)
+
+ if self.status:
+ if self.originalFormat.exists:
+ self.originalFormat.teardown()
+ if self.format.exists:
+ self.format.teardown()
+ udev_settle()
+
+ if self.status:
+ lvm.lvdeactivate(self.vg.name, self._name)
+
+ if recursive:
+ # It's likely that teardown of a VG will fail due to other
+ # LVs being active (filesystems mounted, &c), so don't let
+ # it bring everything down.
+ try:
+ self.vg.teardown(recursive=recursive)
+ except Exception as e:
+ log.debug("vg %s teardown failed; continuing" % self.vg.name)
+
+ def create(self, intf=None):
+ """ Create the device. """
+ log_method_call(self, self.name, status=self.status)
+ if self.exists:
+ raise DeviceError("device already exists", self.name)
+
+ w = None
+ if intf:
+ w = intf.progressWindow(_("Creating"),
+ _("Creating device %s")
+ % (self.path,),
+ 100, pulse = True)
+ try:
+ self.createParents()
+ self.setupParents()
+
+ # should we use --zero for safety's sake?
+ lvm.lvcreate(self.vg.name, self._name, self.size, progress=w)
+ except Exception:
+ raise
+ else:
+ # FIXME set / update self.uuid here
+ self.exists = True
+ self.setup()
+ finally:
+ if w:
+ w.pop()
+
+ def destroy(self):
+ """ Destroy the device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ self.teardown()
+ # set up the vg's pvs so lvm can remove the lv
+ self.vg.setupParents(orig=True)
+ lvm.lvremove(self.vg.name, self._name)
+ self.exists = False
+
+ def resize(self, intf=None):
+ # XXX resize format probably, right?
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ # Setup VG parents (in case they are dmraid partitions for example)
+ self.vg.setupParents(orig=True)
+
+ if self.originalFormat.exists:
+ self.originalFormat.teardown()
+ if self.format.exists:
+ self.format.teardown()
+
+ udev_settle()
+ lvm.lvresize(self.vg.name, self._name, self.size)
+
+ def dracutSetupString(self):
+ # Note no mapName usage here, this is a lvm cmdline name, which
+ # is different (ofcourse)
+ return "rd_LVM_LV=%s/%s" % (self.vg.name, self._name)
+
+
+class MDRaidArrayDevice(StorageDevice):
+ """ An mdraid (Linux RAID) device. """
+ _type = "mdarray"
+
+ def __init__(self, name, level=None, major=None, minor=None, size=None,
+ memberDevices=None, totalDevices=None, bitmap=False,
+ uuid=None, format=None, exists=None,
+ parents=None, sysfsPath=''):
+ """ Create a MDRaidArrayDevice instance.
+
+ Arguments:
+
+ name -- the device name (generally a device node's basename)
+
+ Keyword Arguments:
+
+ level -- the device's RAID level (a string, eg: '1' or 'raid1')
+ parents -- list of member devices (StorageDevice instances)
+ size -- the device's size (units/format TBD)
+ uuid -- the device's UUID
+ minor -- the device minor
+ bitmap -- whether to use a bitmap (boolean)
+ sysfsPath -- sysfs device path
+ format -- a DeviceFormat instance
+ exists -- indicates whether this is an existing device
+ """
+ StorageDevice.__init__(self, name, format=format, exists=exists,
+ major=major, minor=minor, size=size,
+ parents=parents, sysfsPath=sysfsPath)
+
+ self.level = level
+ if level == "container":
+ self._type = "mdcontainer"
+ elif level is not None:
+ self.level = mdraid.raidLevel(level)
+
+ # For new arrays check if we have enough members
+ if (not exists and parents and
+ len(parents) < mdraid.get_raid_min_members(self.level)):
+ raise ValueError, _("A RAID%d set requires atleast %d members") % (
+ self.level, mdraid.get_raid_min_members(self.level))
+
+ self.uuid = uuid
+ self._totalDevices = numeric_type(totalDevices)
+ self._memberDevices = numeric_type(memberDevices)
+ self.sysfsPath = "/devices/virtual/block/%s" % name
+ self.chunkSize = 512.0 / 1024.0 # chunk size in MB
+ self.superBlockSize = 2.0 # superblock size in MB
+
+ # For container members probe size now, as we cannot determine it
+ # when teared down.
+ if self.parents and self.parents[0].type == "mdcontainer":
+ self._size = self.currentSize
+ self._type = "mdbiosraidarray"
+
+ # FIXME: Bitmap is more complicated than this.
+ # It can be internal or external. External requires a filename.
+ self.bitmap = bitmap
+
+ self.formatClass = get_device_format_class("mdmember")
+ if not self.formatClass:
+ raise DeviceError("cannot find class for 'mdmember'", self.name)
+
+ if self.exists and self.uuid:
+ # this is a hack to work around mdadm's insistence on giving
+ # really high minors to arrays it has no config entry for
+ md_f = open("/etc/mdadm.conf", "a")
+ md_f.write("ARRAY %s UUID=%s\n" % (self.path, self.uuid))
+ md_f.close()
+
+ @property
+ def smallestMember(self):
+ try:
+ smallest = sorted(self.devices, key=lambda d: d.size)[0]
+ except IndexError:
+ smallest = None
+ return smallest
+
+ @property
+ def size(self):
+ if not self.devices:
+ return 0
+
+ # For container members return probed size, as we cannot determine it
+ # when teared down.
+ if self.type == "mdbiosraidarray":
+ return self._size
+
+ size = 0
+ smallestMemberSize = self.smallestMember.size - self.superBlockSize
+ if not self.exists or not self.partedDevice:
+ if self.level == mdraid.RAID0:
+ size = self.memberDevices * smallestMemberSize
+ size -= size % self.chunkSize
+ elif self.level == mdraid.RAID1:
+ size = smallestMemberSize
+ elif self.level == mdraid.RAID4:
+ size = (self.memberDevices - 1) * smallestMemberSize
+ size -= size % self.chunkSize
+ elif self.level == mdraid.RAID5:
+ size = (self.memberDevices - 1) * smallestMemberSize
+ size -= size % self.chunkSize
+ elif self.level == mdraid.RAID6:
+ size = (self.memberDevices - 2) * smallestMemberSize
+ size -= size % self.chunkSize
+ elif self.level == mdraid.RAID10:
+ size = (self.memberDevices / 2.0) * smallestMemberSize
+ size -= size % self.chunkSize
+ else:
+ size = self.partedDevice.getSize()
+
+ return size
+
+ @property
+ def description(self):
+ if self.level == mdraid.RAID0:
+ levelstr = "stripe"
+ elif self.level == mdraid.RAID1:
+ levelstr = "mirror"
+ else:
+ levelstr = "raid%s" % self.level
+
+ if self.type == "mdcontainer":
+ return "BIOS RAID container"
+ elif self.type == "mdbiosraidarray":
+ return "BIOS RAID set (%s)" % levelstr
+ else:
+ return "MDRAID set (%s)" % levelstr
+
+ def __str__(self):
+ s = StorageDevice.__str__(self)
+ s += (" level = %(level)s bitmap = %(bitmap)s spares = %(spares)s\n"
+ " members = %(memberDevices)s\n"
+ " total devices = %(totalDevices)s" %
+ {"level": self.level, "bitmap": self.bitmap, "spares": self.spares,
+ "memberDevices": self.memberDevices, "totalDevices": self.totalDevices})
+ return s
+
+ @property
+ def dict(self):
+ d = super(MDRaidArrayDevice, self).dict
+ d.update({"level": self.level, "bitmap": self.bitmap,
+ "spares": self.spares, "memberDevices": self.memberDevices,
+ "totalDevices": self.totalDevices})
+ return d
+
+ def writeKS(self, f, preexisting=False, noformat=False, s=None):
+ args = ["--level=%s" % self.level,
+ "--device=%s" % self.name]
+ mems = []
+
+ if self.spares > 0:
+ args.append("--spares=%s" % self.spares)
+ if preexisting:
+ args.append("--useexisting")
+ if noformat:
+ args.append("--noformat")
+
+ for mem in self.parents:
+ mems.append("raid.%s" % mem.format.uuid)
+
+ f.write("#raid ")
+ self.format.writeKS(f)
+ f.write(" %s" % " ".join(args))
+ f.write(" %s" % " ".join(mems))
+ if s:
+ f.write(" %s" % s)
+
+ @property
+ def mdadmConfEntry(self):
+ """ This array's mdadm.conf entry. """
+ if self.level is None or self.memberDevices is None or not self.uuid:
+ raise DeviceError("array is not fully defined", self.name)
+
+ # containers and the sets within must only have a UUID= parameter
+ if self.type == "mdcontainer" or self.type == "mdbiosraidarray":
+ fmt = "ARRAY %s UUID=%s\n"
+ return fmt % (self.path, self.uuid)
+
+ fmt = "ARRAY %s level=raid%d num-devices=%d UUID=%s\n"
+ return fmt % (self.path, self.level, self.memberDevices, self.uuid)
+
+ @property
+ def totalDevices(self):
+ """ Total number of devices in the array, including spares. """
+ count = len(self.parents)
+ if not self.exists:
+ count = self._totalDevices
+ return count
+
+ def _getMemberDevices(self):
+ return self._memberDevices
+
+ def _setMemberDevices(self, number):
+ if not isinstance(number, int):
+ raise ValueError("memberDevices is an integer")
+
+ if number > self.totalDevices:
+ raise ValueError("memberDevices cannot be greater than totalDevices")
+ self._memberDevices = number
+
+ memberDevices = property(_getMemberDevices, _setMemberDevices,
+ doc="number of member devices")
+
+ def _getSpares(self):
+ spares = 0
+ if self.memberDevices is not None:
+ if self.totalDevices is not None:
+ spares = self.totalDevices - self.memberDevices
+ else:
+ spares = self.memberDevices
+ self._totalDevices = self.memberDevices
+ return spares
+
+ def _setSpares(self, spares):
+ # FIXME: this is too simple to be right
+ if self.totalDevices > spares:
+ self.memberDevices = self.totalDevices - spares
+
+ spares = property(_getSpares, _setSpares)
+
+ def probe(self):
+ """ Probe for any missing information about this device.
+
+ I'd like to avoid paying any attention to "Preferred Minor"
+ as it seems problematic.
+ """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ try:
+ self.devices[0].setup()
+ except Exception:
+ return
+
+ info = mdraid.mdexamine(self.devices[0].path)
+ if self.level is None:
+ self.level = mdraid.raidLevel(info['level'])
+
+ def updateSysfsPath(self):
+ """ Update this device's sysfs path. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ if self.status:
+ self.sysfsPath = "/devices/virtual/block/%s" % self.name
+ else:
+ self.sysfsPath = ''
+
+ def _addDevice(self, device):
+ """ Add a new member device to the array.
+
+ XXX This is for use when probing devices, not for modification
+ of arrays.
+ """
+ log_method_call(self,
+ self.name,
+ device=device.name,
+ status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ if not isinstance(device.format, self.formatClass):
+ raise ValueError("invalid device format for mdraid member")
+
+ if self.uuid and device.format.mdUuid != self.uuid:
+ raise ValueError("cannot add member with non-matching UUID")
+
+ if device in self.devices:
+ raise ValueError("device is already a member of this array")
+
+ # we added it, so now set up the relations
+ self.devices.append(device)
+ device.addChild()
+
+ device.setup()
+ udev_settle()
+ try:
+ mdraid.mdadd(device.path)
+ # mdadd causes udev events
+ udev_settle()
+ except MDRaidError as e:
+ log.warning("failed to add member %s to md array %s: %s"
+ % (device.path, self.path, e))
+
+ if self.status:
+ # we always probe since the device may not be set up when we want
+ # information about it
+ self._size = self.currentSize
+
+ def _removeDevice(self, device):
+ """ Remove a component device from the array.
+
+ XXX This is for use by clearpart, not for reconfiguration.
+ """
+ log_method_call(self,
+ self.name,
+ device=device.name,
+ status=self.status)
+
+ if device not in self.devices:
+ raise ValueError("cannot remove non-member device from array")
+
+ self.devices.remove(device)
+ device.removeChild()
+
+ @property
+ def status(self):
+ """ This device's status.
+
+ For now, this should return a boolean:
+ True the device is open and ready for use
+ False the device is not open
+ """
+ # check the status in sysfs
+ status = False
+ if not self.exists:
+ return status
+
+ state_file = "/sys/%s/md/array_state" % self.sysfsPath
+ if os.access(state_file, os.R_OK):
+ state_f = open(state_file)
+ state = state_f.read().strip()
+ state_f.close()
+ log.debug("%s state is %s" % (self.name, state))
+ if state in ("clean", "active", "active-idle", "readonly", "read-auto"):
+ status = True
+ # mdcontainers have state inactive when started (clear if stopped)
+ if self.type == "mdcontainer" and state == "inactive":
+ status = True
+
+ return status
+
+ @property
+ def degraded(self):
+ """ Return True if the array is running in degraded mode. """
+ rc = False
+ degraded_file = "/sys/%s/md/degraded" % self.sysfsPath
+ if os.access(degraded_file, os.R_OK):
+ deg_f = open(degraded_file)
+ val = deg_f.read().strip()
+ deg_f.close()
+ log.debug("%s degraded is %s" % (self.name, val))
+ if val == "1":
+ rc = True
+
+ return rc
+
+ @property
+ def devices(self):
+ """ Return a list of this array's member device instances. """
+ return self.parents
+
+ def setup(self, intf=None, orig=False):
+ """ Open, or set up, a device. """
+ log_method_call(self, self.name, orig=orig, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ if self.status:
+ return
+
+ disks = []
+ for member in self.devices:
+ member.setup(orig=orig)
+ disks.append(member.path)
+
+ update_super_minor = True
+ if self.type == "mdcontainer" or self.type == "mdbiosraidarray":
+ update_super_minor = False
+
+ mdraid.mdactivate(self.path,
+ members=disks,
+ super_minor=self.minor,
+ update_super_minor=update_super_minor,
+ uuid=self.uuid)
+
+ udev_settle()
+
+ # we always probe since the device may not be set up when we want
+ # information about it
+ self._size = self.currentSize
+
+ def teardown(self, recursive=None):
+ """ Close, or tear down, a device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists and not recursive:
+ raise DeviceError("device has not been created", self.name)
+
+ if self.status:
+ if self.originalFormat.exists:
+ self.originalFormat.teardown()
+ if self.format.exists:
+ self.format.teardown()
+ udev_settle()
+
+ # Since BIOS RAID sets (containers in mdraid terminology) never change
+ # there is no need to stop them and later restart them. Not stopping
+ # (and thus also not starting) them also works around bug 523334
+ if self.type == "mdcontainer" or self.type == "mdbiosraidarray":
+ return
+
+ # We don't really care what the array's state is. If the device
+ # file exists, we want to deactivate it. mdraid has too many
+ # states.
+ if self.exists and os.path.exists(self.path):
+ mdraid.mddeactivate(self.path)
+
+ if recursive:
+ self.teardownParents(recursive=recursive)
+
+ def create(self, intf=None):
+ """ Create the device. """
+ log_method_call(self, self.name, status=self.status)
+ if self.exists:
+ raise DeviceError("device already exists", self.name)
+
+ w = None
+ if intf:
+ w = intf.progressWindow(_("Creating"),
+ _("Creating device %s")
+ % (self.path,),
+ 100, pulse = True)
+ try:
+ self.createParents()
+ self.setupParents()
+
+ disks = [disk.path for disk in self.devices]
+ spares = len(self.devices) - self.memberDevices
+
+ # Figure out format specific options
+ metadata="1.1"
+ # bitmaps are not meaningful on raid0 according to mdadm-3.0.3
+ bitmap = self.level != 0
+ if getattr(self.format, "mountpoint", None) == "/boot":
+ metadata="1.0"
+ bitmap=False
+ elif self.format.type == "swap":
+ bitmap=False
+
+ mdraid.mdcreate(self.path,
+ self.level,
+ disks,
+ spares,
+ metadataVer=metadata,
+ bitmap=bitmap,
+ progress=w)
+ except Exception:
+ raise
+ else:
+ self.exists = True
+ # the array is automatically activated upon creation, but...
+ self.setup()
+ udev_settle()
+ self.updateSysfsPath()
+ info = udev_get_block_device(self.sysfsPath)
+ self.uuid = udev_device_get_md_uuid(info)
+ for member in self.devices:
+ member.mdUuid = self.uuid
+ finally:
+ if w:
+ w.pop()
+
+ @property
+ def formatArgs(self):
+ formatArgs = []
+ if self.format.type == "ext2":
+ if self.level == mdraid.RAID5:
+ formatArgs = ['-R',
+ 'stride=%d' % ((self.memberDevices - 1) * 16)]
+ if self.level == mdraid.RAID4:
+ formatArgs = ['-R',
+ 'stride=%d' % ((self.memberDevices - 1) * 16)]
+ elif self.level == mdraid.RAID0:
+ formatArgs = ['-R',
+ 'stride=%d' % (self.memberDevices * 16)]
+
+ def destroy(self):
+ """ Destroy the device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ self.teardown()
+
+ # The destruction of the formatting on the member devices does the
+ # real work, but it isn't our place to do it from here.
+ self.exists = False
+
+ @property
+ def mediaPresent(self):
+ # Containers should not get any format handling done
+ # (the device node does not allow read / write calls)
+ if self.type == "mdcontainer":
+ return False
+ # BIOS RAID sets should show as present even when teared down
+ elif self.type == "mdbiosraidarray":
+ return True
+ else:
+ return self.partedDevice is not None
+
+ @property
+ def model(self):
+ return self.description
+
+ @property
+ def partitionable(self):
+ return self.type == "mdbiosraidarray"
+
+ @property
+ def isDisk(self):
+ return self.type == "mdbiosraidarray"
+
+ def dracutSetupString(self):
+ return "rd_MD_UUID=%s" % self.uuid
+
+
+class DMRaidArrayDevice(DMDevice):
+ """ A dmraid (device-mapper RAID) device """
+ _type = "dm-raid array"
+ _packages = ["dmraid"]
+ _partitionable = True
+ _isDisk = True
+
+ def __init__(self, name, raidSet=None, format=None,
+ size=None, parents=None, sysfsPath=''):
+ """ Create a DMRaidArrayDevice instance.
+
+ Arguments:
+
+ name -- the dmraid name also the device node's basename
+
+ Keyword Arguments:
+
+ raidSet -- the RaidSet object from block
+ parents -- a list of the member devices
+ sysfsPath -- sysfs device path
+ size -- the device's size
+ format -- a DeviceFormat instance
+ """
+ if isinstance(parents, list):
+ for parent in parents:
+ if not parent.format or parent.format.type != "dmraidmember":
+ raise ValueError("parent devices must contain dmraidmember format")
+ DMDevice.__init__(self, name, format=format, size=size,
+ parents=parents, sysfsPath=sysfsPath, exists=True)
+
+ self.formatClass = get_device_format_class("dmraidmember")
+ if not self.formatClass:
+ raise StorageError("cannot find class for 'dmraidmember'")
+
+ self._raidSet = raidSet
+
+ @property
+ def raidSet(self):
+ return self._raidSet
+
+ def _addDevice(self, device):
+ """ Add a new member device to the array.
+
+ XXX This is for use when probing devices, not for modification
+ of arrays.
+ """
+ log_method_call(self, self.name, device=device.name, status=self.status)
+
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ if not isinstance(device.format, self.formatClass):
+ raise ValueError("invalid device format for dmraid member")
+
+ if device in self.members:
+ raise ValueError("device is already a member of this array")
+
+ # we added it, so now set up the relations
+ self.devices.append(device)
+ device.addChild()
+
+ @property
+ def members(self):
+ return self.parents
+
+ @property
+ def devices(self):
+ """ Return a list of this array's member device instances. """
+ return self.parents
+
+ def deactivate(self):
+ """ Deactivate the raid set. """
+ log_method_call(self, self.name, status=self.status)
+ # This call already checks if the set is not active.
+ self._raidSet.deactivate()
+
+ def activate(self):
+ """ Activate the raid set. """
+ log_method_call(self, self.name, status=self.status)
+ # This call already checks if the set is active.
+ self._raidSet.activate(mknod=True)
+ udev_settle()
+
+ def setup(self, intf=None, orig=False):
+ """ Open, or set up, a device. """
+ log_method_call(self, self.name, orig=orig, status=self.status)
+ StorageDevice.setup(self, intf=intf, orig=orig)
+ self.activate()
+
+ def teardown(self, recursive=None):
+ """ Close, or tear down, a device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists and not recursive:
+ raise DeviceError("device has not been created", self.name)
+
+ log.debug("not tearing down dmraid device %s" % self.name)
+
+ @property
+ def description(self):
+ return "BIOS RAID set (%s)" % self._raidSet.rs.set_type
+
+ @property
+ def model(self):
+ return self.description
+
+ def dracutSetupString(self):
+ return "rd_DM_UUID=%s" % self.name
+
+class MultipathDevice(DMDevice):
+ """ A multipath device """
+ _type = "dm-multipath"
+ _packages = ["device-mapper-multipath"]
+ _partitionable = True
+ _isDisk = True
+
+ def __init__(self, name, info, format=None, size=None,
+ parents=None, sysfsPath=''):
+ """ Create a MultipathDevice instance.
+
+ Arguments:
+
+ name -- the device name (generally a device node's basename)
+ info -- the udev info for this device
+
+ Keyword Arguments:
+
+ sysfsPath -- sysfs device path
+ size -- the device's size
+ format -- a DeviceFormat instance
+ parents -- a list of the backing devices (Device instances)
+ """
+
+ self._info = info
+ self.setupIdentity()
+ DMDevice.__init__(self, name, format=format, size=size,
+ parents=parents, sysfsPath=sysfsPath,
+ exists=True)
+
+ self.config = {
+ 'wwid' : self.identity,
+ 'alias' : self.name,
+ 'mode' : '0600',
+ 'uid' : '0',
+ 'gid' : '0',
+ }
+
+ def setupIdentity(self):
+ """ Adds identifying remarks to MultipathDevice object.
+
+ May be overridden by a sub-class for e.g. RDAC handling.
+ """
+ self._identity_short = self._info['ID_SERIAL_SHORT']
+ self._identity = self._info['ID_SERIAL']
+
+ @property
+ def identity(self):
+ """ Get identity set with setupIdentityFromInfo()
+
+ May be overridden by a sub-class for e.g. RDAC handling.
+ """
+ if not hasattr(self, "_identity"):
+ raise RuntimeError, "setupIdentityFromInfo() has not been called."
+ return self._identity
+
+ @property
+ def wwid(self):
+ identity = self._identity_short
+ ret = []
+ while identity:
+ ret.append(identity[:2])
+ identity = identity[2:]
+ return ":".join(ret)
+
+ @property
+ def model(self):
+ if not self.parents:
+ return ""
+ return self.parents[0].model
+
+ @property
+ def vendor(self):
+ if not self.parents:
+ return ""
+ return self.parents[0].vendor
+
+ @property
+ def description(self):
+ return "WWID %s" % (self.wwid,)
+
+ def addParent(self, parent):
+ """ Add a parent device to the mpath. """
+ log_method_call(self, self.name, status=self.status)
+ if self.status:
+ self.teardown()
+ self.parents.append(parent)
+ self.setup()
+ else:
+ self.parents.append(parent)
+
+ def setupPartitions(self):
+ log_method_call(self, name=self.name, kids=self.kids)
+ rc = iutil.execWithRedirect("kpartx",
+ ["-a", "-p", "p", "/dev/mapper/%s" % self.name],
+ stdout = "/dev/tty5",
+ stderr = "/dev/tty5")
+ if rc:
+ raise MPathError("multipath partition activation failed for '%s'" %
+ self.name)
+ udev_settle()
+
+ def teardown(self, recursive=None):
+ """ Tear down the mpath device. """
+ log_method_call(self, self.name, status=self.status)
+
+ if not self.exists and not recursive:
+ raise DeviceError("device has not been created", self.name)
+
+ if self.exists and os.path.exists(self.path):
+ #self.teardownPartitions()
+ #rc = iutil.execWithRedirect("multipath",
+ # ['-f', self.name],
+ # stdout = "/dev/tty5",
+ # stderr = "/dev/tty5")
+ #if rc:
+ # raise MPathError("multipath deactivation failed for '%s'" %
+ # self.name)
+ bdev = block.getDevice(self.name)
+ devmap = block.getMap(major=bdev[0], minor=bdev[1])
+ if devmap.open_count:
+ return
+ try:
+ block.removeDeviceMap(devmap)
+ except Exception as e:
+ raise MPathError("failed to tear down multipath device %s: %s"
+ % (self.name, e))
+
+ if recursive:
+ self.teardownParents(recursive=recursive)
+
+ def setup(self, intf=None, orig=False):
+ """ Open, or set up, a device. """
+ log_method_call(self, self.name, orig=orig, status=self.status)
+
+ if self.status:
+ return
+
+ StorageDevice.setup(self, intf=intf, orig=orig)
+ udev_settle()
+ rc = iutil.execWithRedirect("multipath",
+ [self.name],
+ stdout = "/dev/tty5",
+ stderr = "/dev/tty5")
+ if rc:
+ raise MPathError("multipath activation failed for '%s'" %
+ self.name)
+ udev_settle()
+ self.setupPartitions()
+ udev_settle()
+
+class NoDevice(StorageDevice):
+ """ A nodev device for nodev filesystems like tmpfs. """
+ _type = "nodev"
+
+ def __init__(self, format=None):
+ """ Create a NoDevice instance.
+
+ Arguments:
+
+ Keyword Arguments:
+
+ format -- a DeviceFormat instance
+ """
+ if format:
+ name = format.type
+ else:
+ name = "none"
+
+ StorageDevice.__init__(self, name, format=format)
+
+ @property
+ def path(self):
+ """ Device node representing this device. """
+ return self.name
+
+ def probe(self):
+ """ Probe for any missing information about this device. """
+ log_method_call(self, self.name, status=self.status)
+
+ def setup(self, intf=None, orig=False):
+ """ Open, or set up, a device. """
+ log_method_call(self, self.name, orig=orig, status=self.status)
+
+ def teardown(self, recursive=False):
+ """ Close, or tear down, a device. """
+ log_method_call(self, self.name, status=self.status)
+
+ def create(self, intf=None):
+ """ Create the device. """
+ log_method_call(self, self.name, status=self.status)
+ self.setupParents()
+
+ def destroy(self):
+ """ Destroy the device. """
+ log_method_call(self, self.name, status=self.status)
+
+
+class FileDevice(StorageDevice):
+ """ A file on a filesystem.
+
+ This exists because of swap files.
+ """
+ _type = "file"
+ _devDir = ""
+
+ def __init__(self, path, format=None, size=None,
+ exists=None, parents=None):
+ """ Create a FileDevice instance.
+
+ Arguments:
+
+ path -- full path to the file
+
+ Keyword Arguments:
+
+ format -- a DeviceFormat instance
+ size -- the file size (units TBD)
+ parents -- a list of required devices (Device instances)
+ exists -- indicates whether this is an existing device
+ """
+ StorageDevice.__init__(self, path, format=format, size=size,
+ exists=exists, parents=parents)
+
+ def probe(self):
+ """ Probe for any missing information about this device. """
+ pass
+
+ @property
+ def fstabSpec(self):
+ return self.name
+
+ @property
+ def path(self):
+ path = self.name
+ root = ""
+ try:
+ status = self.parents[0].format.status
+ except (AttributeError, IndexError):
+ status = False
+
+ if status:
+ # this is the actual active mountpoint
+ root = self.parents[0].format._mountpoint
+ # trim the mountpoint down to the chroot since we already have
+ # the otherwise fully-qualified path
+ mountpoint = self.parents[0].format.mountpoint
+ if mountpoint.endswith("/"):
+ mountpoint = mountpoint[:-1]
+ if mountpoint:
+ root = root[:-len(mountpoint)]
+
+ return os.path.normpath("%s/%s" % (root, path))
+
+ def setup(self, intf=None, orig=False):
+ StorageDevice.setup(self, orig=orig)
+ if self.format and self.format.exists and not self.format.status:
+ self.format.device = self.path
+
+ for parent in self.parents:
+ if orig:
+ parent.originalFormat.setup()
+ else:
+ parent.format.setup()
+
+ def teardown(self, recursive=None):
+ StorageDevice.teardown(self)
+ if self.format and self.format.exists and not self.format.status:
+ self.format.device = self.path
+
+ def create(self, intf=None):
+ """ Create the device. """
+ log_method_call(self, self.name, status=self.status)
+ if self.exists:
+ raise DeviceError("device already exists", self.name)
+
+ w = None
+ if intf:
+ w = intf.waitWindow(_("Creating"),
+ _("Creating file %s") % (self.path,))
+
+ try:
+ # this only checks that parents exist
+ self.createParents()
+ self.setupParents()
+
+ fd = os.open(self.path, os.O_RDWR)
+ buf = '\0' * 1024 * 1024 * self.size
+ os.write(fd, buf)
+ except (OSError, TypeError) as e:
+ log.error("error writing out %s: %s" % (self.path, e))
+ raise DeviceError(e, self.name)
+ else:
+ self.exists = True
+ finally:
+ os.close(fd)
+ if w:
+ w.pop()
+
+ def destroy(self):
+ """ Destroy the device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ os.unlink(self.path)
+ self.exists = False
+
+
+class DirectoryDevice(FileDevice):
+ """ A directory on a filesystem.
+
+ This exists because of bind mounts.
+ """
+ _type = "directory"
+
+ def create(self):
+ """ Create the device. """
+ log_method_call(self, self.name, status=self.status)
+ if self.exists:
+ raise DeviceError("device already exists", self.name)
+
+ self.createParents()
+ self.setupParents()
+ try:
+ iutil.mkdirChain(self.path)
+ except Exception, e:
+ raise DeviceError(e, self.name)
+
+ self.exists = True
+
+ def destroy(self):
+ """ Destroy the device. """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ os.unlink(self.path)
+ self.exists = False
+
+
+class iScsiDiskDevice(DiskDevice, NetworkStorageDevice):
+ """ An iSCSI disk. """
+ _type = "iscsi"
+ _packages = ["iscsi-initiator-utils", "dracut-network"]
+
+ def __init__(self, device, **kwargs):
+ self.node = kwargs.pop("node")
+ self.ibft = kwargs.pop("ibft")
+ self.initiator = kwargs.pop("initiator")
+ DiskDevice.__init__(self, device, **kwargs)
+ NetworkStorageDevice.__init__(self, host_address=self.node.address)
+ log.debug("created new iscsi disk %s %s:%d" % (self.node.name, self.node.address, self.node.port))
+
+ def dracutSetupString(self):
+ if self.ibft:
+ return "iscsi_firmware"
+
+ netroot="netroot=iscsi:"
+ auth = self.node.getAuth()
+ if auth:
+ netroot += "%s:%s" % (auth.username, auth.password)
+ if len(auth.reverse_username) or len(auth.reverse_password):
+ netroot += ":%s:%s" % (auth.reverse_username,
+ auth.reverse_password)
+
+ netroot += "@%s::%d::%s" % (self.node.address, self.node.port,
+ self.node.name)
+
+ netroot += " iscsi_initiator=%s" % self.initiator
+
+ return netroot
+
+class FcoeDiskDevice(DiskDevice, NetworkStorageDevice):
+ """ An FCoE disk. """
+ _type = "fcoe"
+ _packages = ["fcoe-utils", "dracut-network"]
+
+ def __init__(self, device, **kwargs):
+ self.nic = kwargs.pop("nic")
+ self.identifier = kwargs.pop("identifier")
+ DiskDevice.__init__(self, device, **kwargs)
+ NetworkStorageDevice.__init__(self, nic=self.nic)
+ log.debug("created new fcoe disk %s @ %s" % (device, self.nic))
+
+ def dracutSetupString(self):
+ dcb = True
+
+ from .fcoe import fcoe
+ for nic, dcb in fcoe().nics:
+ if nic == self.nic:
+ break
+
+ if dcb:
+ dcbOpt = "dcb"
+ else:
+ dcbOpt = "nodcb"
+
+ return "netroot=fcoe:%s:%s" % (self.nic, dcbOpt)
+
+
+class OpticalDevice(StorageDevice):
+ """ An optical drive, eg: cdrom, dvd+r, &c.
+
+ XXX Is this useful?
+ """
+ _type = "cdrom"
+
+ def __init__(self, name, major=None, minor=None, exists=None,
+ format=None, parents=None, sysfsPath='', vendor="",
+ model=""):
+ StorageDevice.__init__(self, name, format=format,
+ major=major, minor=minor, exists=True,
+ parents=parents, sysfsPath=sysfsPath,
+ vendor=vendor, model=model)
+
+ @property
+ def mediaPresent(self):
+ """ Return a boolean indicating whether or not the device contains
+ media.
+ """
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ try:
+ fd = os.open(self.path, os.O_RDONLY)
+ except OSError as e:
+ # errno 123 = No medium found
+ if e.errno == 123:
+ return False
+ else:
+ return True
+ else:
+ os.close(fd)
+ return True
+
+ def eject(self):
+ """ Eject the drawer. """
+ import _isys
+
+ log_method_call(self, self.name, status=self.status)
+ if not self.exists:
+ raise DeviceError("device has not been created", self.name)
+
+ #try to umount and close device before ejecting
+ self.teardown()
+
+ # Make a best effort attempt to do the eject. If it fails, it's not
+ # critical.
+ fd = os.open(self.path, os.O_RDONLY | os.O_NONBLOCK)
+
+ try:
+ _isys.ejectcdrom(fd)
+ except SystemError as e:
+ log.warning("error ejecting cdrom %s: %s" % (self.name, e))
+
+ os.close(fd)
+
+
+class ZFCPDiskDevice(DiskDevice):
+ """ A mainframe ZFCP disk. """
+ _type = "zfcp"
+
+ def __init__(self, device, **kwargs):
+ self.hba_id = kwargs.pop("hba_id")
+ self.wwpn = kwargs.pop("wwpn")
+ self.fcp_lun = kwargs.pop("fcp_lun")
+ DiskDevice.__init__(self, device, **kwargs)
+
+ def __str__(self):
+ s = DiskDevice.__str__(self)
+ s += (" hba_id = %(hba_id)s wwpn = %(wwpn)s fcp_lun = %(fcp_lun)s" %
+ {"hba_id": self.hba_id,
+ "wwpn": self.wwpn,
+ "fcp_lun": self.fcp_lun})
+ return s
+
+ def dracutSetupString(self):
+ return "rd_ZFCP=%s,%s,%s" % (self.hba_id, self.wwpn, self.fcp_lun,)
+
+
+class DASDDevice(DiskDevice):
+ """ A mainframe DASD. """
+ _type = "dasd"
+
+ def __init__(self, device, **kwargs):
+ self.busid = kwargs.pop('busid')
+ self.opts = kwargs.pop('opts')
+ self.dasd = kwargs.pop('dasd')
+ DiskDevice.__init__(self, device, **kwargs)
+
+ if self.dasd:
+ self.dasd.addDASD(self)
+
+ def getOpts(self):
+ return map(lambda (k, v): "%s=%s" % (k, v,), self.opts.items())
+
+ def dracutSetupString(self):
+ args = ["rd_DASD=%s" % (self.busid,)] + self.getOpts()
+ return ",".join(args)
+
+
+class NFSDevice(StorageDevice, NetworkStorageDevice):
+ """ An NFS device """
+ _type = "nfs"
+ _packages = ["dracut-network"]
+
+ def __init__(self, device, format=None, parents=None):
+ # we could make host/ip, path, &c but will anything use it?
+ StorageDevice.__init__(self, device, format=format, parents=parents)
+ NetworkStorageDevice.__init__(self, device.split(":")[0])
+
+ @property
+ def path(self):
+ """ Device node representing this device. """
+ return self.name
+
+ def setup(self, intf=None, orig=False):
+ """ Open, or set up, a device. """
+ log_method_call(self, self.name, orig=orig, status=self.status)
+
+ def teardown(self, recursive=None):
+ """ Close, or tear down, a device. """
+ log_method_call(self, self.name, status=self.status)
+
+ def create(self, intf=None):
+ """ Create the device. """
+ log_method_call(self, self.name, status=self.status)
+ self.createParents()
+ self.setupParents()
+
+ def destroy(self):
+ """ Destroy the device. """
+ log_method_call(self, self.name, status=self.status)
diff --git a/storage/devicetree.py b/storage/devicetree.py
new file mode 100644
index 0000000..a71b5b0
--- /dev/null
+++ b/storage/devicetree.py
@@ -0,0 +1,2259 @@
+# devicetree.py
+# Device management 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 stat
+import block
+import re
+
+from errors import *
+from devices import *
+from deviceaction import *
+from partitioning import shouldClear
+from pykickstart.constants import *
+import formats
+import devicelibs.mdraid
+import devicelibs.dm
+import devicelibs.lvm
+import devicelibs.mpath
+from udev import *
+from .storage_log import log_method_call
+import iutil
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+def getLUKSPassphrase(intf, device, globalPassphrase):
+ """ Obtain a passphrase for a LUKS encrypted block device.
+
+ The format's mapping name must already be set and the backing
+ device must already be set up before calling this function.
+
+ If successful, this function leaves the device mapped.
+
+ Return value is a two-tuple: (passphrase, isglobal)
+
+ passphrase is the passphrase string, if obtained
+ isglobal is a boolean indicating whether the passphrase is global
+
+ Either or both can be None, depending on the outcome.
+ """
+ if device.format.type != "luks":
+ # this function only works on luks devices
+ raise ValueError("not a luks device")
+
+ if not device.status:
+ # the device should have already been set up
+ raise RuntimeError("device is not set up")
+
+ if device.format.status:
+ # the device is already mapped
+ raise RuntimeError("device is already mapped")
+
+ if not device.format.configured and globalPassphrase:
+ # try the given passphrase first
+ device.format.passphrase = globalPassphrase
+
+ try:
+ device.format.setup()
+ except CryptoError as e:
+ device.format.passphrase = None
+ else:
+ # we've opened the device so we're done.
+ return (globalPassphrase, False)
+
+ if not intf:
+ return (None, None)
+
+ buttons = [_("Back"), _("Continue")]
+ passphrase_incorrect = False
+ while True:
+ if passphrase_incorrect:
+ # TODO: add a flag to passphraseEntryWindow to say the last
+ # passphrase was incorrect so try again
+ passphrase_incorrect = False
+ (passphrase, isglobal) = intf.passphraseEntryWindow(device.name)
+ if not passphrase:
+ rc = intf.messageWindow(_("Confirm"),
+ _("Are you sure you want to skip "
+ "entering a passphrase for device "
+ "%s?\n\n"
+ "If you skip this step the "
+ "device's contents will not "
+ "be available during "
+ "installation.") % device.name,
+ type = "custom",
+ default = 0,
+ custom_buttons = buttons)
+ if rc == 0:
+ continue
+ else:
+ passphrase = None
+ isglobal = None
+ log.info("skipping passphrase for %s" % (device.name,))
+ break
+
+ device.format.passphrase = passphrase
+
+ try:
+ device.format.setup()
+ except CryptoError as e:
+ device.format.passphrase = None
+ passphrase_incorrect = True
+ else:
+ # we've opened the device so we're done.
+ break
+
+ return (passphrase, isglobal)
+
+
+class DeviceTree(object):
+ """ A quasi-tree that represents the devices in the system.
+
+ The tree contains a list of device instances, which does not
+ necessarily reflect the actual state of the system's devices.
+ DeviceActions are used to perform modifications to the tree,
+ except when initially populating the tree.
+
+ DeviceAction instances are registered, possibly causing the
+ addition or removal of Device instances to/from the tree. The
+ DeviceActions are all reversible up to the time their execute
+ method has been called.
+
+ Only one action of any given type/object pair should exist for
+ any given device at any given time.
+
+ DeviceAction instances can only be registered for leaf devices,
+ except for resize actions.
+ """
+
+ def __init__(self, intf=None, ignored=[], exclusive=[], type=CLEARPART_TYPE_NONE,
+ clear=[], zeroMbr=None, reinitializeDisks=None, protected=[],
+ passphrase=None, luksDict=None, iscsi=None, dasd=None):
+ # internal data members
+ self._devices = []
+ self._actions = []
+
+ # indicates whether or not the tree has been fully populated
+ self.populated = False
+
+ self.intf = intf
+ self.exclusiveDisks = exclusive
+ self.clearPartType = type
+ self.clearPartDisks = clear
+ self.zeroMbr = zeroMbr
+ self.reinitializeDisks = reinitializeDisks
+ self.iscsi = iscsi
+ self.dasd = dasd
+
+ # protected device specs as provided by the user
+ self.protectedDevSpecs = protected
+
+ # names of protected devices at the time of tree population
+ self.protectedDevNames = []
+
+ self.unusedRaidMembers = []
+
+ self.__multipaths = {}
+ self.__multipathConfigWriter = devicelibs.mpath.MultipathConfigWriter()
+
+ self.__passphrase = passphrase
+ self.__luksDevs = {}
+ if luksDict and isinstance(luksDict, dict):
+ self.__luksDevs = luksDict
+ self._ignoredDisks = []
+ for disk in ignored:
+ self.addIgnoredDisk(disk)
+ self.immutableDevices = []
+ lvm.lvm_cc_resetFilter()
+
+ def addIgnoredDisk(self, disk):
+ self._ignoredDisks.append(disk)
+ lvm.lvm_cc_addFilterRejectRegexp(disk)
+
+ def pruneActions(self):
+ """ Prune loops and redundant actions from the queue. """
+ # handle device destroy actions
+ actions = self.findActions(type="destroy", object="device")
+ for a in actions:
+ if a not in self._actions:
+ # we may have removed some of the actions in a previous
+ # iteration of this loop
+ continue
+
+ log.debug("action '%s' (%s)" % (a, id(a)))
+ destroys = self.findActions(devid=a.device.id,
+ type="destroy",
+ object="device")
+
+ creates = self.findActions(devid=a.device.id,
+ type="create",
+ object="device")
+
+ # If the device is not preexisting, we remove all actions up
+ # to and including the last destroy action.
+ # If the device is preexisting, we remove all actions from
+ # after the first destroy action up to and including the last
+ # destroy action.
+ loops = []
+ first_destroy_idx = None
+ first_create_idx = None
+ stop_action = None
+ start = None
+ if len(destroys) > 1:
+ # there are multiple destroy actions for this device
+ loops = destroys
+ first_destroy_idx = self._actions.index(loops[0])
+ start = self._actions.index(a) + 1
+ stop_action = destroys[-1]
+
+ if creates:
+ first_create_idx = self._actions.index(creates[0])
+ if not loops or first_destroy_idx > first_create_idx:
+ # this device is not preexisting
+ start = first_create_idx
+ stop_action = destroys[-1]
+
+ if start is None:
+ continue
+
+ # now we remove all actions on this device between the start
+ # index (into self._actions) and stop_action.
+ dev_actions = self.findActions(path=a.device.path)
+ for rem in dev_actions:
+ end = self._actions.index(stop_action)
+ if start <= self._actions.index(rem) <= end:
+ log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
+ self._actions.remove(rem)
+
+ if rem == stop_action:
+ break
+
+ # device create actions
+ actions = self.findActions(type="create", object="device")
+ for a in actions:
+ if a not in self._actions:
+ # we may have removed some of the actions in a previous
+ # iteration of this loop
+ continue
+
+ log.debug("action '%s' (%s)" % (a, id(a)))
+ creates = self.findActions(devid=a.device.id,
+ type="create",
+ object="device")
+
+ destroys = self.findActions(devid=a.device.id,
+ type="destroy",
+ object="device")
+
+ # If the device is preexisting, we remove everything between
+ # the first destroy and the last create.
+ # If the device is not preexisting, we remove everything up to
+ # the last create.
+ loops = []
+ first_destroy_idx = None
+ first_create_idx = None
+ stop_action = None
+ start = None
+ if len(creates) > 1:
+ # there are multiple create actions for this device
+ loops = creates
+ first_create_idx = self._actions.index(loops[0])
+ start = 0
+ stop_action = creates[-1]
+
+ if destroys:
+ first_destroy_idx = self._actions.index(destroys[0])
+ if not loops or first_create_idx > first_destroy_idx:
+ # this device is preexisting
+ start = first_destroy_idx + 1
+ stop_action = creates[-1]
+
+ if start is None:
+ continue
+
+ # remove all actions on this from after the first destroy up
+ # to the last create
+ dev_actions = self.findActions(devid=a.device.id)
+ for rem in dev_actions:
+ if rem == stop_action:
+ break
+
+ end = self._actions.index(stop_action)
+ if start <= self._actions.index(rem) < end:
+ log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
+ self._actions.remove(rem)
+
+ # device resize actions
+ actions = self.findActions(type="resize", object="device")
+ for a in actions:
+ if a not in self._actions:
+ # we may have removed some of the actions in a previous
+ # iteration of this loop
+ continue
+
+ log.debug("action '%s' (%s)" % (a, id(a)))
+ loops = self.findActions(devid=a.device.id,
+ type="resize",
+ object="device")
+
+ if len(loops) == 1:
+ continue
+
+ # remove all but the last resize action on this device
+ for rem in loops[:-1]:
+ log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
+ self._actions.remove(rem)
+
+ # format destroy
+ # XXX I don't think there's a way for these loops to happen
+ actions = self.findActions(type="destroy", object="format")
+ for a in actions:
+ if a not in self._actions:
+ # we may have removed some of the actions in a previous
+ # iteration of this loop
+ continue
+
+ log.debug("action '%s' (%s)" % (a, id(a)))
+ destroys = self.findActions(devid=a.device.id,
+ type="destroy",
+ object="format")
+
+ creates = self.findActions(devid=a.device.id,
+ type="create",
+ object="format")
+
+ # If the format is not preexisting, we remove all actions up
+ # to and including the last destroy action.
+ # If the format is preexisting, we remove all actions from
+ # after the first destroy action up to and including the last
+ # destroy action.
+ loops = []
+ first_destroy_idx = None
+ first_create_idx = None
+ stop_action = None
+ start = None
+ if len(destroys) > 1:
+ # there are multiple destroy actions for this format
+ loops = destroys
+ first_destroy_idx = self._actions.index(loops[0])
+ start = self._actions.index(a) + 1
+ stop_action = destroys[-1]
+
+ if creates:
+ first_create_idx = self._actions.index(creates[0])
+ if not loops or first_destroy_idx > first_create_idx:
+ # this format is not preexisting
+ start = first_create_idx
+ stop_action = destroys[-1]
+
+ if start is None:
+ continue
+
+ # now we remove all actions on this device's format between
+ # the start index (into self._actions) and stop_action.
+ dev_actions = self.findActions(devid=a.device.id,
+ object="format")
+ for rem in dev_actions:
+ end = self._actions.index(stop_action)
+ if start <= self._actions.index(rem) <= end:
+ log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
+ self._actions.remove(rem)
+
+ if rem == stop_action:
+ break
+
+ # format create
+ # XXX I don't think there's a way for these loops to happen
+ actions = self.findActions(type="create", object="format")
+ for a in actions:
+ if a not in self._actions:
+ # we may have removed some of the actions in a previous
+ # iteration of this loop
+ continue
+
+ log.debug("action '%s' (%s)" % (a, id(a)))
+ creates = self.findActions(devid=a.device.id,
+ type="create",
+ object="format")
+
+ destroys = self.findActions(devid=a.device.id,
+ type="destroy",
+ object="format")
+
+ # If the format is preexisting, we remove everything between
+ # the first destroy and the last create.
+ # If the format is not preexisting, we remove everything up to
+ # the last create.
+ loops = []
+ first_destroy_idx = None
+ first_create_idx = None
+ stop_action = None
+ start = None
+ if len(creates) > 1:
+ # there are multiple create actions for this format
+ loops = creates
+ first_create_idx = self._actions.index(loops[0])
+ start = 0
+ stop_action = creates[-1]
+
+ if destroys:
+ first_destroy_idx = self._actions.index(destroys[0])
+ if not loops or first_create_idx > first_destroy_idx:
+ # this format is preexisting
+ start = first_destroy_idx + 1
+ stop_action = creates[-1]
+
+ if start is None:
+ continue
+
+ # remove all actions on this from after the first destroy up
+ # to the last create
+ dev_actions = self.findActions(devid=a.device.id,
+ object="format")
+ for rem in dev_actions:
+ if rem == stop_action:
+ break
+
+ end = self._actions.index(stop_action)
+ if start <= self._actions.index(rem) < end:
+ log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
+ self._actions.remove(rem)
+
+ # format resize
+ actions = self.findActions(type="resize", object="format")
+ for a in actions:
+ if a not in self._actions:
+ # we may have removed some of the actions in a previous
+ # iteration of this loop
+ continue
+
+ log.debug("action '%s' (%s)" % (a, id(a)))
+ loops = self.findActions(devid=a.device.id,
+ type="resize",
+ object="format")
+
+ if len(loops) == 1:
+ continue
+
+ # remove all but the last resize action on this format
+ for rem in loops[:-1]:
+ log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
+ self._actions.remove(rem)
+
+ # format migrate
+ # XXX I don't think there's away for these loops to occur
+ actions = self.findActions(type="migrate", object="format")
+ for a in actions:
+ if a not in self._actions:
+ # we may have removed some of the actions in a previous
+ # iteration of this loop
+ continue
+
+ log.debug("action '%s' (%s)" % (a, id(a)))
+ loops = self.findActions(devid=a.device.id,
+ type="migrate",
+ object="format")
+
+ if len(loops) == 1:
+ continue
+
+ # remove all but the last migrate action on this format
+ for rem in loops[:-1]:
+ log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
+ self._actions.remove(rem)
+
+ def processActions(self, dryRun=None):
+ """ Execute all registered actions. """
+ # in most cases the actions will already be sorted because of the
+ # rules for registration, but let's not rely on that
+ def cmpActions(a1, a2):
+ ret = 0
+ if a1.isDestroy() and a2.isDestroy():
+ if a1.device.path == a2.device.path:
+ # if it's the same device, destroy the format first
+ if a1.isFormat() and a2.isFormat():
+ ret = 0
+ elif a1.isFormat() and not a2.isFormat():
+ ret = -1
+ elif not a1.isFormat() and a2.isFormat():
+ ret = 1
+ elif a1.device.dependsOn(a2.device):
+ ret = -1
+ elif a2.device.dependsOn(a1.device):
+ ret = 1
+ # generally destroy partitions after lvs, vgs, &c
+ elif isinstance(a1.device, PartitionDevice) and \
+ isinstance(a2.device, PartitionDevice):
+ if a1.device.disk == a2.device.disk:
+ ret = cmp(a2.device.partedPartition.number,
+ a1.device.partedPartition.number)
+ else:
+ ret = cmp(a2.device.name, a1.device.name)
+ elif isinstance(a1.device, PartitionDevice) and \
+ a2.device.partitioned:
+ ret = 1
+ elif isinstance(a2.device, PartitionDevice) and \
+ a1.device.partitioned:
+ ret = -1
+ else:
+ ret = 0
+ elif a1.isDestroy():
+ ret = -1
+ elif a2.isDestroy():
+ ret = 1
+ elif a1.isResize() and a2.isResize():
+ if a1.device.path == a2.device.path:
+ if a1.obj == a2.obj:
+ ret = 0
+ elif a1.isFormat() and not a2.isFormat():
+ # same path, one device, one format
+ if a1.isGrow():
+ ret = 1
+ else:
+ ret = -1
+ elif not a1.isFormat() and a2.isFormat():
+ # same path, one device, one format
+ if a1.isGrow():
+ ret = -1
+ else:
+ ret = 1
+ else:
+ ret = cmp(a1.device.name, a2.device.name)
+ elif a1.device.dependsOn(a2.device):
+ if a1.isGrow():
+ ret = 1
+ else:
+ ret = -1
+ elif a2.device.dependsOn(a1.device):
+ if a1.isGrow():
+ ret = -1
+ else:
+ ret = 1
+ elif isinstance(a1.device, PartitionDevice) and \
+ isinstance(a2.device, PartitionDevice):
+ ret = cmp(a1.device.name, a2.device.name)
+ elif isinstance(a1.device, PartitionDevice) and \
+ a2.device.partitioned:
+ if a1.isGrow():
+ ret = -1
+ else:
+ ret = 1
+ elif isinstance(a2.device, PartitionDevice) and \
+ a1.device.partitioned:
+ if a2.isGrow():
+ ret = 1
+ else:
+ ret = -1
+ else:
+ ret = 0
+ elif a1.isResize():
+ ret = -1
+ elif a2.isResize():
+ ret = 1
+ elif a1.isCreate() and a2.isCreate():
+ if a1.device.path == a2.device.path:
+ if a1.obj == a2.obj:
+ ret = 0
+ if a1.isFormat():
+ ret = 1
+ elif a2.isFormat():
+ ret = -1
+ else:
+ ret = 0
+ elif a1.device.dependsOn(a2.device):
+ ret = 1
+ elif a2.device.dependsOn(a1.device):
+ ret = -1
+ # generally create partitions before other device types
+ elif isinstance(a1.device, PartitionDevice) and \
+ isinstance(a2.device, PartitionDevice):
+ if a1.device.disk == a2.device.disk:
+ ret = cmp(a1.device.partedPartition.number,
+ a2.device.partedPartition.number)
+ else:
+ ret = cmp(a1.device.name, a2.device.name)
+ elif isinstance(a1.device, PartitionDevice) and \
+ a2.device.partitioned:
+ ret = 1
+ elif isinstance(a2.device, PartitionDevice) and \
+ a1.device.partitioned:
+ ret = -1
+ elif isinstance(a1.device, PartitionDevice) and \
+ not isinstance(a2.device, PartitionDevice):
+ ret = -1
+ elif isinstance(a2.device, PartitionDevice) and \
+ not isinstance(a1.device, PartitionDevice):
+ ret = 1
+ else:
+ ret = 0
+ elif a1.isCreate():
+ ret = -1
+ elif a2.isCreate():
+ ret = 1
+ elif a1.isMigrate() and a2.isMigrate():
+ if a1.device.path == a2.device.path:
+ ret = 0
+ elif a1.device.dependsOn(a2.device):
+ ret = 1
+ elif a2.device.dependsOn(a1.device):
+ ret = -1
+ elif isinstance(a1.device, PartitionDevice) and \
+ isinstance(a2.device, PartitionDevice):
+ ret = cmp(a1.device.name, a2.device.name)
+ else:
+ ret = cmp(a1.device.name, a2.device.name)
+ else:
+ ret = 0
+
+ log.debug("cmp: %d -- %s | %s" % (ret, a1, a2))
+ return ret
+
+ log.debug("resetting parted disks...")
+ for device in self.devices:
+ if device.partitioned:
+ device.format.resetPartedDisk()
+ if device.originalFormat.type == "disklabel" and \
+ device.originalFormat != device.format:
+ device.originalFormat.resetPartedDisk()
+
+ # reget parted.Partition for remaining preexisting devices
+ for device in self.devices:
+ if isinstance(device, PartitionDevice) and device.exists:
+ device.resetPartedPartition()
+
+ # reget parted.Partition for existing devices we're removing
+ for action in self._actions:
+ if isinstance(action.device, PartitionDevice) and \
+ action.device.exists:
+ action.device.resetPartedPartition()
+
+ # setup actions to create any extended partitions we added
+ #
+ # XXX At this point there can be duplicate partition paths in the
+ # tree (eg: non-existent sda6 and previous sda6 that will become
+ # sda5 in the course of partitioning), so we access the list
+ # directly here.
+ for device in self._devices:
+ if isinstance(device, PartitionDevice) and \
+ device.isExtended and not device.exists:
+ # don't properly register the action since the device is
+ # already in the tree
+ self._actions.append(ActionCreateDevice(device))
+
+ for action in self._actions:
+ log.debug("action: %s" % action)
+
+ log.debug("pruning action queue...")
+ self.pruneActions()
+ for action in self._actions:
+ log.debug("action: %s" % action)
+
+ log.debug("sorting actions...")
+ self._actions.sort(cmp=cmpActions)
+ for action in self._actions:
+ log.debug("action: %s" % action)
+
+ for action in self._actions:
+ log.info("executing action: %s" % action)
+ if not dryRun:
+ try:
+ action.execute(intf=self.intf)
+ except DiskLabelCommitError:
+ # it's likely that a previous format destroy action
+ # triggered setup of an lvm or md device.
+ self.teardownAll()
+ action.execute(intf=self.intf)
+
+ udev_settle()
+ for device in self._devices:
+ # make sure we catch any renumbering parted does
+ if device.exists and isinstance(device, PartitionDevice):
+ device.updateName()
+ device.format.device = device.path
+
+ def _addDevice(self, newdev):
+ """ Add a device to the tree.
+
+ Raise ValueError if the device's identifier is already
+ in the list.
+ """
+ if newdev.path in [d.path for d in self._devices] and \
+ not isinstance(newdev, NoDevice):
+ raise ValueError("device is already in tree")
+
+ # make sure this device's parent devices are in the tree already
+ for parent in newdev.parents:
+ if parent not in self._devices:
+ raise DeviceTreeError("parent device not in tree")
+
+ self._devices.append(newdev)
+ log.debug("added %s %s (id %d) to device tree" % (newdev.type,
+ newdev.name,
+ newdev.id))
+
+ def _removeDevice(self, dev, force=None, moddisk=True):
+ """ Remove a device from the tree.
+
+ Only leaves may be removed.
+ """
+ if dev not in self._devices:
+ raise ValueError("Device '%s' not in tree" % dev.name)
+
+ if not dev.isleaf and not force:
+ log.debug("%s has %d kids" % (dev.name, dev.kids))
+ raise ValueError("Cannot remove non-leaf device '%s'" % dev.name)
+
+ # if this is a partition we need to remove it from the parted.Disk
+ if moddisk and isinstance(dev, PartitionDevice) and \
+ dev.disk is not None:
+ # if this partition hasn't been allocated it could not have
+ # a disk attribute
+ if dev.partedPartition.type == parted.PARTITION_EXTENDED and \
+ len(dev.disk.format.logicalPartitions) > 0:
+ raise ValueError("Cannot remove extended partition %s. "
+ "Logical partitions present." % dev.name)
+
+ dev.disk.format.removePartition(dev.partedPartition)
+
+ # adjust all other PartitionDevice instances belonging to the
+ # same disk so the device name matches the potentially altered
+ # name of the parted.Partition
+ for device in self._devices:
+ if isinstance(device, PartitionDevice) and \
+ device.disk == dev.disk:
+ device.updateName()
+
+ self._devices.remove(dev)
+ log.debug("removed %s %s (id %d) from device tree" % (dev.type,
+ dev.name,
+ dev.id))
+
+ for parent in dev.parents:
+ # Will this cause issues with garbage collection?
+ # Do we care about garbage collection? At all?
+ parent.removeChild()
+
+ def registerAction(self, action):
+ """ Register an action to be performed at a later time.
+
+ Modifications to the Device instance are handled before we
+ get here.
+ """
+ if (action.isDestroy() or action.isResize() or \
+ (action.isCreate() and action.isFormat())) and \
+ action.device not in self._devices:
+ raise DeviceTreeError("device is not in the tree")
+ elif (action.isCreate() and action.isDevice()):
+ # this allows multiple create actions w/o destroy in between;
+ # we will clean it up before processing actions
+ #raise DeviceTreeError("device is already in the tree")
+ if action.device in self._devices:
+ self._removeDevice(action.device)
+ for d in self._devices:
+ if d.path == action.device.path:
+ self._removeDevice(d)
+
+ if action.isCreate() and action.isDevice():
+ self._addDevice(action.device)
+ elif action.isDestroy() and action.isDevice():
+ self._removeDevice(action.device)
+ elif action.isCreate() and action.isFormat():
+ if isinstance(action.device.format, formats.fs.FS) and \
+ action.device.format.mountpoint in self.filesystems:
+ raise DeviceTreeError("mountpoint already in use")
+
+ log.debug("registered action: %s" % action)
+ self._actions.append(action)
+
+ def cancelAction(self, action):
+ """ Cancel a registered action.
+
+ This will unregister the action and do any required
+ modifications to the device list.
+
+ Actions all operate on a Device, so we can use the devices
+ to determine dependencies.
+ """
+ if action.isCreate() and action.isDevice():
+ # remove the device from the tree
+ self._removeDevice(action.device)
+ elif action.isDestroy() and action.isDevice():
+ # add the device back into the tree
+ self._addDevice(action.device)
+ elif action.isFormat() and \
+ (action.isCreate() or action.isMigrate() or action.isResize()):
+ action.cancel()
+
+ self._actions.remove(action)
+
+ def findActions(self, device=None, type=None, object=None, path=None,
+ devid=None):
+ """ Find all actions that match all specified parameters.
+
+ Keyword arguments:
+
+ device -- device to match (Device, or None to match any)
+ type -- action type to match (string, or None to match any)
+ object -- operand type to match (string, or None to match any)
+ path -- device path to match (string, or None to match any)
+
+ """
+ if device is None and type is None and object is None and \
+ path is None and devid is None:
+ return self._actions[:]
+
+ # convert the string arguments to the types used in actions
+ _type = action_type_from_string(type)
+ _object = action_object_from_string(object)
+
+ actions = []
+ for action in self._actions:
+ if device is not None and action.device != device:
+ continue
+
+ if _type is not None and action.type != _type:
+ continue
+
+ if _object is not None and action.obj != _object:
+ continue
+
+ if path is not None and action.device.path != path:
+ continue
+
+ if devid is not None and action.device.id != devid:
+ continue
+
+ actions.append(action)
+
+ return actions
+
+ def getDependentDevices(self, dep):
+ """ Return a list of devices that depend on dep.
+
+ The list includes both direct and indirect dependents.
+ """
+ dependents = []
+
+ # special handling for extended partitions since the logical
+ # partitions and their deps effectively depend on the extended
+ logicals = []
+ if isinstance(dep, PartitionDevice) and dep.partType and \
+ dep.isExtended:
+ # collect all of the logicals on the same disk
+ for part in self.getDevicesByInstance(PartitionDevice):
+ if part.partType and part.isLogical and part.disk == dep.disk:
+ logicals.append(part)
+
+ for device in self.devices:
+ if device.dependsOn(dep):
+ dependents.append(device)
+ else:
+ for logical in logicals:
+ if device.dependsOn(logical):
+ dependents.append(device)
+ break
+
+ return dependents
+
+ def isIgnored(self, info):
+ """ Return True if info is a device we should ignore.
+
+ Arguments:
+
+ info -- a dict representing a udev db entry
+
+ TODO:
+
+ - filtering of SAN/FC devices
+ - filtering by driver?
+
+ """
+ sysfs_path = udev_device_get_sysfs_path(info)
+ name = udev_device_get_name(info)
+ if not sysfs_path:
+ return None
+
+ if name in self._ignoredDisks:
+ return True
+
+ # Special handling for mdraid external metadata sets (mdraid BIOSRAID):
+ # 1) The containers are intermediate devices which will never be
+ # in exclusiveDisks
+ # 2) Sets get added to exclusive disks with their dmraid set name by
+ # the filter ui. Note that making the ui use md names instead is not
+ # possible as the md names are simpy md# and we cannot predict the #
+ if udev_device_get_md_level(info) == "container":
+ return False
+
+ if udev_device_get_md_container(info) and \
+ udev_device_get_md_name(info):
+ md_name = udev_device_get_md_name(info)
+ for i in range(0, len(self.exclusiveDisks)):
+ if re.match("isw_[a-z]*_%s" % md_name, self.exclusiveDisks[i]):
+ self.exclusiveDisks[i] = name
+ return False
+
+ if udev_device_is_disk(info) and \
+ not udev_device_is_md(info) and \
+ not udev_device_is_dm(info) and \
+ not udev_device_is_biosraid(info) and \
+ not udev_device_is_multipath_member(info):
+ if self.exclusiveDisks and name not in self.exclusiveDisks:
+ self.addIgnoredDisk(name)
+ return True
+
+ # Ignore loop and ram devices, we normally already skip these in
+ # udev.py: enumerate_block_devices(), but we can still end up trying
+ # to add them to the tree when they are slaves of other devices, this
+ # happens for example with the livecd
+ if name.startswith("loop") or name.startswith("ram"):
+ return True
+
+ # FIXME: check for virtual devices whose slaves are on the ignore list
+
+ def addUdevDMDevice(self, info):
+ name = udev_device_get_name(info)
+ log_method_call(self, name=name)
+ uuid = udev_device_get_uuid(info)
+ sysfs_path = udev_device_get_sysfs_path(info)
+ device = None
+
+ for dmdev in self.devices:
+ if not isinstance(dmdev, DMDevice):
+ continue
+
+ try:
+ # there is a device in the tree already with the same
+ # major/minor as this one but with a different name
+ # XXX this is kind of racy
+ if dmdev.getDMNode() == os.path.basename(sysfs_path):
+ # XXX should we take the name already in use?
+ device = dmdev
+ break
+ except DMError:
+ # This is a little lame, but the VG device is a DMDevice
+ # and it won't have a dm node. At any rate, this is not
+ # important enough to crash the install.
+ log.debug("failed to find dm node for %s" % dmdev.name)
+ continue
+
+ if device is None:
+ # we couldn't find it, so create it
+ # first, get a list of the slave devs and look them up
+ slaves = []
+ dir = os.path.normpath("/sys/%s/slaves" % sysfs_path)
+ slave_names = os.listdir(dir)
+ for slave_name in slave_names:
+ # if it's a dm-X name, resolve it to a map name first
+ if slave_name.startswith("dm-"):
+ dev_name = dm.name_from_dm_node(slave_name)
+ else:
+ dev_name = slave_name
+ slave_dev = self.getDeviceByName(dev_name)
+ if slave_dev:
+ slaves.append(slave_dev)
+ else:
+ # we haven't scanned the slave yet, so do it now
+ path = os.path.normpath("%s/%s" % (dir, slave_name))
+ new_info = udev_get_block_device(os.path.realpath(path)[4:])
+ if new_info:
+ self.addUdevDevice(new_info)
+ if self.getDeviceByName(dev_name) is None:
+ # if the current slave is still not in
+ # the tree, something has gone wrong
+ log.error("failure scanning device %s: could not add slave %s" % (name, dev_name))
+ return
+
+ # try to get the device again now that we've got all the slaves
+ device = self.getDeviceByName(name)
+
+ if device is None:
+ if udev_device_is_multipath_partition(info, self):
+ diskname = udev_device_get_multipath_partition_disk(info)
+ disk = self.getDeviceByName(diskname)
+ return self.addUdevPartitionDevice(info, disk=disk)
+ elif udev_device_is_dmraid_partition(info, self):
+ diskname = udev_device_get_dmraid_partition_disk(info)
+ disk = self.getDeviceByName(diskname)
+ return self.addUdevPartitionDevice(info, disk=disk)
+
+ # if we get here, we found all of the slave devices and
+ # something must be wrong -- if all of the slaves are in
+ # the tree, this device should be as well
+ if device is None:
+ log.warning("ignoring dm device %s" % name)
+
+ return device
+
+ def addUdevMDDevice(self, info):
+ name = udev_device_get_name(info)
+ log_method_call(self, name=name)
+ uuid = udev_device_get_uuid(info)
+ sysfs_path = udev_device_get_sysfs_path(info)
+ device = None
+
+ slaves = []
+ dir = os.path.normpath("/sys/%s/slaves" % sysfs_path)
+ slave_names = os.listdir(dir)
+ for slave_name in slave_names:
+ # if it's a dm-X name, resolve it to a map name
+ if slave_name.startswith("dm-"):
+ dev_name = dm.name_from_dm_node(slave_name)
+ else:
+ dev_name = slave_name
+ slave_dev = self.getDeviceByName(dev_name)
+ if slave_dev:
+ slaves.append(slave_dev)
+ else:
+ # we haven't scanned the slave yet, so do it now
+ path = os.path.normpath("%s/%s" % (dir, slave_name))
+ new_info = udev_get_block_device(os.path.realpath(path)[4:])
+ if new_info:
+ self.addUdevDevice(new_info)
+ if self.getDeviceByName(dev_name) is None:
+ # if the current slave is still not in
+ # the tree, something has gone wrong
+ log.error("failure scanning device %s: could not add slave %s" % (name, dev_name))
+ return
+
+ # try to get the device again now that we've got all the slaves
+ device = self.getDeviceByName(name)
+
+ # if we get here, we found all of the slave devices and
+ # something must be wrong -- if all of the slaves we in
+ # the tree, this device should be as well
+ if device is None:
+ log.warning("using MD RAID device for %s" % name)
+ try:
+ # level is reported as, eg: "raid1"
+ md_level = udev_device_get_md_level(info)
+ md_devices = int(udev_device_get_md_devices(info))
+ md_uuid = udev_device_get_md_uuid(info)
+ except (KeyError, IndexError, ValueError) as e:
+ log.warning("invalid data for %s: %s" % (name, e))
+ return
+
+ device = MDRaidArrayDevice(name,
+ level=md_level,
+ memberDevices=md_devices,
+ uuid=md_uuid,
+ exists=True,
+ parents=slaves)
+ self._addDevice(device)
+
+ return device
+
+ def addUdevPartitionDevice(self, info, disk=None):
+ name = udev_device_get_name(info)
+ log_method_call(self, name=name)
+ uuid = udev_device_get_uuid(info)
+ sysfs_path = udev_device_get_sysfs_path(info)
+ device = None
+
+ if disk is None:
+ disk_name = os.path.basename(os.path.dirname(sysfs_path))
+ disk_name = disk_name.replace('!','/')
+ disk = self.getDeviceByName(disk_name)
+
+ if disk is None:
+ # create a device instance for the disk
+ new_info = udev_get_block_device(os.path.dirname(sysfs_path))
+ if new_info:
+ self.addUdevDevice(new_info)
+ disk = self.getDeviceByName(disk_name)
+
+ if disk is None:
+ # if the current device is still not in
+ # the tree, something has gone wrong
+ log.error("failure scanning device %s" % disk_name)
+ lvm.lvm_cc_addFilterRejectRegexp(name)
+ return
+
+ # Check that the disk has partitions. If it does not, we must have
+ # reinitialized the disklabel.
+ #
+ # Also ignore partitions on devices we do not support partitioning
+ # of, like logical volumes.
+ if not getattr(disk.format, "partitions", None) or \
+ not disk.partitionable:
+ # When we got here because the disk does not have a disklabel
+ # format (ie a biosraid member), or because it is not
+ # partitionable we want LVM to ignore this partition too
+ if disk.format.type != "disklabel" or not disk.partitionable:
+ lvm.lvm_cc_addFilterRejectRegexp(name)
+ log.debug("ignoring partition %s" % name)
+ return
+
+ try:
+ device = PartitionDevice(name, sysfsPath=sysfs_path,
+ major=udev_device_get_major(info),
+ minor=udev_device_get_minor(info),
+ exists=True, parents=[disk])
+ except DeviceError:
+ # corner case sometime the kernel accepts a partition table
+ # which gets rejected by parted, in this case we will
+ # prompt to re-initialize the disk, so simply skip the
+ # faulty partitions.
+ return
+
+ self._addDevice(device)
+ return device
+
+ def addUdevDiskDevice(self, info):
+ name = udev_device_get_name(info)
+ log_method_call(self, name=name)
+ uuid = udev_device_get_uuid(info)
+ sysfs_path = udev_device_get_sysfs_path(info)
+ serial = udev_device_get_serial(info)
+ bus = udev_device_get_bus(info)
+
+ # udev doesn't always provide a vendor.
+ vendor = udev_device_get_vendor(info)
+ if not vendor:
+ vendor = ""
+
+ device = None
+
+ kwargs = { "serial": serial, "vendor": vendor, "bus": bus }
+ if udev_device_is_iscsi(info):
+ diskType = iScsiDiskDevice
+ kwargs["node"] = self.iscsi.getNode(
+ udev_device_get_iscsi_name(info),
+ udev_device_get_iscsi_address(info),
+ udev_device_get_iscsi_port(info))
+ kwargs["ibft"] = kwargs["node"] in self.iscsi.ibftNodes
+ kwargs["initiator"] = self.iscsi.initiator
+ log.debug("%s is an iscsi disk" % name)
+ elif udev_device_is_fcoe(info):
+ diskType = FcoeDiskDevice
+ kwargs["nic"] = udev_device_get_fcoe_nic(info)
+ kwargs["identifier"] = udev_device_get_fcoe_identifier(info)
+ log.debug("%s is an fcoe disk" % name)
+ elif udev_device_get_md_container(info):
+ diskType = MDRaidArrayDevice
+ parentName = devicePathToName(udev_device_get_md_container(info))
+ kwargs["parents"] = [ self.getDeviceByName(parentName) ]
+ kwargs["level"] = udev_device_get_md_level(info)
+ kwargs["memberDevices"] = int(udev_device_get_md_devices(info))
+ kwargs["uuid"] = udev_device_get_md_uuid(info)
+ kwargs["exists"] = True
+ del kwargs["serial"]
+ del kwargs["vendor"]
+ del kwargs["bus"]
+ elif udev_device_is_dasd(info):
+ diskType = DASDDevice
+ kwargs["dasd"] = self.dasd
+ kwargs["busid"] = udev_device_get_dasd_bus_id(info)
+ kwargs["opts"] = {}
+
+ for attr in ['readonly', 'use_diag', 'erplog', 'failfast']:
+ kwargs["opts"][attr] = udev_device_get_dasd_flag(info, attr)
+
+ log.debug("%s is a dasd device" % name)
+ elif udev_device_is_zfcp(info):
+ diskType = ZFCPDiskDevice
+
+ for attr in ['hba_id', 'wwpn', 'fcp_lun']:
+ kwargs[attr] = udev_device_get_zfcp_attribute(info, attr=attr)
+
+ log.debug("%s is a zfcp device" % name)
+ else:
+ diskType = DiskDevice
+ log.debug("%s is a disk" % name)
+
+ device = diskType(name,
+ major=udev_device_get_major(info),
+ minor=udev_device_get_minor(info),
+ sysfsPath=sysfs_path, **kwargs)
+ self._addDevice(device)
+ return device
+
+ def addUdevOpticalDevice(self, info):
+ log_method_call(self)
+ # XXX should this be RemovableDevice instead?
+ #
+ # Looks like if it has ID_INSTANCE=0:1 we can ignore it.
+ device = OpticalDevice(udev_device_get_name(info),
+ major=udev_device_get_major(info),
+ minor=udev_device_get_minor(info),
+ sysfsPath=udev_device_get_sysfs_path(info),
+ vendor=udev_device_get_vendor(info),
+ model=udev_device_get_model(info))
+ self._addDevice(device)
+ return device
+
+ def addUdevDevice(self, info):
+ name = udev_device_get_name(info)
+ log_method_call(self, name=name, info=info)
+ uuid = udev_device_get_uuid(info)
+ sysfs_path = udev_device_get_sysfs_path(info)
+
+ if self.isIgnored(info):
+ log.debug("ignoring %s (%s)" % (name, sysfs_path))
+ return
+
+ log.debug("scanning %s (%s)..." % (name, sysfs_path))
+ device = self.getDeviceByName(name)
+
+ #
+ # The first step is to either look up or create the device
+ #
+ if udev_device_is_multipath_member(info):
+ device = DiskDevice(name,
+ major=udev_device_get_major(info),
+ minor=udev_device_get_minor(info),
+ sysfsPath=sysfs_path, exists=True,
+ serial=udev_device_get_serial(info),
+ vendor=udev_device_get_vendor(info),
+ model=udev_device_get_model(info))
+ self._addDevice(device)
+ elif udev_device_is_dm(info) and \
+ devicelibs.dm.dm_is_multipath(info):
+ log.debug("%s is a multipath device" % name)
+ self.addUdevDMDevice(info)
+ elif udev_device_is_dm(info):
+ log.debug("%s is a device-mapper device" % name)
+ # try to look up the device
+ if device is None and uuid:
+ # try to find the device by uuid
+ device = self.getDeviceByUuid(uuid)
+
+ if device is None:
+ device = self.addUdevDMDevice(info)
+ elif udev_device_is_md(info):
+ log.debug("%s is an md device" % name)
+ if device is None and uuid:
+ # try to find the device by uuid
+ device = self.getDeviceByUuid(uuid)
+
+ if device is None:
+ device = self.addUdevMDDevice(info)
+ elif udev_device_is_cdrom(info):
+ log.debug("%s is a cdrom" % name)
+ if device is None:
+ device = self.addUdevOpticalDevice(info)
+ elif udev_device_is_biosraid(info) and udev_device_is_disk(info):
+ log.debug("%s is part of a biosraid" % name)
+ if device is None:
+ device = DiskDevice(name,
+ major=udev_device_get_major(info),
+ minor=udev_device_get_minor(info),
+ sysfsPath=sysfs_path, exists=True)
+ self._addDevice(device)
+ elif udev_device_is_disk(info):
+ if device is None:
+ device = self.addUdevDiskDevice(info)
+ elif udev_device_is_partition(info):
+ log.debug("%s is a partition" % name)
+ if device is None:
+ device = self.addUdevPartitionDevice(info)
+ else:
+ log.error("Unknown block device type for: %s" % name)
+ return
+
+ # If this device is protected, mark it as such now. Once the tree
+ # has been populated, devices' protected attribute is how we will
+ # identify protected devices.
+ if device and device.name in self.protectedDevNames:
+ device.protected = True
+
+ # Don't try to do format handling on drives without media or
+ # if we didn't end up with a device somehow.
+ if not device or not device.mediaPresent:
+ return
+
+ # now handle the device's formatting
+ self.handleUdevDeviceFormat(info, device)
+ log.debug("got device: %s" % device)
+ if device.format.type:
+ log.debug("got format: %s" % device.format)
+ device.originalFormat = device.format
+
+ def handleUdevDiskLabelFormat(self, info, device):
+ log_method_call(self, device=device.name)
+ if device.partitioned:
+ # this device is already set up
+ log.debug("disklabel format on %s already set up" % device.name)
+ return
+
+ try:
+ device.setup()
+ except Exception as e:
+ log.debug("setup of %s failed: %s" % (device.name, e))
+ log.warning("aborting disklabel handler for %s" % device.name)
+ return
+
+ # special handling for unsupported partitioned devices
+ if not device.partitionable:
+ try:
+ format = getFormat("disklabel",
+ device=device.path,
+ exists=True)
+ except InvalidDiskLabelError:
+ pass
+ else:
+ if format.partitions:
+ # parted's checks for disklabel presence are less than
+ # rigorous, so we will assume that detected disklabels
+ # with no partitions are spurious
+ device.format = format
+ return
+
+ # if the disk contains protected partitions we will not wipe the
+ # disklabel even if clearpart --initlabel was specified
+ if not self.clearPartDisks or device.name in self.clearPartDisks:
+ initlabel = self.reinitializeDisks
+ sysfs_path = udev_device_get_sysfs_path(info)
+ for protected in self.protectedDevNames:
+ # check for protected partition
+ _p = "/sys/%s/%s" % (sysfs_path, protected)
+ if os.path.exists(os.path.normpath(_p)):
+ initlabel = False
+ break
+
+ # check for protected partition on a device-mapper disk
+ disk_name = re.sub(r'p\d+$', '', protected)
+ if disk_name != protected and disk_name == device.name:
+ initlabel = False
+ break
+ else:
+ initlabel = False
+
+
+ if self.zeroMbr:
+ initcb = lambda: True
+ else:
+ path = device.path
+ description = device.description or device.model
+ bypath = os.path.basename(deviceNameToDiskByPath(path))
+ if bypath:
+ details = "\n\nDevice details:\n%s" % (bypath,)
+ else:
+ details = ""
+
+ initcb = lambda: self.intf.questionInitializeDisk(path,
+ description,
+ device.size,
+ details)
+
+ try:
+ format = getFormat("disklabel",
+ device=device.path,
+ exists=not initlabel)
+ except InvalidDiskLabelError:
+ # if there is preexisting formatting on the device we will
+ # use it instead of ignoring the device
+ if not self.zeroMbr and \
+ getFormat(udev_device_get_format(info)).type is not None:
+ return
+ # if we have a cb function use it. else we ignore the device.
+ if initcb is not None and initcb():
+ format = getFormat("disklabel",
+ device=device.path,
+ exists=False)
+ else:
+ self._removeDevice(device)
+ self.addIgnoredDisk(device.name)
+ return
+
+ if not format.exists:
+ # if we just initialized a disklabel we should schedule
+ # actions for destruction of the previous format and creation
+ # of the new one
+ self.registerAction(ActionDestroyFormat(device))
+ self.registerAction(ActionCreateFormat(device, format))
+
+ # If this is a mac-formatted disk we just initialized, make
+ # sure the partition table partition gets added to the device
+ # tree.
+ if device.format.partedDisk.type == "mac" and \
+ len(device.format.partitions) == 1:
+ name = device.format.partitions[0].getDeviceNodeName()
+ if not self.getDeviceByName(name):
+ partDevice = PartitionDevice(name, exists=True,
+ parents=[device])
+ self._addDevice(partDevice)
+
+ else:
+ device.format = format
+
+ def handleUdevLUKSFormat(self, info, device):
+ log_method_call(self, name=device.name, type=device.format.type)
+ if not device.format.uuid:
+ log.info("luks device %s has no uuid" % device.path)
+ return
+
+ # look up or create the mapped device
+ if not self.getDeviceByName(device.format.mapName):
+ passphrase = self.__luksDevs.get(device.format.uuid)
+ if passphrase:
+ device.format.passphrase = passphrase
+ else:
+ (passphrase, isglobal) = getLUKSPassphrase(self.intf,
+ device,
+ self.__passphrase)
+ if isglobal and device.format.status:
+ self.__passphrase = passphrase
+
+ luks_device = LUKSDevice(device.format.mapName,
+ parents=[device],
+ exists=True)
+ try:
+ luks_device.setup()
+ except (LUKSError, CryptoError, DeviceError) as e:
+ log.info("setup of %s failed: %s" % (device.format.mapName,
+ e))
+ device.removeChild()
+ else:
+ self._addDevice(luks_device)
+ else:
+ log.warning("luks device %s already in the tree"
+ % device.format.mapName)
+
+ def handleUdevLVMPVFormat(self, info, device):
+ log_method_call(self, name=device.name, type=device.format.type)
+ # lookup/create the VG and LVs
+ try:
+ vg_name = udev_device_get_vg_name(info)
+ except KeyError:
+ # no vg name means no vg -- we're done with this pv
+ return
+
+ vg_device = self.getDeviceByName(vg_name)
+ if vg_device:
+ vg_device._addDevice(device)
+ for lv in vg_device.lvs:
+ try:
+ lv.setup()
+ except DeviceError as (msg, name):
+ log.info("setup of %s failed: %s" % (lv.name, msg))
+ else:
+ try:
+ vg_uuid = udev_device_get_vg_uuid(info)
+ vg_size = udev_device_get_vg_size(info)
+ vg_free = udev_device_get_vg_free(info)
+ pe_size = udev_device_get_vg_extent_size(info)
+ pe_count = udev_device_get_vg_extent_count(info)
+ pe_free = udev_device_get_vg_free_extents(info)
+ pv_count = udev_device_get_vg_pv_count(info)
+ except (KeyError, ValueError) as e:
+ log.warning("invalid data for %s: %s" % (device.name, e))
+ return
+
+ vg_device = LVMVolumeGroupDevice(vg_name,
+ device,
+ uuid=vg_uuid,
+ size=vg_size,
+ free=vg_free,
+ peSize=pe_size,
+ peCount=pe_count,
+ peFree=pe_free,
+ pvCount=pv_count,
+ exists=True)
+ self._addDevice(vg_device)
+
+ try:
+ lv_names = udev_device_get_lv_names(info)
+ lv_uuids = udev_device_get_lv_uuids(info)
+ lv_sizes = udev_device_get_lv_sizes(info)
+ lv_attr = udev_device_get_lv_attr(info)
+ except KeyError as e:
+ log.warning("invalid data for %s: %s" % (device.name, e))
+ return
+
+ if not lv_names:
+ log.debug("no LVs listed for VG %s" % device.name)
+ return
+
+ # make a list of indices with snapshots at the end
+ indices = range(len(lv_names))
+ indices.sort(key=lambda i: lv_attr[i][0] in 'Ss')
+ for index in indices:
+ lv_name = lv_names[index]
+ name = "%s-%s" % (vg_name, lv_name)
+ if lv_attr[index][0] in 'Ss':
+ log.debug("found lvm snapshot volume '%s'" % name)
+ origin_name = devicelibs.lvm.lvorigin(vg_name, lv_name)
+ if not origin_name:
+ log.error("lvm snapshot '%s-%s' has unknown origin"
+ % (vg_name, lv_name))
+ continue
+
+ origin = self.getDeviceByName("%s-%s" % (vg_name,
+ origin_name))
+ if not origin:
+ log.warning("snapshot lv '%s' origin lv '%s-%s' "
+ "not found" % (name,
+ vg_name, origin_name))
+ continue
+
+ log.debug("adding %dMB to %s snapshot total"
+ % (lv_sizes[index], origin.name))
+ origin.snapshotSpace += lv_sizes[index]
+ continue
+ elif lv_attr[index][0] in 'Iil':
+ # skip mirror images and log volumes
+ continue
+
+ log_size = 0
+ if lv_attr[index][0] in 'Mm':
+ stripes = 0
+ # identify mirror stripes/copies and mirror logs
+ for (j, _lvname) in enumerate(lv_names):
+ if lv_attr[j][0] not in 'Iil':
+ continue
+
+ if _lvname == "[%s_mlog]" % lv_name:
+ log_size = lv_sizes[j]
+ elif _lvname.startswith("[%s_mimage_" % lv_name):
+ stripes += 1
+ else:
+ stripes = 1
+
+ lv_dev = self.getDeviceByName(name)
+ if lv_dev is None:
+ lv_uuid = lv_uuids[index]
+ lv_size = lv_sizes[index]
+ lv_device = LVMLogicalVolumeDevice(lv_name,
+ vg_device,
+ uuid=lv_uuid,
+ size=lv_size,
+ stripes=stripes,
+ logSize=log_size,
+ exists=True)
+ self._addDevice(lv_device)
+
+ try:
+ lv_device.setup()
+ except DeviceError as (msg, name):
+ log.info("setup of %s failed: %s"
+ % (lv_device.name, msg))
+
+ def handleUdevMDMemberFormat(self, info, device):
+ log_method_call(self, name=device.name, type=device.format.type)
+ # either look up or create the array device
+ name = udev_device_get_name(info)
+ sysfs_path = udev_device_get_sysfs_path(info)
+
+ if udev_device_is_biosraid(info):
+ # this will prevent display of the member devices in the UI
+ device.format.biosraid = True
+
+ md_array = self.getDeviceByUuid(device.format.mdUuid)
+ if device.format.mdUuid and md_array:
+ md_array._addDevice(device)
+ else:
+ # create the array with just this one member
+ # FIXME: why does this exact block appear twice?
+ try:
+ # level is reported as, eg: "raid1"
+ md_level = udev_device_get_md_level(info)
+ md_devices = int(udev_device_get_md_devices(info))
+ md_uuid = udev_device_get_md_uuid(info)
+ except (KeyError, ValueError) as e:
+ log.warning("invalid data for %s: %s" % (name, e))
+ return
+
+ # try to name the array based on the preferred minor
+ md_info = devicelibs.mdraid.mdexamine(device.path)
+ md_path = md_info.get("device", "")
+ md_name = devicePathToName(md_info.get("device", ""))
+ if md_name:
+ try:
+ minor = int(md_name[2:]) # strip off leading "md"
+ except (IndexError, ValueError):
+ minor = None
+ md_name = None
+ else:
+ array = self.getDeviceByName(md_name)
+ if array and array.uuid != md_uuid:
+ md_name = None
+
+ if not md_name:
+ # if we don't have a name yet, find the first unused minor
+ minor = 0
+ while True:
+ if self.getDeviceByName("md%d" % minor):
+ minor += 1
+ else:
+ break
+
+ md_name = "md%d" % minor
+
+ log.debug("using name %s for md array containing member %s"
+ % (md_name, device.name))
+ md_array = MDRaidArrayDevice(md_name,
+ level=md_level,
+ minor=minor,
+ memberDevices=md_devices,
+ uuid=md_uuid,
+ sysfsPath=sysfs_path,
+ exists=True)
+ md_array._addDevice(device)
+ self._addDevice(md_array)
+
+ def handleMultipathMemberFormat(self, info, device):
+ log_method_call(self, name=device.name, type=device.format.type)
+
+ name = udev_device_get_multipath_name(info)
+ if self.__multipaths.has_key(name):
+ mp = self.__multipaths[name]
+ mp.addParent(device)
+ else:
+ mp = MultipathDevice(name, info, parents=[device])
+ self.__multipaths[name] = mp
+
+ def handleUdevDMRaidMemberFormat(self, info, device):
+ log_method_call(self, name=device.name, type=device.format.type)
+ name = udev_device_get_name(info)
+ sysfs_path = udev_device_get_sysfs_path(info)
+ uuid = udev_device_get_uuid(info)
+ major = udev_device_get_major(info)
+ minor = udev_device_get_minor(info)
+
+ def _all_ignored(rss):
+ retval = True
+ for rs in rss:
+ if rs.name not in self._ignoredDisks:
+ retval = False
+ break
+ return retval
+
+ # Have we already created the DMRaidArrayDevice?
+ rss = block.getRaidSetFromRelatedMem(uuid=uuid, name=name,
+ major=major, minor=minor)
+ if len(rss) == 0:
+ # we ignore the device in the hope that all the devices
+ # from this set will be ignored.
+ self.unusedRaidMembers.append(device.name)
+ self.addIgnoredDisk(device.name)
+ return
+
+ # We ignore the device if all the rss are in self._ignoredDisks
+ if _all_ignored(rss):
+ self.addIgnoredDisk(device.name)
+ return
+
+ for rs in rss:
+ dm_array = self.getDeviceByName(rs.name)
+ if dm_array is not None:
+ # We add the new device.
+ dm_array._addDevice(device)
+ else:
+ # Activate the Raid set.
+ rs.activate(mknod=True)
+ dm_array = DMRaidArrayDevice(rs.name,
+ raidSet=rs,
+ parents=[device])
+
+ self._addDevice(dm_array)
+
+ # Wait for udev to scan the just created nodes, to avoid a race
+ # with the udev_get_block_device() call below.
+ udev_settle()
+
+ # Get the DMRaidArrayDevice a DiskLabel format *now*, in case
+ # its partitions get scanned before it does.
+ dm_array.updateSysfsPath()
+ dm_array_info = udev_get_block_device(dm_array.sysfsPath)
+ self.handleUdevDiskLabelFormat(dm_array_info, dm_array)
+
+ # Use the rs's object on the device.
+ # pyblock can return the memebers of a set and the
+ # device has the attribute to hold it. But ATM we
+ # are not really using it. Commenting this out until
+ # we really need it.
+ #device.format.raidmem = block.getMemFromRaidSet(dm_array,
+ # major=major, minor=minor, uuid=uuid, name=name)
+
+ def handleUdevDeviceFormat(self, info, device):
+ log_method_call(self, name=getattr(device, "name", None))
+ name = udev_device_get_name(info)
+ sysfs_path = udev_device_get_sysfs_path(info)
+ uuid = udev_device_get_uuid(info)
+ label = udev_device_get_label(info)
+ format_type = udev_device_get_format(info)
+ serial = udev_device_get_serial(info)
+
+ # Now, if the device is a disk, see if there is a usable disklabel.
+ # If not, see if the user would like to create one.
+ # XXX ignore disklabels on multipath or biosraid member disks
+ if not udev_device_is_biosraid(info) and \
+ not udev_device_is_multipath_member(info):
+ self.handleUdevDiskLabelFormat(info, device)
+ if device.partitioned or self.isIgnored(info) or \
+ (not device.partitionable and
+ device.format.type == "disklabel"):
+ # If the device has a disklabel, or the user chose not to
+ # create one, we are finished with this device. Otherwise
+ # it must have some non-disklabel formatting, in which case
+ # we fall through to handle that.
+ return
+
+ format = None
+ if (not device) or (not format_type) or device.format.type:
+ # this device has no formatting or it has already been set up
+ # FIXME: this probably needs something special for disklabels
+ log.debug("no type or existing type for %s, bailing" % (name,))
+ return
+
+ # set up the common arguments for the format constructor
+ args = [format_type]
+ kwargs = {"uuid": uuid,
+ "label": label,
+ "device": device.path,
+ "serial": serial,
+ "exists": True}
+
+ # set up type-specific arguments for the format constructor
+ if format_type == "multipath_member":
+ kwargs["multipath_members"] = self.getDevicesBySerial(serial)
+ elif format_type == "crypto_LUKS":
+ # luks/dmcrypt
+ kwargs["name"] = "luks-%s" % uuid
+ elif format_type in formats.mdraid.MDRaidMember._udevTypes:
+ # mdraid
+ try:
+ kwargs["mdUuid"] = udev_device_get_md_uuid(info)
+ except KeyError:
+ log.debug("mdraid member %s has no md uuid" % name)
+ elif format_type == "LVM2_member":
+ # lvm
+ try:
+ kwargs["vgName"] = udev_device_get_vg_name(info)
+ except KeyError as e:
+ log.debug("PV %s has no vg_name" % name)
+ try:
+ kwargs["vgUuid"] = udev_device_get_vg_uuid(info)
+ except KeyError:
+ log.debug("PV %s has no vg_uuid" % name)
+ try:
+ kwargs["peStart"] = udev_device_get_pv_pe_start(info)
+ except KeyError:
+ log.debug("PV %s has no pe_start" % name)
+ elif format_type == "vfat":
+ # efi magic
+ if isinstance(device, PartitionDevice) and device.bootable:
+ efi = formats.getFormat("efi")
+ if efi.minSize <= device.size <= efi.maxSize:
+ args[0] = "efi"
+ elif format_type == "hfs":
+ # apple bootstrap magic
+ if isinstance(device, PartitionDevice) and device.bootable:
+ apple = formats.getFormat("appleboot")
+ if apple.minSize <= device.size <= apple.maxSize:
+ args[0] = "appleboot"
+
+ try:
+ log.debug("type detected on '%s' is '%s'" % (name, format_type,))
+ device.format = formats.getFormat(*args, **kwargs)
+ except FSError:
+ log.debug("type '%s' on '%s' invalid, assuming no format" %
+ (format_type, name,))
+ device.format = formats.DeviceFormat()
+ return
+
+ if shouldClear(device, self.clearPartType,
+ clearPartDisks=self.clearPartDisks):
+ # if this is a device that will be cleared by clearpart,
+ # don't bother with format-specific processing
+ return
+
+ #
+ # now do any special handling required for the device's format
+ #
+ if device.format.type == "luks":
+ self.handleUdevLUKSFormat(info, device)
+ elif device.format.type == "mdmember":
+ self.handleUdevMDMemberFormat(info, device)
+ elif device.format.type == "dmraidmember":
+ self.handleUdevDMRaidMemberFormat(info, device)
+ elif device.format.type == "lvmpv":
+ self.handleUdevLVMPVFormat(info, device)
+ elif device.format.type == "multipath_member":
+ self.handleMultipathMemberFormat(info, device)
+
+ def updateDeviceFormat(self, device):
+ log.debug("updating format of device: %s" % device)
+ iutil.notify_kernel("/sys%s" % device.sysfsPath)
+ udev_settle()
+ info = udev_get_device(device.sysfsPath)
+ self.handleUdevDeviceFormat(info, device)
+ if device.format.type:
+ log.debug("got format: %s" % device.format)
+
+ def _handleInconsistencies(self):
+ def reinitializeVG(vg):
+ # First we remove VG data
+ try:
+ vg.destroy()
+ except DeviceError:
+ # the pvremoves will finish the job.
+ log.debug("There was an error destroying the VG %s." % vg.name)
+
+ # remove VG device from list.
+ self._removeDevice(vg)
+
+ for parent in vg.parents:
+ parent.format.destroy()
+
+ # Give the vg the a default format
+ kwargs = {"device": parent.path,
+ "exists": parent.exists}
+ parent.format = formats.getFormat(*[""], **kwargs)
+
+ def leafInconsistencies(device):
+ if device.type == "lvmvg":
+ if device.complete:
+ return
+
+ paths = []
+ for parent in device.parents:
+ paths.append(parent.path)
+
+ # if zeroMbr is true don't ask.
+ if (self.zeroMbr or
+ self.intf.questionReinitInconsistentLVM(pv_names=paths,
+ vg_name=device.name)):
+ reinitializeVG(device)
+ else:
+ # The user chose not to reinitialize.
+ # hopefully this will ignore the vg components too.
+ self._removeDevice(device)
+ lvm.lvm_cc_addFilterRejectRegexp(device.name)
+ lvm.blacklistVG(device.name)
+ for parent in device.parents:
+ if parent.type == "partition":
+ self.immutableDevices.append([parent.name,
+ _("This partition is part of an inconsistent LVM Volume Group.")])
+ else:
+ self._removeDevice(parent, moddisk=False)
+ self.addIgnoredDisk(parent.name)
+ lvm.lvm_cc_addFilterRejectRegexp(parent.name)
+
+ elif device.type == "lvmlv":
+ # we might have already fixed this.
+ if device not in self._devices or \
+ device.name in self._ignoredDisks:
+ return
+ if device.complete:
+ return
+
+ paths = []
+ for parent in device.vg.parents:
+ paths.append(parent.path)
+
+ if (self.zeroMbr or
+ self.intf.questionReinitInconsistentLVM(pv_names=paths,
+ lv_name=device.name)):
+
+ # destroy all lvs.
+ for lv in device.vg.lvs:
+ try:
+ # reinitializeVG should clean up if necessary
+ lv.destroy()
+ except StorageError as e:
+ log.info("error removing lv %s from "
+ "inconsistent/incomplete vg %s"
+ % (lv.lvname, device.vg.name))
+ device.vg._removeLogVol(lv)
+ self._removeDevice(lv)
+
+ reinitializeVG(device.vg)
+ else:
+ # ignore all the lvs.
+ for lv in device.vg.lvs:
+ self._removeDevice(lv)
+ lvm.lvm_cc_addFilterRejectRegexp(lv.name)
+ # ignore the vg
+ self._removeDevice(device.vg)
+ lvm.lvm_cc_addFilterRejectRegexp(device.vg.name)
+ lvm.blacklistVG(device.vg.name)
+ # ignore all the pvs
+ for parent in device.vg.parents:
+ if parent.type == "partition":
+ self.immutableDevices.append([parent.name,
+ _("This partition is part of an inconsistent LVM Volume Group.")])
+ else:
+ self._removeDevice(parent, moddisk=False)
+ self.addIgnoredDisk(parent.name)
+ lvm.lvm_cc_addFilterRejectRegexp(parent.name)
+
+ # Address the inconsistencies present in the tree leaves.
+ for leaf in self.leaves:
+ leafInconsistencies(leaf)
+
+ # Check for unused BIOS raid members, unused dmraid members are added
+ # to self.unusedRaidMembers as they are processed, extend this list
+ # with unused mdraid BIOS raid members
+ for c in self.getDevicesByType("mdcontainer"):
+ if c.kids == 0:
+ self.unusedRaidMembers.extend(map(lambda m: m.name, c.devices))
+
+ self.intf.unusedRaidMembersWarning(self.unusedRaidMembers)
+
+ def populate(self):
+ """ Locate all storage devices. """
+
+ # mark the tree as unpopulated so exception handlers can tell the
+ # exception originated while finding storage devices
+ self.populated = False
+
+ # resolve the protected device specs to device names
+ for spec in self.protectedDevSpecs:
+ name = udev_resolve_devspec(spec)
+ if name:
+ self.protectedDevNames.append(name)
+
+ # FIXME: the backing dev for the live image can't be used as an
+ # install target. note that this is a little bit of a hack
+ # since we're assuming that /dev/live will exist
+ if os.path.exists("/dev/live") and \
+ stat.S_ISBLK(os.stat("/dev/live")[stat.ST_MODE]):
+ livetarget = devicePathToName(os.path.realpath("/dev/live"))
+ log.info("%s looks to be the live device; marking as protected"
+ % (livetarget,))
+ self.protectedDevNames.append(livetarget)
+
+ # First iteration - let's just look for disks.
+ old_devices = {}
+
+ devices = udev_get_block_devices()
+ for dev in devices:
+ old_devices[dev['name']] = dev
+
+ cfg = self.__multipathConfigWriter.write()
+ open("/etc/multipath.conf", "w+").write(cfg)
+ del cfg
+
+ (singles, mpaths, partitions) = devicelibs.mpath.identifyMultipaths(devices)
+ devices = singles + reduce(list.__add__, mpaths, []) + partitions
+ log.info("devices to scan: %s" % [d['name'] for d in devices])
+ for dev in devices:
+ self.addUdevDevice(dev)
+
+ # Having found all the disks, we can now find all the multipaths built
+ # upon them.
+ whitelist = []
+ mpaths = self.__multipaths.values()
+ mpaths.sort(key=lambda d: d.name)
+ for mp in mpaths:
+ log.info("adding mpath device %s" % mp.name)
+ mp.setup()
+ whitelist.append(mp.name)
+ for p in mp.parents:
+ whitelist.append(p.name)
+ self.__multipathConfigWriter.addMultipathDevice(mp)
+ self._addDevice(mp)
+ for d in self.devices:
+ if not d.name in whitelist:
+ self.__multipathConfigWriter.addBlacklistDevice(d)
+ cfg = self.__multipathConfigWriter.write()
+ open("/etc/multipath.conf", "w+").write(cfg)
+ del cfg
+
+ # Now, loop and scan for devices that have appeared since the two above
+ # blocks or since previous iterations.
+ while True:
+ devices = []
+ new_devices = udev_get_block_devices()
+
+ for new_device in new_devices:
+ if not old_devices.has_key(new_device['name']):
+ old_devices[new_device['name']] = new_device
+ devices.append(new_device)
+
+ if len(devices) == 0:
+ # nothing is changing -- we are finished building devices
+ break
+
+ log.info("devices to scan: %s" % [d['name'] for d in devices])
+ for dev in devices:
+ self.addUdevDevice(dev)
+
+ self.populated = True
+
+ # After having the complete tree we make sure that the system
+ # inconsistencies are ignored or resolved.
+ self._handleInconsistencies()
+
+ self.teardownAll()
+ try:
+ os.unlink("/etc/mdadm.conf")
+ except OSError:
+ log.info("failed to unlink /etc/mdadm.conf")
+
+ def teardownAll(self):
+ """ Run teardown methods on all devices. """
+ for device in self.leaves:
+ try:
+ device.teardown(recursive=True)
+ except StorageError as e:
+ log.info("teardown of %s failed: %s" % (device.name, e))
+
+ def setupAll(self):
+ """ Run setup methods on all devices. """
+ for device in self.leaves:
+ try:
+ device.setup()
+ except DeviceError as (msg, name):
+ log.debug("setup of %s failed: %s" % (device.name, msg))
+
+ def getDeviceBySysfsPath(self, path):
+ if not path:
+ return None
+
+ found = None
+ for device in self._devices:
+ if device.sysfsPath == path:
+ found = device
+ break
+
+ return found
+
+ def getDeviceByUuid(self, uuid):
+ if not uuid:
+ return None
+
+ found = None
+ for device in self._devices:
+ if device.uuid == uuid:
+ found = device
+ break
+ elif device.format.uuid == uuid:
+ found = device
+ break
+
+ return found
+
+ def getDevicesBySerial(self, serial):
+ devices = []
+ for device in self._devices:
+ if not hasattr(device, "serial"):
+ log.warning("device %s has no serial attr" % device.name)
+ continue
+ if device.serial == serial:
+ devices.append(device)
+ return devices
+
+ def getDeviceByLabel(self, label):
+ if not label:
+ return None
+
+ found = None
+ for device in self._devices:
+ _label = getattr(device.format, "label", None)
+ if not _label:
+ continue
+
+ if _label == label:
+ found = device
+ break
+
+ return found
+
+ def getDeviceByName(self, name):
+ log.debug("looking for device '%s'..." % name)
+ if not name:
+ return None
+
+ found = None
+ for device in self._devices:
+ if device.name == name:
+ found = device
+ break
+ elif (device.type == "lvmlv" or device.type == "lvmvg") and \
+ device.name == name.replace("--","-"):
+ found = device
+ break
+
+ log.debug("found %s" % found)
+ return found
+
+ def getDeviceByPath(self, path):
+ log.debug("looking for device '%s'..." % path)
+ if not path:
+ return None
+
+ found = None
+ for device in self._devices:
+ if device.path == path:
+ found = device
+ break
+ elif (device.type == "lvmlv" or device.type == "lvmvg") and \
+ device.path == path.replace("--","-"):
+ found = device
+ break
+
+ log.debug("found %s" % found)
+ return found
+
+ def getDevicesByType(self, device_type):
+ # TODO: expand this to catch device format types
+ return [d for d in self._devices if d.type == device_type]
+
+ def getDevicesByInstance(self, device_class):
+ return [d for d in self._devices if isinstance(d, device_class)]
+
+ @property
+ def devices(self):
+ """ List of device instances """
+ devices = []
+ for device in self._devices:
+ if device.path in [d.path for d in devices] and \
+ not isinstance(device, NoDevice):
+ raise DeviceTreeError("duplicate paths in device tree")
+
+ devices.append(device)
+
+ return devices
+
+ @property
+ def filesystems(self):
+ """ List of filesystems. """
+ #""" Dict with mountpoint keys and filesystem values. """
+ filesystems = []
+ for dev in self.leaves:
+ if dev.format and getattr(dev.format, 'mountpoint', None):
+ filesystems.append(dev.format)
+
+ return filesystems
+
+ @property
+ def uuids(self):
+ """ Dict with uuid keys and Device values. """
+ uuids = {}
+ for dev in self._devices:
+ try:
+ uuid = dev.uuid
+ except AttributeError:
+ uuid = None
+
+ if uuid:
+ uuids[uuid] = dev
+
+ try:
+ uuid = dev.format.uuid
+ except AttributeError:
+ uuid = None
+
+ if uuid:
+ uuids[uuid] = dev
+
+ return uuids
+
+ @property
+ def labels(self):
+ """ Dict with label keys and Device values.
+
+ FIXME: duplicate labels are a possibility
+ """
+ labels = {}
+ for dev in self._devices:
+ if dev.format and getattr(dev.format, "label", None):
+ labels[dev.format.label] = dev
+
+ return labels
+
+ @property
+ def leaves(self):
+ """ List of all devices upon which no other devices exist. """
+ leaves = [d for d in self._devices if d.isleaf]
+ return leaves
+
+ def getChildren(self, device):
+ """ Return a list of a device's children. """
+ return [c for c in self._devices if device in c.parents]
+
+ def resolveDevice(self, devspec, blkidTab=None, cryptTab=None):
+ # find device in the tree
+ device = None
+ if devspec.startswith("UUID="):
+ # device-by-uuid
+ uuid = devspec.partition("=")[2]
+ device = self.uuids.get(uuid)
+ if device is None:
+ log.error("failed to resolve device %s" % devspec)
+ elif devspec.startswith("LABEL="):
+ # device-by-label
+ label = devspec.partition("=")[2]
+ device = self.labels.get(label)
+ if device is None:
+ log.error("failed to resolve device %s" % devspec)
+ elif devspec.startswith("/dev/"):
+ # device path
+ device = self.getDeviceByPath(devspec)
+ if device is None:
+ if blkidTab:
+ # try to use the blkid.tab to correlate the device
+ # path with a UUID
+ blkidTabEnt = blkidTab.get(devspec)
+ if blkidTabEnt:
+ log.debug("found blkid.tab entry for '%s'" % devspec)
+ uuid = blkidTabEnt.get("UUID")
+ if uuid:
+ device = self.getDeviceByUuid(uuid)
+ if device:
+ devstr = device.name
+ else:
+ devstr = "None"
+ log.debug("found device '%s' in tree" % devstr)
+ if device and device.format and \
+ device.format.type == "luks":
+ map_name = device.format.mapName
+ log.debug("luks device; map name is '%s'" % map_name)
+ mapped_dev = self.getDeviceByName(map_name)
+ if mapped_dev:
+ device = mapped_dev
+
+ if device is None and cryptTab and \
+ devspec.startswith("/dev/mapper/"):
+ # try to use a dm-crypt mapping name to
+ # obtain the underlying device, possibly
+ # using blkid.tab
+ cryptTabEnt = cryptTab.get(devspec.split("/")[-1])
+ if cryptTabEnt:
+ luks_dev = cryptTabEnt['device']
+ try:
+ device = self.getChildren(luks_dev)[0]
+ except IndexError as e:
+ pass
+ elif device is None:
+ # dear lvm: can we please have a few more device nodes
+ # for each logical volume?
+ # three just doesn't seem like enough.
+ name = devspec[5:] # strip off leading "/dev/"
+ (vg_name, slash, lv_name) = name.partition("/")
+ if lv_name and not "/" in lv_name:
+ # looks like we may have one
+ lv = "%s-%s" % (vg_name, lv_name)
+ device = self.getDeviceByName(lv)
+
+ if device:
+ log.debug("resolved '%s' to '%s' (%s)" % (devspec, device.name, device.type))
+ else:
+ log.debug("failed to resolve '%s'" % devspec)
+ return device
diff --git a/storage/errors.py b/storage/errors.py
new file mode 100644
index 0000000..a0f5f60
--- /dev/null
+++ b/storage/errors.py
@@ -0,0 +1,150 @@
+# errors.py
+# Exception classes 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>
+#
+
+class StorageError(Exception):
+ pass
+
+# Device
+class DeviceError(StorageError):
+ pass
+
+class DeviceCreateError(DeviceError):
+ pass
+
+class DeviceDestroyError(DeviceError):
+ pass
+
+class DeviceResizeError(DeviceError):
+ pass
+
+class DeviceSetupError(DeviceError):
+ pass
+
+class DeviceTeardownError(DeviceError):
+ pass
+
+class DeviceUserDeniedFormatError(DeviceError):
+ pass
+
+# DeviceFormat
+class DeviceFormatError(StorageError):
+ pass
+
+class FormatCreateError(DeviceFormatError):
+ pass
+
+class FormatDestroyError(DeviceFormatError):
+ pass
+
+class FormatSetupError(DeviceFormatError):
+ pass
+
+class FormatTeardownError(DeviceFormatError):
+ pass
+
+class DMRaidMemberError(DeviceFormatError):
+ pass
+
+class MultipathMemberError(DeviceFormatError):
+ pass
+
+class FSError(DeviceFormatError):
+ pass
+
+class FSResizeError(FSError):
+ pass
+
+class FSMigrateError(FSError):
+ pass
+
+class LUKSError(DeviceFormatError):
+ pass
+
+class MDMemberError(DeviceFormatError):
+ pass
+
+class PhysicalVolumeError(DeviceFormatError):
+ pass
+
+class SwapSpaceError(DeviceFormatError):
+ pass
+
+class DiskLabelError(DeviceFormatError):
+ pass
+
+class InvalidDiskLabelError(DiskLabelError):
+ pass
+
+class DiskLabelCommitError(DiskLabelError):
+ pass
+
+# devicelibs
+class SwapError(StorageError):
+ pass
+
+class SuspendError(SwapError):
+ pass
+
+class OldSwapError(SwapError):
+ pass
+
+class UnknownSwapError(SwapError):
+ pass
+
+class MDRaidError(StorageError):
+ pass
+
+class DMError(StorageError):
+ pass
+
+class LVMError(StorageError):
+ pass
+
+class CryptoError(StorageError):
+ pass
+
+class MPathError(StorageError):
+ pass
+
+# DeviceTree
+class DeviceTreeError(StorageError):
+ pass
+
+# DeviceAction
+class DeviceActionError(StorageError):
+ pass
+
+# partitioning
+class PartitioningError(StorageError):
+ pass
+
+class PartitioningWarning(StorageError):
+ pass
+
+# udev
+class UdevError(StorageError):
+ pass
+
+# fstab
+class UnrecognizedFSTabEntryError(StorageError):
+ pass
+
diff --git a/storage/fcoe.py b/storage/fcoe.py
new file mode 100644
index 0000000..cd52bc8
--- /dev/null
+++ b/storage/fcoe.py
@@ -0,0 +1,172 @@
+#
+# fcoe.py - fcoe class
+#
+# Copyright (C) 2009 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import iutil
+import isys
+import logging
+import time
+from flags import flags
+log = logging.getLogger("anaconda")
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+_fcoe_module_loaded = False
+
+def has_fcoe():
+ global _fcoe_module_loaded
+ if not _fcoe_module_loaded:
+ iutil.execWithRedirect("modprobe", [ "fcoe" ],
+ stdout = "/dev/tty5", stderr="/dev/tty5")
+ _fcoe_module_loaded = True
+
+ return os.access("/sys/module/fcoe", os.X_OK)
+
+class fcoe(object):
+ """ FCoE utility class.
+
+ This class will automatically discover and connect to EDD configured
+ FCoE SAN's when the startup() method gets called. It can also be
+ used to manually configure FCoE SAN's through the addSan() method.
+
+ As this class needs to make sure certain things like starting fcoe
+ daemons and connecting to firmware discovered SAN's only happens once
+ and as it keeps a global list of all FCoE devices it is
+ implemented as a Singleton.
+ """
+
+ def __init__(self):
+ self.started = False
+ self.lldpadStarted = False
+ self.fcoemonStarted = False
+ self.nics = []
+
+ # So that users can write fcoe() to get the singleton instance
+ def __call__(self):
+ return self
+
+ def _stabilize(self, intf = None):
+ if intf:
+ w = intf.waitWindow(_("Connecting to FCoE SAN"),
+ _("Connecting to FCoE SAN"))
+
+ # I have no clue how long we need to wait, this ought to do the trick
+ time.sleep(10)
+ iutil.execWithRedirect("udevadm", [ "settle" ],
+ stdout = "/dev/tty5", stderr="/dev/tty5")
+ if intf:
+ w.pop()
+
+ def _startEDD(self, intf = None):
+ rc = iutil.execWithCapture("/usr/libexec/fcoe/fcoe_edd.sh", [ "-i" ],
+ stderr="/dev/tty5")
+ if not rc.startswith("NIC="):
+ log.info("No FCoE EDD info found: %s" % rc)
+ return
+
+ (key, val) = rc.split("=", 1)
+ if val not in isys.getDeviceProperties():
+ log.error("Unknown FCoE NIC found in EDD: %s, ignoring" % val)
+ return
+
+ log.info("FCoE NIC found in EDD: %s" % val)
+ self.addSan(val, dcb=True, intf=intf)
+
+ def startup(self, intf = None):
+ if self.started:
+ return
+
+ if not has_fcoe():
+ return
+
+ self._startEDD(intf)
+ self.started = True
+
+ def _startLldpad(self):
+ if self.lldpadStarted:
+ return
+
+ iutil.execWithRedirect("lldpad", [ "-d" ],
+ stdout = "/dev/tty5", stderr="/dev/tty5")
+ self.lldpadStarted = True
+
+ def _startFcoemon(self):
+ if self.fcoemonStarted:
+ return
+
+ iutil.execWithRedirect("fcoemon", [ ],
+ stdout = "/dev/tty5", stderr="/dev/tty5")
+ self.fcoemonStarted = True
+
+ def addSan(self, nic, dcb=False, intf=None):
+ if not has_fcoe():
+ raise IOError, _("FCoE not available")
+
+ log.info("Activating FCoE SAN attached to %s, dcb: %s" % (nic, dcb))
+
+ iutil.execWithRedirect("ip", [ "link", "set", nic, "up" ],
+ stdout = "/dev/tty5", stderr="/dev/tty5")
+
+ if dcb:
+ self._startLldpad()
+ iutil.execWithRedirect("dcbtool", [ "sc", nic, "dcb", "on" ],
+ stdout = "/dev/tty5", stderr="/dev/tty5")
+ iutil.execWithRedirect("dcbtool", [ "sc", nic, "app:fcoe",
+ "e:1", "a:1", "w:1" ],
+ stdout = "/dev/tty5", stderr="/dev/tty5")
+ self._startFcoemon()
+ else:
+ f = open("/sys/module/fcoe/parameters/create", "w")
+ f.write(nic)
+ f.close()
+
+ self._stabilize(intf)
+ self.nics.append((nic, dcb))
+
+ def writeKS(self, f):
+ # fixme plenty (including add ks support for fcoe in general)
+ return
+
+ def write(self, instPath, anaconda):
+ if not self.nics:
+ return
+
+ if not os.path.isdir(instPath + "/etc/fcoe"):
+ os.makedirs(instPath + "/etc/fcoe", 0755)
+
+ for nic, dcb in self.nics:
+ fd = os.open(instPath + "/etc/fcoe/cfg-" + nic,
+ os.O_RDWR | os.O_CREAT)
+ os.write(fd, '# Created by anaconda\n')
+ os.write(fd, '# Enable/Disable FCoE service at the Ethernet port\n')
+ os.write(fd, 'FCOE_ENABLE="yes"\n')
+ os.write(fd, '# Indicate if DCB service is required at the Ethernet port\n')
+ if dcb:
+ os.write(fd, 'DCB_REQUIRED="yes"\n')
+ else:
+ os.write(fd, 'DCB_REQUIRED="no"\n')
+ os.close(fd)
+
+ return
+
+# Create FCoE singleton
+fcoe = fcoe()
+
+# vim:tw=78:ts=4:et:sw=4
diff --git a/storage/formats/Makefile.am b/storage/formats/Makefile.am
new file mode 100644
index 0000000..7ecaf07
--- /dev/null
+++ b/storage/formats/Makefile.am
@@ -0,0 +1,24 @@
+# storage/formats/Makefile.am for anaconda
+#
+# Copyright (C) 2009 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: David Cantrell <dcantrell@redhat.com>
+
+pkgpyexecdir = $(pyexecdir)/py$(PACKAGE_NAME)
+storageformatsdir = $(pkgpyexecdir)/storage/formats
+storageformats_PYTHON = *.py
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/storage/formats/__init__.py b/storage/formats/__init__.py
new file mode 100644
index 0000000..8a8abd3
--- /dev/null
+++ b/storage/formats/__init__.py
@@ -0,0 +1,403 @@
+# __init__.py
+# Entry point for anaconda storage formats subpackage.
+#
+# 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
+
+from iutil import notify_kernel, get_sysfs_path_by_name
+from ..storage_log import log_method_call
+from ..errors import *
+from ..devicelibs.dm import dm_node_from_name
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+
+device_formats = {}
+def register_device_format(fmt_class):
+ if not issubclass(fmt_class, DeviceFormat):
+ raise ValueError("arg1 must be a subclass of DeviceFormat")
+
+ device_formats[fmt_class._type] = fmt_class
+ log.debug("registered device format class %s as %s" % (fmt_class.__name__,
+ fmt_class._type))
+
+default_fstypes = ("ext4", "ext3", "ext2")
+def get_default_filesystem_type(boot=None):
+ import pyanaconda.platform as platform
+
+ if boot:
+ fstypes = [platform.getPlatform(None).defaultBootFSType]
+ else:
+ fstypes = default_fstypes
+
+ for fstype in fstypes:
+ try:
+ supported = get_device_format_class(fstype).supported
+ except AttributeError:
+ supported = None
+
+ if supported:
+ return fstype
+
+ raise DeviceFormatError("None of %s is supported by your kernel" % ",".join(fstypes))
+
+def getFormat(fmt_type, *args, **kwargs):
+ """ Return a DeviceFormat instance based on fmt_type and args.
+
+ Given a device format type and a set of constructor arguments,
+ return a DeviceFormat instance.
+
+ Return None if no suitable format class is found.
+
+ Arguments:
+
+ fmt_type -- the name of the format type (eg: 'ext3', 'swap')
+
+ Keyword Arguments:
+
+ The keyword arguments may vary according to the format type,
+ but here is the common set:
+
+ device -- path to the device on which the format resides
+ uuid -- the UUID of the (preexisting) formatted device
+ exists -- whether or not the format exists on the device
+
+ """
+ fmt_class = get_device_format_class(fmt_type)
+ fmt = None
+ if fmt_class:
+ fmt = fmt_class(*args, **kwargs)
+ try:
+ className = fmt.__class__.__name__
+ except AttributeError:
+ className = None
+ log.debug("getFormat('%s') returning %s instance" % (fmt_type, className))
+ return fmt
+
+def collect_device_format_classes():
+ """ Pick up all device format classes from this directory.
+
+ Note: Modules must call register_device_format(FormatClass) in
+ order for the format class to be picked up.
+ """
+ dir = os.path.dirname(__file__)
+ for module_file in os.listdir(dir):
+ # make sure we're not importing this module
+ if module_file.endswith(".py") and module_file != __file__:
+ mod_name = module_file[:-3]
+ # imputil is deprecated in python 2.6
+ try:
+ globals()[mod_name] = __import__(mod_name, globals(), locals(), [], -1)
+ except ImportError, e:
+ log.debug("import of device format module '%s' failed" % mod_name)
+
+def get_device_format_class(fmt_type):
+ """ Return an appropriate format class based on fmt_type. """
+ if not device_formats:
+ collect_device_format_classes()
+
+ fmt = device_formats.get(fmt_type)
+ if not fmt:
+ for fmt_class in device_formats.values():
+ if fmt_type and fmt_type == fmt_class._name:
+ fmt = fmt_class
+ break
+ elif fmt_type in fmt_class._udevTypes:
+ fmt = fmt_class
+ break
+
+ # default to no formatting, AKA "Unknown"
+ if not fmt:
+ fmt = DeviceFormat
+
+ return fmt
+
+class DeviceFormat(object):
+ """ Generic device format. """
+ _type = None
+ _name = "Unknown"
+ _udevTypes = []
+ partedFlag = None
+ partedSystem = None
+ _formattable = False # can be formatted
+ _supported = False # is supported
+ _linuxNative = False # for clearpart
+ _packages = [] # required packages
+ _resizable = False # can be resized
+ _bootable = False # can be used as boot
+ _migratable = False # can be migrated
+ _maxSize = 0 # maximum size in MB
+ _minSize = 0 # minimum size in MB
+ _dump = False
+ _check = False
+ _hidden = False # hide devices with this formatting?
+
+ def __init__(self, *args, **kwargs):
+ """ Create a DeviceFormat instance.
+
+ Keyword Arguments:
+
+ device -- path to the underlying device
+ uuid -- this format's UUID
+ exists -- indicates whether this is an existing format
+
+ """
+ self.device = kwargs.get("device")
+ self.uuid = kwargs.get("uuid")
+ self.exists = kwargs.get("exists")
+ self.options = kwargs.get("options")
+ self._migrate = False
+
+ # don't worry about existence if this is a DeviceFormat instance
+ #if self.__class__ is DeviceFormat:
+ # self.exists = True
+
+ def __str__(self):
+ s = ("%(classname)s instance (%(id)s) --\n"
+ " type = %(type)s name = %(name)s status = %(status)s\n"
+ " device = %(device)s uuid = %(uuid)s exists = %(exists)s\n"
+ " options = %(options)s supported = %(supported)s"
+ " formattable = %(format)s resizable = %(resize)s\n" %
+ {"classname": self.__class__.__name__, "id": "%#x" % id(self),
+ "type": self.type, "name": self.name, "status": self.status,
+ "device": self.device, "uuid": self.uuid, "exists": self.exists,
+ "options": self.options, "supported": self.supported,
+ "format": self.formattable, "resize": self.resizable})
+ return s
+
+ @property
+ def dict(self):
+ d = {"type": self.type, "name": self.name, "device": self.device,
+ "uuid": self.uuid, "exists": self.exists,
+ "options": self.options, "supported": self.supported,
+ "resizable": self.resizable}
+ return d
+
+ def _setOptions(self, options):
+ self._options = options
+
+ def _getOptions(self):
+ return self._options
+
+ options = property(_getOptions, _setOptions)
+
+ def _setDevice(self, devspec):
+ if devspec and not devspec.startswith("/"):
+ raise ValueError("device must be a fully qualified path")
+ self._device = devspec
+
+ def _getDevice(self):
+ return self._device
+
+ device = property(lambda f: f._getDevice(),
+ lambda f,d: f._setDevice(d),
+ doc="Full path the device this format occupies")
+
+ @property
+ def name(self):
+ if self._name:
+ name = self._name
+ else:
+ name = self.type
+ return name
+
+ @property
+ def type(self):
+ return self._type
+
+ def probe(self):
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+
+ def notifyKernel(self):
+ log_method_call(self, device=self.device,
+ type=self.type)
+ if not self.device:
+ return
+
+ if self.device.startswith("/dev/mapper/"):
+ try:
+ name = dm_node_from_name(os.path.basename(self.device))
+ except Exception, e:
+ log.warning("failed to get dm node for %s" % self.device)
+ return
+ elif self.device:
+ name = os.path.basename(self.device)
+
+ path = get_sysfs_path_by_name(name)
+ try:
+ notify_kernel(path, action="change")
+ except Exception, e:
+ log.warning("failed to notify kernel of change: %s" % e)
+
+
+ def create(self, *args, **kwargs):
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ # allow late specification of device path
+ device = kwargs.get("device")
+ if device:
+ self.device = device
+
+ if not os.path.exists(self.device):
+ raise FormatCreateError("invalid device specification", self.device)
+
+ def destroy(self, *args, **kwargs):
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ # zero out the 1MB at the beginning and end of the device in the
+ # hope that it will wipe any metadata from filesystems that
+ # previously occupied this device
+ log.debug("zeroing out beginning and end of %s..." % self.device)
+ fd = None
+
+ try:
+ fd = os.open(self.device, os.O_RDWR)
+ buf = '\0' * 1024 * 1024
+ os.write(fd, buf)
+ os.lseek(fd, -1024 * 1024, 2)
+ os.write(fd, buf)
+ os.close(fd)
+ except OSError as e:
+ if getattr(e, "errno", None) == 28: # No space left in device
+ pass
+ else:
+ log.error("error zeroing out %s: %s" % (self.device, e))
+
+ if fd:
+ os.close(fd)
+ except Exception as e:
+ log.error("error zeroing out %s: %s" % (self.device, e))
+ if fd:
+ os.close(fd)
+
+ self.exists = False
+
+ def setup(self, *args, **kwargs):
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+
+ if not self.exists:
+ raise FormatSetupError("format has not been created")
+
+ if self.status:
+ return
+
+ # allow late specification of device path
+ device = kwargs.get("device")
+ if device:
+ self.device = device
+
+ if not self.device or not os.path.exists(self.device):
+ raise FormatSetupError("invalid device specification")
+
+ def teardown(self, *args, **kwargs):
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+
+ @property
+ def status(self):
+ return (self.exists and
+ self.__class__ is not DeviceFormat and
+ isinstance(self.device, str) and
+ self.device and
+ os.path.exists(self.device))
+
+ @property
+ def formattable(self):
+ """ Can we create formats of this type? """
+ return self._formattable
+
+ @property
+ def supported(self):
+ """ Is this format a supported type? """
+ return self._supported
+
+ @property
+ def packages(self):
+ """ Packages required to manage formats of this type. """
+ return self._packages
+
+ @property
+ def resizable(self):
+ """ Can formats of this type be resized? """
+ return self._resizable and self.exists
+
+ @property
+ def bootable(self):
+ """ Is this format type suitable for a boot partition? """
+ return self._bootable
+
+ @property
+ def migratable(self):
+ """ Can formats of this type be migrated? """
+ return self._migratable
+
+ @property
+ def migrate(self):
+ return self._migrate
+
+ @property
+ def linuxNative(self):
+ """ Is this format type native to linux? """
+ return self._linuxNative
+
+ @property
+ def mountable(self):
+ """ Is this something we can mount? """
+ return False
+
+ @property
+ def dump(self):
+ """ Whether or not this format will be dumped by dump(8). """
+ return self._dump
+
+ @property
+ def check(self):
+ """ Whether or not this format is checked on boot. """
+ return self._check
+
+ @property
+ def maxSize(self):
+ """ Maximum size (in MB) for this format type. """
+ return self._maxSize
+
+ @property
+ def minSize(self):
+ """ Minimum size (in MB) for this format type. """
+ return self._minSize
+
+ @property
+ def hidden(self):
+ """ Whether devices with this formatting should be hidden in UIs. """
+ return self._hidden
+
+ def writeKS(self, f):
+ return
+
+
+collect_device_format_classes()
+
+
diff --git a/storage/formats/disklabel.py b/storage/formats/disklabel.py
new file mode 100644
index 0000000..3edb1df
--- /dev/null
+++ b/storage/formats/disklabel.py
@@ -0,0 +1,359 @@
+# disklabel.py
+# Device format classes 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 copy
+
+from ..storage_log import log_method_call
+import parted
+import _ped
+from ..errors import *
+from ..udev import udev_settle
+from . import DeviceFormat, register_device_format
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+
+class DiskLabel(DeviceFormat):
+ """ Disklabel """
+ _type = "disklabel"
+ _name = "partition table"
+ _formattable = True # can be formatted
+ _supported = False # is supported
+
+ def __init__(self, *args, **kwargs):
+ """ Create a DiskLabel instance.
+
+ Keyword Arguments:
+
+ device -- path to the underlying device
+ exists -- indicates whether this is an existing format
+
+ """
+ log_method_call(self, *args, **kwargs)
+ DeviceFormat.__init__(self, *args, **kwargs)
+
+ self._size = None
+
+ self._partedDevice = None
+ self._partedDisk = None
+ self._origPartedDisk = None
+ self._alignment = None
+ self._endAlignment = None
+
+ if self.partedDevice:
+ # set up the parted objects and raise exception on failure
+ self._origPartedDisk = self.partedDisk.duplicate()
+
+ def __deepcopy__(self, memo):
+ """ Create a deep copy of a Disklabel instance.
+
+ We can't do copy.deepcopy on parted objects, which is okay.
+ For these parted objects, we just do a shallow copy.
+ """
+ new = self.__class__.__new__(self.__class__)
+ memo[id(self)] = new
+ shallow_copy_attrs = ('_partedDevice', '_partedDisk', '_origPartedDisk')
+ for (attr, value) in self.__dict__.items():
+ if attr in shallow_copy_attrs:
+ setattr(new, attr, copy.copy(value))
+ else:
+ setattr(new, attr, copy.deepcopy(value, memo))
+
+ return new
+
+ def __str__(self):
+ s = DeviceFormat.__str__(self)
+ s += (" type = %(type)s partition count = %(count)s"
+ " sectorSize = %(sectorSize)s\n"
+ " align_offset = %(offset)s align_grain = %(grain)s\n"
+ " partedDisk = %(disk)r\n"
+ " origPartedDisk = %(orig_disk)r\n"
+ " partedDevice = %(dev)r\n" %
+ {"type": self.labelType, "count": len(self.partitions),
+ "sectorSize": self.partedDevice.sectorSize,
+ "offset": self.alignment.offset,
+ "grain": self.alignment.grainSize,
+ "disk": self.partedDisk, "orig_disk": self._origPartedDisk,
+ "dev": self.partedDevice})
+ return s
+
+ @property
+ def dict(self):
+ d = super(DiskLabel, self).dict
+ d.update({"labelType": self.labelType,
+ "partitionCount": len(self.partitions),
+ "sectorSize": self.partedDevice.sectorSize,
+ "offset": self.alignment.offset,
+ "grainSize": self.alignment.grainSize})
+ return d
+
+ def resetPartedDisk(self):
+ """ Set this instance's partedDisk to reflect the disk's contents. """
+ log_method_call(self, device=self.device)
+ self._partedDisk = self._origPartedDisk
+
+ def freshPartedDisk(self):
+ """ Return a new, empty parted.Disk instance for this device. """
+ log_method_call(self, device=self.device)
+ from pyanaconda.platform import getPlatform
+ platf = getPlatform(None)
+ labelType = platf.diskLabelType(self.partedDevice.type)
+ return parted.freshDisk(device=self.partedDevice, ty=labelType)
+
+ @property
+ def partedDisk(self):
+ if not self._partedDisk:
+ if self.exists:
+ try:
+ self._partedDisk = parted.Disk(device=self.partedDevice)
+ except (_ped.DiskLabelException, _ped.IOException,
+ NotImplementedError) as e:
+ raise InvalidDiskLabelError()
+
+ if self._partedDisk.type == "loop":
+ # When the device has no partition table but it has a FS,
+ # it will be created with label type loop. Treat the
+ # same as if the device had no label (cause it really
+ # doesn't).
+ raise InvalidDiskLabelError()
+ else:
+ self._partedDisk = self.freshPartedDisk()
+
+ # turn off cylinder alignment
+ if self._partedDisk.isFlagAvailable(parted.DISK_CYLINDER_ALIGNMENT):
+ self._partedDisk.unsetFlag(parted.DISK_CYLINDER_ALIGNMENT)
+
+ return self._partedDisk
+
+ @property
+ def partedDevice(self):
+ if not self._partedDevice and self.device and \
+ os.path.exists(self.device):
+ # We aren't guaranteed to be able to get a device. In
+ # particular, built-in USB flash readers show up as devices but
+ # do not always have any media present, so parted won't be able
+ # to find a device.
+ try:
+ self._partedDevice = parted.Device(path=self.device)
+ except (_ped.IOException, _ped.DeviceException):
+ pass
+
+ return self._partedDevice
+
+ @property
+ def labelType(self):
+ """ The disklabel type (eg: 'gpt', 'msdos') """
+ return self.partedDisk.type
+
+ @property
+ def name(self):
+ return "%s (%s)" % (self._name, self.labelType.upper())
+
+ @property
+ def size(self):
+ size = self._size
+ if not size:
+ try:
+ size = self.partedDevice.getSize(unit="MB")
+ except Exception:
+ size = 0
+
+ return size
+
+ @property
+ def status(self):
+ """ Device status. """
+ return False
+
+ def setup(self, *args, **kwargs):
+ """ Open, or set up, a device. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if not self.exists:
+ raise DeviceFormatError("format has not been created")
+
+ if self.status:
+ return
+
+ DeviceFormat.setup(self, *args, **kwargs)
+
+ def teardown(self, *args, **kwargs):
+ """ Close, or tear down, a device. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if not self.exists:
+ raise DeviceFormatError("format has not been created")
+
+ def create(self, *args, **kwargs):
+ """ Create the device. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if self.exists:
+ raise DeviceFormatError("format already exists")
+
+ if self.status:
+ raise DeviceFormatError("device exists and is active")
+
+ DeviceFormat.create(self, *args, **kwargs)
+
+ # We're relying on someone having called resetPartedDisk -- we
+ # could ensure a fresh disklabel by setting self._partedDisk to
+ # None right before calling self.commit(), but that might hide
+ # other problems.
+ self.commit()
+ self.exists = True
+
+ def destroy(self, *args, **kwargs):
+ """ Wipe the disklabel from the device. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if not self.exists:
+ raise DeviceFormatError("format does not exist")
+
+ if not os.access(self.device, os.W_OK):
+ raise DeviceFormatError("device path does not exist")
+
+ self.partedDevice.clobber()
+ self.exists = False
+
+ def commit(self):
+ """ Commit the current partition table to disk and notify the OS. """
+ log_method_call(self, device=self.device,
+ numparts=len(self.partitions))
+ try:
+ self.partedDisk.commit()
+ except parted.DiskException as msg:
+ raise DiskLabelCommitError(msg)
+ else:
+ udev_settle()
+
+ def commitToDisk(self):
+ """ Commit the current partition table to disk. """
+ log_method_call(self, device=self.device,
+ numparts=len(self.partitions))
+ try:
+ self.partedDisk.commitToDevice()
+ except parted.DiskException as msg:
+ raise DiskLabelCommitError(msg)
+
+ def addPartition(self, *args, **kwargs):
+ partition = kwargs.get("partition", None)
+ if not partition:
+ partition = args[0]
+ geometry = partition.geometry
+ constraint = kwargs.get("constraint", None)
+ if not constraint and len(args) > 1:
+ constraint = args[1]
+ elif not constraint:
+ constraint = parted.Constraint(exactGeom=geometry)
+
+ new_partition = parted.Partition(disk=self.partedDisk,
+ type=partition.type,
+ geometry=geometry)
+ self.partedDisk.addPartition(partition=new_partition,
+ constraint=constraint)
+
+ def removePartition(self, partition):
+ try:
+ self.partedDisk.removePartition(partition)
+ except parted.PartitionException as err:
+ log.error("unable to remove partition %s: %s" %(partition, err))
+
+ @property
+ def extendedPartition(self):
+ try:
+ extended = self.partedDisk.getExtendedPartition()
+ except Exception:
+ extended = None
+ return extended
+
+ @property
+ def logicalPartitions(self):
+ try:
+ logicals = self.partedDisk.getLogicalPartitions()
+ except Exception:
+ logicals = []
+ return logicals
+
+ @property
+ def firstPartition(self):
+ try:
+ part = self.partedDisk.getFirstPartition()
+ except Exception:
+ part = None
+ return part
+
+ @property
+ def partitions(self):
+ try:
+ parts = self.partedDisk.partitions
+ except Exception:
+ parts = []
+ return parts
+
+ @property
+ def alignment(self):
+ """ Alignment requirements for this device. """
+ if not self._alignment:
+ try:
+ disklabel_alignment = self.partedDisk.partitionAlignment
+ except _ped.CreateException:
+ disklabel_alignment = parted.Alignment(offset=0, grainSize=1)
+
+ try:
+ optimum_device_alignment = self.partedDevice.optimumAlignment
+ except _ped.CreateException:
+ optimum_device_alignment = None
+
+ try:
+ minimum_device_alignment = self.partedDevice.minimumAlignment
+ except _ped.CreateException:
+ minimum_device_alignment = None
+
+ try:
+ a = optimum_device_alignment.intersect(disklabel_alignment)
+ except (ArithmeticError, AttributeError):
+ try:
+ a = minimum_device_alignment.intersect(disklabel_alignment)
+ except (ArithmeticError, AttributeError):
+ a = disklabel_alignment
+
+ self._alignment = a
+
+ return self._alignment
+
+ @property
+ def endAlignment(self):
+ if not self._endAlignment:
+ self._endAlignment = parted.Alignment(
+ offset = self.alignment.offset - 1,
+ grainSize = self.alignment.grainSize)
+
+ return self._endAlignment
+
+register_device_format(DiskLabel)
+
diff --git a/storage/formats/dmraid.py b/storage/formats/dmraid.py
new file mode 100644
index 0000000..3d2ee86
--- /dev/null
+++ b/storage/formats/dmraid.py
@@ -0,0 +1,114 @@
+# dmraid.py
+# dmraid device formats
+#
+# 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>
+#
+
+from ..storage_log import log_method_call
+from flags import flags
+from ..errors import *
+from . import DeviceFormat, register_device_format
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+
+class DMRaidMember(DeviceFormat):
+ """ A dmraid member disk. """
+ _type = "dmraidmember"
+ _name = "dm-raid member device"
+ # XXX This looks like trouble.
+ #
+ # Maybe a better approach is a RaidMember format with subclass
+ # for MDRaidMember, letting all *_raid_member types fall through
+ # to the generic RaidMember format, which is basically read-only.
+ #
+ # One problem that presents is the possibility of someone passing
+ # a dmraid member to the MDRaidArrayDevice constructor.
+ _udevTypes = ["adaptec_raid_member", "ddf_raid_member",
+ "highpoint_raid_member", "isw_raid_member",
+ "jmicron_raid_member", "lsi_mega_raid_member",
+ "nvidia_raid_member", "promise_fasttrack_raid_member",
+ "silicon_medley_raid_member", "via_raid_member"]
+ _formattable = False # can be formatted
+ _supported = True # is supported
+ _linuxNative = False # for clearpart
+ _packages = ["dmraid"] # required packages
+ _resizable = False # can be resized
+ _bootable = False # can be used as boot
+ _maxSize = 0 # maximum size in MB
+ _minSize = 0 # minimum size in MB
+ _hidden = True # hide devices with this formatting?
+
+ def __init__(self, *args, **kwargs):
+ """ Create a DeviceFormat instance.
+
+ Keyword Arguments:
+
+ device -- path to the underlying device
+ uuid -- this format's UUID
+ exists -- indicates whether this is an existing format
+
+ On initialization this format is like DeviceFormat
+
+ """
+ log_method_call(self, *args, **kwargs)
+ DeviceFormat.__init__(self, *args, **kwargs)
+
+ # Initialize the attribute that will hold the block object.
+ self._raidmem = None
+
+ def __str__(self):
+ s = DeviceFormat.__str__(self)
+ s += (" raidmem = %(raidmem)r" % {"raidmem": self.raidmem})
+ return s
+
+ def _getRaidmem(self):
+ return self._raidmem
+
+ def _setRaidmem(self, raidmem):
+ self._raidmem = raidmem
+
+ raidmem = property(lambda d: d._getRaidmem(),
+ lambda d,r: d._setRaidmem(r))
+
+ def create(self, *args, **kwargs):
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ raise DMRaidMemberError("creation of dmraid members is non-sense")
+
+ def destroy(self, *args, **kwargs):
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ raise DMRaidMemberError("destruction of dmraid members is non-sense")
+
+
+if not flags.cmdline.has_key("noiswmd"):
+ DMRaidMember._udevTypes.remove("isw_raid_member")
+
+# The anaconda cmdline has not been parsed yet when we're first imported,
+# so we can not use flags.dmraid here
+if flags.cmdline.has_key("nodmraid"):
+ DMRaidMember._udevTypes = []
+
+register_device_format(DMRaidMember)
+
diff --git a/storage/formats/fs.py b/storage/formats/fs.py
new file mode 100644
index 0000000..ed2c02d
--- /dev/null
+++ b/storage/formats/fs.py
@@ -0,0 +1,1476 @@
+# filesystems.py
+# Filesystem classes 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>
+# David Cantrell <dcantrell@redhat.com>
+#
+
+""" Filesystem classes for use by anaconda.
+
+ TODO:
+ - migration
+ - bug 472127: allow creation of tmpfs filesystems (/tmp, /var/tmp, &c)
+"""
+import math
+import os
+import sys
+import tempfile
+import selinux
+import isys
+
+from ..errors import *
+from . import DeviceFormat, register_device_format
+import iutil
+from flags import flags
+from parted import fileSystemType
+from ..storage_log import log_method_call
+
+import logging
+log = logging.getLogger("storage")
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+try:
+ lost_and_found_context = selinux.matchpathcon("/lost+found", 0)[1]
+except OSError:
+ lost_and_found_context = None
+
+fs_configs = {}
+
+def get_kernel_filesystems():
+ fs_list = []
+ for line in open("/proc/filesystems").readlines():
+ fs_list.append(line.split()[-1])
+ return fs_list
+
+global kernel_filesystems
+kernel_filesystems = get_kernel_filesystems()
+
+def fsConfigFromFile(config_file):
+ """ Generate a set of attribute name/value pairs with which a
+ filesystem type can be defined.
+
+ The following config file would define a filesystem identical to
+ the static Ext3FS class definition:
+
+ type = ext3
+ mkfs = "mke2fs"
+ resizefs = "resize2fs"
+ labelfs = "e2label"
+ fsck = "e2fsck"
+ packages = ["e2fsprogs"]
+ formattable = True
+ supported = True
+ resizable = True
+ bootable = True
+ linuxNative = True
+ maxSize = 8 * 1024 * 1024
+ minSize = 0
+ defaultFormatOptions = "-t ext3"
+ defaultMountOptions = "defaults"
+
+ """
+ # XXX NOTUSED
+ lines = open(config_file).readlines()
+ fs_attrs = {}
+ for line in lines:
+ (key, value) = [t.strip() for t in line.split("=")]
+ if not hasattr(FS, "_" + key):
+ print "invalid key: %s" % key
+ continue
+
+ fs_attrs[key] = value
+
+ if not fs_attrs.has_key("type"):
+ raise ValueError, _("filesystem configuration missing a type")
+
+ # XXX what's the policy about multiple configs for a given type?
+ fs_configs[fs_attrs['type']] = fs_attrs
+
+class FS(DeviceFormat):
+ """ Filesystem class. """
+ _type = "Abstract Filesystem Class" # fs type name
+ _mountType = None # like _type but for passing to mount
+ _name = None
+ _mkfs = "" # mkfs utility
+ _modules = [] # kernel modules required for support
+ _resizefs = "" # resize utility
+ _labelfs = "" # labeling utility
+ _fsck = "" # fs check utility
+ _fsckErrors = {} # fs check command error codes & msgs
+ _migratefs = "" # fs migration utility
+ _infofs = "" # fs info utility
+ _defaultFormatOptions = [] # default options passed to mkfs
+ _defaultMountOptions = ["defaults"] # default options passed to mount
+ _defaultLabelOptions = []
+ _defaultCheckOptions = []
+ _defaultMigrateOptions = []
+ _defaultInfoOptions = []
+ _migrationTarget = None
+ _existingSizeFields = []
+ _fsProfileSpecifier = None # mkfs option specifying fsprofile
+
+ def __init__(self, *args, **kwargs):
+ """ Create a FS instance.
+
+ Keyword Args:
+
+ device -- path to the device containing the filesystem
+ mountpoint -- the filesystem's mountpoint
+ label -- the filesystem label
+ uuid -- the filesystem UUID
+ mountopts -- mount options for the filesystem
+ size -- the filesystem's size in MiB
+ exists -- indicates whether this is an existing filesystem
+
+ """
+ if self.__class__ is FS:
+ raise TypeError("FS is an abstract class.")
+
+ DeviceFormat.__init__(self, *args, **kwargs)
+ self.mountpoint = kwargs.get("mountpoint")
+ self.mountopts = kwargs.get("mountopts")
+ self.label = kwargs.get("label")
+ self.fsprofile = kwargs.get("fsprofile")
+
+ # filesystem size does not necessarily equal device size
+ self._size = kwargs.get("size", 0)
+ self._minInstanceSize = None # min size of this FS instance
+ self._mountpoint = None # the current mountpoint when mounted
+ if self.exists and self.supported:
+ self._size = self._getExistingSize()
+ foo = self.minSize # force calculation of minimum size
+
+ self._targetSize = self._size
+
+ if self.supported:
+ self.loadModule()
+
+ def __str__(self):
+ s = DeviceFormat.__str__(self)
+ s += (" mountpoint = %(mountpoint)s mountopts = %(mountopts)s\n"
+ " label = %(label)s size = %(size)s"
+ " targetSize = %(targetSize)s\n" %
+ {"mountpoint": self.mountpoint, "mountopts": self.mountopts,
+ "label": self.label, "size": self._size,
+ "targetSize": self.targetSize})
+ return s
+
+ @property
+ def dict(self):
+ d = super(FS, self).dict
+ d.update({"mountpoint": self.mountpoint, "size": self._size,
+ "label": self.label, "targetSize": self.targetSize,
+ "mountable": self.mountable,
+ "migratable": self.migratable})
+ return d
+
+ def _setTargetSize(self, newsize):
+ """ Set a target size for this filesystem. """
+ if not self.exists:
+ raise FSError("filesystem has not been created")
+
+ if newsize is None:
+ # unset any outstanding resize request
+ self._targetSize = None
+ return
+
+ if not self.minSize <= newsize < self.maxSize:
+ raise ValueError("invalid target size request")
+
+ self._targetSize = newsize
+
+ def _getTargetSize(self):
+ """ Get this filesystem's target size. """
+ return self._targetSize
+
+ targetSize = property(_getTargetSize, _setTargetSize,
+ doc="Target size for this filesystem")
+
+ def _getSize(self):
+ """ Get this filesystem's size. """
+ size = self._size
+ if self.resizable and self.targetSize != size:
+ size = self.targetSize
+ return size
+
+ size = property(_getSize, doc="This filesystem's size, accounting "
+ "for pending changes")
+
+ def _getExistingSize(self):
+ """ Determine the size of this filesystem. Filesystem must
+ exist. Each filesystem varies, but the general procedure
+ is to run the filesystem dump or info utility and read
+ the block size and number of blocks for the filesystem
+ and compute megabytes from that.
+
+ The loop that reads the output from the infofsProg is meant
+ to be simple, but take in to account variations in output.
+ The general procedure:
+ 1) Capture output from infofsProg.
+ 2) Iterate over each line of the output:
+ a) Trim leading and trailing whitespace.
+ b) Break line into fields split on ' '
+ c) If line begins with any of the strings in
+ _existingSizeFields, start at the end of
+ fields and take the first one that converts
+ to a long. Store this in the values list.
+ d) Repeat until the values list length equals
+ the _existingSizeFields length.
+ 3) If the length of the values list equals the length
+ of _existingSizeFields, compute the size of this
+ filesystem by multiplying all of the values together
+ to get bytes, then convert to megabytes. Return
+ this value.
+ 4) If we were unable to capture all fields, return 0.
+
+ The caller should catch exceptions from this method. Any
+ exception raised indicates a need to change the fields we
+ are looking for, the command to run and arguments, or
+ something else. If you catch an exception from this method,
+ assume the filesystem cannot be resized.
+ """
+ size = self._size
+
+ if self.infofsProg and self.mountable and self.exists and not size:
+ try:
+ values = []
+ argv = self._defaultInfoOptions + [ self.device ]
+
+ buf = iutil.execWithCapture(self.infofsProg, argv,
+ stderr="/dev/tty5")
+
+ for line in buf.splitlines():
+ found = False
+
+ line = line.strip()
+ tmp = line.split(' ')
+ tmp.reverse()
+
+ for field in self._existingSizeFields:
+ if line.startswith(field):
+ for subfield in tmp:
+ try:
+ values.append(long(subfield))
+ found = True
+ break
+ except ValueError:
+ continue
+
+ if found:
+ break
+
+ if len(values) == len(self._existingSizeFields):
+ break
+
+ if len(values) != len(self._existingSizeFields):
+ return 0
+
+ size = 1
+ for value in values:
+ size *= value
+
+ # report current size as megabytes
+ size = math.floor(size / 1024.0 / 1024.0)
+ except Exception as e:
+ log.error("failed to obtain size of filesystem on %s: %s"
+ % (self.device, e))
+
+ return size
+
+ @property
+ def currentSize(self):
+ """ The filesystem's current actual size. """
+ size = 0
+ if self.exists:
+ size = self._size
+ return float(size)
+
+ def _getFormatOptions(self, options=None):
+ argv = []
+ if options and isinstance(options, list):
+ argv.extend(options)
+ argv.extend(self.defaultFormatOptions)
+ if self._fsProfileSpecifier and self.fsprofile:
+ argv.extend([self._fsProfileSpecifier, self.fsprofile])
+ argv.append(self.device)
+ return argv
+
+ def doFormat(self, *args, **kwargs):
+ """ Create the filesystem.
+
+ Arguments:
+
+ None
+
+ Keyword Arguments:
+
+ intf -- InstallInterface instance
+ options -- list of options to pass to mkfs
+
+ """
+ log_method_call(self, type=self.mountType, device=self.device,
+ mountpoint=self.mountpoint)
+
+ intf = kwargs.get("intf")
+ options = kwargs.get("options")
+
+ if self.exists:
+ raise FormatCreateError("filesystem already exists", self.device)
+
+ if not self.formattable:
+ return
+
+ if not self.mkfsProg:
+ return
+
+ if self.exists:
+ return
+
+ if not os.path.exists(self.device):
+ raise FormatCreateError("device does not exist", self.device)
+
+ argv = self._getFormatOptions(options=options)
+
+ w = None
+ if intf:
+ w = intf.progressWindow(_("Formatting"),
+ _("Creating %s filesystem on %s")
+ % (self.type, self.device),
+ 100, pulse = True)
+
+ try:
+ rc = iutil.execWithPulseProgress(self.mkfsProg,
+ argv,
+ stdout="/dev/tty5",
+ stderr="/dev/tty5",
+ progress=w)
+ except Exception as e:
+ raise FormatCreateError(e, self.device)
+ finally:
+ if w:
+ w.pop()
+
+ if rc:
+ raise FormatCreateError("format failed: %s" % rc, self.device)
+
+ self.exists = True
+ self.notifyKernel()
+
+ if self.label:
+ self.writeLabel(self.label)
+
+ def doMigrate(self, intf=None):
+ if not self.exists:
+ raise FSError("filesystem has not been created")
+
+ if not self.migratable or not self.migrate:
+ return
+
+ if not os.path.exists(self.device):
+ raise FSError("device does not exist")
+
+ # if journal already exists skip
+ if isys.ext2HasJournal(self.device):
+ log.info("Skipping migration of %s, has a journal already."
+ % self.device)
+ return
+
+ argv = self._defaultMigrateOptions[:]
+ argv.append(self.device)
+ try:
+ rc = iutil.execWithRedirect(self.migratefsProg,
+ argv,
+ stdout = "/dev/tty5",
+ stderr = "/dev/tty5")
+ except Exception as e:
+ raise FSMigrateError("filesystem migration failed: %s" % e,
+ self.device)
+
+ if rc:
+ raise FSMigrateError("filesystem migration failed: %s" % rc,
+ self.device)
+
+ # the other option is to actually replace this instance with an
+ # instance of the new filesystem type.
+ self._type = self.migrationTarget
+
+ @property
+ def resizeArgs(self):
+ argv = [self.device, "%d" % (self.targetSize,)]
+ return argv
+
+ def doResize(self, *args, **kwargs):
+ """ Resize this filesystem to new size @newsize.
+
+ Arguments:
+
+ None
+
+ Keyword Arguments:
+
+ intf -- InstallInterface instance
+
+ """
+ intf = kwargs.get("intf")
+
+ if not self.exists:
+ raise FSResizeError("filesystem does not exist", self.device)
+
+ if not self.resizable:
+ raise FSResizeError("filesystem not resizable", self.device)
+
+ if self.targetSize == self.currentSize:
+ return
+
+ if not self.resizefsProg:
+ return
+
+ if not os.path.exists(self.device):
+ raise FSResizeError("device does not exist", self.device)
+
+ self.doCheck(intf=intf)
+
+ # The first minimum size can be incorrect if the fs was not
+ # properly unmounted. After doCheck the minimum size will be correct
+ # so run the check one last time and bump up the size if it was too
+ # small.
+ self._minInstanceSize = None
+ if self.targetSize < self.minSize:
+ self.targetSize = self.minSize
+ log.info("Minimum size changed, setting targetSize on %s to %s" \
+ % (self.device, self.targetSize))
+
+ w = None
+ if intf:
+ w = intf.progressWindow(_("Resizing"),
+ _("Resizing filesystem on %s")
+ % (self.device,),
+ 100, pulse = True)
+
+ try:
+ rc = iutil.execWithPulseProgress(self.resizefsProg,
+ self.resizeArgs,
+ stdout="/dev/tty5",
+ stderr="/dev/tty5",
+ progress=w)
+ except Exception as e:
+ raise FSResizeError(e, self.device)
+ finally:
+ if w:
+ w.pop()
+
+ if rc:
+ raise FSResizeError("resize failed: %s" % rc, self.device)
+
+ self.doCheck(intf=intf)
+
+ # XXX must be a smarter way to do this
+ self._size = self.targetSize
+ self.notifyKernel()
+
+ def _getCheckArgs(self):
+ argv = []
+ argv.extend(self.defaultCheckOptions)
+ argv.append(self.device)
+ return argv
+
+ def _fsckFailed(self, rc):
+ return False
+
+ def _fsckErrorMessage(self, rc):
+ return _("Unknown return code: %d.") % (rc,)
+
+ def doCheck(self, intf=None):
+ if not self.exists:
+ raise FSError("filesystem has not been created")
+
+ if not self.fsckProg:
+ return
+
+ if not os.path.exists(self.device):
+ raise FSError("device does not exist")
+
+ w = None
+ if intf:
+ w = intf.progressWindow(_("Checking"),
+ _("Checking filesystem on %s")
+ % (self.device),
+ 100, pulse = True)
+
+ try:
+ rc = iutil.execWithPulseProgress(self.fsckProg,
+ self._getCheckArgs(),
+ stdout="/dev/tty5",
+ stderr="/dev/tty5",
+ progress = w)
+ except Exception as e:
+ raise FSError("filesystem check failed: %s" % e)
+ finally:
+ if w:
+ w.pop()
+
+ if self._fsckFailed(rc):
+ hdr = _("%(type)s filesystem check failure on %(device)s: ") % \
+ {"type": self.type, "device": self.device}
+
+ msg = self._fsckErrorMessage(rc)
+
+ if intf:
+ help = _("Errors like this usually mean there is a problem "
+ "with the filesystem that will require user "
+ "interaction to repair. Before restarting "
+ "installation, reboot to rescue mode or another "
+ "system that allows you to repair the filesystem "
+ "interactively. Restart installation after you "
+ "have corrected the problems on the filesystem.")
+
+ intf.messageWindow(_("Unrecoverable Error"),
+ hdr + "\n\n" + msg + "\n\n" + help,
+ custom_icon='error')
+ sys.exit(0)
+ else:
+ raise FSError(hdr + msg)
+
+ def loadModule(self):
+ """Load whatever kernel module is required to support this filesystem."""
+ global kernel_filesystems
+
+ if not self._modules or self.mountType in kernel_filesystems:
+ return
+
+ for module in self._modules:
+ try:
+ rc = iutil.execWithRedirect("modprobe", [module],
+ stdout="/dev/tty5",
+ stderr="/dev/tty5")
+ except Exception as e:
+ log.error("Could not load kernel module %s: %s" % (module, e))
+ self._supported = False
+ return
+
+ if rc:
+ log.error("Could not load kernel module %s" % module)
+ self._supported = False
+ return
+
+ # If we successfully loaded a kernel module, for this filesystem, we
+ # also need to update the list of supported filesystems.
+ kernel_filesystems = get_kernel_filesystems()
+
+ def mount(self, *args, **kwargs):
+ """ Mount this filesystem.
+
+ Arguments:
+
+ None
+
+ Keyword Arguments:
+
+ options -- mount options (overrides all other option strings)
+ chroot -- prefix to apply to mountpoint
+ mountpoint -- mountpoint (overrides self.mountpoint)
+ """
+ options = kwargs.get("options", "")
+ chroot = kwargs.get("chroot", "/")
+ mountpoint = kwargs.get("mountpoint")
+
+ if not self.exists:
+ raise FSError("filesystem has not been created")
+
+ if not mountpoint:
+ mountpoint = self.mountpoint
+
+ if not mountpoint:
+ raise FSError("no mountpoint given")
+
+ if self.status:
+ return
+
+ if not isinstance(self, NoDevFS) and not os.path.exists(self.device):
+ raise FSError("device %s does not exist" % self.device)
+
+ # XXX os.path.join is FUBAR:
+ #
+ # os.path.join("/mnt/foo", "/") -> "/"
+ #
+ #mountpoint = os.path.join(chroot, mountpoint)
+ chrootedMountpoint = os.path.normpath("%s/%s" % (chroot, mountpoint))
+ iutil.mkdirChain(chrootedMountpoint)
+ if flags.selinux:
+ ret = isys.resetFileContext(mountpoint, chroot)
+ log.info("set SELinux context for mountpoint %s to %s" \
+ % (mountpoint, ret))
+
+ # passed in options override default options
+ if not options or not isinstance(options, str):
+ options = self.options
+
+ try:
+ rc = isys.mount(self.device, chrootedMountpoint,
+ fstype=self.mountType,
+ options=options,
+ bindMount=isinstance(self, BindFS))
+ except Exception as e:
+ raise FSError("mount failed: %s" % e)
+
+ if rc:
+ raise FSError("mount failed: %s" % rc)
+
+ if flags.selinux and "ro" not in options.split(","):
+ ret = isys.resetFileContext(mountpoint, chroot)
+ log.info("set SELinux context for newly mounted filesystem "
+ "root at %s to %s" %(mountpoint, ret))
+ isys.setFileContext("%s/lost+found" % mountpoint,
+ lost_and_found_context, chroot)
+
+ self._mountpoint = chrootedMountpoint
+
+ def unmount(self):
+ """ Unmount this filesystem. """
+ if not self.exists:
+ raise FSError("filesystem has not been created")
+
+ if not self._mountpoint:
+ # not mounted
+ return
+
+ if not os.path.exists(self._mountpoint):
+ raise FSError("mountpoint does not exist")
+
+ rc = isys.umount(self._mountpoint, removeDir = False)
+ if rc:
+ raise FSError("umount failed")
+
+ self._mountpoint = None
+
+ def _getLabelArgs(self, label):
+ argv = []
+ argv.extend(self.defaultLabelOptions)
+ argv.extend([self.device, label])
+ return argv
+
+ def writeLabel(self, label):
+ """ Create a label for this filesystem. """
+ if not self.exists:
+ raise FSError("filesystem has not been created")
+
+ if not self.labelfsProg:
+ return
+
+ if not os.path.exists(self.device):
+ raise FSError("device does not exist")
+
+ argv = self._getLabelArgs(label)
+ rc = iutil.execWithRedirect(self.labelfsProg,
+ argv,
+ stderr="/dev/tty5")
+ if rc:
+ raise FSError("label failed")
+
+ self.label = label
+ self.notifyKernel()
+
+ @property
+ def isDirty(self):
+ return False
+
+ @property
+ def mkfsProg(self):
+ """ Program used to create filesystems of this type. """
+ return self._mkfs
+
+ @property
+ def fsckProg(self):
+ """ Program used to check filesystems of this type. """
+ return self._fsck
+
+ @property
+ def resizefsProg(self):
+ """ Program used to resize filesystems of this type. """
+ return self._resizefs
+
+ @property
+ def labelfsProg(self):
+ """ Program used to manage labels for this filesystem type. """
+ return self._labelfs
+
+ @property
+ def migratefsProg(self):
+ """ Program used to migrate filesystems of this type. """
+ return self._migratefs
+
+ @property
+ def infofsProg(self):
+ """ Program used to get information for this filesystem type. """
+ return self._infofs
+
+ @property
+ def migrationTarget(self):
+ return self._migrationTarget
+
+ @property
+ def utilsAvailable(self):
+ # we aren't checking for fsck because we shouldn't need it
+ for prog in [self.mkfsProg, self.resizefsProg, self.labelfsProg,
+ self.infofsProg]:
+ if not prog:
+ continue
+
+ if not filter(lambda d: os.access("%s/%s" % (d, prog), os.X_OK),
+ os.environ["PATH"].split(":")):
+ return False
+
+ return True
+
+ @property
+ def supported(self):
+ log_method_call(self, supported=self._supported)
+ return self._supported and self.utilsAvailable
+
+ @property
+ def mountable(self):
+ return (self.mountType in kernel_filesystems) or \
+ (os.access("/sbin/mount.%s" % (self.mountType,), os.X_OK))
+
+ @property
+ def defaultFormatOptions(self):
+ """ Default options passed to mkfs for this filesystem type. """
+ # return a copy to prevent modification
+ return self._defaultFormatOptions[:]
+
+ @property
+ def defaultMountOptions(self):
+ """ Default options passed to mount for this filesystem type. """
+ # return a copy to prevent modification
+ return self._defaultMountOptions[:]
+
+ @property
+ def defaultLabelOptions(self):
+ """ Default options passed to labeler for this filesystem type. """
+ # return a copy to prevent modification
+ return self._defaultLabelOptions[:]
+
+ @property
+ def defaultCheckOptions(self):
+ """ Default options passed to checker for this filesystem type. """
+ # return a copy to prevent modification
+ return self._defaultCheckOptions[:]
+
+ def _getOptions(self):
+ options = ",".join(self.defaultMountOptions)
+ if self.mountopts:
+ # XXX should we clobber or append?
+ options = self.mountopts
+ return options
+
+ def _setOptions(self, options):
+ self.mountopts = options
+
+ options = property(_getOptions, _setOptions)
+
+ def _isMigratable(self):
+ """ Can filesystems of this type be migrated? """
+ return bool(self._migratable and self.migratefsProg and
+ filter(lambda d: os.access("%s/%s"
+ % (d, self.migratefsProg,),
+ os.X_OK),
+ os.environ["PATH"].split(":")) and
+ self.migrationTarget)
+
+ migratable = property(_isMigratable)
+
+ def _setMigrate(self, migrate):
+ if not migrate:
+ self._migrate = migrate
+ return
+
+ if self.migratable and self.exists:
+ self._migrate = migrate
+ else:
+ raise ValueError("cannot set migrate on non-migratable filesystem")
+
+ migrate = property(lambda f: f._migrate, lambda f,m: f._setMigrate(m))
+
+ @property
+ def type(self):
+ _type = self._type
+ if self.migrate:
+ _type = self.migrationTarget
+
+ return _type
+
+ @property
+ def mountType(self):
+ if not self._mountType:
+ self._mountType = self._type
+
+ return self._mountType
+
+ # These methods just wrap filesystem-specific methods in more
+ # generically named methods so filesystems and formatted devices
+ # like swap and LVM physical volumes can have a common API.
+ def create(self, *args, **kwargs):
+ if self.exists:
+ raise FSError("filesystem already exists")
+
+ DeviceFormat.create(self, *args, **kwargs)
+
+ return self.doFormat(*args, **kwargs)
+
+ def setup(self, *args, **kwargs):
+ """ Mount the filesystem.
+
+ The filesystem will be mounted at the directory indicated by
+ self.mountpoint.
+ """
+ return self.mount(**kwargs)
+
+ def teardown(self, *args, **kwargs):
+ return self.unmount(*args, **kwargs)
+
+ @property
+ def status(self):
+ # FIXME check /proc/mounts or similar
+ if not self.exists:
+ return False
+ return self._mountpoint is not None
+
+ def writeKS(self, f):
+ f.write("%s --fstype=%s" % (self.mountpoint, self.type))
+
+ if self.label:
+ f.write(" --label=\"%s\"" % self.label)
+
+
+class Ext2FS(FS):
+ """ ext2 filesystem. """
+ _type = "ext2"
+ _mkfs = "mke2fs"
+ _modules = ["ext2"]
+ _resizefs = "resize2fs"
+ _labelfs = "e2label"
+ _fsck = "e2fsck"
+ _fsckErrors = {4: _("File system errors left uncorrected."),
+ 8: _("Operational error."),
+ 16: _("Usage or syntax error."),
+ 32: _("e2fsck cancelled by user request."),
+ 128: _("Shared library error.")}
+ _packages = ["e2fsprogs"]
+ _formattable = True
+ _supported = True
+ _resizable = True
+ _bootable = True
+ _linuxNative = True
+ _maxSize = 8 * 1024 * 1024
+ _minSize = 0
+ _defaultFormatOptions = []
+ _defaultMountOptions = ["defaults"]
+ _defaultCheckOptions = ["-f", "-p", "-C", "0"]
+ _dump = True
+ _check = True
+ _migratable = True
+ _migrationTarget = "ext3"
+ _migratefs = "tune2fs"
+ _defaultMigrateOptions = ["-j"]
+ _infofs = "dumpe2fs"
+ _defaultInfoOptions = ["-h"]
+ _existingSizeFields = ["Block count:", "Block size:"]
+ _fsProfileSpecifier = "-T"
+ partedSystem = fileSystemType["ext2"]
+
+ def _fsckFailed(self, rc):
+ for errorCode in self._fsckErrors.keys():
+ if rc & errorCode:
+ return True
+ return False
+
+ def _fsckErrorMessage(self, rc):
+ msg = ''
+
+ for errorCode in self._fsckErrors.keys():
+ if rc & errorCode:
+ msg += "\n" + self._fsckErrors[errorCode]
+
+ return msg.strip()
+
+ def doMigrate(self, intf=None):
+ FS.doMigrate(self, intf=intf)
+ self.tuneFS()
+
+ def doFormat(self, *args, **kwargs):
+ FS.doFormat(self, *args, **kwargs)
+ self.tuneFS()
+
+ def tuneFS(self):
+ if not isys.ext2HasJournal(self.device):
+ # only do this if there's a journal
+ return
+
+ try:
+ rc = iutil.execWithRedirect("tune2fs",
+ ["-c0", "-i0",
+ "-ouser_xattr,acl", self.device],
+ stdout = "/dev/tty5",
+ stderr = "/dev/tty5")
+ except Exception as e:
+ log.error("failed to run tune2fs on %s: %s" % (self.device, e))
+
+ @property
+ def minSize(self):
+ """ Minimum size for this filesystem in MB. """
+ if self._minInstanceSize is None:
+ # try once in the beginning to get the minimum size for an
+ # existing filesystem.
+ size = self._minSize
+ blockSize = None
+
+ if self.exists and os.path.exists(self.device):
+ # get block size
+ buf = iutil.execWithCapture(self.infofsProg,
+ ["-h", self.device],
+ stderr="/dev/tty5")
+ for line in buf.splitlines():
+ if line.startswith("Block size:"):
+ blockSize = int(line.split(" ")[-1])
+ break
+
+ if blockSize is None:
+ raise FSError("failed to get block size for %s filesystem "
+ "on %s" % (self.mountType, self.device))
+
+ # get minimum size according to resize2fs
+ buf = iutil.execWithCapture(self.resizefsProg,
+ ["-P", self.device],
+ stderr="/dev/tty5")
+ for line in buf.splitlines():
+ if "minimum size of the filesystem:" not in line:
+ continue
+
+ # line will look like:
+ # Estimated minimum size of the filesystem: 1148649
+ #
+ # NOTE: The minimum size reported is in blocks. Convert
+ # to bytes, then megabytes, and finally round up.
+ (text, sep, minSize) = line.partition(": ")
+ size = long(minSize) * blockSize
+ size = math.ceil(size / 1024.0 / 1024.0)
+ break
+
+ if size is None:
+ log.warning("failed to get minimum size for %s filesystem "
+ "on %s" % (self.mountType, self.device))
+
+ self._minInstanceSize = size
+
+ return self._minInstanceSize
+
+ @property
+ def isDirty(self):
+ return isys.ext2IsDirty(self.device)
+
+ @property
+ def resizeArgs(self):
+ argv = ["-p", self.device, "%dM" % (self.targetSize,)]
+ return argv
+
+register_device_format(Ext2FS)
+
+
+class Ext3FS(Ext2FS):
+ """ ext3 filesystem. """
+ _type = "ext3"
+ _defaultFormatOptions = ["-t", "ext3"]
+ _migrationTarget = "ext4"
+ _modules = ["ext3"]
+ _defaultMigrateOptions = ["-O", "extents"]
+ partedSystem = fileSystemType["ext3"]
+
+ def _isMigratable(self):
+ """ Can filesystems of this type be migrated? """
+ return (flags.cmdline.has_key("ext4migrate") and
+ Ext2FS._isMigratable(self))
+
+ migratable = property(_isMigratable)
+
+register_device_format(Ext3FS)
+
+
+class Ext4FS(Ext3FS):
+ """ ext4 filesystem. """
+ _type = "ext4"
+ _defaultFormatOptions = ["-t", "ext4"]
+ _migratable = False
+ _modules = ["ext4"]
+ partedSystem = fileSystemType["ext4"]
+
+register_device_format(Ext4FS)
+
+
+class FATFS(FS):
+ """ FAT filesystem. """
+ _type = "vfat"
+ _mkfs = "mkdosfs"
+ _modules = ["vfat"]
+ _labelfs = "dosfslabel"
+ _fsck = "dosfsck"
+ _fsckErrors = {1: _("Recoverable errors have been detected or dosfsck has "
+ "discovered an internal inconsistency."),
+ 2: _("Usage error.")}
+ _supported = True
+ _formattable = True
+ _maxSize = 1024 * 1024
+ _packages = [ "dosfstools" ]
+ _defaultMountOptions = ["umask=0077", "shortname=winnt"]
+ # FIXME this should be fat32 in some cases
+ partedSystem = fileSystemType["fat16"]
+
+ def _fsckFailed(self, rc):
+ if rc >= 1:
+ return True
+ return False
+
+ def _fsckErrorMessage(self, rc):
+ return self._fsckErrors[rc]
+
+register_device_format(FATFS)
+
+
+class EFIFS(FATFS):
+ _type = "efi"
+ _mountType = "vfat"
+ _modules = ["vfat"]
+ _name = "EFI System Partition"
+ _minSize = 50
+ _maxSize = 256
+ _bootable = True
+
+ @property
+ def supported(self):
+ import pyanaconda.platform as platform
+ p = platform.getPlatform(None)
+ return (isinstance(p, platform.EFI) and
+ p.isEfi and
+ self.utilsAvailable)
+
+register_device_format(EFIFS)
+
+
+class BTRFS(FS):
+ """ btrfs filesystem """
+ _type = "btrfs"
+ _mkfs = "mkfs.btrfs"
+ _modules = ["btrfs"]
+ _resizefs = "btrfsctl"
+ _formattable = True
+ _linuxNative = True
+ _bootable = False
+ _maxLabelChars = 256
+ _supported = True
+ _dump = True
+ _check = True
+ _packages = ["btrfs-progs"]
+ _maxSize = 16 * 1024 * 1024
+ # FIXME parted needs to be thaught about btrfs so that we can set the
+ # partition table type correctly for btrfs partitions
+ # partedSystem = fileSystemType["btrfs"]
+
+ def _getFormatOptions(self, options=None):
+ argv = []
+ if options and isinstance(options, list):
+ argv.extend(options)
+ argv.extend(self.defaultFormatOptions)
+ if self.label:
+ argv.extend(["-L", self.label])
+ argv.append(self.device)
+ return argv
+
+ @property
+ def resizeArgs(self):
+ argv = ["-r", "%dm" % (self.targetSize,), self.device]
+ return argv
+
+ @property
+ def supported(self):
+ """ Is this filesystem a supported type? """
+ supported = self._supported
+ if flags.cmdline.has_key("btrfs"):
+ supported = self.utilsAvailable
+
+ return supported
+
+register_device_format(BTRFS)
+
+
+class GFS2(FS):
+ """ gfs2 filesystem. """
+ _type = "gfs2"
+ _mkfs = "mkfs.gfs2"
+ _modules = ["dlm", "gfs2"]
+ _formattable = True
+ _defaultFormatOptions = ["-j", "1", "-p", "lock_nolock", "-O"]
+ _linuxNative = True
+ _supported = False
+ _dump = True
+ _check = True
+ _packages = ["gfs2-utils"]
+ # FIXME parted needs to be thaught about btrfs so that we can set the
+ # partition table type correctly for btrfs partitions
+ # partedSystem = fileSystemType["gfs2"]
+
+ @property
+ def supported(self):
+ """ Is this filesystem a supported type? """
+ supported = self._supported
+ if flags.cmdline.has_key("gfs2"):
+ supported = self.utilsAvailable
+
+ return supported
+
+register_device_format(GFS2)
+
+
+class JFS(FS):
+ """ JFS filesystem """
+ _type = "jfs"
+ _mkfs = "mkfs.jfs"
+ _modules = ["jfs"]
+ _labelfs = "jfs_tune"
+ _defaultFormatOptions = ["-q"]
+ _defaultLabelOptions = ["-L"]
+ _maxLabelChars = 16
+ _maxSize = 8 * 1024 * 1024
+ _formattable = True
+ _linuxNative = True
+ _supported = True
+ _bootable = True
+ _dump = True
+ _check = True
+ _infofs = "jfs_tune"
+ _defaultInfoOptions = ["-l"]
+ _existingSizeFields = ["Aggregate block size:", "Aggregate size:"]
+ partedSystem = fileSystemType["jfs"]
+
+ @property
+ def supported(self):
+ """ Is this filesystem a supported type? """
+ supported = self._supported
+ if flags.cmdline.has_key("jfs"):
+ supported = self.utilsAvailable
+
+ return supported
+
+register_device_format(JFS)
+
+
+class ReiserFS(FS):
+ """ reiserfs filesystem """
+ _type = "reiserfs"
+ _mkfs = "mkreiserfs"
+ _resizefs = "resize_reiserfs"
+ _labelfs = "reiserfstune"
+ _modules = ["reiserfs"]
+ _defaultFormatOptions = ["-f", "-f"]
+ _defaultLabelOptions = ["-l"]
+ _maxLabelChars = 16
+ _maxSize = 16 * 1024 * 1024
+ _formattable = True
+ _linuxNative = True
+ _supported = True
+ _bootable = True
+ _dump = True
+ _check = True
+ _packages = ["reiserfs-utils"]
+ _infofs = "debugreiserfs"
+ _defaultInfoOptions = []
+ _existingSizeFields = ["Count of blocks on the device:", "Blocksize:"]
+ partedSystem = fileSystemType["reiserfs"]
+
+ @property
+ def supported(self):
+ """ Is this filesystem a supported type? """
+ supported = self._supported
+ if flags.cmdline.has_key("reiserfs"):
+ supported = self.utilsAvailable
+
+ return supported
+
+ @property
+ def resizeArgs(self):
+ argv = ["-s", "%dM" % (self.targetSize,), self.device]
+ return argv
+
+register_device_format(ReiserFS)
+
+
+class XFS(FS):
+ """ XFS filesystem """
+ _type = "xfs"
+ _mkfs = "mkfs.xfs"
+ _modules = ["xfs"]
+ _labelfs = "xfs_admin"
+ _defaultFormatOptions = ["-f"]
+ _defaultLabelOptions = ["-L"]
+ _maxLabelChars = 16
+ _maxSize = 16 * 1024 * 1024
+ _formattable = True
+ _linuxNative = True
+ _supported = True
+ _bootable = True
+ _dump = True
+ _check = True
+ _packages = ["xfsprogs"]
+ _infofs = "xfs_db"
+ _defaultInfoOptions = ["-c", "\"sb 0\"", "-c", "\"p dblocks\"",
+ "-c", "\"p blocksize\""]
+ _existingSizeFields = ["dblocks =", "blocksize ="]
+ partedSystem = fileSystemType["xfs"]
+
+ def _getLabelArgs(self, label):
+ argv = []
+ argv.extend(self.defaultLabelOptions)
+ argv.extend([label, self.device])
+ return argv
+
+register_device_format(XFS)
+
+
+class HFS(FS):
+ _type = "hfs"
+ _mkfs = "hformat"
+ _modules = ["hfs"]
+ _formattable = True
+ partedSystem = fileSystemType["hfs"]
+
+register_device_format(HFS)
+
+
+class AppleBootstrapFS(HFS):
+ _type = "appleboot"
+ _mountType = "hfs"
+ _name = "Apple Bootstrap"
+ _bootable = True
+ _minSize = 800.00 / 1024.00
+ _maxSize = 1
+
+ @property
+ def supported(self):
+ import pyanaconda.platform as platform
+ return (isinstance(platform.getPlatform(None), platform.NewWorldPPC)
+ and self.utilsAvailable)
+
+ def writeKS(self, f):
+ f.write("appleboot --fstype=%s" % self.type)
+
+register_device_format(AppleBootstrapFS)
+
+
+# this doesn't need to be here
+class HFSPlus(FS):
+ _type = "hfs+"
+ _modules = ["hfsplus"]
+ _udevTypes = ["hfsplus"]
+ partedSystem = fileSystemType["hfs+"]
+
+register_device_format(HFSPlus)
+
+
+class NTFS(FS):
+ """ ntfs filesystem. """
+ _type = "ntfs"
+ _resizefs = "ntfsresize"
+ _fsck = "ntfsresize"
+ _resizable = True
+ _minSize = 1
+ _maxSize = 16 * 1024 * 1024
+ _defaultMountOptions = ["defaults", "ro"]
+ _defaultCheckOptions = ["-c"]
+ _packages = ["ntfsprogs"]
+ _infofs = "ntfsinfo"
+ _defaultInfoOptions = ["-m"]
+ _existingSizeFields = ["Cluster Size:", "Volume Size in Clusters:"]
+ partedSystem = fileSystemType["ntfs"]
+
+ def _fsckFailed(self, rc):
+ if rc != 0:
+ return True
+ return False
+
+ @property
+ def minSize(self):
+ """ The minimum filesystem size in megabytes. """
+ if self._minInstanceSize is None:
+ # we try one time to determine the minimum size.
+ size = self._minSize
+ if self.exists and os.path.exists(self.device):
+ minSize = None
+ buf = iutil.execWithCapture(self.resizefsProg,
+ ["-m", self.device],
+ stderr = "/dev/tty5")
+ for l in buf.split("\n"):
+ if not l.startswith("Minsize"):
+ continue
+ try:
+ min = l.split(":")[1].strip()
+ minSize = int(min) + 250
+ except Exception, e:
+ minSize = None
+ log.warning("Unable to parse output for minimum size on %s: %s" %(self.device, e))
+
+ if minSize is None:
+ log.warning("Unable to discover minimum size of filesystem "
+ "on %s" %(self.device,))
+ else:
+ size = minSize
+
+ self._minInstanceSize = size
+
+ return self._minInstanceSize
+
+ @property
+ def resizeArgs(self):
+ # You must supply at least two '-f' options to ntfsresize or
+ # the proceed question will be presented to you.
+ argv = ["-ff", "-s", "%dM" % (self.targetSize,), self.device]
+ return argv
+
+
+register_device_format(NTFS)
+
+
+# if this isn't going to be mountable it might as well not be here
+class NFS(FS):
+ """ NFS filesystem. """
+ _type = "nfs"
+ _modules = ["nfs"]
+
+ def _deviceCheck(self, devspec):
+ if devspec is not None and ":" not in devspec:
+ raise ValueError("device must be of the form <host>:<path>")
+
+ @property
+ def mountable(self):
+ return False
+
+ def _setDevice(self, devspec):
+ self._deviceCheck(devspec)
+ self._device = devspec
+
+ def _getDevice(self):
+ return self._device
+
+ device = property(lambda f: f._getDevice(),
+ lambda f,d: f._setDevice(d),
+ doc="Full path the device this format occupies")
+
+register_device_format(NFS)
+
+
+class NFSv4(NFS):
+ """ NFSv4 filesystem. """
+ _type = "nfs4"
+ _modules = ["nfs4"]
+
+register_device_format(NFSv4)
+
+
+class Iso9660FS(FS):
+ """ ISO9660 filesystem. """
+ _type = "iso9660"
+ _formattable = False
+ _supported = True
+ _resizable = False
+ _bootable = False
+ _linuxNative = False
+ _dump = False
+ _check = False
+ _migratable = False
+ _defaultMountOptions = ["ro"]
+
+ def writeKS(self, f):
+ return
+
+register_device_format(Iso9660FS)
+
+
+class NoDevFS(FS):
+ """ nodev filesystem base class """
+ _type = "nodev"
+
+ def __init__(self, *args, **kwargs):
+ FS.__init__(self, *args, **kwargs)
+ self.exists = True
+ self.device = self.type
+
+ def _setDevice(self, devspec):
+ self._device = devspec
+
+ def _getExistingSize(self):
+ pass
+
+ def writeKS(self, f):
+ return
+
+register_device_format(NoDevFS)
+
+
+class DevPtsFS(NoDevFS):
+ """ devpts filesystem. """
+ _type = "devpts"
+ _defaultMountOptions = ["gid=5", "mode=620"]
+
+register_device_format(DevPtsFS)
+
+
+# these don't really need to be here
+class ProcFS(NoDevFS):
+ _type = "proc"
+
+register_device_format(ProcFS)
+
+
+class SysFS(NoDevFS):
+ _type = "sysfs"
+
+register_device_format(SysFS)
+
+
+class TmpFS(NoDevFS):
+ _type = "tmpfs"
+
+register_device_format(TmpFS)
+
+
+class BindFS(FS):
+ _type = "bind"
+
+ @property
+ def mountable(self):
+ return True
+
+ def _getExistingSize(self):
+ pass
+
+ def writeKS(self, f):
+ return
+
+register_device_format(BindFS)
+
+
diff --git a/storage/formats/luks.py b/storage/formats/luks.py
new file mode 100644
index 0000000..b164f14
--- /dev/null
+++ b/storage/formats/luks.py
@@ -0,0 +1,352 @@
+# luks.py
+# Device format classes 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
+
+try:
+ import volume_key
+except ImportError:
+ volume_key = None
+
+from ..storage_log import log_method_call
+from ..errors import *
+from ..devicelibs import crypto
+from . import DeviceFormat, register_device_format
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+
+class LUKS(DeviceFormat):
+ """ A LUKS device. """
+ _type = "luks"
+ _name = "LUKS"
+ _lockedName = _("Encrypted")
+ _udevTypes = ["crypto_LUKS"]
+ _formattable = True # can be formatted
+ _supported = False # is supported
+ _linuxNative = True # for clearpart
+ _packages = ["cryptsetup-luks"] # required packages
+
+ def __init__(self, *args, **kwargs):
+ """ Create a LUKS instance.
+
+ Keyword Arguments:
+
+ device -- the path to the underlying device
+ name -- the name of the mapped device
+ uuid -- this device's UUID
+ passphrase -- device passphrase (string)
+ key_file -- path to a file containing a key (string)
+ cipher -- cipher mode string
+ key_size -- key size in bits
+ exists -- indicates whether this is an existing format
+ escrow_cert -- certificate to use for key escrow
+ add_backup_passphrase -- generate a backup passphrase?
+ """
+ log_method_call(self, *args, **kwargs)
+ DeviceFormat.__init__(self, *args, **kwargs)
+ self.cipher = kwargs.get("cipher")
+ self.key_size = kwargs.get("key_size")
+ self.mapName = kwargs.get("name")
+
+ if not self.exists and not self.cipher:
+ self.cipher = "aes-xts-plain"
+ if not self.key_size:
+ # default to the max (512 bits) for aes-xts
+ self.key_size = 512
+
+ # FIXME: these should both be lists, but managing them will be a pain
+ self.__passphrase = kwargs.get("passphrase")
+ self._key_file = kwargs.get("key_file")
+ self.escrow_cert = kwargs.get("escrow_cert")
+ self.add_backup_passphrase = kwargs.get("add_backup_passphrase", False)
+
+ if not self.mapName and self.exists and self.uuid:
+ self.mapName = "luks-%s" % self.uuid
+ elif not self.mapName and self.device:
+ self.mapName = "luks-%s" % os.path.basename(self.device)
+
+ def __str__(self):
+ s = DeviceFormat.__str__(self)
+ if self.__passphrase:
+ passphrase = "(set)"
+ else:
+ passphrase = "(not set)"
+ s += (" cipher = %(cipher)s keySize = %(keySize)s"
+ " mapName = %(mapName)s\n"
+ " keyFile = %(keyFile)s passphrase = %(passphrase)s\n"
+ " escrowCert = %(escrowCert)s addBackup = %(backup)s" %
+ {"cipher": self.cipher, "keySize": self.key_size,
+ "mapName": self.mapName, "keyFile": self._key_file,
+ "passphrase": passphrase, "escrowCert": self.escrow_cert,
+ "backup": self.add_backup_passphrase})
+ return s
+
+ @property
+ def dict(self):
+ d = super(LUKS, self).dict
+ d.update({"cipher": self.cipher, "keySize": self.key_size,
+ "mapName": self.mapName, "hasKey": self.hasKey,
+ "escrowCert": self.escrow_cert,
+ "backup": self.add_backup_passphrase})
+ return d
+
+ @property
+ def name(self):
+ name = self._name
+ # for existing locked devices, show "Encrypted" instead of LUKS
+ if self.hasKey or not self.exists:
+ name = self._name
+ else:
+ name = "%s (%s)" % (self._lockedName, self._name)
+ return name
+
+ def _setPassphrase(self, passphrase):
+ """ Set the passphrase used to access this device. """
+ self.__passphrase = passphrase
+
+ passphrase = property(fset=_setPassphrase)
+
+ @property
+ def hasKey(self):
+ return ((self.__passphrase not in ["", None]) or
+ (self._key_file and os.access(self._key_file, os.R_OK)))
+
+ @property
+ def configured(self):
+ """ To be ready we need a key or passphrase and a map name. """
+ return self.hasKey and self.mapName
+
+ @property
+ def status(self):
+ if not self.exists or not self.mapName:
+ return False
+ return os.path.exists("/dev/mapper/%s" % self.mapName)
+
+ def probe(self):
+ """ Probe for any missing information about this format.
+
+ cipher mode, key size
+ """
+ raise NotImplementedError("probe method not defined for LUKS")
+
+ def setup(self, *args, **kwargs):
+ """ Open, or set up, the format. """
+ log_method_call(self, device=self.device, mapName=self.mapName,
+ type=self.type, status=self.status)
+ if not self.configured:
+ raise LUKSError("luks device not configured")
+
+ if self.status:
+ return
+
+ DeviceFormat.setup(self, *args, **kwargs)
+ crypto.luks_open(self.device, self.mapName,
+ passphrase=self.__passphrase,
+ key_file=self._key_file)
+
+ def teardown(self, *args, **kwargs):
+ """ Close, or tear down, the format. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if not self.exists:
+ raise LUKSError("format has not been created")
+
+ if self.status:
+ log.debug("unmapping %s" % self.mapName)
+ crypto.luks_close(self.mapName)
+
+ def create(self, *args, **kwargs):
+ """ Create the format. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if not self.hasKey:
+ raise LUKSError("luks device has no key/passphrase")
+
+ intf = kwargs.get("intf")
+ w = None
+ if intf:
+ w = intf.waitWindow(_("Formatting"),
+ _("Encrypting %s") % kwargs.get("device",
+ self.device))
+
+ try:
+ DeviceFormat.create(self, *args, **kwargs)
+ crypto.luks_format(self.device,
+ passphrase=self.__passphrase,
+ key_file=self._key_file,
+ cipher=self.cipher,
+ key_size=self.key_size)
+ except Exception:
+ raise
+ else:
+ self.uuid = crypto.luks_uuid(self.device)
+ self.exists = True
+ self.mapName = "luks-%s" % self.uuid
+ self.notifyKernel()
+ finally:
+ if w:
+ w.pop()
+
+ def destroy(self, *args, **kwargs):
+ """ Create the format. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ self.teardown()
+ DeviceFormat.destroy(self, *args, **kwargs)
+
+ @property
+ def keyFile(self):
+ """ Path to key file to be used in /etc/crypttab """
+ return self._key_file
+
+ def addKeyFromFile(self, keyfile):
+ """ Add a new key from a file.
+
+ Add the contents of the specified key file to an available key
+ slot in the LUKS header.
+ """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status, file=keyfile)
+ if not self.exists:
+ raise LUKSError("format has not been created")
+
+ crypto.luks_add_key(self.device,
+ passphrase=self.__passphrase,
+ key_file=self._key_file,
+ new_key_file=keyfile)
+
+ def addPassphrase(self, passphrase):
+ """ Add a new passphrase.
+
+ Add the specified passphrase to an available key slot in the
+ LUKS header.
+ """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if not self.exists:
+ raise LUKSError("format has not been created")
+
+ crypto.luks_add_key(self.device,
+ passphrase=self.__passphrase,
+ key_file=self._key_file,
+ new_passphrase=passphrase)
+
+ def removeKeyFromFile(self, keyfile):
+ """ Remove a key contained in a file.
+
+ Remove key contained in the specified key file from the LUKS
+ header.
+ """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status, file=keyfile)
+ if not self.exists:
+ raise LUKSError("format has not been created")
+
+ crypto.luks_remove_key(self.device,
+ passphrase=self.__passphrase,
+ key_file=self._key_file,
+ del_key_file=keyfile)
+
+
+ def removePassphrase(self, passphrase):
+ """ Remove the specified passphrase from the LUKS header. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if not self.exists:
+ raise LUKSError("format has not been created")
+
+ crypto.luks_remove_key(self.device,
+ passphrase=self.__passphrase,
+ key_file=self._key_file,
+ del_passphrase=passphrase)
+
+ def _escrowVolumeIdent(self, vol):
+ """ Return an escrow packet filename prefix for a volume_key.Volume. """
+ label = vol.label
+ if label is not None:
+ label = label.replace("/", "_")
+ uuid = vol.uuid
+ if uuid is not None:
+ uuid = uuid.replace("/", "_")
+ # uuid is never None on LUKS volumes
+ if label is not None and uuid is not None:
+ volume_ident = "%s-%s" % (label, uuid)
+ elif uuid is not None:
+ volume_ident = uuid
+ elif label is not None:
+ volume_ident = label
+ else:
+ volume_ident = "_unknown"
+ return volume_ident
+
+ def escrow(self, directory, backupPassphrase):
+ log.debug("escrow: escrowVolume start for %s" % self.device)
+ if volume_key is None:
+ raise LUKSError("Missing key escrow support libraries")
+
+ vol = volume_key.Volume.open(self.device)
+ volume_ident = self._escrowVolumeIdent(vol)
+
+ ui = volume_key.UI()
+ # This callback is not expected to be used, let it always fail
+ ui.generic_cb = lambda unused_prompt, unused_echo: None
+ def known_passphrase_cb(unused_prompt, failed_attempts):
+ if failed_attempts == 0:
+ return self.__passphrase
+ return None
+ ui.passphrase_cb = known_passphrase_cb
+
+ log.debug("escrow: getting secret")
+ vol.get_secret(volume_key.SECRET_DEFAULT, ui)
+ log.debug("escrow: creating packet")
+ default_packet = vol.create_packet_assymetric_from_cert_data \
+ (volume_key.SECRET_DEFAULT, self.escrow_cert, ui)
+ log.debug("escrow: packet created")
+ with open("%s/%s-escrow" % (directory, volume_ident), "wb") as f:
+ f.write(default_packet)
+ log.debug("escrow: packet written")
+
+ if self.add_backup_passphrase:
+ log.debug("escrow: adding backup passphrase")
+ vol.add_secret(volume_key.SECRET_PASSPHRASE, backupPassphrase)
+ log.debug("escrow: creating backup packet")
+ backup_passphrase_packet = \
+ vol.create_packet_assymetric_from_cert_data \
+ (volume_key.SECRET_PASSPHRASE, self.escrow_cert, ui)
+ log.debug("escrow: backup packet created")
+ with open("%s/%s-escrow-backup-passphrase" %
+ (directory, volume_ident), "wb") as f:
+ f.write(backup_passphrase_packet)
+ log.debug("escrow: backup packet written")
+
+ log.debug("escrow: escrowVolume done for %s" % repr(self.device))
+
+
+register_device_format(LUKS)
+
diff --git a/storage/formats/lvmpv.py b/storage/formats/lvmpv.py
new file mode 100644
index 0000000..9fe9ba3
--- /dev/null
+++ b/storage/formats/lvmpv.py
@@ -0,0 +1,156 @@
+# lvmpv.py
+# Device format classes 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
+
+from ..storage_log import log_method_call
+from parted import PARTITION_LVM
+from ..errors import *
+from ..devicelibs import lvm
+from . import DeviceFormat, register_device_format
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+
+class LVMPhysicalVolume(DeviceFormat):
+ """ An LVM physical volume. """
+ _type = "lvmpv"
+ _name = "physical volume (LVM)"
+ _udevTypes = ["LVM2_member"]
+ partedFlag = PARTITION_LVM
+ _formattable = True # can be formatted
+ _supported = True # is supported
+ _linuxNative = True # for clearpart
+ _packages = ["lvm2"] # required packages
+
+ def __init__(self, *args, **kwargs):
+ """ Create an LVMPhysicalVolume instance.
+
+ Keyword Arguments:
+
+ device -- path to the underlying device
+ uuid -- this PV's uuid (not the VG uuid)
+ vgName -- the name of the VG this PV belongs to
+ vgUuid -- the UUID of the VG this PV belongs to
+ peStart -- offset of first physical extent
+ exists -- indicates whether this is an existing format
+
+ """
+ log_method_call(self, *args, **kwargs)
+ DeviceFormat.__init__(self, *args, **kwargs)
+ self.vgName = kwargs.get("vgName")
+ self.vgUuid = kwargs.get("vgUuid")
+ # liblvm may be able to tell us this at some point, even
+ # for not-yet-created devices
+ self.peStart = kwargs.get("peStart", 0.1875) # in MB
+
+ def __str__(self):
+ s = DeviceFormat.__str__(self)
+ s += (" vgName = %(vgName)s vgUUID = %(vgUUID)s"
+ " peStart = %(peStart)s" %
+ {"vgName": self.vgName, "vgUUID": self.vgUuid,
+ "peStart": self.peStart})
+ return s
+
+ @property
+ def dict(self):
+ d = super(LVMPhysicalVolume, self).dict
+ d.update({"vgName": self.vgName, "vgUUID": self.vgUuid,
+ "peStart": self.peStart})
+ return d
+
+ def probe(self):
+ """ Probe for any missing information about this device. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if not self.exists:
+ raise PhysicalVolumeError("format has not been created")
+
+ #info = lvm.pvinfo(self.device)
+ #self.vgName = info['vg_name']
+ #self.vgUuid = info['vg_uuid']
+
+ def create(self, *args, **kwargs):
+ """ Create the format. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ intf = kwargs.get("intf")
+ w = None
+ if intf:
+ w = intf.progressWindow(_("Formatting"),
+ _("Creating %s on %s")
+ % (self.name, self.device),
+ 100, pulse = True)
+
+ try:
+ DeviceFormat.create(self, *args, **kwargs)
+ # Consider use of -Z|--zero
+ # -f|--force or -y|--yes may be required
+
+ # lvm has issues with persistence of metadata, so here comes the
+ # hammer...
+ DeviceFormat.destroy(self, *args, **kwargs)
+
+ lvm.pvcreate(self.device, progress=w)
+ except Exception:
+ raise
+ else:
+ self.exists = True
+ self.notifyKernel()
+ finally:
+ if w:
+ w.pop()
+
+ def destroy(self, *args, **kwargs):
+ """ Destroy the format. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if not self.exists:
+ raise PhysicalVolumeError("format has not been created")
+
+ if self.status:
+ raise PhysicalVolumeError("device is active")
+
+ # FIXME: verify path exists?
+ try:
+ lvm.pvremove(self.device)
+ except LVMError:
+ DeviceFormat.destroy(self, *args, **kwargs)
+
+ self.exists = False
+ self.notifyKernel()
+
+ @property
+ def status(self):
+ # XXX hack
+ return (self.exists and self.vgName and
+ os.path.isdir("/dev/mapper/%s" % self.vgName))
+
+ def writeKS(self, f):
+ f.write("pv.%s" % self.uuid)
+
+register_device_format(LVMPhysicalVolume)
+
diff --git a/storage/formats/mdraid.py b/storage/formats/mdraid.py
new file mode 100644
index 0000000..d153807
--- /dev/null
+++ b/storage/formats/mdraid.py
@@ -0,0 +1,124 @@
+# mdraid.py
+# Device format classes 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
+
+from ..storage_log import log_method_call
+from flags import flags
+from parted import PARTITION_RAID
+from ..errors import *
+from ..devicelibs import mdraid
+from . import DeviceFormat, register_device_format
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+
+class MDRaidMember(DeviceFormat):
+ """ An mdraid member disk. """
+ _type = "mdmember"
+ _name = "software RAID"
+ _udevTypes = ["linux_raid_member"]
+ partedFlag = PARTITION_RAID
+ _formattable = True # can be formatted
+ _supported = True # is supported
+ _linuxNative = True # for clearpart
+ _packages = ["mdadm"] # required packages
+
+ def __init__(self, *args, **kwargs):
+ """ Create a MDRaidMember instance.
+
+ Keyword Arguments:
+
+ device -- path to underlying device
+ uuid -- this member device's uuid
+ mdUuid -- the uuid of the array this device belongs to
+ exists -- indicates whether this is an existing format
+
+ """
+ log_method_call(self, *args, **kwargs)
+ DeviceFormat.__init__(self, *args, **kwargs)
+ self.mdUuid = kwargs.get("mdUuid")
+ self.raidMinor = None
+
+ #self.probe()
+ self.biosraid = False
+
+ def __str__(self):
+ s = DeviceFormat.__str__(self)
+ s += (" mdUUID = %(mdUUID)s biosraid = %(biosraid)s" %
+ {"mdUUID": self.mdUuid, "biosraid": self.biosraid})
+ return s
+
+ @property
+ def dict(self):
+ d = super(MDRaidMember, self).dict
+ d.update({"mdUUID": self.mdUuid, "biosraid": self.biosraid})
+ return d
+
+ def probe(self):
+ """ Probe for any missing information about this format. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if not self.exists:
+ raise MDMemberError("format does not exist")
+
+ info = mdraid.mdexamine(self.device)
+ if self.uuid is None:
+ self.uuid = info['uuid']
+ if self.raidMinor is None:
+ self.raidMinor = info['mdMinor']
+
+ def destroy(self, *args, **kwargs):
+ if not self.exists:
+ raise MDMemberError("format does not exist")
+
+ if not os.access(self.device, os.W_OK):
+ raise MDMemberError("device path does not exist")
+
+ mdraid.mddestroy(self.device)
+ self.exists = False
+
+ @property
+ def status(self):
+ # XXX hack -- we don't have a nice way to see if the array is active
+ return False
+
+ @property
+ def hidden(self):
+ return (self._hidden or self.biosraid)
+
+ def writeKS(self, f):
+ f.write("raid.%s" % self.mdUuid)
+
+# nodmraid -> Wether to use BIOS RAID or not
+# Note the anaconda cmdline has not been parsed yet when we're first imported,
+# so we can not use flags.dmraid here
+if not flags.cmdline.has_key("noiswmd") and \
+ not flags.cmdline.has_key("nodmraid"):
+ MDRaidMember._udevTypes.append("isw_raid_member")
+
+register_device_format(MDRaidMember)
+
diff --git a/storage/formats/multipath.py b/storage/formats/multipath.py
new file mode 100644
index 0000000..86c05d6
--- /dev/null
+++ b/storage/formats/multipath.py
@@ -0,0 +1,95 @@
+# multipath.py
+# multipath device formats
+#
+# Copyright (C) 2009 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
+#
+# 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): Peter Jones <pjones@redhat.com>
+#
+
+from ..storage_log import log_method_call
+from ..errors import *
+from . import DeviceFormat, register_device_format
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+class MultipathMember(DeviceFormat):
+ """ A multipath member disk. """
+ _type = "multipath_member"
+ _name = "multipath member device"
+ _udev_types = ["multipath_member"]
+ _formattable = False # can be formatted
+ _supported = True # is supported
+ _linuxNative = False # for clearpart
+ _packages = ["device-mapper-multipath"] # required packages
+ _resizable = False # can be resized
+ _bootable = False # can be used as boot
+ _maxSize = 0 # maximum size in MB
+ _minSize = 0 # minimum size in MB
+ _hidden = True # hide devices with this formatting?
+
+ def __init__(self, *args, **kwargs):
+ """ Create a DeviceFormat instance.
+
+ Keyword Arguments:
+
+ device -- path to the underlying device
+ uuid -- this format's UUID
+ exists -- indicates whether this is an existing format
+
+ On initialization this format is like DeviceFormat
+
+ """
+ log_method_call(self, *args, **kwargs)
+ DeviceFormat.__init__(self, *args, **kwargs)
+
+ # Initialize the attribute that will hold the block object.
+ self._member = None
+
+ def __str__(self):
+ s = DeviceFormat.__str__(self)
+ s += (" member = %(member)r" % {"member": self.member})
+ return s
+
+ def _getMember(self):
+ return self._member
+
+ def _setMember(self, member):
+ self._member = member
+
+ member = property(lambda s: s._getMember(),
+ lambda s,m: s._setMember(m))
+
+ def create(self, *args, **kwargs):
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ raise MultipathMemberError("creation of multipath members is non-sense")
+
+ def destroy(self, *args, **kwargs):
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ raise MultipathMemberError("destruction of multipath members is non-sense")
+
+register_device_format(MultipathMember)
+
diff --git a/storage/formats/prepboot.py b/storage/formats/prepboot.py
new file mode 100644
index 0000000..b1a868b
--- /dev/null
+++ b/storage/formats/prepboot.py
@@ -0,0 +1,64 @@
+# prepboot.py
+# Format class for PPC PReP Boot.
+#
+# 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>
+#
+
+from ..errors import *
+from . import DeviceFormat, register_device_format
+from parted import PARTITION_PREP
+
+class PPCPRePBoot(DeviceFormat):
+ """ Generic device format. """
+ _type = "prepboot"
+ _name = "PPC PReP Boot"
+ _udevTypes = []
+ partedFlag = PARTITION_PREP
+ _formattable = True # can be formatted
+ _linuxNative = True # for clearpart
+ _bootable = True # can be used as boot
+ _maxSize = 4 # maximum size in MB
+ _minSize = 10 # minimum size in MB
+
+ def __init__(self, *args, **kwargs):
+ """ Create a PRePBoot instance.
+
+ Keyword Arguments:
+
+ device -- path to the underlying device
+ exists -- indicates whether this is an existing format
+
+ """
+ DeviceFormat.__init__(self, *args, **kwargs)
+
+ @property
+ def status(self):
+ return False
+
+ @property
+ def supported(self):
+ import pyanaconda.platform as platform
+ return isinstance(platform.getPlatform(None), platform.IPSeriesPPC)
+
+ def writeKS(self, f):
+ f.write("prepboot --fstype=%s" % self.type)
+
+
+register_device_format(PPCPRePBoot)
+
diff --git a/storage/formats/swap.py b/storage/formats/swap.py
new file mode 100644
index 0000000..362f6d5
--- /dev/null
+++ b/storage/formats/swap.py
@@ -0,0 +1,186 @@
+# swap.py
+# Device format classes 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>
+#
+
+from iutil import numeric_type
+from parted import PARTITION_SWAP, fileSystemType
+from ..storage_log import log_method_call
+from ..errors import *
+from ..devicelibs import swap
+from . import DeviceFormat, register_device_format
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+
+class SwapSpace(DeviceFormat):
+ """ Swap space """
+ _type = "swap"
+ _name = None
+ _udevTypes = ["swap"]
+ partedFlag = PARTITION_SWAP
+ partedSystem = fileSystemType["linux-swap(v1)"]
+ _formattable = True # can be formatted
+ _supported = True # is supported
+ _linuxNative = True # for clearpart
+
+ def __init__(self, *args, **kwargs):
+ """ Create a SwapSpace instance.
+
+ Keyword Arguments:
+
+ device -- path to the underlying device
+ uuid -- this swap space's uuid
+ label -- this swap space's label
+ priority -- this swap space's priority
+ exists -- indicates whether this is an existing format
+
+ """
+ log_method_call(self, *args, **kwargs)
+ DeviceFormat.__init__(self, *args, **kwargs)
+
+ self.priority = kwargs.get("priority")
+ self.label = kwargs.get("label")
+
+ def __str__(self):
+ s = DeviceFormat.__str__(self)
+ s += (" priority = %(priority)s label = %(label)s" %
+ {"priority": self.priority, "label": self.label})
+ return s
+
+ @property
+ def dict(self):
+ d = super(SwapSpace, self).dict
+ d.update({"priority": self.priority, "label": self.label})
+ return d
+
+ def _setPriority(self, priority):
+ if priority is None:
+ self._priority = None
+ return
+
+ if not isinstance(priority, int) or not 0 <= priority <= 32767:
+ raise ValueError("swap priority must be an integer between 0 and 32767")
+
+ self._priority = priority
+
+ def _getPriority(self):
+ return self._priority
+
+ priority = property(_getPriority, _setPriority,
+ doc="The priority of the swap device")
+
+ def _getOptions(self):
+ opts = ""
+ if self.priority is not None:
+ opts += "pri=%d" % self.priority
+
+ return opts
+
+ def _setOptions(self, opts):
+ if not opts:
+ self.priority = None
+ return
+
+ for option in opts.split(","):
+ (opt, equals, arg) = option.partition("=")
+ if equals and opt == "pri":
+ try:
+ self.priority = int(arg)
+ except ValueError:
+ log.info("invalid value for swap priority: %s" % arg)
+
+ options = property(_getOptions, _setOptions,
+ doc="The swap device's fstab options string")
+
+ @property
+ def status(self):
+ """ Device status. """
+ return self.exists and swap.swapstatus(self.device)
+
+ def setup(self, *args, **kwargs):
+ """ Open, or set up, a device. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if not self.exists:
+ raise SwapSpaceError("format has not been created")
+
+ if self.status:
+ return
+
+ DeviceFormat.setup(self, *args, **kwargs)
+ swap.swapon(self.device, priority=self.priority)
+
+ def teardown(self, *args, **kwargs):
+ """ Close, or tear down, a device. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ if not self.exists:
+ raise SwapSpaceError("format has not been created")
+
+ if self.status:
+ swap.swapoff(self.device)
+
+ def create(self, *args, **kwargs):
+ """ Create the device. """
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ intf = kwargs.get("intf")
+ force = kwargs.get("force")
+ if not force and self.exists:
+ raise SwapSpaceError("format already exists")
+
+ if force:
+ self.teardown()
+ elif self.status:
+ raise SwapSpaceError("device exists and is active")
+
+ w = None
+ if intf:
+ w = intf.progressWindow(_("Formatting"),
+ _("Creating %s on %s")
+ % (self.type,
+ kwargs.get("device", self.device)),
+ 100, pulse = True)
+
+ try:
+ DeviceFormat.create(self, *args, **kwargs)
+ swap.mkswap(self.device, label=self.label, progress=w)
+ except Exception:
+ raise
+ else:
+ self.exists = True
+ finally:
+ if w:
+ w.pop()
+
+ def writeKS(self, f):
+ f.write("swap")
+
+ if self.label:
+ f.write(" --label=\"%s\"" % self.label)
+
+
+register_device_format(SwapSpace)
+
diff --git a/storage/iscsi.py b/storage/iscsi.py
new file mode 100644
index 0000000..55caa17
--- /dev/null
+++ b/storage/iscsi.py
@@ -0,0 +1,333 @@
+#
+# iscsi.py - iscsi class
+#
+# Copyright (C) 2005, 2006 IBM, Inc. All rights reserved.
+# Copyright (C) 2006 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
+#
+
+from constants import *
+from udev import *
+import os
+import iutil
+from flags import flags
+import logging
+import shutil
+import time
+import hashlib
+import random
+log = logging.getLogger("anaconda")
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+has_libiscsi = True
+try:
+ import libiscsi
+except ImportError:
+ has_libiscsi = False
+
+# Note that stage2 copies all files under /sbin to /usr/sbin
+ISCSID=""
+INITIATOR_FILE="/etc/iscsi/initiatorname.iscsi"
+
+def find_iscsi_files():
+ global ISCSID
+ if ISCSID == "":
+ for dir in ("/usr/sbin", "/tmp/updates", "/mnt/source/RHupdates"):
+ path="%s/iscsid" % (dir,)
+ if os.access(path, os.X_OK):
+ ISCSID=path
+
+def has_iscsi():
+ find_iscsi_files()
+ if ISCSID == "" or not has_libiscsi:
+ return False
+
+ log.info("ISCSID is %s" % (ISCSID,))
+
+ # make sure the module is loaded
+ if not os.access("/sys/module/iscsi_tcp", os.X_OK):
+ return False
+ return True
+
+def randomIname():
+ """Generate a random initiator name the same way as iscsi-iname"""
+
+ s = "iqn.1994-05.com.fedora:01."
+ m = hashlib.md5()
+ u = os.uname()
+ for i in u:
+ m.update(i)
+ dig = m.hexdigest()
+
+ for i in range(0, 6):
+ s += dig[random.randrange(0, 32)]
+ return s
+
+def stabilize(intf = None):
+ # Wait for udev to create the devices for the just added disks
+ if intf:
+ w = intf.waitWindow(_("Scanning iSCSI nodes"),
+ _("Scanning iSCSI nodes"))
+ # It is possible when we get here the events for the new devices
+ # are not send yet, so sleep to make sure the events are fired
+ time.sleep(2)
+ udev_settle()
+ if intf:
+ w.pop()
+
+class iscsi(object):
+ """ iSCSI utility class.
+
+ This class will automatically discover and login to iBFT (or
+ other firmware) configured iscsi devices when the startup() method
+ gets called. It can also be used to manually configure iscsi devices
+ through the addTarget() method.
+
+ As this class needs to make sure certain things like starting iscsid
+ and logging in to firmware discovered disks only happens once
+ and as it keeps a global list of all iSCSI devices it is implemented as
+ a Singleton.
+ """
+
+ def __init__(self):
+ # This list contains all nodes
+ self.nodes = []
+ # This list contains nodes discovered through iBFT (or other firmware)
+ self.ibftNodes = []
+ self._initiator = ""
+ self.initiatorSet = False
+ self.started = False
+
+ if flags.ibft:
+ try:
+ initiatorname = libiscsi.get_firmware_initiator_name()
+ self._initiator = initiatorname
+ self.initiatorSet = True
+ except:
+ pass
+
+ # So that users can write iscsi() to get the singleton instance
+ def __call__(self):
+ return self
+
+ def _getInitiator(self):
+ if self._initiator != "":
+ return self._initiator
+
+ return randomIname()
+
+ def _setInitiator(self, val):
+ if self.initiatorSet and val != self._initiator:
+ raise ValueError, "Unable to change iSCSI initiator name once set"
+ if len(val) == 0:
+ raise ValueError, "Must provide a non-zero length string"
+ self._initiator = val
+
+ initiator = property(_getInitiator, _setInitiator)
+
+ def _startIBFT(self, intf = None):
+ if not flags.ibft:
+ return
+
+ try:
+ found_nodes = libiscsi.discover_firmware()
+ except:
+ # an exception here means there is no ibft firmware, just return
+ return
+
+ for node in found_nodes:
+ try:
+ node.login()
+ log.info("iscsi._startIBFT logged in to %s %s %s" % (node.name, node.address, node.port))
+ self.nodes.append(node)
+ self.ibftNodes.append(node)
+ except IOError, e:
+ log.error("Could not log into ibft iscsi target %s: %s" %
+ (node.name, str(e)))
+ pass
+
+ stabilize(intf)
+
+ def startup(self, intf = None):
+ if self.started:
+ return
+
+ if not has_iscsi():
+ return
+
+ if self._initiator == "":
+ log.info("no initiator set")
+ return
+
+ if intf:
+ w = intf.waitWindow(_("Initializing iSCSI initiator"),
+ _("Initializing iSCSI initiator"))
+
+ log.debug("Setting up %s" % (INITIATOR_FILE, ))
+ log.info("iSCSI initiator name %s", self.initiator)
+ if os.path.exists(INITIATOR_FILE):
+ os.unlink(INITIATOR_FILE)
+ if not os.path.isdir("/etc/iscsi"):
+ os.makedirs("/etc/iscsi", 0755)
+ fd = os.open(INITIATOR_FILE, os.O_RDWR | os.O_CREAT)
+ os.write(fd, "InitiatorName=%s\n" %(self.initiator))
+ os.close(fd)
+ self.initiatorSet = True
+
+ for dir in ['ifaces','isns','nodes','send_targets','slp','static']:
+ fulldir = "/var/lib/iscsi/%s" % (dir,)
+ if not os.path.isdir(fulldir):
+ os.makedirs(fulldir, 0755)
+
+ log.info("iSCSI startup")
+ iutil.execWithRedirect(ISCSID, [],
+ stdout="/dev/tty5", stderr="/dev/tty5")
+ time.sleep(1)
+
+ if intf:
+ w.pop()
+
+ self._startIBFT(intf)
+ self.started = True
+
+ def addTarget(self, ipaddr, port="3260", user=None, pw=None,
+ user_in=None, pw_in=None, intf=None):
+ authinfo = None
+ found = 0
+ logged_in = 0
+
+ if not has_iscsi():
+ raise IOError, _("iSCSI not available")
+ if self._initiator == "":
+ raise ValueError, _("No initiator name set")
+
+ if user or pw or user_in or pw_in:
+ # Note may raise a ValueError
+ authinfo = libiscsi.chapAuthInfo(username=user, password=pw,
+ reverse_username=user_in,
+ reverse_password=pw_in)
+ self.startup(intf)
+
+ # Note may raise an IOError
+ found_nodes = libiscsi.discover_sendtargets(address=ipaddr,
+ port=int(port),
+ authinfo=authinfo)
+ if found_nodes == None:
+ raise IOError, _("No iSCSI nodes discovered")
+
+ if intf:
+ w = intf.waitWindow(_("Logging in to iSCSI nodes"),
+ _("Logging in to iSCSI nodes"))
+
+ for node in found_nodes:
+ # skip nodes we already have
+ if node in self.nodes:
+ continue
+
+ found = found + 1
+ try:
+ if (authinfo):
+ node.setAuth(authinfo)
+ node.login()
+ log.info("iscsi.addTarget logged in to %s %s %s" % (node.name, node.address, node.port))
+ self.nodes.append(node)
+ logged_in = logged_in + 1
+ except IOError, e:
+ log.warning(
+ "Could not log into discovered iscsi target %s: %s" %
+ (node.name, str(e)))
+ # some nodes may require different credentials
+ pass
+
+ if intf:
+ w.pop()
+
+ if found == 0:
+ raise IOError, _("No new iSCSI nodes discovered")
+
+ if logged_in == 0:
+ raise IOError, _("Could not log in to any of the discovered nodes")
+
+ stabilize(intf)
+
+ def writeKS(self, f):
+ if not self.initiatorSet:
+ return
+ f.write("iscsiname %s\n" %(self.initiator,))
+ for n in self.nodes:
+ f.write("iscsi --ipaddr %s --port %s" %(n.address, n.port))
+ auth = n.getAuth()
+ if auth:
+ f.write(" --user %s" % auth.username)
+ f.write(" --password %s" % auth.password)
+ if len(auth.reverse_username):
+ f.write(" --reverse-user %s" % auth.reverse_username)
+ if len(auth.reverse_password):
+ f.write(" --reverse-password %s" % auth.reverse_password)
+ f.write("\n")
+
+ def write(self, instPath, anaconda):
+ if not self.initiatorSet:
+ return
+
+ # set iscsi nodes to autostart
+ root = anaconda.storage.rootDevice
+ for node in self.nodes:
+ autostart = True
+ disks = self.getNodeDisks(node, anaconda.storage)
+ for disk in disks:
+ # nodes used for root get started by the initrd
+ if root.dependsOn(disk):
+ autostart = False
+
+ if autostart:
+ node.setParameter("node.startup", "automatic")
+
+ if not os.path.isdir(instPath + "/etc/iscsi"):
+ os.makedirs(instPath + "/etc/iscsi", 0755)
+ fd = os.open(instPath + INITIATOR_FILE, os.O_RDWR | os.O_CREAT)
+ os.write(fd, "InitiatorName=%s\n" %(self.initiator))
+ os.close(fd)
+
+ # copy "db" files. *sigh*
+ if os.path.isdir(instPath + "/var/lib/iscsi"):
+ shutil.rmtree(instPath + "/var/lib/iscsi")
+ if os.path.isdir("/var/lib/iscsi"):
+ shutil.copytree("/var/lib/iscsi", instPath + "/var/lib/iscsi",
+ symlinks=True)
+
+ def getNode(self, name, address, port):
+ for node in self.nodes:
+ if node.name == name and node.address == address and \
+ node.port == int(port):
+ return node
+
+ return None
+
+ def getNodeDisks(self, node, storage):
+ nodeDisks = []
+ iscsiDisks = storage.devicetree.getDevicesByType("iscsi")
+ for disk in iscsiDisks:
+ if disk.node == node:
+ nodeDisks.append(disk)
+
+ return nodeDisks
+
+# Create iscsi singleton
+iscsi = iscsi()
+
+# vim:tw=78:ts=4:et:sw=4
diff --git a/storage/miscutils.py b/storage/miscutils.py
new file mode 100644
index 0000000..e577497
--- /dev/null
+++ b/storage/miscutils.py
@@ -0,0 +1,57 @@
+# iutil.py stubs
+import os
+
+import logging
+log = logging.getLogger("storage")
+
+def notify_kernel(path, action="change"):
+ """ Signal the kernel that the specified device has changed. """
+ log.debug("notifying kernel of '%s' event on device %s" % (action, path))
+ path = os.path.join(path, "uevent")
+ if not path.startswith("/sys/") or not os.access(path, os.W_OK):
+ log.debug("sysfs path '%s' invalid" % path)
+ raise ValueError("invalid sysfs path")
+
+ f = open(path, "a")
+ f.write("%s\n" % action)
+ f.close()
+
+def get_sysfs_path_by_name(dev_name, class_name="block"):
+ dev_name = os.path.basename(dev_name)
+ sysfs_class_dir = "/sys/class/%s" % class_name
+ dev_path = os.path.join(sysfs_class_dir, dev_name)
+ if os.path.exists(dev_path):
+ return dev_path
+
+import inspect
+def log_method_call(d, *args, **kwargs):
+ classname = d.__class__.__name__
+ methodname = inspect.stack()[1][3]
+ fmt = "%s.%s:"
+ fmt_args = [classname, methodname]
+ for arg in args:
+ fmt += " %s ;"
+ fmt_args.append(arg)
+
+ for k, v in kwargs.items():
+ fmt += " %s: %s ;"
+ fmt_args.extend([k, v])
+
+ log.debug(fmt % tuple(fmt_args))
+
+def numeric_type(num):
+ """ Verify that a value is given as a numeric data type.
+
+ Return the number if the type is sensible or raise ValueError
+ if not.
+ """
+ if num is None:
+ num = 0
+ elif not (isinstance(num, int) or \
+ isinstance(num, long) or \
+ isinstance(num, float)):
+ raise ValueError("value (%s) must be either a number or None" % num)
+
+ return num
+
+
diff --git a/storage/partitioning.py b/storage/partitioning.py
new file mode 100644
index 0000000..719e21f
--- /dev/null
+++ b/storage/partitioning.py
@@ -0,0 +1,1647 @@
+# partitioning.py
+# Disk partitioning functions.
+#
+# 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 sys
+import os
+from operator import add, sub, gt, lt
+
+import parted
+from pykickstart.constants import *
+
+from constants import *
+
+from errors import *
+from deviceaction import *
+from devices import PartitionDevice, LUKSDevice, devicePathToName
+from formats import getFormat
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+def _createFreeSpacePartitions(anaconda):
+ # get a list of disks that have at least one free space region of at
+ # least 100MB
+ disks = []
+ for disk in anaconda.storage.partitioned:
+ if anaconda.storage.clearPartDisks and \
+ (disk.name not in anaconda.storage.clearPartDisks):
+ continue
+
+ part = disk.format.firstPartition
+ while part:
+ if not part.type & parted.PARTITION_FREESPACE:
+ part = part.nextPartition()
+ continue
+
+ if part.getSize(unit="MB") > 100:
+ disks.append(disk)
+ break
+
+ part = part.nextPartition()
+
+ # create a separate pv partition for each disk with free space
+ devs = []
+ for disk in disks:
+ if anaconda.storage.encryptedAutoPart:
+ fmt_type = "luks"
+ fmt_args = {"escrow_cert": anaconda.storage.autoPartEscrowCert,
+ "add_backup_passphrase": anaconda.storage.autoPartAddBackupPassphrase}
+ else:
+ fmt_type = "lvmpv"
+ fmt_args = {}
+ part = anaconda.storage.newPartition(fmt_type=fmt_type,
+ fmt_args=fmt_args,
+ grow=True,
+ disks=[disk])
+ anaconda.storage.createDevice(part)
+ devs.append(part)
+
+ return (disks, devs)
+
+def _schedulePartitions(anaconda, disks):
+ #
+ # Convert storage.autoPartitionRequests into Device instances and
+ # schedule them for creation
+ #
+ # First pass is for partitions only. We'll do LVs later.
+ #
+ for request in anaconda.storage.autoPartitionRequests:
+ if request.asVol:
+ continue
+
+ if request.fstype is None:
+ request.fstype = anaconda.storage.defaultFSType
+ # This is a little unfortunate but let the backend dictate the rootfstype
+ # so that things like live installs can do the right thing
+ if request.mountpoint == "/" and anaconda.backend.rootFsType != None:
+ request.fstype = anaconda.backend.rootFsType
+
+ dev = anaconda.storage.newPartition(fmt_type=request.fstype,
+ size=request.size,
+ grow=request.grow,
+ maxsize=request.maxSize,
+ mountpoint=request.mountpoint,
+ disks=disks,
+ weight=request.weight)
+
+ # schedule the device for creation
+ anaconda.storage.createDevice(dev)
+
+ # make sure preexisting broken lvm/raid configs get out of the way
+ return
+
+def _scheduleLVs(anaconda, devs):
+ if anaconda.storage.encryptedAutoPart:
+ pvs = []
+ for dev in devs:
+ pv = LUKSDevice("luks-%s" % dev.name,
+ format=getFormat("lvmpv", device=dev.path),
+ size=dev.size,
+ parents=dev)
+ pvs.append(pv)
+ anaconda.storage.createDevice(pv)
+ else:
+ pvs = devs
+
+ # create a vg containing all of the autopart pvs
+ vg = anaconda.storage.newVG(pvs=pvs)
+ anaconda.storage.createDevice(vg)
+
+ initialVGSize = vg.size
+
+ #
+ # Convert storage.autoPartitionRequests into Device instances and
+ # schedule them for creation.
+ #
+ # Second pass, for LVs only.
+ for request in anaconda.storage.autoPartitionRequests:
+ if not request.asVol:
+ continue
+
+ if request.requiredSpace and request.requiredSpace > initialVGSize:
+ continue
+
+ if request.fstype is None:
+ request.fstype = anaconda.storage.defaultFSType
+
+ # This is a little unfortunate but let the backend dictate the rootfstype
+ # so that things like live installs can do the right thing
+ if request.mountpoint == "/" and anaconda.backend.rootFsType != None:
+ request.fstype = anaconda.backend.rootFsType
+
+ # FIXME: move this to a function and handle exceptions
+ dev = anaconda.storage.newLV(vg=vg,
+ fmt_type=request.fstype,
+ mountpoint=request.mountpoint,
+ grow=request.grow,
+ maxsize=request.maxSize,
+ size=request.size)
+
+ # schedule the device for creation
+ anaconda.storage.createDevice(dev)
+
+
+def doAutoPartition(anaconda):
+ log.debug("doAutoPartition(%s)" % anaconda)
+ log.debug("doAutoPart: %s" % anaconda.storage.doAutoPart)
+ log.debug("clearPartType: %s" % anaconda.storage.clearPartType)
+ log.debug("clearPartDisks: %s" % anaconda.storage.clearPartDisks)
+ log.debug("autoPartitionRequests: %s" % anaconda.storage.autoPartitionRequests)
+ log.debug("storage.disks: %s" % [d.name for d in anaconda.storage.disks])
+ log.debug("storage.partitioned: %s" % [d.name for d in anaconda.storage.partitioned])
+ log.debug("all names: %s" % [d.name for d in anaconda.storage.devices])
+ if anaconda.dir == DISPATCH_BACK:
+ anaconda.storage.reset()
+ return
+
+ disks = []
+ devs = []
+
+ if anaconda.storage.doAutoPart:
+ clearPartitions(anaconda.storage)
+
+ if anaconda.storage.doAutoPart:
+ (disks, devs) = _createFreeSpacePartitions(anaconda)
+
+ if disks == []:
+ if anaconda.ksdata:
+ msg = _("Could not find enough free space for automatic "
+ "partitioning. Press 'OK' to exit the installer.")
+ else:
+ msg = _("Could not find enough free space for automatic "
+ "partitioning, please use another partitioning method.")
+
+ anaconda.intf.messageWindow(_("Error Partitioning"), msg,
+ custom_icon='error')
+
+ if anaconda.ksdata:
+ sys.exit(0)
+
+ anaconda.storage.reset()
+ return DISPATCH_BACK
+
+ _schedulePartitions(anaconda, disks)
+
+ # sanity check the individual devices
+ log.warning("not sanity checking devices because I don't know how yet")
+
+ # run the autopart function to allocate and grow partitions
+ try:
+ doPartitioning(anaconda.storage,
+ exclusiveDisks=anaconda.storage.clearPartDisks)
+
+ if anaconda.storage.doAutoPart:
+ _scheduleLVs(anaconda, devs)
+
+ # grow LVs
+ growLVM(anaconda.storage)
+ except PartitioningWarning as msg:
+ if not anaconda.ksdata:
+ anaconda.intf.messageWindow(_("Warnings During Automatic "
+ "Partitioning"),
+ _("Following warnings occurred during automatic "
+ "partitioning:\n\n%s") % (msg,),
+ custom_icon='warning')
+ else:
+ log.warning(msg)
+ except PartitioningError as msg:
+ # restore drives to original state
+ anaconda.storage.reset()
+ if not anaconda.ksdata:
+ extra = ""
+
+ if anaconda.displayMode != "t":
+ anaconda.dispatch.skipStep("partition", skip = 0)
+ else:
+ extra = _("\n\nPress 'OK' to exit the installer.")
+ anaconda.intf.messageWindow(_("Error Partitioning"),
+ _("Could not allocate requested partitions: \n\n"
+ "%(msg)s.%(extra)s") % {'msg': msg, 'extra': extra},
+ custom_icon='error')
+
+ if anaconda.ksdata:
+ sys.exit(0)
+ else:
+ return
+
+ # sanity check the collection of devices
+ log.warning("not sanity checking storage config because I don't know how yet")
+ # now do a full check of the requests
+ (errors, warnings) = anaconda.storage.sanityCheck()
+ if warnings:
+ for warning in warnings:
+ log.warning(warning)
+ if errors:
+ errortxt = "\n".join(errors)
+ if anaconda.ksdata:
+ extra = _("\n\nPress 'OK' to exit the installer.")
+ else:
+ extra = _("\n\nPress 'OK' to choose a different partitioning option.")
+
+ anaconda.intf.messageWindow(_("Automatic Partitioning Errors"),
+ _("The following errors occurred with your "
+ "partitioning:\n\n%(errortxt)s\n\n"
+ "This can happen if there is not enough "
+ "space on your hard drive(s) for the "
+ "installation. %(extra)s")
+ % {'errortxt': errortxt, 'extra': extra},
+ custom_icon='error')
+ #
+ # XXX if in kickstart we reboot
+ #
+ if anaconda.ksdata:
+ anaconda.intf.messageWindow(_("Unrecoverable Error"),
+ _("The system will now reboot."))
+ sys.exit(0)
+ anaconda.storage.reset()
+ return DISPATCH_BACK
+
+def shouldClear(device, clearPartType, clearPartDisks=None):
+ if clearPartType not in [CLEARPART_TYPE_LINUX, CLEARPART_TYPE_ALL]:
+ return False
+
+ if isinstance(device, PartitionDevice):
+ # Never clear the special first partition on a Mac disk label, as that
+ # holds the partition table itself.
+ if device.disk.format.partedDisk.type == "mac" and \
+ device.partedPartition.number == 1 and \
+ device.partedPartition.name == "Apple":
+ return False
+
+ # If we got a list of disks to clear, make sure this one's on it
+ if clearPartDisks and device.disk.name not in clearPartDisks:
+ return False
+
+ # We don't want to fool with extended partitions, freespace, &c
+ if device.partType not in [parted.PARTITION_NORMAL,
+ parted.PARTITION_LOGICAL]:
+ return False
+
+ if clearPartType == CLEARPART_TYPE_LINUX and \
+ not device.format.linuxNative and \
+ not device.getFlag(parted.PARTITION_LVM) and \
+ not device.getFlag(parted.PARTITION_RAID) and \
+ not device.getFlag(parted.PARTITION_SWAP):
+ return False
+ elif device.isDisk and not device.partitioned:
+ # If we got a list of disks to clear, make sure this one's on it
+ if clearPartDisks and device.name not in clearPartDisks:
+ return False
+
+ if clearPartType == CLEARPART_TYPE_LINUX and \
+ not device.format.linuxNative:
+ return False
+
+ # Don't clear devices holding install media.
+ if device.protected:
+ return False
+
+ # TODO: do platform-specific checks on ia64, pSeries, iSeries, mac
+
+ return True
+
+def clearPartitions(storage):
+ """ Clear partitions and dependent devices from disks.
+
+ Arguments:
+
+ storage -- a storage.Storage instance
+
+ Keyword arguments:
+
+ None
+
+ NOTES:
+
+ - Needs some error handling, especially for the parted bits.
+
+ """
+ if storage.clearPartType is None or storage.clearPartType == CLEARPART_TYPE_NONE:
+ # not much to do
+ return
+
+ # we are only interested in partitions that physically exist
+ partitions = [p for p in storage.partitions if p.exists]
+ # Sort partitions by descending partition number to minimize confusing
+ # things like multiple "destroy sda5" actions due to parted renumbering
+ # partitions. This can still happen through the UI but it makes sense to
+ # avoid it where possible.
+ partitions.sort(key=lambda p: p.partedPartition.number, reverse=True)
+ for part in partitions:
+ log.debug("clearpart: looking at %s" % part.name)
+ if not shouldClear(part, storage.clearPartType, storage.clearPartDisks):
+ continue
+
+ log.debug("clearing %s" % part.name)
+
+ # XXX is there any argument for not removing incomplete devices?
+ # -- maybe some RAID devices
+ devices = storage.deviceDeps(part)
+ while devices:
+ log.debug("devices to remove: %s" % ([d.name for d in devices],))
+ leaves = [d for d in devices if d.isleaf]
+ log.debug("leaves to remove: %s" % ([d.name for d in leaves],))
+ for leaf in leaves:
+ storage.destroyDevice(leaf)
+ devices.remove(leaf)
+
+ log.debug("partitions: %s" % [p.getDeviceNodeName() for p in part.partedPartition.disk.partitions])
+ storage.destroyDevice(part)
+
+ # now remove any empty extended partitions
+ removeEmptyExtendedPartitions(storage)
+
+def removeEmptyExtendedPartitions(storage):
+ for disk in storage.partitioned:
+ log.debug("checking whether disk %s has an empty extended" % disk.name)
+ extended = disk.format.extendedPartition
+ logical_parts = disk.format.logicalPartitions
+ log.debug("extended is %s ; logicals is %s" % (extended, [p.getDeviceNodeName() for p in logical_parts]))
+ if extended and not logical_parts:
+ log.debug("removing empty extended partition from %s" % disk.name)
+ extended_name = devicePathToName(extended.getDeviceNodeName())
+ extended = storage.devicetree.getDeviceByName(extended_name)
+ storage.destroyDevice(extended)
+ #disk.partedDisk.removePartition(extended.partedPartition)
+
+ for disk in [d for d in storage.disks if d not in storage.partitioned]:
+ # clear any whole-disk formats that need clearing
+ if shouldClear(disk, storage.clearPartType, storage.clearPartDisks):
+ log.debug("clearing %s" % disk.name)
+ devices = storage.deviceDeps(disk)
+ while devices:
+ log.debug("devices to remove: %s" % ([d.name for d in devices],))
+ leaves = [d for d in devices if d.isleaf]
+ log.debug("leaves to remove: %s" % ([d.name for d in leaves],))
+ for leaf in leaves:
+ storage.destroyDevice(leaf)
+ devices.remove(leaf)
+
+ destroy_action = ActionDestroyFormat(disk)
+ newLabel = getFormat("disklabel", device=disk.path)
+ create_action = ActionCreateFormat(disk, format=newLabel)
+ storage.devicetree.registerAction(destroy_action)
+ storage.devicetree.registerAction(create_action)
+
+def partitionCompare(part1, part2):
+ """ More specifically defined partitions come first.
+
+ < 1 => x < y
+ 0 => x == y
+ > 1 => x > y
+ """
+ ret = 0
+
+ if part1.req_base_weight:
+ ret -= part1.req_base_weight
+
+ if part2.req_base_weight:
+ ret += part2.req_base_weight
+
+ # bootable partitions to the front
+ ret -= cmp(part1.req_bootable, part2.req_bootable) * 1000
+
+ # more specific disk specs to the front of the list
+ # req_disks being empty is equivalent to it being an infinitely long list
+ if part1.req_disks and not part2.req_disks:
+ ret -= 500
+ elif not part1.req_disks and part2.req_disks:
+ ret += 500
+ else:
+ ret += cmp(len(part1.req_disks), len(part2.req_disks)) * 500
+
+ # primary-only to the front of the list
+ ret -= cmp(part1.req_primary, part2.req_primary) * 200
+
+ # fixed size requests to the front
+ ret += cmp(part1.req_grow, part2.req_grow) * 100
+
+ # larger requests go to the front of the list
+ ret -= cmp(part1.req_base_size, part2.req_base_size) * 50
+
+ # potentially larger growable requests go to the front
+ if part1.req_grow and part2.req_grow:
+ if not part1.req_max_size and part2.req_max_size:
+ ret -= 25
+ elif part1.req_max_size and not part2.req_max_size:
+ ret += 25
+ else:
+ ret -= cmp(part1.req_max_size, part2.req_max_size) * 25
+
+ # give a little bump based on mountpoint
+ if hasattr(part1.format, "mountpoint") and \
+ hasattr(part2.format, "mountpoint"):
+ ret += cmp(part1.format.mountpoint, part2.format.mountpoint) * 10
+
+ if ret > 0:
+ ret = 1
+ elif ret < 0:
+ ret = -1
+
+ return ret
+
+def getNextPartitionType(disk, no_primary=None):
+ """ Find the type of partition to create next on a disk.
+
+ Return a parted partition type value representing the type of the
+ next partition we will create on this disk.
+
+ If there is only one free primary partition and we can create an
+ extended partition, we do that.
+
+ If there are free primary slots and an extended partition we will
+ recommend creating a primary partition. This can be overridden
+ with the keyword argument no_primary.
+
+ Arguments:
+
+ disk -- a parted.Disk instance representing the disk
+
+ Keyword arguments:
+
+ no_primary -- given a choice between primary and logical
+ partitions, prefer logical
+
+ """
+ part_type = None
+ extended = disk.getExtendedPartition()
+ supports_extended = disk.supportsFeature(parted.DISK_TYPE_EXTENDED)
+ logical_count = len(disk.getLogicalPartitions())
+ max_logicals = disk.getMaxLogicalPartitions()
+ primary_count = disk.primaryPartitionCount
+
+ if primary_count < disk.maxPrimaryPartitionCount:
+ if primary_count == disk.maxPrimaryPartitionCount - 1:
+ # can we make an extended partition? now's our chance.
+ if not extended and supports_extended:
+ part_type = parted.PARTITION_EXTENDED
+ elif not extended:
+ # extended partitions not supported. primary or nothing.
+ if not no_primary:
+ part_type = parted.PARTITION_NORMAL
+ else:
+ # there is an extended and a free primary
+ if not no_primary:
+ part_type = parted.PARTITION_NORMAL
+ elif logical_count < max_logicals:
+ # we have an extended with logical slots, so use one.
+ part_type = parted.PARTITION_LOGICAL
+ else:
+ # there are two or more primary slots left. use one unless we're
+ # not supposed to make primaries.
+ if not no_primary:
+ part_type = parted.PARTITION_NORMAL
+ elif extended and logical_count < max_logicals:
+ part_type = parted.PARTITION_LOGICAL
+ elif extended and logical_count < max_logicals:
+ part_type = parted.PARTITION_LOGICAL
+
+ return part_type
+
+def getBestFreeSpaceRegion(disk, part_type, req_size,
+ boot=None, best_free=None, grow=None):
+ """ Return the "best" free region on the specified disk.
+
+ For non-boot partitions, we return the largest free region on the
+ disk. For boot partitions, we return the first region that is
+ large enough to hold the partition.
+
+ Partition type (parted's PARTITION_NORMAL, PARTITION_LOGICAL) is
+ taken into account when locating a suitable free region.
+
+ For locating the best region from among several disks, the keyword
+ argument best_free allows the specification of a current "best"
+ free region with which to compare the best from this disk. The
+ overall best region is returned.
+
+ Arguments:
+
+ disk -- the disk (a parted.Disk instance)
+ part_type -- the type of partition we want to allocate
+ (one of parted's partition type constants)
+ req_size -- the requested size of the partition (in MB)
+
+ Keyword arguments:
+
+ boot -- indicates whether this will be a bootable partition
+ (boolean)
+ best_free -- current best free region for this partition
+ grow -- indicates whether this is a growable request
+
+ """
+ log.debug("getBestFreeSpaceRegion: disk=%s part_type=%d req_size=%dMB "
+ "boot=%s best=%s grow=%s" %
+ (disk.device.path, part_type, req_size, boot, best_free, grow))
+ extended = disk.getExtendedPartition()
+
+ for _range in disk.getFreeSpaceRegions():
+ if extended:
+ # find out if there is any overlap between this region and the
+ # extended partition
+ log.debug("looking for intersection between extended (%d-%d) and free (%d-%d)" %
+ (extended.geometry.start, extended.geometry.end, _range.start, _range.end))
+
+ # parted.Geometry.overlapsWith can handle this
+ try:
+ free_geom = extended.geometry.intersect(_range)
+ except ArithmeticError, e:
+ # this freespace region does not lie within the extended
+ # partition's geometry
+ free_geom = None
+
+ if (free_geom and part_type == parted.PARTITION_NORMAL) or \
+ (not free_geom and part_type == parted.PARTITION_LOGICAL):
+ log.debug("free region not suitable for request")
+ continue
+
+ if part_type == parted.PARTITION_NORMAL:
+ # we're allocating a primary and the region is not within
+ # the extended, so we use the original region
+ free_geom = _range
+ else:
+ free_geom = _range
+
+ log.debug("current free range is %d-%d (%dMB)" % (free_geom.start,
+ free_geom.end,
+ free_geom.getSize()))
+ free_size = free_geom.getSize()
+
+ # For boot partitions, we want the first suitable region we find.
+ # For growable or extended partitions, we want the largest possible
+ # free region.
+ # For all others, we want the smallest suitable free region.
+ if grow or part_type == parted.PARTITION_EXTENDED:
+ op = gt
+ else:
+ op = lt
+ if req_size <= free_size:
+ if not best_free or op(free_geom.length, best_free.length):
+ best_free = free_geom
+
+ if boot:
+ # if this is a bootable partition we want to
+ # use the first freespace region large enough
+ # to satisfy the request
+ break
+
+ return best_free
+
+def sectorsToSize(sectors, sectorSize):
+ """ Convert length in sectors to size in MB.
+
+ Arguments:
+
+ sectors - sector count
+ sectorSize - sector size for the device, in bytes
+ """
+ return (sectors * sectorSize) / (1024.0 * 1024.0)
+
+def sizeToSectors(size, sectorSize):
+ """ Convert size in MB to length in sectors.
+
+ Arguments:
+
+ size - size in MB
+ sectorSize - sector size for the device, in bytes
+ """
+ return (size * 1024.0 * 1024.0) / sectorSize
+
+def removeNewPartitions(disks, partitions):
+ """ Remove newly added input partitions from input disks.
+
+ Arguments:
+
+ disks -- list of StorageDevice instances with DiskLabel format
+ partitions -- list of PartitionDevice instances
+
+ """
+ log.debug("removing all non-preexisting partitions %s from disk(s) %s"
+ % (["%s(id %d)" % (p.name, p.id) for p in partitions
+ if not p.exists],
+ [d.name for d in disks]))
+ for part in partitions:
+ if part.partedPartition and part.disk in disks:
+ if part.exists:
+ # we're only removing partitions that don't physically exist
+ continue
+
+ if part.isExtended:
+ # these get removed last
+ continue
+
+ part.disk.format.partedDisk.removePartition(part.partedPartition)
+ part.partedPartition = None
+ part.disk = None
+
+ for disk in disks:
+ # remove empty extended so it doesn't interfere
+ extended = disk.format.extendedPartition
+ if extended and not disk.format.logicalPartitions:
+ log.debug("removing empty extended partition from %s" % disk.name)
+ disk.format.partedDisk.removePartition(extended)
+
+def addPartition(disklabel, free, part_type, size):
+ """ Return new partition after adding it to the specified disk.
+
+ Arguments:
+
+ disklabel -- disklabel instance to add partition to
+ free -- where to add the partition (parted.Geometry instance)
+ part_type -- partition type (parted.PARTITION_* constant)
+ size -- size (in MB) of the new partition
+
+ The new partition will be aligned.
+
+ Return value is a parted.Partition instance.
+
+ """
+ start = free.start
+ if not disklabel.alignment.isAligned(free, start):
+ start = disklabel.alignment.alignNearest(free, start)
+
+ if part_type == parted.PARTITION_LOGICAL:
+ # make room for logical partition's metadata
+ start += disklabel.alignment.grainSize
+
+ if start != free.start:
+ log.debug("adjusted start sector from %d to %d" % (free.start, start))
+
+ if part_type == parted.PARTITION_EXTENDED:
+ end = free.end
+ length = end - start + 1
+ else:
+ # size is in MB
+ length = sizeToSectors(size, disklabel.partedDevice.sectorSize)
+ end = start + length - 1
+
+ if not disklabel.endAlignment.isAligned(free, end):
+ end = disklabel.endAlignment.alignNearest(free, end)
+ log.debug("adjusted length from %d to %d" % (length, end - start + 1))
+
+ new_geom = parted.Geometry(device=disklabel.partedDevice,
+ start=start,
+ end=end)
+
+ max_length = disklabel.partedDisk.maxPartitionLength
+ if max_length and new_geom.length > max_length:
+ raise PartitioningError("requested size exceeds maximum allowed")
+
+ # create the partition and add it to the disk
+ partition = parted.Partition(disk=disklabel.partedDisk,
+ type=part_type,
+ geometry=new_geom)
+ constraint = parted.Constraint(exactGeom=new_geom)
+ disklabel.partedDisk.addPartition(partition=partition,
+ constraint=constraint)
+ return partition
+
+def getFreeRegions(disks):
+ """ Return a list of free regions on the specified disks.
+
+ Arguments:
+
+ disks -- list of parted.Disk instances
+
+ Return value is a list of unaligned parted.Geometry instances.
+
+ """
+ free = []
+ for disk in disks:
+ for f in disk.format.partedDisk.getFreeSpaceRegions():
+ if f.length > 0:
+ free.append(f)
+
+ return free
+
+def doPartitioning(storage, exclusiveDisks=None):
+ """ Allocate and grow partitions.
+
+ When this function returns without error, all PartitionDevice
+ instances must have their parents set to the disk they are
+ allocated on, and their partedPartition attribute set to the
+ appropriate parted.Partition instance from their containing
+ disk. All req_xxxx attributes must be unchanged.
+
+ Arguments:
+
+ storage - Main anaconda Storage instance
+
+ Keyword arguments:
+
+ exclusiveDisks -- list of names of disks to use
+
+ """
+ anaconda = storage.anaconda
+ disks = storage.partitioned
+ if exclusiveDisks:
+ disks = [d for d in disks if d.name in exclusiveDisks]
+
+ for disk in disks:
+ disk.setup()
+
+ partitions = storage.partitions[:]
+ for part in storage.partitions:
+ part.req_bootable = False
+
+ if part.exists or \
+ (storage.deviceImmutable(part) and part.partedPartition):
+ # if the partition is preexisting or part of a complex device
+ # then we shouldn't modify it
+ partitions.remove(part)
+ continue
+
+ if not part.exists:
+ # start over with flexible-size requests
+ part.req_size = part.req_base_size
+
+ # FIXME: isn't there a better place for this to happen?
+ try:
+ bootDev = anaconda.platform.bootDevice()
+ except DeviceError:
+ bootDev = None
+
+ if bootDev:
+ bootDev.req_bootable = True
+
+ removeNewPartitions(disks, partitions)
+ free = getFreeRegions(disks)
+ allocatePartitions(storage, disks, partitions, free)
+ growPartitions(disks, partitions, free)
+
+ # The number and thus the name of partitions may have changed now,
+ # allocatePartitions() takes care of this for new partitions, but not
+ # for pre-existing ones, so we update the name of all partitions here
+ for part in storage.partitions:
+ # needed because of XXX hack below
+ if part.isExtended:
+ continue
+ part.updateName()
+
+ # XXX hack -- if we created any extended partitions we need to add
+ # them to the tree now
+ for disk in disks:
+ extended = disk.format.extendedPartition
+ if not extended:
+ # remove any obsolete extended partitions
+ for part in storage.partitions:
+ if part.disk == disk and part.isExtended:
+ storage.devicetree._removeDevice(part, moddisk=False)
+ continue
+
+ extendedName = devicePathToName(extended.getDeviceNodeName())
+ # remove any obsolete extended partitions
+ for part in storage.partitions:
+ if part.disk == disk and part.isExtended and \
+ part.name != extendedName:
+ storage.devicetree._removeDevice(part, moddisk=False)
+
+ device = storage.devicetree.getDeviceByName(extendedName)
+ if device:
+ if not device.exists:
+ # created by us, update partedPartition
+ device.partedPartition = extended
+ continue
+
+ # This is a little odd because normally instantiating a partition
+ # that does not exist means leaving self.parents empty and instead
+ # populating self.req_disks. In this case, we need to skip past
+ # that since this partition is already defined.
+ device = PartitionDevice(extendedName, parents=disk)
+ device.parents = [disk]
+ device.partedPartition = extended
+ # just add the device for now -- we'll handle actions at the last
+ # moment to simplify things
+ storage.devicetree._addDevice(device)
+
+def allocatePartitions(storage, disks, partitions, freespace):
+ """ Allocate partitions based on requested features.
+
+ Non-existing partitions are sorted according to their requested
+ attributes, and then allocated.
+
+ The basic approach to sorting is that the more specifically-
+ defined a request is, the earlier it will be allocated. See
+ the function partitionCompare for details on the sorting
+ criteria.
+
+ The PartitionDevice instances will have their name and parents
+ attributes set once they have been allocated.
+ """
+ log.debug("allocatePartitions: disks=%s ; partitions=%s" %
+ ([d.name for d in disks],
+ ["%s(id %d)" % (p.name, p.id) for p in partitions]))
+
+ new_partitions = [p for p in partitions if not p.exists]
+ new_partitions.sort(cmp=partitionCompare)
+
+ # the following dicts all use device path strings as keys
+ disklabels = {} # DiskLabel instances for each disk
+ all_disks = {} # StorageDevice for each disk
+ for disk in disks:
+ if disk.path not in disklabels.keys():
+ disklabels[disk.path] = disk.format
+ all_disks[disk.path] = disk
+
+ removeNewPartitions(disks, new_partitions)
+
+ for _part in new_partitions:
+ if _part.partedPartition and _part.isExtended:
+ # ignore new extendeds as they are implicit requests
+ continue
+
+ # obtain the set of candidate disks
+ req_disks = []
+ if _part.disk:
+ # we have a already selected a disk for this request
+ req_disks = [_part.disk]
+ elif _part.req_disks:
+ # use the requested disk set
+ req_disks = _part.req_disks
+ else:
+ # no disks specified means any disk will do
+ req_disks = disks
+
+ req_disks.sort(key=lambda d: d.name, cmp=storage.compareDisks)
+ log.debug("allocating partition: %s ; id: %d ; disks: %s ;\n"
+ "boot: %s ; primary: %s ; size: %dMB ; grow: %s ; "
+ "max_size: %s" % (_part.name, _part.id, req_disks,
+ _part.req_bootable, _part.req_primary,
+ _part.req_size, _part.req_grow,
+ _part.req_max_size))
+ free = None
+ use_disk = None
+ part_type = None
+ growth = 0
+ # loop through disks
+ for _disk in req_disks:
+ disklabel = disklabels[_disk.path]
+ sectorSize = disklabel.partedDevice.sectorSize
+ best = None
+ current_free = free
+
+ # for growable requests, we don't want to pass the current free
+ # geometry to getBestFreeRegion -- this allows us to try the
+ # best region from each disk and choose one based on the total
+ # growth it allows
+ if _part.req_grow:
+ current_free = None
+
+ log.debug("checking freespace on %s" % _disk.name)
+
+ new_part_type = getNextPartitionType(disklabel.partedDisk)
+ if new_part_type is None:
+ # can't allocate any more partitions on this disk
+ log.debug("no free partition slots on %s" % _disk.name)
+ continue
+
+ if _part.req_primary and new_part_type != parted.PARTITION_NORMAL:
+ if (disklabel.partedDisk.primaryPartitionCount <
+ disklabel.partedDisk.maxPrimaryPartitionCount):
+ # don't fail to create a primary if there are only three
+ # primary partitions on the disk (#505269)
+ new_part_type = parted.PARTITION_NORMAL
+ else:
+ # we need a primary slot and none are free on this disk
+ log.debug("no primary slots available on %s" % _disk.name)
+ continue
+
+ best = getBestFreeSpaceRegion(disklabel.partedDisk,
+ new_part_type,
+ _part.req_size,
+ best_free=current_free,
+ boot=_part.req_bootable,
+ grow=_part.req_grow)
+
+ if best == free and not _part.req_primary and \
+ new_part_type == parted.PARTITION_NORMAL:
+ # see if we can do better with a logical partition
+ log.debug("not enough free space for primary -- trying logical")
+ new_part_type = getNextPartitionType(disklabel.partedDisk,
+ no_primary=True)
+ if new_part_type:
+ best = getBestFreeSpaceRegion(disklabel.partedDisk,
+ new_part_type,
+ _part.req_size,
+ best_free=current_free,
+ boot=_part.req_bootable,
+ grow=_part.req_grow)
+
+ if best and free != best:
+ update = True
+ if _part.req_grow:
+ log.debug("evaluating growth potential for new layout")
+ new_growth = 0
+ for disk_path in disklabels.keys():
+ log.debug("calculating growth for disk %s" % disk_path)
+ # Now we check, for growable requests, which of the two
+ # free regions will allow for more growth.
+
+ # set up chunks representing the disks' layouts
+ temp_parts = []
+ for _p in new_partitions[:new_partitions.index(_part)]:
+ if _p.disk.path == disk_path:
+ temp_parts.append(_p)
+
+ # add the current request to the temp disk to set up
+ # its partedPartition attribute with a base geometry
+ if disk_path == _disk.path:
+ temp_part = addPartition(disklabel,
+ best,
+ new_part_type,
+ _part.req_size)
+ _part.partedPartition = temp_part
+ _part.disk = _disk
+ temp_parts.append(_part)
+
+ chunks = getDiskChunks(all_disks[disk_path],
+ temp_parts, freespace)
+
+ # grow all growable requests
+ disk_growth = 0
+ disk_sector_size = disklabels[disk_path].partedDevice.sectorSize
+ for chunk in chunks:
+ chunk.growRequests()
+ # record the growth for this layout
+ new_growth += chunk.growth
+ disk_growth += chunk.growth
+ for req in chunk.requests:
+ log.debug("request %d (%s) growth: %d (%dMB) "
+ "size: %dMB" %
+ (req.partition.id,
+ req.partition.name,
+ req.growth,
+ sectorsToSize(req.growth,
+ disk_sector_size),
+ sectorsToSize(req.growth + req.base,
+ disk_sector_size)))
+ log.debug("disk %s growth: %d (%dMB)" %
+ (disk_path, disk_growth,
+ sectorsToSize(disk_growth,
+ disk_sector_size)))
+
+ disklabel.partedDisk.removePartition(temp_part)
+ _part.partedPartition = None
+ _part.disk = None
+
+ log.debug("total growth: %d sectors" % new_growth)
+
+ # update the chosen free region unless the previous
+ # choice yielded greater total growth
+ if new_growth < growth:
+ log.debug("keeping old free: %d < %d" % (new_growth,
+ growth))
+ update = False
+ else:
+ growth = new_growth
+
+ if update:
+ # now we know we are choosing a new free space,
+ # so update the disk and part type
+ log.debug("updating use_disk to %s (%s), type: %s"
+ % (_disk, _disk.name, new_part_type))
+ part_type = new_part_type
+ use_disk = _disk
+ log.debug("new free: %s (%d-%d / %dMB)" % (best,
+ best.start,
+ best.end,
+ best.getSize()))
+ log.debug("new free allows for %d sectors of growth" %
+ growth)
+ free = best
+
+ # For platforms with a fake boot partition (like Apple Bootstrap or
+ # PReP) and multiple disks, we need to ensure the /boot partition
+ # ends up on the same disk as the fake one.
+ mountpoint = getattr(_part.format, "mountpoint", "")
+ if not mountpoint:
+ mountpoint = ""
+
+ if free and (_part.req_bootable or mountpoint.startswith("/boot")):
+ # if this is a bootable partition we want to
+ # use the first freespace region large enough
+ # to satisfy the request
+ log.debug("found free space for bootable request")
+ break
+
+ if free is None:
+ raise PartitioningError("not enough free space on disks")
+
+ _disk = use_disk
+ disklabel = _disk.format
+
+ # create the extended partition if needed
+ if part_type == parted.PARTITION_EXTENDED:
+ log.debug("creating extended partition")
+ addPartition(disklabel, free, part_type, None)
+
+ # now the extended partition exists, so set type to logical
+ part_type = parted.PARTITION_LOGICAL
+
+ # recalculate freespace
+ log.debug("recalculating free space")
+ free = getBestFreeSpaceRegion(disklabel.partedDisk,
+ part_type,
+ _part.req_size,
+ boot=_part.req_bootable,
+ grow=_part.req_grow)
+ if not free:
+ raise PartitioningError("not enough free space after "
+ "creating extended partition")
+
+ partition = addPartition(disklabel, free, part_type, _part.req_size)
+ log.debug("created partition %s of %dMB and added it to %s" %
+ (partition.getDeviceNodeName(), partition.getSize(),
+ disklabel.device))
+
+ # this one sets the name
+ _part.partedPartition = partition
+ _part.disk = _disk
+
+ # parted modifies the partition in the process of adding it to
+ # the disk, so we need to grab the latest version...
+ _part.partedPartition = disklabel.partedDisk.getPartitionByPath(_part.path)
+
+
+class Request(object):
+ """ A partition request.
+
+ Request instances are used for calculating how much to grow
+ partitions.
+ """
+ def __init__(self, partition):
+ """ Create a Request instance.
+
+ Arguments:
+
+ partition -- a PartitionDevice instance
+
+ """
+ self.partition = partition # storage.devices.PartitionDevice
+ self.growth = 0 # growth in sectors
+ self.max_growth = 0 # max growth in sectors
+ self.done = not partition.req_grow # can we grow this request more?
+ self.base = partition.partedPartition.geometry.length # base sectors
+
+ sector_size = partition.partedPartition.disk.device.sectorSize
+
+ if partition.req_grow:
+ limits = filter(lambda l: l > 0,
+ [sizeToSectors(partition.req_max_size, sector_size),
+ sizeToSectors(partition.format.maxSize, sector_size),
+ partition.partedPartition.disk.maxPartitionLength])
+
+ if limits:
+ max_sectors = min(limits)
+ self.max_growth = max_sectors - self.base
+
+ @property
+ def growable(self):
+ """ True if this request is growable. """
+ return self.partition.req_grow
+
+ @property
+ def id(self):
+ """ The id of the PartitionDevice this request corresponds to. """
+ return self.partition.id
+
+ def __str__(self):
+ s = ("%(type)s instance --\n"
+ "id = %(id)s name = %(name)s growable = %(growable)\n"
+ "base = %(base)d growth = %(grow)d max_grow = %(max_grow)d\n"
+ "done = %(done)s" %
+ {"type": self.__class__.__name__, "id": self.id,
+ "name": self.partition.name, "growable": self.growable,
+ "base": self.base, "growth": self.growth,
+ "max_grow": self.max_growth, "done": self.done})
+ return s
+
+
+class Chunk(object):
+ """ A free region on disk from which partitions will be allocated """
+ def __init__(self, geometry, requests=None):
+ """ Create a Chunk instance.
+
+ Arguments:
+
+ geometry -- parted.Geometry instance describing the free space
+
+
+ Keyword Arguments:
+
+ requests -- list of Request instances allocated from this chunk
+
+ """
+ self.geometry = geometry # parted.Geometry
+ self.pool = self.geometry.length # free sector count
+ self.sectorSize = self.geometry.device.sectorSize
+ self.base = 0 # sum of growable requests' base
+ # sizes, in sectors
+ self.requests = [] # list of Request instances
+ if isinstance(requests, list):
+ for req in requests:
+ self.addRequest(req)
+
+ def __str__(self):
+ s = ("%(type)s instance --\n"
+ "device = %(device)s start = %(start)d end = %(end)d\n"
+ "length = %(length)d size = %(size)d pool = %(pool)d\n"
+ "remaining = %(rem)d sectorSize = %(sectorSize)d" %
+ {"type": self.__class__.__name__,
+ "device": self.geometry.device.path,
+ "start": self.geometry.start, "end": self.geometry.end,
+ "length": self.geometry.length, "size": self.geometry.getSize(),
+ "pool": self.pool, "rem": self.remaining,
+ "sectorSize": self.sectorSize})
+
+ return s
+
+ def addRequest(self, req):
+ """ Add a Request to this chunk. """
+ log.debug("adding request %d to chunk %s" % (req.partition.id, self))
+ self.requests.append(req)
+ self.pool -= req.base
+
+ if not req.done:
+ self.base += req.base
+
+ def getRequestByID(self, id):
+ """ Retrieve a request from this chunk based on its id. """
+ for request in self.requests:
+ if request.id == id:
+ return request
+
+ @property
+ def growth(self):
+ """ Sum of growth in sectors for all requests in this chunk. """
+ return sum(r.growth for r in self.requests)
+
+ @property
+ def hasGrowable(self):
+ """ True if this chunk contains at least one growable request. """
+ for req in self.requests:
+ if req.growable:
+ return True
+ return False
+
+ @property
+ def remaining(self):
+ """ Number of requests still being grown in this chunk. """
+ return len([d for d in self.requests if not d.done])
+
+ @property
+ def done(self):
+ """ True if we are finished growing all requests in this chunk. """
+ return self.remaining == 0
+
+ def trimOverGrownRequest(self, req, base=None):
+ """ Enforce max growth and return extra sectors to the pool. """
+ if req.max_growth and req.growth >= req.max_growth:
+ if req.growth > req.max_growth:
+ # we've grown beyond the maximum. put some back.
+ extra = req.growth - req.max_growth
+ log.debug("taking back %d (%dMB) from %d (%s)" %
+ (extra,
+ sectorsToSize(extra, self.sectorSize),
+ req.partition.id, req.partition.name))
+ self.pool += extra
+ req.growth = req.max_growth
+
+ # We're done growing this partition, so it no longer
+ # factors into the growable base used to determine
+ # what fraction of the pool each request gets.
+ if base is not None:
+ base -= req.base
+ req.done = True
+
+ return base
+
+ def growRequests(self):
+ """ Calculate growth amounts for requests in this chunk. """
+ log.debug("Chunk.growRequests: %s" % self)
+
+ # sort the partitions by start sector
+ self.requests.sort(key=lambda r: r.partition.partedPartition.geometry.start)
+
+ # we use this to hold the base for the next loop through the
+ # chunk's requests since we want the base to be the same for
+ # all requests in any given growth iteration
+ new_base = self.base
+ last_pool = 0 # used to track changes to the pool across iterations
+ while not self.done and self.pool and last_pool != self.pool:
+ last_pool = self.pool # to keep from getting stuck
+ self.base = new_base
+ log.debug("%d partitions and %d (%dMB) left in chunk" %
+ (self.remaining, self.pool,
+ sectorsToSize(self.pool, self.sectorSize)))
+ for p in self.requests:
+ if p.done:
+ continue
+
+ # Each partition is allocated free sectors from the pool
+ # based on the relative _base_ sizes of the remaining
+ # growable partitions.
+ share = p.base / float(self.base)
+ growth = int(share * last_pool) # truncate, don't round
+ p.growth += growth
+ self.pool -= growth
+ log.debug("adding %d (%dMB) to %d (%s)" %
+ (growth,
+ sectorsToSize(growth, self.sectorSize),
+ p.partition.id, p.partition.name))
+
+ new_base = self.trimOverGrownRequest(p, base=new_base)
+ log.debug("new grow amount for partition %d (%s) is %d "
+ "sectors, or %dMB" %
+ (p.partition.id, p.partition.name, p.growth,
+ sectorsToSize(p.growth, self.sectorSize)))
+
+ if self.pool:
+ # allocate any leftovers in pool to the first partition
+ # that can still grow
+ for p in self.requests:
+ if p.done:
+ continue
+
+ p.growth += self.pool
+ self.pool = 0
+
+ self.trimOverGrownRequest(p)
+ if self.pool == 0:
+ break
+
+
+def getDiskChunks(disk, partitions, free):
+ """ Return a list of Chunk instances representing a disk.
+
+ Arguments:
+
+ disk -- a StorageDevice with a DiskLabel format
+ partitions -- list of PartitionDevice instances
+ free -- list of parted.Geometry instances representing free space
+
+ Partitions and free regions not on the specified disk are ignored.
+
+ """
+ # list of all new partitions on this disk
+ disk_parts = [p for p in partitions if p.disk == disk and not p.exists]
+ disk_free = [f for f in free if f.device.path == disk.path]
+
+
+ chunks = [Chunk(f) for f in disk_free]
+
+ for p in disk_parts:
+ if p.isExtended:
+ # handle extended partitions specially since they are
+ # indeed very special
+ continue
+
+ for i, f in enumerate(disk_free):
+ if f.contains(p.partedPartition.geometry):
+ chunks[i].addRequest(Request(p))
+ break
+
+ return chunks
+
+def growPartitions(disks, partitions, free):
+ """ Grow all growable partition requests.
+
+ Partitions have already been allocated from chunks of free space on
+ the disks. This function does not modify the ordering of partitions
+ or the free chunks from which they are allocated.
+
+ Free space within a given chunk is allocated to each growable
+ partition allocated from that chunk in an amount corresponding to
+ the ratio of that partition's base size to the sum of the base sizes
+ of all growable partitions allocated from the chunk.
+
+ Arguments:
+
+ disks -- a list of all usable disks (DiskDevice instances)
+ partitions -- a list of all partitions (PartitionDevice instances)
+ free -- a list of all free regions (parted.Geometry instances)
+ """
+ log.debug("growPartitions: disks=%s, partitions=%s" %
+ ([d.name for d in disks],
+ ["%s(id %d)" % (p.name, p.id) for p in partitions]))
+ all_growable = [p for p in partitions if p.req_grow]
+ if not all_growable:
+ log.debug("no growable partitions")
+ return
+
+ log.debug("growable partitions are %s" % [p.name for p in all_growable])
+
+ for disk in disks:
+ log.debug("growing partitions on %s" % disk.name)
+ sector_size = disk.format.partedDevice.sectorSize
+
+ # find any extended partition on this disk
+ extended_geometry = getattr(disk.format.extendedPartition,
+ "geometry",
+ None) # parted.Geometry
+
+ # list of free space regions on this disk prior to partition allocation
+ disk_free = [f for f in free if f.device.path == disk.path]
+ if not disk_free:
+ log.debug("no free space on %s" % disk.name)
+ continue
+
+ chunks = getDiskChunks(disk, partitions, disk_free)
+ log.debug("disk %s has %d chunks" % (disk.name, len(chunks)))
+ # grow the partitions in each chunk as a group
+ for chunk in chunks:
+ if not chunk.hasGrowable:
+ # no growable partitions in this chunk
+ continue
+
+ chunk.growRequests()
+
+ # recalculate partition geometries
+ disklabel = disk.format
+ start = chunk.geometry.start
+ # align start sector as needed
+ if not disklabel.alignment.isAligned(chunk.geometry, start):
+ start = disklabel.alignment.alignUp(chunk.geometry, start)
+ new_partitions = []
+ for p in chunk.requests:
+ ptype = p.partition.partedPartition.type
+ log.debug("partition %s (%d): %s" % (p.partition.name,
+ p.partition.id, ptype))
+ if ptype == parted.PARTITION_EXTENDED:
+ continue
+
+ # XXX since we need one metadata sector before each
+ # logical partition we burn one logical block to
+ # safely align the start of each logical partition
+ if ptype == parted.PARTITION_LOGICAL:
+ start += disklabel.alignment.grainSize
+
+ old_geometry = p.partition.partedPartition.geometry
+ new_length = p.base + p.growth
+ end = start + new_length - 1
+ # align end sector as needed
+ if not disklabel.endAlignment.isAligned(chunk.geometry, end):
+ end = disklabel.endAlignment.alignDown(chunk.geometry, end)
+ new_geometry = parted.Geometry(device=disklabel.partedDevice,
+ start=start,
+ end=end)
+ log.debug("new geometry for %s: %s" % (p.partition.name,
+ new_geometry))
+ start = end + 1
+ new_partition = parted.Partition(disk=disklabel.partedDisk,
+ type=ptype,
+ geometry=new_geometry)
+ new_partitions.append((new_partition, p.partition))
+
+ # remove all new partitions from this chunk
+ removeNewPartitions([disk], [r.partition for r in chunk.requests])
+ log.debug("back from removeNewPartitions")
+
+ # adjust the extended partition as needed
+ # we will ony resize an extended partition that we created
+ log.debug("extended: %s" % extended_geometry)
+ if extended_geometry and \
+ chunk.geometry.contains(extended_geometry):
+ log.debug("setting up new geometry for extended on %s" % disk.name)
+ ext_start = 0
+ ext_end = 0
+ for (partition, device) in new_partitions:
+ if partition.type != parted.PARTITION_LOGICAL:
+ continue
+
+ if not ext_start or partition.geometry.start < ext_start:
+ # account for the logical block difference in start
+ # sector for the extended -v- first logical
+ # (partition.geometry.start is already aligned)
+ ext_start = partition.geometry.start - disklabel.alignment.grainSize
+
+ if not ext_end or partition.geometry.end > ext_end:
+ ext_end = partition.geometry.end
+
+ new_geometry = parted.Geometry(device=disklabel.partedDevice,
+ start=ext_start,
+ end=ext_end)
+ log.debug("new geometry for extended: %s" % new_geometry)
+ new_extended = parted.Partition(disk=disklabel.partedDisk,
+ type=parted.PARTITION_EXTENDED,
+ geometry=new_geometry)
+ ptypes = [p.type for (p, d) in new_partitions]
+ for pt_idx, ptype in enumerate(ptypes):
+ if ptype == parted.PARTITION_LOGICAL:
+ new_partitions.insert(pt_idx, (new_extended, None))
+ break
+
+ # add the partitions with their new geometries to the disk
+ for (partition, device) in new_partitions:
+ if device:
+ name = device.name
+ else:
+ # If there was no extended partition on this disk when
+ # doPartitioning was called we won't have a
+ # PartitionDevice instance for it.
+ name = partition.getDeviceNodeName()
+
+ log.debug("setting %s new geometry: %s" % (name,
+ partition.geometry))
+ constraint = parted.Constraint(exactGeom=partition.geometry)
+ disklabel.partedDisk.addPartition(partition=partition,
+ constraint=constraint)
+ path = partition.path
+ if device:
+ # set the device's name
+ device.partedPartition = partition
+ # without this, the path attr will be a basename. eek.
+ device.disk = disk
+
+ # make sure we store the disk's version of the partition
+ newpart = disklabel.partedDisk.getPartitionByPath(path)
+ device.partedPartition = newpart
+
+
+def hasFreeDiskSpace(storage, exclusiveDisks=None):
+ """Returns True if there is at least 100Mb of free usable space in any of
+ the disks. False otherwise.
+
+ """
+ # FIXME: This function needs to be implemented. It is used, at least, by
+ # iw/partition_gui.py. It should be implemented after the new
+ # doPartitioning code is commited for fedora 13. Since it returns True
+ # the user will always be able to access the create partition screen. If
+ # no partition can be created, the user will go back to the previous
+ # storage state after seeing a warning message.
+ return True
+
+
+def lvCompare(lv1, lv2):
+ """ More specifically defined lvs come first.
+
+ < 1 => x < y
+ 0 => x == y
+ > 1 => x > y
+ """
+ ret = 0
+
+ # larger requests go to the front of the list
+ ret -= cmp(lv1.size, lv2.size) * 100
+
+ # fixed size requests to the front
+ ret += cmp(lv1.req_grow, lv2.req_grow) * 50
+
+ # potentially larger growable requests go to the front
+ if lv1.req_grow and lv2.req_grow:
+ if not lv1.req_max_size and lv2.req_max_size:
+ ret -= 25
+ elif lv1.req_max_size and not lv2.req_max_size:
+ ret += 25
+ else:
+ ret -= cmp(lv1.req_max_size, lv2.req_max_size) * 25
+
+ if ret > 0:
+ ret = 1
+ elif ret < 0:
+ ret = -1
+
+ return ret
+
+def growLVM(storage):
+ """ Grow LVs according to the sizes of the PVs. """
+ for vg in storage.vgs:
+ total_free = vg.freeSpace
+ if total_free < 0:
+ # by now we have allocated the PVs so if there isn't enough
+ # space in the VG we have a real problem
+ raise PartitioningError("not enough space for LVM requests")
+ elif not total_free:
+ log.debug("vg %s has no free space" % vg.name)
+ continue
+
+ log.debug("vg %s: %dMB free ; lvs: %s" % (vg.name, vg.freeSpace,
+ [l.lvname for l in vg.lvs]))
+
+ # figure out how much to grow each LV
+ grow_amounts = {}
+ lv_total = vg.size - total_free
+ log.debug("used: %dMB ; vg.size: %dMB" % (lv_total, vg.size))
+
+ # This first loop is to calculate percentage-based growth
+ # amounts. These are based on total free space.
+ lvs = vg.lvs
+ lvs.sort(cmp=lvCompare)
+ for lv in lvs:
+ if not lv.req_grow or not lv.req_percent:
+ continue
+
+ portion = (lv.req_percent * 0.01)
+ grow = portion * vg.vgFree
+ new_size = lv.req_size + grow
+ if lv.req_max_size and new_size > lv.req_max_size:
+ grow -= (new_size - lv.req_max_size)
+
+ if lv.format.maxSize and lv.format.maxSize < new_size:
+ grow -= (new_size - lv.format.maxSize)
+
+ # clamp growth amount to a multiple of vg extent size
+ grow_amounts[lv.name] = vg.align(grow)
+ total_free -= grow
+ lv_total += grow
+
+ # This second loop is to calculate non-percentage-based growth
+ # amounts. These are based on free space remaining after
+ # calculating percentage-based growth amounts.
+
+ # keep a tab on space not allocated due to format or requested
+ # maximums -- we'll dole it out to subsequent requests
+ leftover = 0
+ for lv in lvs:
+ log.debug("checking lv %s: req_grow: %s ; req_percent: %s"
+ % (lv.name, lv.req_grow, lv.req_percent))
+ if not lv.req_grow or lv.req_percent:
+ continue
+
+ portion = float(lv.req_size) / float(lv_total)
+ grow = portion * total_free
+ log.debug("grow is %dMB" % grow)
+
+ todo = lvs[lvs.index(lv):]
+ unallocated = reduce(lambda x,y: x+y,
+ [l.req_size for l in todo
+ if l.req_grow and not l.req_percent])
+ extra_portion = float(lv.req_size) / float(unallocated)
+ extra = extra_portion * leftover
+ log.debug("%s getting %dMB (%d%%) of %dMB leftover space"
+ % (lv.name, extra, extra_portion * 100, leftover))
+ leftover -= extra
+ grow += extra
+ log.debug("grow is now %dMB" % grow)
+ max_size = lv.req_size + grow
+ if lv.req_max_size and max_size > lv.req_max_size:
+ max_size = lv.req_max_size
+
+ if lv.format.maxSize and max_size > lv.format.maxSize:
+ max_size = lv.format.maxSize
+
+ log.debug("max size is %dMB" % max_size)
+ max_size = max_size
+ leftover += (lv.req_size + grow) - max_size
+ grow = max_size - lv.req_size
+ log.debug("lv %s gets %dMB" % (lv.name, vg.align(grow)))
+ grow_amounts[lv.name] = vg.align(grow)
+
+ if not grow_amounts:
+ log.debug("no growable lvs in vg %s" % vg.name)
+ continue
+
+ # now grow the lvs by the amounts we've calculated above
+ for lv in lvs:
+ if lv.name not in grow_amounts.keys():
+ continue
+ lv.size += grow_amounts[lv.name]
+
+ # now there shouldn't be any free space left, but if there is we
+ # should allocate it to one of the LVs
+ vg_free = vg.freeSpace
+ log.debug("vg %s has %dMB free" % (vg.name, vg_free))
+ if vg_free:
+ for lv in lvs:
+ if not lv.req_grow:
+ continue
+
+ if lv.req_max_size and lv.size == lv.req_max_size:
+ continue
+
+ if lv.format.maxSize and lv.size == lv.format.maxSize:
+ continue
+
+ # first come, first served
+ projected = lv.size + vg.freeSpace
+ if lv.req_max_size and projected > lv.req_max_size:
+ projected = lv.req_max_size
+
+ if lv.format.maxSize and projected > lv.format.maxSize:
+ projected = lv.format.maxSize
+
+ log.debug("giving leftover %dMB to %s" % (projected - lv.size,
+ lv.name))
+ lv.size = projected
+
diff --git a/storage/partspec.py b/storage/partspec.py
new file mode 100644
index 0000000..8ad81ca
--- /dev/null
+++ b/storage/partspec.py
@@ -0,0 +1,66 @@
+# partspec.py
+#
+# 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): Chris Lumens <clumens@redhat.com>
+#
+
+class PartSpec(object):
+ def __init__(self, mountpoint=None, fstype=None, size=None, maxSize=None,
+ grow=False, asVol=False, weight=0, requiredSpace=0):
+ """ Create a new storage specification. These are used to specify
+ the default partitioning layout as an object before we have the
+ storage system up and running. The attributes are obvious
+ except for the following:
+
+ asVol -- Should this be allocated as a logical volume? If not,
+ it will be allocated as a partition.
+ weight -- An integer that modifies the sort algorithm for partition
+ requests. A larger value means the partition will end up
+ closer to the front of the disk. This is mainly used to
+ make sure /boot ends up in front, and any special (PReP,
+ appleboot, etc.) partitions end up in front of /boot.
+ This value means nothing if asVol=False.
+ requiredSpace -- This value is only taken into account if
+ asVol=True, and specifies the size in MB that the
+ containing VG must be for this PartSpec to even
+ get used. The VG's size is calculated before any
+ other LVs are created inside it. If not enough
+ space exists, this PartSpec will never get turned
+ into an LV.
+ """
+
+ self.mountpoint = mountpoint
+ self.fstype = fstype
+ self.size = size
+ self.maxSize = maxSize
+ self.grow = grow
+ self.asVol = asVol
+ self.weight = weight
+ self.requiredSpace = requiredSpace
+
+ def __str__(self):
+ s = ("%(type)s instance (%(id)s) -- \n"
+ " mountpoint = %(mountpoint)s asVol = %(asVol)s\n"
+ " weight = %(weight)s fstype = %(fstype)s\n"
+ " size = %(size)s maxSize = %(maxSize)s grow = %(grow)s\n" %
+ {"type": self.__class__.__name__, "id": "%#x" % id(self),
+ "mountpoint": self.mountpoint, "asVol": self.asVol,
+ "weight": self.weight, "fstype": self.fstype, "size": self.size,
+ "maxSize": self.maxSize, "grow": self.grow})
+
+ return s
diff --git a/storage/storage_log.py b/storage/storage_log.py
new file mode 100644
index 0000000..a52513d
--- /dev/null
+++ b/storage/storage_log.py
@@ -0,0 +1,32 @@
+import logging
+import anaconda_log
+import inspect
+
+def log_method_call(d, *args, **kwargs):
+ classname = d.__class__.__name__
+ stack = inspect.stack()
+ methodname = stack[1][3]
+
+ spaces = len(stack) * ' '
+ fmt = "%s%s.%s:"
+ fmt_args = [spaces, classname, methodname]
+
+ for arg in args:
+ fmt += " %s ;"
+ fmt_args.append(arg)
+
+ for k, v in kwargs.items():
+ fmt += " %s: %s ;"
+ fmt_args.extend([k, v])
+
+ logger.debug(fmt % tuple(fmt_args))
+
+
+logger = logging.getLogger("storage")
+logger.setLevel(logging.DEBUG)
+anaconda_log.logger.addFileHandler("/tmp/storage.log", logger, logging.DEBUG)
+anaconda_log.logger.addFileHandler("/dev/tty3", logger,
+ anaconda_log.DEFAULT_TTY_LEVEL,
+ anaconda_log.TTY_FORMAT,
+ autoLevel=True)
+anaconda_log.logger.forwardToSyslog(logger)
diff --git a/storage/udev.py b/storage/udev.py
new file mode 100644
index 0000000..625bfca
--- /dev/null
+++ b/storage/udev.py
@@ -0,0 +1,515 @@
+# udev.py
+# Python module for querying the udev database for device information.
+#
+# 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 iutil
+from errors import *
+from baseudev import *
+
+import logging
+log = logging.getLogger("storage")
+
+def udev_resolve_devspec(devspec):
+ if not devspec:
+ return None
+
+ import devices as _devices
+ ret = None
+ for dev in udev_get_block_devices():
+ if devspec.startswith("LABEL="):
+ if udev_device_get_label(dev) == devspec[6:]:
+ ret = dev
+ break
+ elif devspec.startswith("UUID="):
+ if udev_device_get_uuid(dev) == devspec[5:]:
+ ret = dev
+ break
+ elif udev_device_get_name(dev) == _devices.devicePathToName(devspec):
+ ret = dev
+ break
+ else:
+ for link in dev["symlinks"]:
+ if devspec == link:
+ ret = dev
+ break
+
+ del _devices
+ if ret:
+ return udev_device_get_name(ret)
+
+def udev_resolve_glob(glob):
+ import fnmatch
+ ret = []
+
+ if not glob:
+ return ret
+
+ for dev in udev_get_block_devices():
+ name = udev_device_get_name(dev)
+
+ if fnmatch.fnmatch(name, glob):
+ ret.append(name)
+ else:
+ for link in dev["symlinks"]:
+ if fnmatch.fnmatch(link, glob):
+ ret.append(name)
+
+ return ret
+
+def udev_get_block_devices():
+ udev_settle()
+ entries = []
+ for path in udev_enumerate_block_devices():
+ entry = udev_get_block_device(path)
+ if entry:
+ if entry["name"].startswith("md"):
+ # mdraid is really braindead, when a device is stopped
+ # it is no longer usefull in anyway (and we should not
+ # probe it) yet it still sticks around, see bug rh523387
+ state = None
+ state_file = "/sys/%s/md/array_state" % entry["sysfs_path"]
+ if os.access(state_file, os.R_OK):
+ with open(state_file) as state_f:
+ state = state_f.read().strip()
+ if state == "clear":
+ continue
+ entries.append(entry)
+ return entries
+
+def __is_blacklisted_blockdev(dev_name):
+ """Is this a blockdev we never want for an install?"""
+ if dev_name.startswith("loop") or dev_name.startswith("ram") or dev_name.startswith("fd"):
+ return True
+
+ dev_path = "/sys/class/block/%s/device/model" %(dev_name,)
+ if os.path.exists(dev_path):
+ with open(dev_path) as dev_f:
+ model = dev_f.read()
+ for bad in ("IBM *STMF KERNEL", "SCEI Flash-5", "DGC LUNZ"):
+ if model.find(bad) != -1:
+ log.info("ignoring %s with model %s" %(dev_name, model))
+ return True
+
+ return False
+
+def udev_enumerate_block_devices():
+ import os.path
+
+ return filter(lambda d: not __is_blacklisted_blockdev(os.path.basename(d)),
+ udev_enumerate_devices(deviceClass="block"))
+
+def udev_get_block_device(sysfs_path):
+ dev = udev_get_device(sysfs_path)
+ if not dev or not dev.has_key("name"):
+ return None
+ else:
+ return dev
+
+
+# These are functions for retrieving specific pieces of information from
+# udev database entries.
+def udev_device_get_name(udev_info):
+ """ Return the best name for a device based on the udev db data. """
+ return udev_info.get("DM_NAME", udev_info["name"])
+
+def udev_device_get_format(udev_info):
+ """ Return a device's format type as reported by udev. """
+ return udev_info.get("ID_FS_TYPE")
+
+def udev_device_get_uuid(udev_info):
+ """ Get the UUID from the device's format as reported by udev. """
+ md_uuid = udev_info.get("MD_UUID")
+ uuid = udev_info.get("ID_FS_UUID")
+ # we don't want to return the array's uuid as a member's uuid
+ if uuid and not md_uuid == uuid:
+ return udev_info.get("ID_FS_UUID")
+
+def udev_device_get_label(udev_info):
+ """ Get the label from the device's format as reported by udev. """
+ return udev_info.get("ID_FS_LABEL")
+
+def udev_device_is_dm(info):
+ """ Return True if the device is a device-mapper device. """
+ return info.has_key("DM_NAME")
+
+def udev_device_is_md(info):
+ """ Return True if the device is a mdraid array device. """
+ # Don't identify partitions on mdraid arrays as raid arrays
+ if udev_device_is_partition(info):
+ return False
+ # isw raid set *members* have MD_METADATA set, but are not arrays!
+ return info.has_key("MD_METADATA") and \
+ info.get("ID_FS_TYPE") != "isw_raid_member"
+
+def udev_device_is_cciss(info):
+ """ Return True if the device is a CCISS device. """
+ return udev_device_get_name(info).startswith("cciss")
+
+def udev_device_is_dasd(info):
+ """ Return True if the device is a dasd device. """
+ devname = info.get("DEVNAME")
+ if devname:
+ return devname.startswith("dasd")
+ else:
+ return False
+
+def udev_device_is_zfcp(info):
+ """ Return True if the device is a zfcp device. """
+ if info.get("DEVTYPE") != "disk":
+ return False
+
+ subsystem = "/sys" + info.get("sysfs_path")
+
+ while True:
+ topdir = os.path.realpath(os.path.dirname(subsystem))
+ driver = "%s/driver" % (topdir,)
+
+ if os.path.islink(driver):
+ subsystemname = os.path.basename(os.readlink(subsystem))
+ drivername = os.path.basename(os.readlink(driver))
+
+ if subsystemname == 'ccw' and drivername == 'zfcp':
+ return True
+
+ newsubsystem = os.path.dirname(topdir)
+
+ if newsubsystem == topdir:
+ break
+
+ subsystem = newsubsystem + "/subsystem"
+
+ return False
+
+def udev_device_get_zfcp_attribute(info, attr=None):
+ """ Return the value of the specified attribute of the zfcp device. """
+ if not attr:
+ log.debug("udev_device_get_zfcp_attribute() called with attr=None")
+ return None
+
+ attribute = "/sys%s/device/%s" % (info.get("sysfs_path"), attr,)
+ attribute = os.path.realpath(attribute)
+
+ if not os.path.isfile(attribute):
+ log.warning("%s is not a valid zfcp attribute" % (attribute,))
+ return None
+
+ with open(attribute, "r") as f:
+ return f.read().strip()
+
+def udev_device_get_dasd_bus_id(info):
+ """ Return the CCW bus ID of the dasd device. """
+ return info.get("sysfs_path").split("/")[-3]
+
+def udev_device_get_dasd_flag(info, flag=None):
+ """ Return the specified flag for the dasd device. """
+ if flag is None:
+ return None
+
+ path = "/sys" + info.get("sysfs_path") + "/device/" + flag
+ if not os.path.isfile(path):
+ return None
+
+ with open(path, "r") as f:
+ return f.read().strip()
+
+def udev_device_is_cdrom(info):
+ """ Return True if the device is an optical drive. """
+ # FIXME: how can we differentiate USB drives from CD-ROM drives?
+ # -- USB drives also generate a sdX device.
+ return info.get("ID_CDROM") == "1"
+
+def udev_device_is_disk(info):
+ """ Return True is the device is a disk. """
+ if udev_device_is_cdrom(info):
+ return False
+ has_range = os.path.exists("/sys/%s/range" % info['sysfs_path'])
+ return info.get("DEVTYPE") == "disk" or has_range
+
+def udev_device_is_partition(info):
+ has_start = os.path.exists("/sys/%s/start" % info['sysfs_path'])
+ return info.get("DEVTYPE") == "partition" or has_start
+
+def udev_device_get_serial(udev_info):
+ """ Get the serial number/UUID from the device as reported by udev. """
+ return udev_info.get("ID_SERIAL_SHORT", udev_info.get("ID_SERIAL"))
+
+def udev_device_get_wwid(udev_info):
+ """ The WWID of a device is typically just its serial number, but with
+ colons in the name to make it more readable. """
+ serial = udev_device_get_serial(udev_info)
+
+ if serial and len(serial) == 32:
+ retval = ""
+ for i in range(0, 16):
+ retval += serial[i*2:i*2+2] + ":"
+
+ return retval[0:-1]
+
+ return ""
+
+def udev_device_get_vendor(udev_info):
+ """ Get the vendor of the device as reported by udev. """
+ return udev_info.get("ID_VENDOR_FROM_DATABASE", udev_info.get("ID_VENDOR"))
+
+def udev_device_get_model(udev_info):
+ """ Get the model of the device as reported by udev. """
+ return udev_info.get("ID_MODEL_FROM_DATABASE", udev_info.get("ID_MODEL"))
+
+def udev_device_get_bus(udev_info):
+ """ Get the bus a device is connected to the system by. """
+ return udev_info.get("ID_BUS", "").upper()
+
+def udev_device_get_path(info):
+ return info["ID_PATH"]
+
+def udev_device_get_sysfs_path(info):
+ return info['sysfs_path']
+
+def udev_device_get_major(info):
+ return int(info["MAJOR"])
+
+def udev_device_get_minor(info):
+ return int(info["MINOR"])
+
+def udev_device_get_md_level(info):
+ return info.get("MD_LEVEL")
+
+def udev_device_get_md_devices(info):
+ return int(info["MD_DEVICES"])
+
+def udev_device_get_md_uuid(info):
+ return info["MD_UUID"]
+
+def udev_device_get_md_container(info):
+ return info.get("MD_CONTAINER")
+
+def udev_device_get_md_name(info):
+ return info.get("MD_DEVNAME")
+
+def udev_device_get_vg_name(info):
+ return info['LVM2_VG_NAME']
+
+def udev_device_get_vg_uuid(info):
+ return info['LVM2_VG_UUID']
+
+def udev_device_get_vg_size(info):
+ # lvm's decmial precision is not configurable, so we tell it to use
+ # KB and convert to MB here
+ return float(info['LVM2_VG_SIZE']) / 1024
+
+def udev_device_get_vg_free(info):
+ # lvm's decmial precision is not configurable, so we tell it to use
+ # KB and convert to MB here
+ return float(info['LVM2_VG_FREE']) / 1024
+
+def udev_device_get_vg_extent_size(info):
+ # lvm's decmial precision is not configurable, so we tell it to use
+ # KB and convert to MB here
+ return float(info['LVM2_VG_EXTENT_SIZE']) / 1024
+
+def udev_device_get_vg_extent_count(info):
+ return int(info['LVM2_VG_EXTENT_COUNT'])
+
+def udev_device_get_vg_free_extents(info):
+ return int(info['LVM2_VG_FREE_COUNT'])
+
+def udev_device_get_vg_pv_count(info):
+ return int(info['LVM2_PV_COUNT'])
+
+def udev_device_get_pv_pe_start(info):
+ # lvm's decmial precision is not configurable, so we tell it to use
+ # KB and convert to MB here
+ return float(info['LVM2_PE_START']) / 1024
+
+def udev_device_get_lv_names(info):
+ names = info['LVM2_LV_NAME']
+ if not names:
+ names = []
+ elif not isinstance(names, list):
+ names = [names]
+ return names
+
+def udev_device_get_lv_uuids(info):
+ uuids = info['LVM2_LV_UUID']
+ if not uuids:
+ uuids = []
+ elif not isinstance(uuids, list):
+ uuids = [uuids]
+ return uuids
+
+def udev_device_get_lv_sizes(info):
+ # lvm's decmial precision is not configurable, so we tell it to use
+ # KB and convert to MB here
+ sizes = info['LVM2_LV_SIZE']
+ if not sizes:
+ sizes = []
+ elif not isinstance(sizes, list):
+ sizes = [sizes]
+
+ return [float(s) / 1024 for s in sizes]
+
+def udev_device_get_lv_attr(info):
+ attr = info['LVM2_LV_ATTR']
+ if not attr:
+ attr = []
+ elif not isinstance(attr, list):
+ attr = [attr]
+ return attr
+
+def udev_device_is_biosraid(info):
+ # Note that this function does *not* identify raid sets.
+ # Tests to see if device is parto of a dmraid set.
+ # dmraid and mdraid have the same ID_FS_USAGE string, ID_FS_TYPE has a
+ # string that describes the type of dmraid (isw_raid_member...), I don't
+ # want to maintain a list and mdraid's ID_FS_TYPE='linux_raid_member', so
+ # dmraid will be everything that is raid and not linux_raid_member
+ from formats.dmraid import DMRaidMember
+ from formats.mdraid import MDRaidMember
+ if info.has_key("ID_FS_TYPE") and \
+ (info["ID_FS_TYPE"] in DMRaidMember._udevTypes or \
+ info["ID_FS_TYPE"] in MDRaidMember._udevTypes) and \
+ info["ID_FS_TYPE"] != "linux_raid_member":
+ return True
+
+ return False
+
+def udev_device_get_dmraid_partition_disk(info):
+ try:
+ p_index = info["DM_NAME"].rindex("p")
+ except (KeyError, AttributeError, ValueError):
+ return None
+
+ if not info["DM_NAME"][p_index+1:].isdigit():
+ return None
+
+ return info["DM_NAME"][:p_index]
+
+def udev_device_is_dmraid_partition(info, devicetree):
+ diskname = udev_device_get_dmraid_partition_disk(info)
+ dmraid_devices = devicetree.getDevicesByType("dm-raid array")
+
+ for device in dmraid_devices:
+ if diskname == device.name:
+ return True
+
+ return False
+
+def udev_device_is_multipath_partition(info, devicetree):
+ """ Return True if the device is a partition of a multipath device. """
+ if not udev_device_is_dm(info):
+ return False
+ if not info["DM_NAME"].startswith("mpath"):
+ return False
+ diskname = udev_device_get_dmraid_partition_disk(info)
+ if diskname is None:
+ return False
+
+ # this is sort of a lame check, but basically, if diskname gave us "mpath0"
+ # and we start with "mpath" but we're not "mpath0", then we must be
+ # "mpath0" plus some non-numeric crap.
+ if diskname != info["DM_NAME"]:
+ return True
+
+ return False
+
+def udev_device_get_multipath_partition_disk(info):
+ """ Return True if the device is a partition of a multipath device. """
+ # XXX PJFIX This whole function is crap.
+ if not udev_device_is_dm(info):
+ return False
+ if not info["DM_NAME"].startswith("mpath"):
+ return False
+ diskname = udev_device_get_dmraid_partition_disk(info)
+ return diskname
+
+def udev_device_is_multipath_member(info):
+ """ Return True if the device is part of a multipath. """
+ return info.get("ID_FS_TYPE") == "multipath_member"
+
+def udev_device_get_multipath_name(info):
+ """ Return the name of the multipath that the device is a member of. """
+ if udev_device_is_multipath_member(info):
+ return info['ID_MPATH_NAME']
+ return None
+
+# iscsi disks have ID_PATH in the form of:
+# ip-${iscsi_address}:${iscsi_port}-iscsi-${iscsi_tgtname}-lun-${lun}
+# Note that in the case of IPV6 iscsi_address itself can contain :
+# too, but iscsi_port never contains :
+def udev_device_is_iscsi(info):
+ try:
+ path_components = udev_device_get_path(info).split("-")
+
+ if info["ID_BUS"] == "scsi" and len(path_components) >= 6 and \
+ path_components[0] == "ip" and path_components[2] == "iscsi":
+ return True
+ except KeyError:
+ pass
+
+ return False
+
+def udev_device_get_iscsi_name(info):
+ path_components = udev_device_get_path(info).split("-")
+
+ # Tricky, the name itself contains atleast 1 - char
+ return "-".join(path_components[3:len(path_components)-2])
+
+def udev_device_get_iscsi_address(info):
+ path_components = udev_device_get_path(info).split("-")
+
+ # IPV6 addresses contain : within the address, so take everything
+ # before the last : as address
+ return ":".join(path_components[1].split(":")[:-1])
+
+def udev_device_get_iscsi_port(info):
+ path_components = udev_device_get_path(info).split("-")
+
+ # IPV6 contains : within the address, the part after the last : is the port
+ return path_components[1].split(":")[-1]
+
+# fcoe disks have ID_PATH in the form of:
+# pci-eth#-fc-${id}
+# fcoe parts look like this:
+# pci-eth#-fc-${id}-part#
+def udev_device_is_fcoe(info):
+ try:
+ path_components = udev_device_get_path(info).split("-")
+
+ if info["ID_BUS"] == "scsi" and len(path_components) >= 4 and \
+ path_components[0] == "pci" and path_components[2] == "fc" and \
+ path_components[1][0:3] == "eth":
+ return True
+ except LookupError:
+ pass
+
+ return False
+
+def udev_device_get_fcoe_nic(info):
+ path_components = udev_device_get_path(info).split("-")
+
+ return path_components[1]
+
+def udev_device_get_fcoe_identifier(info):
+ path_components = udev_device_get_path(info).split("-")
+
+ return path_components[3]
diff --git a/storage/zfcp.py b/storage/zfcp.py
new file mode 100644
index 0000000..7692cad
--- /dev/null
+++ b/storage/zfcp.py
@@ -0,0 +1,441 @@
+#
+# zfcp.py - mainframe zfcp configuration install data
+#
+# Copyright (C) 2001, 2002, 2003, 2004 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
+#
+# Author(s): Karsten Hopp <karsten@redhat.com>
+#
+
+import string
+import os
+from constants import *
+from udev import udev_settle
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("anaconda")
+import warnings
+
+def loggedWriteLineToFile(fn, value):
+ f = open(fn, "w")
+ log.debug("echo %s > %s" % (value, fn))
+ f.write("%s\n" % (value))
+ f.close()
+
+zfcpsysfs = "/sys/bus/ccw/drivers/zfcp"
+scsidevsysfs = "/sys/bus/scsi/devices"
+
+class ZFCPDevice:
+ def __init__(self, devnum, wwpn, fcplun):
+ self.devnum = self.sanitizeDeviceInput(devnum)
+ self.wwpn = self.sanitizeWWPNInput(wwpn)
+ self.fcplun = self.sanitizeFCPLInput(fcplun)
+
+ if not self.checkValidDevice(self.devnum):
+ raise ValueError, _("You have not specified a device number or the number is invalid")
+ if not self.checkValidWWPN(self.wwpn):
+ raise ValueError, _("You have not specified a worldwide port name or the name is invalid.")
+ if not self.checkValidFCPLun(self.fcplun):
+ raise ValueError, _("You have not specified a FCP LUN or the number is invalid.")
+
+ def __str__(self):
+ return "%s %s %s" %(self.devnum, self.wwpn, self.fcplun)
+
+ def sanitizeDeviceInput(self, dev):
+ if dev is None or dev == "":
+ return None
+ dev = dev.lower()
+ bus = dev[:string.rfind(dev, ".") + 1]
+ dev = dev[string.rfind(dev, ".") + 1:]
+ dev = "0" * (4 - len(dev)) + dev
+ if not len(bus):
+ return "0.0." + dev
+ else:
+ return bus + dev
+
+ def sanitizeWWPNInput(self, id):
+ if id is None or id == "":
+ return None
+ id = id.lower()
+ if id[:2] != "0x":
+ return "0x" + id
+ return id
+
+ # ZFCP LUNs are usually entered as 16 bit, sysfs accepts only 64 bit
+ # (#125632), expand with zeroes if necessary
+ def sanitizeFCPLInput(self, lun):
+ if lun is None or lun == "":
+ return None
+ lun = lun.lower()
+ if lun[:2] == "0x":
+ lun = lun[2:]
+ lun = "0x" + "0" * (4 - len(lun)) + lun
+ lun = lun + "0" * (16 - len(lun) + 2)
+ return lun
+
+ def _hextest(self, hex):
+ try:
+ int(hex, 16)
+ return True
+ except TypeError:
+ return False
+
+ def checkValidDevice(self, id):
+ if id is None or id == "":
+ return False
+ if len(id) != 8: # p.e. 0.0.0600
+ return False
+ if id[0] not in string.digits or id[2] not in string.digits:
+ return False
+ if id[1] != "." or id[3] != ".":
+ return False
+ return self._hextest(id[4:])
+
+ def checkValid64BitHex(self, hex):
+ if hex is None or hex == "":
+ return False
+ if len(hex) != 18:
+ return False
+ return self._hextest(hex)
+ checkValidWWPN = checkValidFCPLun = checkValid64BitHex
+
+ def onlineDevice(self):
+ online = "%s/%s/online" %(zfcpsysfs, self.devnum)
+ portadd = "%s/%s/port_add" %(zfcpsysfs, self.devnum)
+ portdir = "%s/%s/%s" %(zfcpsysfs, self.devnum, self.wwpn)
+ unitadd = "%s/unit_add" %(portdir)
+ unitdir = "%s/%s" %(portdir, self.fcplun)
+ failed = "%s/failed" %(unitdir)
+
+ try:
+ if not os.path.exists(online):
+ loggedWriteLineToFile("/proc/cio_ignore",
+ "free %s" %(self.devnum,))
+ udev_settle()
+ except IOError as e:
+ raise ValueError, _("Could not free zFCP device %(devnum)s from "
+ "device ignore list (%(e)s).") \
+ % {'devnum': self.devnum, 'e': e}
+
+ if not os.path.exists(online):
+ raise ValueError, _(
+ "zFCP device %s not found, not even in device ignore list."
+ %(self.devnum,))
+
+ try:
+ f = open(online, "r")
+ devonline = f.readline().strip()
+ f.close()
+ if devonline != "1":
+ loggedWriteLineToFile(online, "1")
+ else:
+ log.info("zFCP device %s already online." %(self.devnum,))
+ except IOError as e:
+ raise ValueError, _("Could not set zFCP device %(devnum)s "
+ "online (%(e)s).") \
+ % {'devnum': self.devnum, 'e': e}
+
+ if not os.path.exists(portdir):
+ if os.path.exists(portadd):
+ # older zfcp sysfs interface
+ try:
+ loggedWriteLineToFile(portadd, self.wwpn)
+ udev_settle()
+ except IOError as e:
+ raise ValueError, _("Could not add WWPN %(wwpn)s to zFCP "
+ "device %(devnum)s (%(e)s).") \
+ % {'wwpn': self.wwpn,
+ 'devnum': self.devnum,
+ 'e': e}
+ else:
+ # newer zfcp sysfs interface with auto port scan
+ raise ValueError, _("WWPN %(wwpn)s not found at zFCP device "
+ "%(devnum)s.") % {'wwpn': self.wwpn,
+ 'devnum': self.devnum}
+ else:
+ if os.path.exists(portadd):
+ # older zfcp sysfs interface
+ log.info("WWPN %(wwpn)s at zFCP device %(devnum)s already "
+ "there.") % {'wwpn': self.wwpn,
+ 'devnum': self.devnum}
+
+ if not os.path.exists(unitdir):
+ try:
+ loggedWriteLineToFile(unitadd, self.fcplun)
+ udev_settle()
+ except IOError as e:
+ raise ValueError, _("Could not add LUN %(fcplun)s to WWPN "
+ "%(wwpn)s on zFCP device %(devnum)s "
+ "(%(e)s).") \
+ % {'fcplun': self.fcplun, 'wwpn': self.wwpn,
+ 'devnum': self.devnum, 'e': e}
+ else:
+ raise ValueError, _("LUN %(fcplun)s at WWPN %(wwpn)s on zFCP "
+ "device %(devnum)s already configured.") \
+ % {'fcplun': self.fcplun,
+ 'wwpn': self.wwpn,
+ 'devnum': self.devnum}
+
+ fail = "0"
+ try:
+ f = open(failed, "r")
+ fail = f.readline().strip()
+ f.close()
+ except IOError as e:
+ raise ValueError, _("Could not read failed attribute of LUN "
+ "%(fcplun)s at WWPN %(wwpn)s on zFCP device "
+ "%(devnum)s (%(e)s).") \
+ % {'fcplun': self.fcplun,
+ 'wwpn': self.wwpn,
+ 'devnum': self.devnum,
+ 'e': e}
+ if fail != "0":
+ self.offlineDevice()
+ raise ValueError, _("Failed LUN %(fcplun)s at WWPN %(wwpn)s on "
+ "zFCP device %(devnum)s removed again.") \
+ % {'fcplun': self.fcplun,
+ 'wwpn': self.wwpn,
+ 'devnum': self.devnum}
+
+ return True
+
+ def offlineSCSIDevice(self):
+ f = open("/proc/scsi/scsi", "r")
+ lines = f.readlines()
+ f.close()
+ # alternatively iterate over /sys/bus/scsi/devices/*:0:*:*/
+
+ for line in lines:
+ if not line.startswith("Host"):
+ continue
+ scsihost = string.split(line)
+ host = scsihost[1]
+ channel = "0"
+ id = scsihost[5]
+ lun = scsihost[7]
+ scsidev = "%s:%s:%s:%s" % (host[4:], channel, id, lun)
+ fcpsysfs = "%s/%s" % (scsidevsysfs, scsidev)
+ scsidel = "%s/%s/delete" % (scsidevsysfs, scsidev)
+
+ f = open("%s/hba_id" %(fcpsysfs), "r")
+ fcphbasysfs = f.readline().strip()
+ f.close()
+ f = open("%s/wwpn" %(fcpsysfs), "r")
+ fcpwwpnsysfs = f.readline().strip()
+ f.close()
+ f = open("%s/fcp_lun" %(fcpsysfs), "r")
+ fcplunsysfs = f.readline().strip()
+ f.close()
+
+ if fcphbasysfs == self.devnum \
+ and fcpwwpnsysfs == self.wwpn \
+ and fcplunsysfs == self.fcplun:
+ loggedWriteLineToFile(scsidel, "1")
+ udev_settle()
+ return
+
+ log.warn("no scsi device found to delete for zfcp %s %s %s"
+ %(self.devnum, self.wwpn, self.fcplun))
+
+ def offlineDevice(self):
+ offline = "%s/%s/online" %(zfcpsysfs, self.devnum)
+ portadd = "%s/%s/port_add" %(zfcpsysfs, self.devnum)
+ portremove = "%s/%s/port_remove" %(zfcpsysfs, self.devnum)
+ unitremove = "%s/%s/%s/unit_remove" %(zfcpsysfs, self.devnum, self.wwpn)
+ portdir = "%s/%s/%s" %(zfcpsysfs, self.devnum, self.wwpn)
+ devdir = "%s/%s" %(zfcpsysfs, self.devnum)
+
+ try:
+ self.offlineSCSIDevice()
+ except IOError as e:
+ raise ValueError, _("Could not correctly delete SCSI device of "
+ "zFCP %(devnum)s %(wwpn)s %(fcplun)s "
+ "(%(e)s).") \
+ % {'devnum': self.devnum, 'wwpn': self.wwpn,
+ 'fcplun': self.fcplun, 'e': e}
+
+ try:
+ loggedWriteLineToFile(unitremove, self.fcplun)
+ except IOError as e:
+ raise ValueError, _("Could not remove LUN %(fcplun)s at WWPN "
+ "%(wwpn)s on zFCP device %(devnum)s "
+ "(%(e)s).") \
+ % {'fcplun': self.fcplun, 'wwpn': self.wwpn,
+ 'devnum': self.devnum, 'e': e}
+
+ if os.path.exists(portadd):
+ # only try to remove ports with older zfcp sysfs interface
+ for lun in os.listdir(portdir):
+ if lun.startswith("0x") and \
+ os.path.isdir(os.path.join(portdir, lun)):
+ log.info("Not removing WWPN %s at zFCP device %s since port still has other LUNs, e.g. %s."
+ %(self.wwpn, self.devnum, lun))
+ return True
+
+ try:
+ loggedWriteLineToFile(portremove, self.wwpn)
+ except IOError as e:
+ raise ValueError, _("Could not remove WWPN %(wwpn)s on zFCP "
+ "device %(devnum)s (%(e)s).") \
+ % {'wwpn': self.wwpn,
+ 'devnum': self.devnum, 'e': e}
+
+ if os.path.exists(portadd):
+ # older zfcp sysfs interface
+ for port in os.listdir(devdir):
+ if port.startswith("0x") and \
+ os.path.isdir(os.path.join(devdir, port)):
+ log.info("Not setting zFCP device %s offline since it still has other ports, e.g. %s."
+ %(self.devnum, port))
+ return True
+ else:
+ # newer zfcp sysfs interface with auto port scan
+ import glob
+ luns = glob.glob("%s/0x????????????????/0x????????????????"
+ %(devdir,))
+ if len(luns) != 0:
+ log.info("Not setting zFCP device %s offline since it still has other LUNs, e.g. %s."
+ %(self.devnum, luns[0]))
+ return True
+
+ try:
+ loggedWriteLineToFile(offline, "0")
+ except IOError as e:
+ raise ValueError, _("Could not set zFCP device %(devnum)s "
+ "offline (%(e)s).") \
+ % {'devnum': self.devnum, 'e': e}
+
+ return True
+
+class ZFCP:
+ """ ZFCP utility class.
+
+ This class will automatically online to ZFCP drives configured in
+ /tmp/fcpconfig when the startup() method gets called. It can also be
+ used to manually configure ZFCP devices through the addFCP() method.
+
+ As this class needs to make sure that /tmp/fcpconfig configured
+ drives are only onlined once and as it keeps a global list of all ZFCP
+ devices it is implemented as a Singleton.
+ """
+
+ def __init__(self):
+ self.fcpdevs = []
+ self.hasReadConfig = False
+ self.down = True
+
+ # So that users can write zfcp() to get the singleton instance
+ def __call__(self):
+ return self
+
+ def readConfig(self):
+ try:
+ f = open("/tmp/fcpconfig", "r")
+ except IOError:
+ log.info("no /tmp/fcpconfig; not configuring zfcp")
+ return
+
+ lines = f.readlines()
+ f.close()
+ for line in lines:
+ # each line is a string separated list of values to describe a dev
+ # there are two valid formats for the line:
+ # devnum scsiid wwpn scsilun fcplun (scsiid + scsilun ignored)
+ # devnum wwpn fcplun
+ line = string.strip(line).lower()
+ if line.startswith("#"):
+ continue
+ fcpconf = string.split(line)
+ if len(fcpconf) == 3:
+ devnum = fcpconf[0]
+ wwpn = fcpconf[1]
+ fcplun = fcpconf[2]
+ elif len(fcpconf) == 5:
+ warnings.warn("SCSI ID and SCSI LUN values for ZFCP devices are ignored and deprecated.", DeprecationWarning)
+ devnum = fcpconf[0]
+ wwpn = fcpconf[2]
+ fcplun = fcpconf[4]
+ else:
+ log.warn("Invalid line found in /tmp/fcpconfig!")
+ continue
+
+ try:
+ self.addFCP(devnum, wwpn, fcplun)
+ except ValueError, e:
+ log.warn(str(e))
+ continue
+
+ def addFCP(self, devnum, wwpn, fcplun):
+ d = ZFCPDevice(devnum, wwpn, fcplun)
+ if d.onlineDevice():
+ self.fcpdevs.append(d)
+
+ def shutdown(self):
+ if self.down:
+ return
+ self.down = True
+ if len(self.fcpdevs) == 0:
+ return
+ for d in self.fcpdevs:
+ try:
+ d.offlineDevice()
+ except ValueError, e:
+ log.warn(str(e))
+
+ def startup(self):
+ if not self.down:
+ return
+ self.down = False
+ if not self.hasReadConfig:
+ self.readConfig()
+ self.hasReadConfig = True
+ # readConfig calls addFCP which calls onlineDevice already
+ return
+
+ if len(self.fcpdevs) == 0:
+ return
+ for d in self.fcpdevs:
+ try:
+ d.onlineDevice()
+ except ValueError, e:
+ log.warn(str(e))
+
+ def writeKS(self, f):
+ if len(self.fcpdevs) == 0:
+ return
+ for d in self.fcpdevs:
+ f.write("zfcp --devnum %s --wwpn %s --fcplun %s\n" %(d.devnum,
+ d.wwpn,
+ d.fcplun))
+
+ def write(self, instPath):
+ if len(self.fcpdevs) == 0:
+ return
+ f = open(instPath + "/etc/zfcp.conf", "w")
+ for d in self.fcpdevs:
+ f.write("%s\n" %(d,))
+ f.close()
+
+ f = open(instPath + "/etc/modprobe.conf", "a")
+ f.write("alias scsi_hostadapter zfcp\n")
+ f.close()
+
+# Create ZFCP singleton
+ZFCP = ZFCP()
+
+# vim:tw=78:ts=4:et:sw=4