0707010002C028000041FD000001F4000001F4000000034A856CF800000000000000FD0000000200000000000000000000000200000000. 0707010002C029000041C0000001F4000001F4000000044A856CC800000000000000FD0000000200000000000000000000000800000000storage 0707010002C02B000081A4000001F4000001F4000000014A843E43000026E0000000FD0000000200000000000000000000001100000000storage/iscsi.py #
# 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 .
#
from constants 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)
iutil.execWithRedirect("udevadm", [ "settle" ],
stdout = "/dev/tty5", stderr="/dev/tty5",
searchPath = 1)
if intf:
w.pop()
class iscsi(object):
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
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()
self.nodes.append(node)
self.ibftNodes.append(node)
except:
# FIXME, what to do when we cannot log in to a firmware
# provided node ??
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()
self.nodes.append(node)
logged_in = logged_in + 1
except:
# 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
if not flags.test:
root = anaconda.id.storage.fsset.rootDevice
# set iscsi nodes to autostart
for node in self.nodes:
autostart = True
disks = self.getNodeDisks(node, anaconda.id.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
# vim:tw=78:ts=4:et:sw=4
0707010002C02C000081A4000001F4000001F4000000014A843E4300011F46000000FD0000000200000000000000000000001400000000storage/__init__.py # __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
#
import os
import time
import stat
import errno
import sys
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 udev import *
import iscsi
import fcoe
import zfcp
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
import logging
log = logging.getLogger("storage")
def storageInitialize(anaconda):
storage = anaconda.id.storage
storage.shutdown()
if anaconda.dir == DISPATCH_BACK:
return
# XXX I don't understand why I have to do this
udev_trigger(subsystem="block")
# Set up the protected partitions list now.
if os.path.exists("/dev/live") and \
stat.S_ISBLK(os.stat("/dev/live")[stat.ST_MODE]):
target = os.readlink("/dev/live")
storage.protectedDevSpecs = [target]
storage.reset()
elif anaconda.methodstr and anaconda.methodstr.startswith("hd:"):
method = anaconda.methodstr[3:]
devspec = method.split(":", 3)[0]
storage.protectedDevSpecs.append(devspec)
storage.reset()
if not storage.protectedDevices:
if anaconda.id.getUpgrade():
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.id.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.id.storage.encryptedAutoPart or new_luks) and \
not anaconda.id.storage.encryptionPassphrase:
while True:
(passphrase, retrofit) = anaconda.intf.getLuksPassphrase(preexist=existing_luks)
if passphrase:
anaconda.id.storage.encryptionPassphrase = passphrase
anaconda.id.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.id.storage)
anaconda.id.storage.encryptedAutoPart = False
break
if anaconda.id.storage.encryptionPassphrase:
for dev in anaconda.id.storage.devices:
if dev.format.type == "luks" and not dev.format.exists:
dev.format.passphrase = anaconda.id.storage.encryptionPassphrase
if anaconda.isKickstart:
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.
for disk in anaconda.id.storage.disks:
disk.teardown()
if rc == 0:
return DISPATCH_BACK
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.encryptionRetrofit = False
self.reinitializeDisks = False
self.zeroMbr = None
self.protectedDevSpecs = []
self.autoPartitionRequests = []
self.__luksDevs = {}
self.iscsi = iscsi.iscsi()
self.fcoe = fcoe.fcoe()
self.zfcp = zfcp.ZFCP()
self._nextID = 0
self.defaultFSType = get_default_filesystem_type()
self.defaultBootFSType = get_default_filesystem_type(boot=True)
# 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)
self.fsset = FSSet(self.devicetree)
def doIt(self):
self.devicetree.processActions()
self.doEncryptionPassphraseRetrofits()
# now set the boot partition's flag
try:
boot = self.anaconda.platform.bootDevice()
except DeviceError:
boot = None
else:
if hasattr(boot, "bootable"):
boot.bootable = True
boot.disk.commit()
@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()
if self.anaconda.id.getUpgrade():
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)
self.devicetree.populate()
self.fsset = FSSet(self.devicetree)
self.anaconda.id.rootParts = None
self.anaconda.id.upgradeRoot = None
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.path)
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 isinstance(device, DiskDevice) and device.mediaPresent:
disks.append(device)
disks.sort(key=lambda d: d.name)
return disks
@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 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:
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:
if array.minor is not None:
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.udev_trigger(subsystem="block")
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:
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))
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.id.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 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.disks:
if disk.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:
hn = safeLvmName(hn)
vgtemplate = "vg_%s" % (hn.split('.')[0].lower(),)
else:
hn = safeLvmName(hn)
vgtemplate = "vg_%s" % (hn.lower(),)
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:
tmp = safeLvmName(mountpoint)
lvtemplate = "lv_%s" % (tmp,)
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.fsset.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 %s "
"MB which is lower than recommended "
"for a normal %s install.")
%(self.anaconda.backend.getMinimumSizeMB("/"),
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 %s partition is less than %s "
"megabytes which is lower than recommended "
"for a normal %s install.")
%(mount, size, 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 write(self, instPath):
self.fsset.write(instPath)
self.iscsi.write(instPath, self.anaconda)
self.fcoe.write(instPath, self.anaconda)
self.zfcp.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 getReleaseString(mountpoint):
relName = None
relVer = None
filename = "%s/etc/redhat-release" % mountpoint
if os.access(filename, os.R_OK):
with open(filename) as f:
try:
relstr = f.readline().strip()
except (IOError, AttributeError):
relstr = ""
# get the release name and version
# assumes that form is something
# like "Red Hat Linux release 6.2 (Zoot)"
(product, sep, version) = relstr.partition(" release ")
if sep:
relName = product
relVer = version.split()[0]
return (relName, relVer)
def findExistingRootDevices(anaconda, upgradeany=False):
""" Return a list of all root filesystems in the device tree. """
rootDevs = []
if not os.path.exists(anaconda.rootPath):
iutil.mkdirChain(anaconda.rootPath)
roots = []
for device in anaconda.id.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.id.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.id.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(chroot=rootPath)
# 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.id.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
if flags.setupFilesystems:
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("\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):
self.devicetree = devicetree
self.cryptTab = None
self.blkidTab = None
self.origFStab = None
self.active = False
self._dev = None
self._devpts = None
self._sysfs = None
self._proc = None
self._devshm = None
self.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:
log.warning("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=""):
""" 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 = ""
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:
self.devicetree._addDevice(device)
def fsFreeSpace(self, chroot='/'):
space = []
for device in self.devices:
if not device.format.mountable or \
not device.format.status:
continue
path = "%s/%s" % (chroot, device.format.mountpoint)
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):
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]
try:
device.setup()
device.format.setup()
except SuspendError:
if anaconda.intf:
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
anaconda.intf.messageWindow(_("Error"), msg)
sys.exit(0)
except DeviceError as (msg, path):
if anaconda.intf:
if upgrading:
err = _("Error enabling swap device %s: %s\n\n"
"The /etc/fstab on your upgrade partition "
"does not reference a valid swap "
"device.\n\nPress OK to exit the "
"installer") % (path, msg)
else:
err = _("Error enabling swap device %s: %s\n\n"
"This most likely means this swap "
"device has not been initialized.\n\n"
"Press OK to exit the installer.") % \
(path, msg)
anaconda.intf.messageWindow(_("Error"), err)
sys.exit(0)
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:
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 to exit the "
"installer.")
% (device.format.mountpoint,))
else:
intf.messageWindow(_("Invalid mount point"),
_("An error occurred when trying "
"to create %s: %s. This is "
"a fatal error and the install "
"cannot continue.\n\n"
"Press to exit the "
"installer.")
% (device.format.mountpoint, e.strerror))
log.error("OSError: (%d) %s" % (e.errno, e.strerror))
sys.exit(0)
except SystemError as (num, msg):
if raiseErrors:
raise
if intf and not device.format.linuxNative:
ret = intf.messageWindow(_("Unable to mount filesystem"),
_("An error occurred mounting "
"device %s as %s. You may "
"continue installation, but "
"there may be problems.") %
(device.path,
device.format.mountpoint),
type="custom",
custom_icon="warning",
custom_buttons=[_("_Exit installer"),
_("_Continue")])
if ret == 0:
sys.exit(0)
else:
continue
log.error("SystemError: (%d) %s" % (num, msg) )
sys.exit(0)
except FSError as msg:
if intf:
intf.messageWindow(_("Unable to mount filesystem"),
_("An error occurred mounting "
"device %s as %s: %s. This is "
"a fatal error and the install "
"cannot continue.\n\n"
"Press to exit the "
"installer.")
% (device.path,
device.format.mountpoint,
msg))
log.error("FSError: %s" % msg)
sys.exit(0)
self.active = True
def umountFilesystems(self, instPath, 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, rootPath, device, size):
""" Create and activate a swap file under 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):
root = self.rootDevice
dev = "%s/%s" % (instPath, root.path)
if not os.path.exists("%s/dev/root" %(instPath,)) and os.path.exists(dev):
rdev = os.stat(dev).st_rdev
os.mknod("%s/dev/root" % (instPath,), stat.S_IFBLK | 0600, rdev)
@property
def swapDevices(self):
swaps = []
for device in self.devices:
if device.format.type == "swap":
swaps.append(device)
return swaps
@property
def rootDevice(self):
for device in self.devices:
try:
mountpoint = device.format.mountpoint
except AttributeError:
mountpoint = None
if mountpoint == "/":
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):
""" write out all config files based on the set of filesystems """
# /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()
open(mdadm_path, "w").write(mdadm_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")
conf = "# mdadm.conf written out by anaconda\n"
conf += "MAILADDR root\n"
devices = self.mountpoints.values() + self.swapDevices
for array in arrays:
writeConf = False
for device in devices:
if device == array or device.dependsOn(array):
writeConf = True
break
if writeConf:
conf += array.mdadmConfEntry
return conf
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 vol_id(8) for more info
#
""" % time.asctime()
devices = self.mountpoints.values() + 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
0707010002C02D000081A4000001F4000001F4000000014A843E4300000661000000FD0000000200000000000000000000001500000000storage/miscutils.py # 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
0707010002C02E000081A4000001F4000001F4000000014A843E43000001B9000000FD0000000200000000000000000000001700000000storage/storage_log.py import logging
#handler = logging.StreamHandler()
file_handler = logging.FileHandler("/tmp/storage.log")
formatter = logging.Formatter("[%(asctime)s] %(levelname)8s: %(message)s")
file_handler.setFormatter(formatter)
tty3_handler = logging.FileHandler("/dev/tty3")
tty3_handler.setFormatter(formatter)
logger = logging.getLogger("storage")
logger.addHandler(file_handler)
logger.addHandler(tty3_handler)
logger.setLevel(logging.DEBUG)
0707010002C02F000041FD000001F4000001F4000000024A8529B700000000000000FD0000000200000000000000000000001000000000storage/formats 0707010002C030000081A4000001F4000001F4000000014A843E430000136D000000FD0000000200000000000000000000001800000000storage/formats/swap.py # 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
#
from iutil import log_method_call, numeric_type
from parted import PARTITION_SWAP
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
_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 _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)
if self.exists:
raise SwapSpaceError("format already exists")
if self.status:
raise SwapSpaceError("device exists and is active")
DeviceFormat.create(self, *args, **kwargs)
swap.mkswap(self.device, label=self.label)
self.exists = True
def writeKS(self, f):
f.write("swap")
register_device_format(SwapSpace)
0707010002C031000081A4000001F4000001F4000000014A843E4300002E47000000FD0000000200000000000000000000001C00000000storage/formats/__init__.py # __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
#
import os
from iutil import notify_kernel, get_sysfs_path_by_name, 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")
default_boot_fstypes = ("ext4", "ext3", "ext2")
def get_default_filesystem_type(boot=None):
if boot:
fstypes = default_boot_fstypes
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
_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
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 _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
@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
def writeKS(self, f):
return
collect_device_format_classes()
0707010002C032000081A4000001F4000001F4000000014A843E430000225E000000FD0000000200000000000000000000001800000000storage/formats/luks.py # 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
#
import os
from iutil 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
"""
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")
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)
@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 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")
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)
self.uuid = crypto.luks_uuid(self.device)
self.exists = True
self.mapName = "luks-%s" % self.uuid
self.notifyKernel()
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)
register_device_format(LUKS)
0707010002C033000081A4000001F4000001F4000000014A843E43000011A9000000FD0000000200000000000000000000001900000000storage/formats/lvmpv.py # 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
#
import os
from iutil 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 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)
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)
self.exists = True
self.notifyKernel()
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)
0707010002C034000081A4000001F4000001F4000000014A843E4300000C13000000FD0000000200000000000000000000001D00000000storage/formats/multipath.py # 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 .
#
# 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
#
from iutil 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
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
@property
def member(self):
return self._member
@member.setter
def member(self, member):
self._member = member
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)
0707010002C035000081A4000001F4000001F4000000014A843E43000008D9000000FD0000000200000000000000000000001C00000000storage/formats/prepboot.py # 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
#
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 platform
return isinstance(platform.getPlatform(None), platform.IPSeriesPPC)
def writeKS(self, f):
f.write("prepboot --fstype=%s" % self.type)
register_device_format(PPCPRePBoot)
0707010002C036000081A4000001F4000001F4000000014A843E430000100F000000FD0000000200000000000000000000001A00000000storage/formats/dmraid.py # 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
#
from iutil 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
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
@property
def raidmem(self):
return self._raidmem
@raidmem.setter
def raidmem(self, raidmem):
self._raidmem = raidmem
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 flags.cmdline.has_key("iswmd"):
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)
0707010002C037000081A4000001F4000001F4000000014A843E4300009AAA000000FD0000000200000000000000000000001600000000storage/formats/fs.py # 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
# David Cantrell
#
""" Filesystem classes for use by anaconda.
TODO:
- migration
- bug 472127: allow creation of tmpfs filesystems (/tmp, /var/tmp, &c)
"""
import os
import tempfile
import isys
from ..errors import *
from . import DeviceFormat, register_device_format
import iutil
from flags import flags
# is this nasty?
log_method_call = iutil.log_method_call
import logging
log = logging.getLogger("storage")
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
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 fsFromConfig(attrs, *args, **kwargs):
""" Create an FS instance based on a set of attributes, passing on
constructor arguments.
"""
# XXX NOTUSED
if not attrs.has_key("type"):
raise ValueError, _("attr dict must include a type")
fs = FS(*args, **kwargs)
for (attr, value) in attrs.items():
setattr(fs, "_%s" % attr, value)
if attrs["type"] in nodev_filesystems:
setattr(fs, "_nodev", True)
return fs
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
_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 = []
lostAndFoundContext = None
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)
# TODO: fsprofiles and other ways to add format args
self.mountpoint = kwargs.get("mountpoint")
self.mountopts = kwargs.get("mountopts")
self.label = kwargs.get("label")
# 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:
self._size = self._getExistingSize()
foo = self.minSize # force calculation of minimum size
self._targetSize = self._size
if self.supported:
self.loadModule()
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.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 = 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)
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 filesystem on %s")
% (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()
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",
searchPath = 1)
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)
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 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 rc >= 4:
raise FSError("filesystem check failed: %s" % rc)
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",
searchPath=1)
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:
ret = isys.resetFileContext(mountpoint, chroot)
log.info("set SELinux context for newly mounted filesystem "
"root at %s to %s" %(mountpoint, ret))
if self.lostAndFoundContext is None:
self.lostAndFoundContext = isys.matchPathContext("/lost+found")
isys.setFileContext("%s/lost+found" % mountpoint,
self.lostAndFoundContext, 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",
searchPath=1)
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))
class Ext2FS(FS):
""" ext2 filesystem. """
_type = "ext2"
_mkfs = "mke2fs"
_modules = ["ext2"]
_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 = []
_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:"]
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("/usr/sbin/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
if self.exists and os.path.exists(self.device):
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
(text, sep, minSize) = line.partition(": ")
size = int(minSize) / 1024.0
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"]
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"]
register_device_format(Ext4FS)
class FATFS(FS):
""" FAT filesystem. """
_type = "vfat"
_mkfs = "mkdosfs"
_modules = ["vfat"]
_labelfs = "dosfslabel"
_fsck = "dosfsck"
_supported = True
_formattable = True
_maxSize = 1024 * 1024
_packages = [ "dosfstools" ]
_defaultMountOptions = ["umask=0077", "shortname=winnt"]
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 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 = False
_dump = True
_check = True
_packages = ["btrfs-progs"]
_maxSize = 16 * 1024 * 1024
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("icantbelieveitsnotbtr"):
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"]
@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 = False
_dump = True
_check = True
_infofs = "jfs_tune"
_defaultInfoOptions = ["-l"]
_existingSizeFields = ["Aggregate block size:", "Aggregate size:"]
@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 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
_dump = True
_check = True
_packages = ["xfsprogs"]
_infofs = "xfs_db"
_defaultInfoOptions = ["-c", "\"sb 0\"", "-c", "\"p dblocks\"",
"-c", "\"p blocksize\""]
_existingSizeFields = ["dblocks =", "blocksize ="]
register_device_format(XFS)
class HFS(FS):
_type = "hfs"
_mkfs = "hformat"
_modules = ["hfs"]
_formattable = True
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 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"]
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"]
_defaultCheckOptions = ["-c"]
_packages = ["ntfsprogs"]
_infofs = "ntfsinfo"
_defaultInfoOptions = ["-m"]
_existingSizeFields = ["Cluster Size:", "Volume Size in Clusters:"]
@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 :")
@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)
0707010002C038000081A4000001F4000001F4000000014A843E4300000E98000000FD0000000200000000000000000000001A00000000storage/formats/mdraid.py # 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
#
import os
from iutil 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()
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
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 flags.cmdline.has_key("iswmd") and not flags.cmdline.has_key("nodmraid"):
MDRaidMember._udevTypes.append("isw_raid_member")
register_device_format(MDRaidMember)
0707010002C039000081A4000001F4000001F4000000014A843E4300000B2C000000FD0000000200000000000000000000001200000000storage/errors.py # 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
#
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
# devicelibs
class SwapError(StorageError):
pass
class SuspendError(SwapError):
pass
class OldSwapError(SwapError):
pass
class MDRaidError(StorageError):
pass
class DMError(StorageError):
pass
class LVMError(StorageError):
pass
class CryptoError(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
0707010002C03A000041FD000001F4000001F4000000024A8529B700000000000000FD0000000200000000000000000000001300000000storage/devicelibs 0707010002C03B000081A4000001F4000001F4000000014A843E4300000CB8000000FD0000000200000000000000000000001B00000000storage/devicelibs/swap.py # 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
#
import resource
import iutil
import os
from ..errors import *
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
def mkswap(device, label=''):
argv = []
if label:
argv.extend(["-L", label])
argv.append(device)
rc = iutil.execWithRedirect("mkswap", argv,
stderr = "/dev/tty5",
stdout = "/dev/tty5",
searchPath=1)
if rc:
raise SwapError("mkswap failed for '%s'" % device)
def swapon(device, priority=None):
pagesize = resource.getpagesize()
buf = 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
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",
searchPath=1)
if rc:
raise SwapError("swapon failed for '%s'" % device)
def swapoff(device):
rc = iutil.execWithRedirect("swapoff", [device],
stderr = "/dev/tty5",
stdout = "/dev/tty5",
searchPath=1)
if rc:
raise SwapError("swapoff failed for '%s'" % device)
def swapstatus(device):
lines = open("/proc/swaps").readlines()
status = False
for line in lines:
if not line.strip():
continue
if line.split()[0] == device:
status = True
break
return status
0707010002C03C000081A4000001F4000001F4000000014A843E4300000000000000FD0000000200000000000000000000001F00000000storage/devicelibs/__init__.py 0707010002C03D000081A4000001F4000001F4000000014A843E430000158D000000FD0000000200000000000000000000001D00000000storage/devicelibs/crypto.py #
# 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 .
#
# Author(s): Dave Lehman
# Martin Sivak
#
import os
from pycryptsetup import CryptSetup
import iutil
from ..errors import *
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
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",
searchPath = 1)
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",
searchPath = 1)
os.close(p[0])
if rc:
raise CryptoError("luks_remove_key failed with errcode %d" % (rc,))
0707010002C03E000081A4000001F4000001F4000000014A843E43000031A6000000FD0000000200000000000000000000001A00000000storage/devicelibs/lvm.py #
# 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 .
#
# Author(s): Dave Lehman
#
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_data
config_args_data["filterRejects"] = []
config_args_data["filterAccepts"] = []
# 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
def safeLvmName(name):
tmp = name.strip()
tmp = tmp.replace("/", "_")
tmp = re.sub("[^0-9a-zA-Z._]", "", tmp)
tmp = tmp.lstrip("_")
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 pvcreate(device):
args = ["pvcreate"] + \
config_args + \
[device]
rc = iutil.execWithRedirect("lvm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("pvcreate failed for %s" % device)
def pvresize(device, size):
args = ["pvresize"] + \
["--setphysicalvolumesize", ("%dm" % size)] + \
config_args + \
[device]
rc = iutil.execWithRedirect("lvm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("pvresize failed for %s" % device)
def pvremove(device):
args = ["pvremove"] + \
config_args + \
[device]
rc = iutil.execWithRedirect("lvm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("pvremove failed for %s" % device)
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):
argv = ["vgcreate"]
if pe_size:
argv.extend(["-s", "%dm" % pe_size])
argv.extend(config_args)
argv.append(vg_name)
argv.extend(pv_list)
rc = iutil.execWithRedirect("lvm", argv,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("vgcreate failed for %s" % vg_name)
def vgremove(vg_name):
args = ["vgremove"] + \
config_args +\
[vg_name]
rc = iutil.execWithRedirect("lvm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("vgremove failed for %s" % vg_name)
def vgactivate(vg_name):
args = ["vgchange", "-a", "y"] + \
config_args + \
[vg_name]
rc = iutil.execWithRedirect("lvm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("vgactivate failed for %s" % vg_name)
def vgdeactivate(vg_name):
args = ["vgchange", "-a", "n"] + \
config_args + \
[vg_name]
rc = iutil.execWithRedirect("lvm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("vgdeactivate failed for %s" % vg_name)
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)
rc = iutil.execWithRedirect("lvm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("vgreduce failed for %s" % vg_name)
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 lvcreate(vg_name, lv_name, size):
args = ["lvcreate"] + \
["-L", "%dm" % size] + \
["-n", lv_name] + \
config_args + \
[vg_name]
rc = iutil.execWithRedirect("lvm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("lvcreate failed for %s/%s" % (vg_name, lv_name))
def lvremove(vg_name, lv_name):
args = ["lvremove"] + \
config_args + \
["%s/%s" % (vg_name, lv_name)]
rc = iutil.execWithRedirect("lvm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("lvremove failed for %s" % lv_name)
def lvresize(vg_name, lv_name, size):
args = ["lvresize"] + \
["--force", "-L", "%dm" % size] + \
config_args + \
["%s/%s" % (vg_name, lv_name)]
rc = iutil.execWithRedirect("lvm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("lvresize failed for %s" % lv_name)
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)]
rc = iutil.execWithRedirect("lvm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("lvactivate failed for %s" % lv_name)
def lvdeactivate(vg_name, lv_name):
args = ["lvchange", "-a", "n"] + \
config_args + \
["%s/%s" % (vg_name, lv_name)]
rc = iutil.execWithRedirect("lvm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5",
searchPath=1)
if rc:
raise LVMError("lvdeactivate failed for %s" % lv_name)
0707010002C03F000081A4000001F4000001F4000000014A843E4300000E99000000FD0000000200000000000000000000001900000000storage/devicelibs/dm.py #
# 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 .
#
# Author(s): Dave Lehman
#
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(major, minor):
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
0707010002C040000081A4000001F4000001F4000000014A843E4300001B9C000000FD0000000200000000000000000000001D00000000storage/devicelibs/mdraid.py #
# 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 .
#
# Author(s): Dave Lehman
#
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
RAID1 = 1
RAID0 = 0
def getRaidLevels():
mdstat_descriptors = {
RAID10: ("[RAID10]", "[raid10]"),
RAID6: ("[RAID6]", "[raid6]"),
RAID5: ("[RAID5]", "[raid5]"),
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),
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,
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)),
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 mdcreate(device, level, disks, spares=0):
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)
argv.extend(disks)
rc = iutil.execWithRedirect("mdadm",
argv,
stderr = "/dev/tty5",
stdout = "/dev/tty5",
searchPath=1)
if rc:
raise MDRaidError("mdcreate failed for %s" % device)
# mdadm insists on starting the new array, so we have to stop it here
#self.mddeactivate(device)
def mddestroy(device):
rc = iutil.execWithRedirect("mdadm",
["--zero-superblock", device],
stderr = "/dev/tty5",
stdout = "/dev/tty5",
searchPath=1)
if rc:
raise MDRaidError("mddestroy failed for %s" % device)
def mdadd(device, no_degraded=False):
args = ["--incremental", "--quiet"]
if no_degraded:
args.append("--no-degraded")
args.append(device)
rc = iutil.execWithRedirect("mdadm",
args,
stderr = "/dev/tty5",
stdout = "/dev/tty5",
searchPath=1)
if rc:
raise MDRaidError("mdadd failed for %s" % device)
def mdactivate(device, members=[], super_minor=None, 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 = ""
rc = iutil.execWithRedirect("mdadm",
["--assemble",
device,
identifier,
"--run",
"--auto=md",
"--update=super-minor"] + members,
stderr = "/dev/tty5",
stdout = "/dev/tty5",
searchPath=1)
if rc:
raise MDRaidError("mdactivate failed for %s" % device)
def mddeactivate(device):
rc = iutil.execWithRedirect("mdadm",
["--stop", device],
stderr = "/dev/tty5",
stdout = "/dev/tty5",
searchPath=1)
if rc:
raise MDRaidError("mddeactivate failed for %s" % device)
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
0707010002C041000081A4000001F4000001F4000000014A843E430001A9D0000000FD0000000200000000000000000000001300000000storage/devices.py # 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
#
"""
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
# device backend modules
from devicelibs import mdraid
from devicelibs import lvm
from devicelibs import dm
import parted
import _ped
import platform
import block
from errors import *
from iutil import log_method_call, notify_kernel, numeric_type
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 ""
for path in os.listdir(bypath):
target = os.path.basename(os.readlink(bypath + '/' + path))
if target == deviceName:
return path
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
shallow_copy_attrs = ('_partedDisk', '_partedDevice',
'_partedPartition', '_origPartedDisk',
'_raidSet')
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 = ("%(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
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):
""" Run setup method of all parent devices. """
for parent in self.parents:
parent.setup()
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.path)
#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 this device's
format type as well those required by all of its parent
devices.
"""
packages = self._packages
packages.extend(self.format.packages)
for parent in self.parents:
for package in parent.packages:
if package not in packages:
packages.append(package)
for package in parent.format.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
def __init__(self, device, format=None,
size=None, major=None, minor=None,
sysfsPath='', parents=None, exists=None, serial=None):
""" 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
"""
# allow specification of individual parents
if isinstance(parents, Device):
parents = [parents]
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.exists = exists
self.serial = serial
self.protected = False
# this may be handy for disk, dmraid, mpath, mdraid
self.diskLabel = None
self.format = format
self.fstabComment = ""
self._targetSize = self._size
self._partedDevice = None
@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.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 label = %(diskLabel)s\n"
" target size = %(targetSize)s path = %(path)s\n"
" format args = %(formatArgs)s" %
{"uuid": self.uuid, "format": self.format, "size": self.size,
"major": self.major, "minor": self.minor, "exists": self.exists,
"sysfs": self.sysfsPath, "diskLabel": self.diskLabel,
"targetSize": self.targetSize, "path": self.path,
"formatArgs": self.formatArgs})
return s
@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
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):
""" Open, or set up, a device. """
log_method_call(self, self.name, status=self.status)
if not self.exists:
raise DeviceError("device has not been created", self.path)
self.setupParents()
for parent in self.parents:
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.path)
if self.status and self.format.exists:
self.format.teardown()
udev_settle(timeout=10)
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.path)
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.path)
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.path)
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.path)
if not self.isleaf:
raise DeviceError("Cannot destroy non-leaf device", self.path)
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)
return (self.sysfsPath and os.path.exists(devpath) and
os.access(remfile, os.R_OK) and
open(remfile).readline().strip() == "1")
class DiskDevice(StorageDevice):
""" A disk """
_type = "disk"
def __init__(self, device, format=None,
size=None, major=None, minor=None, sysfsPath='', \
parents=None, initcb=None, initlabel=None):
""" 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
initcb -- the call back to be used when initiating disk.
initlabel -- whether to start with a fresh disklabel
DiskDevices always exist.
"""
StorageDevice.__init__(self, device, format=format, size=size,
major=major, minor=minor, exists=True,
sysfsPath=sysfsPath, parents=parents)
self._partedDisk = None
self._initlabel = initlabel
self._initcb = initcb
# We save the actual state of the disk here. Before the first
# modification (addPartition or removePartition) to the partition
# table we reset self.partedPartition to this state so we can
# perform the modifications one at a time.
if self.partedDisk:
self._origPartedDisk = self.partedDisk.duplicate()
else:
self._origPartedDisk = None
@property
def partedDisk(self):
if self._partedDisk:
return self._partedDisk
log.debug("looking up parted Device: %s" % self.path)
if self.partedDevice:
log.debug("creating parted Disk: %s" % self.path)
if self._initlabel:
self._partedDisk = self.freshPartedDisk()
else:
try:
self._partedDisk = parted.Disk(device=self.partedDevice)
except _ped.DiskLabelException:
# if we have a cb function use it. else an error.
if self._initcb is not None and self._initcb():
self._partedDisk = parted.freshDisk( \
device=self.partedDevice, \
ty = platform.getPlatform(None).diskType)
else:
raise DeviceUserDeniedFormatError("User prefered to not format.")
# 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).
if self._partedDisk.type == "loop":
if self._initcb is not None and self._initcb():
self._partedDisk = parted.freshDisk( \
device=self.partedDevice, \
ty = platform.getPlatform(None).diskType)
else:
raise DeviceUserDeniedFormatError("User prefered to not format.")
return self._partedDisk
def __str__(self):
s = StorageDevice.__str__(self)
s += (" removable = %(removable)s partedDevice = %(partedDevice)r\n"
" partedDisk = %(partedDisk)r" %
{"removable": self.removable, "partedDisk": self.partedDisk,
"partedDevice": self.partedDevice})
return s
def freshPartedDisk(self):
log_method_call(self, self.name)
labelType = platform.getPlatform(None).diskType
return parted.freshDisk(device=self.partedDevice, ty=labelType)
@property
def mediaPresent(self):
return self.partedDevice is not None
@property
def model(self):
return getattr(self.partedDevice, "model", None)
@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 resetPartedDisk(self):
""" Reset parted.Disk to reflect the actual layout of the disk. """
log_method_call(self, self.name)
self._partedDisk = self._origPartedDisk
def removePartition(self, device):
log_method_call(self, self.name, part=device.name)
if not self.mediaPresent:
raise DeviceError("cannot remove partition from disk %s which has no media" % self.name, self.path)
partition = self.partedDisk.getPartitionByPath(device.path)
if partition:
self.partedDisk.removePartition(partition)
def addPartition(self, device):
log_method_call(self, self.name, part=device.name)
if not self.mediaPresent:
raise DeviceError("cannot add partition to disk with no media", self.path)
for part in self.partedDisk.partitions:
log.debug("disk %s: partition %s has geom %s" % (self.name,
part.getDeviceNodeName(),
part.geometry))
geometry = device.partedPartition.geometry
constraint = parted.Constraint(exactGeom=geometry)
partition = parted.Partition(disk=self.partedDisk,
type=device.partedPartition.type,
geometry=geometry)
self.partedDisk.addPartition(partition,
constraint=constraint)
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)
if not self.diskLabel:
log.debug("setting %s diskLabel to %s" % (self.name,
self.partedDisk.type))
self.diskLabel = self.partedDisk.type
def commit(self, intf=None):
""" Commit changes to the device. """
log_method_call(self, self.name, status=self.status)
if not self.mediaPresent:
raise DeviceError("cannot commit to disk with no media", self.path)
self.setupParents()
self.setup()
# give committing 5 tries, failing that, raise an exception
attempt = 1
maxTries = 5
keepTrying = True
while keepTrying and (attempt <= maxTries):
try:
self.partedDisk.commit()
keepTrying = False
except parted.DiskException as msg:
log.warning(msg)
attempt += 1
if keepTrying:
raise DeviceError("cannot commit to disk after %d attempts" % (maxTries,), self.path)
# commit makes the kernel re-scan the partition table
udev_settle()
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.path)
self.partedDisk.deleteAllPartitions()
# this is perhaps a separate operation (wiping the disklabel)
self.partedDisk.clobber()
self.partedDisk.commit()
self.teardown()
def setup(self, intf=None):
""" Open, or set up, a device. """
log_method_call(self, self.name, status=self.status)
if not os.path.exists(self.path):
raise DeviceError("device does not exist", self.path)
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
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
# 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 = parted.getPartitionByName(self.path)
self._partedPartition = self.disk.partedDisk.getPartitionByPath(self.path)
if not self._partedPartition:
raise DeviceError("cannot find parted partition instance", 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.
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" %
{"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})
return s
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 1))
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.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)
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 _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.path)
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,
part=self.partedPartition)
if not self.partedPartition:
return
return self.partedPartition.isFlagAvailable(flag)
def getFlag(self, flag):
log_method_call(self, path=self.path, flag=flag,
part=self.partedPartition)
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,
part=self.partedPartition)
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,
part=self.partedPartition)
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.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.path)
self.createParents()
self.setupParents()
self.disk.addPartition(self)
self.disk.commit()
# 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()
self.partedPartition = self.disk.partedDisk.getPartitionByPath(self.path)
self.exists = True
self.setup()
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)
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
partition = self.disk.partedDisk.getPartitionByPath(self.path)
(constraint, geometry) = self._computeResize(partition)
self.disk.partedDisk.setPartitionGeometry(partition=partition,
constraint=constraint,
start=geometry.start,
end=geometry.end)
self.disk.commit()
self.notifyKernel()
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.path)
if not self.sysfsPath:
return
if not self.isleaf:
raise DeviceError("Cannot destroy non-leaf device", self.path)
self.setupParents()
self.disk.removePartition(self)
self.disk.commit()
self.exists = False
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.path)
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=self.disk, new=disk)
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
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 fstabSpec(self):
""" Return the device specifier for use in /etc/fstab. """
return self.path
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.path)
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.path)
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.path)
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.path)
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):
""" Open, or set up, a device. """
log_method_call(self, self.name, status=self.status)
if not self.exists:
raise DeviceError("device has not been created", self.path)
self.slave.setup()
self.slave.format.setup()
# 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.path)
if self.status and self.format.exists:
self.format.teardown()
udev_settle(timeout=10)
if self.slave.format.exists:
self.slave.format.teardown()
udev_settle(timeout=10)
if recursive:
self.teardownParents(recursive=recursive)
def destroy(self):
log_method_call(self, self.name, status=self.status)
self.format.teardown()
udev_settle(timeout=10)
self.teardown()
@property
def slave(self):
""" This device's backing device. """
return self.parents[0]
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 = 4.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
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.path)
@property
def path(self):
""" Device node representing this device. """
# Thank you lvm for this lovely hack.
return "%s/%s" % (self._devDir, self.name.replace("-","--"))
def getDMNode(self):
""" Return the dm-X (eg: dm-0) device node for this device. """
# Thank you lvm for this lovely hack.
log_method_call(self, self.name, status=self.status)
if not self.exists:
raise DeviceError("device has not been created", self.path)
return dm.dm_node_from_name(self.name.replace("-","--"))
@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.path)
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):
""" 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, status=self.status)
if not self.exists:
raise DeviceError("device has not been created", self.path)
if self.status:
return
if len(self.parents) < self.pvCount:
raise DeviceError("cannot activate VG with missing PV(s)", self.path)
self.setupParents()
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.path)
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.path)
pv_list = []
#for pv in self.parents:
# This is a little bit different from other devices in that
# for VG we need the PVs to be formatted before we can create
# the VG.
# pv.create()
# pv.format.create()
# pv_list.append(pv.path)
pv_list = [pv.path for pv in self.parents]
self.createParents()
self.setupParents()
lvm.vgcreate(self.name, pv_list, self.peSize)
# FIXME set / update self.uuid here
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.path)
# set up the pvs since lvm needs access to them to do the vgremove
self.setupParents()
# 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.path)
finally:
self.notifyKernel()
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.path)
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.path)
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.path)
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.path)
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 (%s) uses %dMB" % (lv.name, lv, lv.size))
used += self.align(lv.size, 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,
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
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.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" %
{"vgdev": self.vg, "percent": self.req_percent})
return s
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)
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 vg(self):
""" This Logical Volume's Volume Group. """
return self.parents[0]
@property
def path(self):
""" Device node representing this device. """
# Thank you lvm for this lovely hack.
return "%s/%s-%s" % (self._devDir, self.vg.name.replace("-","--"),
self._name.replace("-","--"))
def getDMNode(self):
""" Return the dm-X (eg: dm-0) device node for this device. """
# Thank you lvm for this lovely hack.
log_method_call(self, self.name, status=self.status)
if not self.exists:
raise DeviceError("device has not been created", self.path)
return dm.dm_node_from_name("%s-%s" % (self.vg.name.replace("-","--"), \
self._name.replace("-","--")))
@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
@property
def status(self):
""" True if the LV is active, False otherwise. """
try:
lvstatus = lvm.lvs(self.vg.name)
except lvm.LVMError:
return False
try:
if lvstatus[self._name]['attr'].find('a') == -1:
return False
else:
return True
except KeyError:
return False
def setup(self, intf=None):
""" Open, or set up, a device. """
log_method_call(self, self.name, status=self.status)
if not self.exists:
raise DeviceError("device has not been created", self.path)
if self.status:
return
self.vg.setup()
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.path)
if self.status and self.format.exists:
self.format.teardown()
udev_settle(timeout=10)
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.path)
self.createParents()
self.setupParents()
# should we use --zero for safety's sake?
lvm.lvcreate(self.vg.name, self._name, self.size)
# FIXME set / update self.uuid here
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.path)
self.teardown()
# set up the vg's pvs so lvm can remove the lv
self.vg.setupParents()
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.path)
# Setup VG parents (in case they are dmraid partitions for example)
self.vg.setupParents()
if self.format.exists:
self.format.teardown()
udev_settle(timeout=10)
lvm.lvresize(self.vg.name, self._name, self.size)
class MDRaidArrayDevice(StorageDevice):
""" An mdraid (Linux RAID) device.
Since this is derived from StorageDevice, not PartitionDevice, it
can be used to represent a partitionable device.
"""
_type = "mdarray"
def __init__(self, name, level=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,
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)
self.uuid = uuid
self._totalDevices = numeric_type(totalDevices)
self._memberDevices = numeric_type(memberDevices)
self.sysfsPath = "/devices/virtual/block/%s" % name
self.chunkSize = 64.0 / 1024.0 # chunk size in MB
self.superBlockSize = 128.0 / 1024.0 # superblock size in MB
# 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.path)
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
open("/etc/mdadm.conf", "a").write("ARRAY %s UUID=%s\n"
% (self.path, self.uuid))
@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
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.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
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
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.path)
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.path)
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'])
@property
def fstabSpec(self):
return self.path
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.path)
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.path)
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(timeout=10)
try:
mdraid.mdadd(device.path, len(self.devices) < self.memberDevices)
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 = open(state_file).read().strip()
log.debug("%s state is %s" % (self.name, state))
if state in ("clean", "active", "active-idle"):
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):
val = open(degraded_file).read().strip()
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):
""" Open, or set up, a device. """
log_method_call(self, self.name, status=self.status)
if not self.exists:
raise DeviceError("device has not been created", self.path)
if self.status:
return
disks = []
for member in self.devices:
member.setup()
disks.append(member.path)
mdraid.mdactivate(self.path,
members=disks,
super_minor=self.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.path)
if self.status and self.format.exists:
self.format.teardown()
udev_settle(timeout=10)
# 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.path)
disks = [disk.path for disk in self.devices]
self.createParents()
self.setupParents()
spares = len(self.devices) - self.memberDevices
mdraid.mdcreate(self.path,
self.level,
disks,
spares)
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
@property
def formatArgs(self):
formatArgs = []
if self.format.type == "ext2":
if self.level == mdraid.RAID5:
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.path)
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
class DMRaidArrayDevice(DiskDevice):
""" A dmraid (device-mapper RAID) device """
_type = "dm-raid array"
_packages = ["dmraid"]
_devDir = "/dev/mapper"
def __init__(self, name, raidSet=None, format=None,
size=None, major=None, minor=None, parents=None,
sysfsPath='', initcb=None, initlabel=None):
""" 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
initcb -- the call back to be used when initiating disk.
initlabel -- whether to start with a fresh disklabel
"""
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")
DiskDevice.__init__(self, name, format=format, size=size,
major=major, minor=minor, parents=parents,
sysfsPath=sysfsPath, initcb=initcb,
initlabel=initlabel)
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.path)
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 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.path)
if self.status:
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:
self.sysfsPath = ''
def teardown(self, recursive=None):
""" Close, or tear down, a device. """
log_method_call(self, self.name, status=self.status)
if not self.exists:
raise DeviceError("device has not been created", self.path)
if self.format.exists:
self.format.teardown()
udev_settle(timeout=10)
# This call already checks if the set is not active.
self._raidSet.deactivate()
def setup(self, intf=None):
""" Open, or set up, a device. """
log_method_call(self, self.name, status=self.status)
if not self.exists:
raise DeviceError("device has not been created", self.path)
# This call already checks if the set is active.
self._raidSet.activate(mknod=True)
udev_settle()
# we always probe since the device may not be set up when we want
# information about it
self._size = self.currentSize
class _MultipathDeviceNameGenerator:
def __init__(self):
self.number = 0
def get(self):
ret = self.number
self.number += 1
return ret
_multipathDeviceNameGenerator = _MultipathDeviceNameGenerator()
def generateMultipathDeviceName():
number = _multipathDeviceNameGenerator.get()
return "mpath%s" % (number, )
class MultipathDevice(DiskDevice):
""" A multipath device """
_type = "dm-multipath"
_packages = ["device-mapper-multipath"]
_devDir = "/dev/mapper"
def __init__(self, name, info, format=None, size=None,
parents=None, sysfsPath='', initcb=None,
initlabel=None):
""" 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)
initcb -- the call back to be used when initiating disk.
initlabel -- whether to start with a fresh disklabel
"""
self._info = info
self._isUp = False
self._pyBlockMultiPath = None
self.setupIdentity()
DiskDevice.__init__(self, name, format=format, size=size,
parents=parents, sysfsPath=sysfsPath,
initcb=initcb, initlabel=initlabel)
def setupIdentity(self):
""" Adds identifying remarks to MultipathDevice object.
May be overridden by a sub-class for e.g. RDAC handling.
"""
self._serial = self._info['ID_SERIAL_SHORT']
@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, "_serial"):
raise RuntimeError, "setupIdentityFromInfo() has not been called."
return self._serial
@property
def status(self):
return self._isUp
@property
def wwid(self):
serial = self.identity
ret = []
while serial:
ret.append(serial[:2])
serial = serial[2:]
return ":".join(ret)
@property
def description(self):
return "WWID %s" % (self.wwid,)
def addParent(self, parent):
if self.status:
self.teardown()
self.parents.append(parent)
self.setup()
else:
self.parents.append(parent)
def setup(self, intf=None):
if self.status:
self.teardown()
self._isUp = True
parents = []
for p in self.parents:
parents.append(p.path)
self._pyBlockMultiPath = block.device.MultiPath(*parents)
def teardown(self, recursive=None):
if not self.status:
return
self._isUp = False
self._pyBlockMultiPath = None
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):
""" Open, or set up, a device. """
log_method_call(self, self.name, 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
root = root[:-len(self.parents[0].format.mountpoint)]
return os.path.normpath("%s/%s" % (root, path))
def setup(self):
StorageDevice.setup(self)
if self.format and self.format.exists and not self.format.status:
self.format.device = self.path
for parent in self.parents:
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.path)
# this only checks that parents exist
self.createParents()
self.setupParents()
try:
fd = os.open(self.path, os.O_RDWR)
except OSError as e:
raise DeviceError(e, self.path)
try:
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))
finally:
os.close(fd)
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.path)
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.path)
self.createParents()
self.setupParents()
try:
iutil.mkdirChain(self.path)
except Exception, e:
raise DeviceError(e, self.path)
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.path)
os.unlink(self.path)
self.exists = False
class iScsiDiskDevice(DiskDevice, NetworkStorageDevice):
""" An iSCSI disk. """
_type = "iscsi"
_packages = ["iscsi-initiator-utils"]
def __init__(self, device, **kwargs):
self.node = kwargs.pop("node")
self.ibft = kwargs.pop("ibft")
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)
return netroot
class FcoeDiskDevice(DiskDevice, NetworkStorageDevice):
""" An FCoE disk. """
_type = "fcoe"
_packages = ["fcoe-utils"]
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))
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=''):
StorageDevice.__init__(self, name, format=format,
major=major, minor=minor, exists=True,
parents=parents, sysfsPath=sysfsPath)
@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.path)
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.path)
#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, name, size=None, major=None, minor=None,
devnum=None, wwpn=None, fcplun=None,
parents=None, sysfsPath=''):
self.devnum = devnum
self.wwpn = wwpn
self.fcplun = fcplun
name = "zfcp://%s/%s/%s" % (self.devnum, self.wwpn, self.fcplun)
DiskDevice.__init__(self, name, size=size,
major=major, minor=minor,
parents=parents, sysfsPath=sysfsPath)
def __str__(self):
s = DiskDevice.__str__(self)
s += (" devnum = %(devnum)s wwpn = %(wwpn)s fcplun = %(fcplun)s" %
{"devnum": self.devnum, "wwpn": self.wwpn, "fcplun": self.fcplun})
return s
class DASDDevice(DiskDevice):
""" A mainframe DASD. """
_type = "dasd"
def __init__(self, device, size=None, major=None, minor=None,
parents=None, sysfsPath=''):
DiskDevice.__init__(self, device, size=size,
major=major, minor=minor,
parents=parents, sysfsPath=sysfsPath)
class NFSDevice(StorageDevice, NetworkStorageDevice):
""" An NFS device """
_type = "nfs"
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):
""" Open, or set up, a device. """
log_method_call(self, self.name, 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)
0707010002C042000081B4000001F4000001F4000000014A856CC800015400000000FD0000000200000000000000000000001600000000storage/devicetree.py # 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
#
import os
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
from udev import *
from iutil import log_method_call
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)
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)
# Don't really know where to put this.
def questionInitializeDisk(intf=None, name=None, description=None):
retVal = False # The less destructive default
if not intf or not name:
pass
else:
if not name.startswith('/dev/'):
devpath = '/dev/' + name
else:
devpath = name
dev = parted.getDevice(devpath)
bypath = deviceNameToDiskByPath(name)
details = ""
if description is None:
description = dev.model
if bypath:
details = "\n\nDevice details:\n%s" % (bypath,)
rc = intf.messageWindow(_("Warning"),
_("Error processing drive:\n\n"
"%s\n%-0.fMB\n%s\n\n"
"This device may need to be reinitialized.\n\n"
"REINITIALIZING WILL CAUSE ALL DATA TO BE LOST!%s")
% (name, dev.getSize(), description, details,),
type="custom",
custom_buttons = [ _("_Ignore drive"),
_("_Re-initialize drive") ],
custom_icon="question")
if rc == 0:
pass
else:
retVal = True
return retVal
def questionReinitILVM(intf=None, pv_names=None, lv_name=None, vg_name=None):
retVal = False # The less destructive default
if not intf or not pv_names or (lv_name is None and vg_name is None):
pass
else:
if vg_name is not None:
message = "Volume Group %s" % vg_name
elif lv_name is not None:
message = "Logical Volume %s" % lv_name
rc = intf.messageWindow(_("Warning"),
_("Error processing LVM.\n"
"There is inconsistent LVM data on %s. You can reinitialize "
"all related PVs (%s) which will erase the LVM metadata, or "
"ignore which will preserve the contents." % (message, ", ".join(pv_names))),
type="custom",
custom_buttons = [ _("_Ignore"),
_("_Re-initialize") ],
custom_icon="question")
if rc == 0:
pass
else:
retVal = True # thie means clobber.
return retVal
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):
# 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
# protected device specs as provided by the user
self.protectedDevSpecs = protected
# names of protected devices at the time of tree population
self.protectedDevNames = []
self.__passphrase = passphrase
self.__luksDevs = {}
self.__multipaths = {}
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 \
not isinstance(a2.device, DiskDevice):
ret = 1
elif isinstance(a2.device, PartitionDevice) and \
not isinstance(a1.device, DiskDevice):
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 \
not isinstance(a2.device, DiskDevice):
if a1.isGrow():
ret = -1
else:
ret = 1
elif isinstance(a2.device, PartitionDevice) and \
not isinstance(a1.device, DiskDevice):
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 \
not isinstance(a2.device, DiskDevice):
ret = -1
elif isinstance(a2.device, PartitionDevice) and \
not isinstance(a1.device, DiskDevice):
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
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)
log.debug("resetting parted disks...")
for device in self.devices:
if isinstance(device, DiskDevice):
device.resetPartedDisk()
for action in self._actions:
log.info("executing action: %s" % action)
if not dryRun:
action.execute(intf=self.intf)
udev_settle(timeout=10)
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) to device tree" % (newdev.name,
newdev.type))
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.partedDisk.getLogicalPartitions()) > 0:
raise ValueError("Cannot remove extended partition %s. "
"Logical partitions present." % dev.name)
dev.disk.partedDisk.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) from device tree" % (dev.name,
dev.type))
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
for ignored in self._ignoredDisks:
if ignored == os.path.basename(os.path.dirname(sysfs_path)):
# this is a partition on a disk in the ignore list
lvm.lvm_cc_addFilterRejectRegexp(name)
return True
# Ignore partitions found on the raw disks which are part of a
# biosraidset
sets = self.getDevicesByType("dm-raid array")
sets.extend(self.getDevicesByType("mdcontainer"))
for set in sets:
for disk in set.parents:
if disk.name == os.path.basename(os.path.dirname(sysfs_path)):
lvm.lvm_cc_addFilterRejectRegexp(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)
device = PartitionDevice(name, sysfsPath=sysfs_path,
major=udev_device_get_major(info),
minor=udev_device_get_minor(info),
exists=True, parents=[disk])
elif udev_device_is_dmraid_partition(info, self):
diskname = udev_device_get_dmraid_partition_disk(info)
disk = self.getDeviceByName(diskname)
device = PartitionDevice(name, sysfsPath=sysfs_path,
major=udev_device_get_major(info),
minor=udev_device_get_minor(info),
exists=True, parents=[disk])
if not device is None:
# DWL FIXME: call self.addUdevPartitionDevice here instead
self._addDevice(device)
# 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):
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
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)
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)
device = None
kwargs = {}
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
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)
else:
diskType = DiskDevice
log.debug("%s is a disk" % name)
if self.zeroMbr:
cb = lambda: True
else:
cb = lambda: questionInitializeDisk(self.intf, name)
# if the disk contains protected partitions we will
# not wipe the disklabel even if clearpart --initlabel
# was specified
if not self.clearPartDisks or name in self.clearPartDisks:
initlabel = self.reinitializeDisks
for protected in self.protectedDevNames:
_p = "/sys/%s/%s" % (sysfs_path, protected)
if os.path.exists(os.path.normpath(_p)):
initlabel = False
break
else:
initlabel = False
try:
device = diskType(name,
major=udev_device_get_major(info),
minor=udev_device_get_minor(info),
sysfsPath=sysfs_path,
initcb=cb, initlabel=initlabel, **kwargs)
except DeviceUserDeniedFormatError: #drive not initialized?
self.addIgnoredDisk(name)
return
self._addDevice(device)
# If this is a mac-formatted disk we just initialized, make sure the
# partition table partition gets added to the device tree.
if device.partedDisk and device.partedDisk.type == "mac" and len(device.partedDisk.partitions) == 1:
name = device.partedDisk.partitions[0].getDeviceNodeName()
if not self.getDeviceByName(name):
partDevice = PartitionDevice(name, exists=True, parents=[device])
self._addDevice(partDevice)
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))
self._addDevice(device)
return device
def addUdevDevice(self, info):
# FIXME: this should be broken up into more discrete chunks
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)
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 = StorageDevice(name,
major=udev_device_get_major(info),
minor=udev_device_get_minor(info),
sysfsPath=sysfs_path, exists=True,
serial=udev_device_get_serial(info))
self._addDevice(device)
elif udev_device_is_dm(info) and \
devicelibs.dm.dm_is_multipath(info["DM_MAJOR"], info["DM_MINOR"]):
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):
# This is special handling to avoid the "unrecognized disklabel"
# code since biosraid member disks won't have a disklabel. We
# use a StorageDevice because DiskDevices need disklabels.
# Quite lame, but it doesn't matter much since we won't use
# the StorageDevice instances for anything.
log.debug("%s is part of a biosraid" % name)
if device is None:
device = StorageDevice(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:
try:
device = self.addUdevPartitionDevice(info)
except AttributeError as e:
import pdb; pdb.set_trace()
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
# now handle the device's formatting
self.handleUdevDeviceFormat(info, device)
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, path):
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)
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
lvs = []
for (index, lv_name) in enumerate(lv_names):
name = "%s-%s" % (vg_name, lv_name)
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,
exists=True)
self._addDevice(lv_device)
try:
lv_device.setup()
except DeviceError as (msg, path):
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)
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)
serial = udev_device_get_serial(info)
found = False
if self.__multipaths.has_key(serial):
mp = self.__multipaths[serial]
mp.addParent(device)
else:
name = generateMultipathDeviceName()
devname = "/dev/mapper/%s" % (name,)
if self.zeroMbr:
cb = lambda: True
else:
desc = []
serialtmp = serial
while serialtmp:
desc.append(serialtmp[:2])
serialtmp = serialtmp[2:]
desc = "WWID %s" % (":".join(desc),)
cb = lambda: questionInitializeDisk(self.intf, devname, desc)
initlabel = False
if not self.clearPartDisks or \
name in self.clearPartDisks:
initlabel = self.reinitializeDisks
for protected in self.protectedDevNames:
disk_name = re.sub(r'p\d+$', '', protected)
if disk_name != protected and \
disk_name == name:
initlabel = False
break
mp = MultipathDevice(name, info, parents=[device], initcb=cb,
initlabel=initlabel)
self.__multipaths[serial] = 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.
# FIXME: Can we reformat a raid device?
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)
# Create the DMRaidArray
if self.zeroMbr:
cb = lambda: True
else:
cb = lambda: questionInitializeDisk(self.intf,
rs.name)
# Create the DMRaidArray
if not self.clearPartDisks or \
rs.name in self.clearPartDisks:
# if the disk contains protected partitions
# we will not wipe the disklabel even if
# clearpart --initlabel was specified
initlabel = self.reinitializeDisks
for protected in self.protectedDevNames:
disk_name = re.sub(r'p\d+$', '', protected)
if disk_name != protected and \
disk_name == rs.name:
initlabel = False
break
try:
dm_array = DMRaidArrayDevice(rs.name,
raidSet=rs,
parents=[device],
initcb=cb,
initlabel=initlabel)
self._addDevice(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)
except DeviceUserDeniedFormatError:
# We should ignore the dmraid and its components
self.addIgnoredDisk(rs.name)
if _all_ignored(rss):
self.addIgnoredDisk(device.name)
rs.deactivate()
def handleUdevDeviceFormat(self, info, device):
log_method_call(self, name=getattr(device, "name", None))
log.debug("%s" % info)
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)
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 getattr(device, "partedDisk", None):
# Any detected formatting is spurious. Ignore it.
# We don't want to try to remove it since that could wipe out
# valid data like the partition table or data in existing
# partitions.
device.format = None
return
if shouldClear(device, self.clearPartType,
clearPartDisks=self.clearPartDisks):
# if this is a partition 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 _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 = {"uuid": parent.uuid,
"label": parent.diskLabel,
"device": parent.path,
"exists": parent.exists}
parent.format = formats.getFormat(*[""], **kwargs)
def leafInconsistencies(device):
if device.type == "lvmvg":
paths = []
for parent in device.parents:
paths.append(parent.path)
# when zeroMbr is true he wont ask.
if not device.complete and (self.zeroMbr or \
questionReinitILVM(intf=self.intf, \
vg_name=device.name, pv_names=paths)):
reinitializeVG(device)
elif not device.complete:
# 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)
return
elif device.type == "lvmlv":
# we might have already fixed this.
if device not in self._devices or \
device.name in self._ignoredDisks:
return
paths = []
for parent in device.vg.parents:
paths.append(parent.path)
if not device.complete and (self.zeroMbr or \
questionReinitILVM(intf=self.intf, \
lv_name=device.name, pv_names=paths)):
# 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)
elif not device.complete:
# 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)
return
# Address the inconsistencies present in the tree leaves.
for leaf in self.leaves:
leafInconsistencies(leaf)
# Automatically handle the cases where we find a format on a
# disk with partitions. I trust that the partitions list
# avoids the ignored devices.
for part in self.getDevicesByInstance(PartitionDevice):
if part.parents[0].format.type is not None:
disk = part.parents[0]
format = formats.getFormat(None,
device=disk.path,
exists=True)
log.warning("Automatically corrected fomrat error on %s. "
"Changed from %s to %s." %
(disk.name, disk.format, format))
disk.format = format
def identifyMultipaths(self, 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 pairs [sda,sdc] and [sdb,sdd]
# [sr0, sda, sda1, sdb, sda2, sdc, sdd, sdc1, sdc2, sde, sde1]
# sample output:
# [sr0, sda, sdb, sdc, sdd, sde, sde1]
log.info("devices to scan for multipath: %s" % [d['name'] for d in devices])
serials = {}
non_disk_devices = {}
for d in devices:
serial = udev_device_get_serial(d)
if (not udev_device_is_disk(d)) or \
(not d.has_key('ID_SERIAL_SHORT')):
non_disk_devices.setdefault(serial, [])
non_disk_devices[serial].append(d)
log.info("adding %s to non_disk_device list" % (d['name'],))
continue
serials.setdefault(serial, [])
serials[serial].append(d)
singlepath_disks = []
multipath_disks = []
for serial, disks in serials.items():
if len(disks) == 1:
log.info("adding %s to singlepath_disks" % (disks[0]['name'],))
singlepath_disks.append(disks[0])
else:
multipath_members = {}
for d in disks:
log.info("adding %s to multipath_disks" % (d['name'],))
d["ID_FS_TYPE"] = "multipath_member"
multipath_disks.append(d)
multipath_members[d['name']] = { 'info': d,
'found': False }
log.info("found multipath set: [%s]" % [d['name'] for d in disks])
for serial in [d['ID_SERIAL_SHORT'] for d in multipath_disks]:
if non_disk_devices.has_key(serial):
log.info("filtering out non disk devices [%s]" % [d['name'] for d in non_disk_devices[serial]])
del non_disk_devices[serial]
partition_devices = []
for devs in non_disk_devices.values():
partition_devices += devs
# this is the list of devices we want to keep from the original
# device list, but we want to maintain its original order.
okdevs = singlepath_disks + multipath_disks + partition_devices
names = [d['name'] for d in okdevs]
retdevs = []
for dev in devices:
if dev['name'] in names:
retdevs.append(dev)
log.info("devices post multipath scan: %s" % [d['name'] for d in retdevs])
return retdevs
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)
# each iteration scans any devices that have appeared since the
# previous iteration
old_devices = {}
ignored_devices = []
first_iteration = True
handled_mpaths = False
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:
if handled_mpaths:
# nothing is changing -- we are finished building devices
break
for mp in self.__multipaths.values():
log.info("adding mpath device %s" % (mp.name,))
mp.setup()
self._addDevice(mp)
handled_mpaths = True
if first_iteration:
devices = self.identifyMultipaths(devices)
first_iteration = False
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 (DeviceError, DeviceFormatError, LVMError) 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, path):
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
0707010002C043000081A4000001F4000001F4000000014A843E430000240E000000FD0000000200000000000000000000001000000000storage/zfcp.py #
# 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 .
#
# Author(s): Karsten Hopp
#
import string
import os
from constants import *
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)
unitadd = "%s/%s/%s/unit_add" %(zfcpsysfs, self.devnum, self.wwpn)
try:
if not os.path.exists(unitadd):
loggedWriteLineToFile(portadd, self.wwpn)
loggedWriteLineToFile(unitadd, self.fcplun)
loggedWriteLineToFile(online, "1")
except Exception, e:
log.warn("error bringing zfcp device %s online: %s"
%(self.devnum, e))
return False
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)
portremove = "%s/%s/port_remove" %(zfcpsysfs, self.devnum)
unitremove = "%s/%s/%s/unit_remove" %(zfcpsysfs, self.devnum, self.wwpn)
try:
self.offlineSCSIDevice()
loggedWriteLineToFile(offline, "0")
loggedWriteLineToFile(unitremove, self.fcplun)
loggedWriteLineToFile(portremove, self.wwpn)
except Exception, e:
log.warn("error bringing zfcp device %s offline: %s"
%(self.devnum, e))
return False
return True
class ZFCP:
def __init__(self):
self.fcpdevs = []
self.hasReadConfig = False
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 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.hasReadConfig:
self.readConfig()
self.hasReadConfig = True
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()
# vim:tw=78:ts=4:et:sw=4
0707010002C044000081A4000001F4000001F4000000014A843E4300003068000000FD0000000200000000000000000000001000000000storage/udev.py # 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
#
import os
import stat
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
else:
if udev_device_get_name(dev) == _devices.devicePathToName(devspec):
ret = dev
break
del _devices
if ret:
return udev_device_get_name(dev)
def udev_get_block_devices():
udev_settle(timeout=30)
entries = []
for path in udev_enumerate_block_devices():
entry = udev_get_block_device(path)
if entry:
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
# 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 = os.path.realpath("/dev/live")
if livetarget.startswith("/dev"):
livetarget = livetarget[5:]
if livetarget.startswith(dev_name):
log.info("%s looks to be the live device; ignoring" % (dev_name,))
return True
if os.path.exists("/sys/class/block/%s/device/model" %(dev_name,)):
model = open("/sys/class/block/%s/device/model" %(dev_name,)).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 {"name": None, "symlinks": []}
else:
return dev
def udev_parse_block_entry(buf):
dev = udev_parse_entry(buf)
for (key, value) in dev.iteritems():
if value.count(" "):
# eg: DEVLINKS
dev.update({key: value.split()})
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_multipath_member(info):
""" Return True if the device is part of a multipath. """
return info.get("ID_FS_TYPE") == "multipath_member"
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. """
return info.has_key("MD_DEVNAME") and \
info.has_key("MD_METADATA")
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")
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["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_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_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
# iscsi disks have ID_PATH in the form of:
# ip-${iscsi_address}:${iscsi_port}-iscsi-${iscsi_tgtname}-lun-${lun}
def udev_device_is_iscsi(info):
try:
path_components = info["ID_PATH"].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 = info["ID_PATH"].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 = info["ID_PATH"].split("-")
return path_components[1].split(":")[0]
def udev_device_get_iscsi_port(info):
path_components = info["ID_PATH"].split("-")
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 = info["ID_PATH"].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 = info["ID_PATH"].split("-")
return path_components[1]
def udev_device_get_fcoe_identifier(info):
path_components = info["ID_PATH"].split("-")
return path_components[3]
0707010002C045000081A4000001F4000001F4000000014A843E430000056B000000FD0000000200000000000000000000001400000000storage/partspec.py # 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
#
class PartSpec(object):
def __init__(self, mountpoint=None, fstype=None, size=None, maxSize=None,
grow=False, asVol=False, weight=0):
self.mountpoint = mountpoint
self.fstype = fstype
self.size = size
self.maxSize = maxSize
self.grow = grow
self.asVol = asVol
self.weight = weight
0707010002C046000081A4000001F4000001F4000000014A843E4300000CFB000000FD0000000200000000000000000000001000000000storage/fcoe.py #
# 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 .
#
import os
import iutil
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",
searchPath = 1)
_fcoe_module_loaded = True
return os.access("/sys/module/fcoe", os.X_OK)
class fcoe(object):
def __init__(self):
self.started = False
self.nics = []
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",
searchPath = 1)
if intf:
w.pop()
def startup(self, intf = None):
if self.started:
return
if not has_fcoe():
return
# Place holder for adding autodetection of FCoE setups based on
# firmware tables (like iBFT for iSCSI)
self.started = True
def addSan(self, nic, intf=None):
if not has_fcoe():
raise IOError, _("FCoE not available")
log.info("Activating FCoE SAN attached to %s" % nic)
f = open("/sys/module/fcoe/parameters/create", "w")
f.write(nic)
f.close()
self._stabilize(intf)
self.nics.append(nic)
def writeKS(self, f):
# fixme plenty (including add ks support for fcoe in general)
return
def write(self, instPath, anaconda):
if flags.test or not self.nics:
return
if not os.path.isdir(instPath + "/etc/fcoe"):
os.makedirs(instPath + "/etc/fcoe", 0755)
for nic 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')
os.write(fd, 'DCB_REQUIRED="no"\n')
os.close(fd)
return
# vim:tw=78:ts=4:et:sw=4
0707010002C047000081A4000001F4000001F4000000014A843E4300002FB6000000FD0000000200000000000000000000001800000000storage/deviceaction.py # 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
#
import copy
from udev import *
from devices import StorageDevice, PartitionDevice
from formats import getFormat
from errors import *
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
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():
if self.device.format:
fmt_type = self.device.format.type
else:
fmt_type = None
s += " %s on" % fmt_type
if self.isMigrate():
pass
s += " %s (%s)" % (self.device.name, self.device.type)
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()
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):
if isinstance(self.device, PartitionDevice):
if self.format.partedFlag is not None:
self.device.setFlag(self.format.partedFlag)
self.device.disk.commit()
udev_settle()
self.device.setup()
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
@property
def format(self):
return self.device.format
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)
# Save a deep copy of the device stack this format occupies.
# This is necessary since the stack of devices and formats
# required to get to this format may get yanked out from under
# us between now and execute.
self._device = copy.deepcopy(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:
if isinstance(self.device, PartitionDevice) and \
self.origFormat.partedFlag is not None:
# unset partition flags and commit
self.device.unsetFlag(self.origFormat.partedFlag)
self.device.disk.commit()
udev_settle()
# set up our copy of the original device stack since the
# reference we got may have had any number of things changed
# since then (most notably, formats removed by this very
# class' constructor)
self._device.setup()
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()
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()
self.device.format.doMigrate(intf=intf)
def cancel(self):
self.device.format.migrate = False
0707010002C048000081A4000001F4000001F4000000014A843E430000BC76000000FD0000000200000000000000000000001800000000storage/partitioning.py # 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
#
import sys
import os
from operator import add, sub
import parted
from pykickstart.constants import *
from constants import *
from errors import *
from deviceaction import *
from devices import PartitionDevice, LUKSDevice, devicePathToName
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.id.storage.disks:
if anaconda.id.storage.clearPartDisks and \
(disk.name not in anaconda.id.storage.clearPartDisks):
continue
partedDisk = disk.partedDisk
part = disk.partedDisk.getFirstPartition()
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.id.storage.encryptedAutoPart:
fmt_type = "luks"
else:
fmt_type = "lvmpv"
part = anaconda.id.storage.newPartition(fmt_type=fmt_type,
size=1,
grow=True,
disks=[disk])
anaconda.id.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.id.storage.autoPartitionRequests:
if request.asVol:
continue
if request.fstype is None:
request.fstype = anaconda.id.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.id.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.id.storage.createDevice(dev)
# make sure preexisting broken lvm/raid configs get out of the way
return
def _scheduleLVs(anaconda, devs):
if anaconda.id.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.id.storage.createDevice(pv)
else:
pvs = devs
# create a vg containing all of the autopart pvs
vg = anaconda.id.storage.newVG(pvs=pvs)
anaconda.id.storage.createDevice(vg)
#
# Convert storage.autoPartitionRequests into Device instances and
# schedule them for creation.
#
# Second pass, for LVs only.
for request in anaconda.id.storage.autoPartitionRequests:
if not request.asVol:
continue
if request.fstype is None:
request.fstype = anaconda.id.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.id.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.id.storage.createDevice(dev)
def doAutoPartition(anaconda):
log.debug("doAutoPartition(%s)" % anaconda)
log.debug("doAutoPart: %s" % anaconda.id.storage.doAutoPart)
log.debug("clearPartType: %s" % anaconda.id.storage.clearPartType)
log.debug("clearPartDisks: %s" % anaconda.id.storage.clearPartDisks)
log.debug("autoPartitionRequests: %s" % anaconda.id.storage.autoPartitionRequests)
log.debug("storage.disks: %s" % anaconda.id.storage.disks)
log.debug("all names: %s" % [d.name for d in anaconda.id.storage.devices])
if anaconda.dir == DISPATCH_BACK:
anaconda.id.storage.reset()
return
disks = []
devs = []
if anaconda.id.storage.doAutoPart:
clearPartitions(anaconda.id.storage)
if anaconda.id.storage.doAutoPart:
(disks, devs) = _createFreeSpacePartitions(anaconda)
if disks == []:
anaconda.intf.messageWindow(_("Error Partitioning"),
_("Could not find enough free space "
"for automatic partitioning, please "
"use another partitioning method."),
custom_icon='error')
anaconda.id.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.id.storage,
exclusiveDisks=anaconda.id.storage.clearPartDisks)
if anaconda.id.storage.doAutoPart:
_scheduleLVs(anaconda, devs)
# grow LVs
growLVM(anaconda.id.storage)
except PartitioningWarning as msg:
if not anaconda.isKickstart:
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.id.storage.reset()
if not anaconda.isKickstart:
extra = ""
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"
"%s.%s") % (msg, extra), custom_icon='error')
if anaconda.isKickstart:
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.id.storage.sanityCheck()
if warnings:
for warning in warnings:
log.warning(warning)
if errors:
errortxt = "\n".join(errors)
if anaconda.isKickstart:
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%s\n\n"
"This can happen if there is not enough "
"space on your hard drive(s) for the "
"installation. %s")
% (errortxt, extra),
custom_icon='error')
#
# XXX if in kickstart we reboot
#
if anaconda.isKickstart:
anaconda.intf.messageWindow(_("Unrecoverable Error"),
_("The system will now reboot."))
sys.exit(0)
anaconda.id.storage.reset()
return DISPATCH_BACK
def shouldClear(part, clearPartType, clearPartDisks=None):
if not isinstance(part, PartitionDevice):
return False
if not clearPartType in [CLEARPART_TYPE_LINUX, CLEARPART_TYPE_ALL]:
return False
# Never clear the special first partition on a Mac disk label, as that
# holds the partition table itself.
if part.disk.partedDisk.type == "mac" and \
part.partedPartition.number == 1 and \
part.partedPartition.name == "Apple":
return False
# If we got a list of disks to clear, make sure this one's on it
if clearPartDisks and part.disk.name not in clearPartDisks:
return False
# Don't clear partitions holding install media.
if part.protected:
return False
# We don't want to fool with extended partitions, freespace, &c
if part.partType not in [parted.PARTITION_NORMAL, parted.PARTITION_LOGICAL]:
return False
if clearPartType == CLEARPART_TYPE_LINUX and \
not part.format.linuxNative and \
not part.getFlag(parted.PARTITION_LVM) and \
not part.getFlag(parted.PARTITION_RAID) and \
not part.getFlag(parted.PARTITION_SWAP):
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]
disks = [] # a list of disks from which we've removed partitions
clearparts = [] # list of partitions we'll remove
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])
disk_name = os.path.basename(part.partedPartition.disk.device.path)
if disk_name not in disks:
disks.append(disk_name)
clearparts.append(part)
for part in clearparts:
storage.destroyDevice(part)
# now remove any empty extended partitions
removeEmptyExtendedPartitions(storage)
def removeEmptyExtendedPartitions(storage):
for disk in storage.disks:
log.debug("checking whether disk %s has an empty extended" % disk.name)
extended = disk.partedDisk.getExtendedPartition()
logical_parts = disk.partedDisk.getLogicalPartitions()
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)
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
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
# larger requests go to the front of the list
ret -= cmp(part1.req_base_size, part2.req_base_size) * 100
# fixed size requests to the front
ret += cmp(part1.req_grow, part2.req_grow) * 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
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 and \
extended and logical_count < max_logicals:
part_type = parted.PARTITION_LOGICAL
elif primary_count == (disk.maxPrimaryPartitionCount - 1) and \
not extended and supports_extended:
# last chance to create an extended partition
part_type = parted.PARTITION_EXTENDED
elif no_primary and extended and logical_count < max_logicals:
# create a logical even though we could presumably create a
# primary instead
part_type = parted.PARTITION_LOGICAL
elif not no_primary:
# XXX there is a possiblity that the only remaining free space on
# the disk lies within the extended partition, but we will
# try to create a primary first
part_type = parted.PARTITION_NORMAL
return part_type
def getBestFreeSpaceRegion(disk, part_type, req_size,
boot=None, best_free=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
"""
log.debug("getBestFreeSpaceRegion: disk=%s part_type=%d req_size=%dMB boot=%s best=%s" %
(disk.device.path, part_type, req_size, boot, best_free))
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()
if req_size <= free_size:
if not best_free or 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 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.disks
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
# FIXME: make sure non-existent partitions have empty parents list
allocatePartitions(disks, partitions)
growPartitions(disks, partitions)
# 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.partedDisk.getExtendedPartition()
if not extended:
continue
extendedName = devicePathToName(extended.getDeviceNodeName())
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
storage.createDevice(device)
def allocatePartitions(disks, partitions):
""" 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" % (disks,
partitions))
new_partitions = [p for p in partitions if not p.exists]
new_partitions.sort(cmp=partitionCompare)
# XXX is this needed anymore?
partedDisks = {}
for disk in disks:
if disk.path not in partedDisks.keys():
partedDisks[disk.path] = disk.partedDisk #.duplicate()
# remove all newly added partitions from the disk
log.debug("removing all non-preexisting from disk(s)")
for _part in new_partitions:
if _part.partedPartition:
if _part.isExtended:
# these get removed last
continue
#_part.disk.partedDisk.removePartition(_part.partedPartition)
partedDisk = partedDisks[_part.disk.partedDisk.device.path]
#log.debug("removing part %s (%s) from disk %s (%s)" %
# (_part.partedPartition.path,
# [p.path for p in _part.partedPartition.disk.partitions],
# partedDisk.device.path,
# [p.path for p in partedDisk.partitions]))
partedDisk.removePartition(_part.partedPartition)
_part.partedPartition = None
_part.disk = None
# remove empty extended so it doesn't interfere
extended = partedDisk.getExtendedPartition()
if extended and not partedDisk.getLogicalPartitions():
log.debug("removing empty extended partition")
#partedDisk.minimizeExtendedPartition()
partedDisk.removePartition(extended)
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
log.debug("allocating partition: %s ; disks: %s ; boot: %s ; "
"primary: %s ; size: %dMB ; grow: %s ; max_size: %s" %
(_part.name, 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
# loop through disks
for _disk in req_disks:
disk = partedDisks[_disk.path]
#for p in disk.partitions:
# log.debug("disk %s: part %s" % (disk.device.path, p.path))
sectorSize = disk.device.physicalSectorSize
best = None
log.debug("checking freespace on %s" % _disk.name)
new_part_type = getNextPartitionType(disk)
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 disk.primaryPartitionCount < disk.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(disk,
new_part_type,
_part.req_size,
best_free=free,
boot=_part.req_bootable)
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(disk, no_primary=True)
if new_part_type:
best = getBestFreeSpaceRegion(disk,
new_part_type,
_part.req_size,
best_free=free,
boot=_part.req_bootable)
if best and free != best:
# 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()))
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
disk = _disk.partedDisk
# create the extended partition if needed
# TODO: move to a function (disk, free)
if part_type == parted.PARTITION_EXTENDED:
log.debug("creating extended partition")
geometry = parted.Geometry(device=disk.device,
start=free.start,
length=free.length,
end=free.end)
extended = parted.Partition(disk=disk,
type=parted.PARTITION_EXTENDED,
geometry=geometry)
constraint = parted.Constraint(device=disk.device)
# FIXME: we should add this to the tree as well
disk.addPartition(extended, constraint)
# end proposed function
# now the extended partition exists, so set type to logical
part_type = parted.PARTITION_LOGICAL
# recalculate freespace
log.debug("recalculating free space")
free = getBestFreeSpaceRegion(disk,
part_type,
_part.req_size,
boot=_part.req_bootable)
if not free:
raise PartitioningError("not enough free space after "
"creating extended partition")
# create minimum geometry for this request
# req_size is in MB
sectors_per_track = disk.device.biosGeometry[2]
length = (_part.req_size * (1024 * 1024)) / sectorSize
new_geom = parted.Geometry(device=disk.device,
start=max(sectors_per_track, free.start),
length=length)
# create maximum and minimum geometries for constraint
start = max(0 , free.start - 1)
max_geom = parted.Geometry(device=disk.device,
start=start,
length=min(length + 1, disk.device.length - start))
min_geom = parted.Geometry(device=disk.device,
start=free.start + 1,
length=length-1)
# create the partition and add it to the disk
partition = parted.Partition(disk=disk,
type=part_type,
geometry=new_geom)
constraint = parted.Constraint(maxGeom=max_geom, minGeom=min_geom)
disk.addPartition(partition=partition,
constraint=constraint)
log.debug("created partition %s of %dMB and added it to %s" %
(partition.getDeviceNodeName(), partition.getSize(), disk))
# 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 = disk.getPartitionByPath(_part.path)
def growPartitions(disks, partitions):
""" Grow all growable partition requests.
All requests should know what disk they will be on by the time
this function is called. This is reflected in the
PartitionDevice's disk attribute. Note that the req_disks
attribute remains unchanged.
The total available free space is summed up for each disk and
partition requests are allocated a maximum percentage of the
available free space on their disk based on their own base size.
Each attempted size means calling allocatePartitions again with
one request's size having changed.
After taking into account several factors that may limit the
maximum size of a requested partition, we arrive at a firm
maximum number of sectors by which a request can potentially grow.
An initial attempt is made to allocate the full maximum size. If
this fails, we begin a rough binary search with a maximum of three
iterations to settle on a new size.
Arguments:
disks -- a list of all usable disks (DiskDevice instances)
partitions -- a list of all partitions (PartitionDevice
instances)
"""
log.debug("growPartitions: disks=%s, partitions=%s" %
([d.name for d in disks], [p.name for p in partitions]))
all_growable = [p for p in partitions if p.req_grow]
if not all_growable:
return
# sort requests by base size in decreasing order
all_growable.sort(key=lambda p: p.req_size, reverse=True)
log.debug("growable requests are %s" % [p.name for p in all_growable])
for disk in disks:
log.debug("growing requests on %s" % disk.name)
for p in disk.partedDisk.partitions:
log.debug(" %s: %s (%dMB)" % (disk.name, p.getDeviceNodeName(),
p.getSize()))
sectorSize = disk.partedDisk.device.physicalSectorSize
# get a list of free space regions on the disk
free = disk.partedDisk.getFreeSpaceRegions()
if not free:
log.debug("no free space on %s" % disk.name)
continue
# sort the free regions in decreasing order of size
free.sort(key=lambda r: r.length, reverse=True)
disk_free = reduce(lambda x,y: x + y, [f.length for f in free])
log.debug("total free: %d sectors ; largest: %d sectors (%dMB)"
% (disk_free, free[0].length, free[0].getSize()))
# make a list of partitions currently allocated on this disk
# -- they're already sorted
growable = []
disk_total = 0
for part in all_growable:
#log.debug("checking if part %s (%s) is on this disk" % (part.name,
# part.disk.name))
if part.disk == disk:
growable.append(part)
disk_total += part.partedPartition.geometry.length
log.debug("add %s (%dMB/%d sectors) to growable total"
% (part.name, part.partedPartition.getSize(),
part.partedPartition.geometry.length))
log.debug("growable total is now %d sectors" % disk_total)
# now we loop through the partitions...
# this first loop is to identify obvious chunks of free space that
# will be left over due to max size
leftover = 0
limited = {}
unlimited_total = 0
for part in growable:
# calculate max number of sectors this request can grow
req_sectors = part.partedPartition.geometry.length
share = float(req_sectors) / float(disk_total)
max_grow = (share * disk_free)
max_sectors = req_sectors + max_grow
limited[id(part)] = False
if part.req_max_size:
req_max_sect = (part.req_max_size * (1024 * 1024)) / sectorSize
if req_max_sect < max_sectors:
mb = ((max_sectors - req_max_sect) * sectorSize) / (1024*1024)
log.debug("adding %dMB to leftovers from %s"
% (mb, part.name))
leftover += (max_sectors - req_max_sect)
limited[id(part)] = True
if not limited[id(part)]:
unlimited_total += req_sectors
# now we loop through the partitions...
for part in growable:
# calculate max number of sectors this request can grow
req_sectors = part.partedPartition.geometry.length
share = float(req_sectors) / float(disk_total)
max_grow = (share * disk_free)
if not limited[id(part)]:
leftover_share = float(req_sectors) / float(unlimited_total)
max_grow += leftover_share * leftover
max_sectors = req_sectors + max_grow
max_mb = (max_sectors * sectorSize) / (1024 * 1024)
log.debug("%s: base_size=%dMB, max_size=%sMB" %
(part.name, part.req_base_size, part.req_max_size))
log.debug("%s: current_size=%dMB (%d sectors)" %
(part.name, part.partedPartition.getSize(),
part.partedPartition.geometry.length))
log.debug("%s: %dMB (%d sectors, or %d%% of %d)" %
(part.name, max_mb, max_sectors, share * 100, disk_free))
log.debug("checking constraints on max size...")
# don't grow beyond the request's maximum size
if part.req_max_size:
log.debug("max_size: %dMB" % part.req_max_size)
# FIXME: round down to nearest cylinder boundary
req_max_sect = (part.req_max_size * (1024 * 1024)) / sectorSize
if req_max_sect < max_sectors:
max_grow -= (max_sectors - req_max_sect)
max_sectors = req_sectors + max_grow
# don't grow beyond the resident filesystem's max size
if part.format.maxSize > 0:
log.debug("format maxsize: %dMB" % part.format.maxSize)
# FIXME: round down to nearest cylinder boundary
fs_max_sect = (part.format.maxSize * (1024 * 1024)) / sectorSize
if fs_max_sect < max_sectors:
max_grow -= (max_sectors - fs_max_sect)
max_sectors = req_sectors + max_grow
# we can only grow as much as the largest free region on the disk
if free[0].length < max_grow:
log.debug("largest free region: %d sectors (%dMB)" %
(free[0].length, free[0].getSize()))
# FIXME: round down to nearest cylinder boundary
max_grow = free[0].length
max_sectors = req_sectors + max_grow
# Now, we try to grow this partition as close to max_grow
# sectors as we can.
#
# We could call allocatePartitions after modifying this
# request and saving the original value of part.req_size,
# or we could try to use disk.maximizePartition().
max_size = (max_sectors * sectorSize) / (1024 * 1024)
orig_size = part.req_size
# try the max size to begin with
log.debug("attempting to allocate maximum size: %dMB" % max_size)
part.req_size = max_size
try:
allocatePartitions(disks, partitions)
except PartitioningError, e:
log.debug("max size attempt failed: %s (%dMB)" % (part.name,
max_size))
part.req_size = orig_size
else:
continue
log.debug("starting binary search: size=%d max_size=%d" % (part.req_size, max_size))
count = 0
op_func = add
increment = max_grow
last_good_size = part.req_size
last_outcome = None
while count < 3:
last_size = part.req_size
increment /= 2
req_sectors = op_func(req_sectors, increment)
part.req_size = (req_sectors * sectorSize) / (1024 * 1024)
log.debug("attempting size=%dMB" % part.req_size)
count += 1
try:
allocatePartitions(disks, partitions)
except PartitioningError, e:
log.debug("attempt at %dMB failed" % part.req_size)
op_func = sub
last_outcome = False
else:
op_func = add
last_good_size = part.req_size
last_outcome = True
if not last_outcome:
part.req_size = last_good_size
log.debug("backing up to size=%dMB" % part.req_size)
try:
allocatePartitions(disks, partitions)
except PartitioningError, e:
raise PartitioningError("failed to grow partitions")
# reset all requests to their original requested size
for part in partitions:
if part.exists:
continue
part.req_size = part.req_base_size
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 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
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!