From: Michael Tremer Date: Wed, 18 Feb 2009 22:30:19 +0000 (+0100) Subject: Fixed LVM and RAID stuff. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6f70efdfff2c81ac395053b3cc6f0177e73355a5;p=ipfire-3.x.git Fixed LVM and RAID stuff. --- diff --git a/src/install/etc/sysconfig/modules b/src/install/etc/sysconfig/modules deleted file mode 100644 index 479d4a553..000000000 --- a/src/install/etc/sysconfig/modules +++ /dev/null @@ -1,22 +0,0 @@ -######################################################################## -# Begin /etc/sysconfig/modules -# -# Description : Module auto-loading configuration -# -# Authors : -# -# Version : 00.00 -# -# Notes : The syntax of this file is as follows: -# [ ...] -# -# Each module should be on it's own line, and any options that you want -# passed to the module should follow it. The line deliminator is either -# a space or a tab. -######################################################################## - -ext3 -reiserfs -xfs - -# End /etc/sysconfig/modules diff --git a/src/pomona/Makefile b/src/pomona/Makefile index 4b549e1a2..ab6f4978b 100644 --- a/src/pomona/Makefile +++ b/src/pomona/Makefile @@ -37,7 +37,6 @@ install: all [ $$? = 0 ] || exit 1; \ done cp -vf *.py lang-{table,names} $(PYTHONLIBDIR) - chmod 755 $(PYTHONLIBDIR)/installer.py sed -e "s/VERSION/$(VERSION)/g" \ -e "s/SNAME/$(SNAME)/g" \ -e "s/PNAME/$(PNAME)/g" \ diff --git a/src/pomona/autopart.py b/src/pomona/autopart.py index 67964f910..0dcb89ea4 100644 --- a/src/pomona/autopart.py +++ b/src/pomona/autopart.py @@ -304,13 +304,6 @@ def fitSized(diskset, requests, primOnly = 0, newParts = None): if requests.isBootable(request): drives = getDriveList(request, diskset) numDrives = 0 # allocate bootable requests first - # FIXME: this is a hack to make sure prep boot is even more first - if request.fstype == fsset.fileSystemTypeGet("PPC PReP Boot"): - numDrives = -1 - if request.fstype == fsset.fileSystemTypeGet("Apple Bootstrap"): - numDrives = -1 - if request.fstype == fsset.fileSystemTypeGet("efi"): - numDrives = -1 else: drives = getDriveList(request, diskset) numDrives = len(drives) @@ -1169,13 +1162,9 @@ def doClearPartAction(pomona, partitions, diskset): continue def doAutoPartition(pomona): - instClass = pomona.id.instClass diskset = pomona.id.diskset partitions = pomona.id.partitions - if pomona.isKickstart: - partitions.setProtected(pomona.dispatch) - if pomona.dir == DISPATCH_BACK: diskset.refreshDevices() partitions.setFromDisk(diskset) @@ -1189,7 +1178,7 @@ def doAutoPartition(pomona): # XXX if we noop, then we fail later steps... let's just make it # the workstation default. should instead just never get here # if no autopart info - instClass.setDefaultPartitioning(partitions, doClear = 0) + pomona.setDefaultPartitioning(partitions, doClear = 0) # reset drive and request info to original state # XXX only do this if we're dirty @@ -1467,30 +1456,19 @@ def doAutoPartition(pomona): try: doPartitioning(diskset, partitions, doRefresh = 0) except PartitioningWarning, msg: - if not pomona.isKickstart: - pomona.intf.messageWindow(_("Warnings During Automatic Partitioning"), + pomona.intf.messageWindow(_("Warnings During Automatic Partitioning"), _("Following warnings occurred during automatic " "partitioning:\n\n%s") % (msg,), custom_icon='warning') - else: - lvmLog.warning(str(msg)) except PartitioningError, msg: # restore drives to original state diskset.refreshDevices() partitions.setFromDisk(diskset) partitions.setProtected(pomona.dispatch) - if not pomona.isKickstart: - extra = "" - pomona.dispatch.skipStep("partition", skip = 0) - else: - extra = _("\n\nPress 'OK' to exit the installer.") + pomona.dispatch.skipStep("partition", skip = 0) pomona.intf.messageWindow(_("Error Partitioning"), _("Could not allocate requested partitions: \n\n" - "%s.%s") % (msg, extra), custom_icon='error') - - - if pomona.isKickstart: - sys.exit(0) + "%s.") % (msg,), custom_icon='error') # now do a full check of the requests (errors, warnings) = partitions.sanityCheckAllRequests(diskset) @@ -1499,10 +1477,7 @@ def doAutoPartition(pomona): lvmLog.warning(warning) if errors: errortxt = string.join(errors, '\n') - if pomona.isKickstart: - extra = _("\n\nPress 'OK' to exit the installer.") - else: - extra = _("\n\nPress 'OK' to choose a different partitioning option.") + extra = _("\n\nPress 'OK' to choose a different partitioning option.") pomona.intf.messageWindow(_("Automatic Partitioning Errors"), _("The following errors occurred with your " @@ -1512,13 +1487,6 @@ def doAutoPartition(pomona): "installation. %s") % (errortxt, extra), custom_icon='error') - # - # XXX if in kickstart we reboot - # - if pomona.isKickstart: - pomona.intf.messageWindow(_("Unrecoverable Error"), - _("Your system will now be rebooted.")) - sys.exit(0) return DISPATCH_BACK def autoCreatePartitionRequests(autoreq): diff --git a/src/pomona/backend.py b/src/pomona/backend.py index 6c29ae65e..325fbe88b 100644 --- a/src/pomona/backend.py +++ b/src/pomona/backend.py @@ -36,6 +36,9 @@ class PomonaBackend: self.instPath = instPath self.modeText = "" + # some backends may have a special case for rootfs formatting + self.skipFormatRoot = False + def doPreSelection(self, intf, id, instPath): pass diff --git a/src/pomona/bootloader.py b/src/pomona/bootloader.py index 90fffd6a9..755b3eb8a 100644 --- a/src/pomona/bootloader.py +++ b/src/pomona/bootloader.py @@ -64,24 +64,10 @@ def getBootDevString(line): # there's no guarantee that data is written to the disk and grub # reads both the filesystem and the disk. suck. def syncDataToDisk(dev, mntpt, instRoot = "/"): - import isys, fsset isys.sync() isys.sync() isys.sync() - # and xfs is even more "special" (#117968) - if fsset.isValidXFS(dev): - pyfire.executil.execWithRedirect("/usr/sbin/xfs_freeze", - ["/usr/sbin/xfs_freeze", "-f", mntpt], - stdout = "/dev/tty5", - stderr = "/dev/tty5", - root = instRoot) - pyfire.executil.execWithRedirect("/usr/sbin/xfs_freeze", - ["/usr/sbin/xfs_freeze", "-u", mntpt], - stdout = "/dev/tty5", - stderr = "/dev/tty5", - root = instRoot) - class BootyNoKernelWarning(Exception): pass diff --git a/src/pomona/cryptodev.py b/src/pomona/cryptodev.py new file mode 100644 index 000000000..0efe94a4b --- /dev/null +++ b/src/pomona/cryptodev.py @@ -0,0 +1,252 @@ +# +# cryptodev.py +# +# Copyright (C) 2007 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 + +import logging +log = logging.getLogger("pomona") + +def isLuks(device): + if not device.startswith("/"): + device = "/dev/" + device + rc = iutil.execWithRedirect("cryptsetup", + ["isLuks", device], + stdout = "/dev/null", + stderr = "/dev/null", + searchPath = 1) + if rc: + return False + else: + return True + +def luksUUID(device): + if not device.startswith("/"): + device = "/dev/" + device + + if not isLuks(device): + return None + + uuid = iutil.execWithCapture("cryptsetup", ["luksUUID", device]) + uuid = uuid.strip() + return uuid + +class LUKSDevice: + """LUKSDevice represents an encrypted block device using LUKS/dm-crypt. + It requires an underlying block device and a passphrase to become + functional.""" + def __init__(self, device=None, passphrase=None, format=0): + self._device = None + self.passphrase = "" + self.name = "" + self.uuid = None + self.nameLocked = False + self.format = format + self.preexist = not format + self.packages = ["cryptsetup-luks"] + self.scheme = "LUKS" + + self.setDevice(device) + self.setPassphrase(passphrase) + if self.getUUID(): + name = "%s-%s" % (self.scheme.lower(), self.uuid) + self.setName(name, lock=True) + + def getScheme(self): + """Returns the name of the encryption scheme used by the device.""" + return self.scheme + + def setDevice(self, device): + if self._device == device: + return + + self._device = device + if device is not None: + # this will be temporary, but may be useful for debugging + name = "%s-%s" % (self.scheme.lower(), + os.path.basename(device)) + self.setName(name) + + def getDevice(self, encrypted=0): + if encrypted: + dev = self._device + else: + dev = "mapper/%s" % (self.name,) + + return dev + + def getUUID(self): + if self.format: + # self.format means we're going to reformat but haven't yet + # so we shouldn't act like there's anything worth seeing there + return + + if not self.uuid: + self.uuid = luksUUID(self.getDevice(encrypted=1)) + + return self.uuid + + def setName(self, name, lock=False): + """Set the name of the mapped device, eg: 'dmcrypt-sda3'""" + if self.name == name: + return + + if self.name and not self.getStatus(): + raise RuntimeError, "Cannot rename an active mapping." + + if self.nameLocked: + log.debug("Failed to change locked mapping name: %s" % + (self.name,)) + return + + self.name = name + if lock and name: + # don't allow anyone to lock the name as "" or None + self.nameLocked = True + + def setPassphrase(self, passphrase): + """Set the (plaintext) passphrase used to access the device.""" + self.passphrase = passphrase + + def crypttab(self): + """Return a crypttab formatted line describing this mapping.""" + format = "%-23s %-15s %s\n" + line = format % (self.name, + "UUID=%s" % (self.getUUID(),), + "none") + return line + + def getStatus(self): + """0 means active, 1 means inactive (or non-existent)""" + if not self.name: + return 1 + + rc = iutil.execWithRedirect("cryptsetup", + ["status", self.name], + stdout = "/dev/null", + stderr = "/dev/null", + searchPath = 1) + return rc + + def formatDevice(self): + """Write a LUKS header onto the device.""" + if not self.format: + return + + if not self.getStatus(): + log.debug("refusing to format active mapping %s" % (self.name,)) + return 1 + + if not self.passphrase: + raise RuntimeError, "Cannot create mapping without a passphrase." + + device = self.getDevice(encrypted=1) + if not device: + raise ValueError, "Cannot open mapping without a device." + + log.info("formatting %s as %s" % (device, self.getScheme())) + p = os.pipe() + os.write(p[1], "%s\n" % (self.passphrase,)) + os.close(p[1]) + + rc = iutil.execWithRedirect("cryptsetup", + ["-q", "luksFormat", + "/dev/%s" % (device,)], + stdin = p[0], + stdout = "/dev/null", + stderr = "/dev/tty5", + searchPath = 1) + self.format = 0 + return rc + + def openDevice(self): + if not self.getStatus(): + # already mapped + return 0 + + if not self.passphrase: + raise RuntimeError, "Cannot create mapping without a passphrase." + + device = self.getDevice(encrypted=1) + if not device: + raise ValueError, "Cannot open mapping without a device." + + uuid = self.getUUID() + if not uuid: + raise RuntimeError, "Device has no UUID." + + self.setName("%s-%s" % (self.scheme.lower(), uuid), lock=True) + + log.info("mapping %s device %s to %s" % (self.getScheme(), + device, + self.name)) + + p = os.pipe() + os.write(p[1], "%s\n" % (self.passphrase,)) + os.close(p[1]) + + rc = iutil.execWithRedirect("cryptsetup", + ["luksOpen", + "/dev/%s" % (device,), + self.name], + stdin = p[0], + stdout = "/dev/null", + stderr = "/dev/tty5", + searchPath = 1) + return rc + + def closeDevice(self): + if self.getStatus(): + # not mapped + return 0 + + log.info("unmapping %s device %s" % (self.getScheme(), self.name)) + rc = iutil.execWithRedirect("cryptsetup", + ["luksClose", self.name], + stdout = "/dev/null", + stderr = "/dev/tty5", + searchPath = 1) + return rc + + def addPassphrase(self, newpass): + if not newpass: + return 1 + + if newpass == self.passphrase: + return 0 + + p = os.pipe() + os.write(p[1], "%s\n%s" % (self.passphrase, newpass)) + os.close(p[1]) + + device = self.getDevice(encrypted=1) + log.info("adding new passphrase to %s device %s" % (self.getScheme(), + device)) + rc = iutil.execWithRedirect("cryptsetup", + ["-q", + "luksAddKey", + "/dev/%s" % (device,)], + stdin = p[0], + stdout = "/dev/null", + stderr = "/dev/tty5", + searchPath = 1) + + return rc diff --git a/src/pomona/dmraid.py b/src/pomona/dmraid.py new file mode 100644 index 000000000..5cee46986 --- /dev/null +++ b/src/pomona/dmraid.py @@ -0,0 +1,302 @@ +# +# dmraid.py - dmraid probing control +# +# Copyright (C) 2005 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): Peter Jones +# + +"""DMRaid probing control.""" +# XXX dmraid and md raid should be abstracted from the same thing. -pj +# XXX dmraid and lvm should use a common control mechanism (such as block.dm) +# for device-mapper. -pj + +import sys +import string +import block +import partedUtils +import raid +from flags import flags + +import logging +from pomona_log import logger, logFile + +logger.addLogger ("pomona.dmraid", minLevel=logging.DEBUG) +log = logging.getLogger("pomona.dmraid") +logger.addFileHandler (logFile, log) + +import isys + +# these arches can have their /boot on DMRAID and not have their +# boot loader blow up +# XXX This needs to be functional so it can test if drives sit on particular +# controlers. -pj +dmraidBootArches = [ "i386", "x86_64" ] + +dmNameUpdates = {} + +class DmDriveCache: + def __init__(self): + self.cache = {} + + def _addMapDevs(self, name, devs, obj): + isys.cachedDrives["mapper/" + name] = obj + log.debug("adding %s to isys cache" % ("mapper/" + name,)) + for dev in devs: + disk = dev.split('/')[-1] + if isys.cachedDrives.has_key(disk): + self.cache.setdefault(obj.name, {}) + self.cache[obj.name][obj.name] = obj + log.debug("adding %s to dm cache" % (disk,)) + self.cache[obj.name][disk] = isys.cachedDrives[disk] + log.debug("removing %s from isys cache" % (disk,)) + del isys.cachedDrives[disk] + + def add(self, obj): + if isinstance(obj, block.MultiPath): + return self._addMapDevs(obj.name, obj.bdevs, obj) + else: + members = [] + for m in obj.members: + if isinstance(m, block.RaidDev): + members.append(m.rd.device.path) + return self._addMapDevs(obj.name, members, obj) + + def remove(self, name): + objname = "mapper/" + name + if isys.cachedDrives.has_key(objname): + obj = isys.cachedDrives[objname] + log.debug("removing %s from isys cache" % (objname,)) + del isys.cachedDrives[objname] + if self.cache.has_key(obj.name): + del self.cache[obj.name][obj.name] + for k,v in self.cache[obj.name].items(): + log.debug("adding %s to isys cache" % (name,)) + isys.cachedDrives[k] = v + log.debug("removing %s from dm cache" % (obj,)) + del self.cache[obj.name] + + def rename(self, obj, newname): + oldname = 'mapper/' + obj.name + if isys.cachedDrives.has_key(oldname): + dmNameUpdates[obj.name] = newname + self.remove(oldname) + # XXX why doesn't setting the property work? + obj.set_name(newname) + self.add(obj) + + def __contains__(self, name): + return self.cache.has_key(name) + +cacheDrives = DmDriveCache() + +class DegradedRaidWarning(Warning): + def __init__(self, *args): + self.args = args + def __str__(self): + return self.args and ('%s' % self.args[0]) or repr(self) + +def scanForRaid(drives, degradedOk=False): + """Scans for dmraid devices on drives. + + drives is a list of device names. + Returns a list of (raidSet, parentRaidSet, devices, level, totalDisks) + tuples. + """ + + log.debug("scanning for dmraid on drives %s" % (drives,)) + + probeDrives = [] + for d in drives: + probeDrives.append("/dev/%s" % (d,)) + + dmsets = [] + def nonDegraded(rs): + log.debug("got raidset %s (%s)" % (rs, string.join(rs.member_devpaths))) + log.debug(" valid: %s found_devs: %s total_devs: %s" % (rs.valid, rs.rs.found_devs, rs.rs.total_devs)) + + if not rs.valid and not degradedOk: + log.warning("raid %s (%s) is degraded" % (rs, rs.name)) + #raise DegradedRaidWarning, rs + return False + return True + + raidsets = filter(nonDegraded, block.getRaidSets(probeDrives) or []) + def updateName(rs): + if dmNameUpdates.has_key(rs.name): + rs.set_name(dmNameUpdates[rs.name]) + cacheDrives.add(rs) + return rs + + return reduce(lambda x,y: x + [updateName(y),], raidsets, []) + +def renameRaidSet(rs, name): + cacheDrives.rename(rs, name) + +def startAllRaid(driveList): + """Do a raid start on raid devices.""" + + if not flags.dmraid: + return [] + log.debug("starting all dmraids on drives %s" % (driveList,)) + + try: + dmList = scanForRaid(driveList) + except Exception, e: + log.error("error scanning dmraid, disabling: %s" %(e,)) + flags.dmraid = 0 + dmList = [] + + newDmList = [] + for rs in dmList: + rs.prefix = '/dev/mapper/' + log.debug("starting raid %s with mknod=True" % (rs,)) + try: + rs.activate(mknod=True) + newDmList.append(rs) + except Exception, e: + log.error("Activating raid %s failed: " % (rs.rs,)) + log.error(" table: %s" % (rs.rs.table,)) + log.error(" exception: %s" % (e,)) + try: + rs.deactivate() + del rs + except: + pass + + return newDmList + +def stopAllRaid(dmList): + """Do a raid stop on each of the raid device tuples given.""" + + if not flags.dmraid: + return + log.debug("stopping all dmraids") + for rs in dmList: + log.debug("stopping raid %s" % (rs,)) + if rs.name in cacheDrives: + cacheDrives.remove(rs.name) + + rs.deactivate() + #block.removeDeviceMap(map) + +def isRaid6(raidlevel): + """Return whether raidlevel is a valid descriptor of RAID6.""" + return False + +def isRaid5(raidlevel): + """Return whether raidlevel is a valid descriptor of RAID5.""" + return False + +def isRaid1(raidlevel): + """Return whether raidlevel is a valid descriptor of RAID1.""" + return raid.isRaid1(raidlevel) + +def isRaid0(raidlevel): + """Return whether raidlevel is a valid descriptor of RAID1.""" + return raid.isRaid0(raidlevel) + +def get_raid_min_members(raidlevel): + """Return the minimum number of raid members required for raid level""" + return raid.get_raid_min_members(raidlevel) + +def get_raid_max_spares(raidlevel, nummembers): + """Return the maximum number of raid spares for raidlevel.""" + return raid.get_raid_max_spares(raidlevel, nummembers) + +def register_raid_device(dmname, newdevices, newlevel, newnumActive): + """Register a new RAID device in the dmlist.""" + raise NotImplementedError + +def lookup_raid_device(dmname): + """Return the requested RAID device information.""" + for rs, parent, devices, level, nrDisks, totalDisks in \ + partedUtils.DiskSet.dmList: + if dmname == rs.name: + return (rs.name, devices, level, totalDisks) + raise KeyError, "dm device not found" + +def scanForMPath(drives): + log.debug("scanning for multipath on drives %s" % (drives,)) + mpaths = [] + + probeDrives = [] + for d in drives: + probeDrives.append("/dev/%s" % (d,)) + + import block as _block + + log.debug("loading bdevid modules from: '%s'" % (_block.getBdevidPath(),)) + + _block.load("scsi") + mpaths = _block.getMPaths(probeDrives) + log.debug("mpaths: %s" % (mpaths,)) + + def updateName(mp): + if dmNameUpdates.has_key(mp.name): + mp.set_name(dmNameUpdates[mp.name]) + cacheDrives.add(mp) + return mp + + return reduce(lambda x,y: x + [updateName(y),], mpaths, []) + +def renameMPath(mpath, name): + cacheDrives.rename(mpath, name) + +def startMPath(mpath): + if flags.mpath == 0: + return + mpath.prefix = '/dev/mapper/' + log.debug("starting mpath %s with mknod=True" % (mpath,)) + mpath.activate(mknod=True) + +def startAllMPath(driveList): + """Start all of the MPaths of the specified drives.""" + + if not flags.mpath: + return [] + log.debug("starting all mpaths on drives %s" % (driveList,)) + + try: + mpList = scanForMPath(driveList) + except Exception, e: + log.error("error scanning mpaths, disabling: %s" %(e,)) + flags.mpath = 0 + mpList = [] + + for mp in mpList: + startMPath(mp) + return mpList + +def stopMPath(mp): + if flags.mpath == 0: + return + + log.debug("stopping mpath %s" % (mp,)) + if mp.name in cacheDrives: + cacheDrives.remove(mp.name) + + mp.deactivate() + #block.removeDeviceMap(map) + +def stopAllMPath(mpList): + """Do a mpath stop on each of the mpath device tuples given.""" + + if not flags.mpath: + return + log.debug("stopping all mpaths") + for mp in mpList: + stopMPath(mp) diff --git a/src/pomona/errors.py b/src/pomona/errors.py new file mode 100644 index 000000000..6708b1db5 --- /dev/null +++ b/src/pomona/errors.py @@ -0,0 +1,159 @@ +# +# errors.py: exception classes used throughout pomona +# +# Copyright (C) 2002, 2007, 2008 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): Peter Jones +# Chris Lumens +# Matt Wilson +# Jeremy Katz +# Mike Fulbright +# + +import string +import os +from constants import lvmErrorOutput + +"""Exceptions for use in lvm operations.""" + +class LvmError(Exception): + """An error occurred with lvm.""" + def __init__(self, command, name=None): + self.command = command + self.name = name + self.log = self.getLvmOutput() + + def getLvmOutput(self): + if not os.access(lvmErrorOutput, os.R_OK): + return "" + f = open(lvmErrorOutput, "r") + lines = reduce(lambda x,y: x + [string.strip(y),], f.readlines(), []) + lines = string.join(reduce(lambda x,y: x + [" %s" % (y,)], \ + lines, []), "\n") + return lines + + def __str__(self): + s = "" + if not self.name is None: + s = " for device %s" % (self.name,) + return "%s failed%s\nLog:\n%s" % (self.command, s, self.log) + +class LVCreateError(LvmError): + def __init__(self, vgname, lvname, size): + self.vgname = vgname + self.lvname = lvname + self.size = size + self.log = self.getLvmOutput() + + def __str__(self): + return "lvcreate of %d Megabyte lv \"%s\" on vg \"%s\" failed\n" \ + "Log:\n%s" % ( \ + self.size, self.lvname, self.vgname, self.log) + +class LVRemoveError(LvmError): + def __init__(self, vgname, lvname): + self.vgname = vgname + self.lvname = lvname + self.log = self.getLvmOutput() + + def __str__(self): + return "lvremove of lv \"%s\" from vg \"%s\" failed\nLog:\n%s" % ( \ + self.lvname, self.vgname, self.log) + +class LVResizeError(LvmError): + def __init__(self, vgname, lvname): + self.vgname = vgname + self.lvname = lvname + self.log = self.getLvmOutput() + + def __str__(self): + return "lvresize of lv \"%s\" from vg \"%s\" failed\nLog:\n%s" % ( \ + self.lvname, self.vgname, self.log) + +class VGCreateError(LvmError): + def __init__(self, vgname, PESize, nodes): + self.vgname = vgname + self.PESize = PESize + self.nodes = nodes + self.log = self.getLvmOutput() + + def __str__(self): + nodes = string.join(self.nodes, ' ') + return "vgcreate failed creating vg \"%s\" (PESize=%dkB) on PVs: %s\n" \ + "Log:\n%s" % ( \ + self.vgname, self.PESize, nodes, self.log) + +class VGRemoveError(LvmError): + def __init__(self, vgname): + self.vgname = vgname + self.log = self.getLvmOutput() + + def __str__(self): + return "vgremove of vg \"%s\" failed\nLog:\n%s" % ( \ + self.vgname, self.log) + +class PVRemoveError(LvmError): + def __init__(self, pvname): + self.pvname = pvname + self.log = self.getLvmOutput() + + def __str__(self): + return "pvremove of pv \"%s\" failed\nLog:\n%s" % ( \ + self.pvname, self.log) + +class PVCreateError(LvmError): + def __init__(self, pvname): + self.pvname = pvname + self.log = self.getLvmOutput() + + def __str__(self): + return "pvcreate of pv \"%s\" failed\nLog:\n%s" % ( \ + self.pvname, self.log) + +"""Exceptions for use in partitioning.""" + +class PartitioningError(Exception): + """A critical error which must be resolved to continue the installation.""" + def __init__(self, message=""): + self.message = str(message) + + def __str__ (self): + return self.message + +class PartitioningWarning(Exception): + """A warning which may be ignored and still complete the installation.""" + def __init__(self, message=""): + self.message = str(message) + + def __str__ (self): + return self.message + +class LabelError(Exception): + """The device could not be labeled.""" + def __init__(self, message=""): + self.message = str(message) + + def __str__(self): + return self.message + +"""Exceptions for use in package selection.""" + +class NoSuchGroup(Exception): + def __init__ (self, value): + self.value = value + + def __str__ (self): + return self.value diff --git a/src/pomona/flags.py b/src/pomona/flags.py index 7ff178b91..69b3768f6 100644 --- a/src/pomona/flags.py +++ b/src/pomona/flags.py @@ -50,6 +50,8 @@ class Flags: self.__dict__['flags']['debug'] = 0 self.__dict__['flags']['cmdline'] = self.createCmdlineDict() self.__dict__['flags']['network'] = False + self.__dict__['flags']['mpath'] = 1 + self.__dict__['flags']['dmraid'] = 1 for line in os.popen("tty"): line = line.strip() diff --git a/src/pomona/fsset.py b/src/pomona/fsset.py index 5510eeed2..1d0d3c3d8 100644 --- a/src/pomona/fsset.py +++ b/src/pomona/fsset.py @@ -701,9 +701,9 @@ fileSystemTypeRegister(ext3FileSystem()) class ext4FileSystem(extFileSystem): def __init__(self): extFileSystem.__init__(self) - self.name = "ext4dev" + self.name = "ext4" self.partedFileSystemType = parted.file_system_type_get("ext3") - self.extraFormatArgs = [ "-t", "ext4dev" ] + self.extraFormatArgs = [ "-t", "ext4" ] self.bootable = False # this is way way experimental at present... diff --git a/src/pomona/isys/sundries.h b/src/pomona/isys/sundries.h new file mode 100644 index 000000000..1fb67b868 --- /dev/null +++ b/src/pomona/isys/sundries.h @@ -0,0 +1,88 @@ +/* + * sundries.h: Support function prototypes. Functions are in sundries.c. + * + * Copyright (C) 2007 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 . + */ + +#include +#include +#include +#include +#include +#include +#if !defined(bool_t) && !defined(__GLIBC__) +#include +#endif + +extern int mount_quiet; +extern int verbose; +extern int sloppy; + +#define streq(s, t) (strcmp ((s), (t)) == 0) + + +/* String list data structure. */ +typedef struct string_list +{ + char *hd; + struct string_list *tl; +} *string_list; + +#define car(p) ((p) -> hd) +#define cdr(p) ((p) -> tl) + +string_list cons (char *a, const string_list); + +/* Functions in sundries.c that are used in mount.c and umount.c */ +void block_signals (int how); +char *canonicalize (const char *path); +char *realpath (const char *path, char *resolved_path); +void error (const char *fmt, ...); +int matching_type (const char *type, string_list types); +string_list parse_list (char *strings); +void *xmalloc (size_t size); +char *xstrdup (const char *s); +char *xstrndup (const char *s, int n); +char *xstrconcat2 (const char *, const char *); +char *xstrconcat3 (const char *, const char *, const char *); +char *xstrconcat4 (const char *, const char *, const char *, const char *); + +/* Here is some serious cruft. */ +#ifdef __GNUC__ +#if __GNUC__ > 2 || (defined(__GNUC_MINOR__) && __GNUC__ == 2 && __GNUC_MINOR__ >= 5) +void die (int errcode, const char *fmt, ...) __attribute__ ((noreturn)); +#else /* GNUC < 2.5 */ +void volatile die (int errcode, const char *fmt, ...); +#endif /* GNUC < 2.5 */ +#else /* !__GNUC__ */ +void die (int errcode, const char *fmt, ...); +#endif /* !__GNUC__ */ + +#ifdef HAVE_NFS +int nfsmount (const char *spec, const char *node, int *flags, + char **orig_opts, char **opt_args, int running_bg); +#endif + +/* exit status - bits below are ORed */ +#define EX_USAGE 1 /* incorrect invocation or permission */ +#define EX_SYSERR 2 /* out of memory, cannot fork, ... */ +#define EX_SOFTWARE 4 /* internal mount bug or wrong version */ +#define EX_USER 8 /* user interrupt */ +#define EX_FILEIO 16 /* problems writing, locking, ... mtab/fstab */ +#define EX_FAIL 32 /* mount failure */ +#define EX_SOMEOK 64 /* some mount succeeded */ + +#define EX_BG 256 /* retry in background (internal only) */ diff --git a/src/pomona/lvm.py b/src/pomona/lvm.py index bd708d33c..7531964cc 100644 --- a/src/pomona/lvm.py +++ b/src/pomona/lvm.py @@ -63,6 +63,11 @@ def has_lvm(): # now check to see if lvm is available has_lvm() +if lvmDevicePresent == 1: + log.info("LVM is enabled.") +else: + log.info("LVM is disabled.") + def lvmExec(*args): try: return iutil.execWithRedirect("lvm", args, stdout = lvmErrorOutput, @@ -86,7 +91,7 @@ def vgscan(): """Runs vgscan.""" global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: + if lvmDevicePresent == 0: return rc = lvmExec("vgscan", "-v") @@ -110,7 +115,7 @@ def vgcheckactive(volgroup = None): volgroup - optional parameter to inquire about a specific volume group. """ global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: + if lvmDevicePresent == 0: return False args = ["lvs", "--noheadings", "--units", "b", "--nosuffix", @@ -136,7 +141,7 @@ def vgactivate(volgroup = None): volgroup - optional single volume group to activate """ global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: + if lvmDevicePresent == 0: return args = ["vgchange", "-ay", "-v"] @@ -154,7 +159,7 @@ def vgdeactivate(volgroup = None): volgroup - optional single volume group to deactivate """ global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: + if lvmDevicePresent == 0: return args = ["vgchange", "-an", "-v"] @@ -173,7 +178,7 @@ def lvcreate(lvname, vgname, size): size - size of lv, in megabytes. """ global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: + if lvmDevicePresent == 0: return writeForceConf() vgscan() @@ -194,7 +199,7 @@ def lvremove(lvname, vgname): vgname - name of volume group lv is in. """ global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: + if lvmDevicePresent == 0: return args = ["lvremove", "-f", "-v"] @@ -210,7 +215,7 @@ def lvremove(lvname, vgname): def lvresize(lvname, vgname, size): global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: + if lvmDevicePresent == 0: return args = ["lvresize", "-An", "-L", "%dM" %(size,), "-v", "--force", @@ -232,7 +237,7 @@ def vgcreate(vgname, PESize, nodes): nodes - LVM Physical Volumes on which to put the new VG. """ global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: + if lvmDevicePresent == 0: return # rescan now that we've recreated pvs. ugh. @@ -256,7 +261,7 @@ def vgremove(vgname): vgname - name of volume group. """ global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: + if lvmDevicePresent == 0: return # find the Physical Volumes which make up this Volume Group, so we @@ -313,7 +318,7 @@ def pvcreate(node): node - path to device node on which to create the new PV." """ global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: + if lvmDevicePresent == 0: return # rescan now that we've recreated pvs. ugh. diff --git a/src/pomona/packages.py b/src/pomona/packages.py index 3bb39be4f..27dac7eb5 100644 --- a/src/pomona/packages.py +++ b/src/pomona/packages.py @@ -22,6 +22,8 @@ import os import sys import fsset import shutil +import time +import lvm from flags import flags from constants import * @@ -45,34 +47,91 @@ def copyPomonaLogs(pomona): except: pass +def doMigrateFilesystems(pomona): + if pomona.dir == DISPATCH_BACK: + return DISPATCH_NOOP + + if pomona.id.fsset.haveMigratedFilesystems(): + return DISPATCH_NOOP + + pomona.id.fsset.migrateFilesystems (pomona) + def turnOnFilesystems(pomona): + def handleResizeError(e, dev): + if os.path.exists("/tmp/resize.out"): + details = open("/tmp/resize.out", "r").read() + else: + details = "%s" %(e,) + pomona.intf.detailedMessageWindow(_("Resizing Failed"), + _("There was an error encountered " + "resizing the device %s.") %(dev,), + details, + type = "custom", + custom_buttons = [_("_Exit installer")]) + sys.exit(1) + if pomona.dir == DISPATCH_BACK: log.info("unmounting filesystems") pomona.id.fsset.umountFilesystems(pomona.rootPath) return - #pomona.id.partitions.doMetaDeletes(pomona.id.diskset) - pomona.id.diskset.clearDevices() + if not pomona.id.fsset.isActive(): + # turn off any swaps that we didn't turn on + # needed for live installs + iutil.execWithRedirect("swapoff", ["-a"], + stdout = "/dev/tty5", stderr="/dev/tty5", + searchPath = 1) + pomona.id.partitions.doMetaDeletes(pomona.id.diskset) pomona.id.fsset.setActive(pomona.id.diskset) + try: + pomona.id.fsset.shrinkFilesystems(pomona.id.diskset, pomona.rootPath) + except fsset.ResizeError, (e, dev): + handleResizeError(e, dev) + if not pomona.id.fsset.isActive(): pomona.id.diskset.savePartitions() - pomona.id.fsset.checkBadblocks(pomona.rootPath) - pomona.id.fsset.formatSwap(pomona.rootPath) - pomona.id.fsset.turnOnSwap(pomona.rootPath) - pomona.id.fsset.makeFilesystems(pomona.rootPath) - pomona.id.fsset.mountFilesystems(pomona) + # this is somewhat lame, but we seem to be racing with + # device node creation sometimes. so wait for device nodes + # to settle + w = pomona.intf.waitWindow(_("Activating"), _("Activating new partitions. Please wait...")) + time.sleep(1) + rc = iutil.execWithRedirect("/sbin/udevadm", [ "settle" ], + stdout = "/dev/tty5", + stderr = "/dev/tty5", + searchPath = 1) + w.pop() + + pomona.id.partitions.doEncryptionRetrofits() -def doMigrateFilesystems(pomona): - if pomona.dir == DISPATCH_BACK: - return DISPATCH_NOOP + try: + pomona.id.partitions.doMetaResizes(pomona.id.diskset) + except lvm.LVResizeError, e: + handleResizeError("%s" %(e,), "%s/%s" %(e.vgname, e.lvname)) - if pomona.id.fsset.haveMigratedFilesystems(): - return DISPATCH_NOOP + try: + pomona.id.fsset.growFilesystems(pomona.id.diskset, pomona.rootPath) + except fsset.ResizeError, (e, dev): + handleResizeError(e, dev) + + if not pomona.id.fsset.volumesCreated: + try: + pomona.id.fsset.createLogicalVolumes(pomona.rootPath) + except SystemError, e: + log.error("createLogicalVolumes failed with %s", str(e)) + pomona.intf.messageWindow(_("LVM operation failed"), + str(e)+"\n\n"+_("The installer will now exit..."), + type="custom", custom_icon="error", custom_buttons=[_("_Reboot")]) + sys.exit(0) - pomona.id.fsset.migrateFilesystems(pomona) + pomona.id.fsset.formatSwap(pomona.rootPath) + pomona.id.fsset.turnOnSwap(pomona.rootPath) + pomona.id.fsset.makeFilesystems(pomona.rootPath, + pomona.backend.skipFormatRoot) + pomona.id.fsset.mountFilesystems(pomona,0,0, + pomona.backend.skipFormatRoot) def setupTimezone(pomona): - # we don't need this going backwards + # we don't need this on going backwards if pomona.dir == DISPATCH_BACK: return @@ -89,13 +148,10 @@ def setupTimezone(pomona): args = [ "--hctosys" ] if pomona.id.timezone.utc: args.append("-u") - elif pomona.id.timezone.arc: - #args.append("-a") - args.append("-l") try: - iutil.execWithRedirect("/sbin/hwclock", args, stdin = None, - stdout = "/dev/tty5", stderr = "/dev/tty5") + iutil.execWithRedirect("/usr/sbin/hwclock", args, stdin = None, + stdout = "/dev/tty5", stderr = "/dev/tty5") except RuntimeError: log.error("Failed to set clock") diff --git a/src/pomona/partRequests.py b/src/pomona/partRequests.py index 73ec628eb..25f70b7d9 100644 --- a/src/pomona/partRequests.py +++ b/src/pomona/partRequests.py @@ -31,7 +31,6 @@ import string import os, sys, math from constants import * -from flags import * import gettext _ = lambda x: gettext.ldgettext("pomona", x) @@ -271,14 +270,6 @@ class RequestSpec: if self.fstype is None: return None - if flags.livecdInstall and self.mountpoint == "/" and not self.format: - return _("The mount point %s must be formatted during live CD " - "installs.") % self.mountpoint - if flags.livecdInstall and self.mountpoint == "/" and self.fstype.getName() not in ["ext3", "ext2"]: - return _("The mount point %s must be formatted during live CD " - "installs.") % self.mountpoint - - if self.fstype.isMountable(): if self.mountpoint in mustbeonroot: return _("This mount point is invalid. The %s directory must " diff --git a/src/pomona/partedUtils.py b/src/pomona/partedUtils.py index cee5a1e7b..8814921a2 100644 --- a/src/pomona/partedUtils.py +++ b/src/pomona/partedUtils.py @@ -36,7 +36,7 @@ import raid import dmraid import block import lvm -import traceback +import inspect from flags import flags from errors import * from constants import * @@ -79,7 +79,7 @@ def start_sector_to_cyl(device, sector): / (device.heads * device.sectors)) + 1)) def end_sector_to_cyl(device, sector): - """Return the closest cylinder (round up) to sector on device.""" + """Return the closest cylinder (round up) to sector on device.""" return int(math.ceil(float((sector + 1)) / (device.heads * device.sectors))) @@ -88,7 +88,7 @@ def start_cyl_to_sector(device, cyl): return long((cyl - 1) * (device.heads * device.sectors)) def end_cyl_to_sector(device, cyl): - "Return the sector corresponding to cylinder as a ending cylinder." + "Return the sector corresponding to cylinder as a ending cylinder." return long(((cyl) * (device.heads * device.sectors)) - 1) def getPartSize(partition): @@ -124,7 +124,7 @@ def getMaxAvailPartSizeMB(part): return math.floor(maxlen * part.geom.dev.sector_size / 1024.0 / 1024.0) def get_partition_by_name(disks, partname): - """Return the parted part object associated with partname. + """Return the parted part object associated with partname. Arguments: disks -- Dictionary of diskname->PedDisk objects @@ -232,7 +232,7 @@ def get_max_logical_partitions(disk): return 11 def map_foreign_to_fsname(type): - """Return the partition type associated with the numeric type.""" + """Return the partition type associated with the numeric type.""" if type in allPartitionTypesDict.keys(): return allPartitionTypesDict[type] else: @@ -321,8 +321,11 @@ def labelDisk(deviceFile, forceLabelType=None): def checkDiskLabel(disk, intf): """Check that the disk label on disk is valid for this machine type.""" arch = iutil.getArch() - if not arch in archLabels.keys() and disk.type.name == "msdos": - return 0 + if arch in archLabels.keys(): + pass + else: + if disk.type.name == "msdos": + return 0 if intf: rc = intf.messageWindow(_("Warning"), @@ -375,7 +378,7 @@ def validateFsType(part): # if the partition already has a type, no need to search if part.fs_type: return - + # first fsystem to probe wins, so sort the types into a preferred # order. fsnames = fsTypes.keys() @@ -396,7 +399,7 @@ def validateFsType(part): # in the case where a user does not modify partitions part.set_system(fstype) return - + def isLinuxNativeByNumtype(numtype): """Check if the type is a 'Linux native' filesystem.""" linuxtypes = [0x82, 0x83, 0x8e, 0xfd] @@ -408,15 +411,15 @@ def isLinuxNativeByNumtype(numtype): return 0 def sniffFilesystemType(device): - """Sniff to determine the type of fs on device. + """Sniff to determine the type of fs on device. device - name of device to sniff. """ return isys.readFSType(device) def getReleaseString(mountpoint): - if os.access(mountpoint + "/etc/system-release", os.R_OK): - f = open(mountpoint + "/etc/system-release", "r") + if os.access(mountpoint + "/etc/redhat-release", os.R_OK): + f = open(mountpoint + "/etc/redhat-release", "r") try: lines = f.readlines() except IOError: @@ -429,7 +432,7 @@ def getReleaseString(mountpoint): # return the first line with the newline at the end stripped if len(lines) == 0: return "" - relstr = string.strip(lines[0][:-1]) + relstr = string.strip(lines[0][:-1]) # get the release name and version # assumes that form is something @@ -496,7 +499,7 @@ class DiskSet: def renameMPath(self, mp, name): dmraid.renameMPath(mp, name) - + def stopMPath(self): """Stop all of the mpath devices associated with the DiskSet.""" @@ -527,6 +530,7 @@ class DiskSet: def stopDmRaid(self): """Stop all of the dmraid devices associated with the DiskSet.""" + if DiskSet.dmList: dmraid.stopAllRaid(DiskSet.dmList) DiskSet.dmList = None @@ -598,6 +602,14 @@ class DiskSet: if crypto: crypto.closeDevice() + # not doing this right now, because we should _always_ have a + # partition table of some kind on dmraid. + #if False: + # for rs in DiskSet.dmList or [] + DiskSet.mpList or []: + # label = isys.readFSLabel(rs.name) + # if label: + # labels[rs.name] = label + for dev, devices, level, numActive in DiskSet.mdList: crypto = encryptedDevices.get(dev) if crypto and not crypto.openDevice(): @@ -669,12 +681,11 @@ class DiskSet: except SystemError: pass - if found: isys.umount(self.pomona.rootPath) # now, look for candidate lvm roots - lvm.vgscan() - lvm.vgactivate() + lvm.vgscan() + lvm.vgactivate() for dev, crypto in self.pomona.id.partitions.encryptedDevices.items(): # FIXME: order these so LVM and RAID always work on the first try @@ -703,10 +714,7 @@ class DiskSet: except SystemError: pass - if found: - isys.umount(self.pomona.rootPath) - - lvm.vgdeactivate() + lvm.vgdeactivate() # don't stop raid until after we've looked for lvm on top of it self.stopMdRaid() @@ -731,6 +739,12 @@ class DiskSet: theDev = node if part.fs_type: fstype = part.fs_type.name + else: + fstype = None + + # parted doesn't tell ext4 from ext3 + if fstype == "ext3": + fstype = isys.readFSType(theDev) if crypto and not crypto.openDevice(): theDev = crypto.getDevice() @@ -750,20 +764,18 @@ class DiskSet: part = disk.next_partition(part) continue - isys.umount(self.pomona.rootPath) - part = disk.next_partition(part) return rootparts def driveList (self): """Return the list of drives on the system.""" - drives = isys.hardDriveDict().keys() - drives.sort (isys.compareDrives) - return drives + drives = isys.hardDriveDict().keys() + drives.sort (isys.compareDrives) + return drives def drivesByName (self): """Return a dictionary of the drives on the system.""" - return isys.hardDriveDict() + return isys.hardDriveDict() def savePartitions (self): """Write the partition tables out to the disks.""" @@ -783,13 +795,6 @@ class DiskSet: del disk continue - # FIXME: this belongs in parted itself, but let's do a hack... - if iutil.isX86() and disk.type.name == "gpt": - log.debug("syncing gpt to mbr for disk %s" % (disk.dev.path,)) - iutil.execWithRedirect("gptsync", [disk.dev.path,], - stdout="/tmp/gptsync.log", - stderr="/tmp/gptsync.err", - searchPath = 1) del disk self.refreshDevices() @@ -826,6 +831,26 @@ class DiskSet: del self.disks[disk] self.devicesOpen = False + def isDisciplineFBA (self, drive): + drive = drive.replace('/dev/', '') + + if drive.startswith("dasd"): + discipline = "/sys/block/%s/device/discipline" % (drive,) + if os.path.isfile(discipline): + try: + fp = open(discipline, "r") + lines = fp.readlines() + fp.close() + + if len(lines) == 1: + if lines[0].strip() == "FBA": + return True + except: + log.error("failed to check discipline of %s" % (drive,)) + pass + + return False + def _askForLabelPermission(self, intf, drive, clearDevs, initAll, ks): #Do not try to initialize device's part. table in rescue mode if self.pomona.rescue: @@ -833,7 +858,8 @@ class DiskSet: return False rc = 0 - if (ks and (drive in clearDevs) and initAll): + if (ks and (drive in clearDevs) and initAll) or \ + self.isDisciplineFBA(drive): rc = 1 elif intf: deviceFile = "/dev/" + drive @@ -863,7 +889,6 @@ class DiskSet: try: try: - # FIXME: need the right fix for z/VM formatted dasd disk = labelDisk(deviceFile) except parted.error, msg: log.error("parted error: %s" % (msg,)) @@ -920,11 +945,6 @@ class DiskSet: clearDevs = [] initAll = False - if self.pomona.isKickstart: - ks = True - clearDevs = self.pomona.id.ksdata.clearpart.drives - initAll = self.pomona.id.ksdata.clearpart.initAll - if initAll and ((clearDevs is None) or (len(clearDevs) == 0) \ or (drive in clearDevs)) and not flags.test \ and not hasProtectedPartitions(drive, self.pomona): @@ -976,7 +996,7 @@ class DiskSet: "of this disk or use any partitions beyond /dev/%s15 " "in %s") % (drive, drive, name) - rc = intf.messageWindow(_("Warning"), str, + rc = intf.messageWindow(_("Warning"), str, type="custom", custom_buttons = [_("_Reboot"), _("_Continue")], @@ -1014,7 +1034,7 @@ class DiskSet: ptype = None rc.append((device, ptype)) part = disk.next_partition (part) - + return rc def diskState (self): @@ -1060,7 +1080,7 @@ class DiskSet: "of this problem.")) return True return False - + def exceptionDisks(self, pomona, probe=True): if probe: @@ -1151,7 +1171,7 @@ allPartitionTypesDict = { 0xe1: "DOS access", 0xe3: "DOS R/O", 0xeb: "BEOS", - 0xee: "EFI GPT", + 0xee: "EFI GPT", 0xef: "EFI (FAT-12/16/32)", 0xf2: "DOS secondary", 0xfd: "Linux RAID", diff --git a/src/pomona/partitions.py b/src/pomona/partitions.py index afea78b61..6eae9480d 100644 --- a/src/pomona/partitions.py +++ b/src/pomona/partitions.py @@ -54,8 +54,6 @@ def partitionObjectsInitialize(pomona): # shut down all dm devices pomona.id.diskset.closeDevices() pomona.id.diskset.stopMdRaid() - pomona.id.iscsi.shutdown() - pomona.id.zfcp.shutdown() # clean slate about drives isys.flushDriveDict() @@ -63,9 +61,6 @@ def partitionObjectsInitialize(pomona): if pomona.dir == DISPATCH_BACK: return - # ensure zfcp devs are up - pomona.id.zfcp.startup() - # pull in the new iscsi drive isys.flushDriveDict() @@ -217,10 +212,6 @@ class Partitions: self.useAutopartitioning = 1 self.useFdisk = 0 - # autopartitioning info becomes kickstart partition requests - # and its useful to be able to differentiate between the two - self.isKickstart = 0 - if readDisks: self.pomona.id.diskset.refreshDevices() self.setFromDisk(self.pomona.id.diskset) diff --git a/src/pomona/pomona b/src/pomona/pomona index c03628343..6a2647355 100644 --- a/src/pomona/pomona +++ b/src/pomona/pomona @@ -25,6 +25,11 @@ # # ############################################################################### +for i in md_mod dm_mod ext3 xfs reiserfs reiser4; do + echo "Loading module $i" + modprobe $i +done + echo "Running the Pomona Text Installer..." python /usr/lib/pomona/installer.py $@ diff --git a/src/pomona/tui_partition.py b/src/pomona/tui_partition.py index 041a8379a..ab799503d 100644 --- a/src/pomona/tui_partition.py +++ b/src/pomona/tui_partition.py @@ -2,16 +2,23 @@ # partition_text.py: allows the user to choose how to partition their disks # in text mode # -# Jeremy Katz +# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Red Hat, Inc. +# All rights reserved. # -# Copyright 2001-2006 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 software may be freely redistributed under the terms of the GNU -# library public license. +# 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 Library Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author(s): Jeremy Katz # import os, sys @@ -19,11 +26,12 @@ import isys import string import copy import parted -from partitioning import * +import partitions from partedUtils import * from partIntfHelpers import * from partRequests import * from fsset import * +from raid import availRaidLevels from autopart import * from snack import * from constants import * @@ -59,8 +67,65 @@ class PartitionWindow: return i def populate(self): + # XXX we really should separate this stuff out into interface + # independent bits... self.lb.clear() + # first do LVM + lvmrequests = self.partitions.getLVMRequests() + if lvmrequests: + for vgname in lvmrequests.keys(): + vgrequest = self.partitions.getRequestByVolumeGroupName(vgname) + size = vgrequest.getActualSize(self.partitions, self.diskset) + device = "VG %s" % (vgname,) + self.lb.append(["%s" % (device,), + "", "", "%dM" %(size), + "VolGroup", ""], str(vgrequest.uniqueID), + [LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT]) + + for lvrequest in lvmrequests[vgname]: + lvdevice = "LV %s" % (lvrequest.logicalVolumeName,) + if lvrequest.fstype and lvrequest.mountpoint: + mntpt = lvrequest.mountpoint + else: + mntpt = "" + lvsize = lvrequest.getActualSize(self.partitions, self.diskset) + ptype = lvrequest.fstype.getName() + self.lb.append(["%s" %(lvdevice), + "", "", "%dM" %(lvsize), + "%s" %(ptype), "%s" %(mntpt)], str(lvrequest.uniqueID), + [LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT]) + + + # next, add the raid partitions + self.raidminors = {} + raidrequests = self.partitions.getRaidRequests() + if raidrequests: + for request in raidrequests: + if request and request.mountpoint: + mount = request.mountpoint + else: + mount = "" + + if request.fstype: + ptype = request.fstype.getName() + else: + ptype = _("None") + + try: + self.raidminors[request.raidminor] = True + device = "/dev/md%d" % (request.raidminor,) + except: + minor = self._findFirstUnused(self.raidminors) + self.raidminors[minor] = True + device = _("RAID Device %s" %(str(minor))) + + size = request.size + self.lb.append(["%s" %(device), + "", "", "%dM" %(size), + "%s" %(ptype), "%s" %(mount)], str(request.uniqueID), + [LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT]) + # next, add the drives and partitions to the list drives = self.diskset.disks.keys() drives.sort() @@ -74,14 +139,13 @@ class PartitionWindow: part = disk.next_partition() while part: if part.type & parted.PARTITION_METADATA: -# print "partition %s has type %d" %(get_partition_name(part), part.type) +# print("partition %s has type %d" %(get_partition_name(part), part.type)) part = disk.next_partition(part) continue - # ignore the tiny < 1 MB partitions (#119479) if getPartSizeMB(part) <= 1.0: if not part.is_active() or not part.get_flag(parted.PARTITION_BOOT): - part = disk.next_partition(part) + part = disk.next_partition(part) continue device = get_partition_name(part) @@ -95,11 +159,13 @@ class PartitionWindow: ptype = _("Free space") elif part.type & parted.PARTITION_EXTENDED: ptype = _("Extended") + elif part.get_flag(parted.PARTITION_RAID) == 1: + ptype = _("software RAID") elif part.fs_type: if request and request.fstype != None: ptype = request.fstype.getName() - if ptype == "foreign": - ptype = map_foreign_to_fsname(part.native_type) + if ptype == "foreign": + ptype = map_foreign_to_fsname(part.native_type) else: ptype = part.fs_type.name else: @@ -107,8 +173,8 @@ class PartitionWindow: ptype = request.fstype.getName() if ptype == "foreign": ptype = map_foreign_to_fsname(part.native_type) - else: - ptype = _("None") + else: + ptype = _("None") start = (part.geom.start / sectorsPerCyl) + 1 end = (part.geom.end / sectorsPerCyl) + 1 @@ -135,12 +201,14 @@ class PartitionWindow: "%dM" %(size), "%s" %(ptype), ""], part, - [LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT]) - + [LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT]) + else: dev = devify(get_partition_name(part)) # save some space per #90838 - if dev.startswith("/dev/"): + if dev.startswith("/dev/iseries/"): + dev = dev[13:] + elif dev.startswith("/dev/"): dev = dev[5:] self.lb.append(["%s%s" %(indent, dev), "%d" %(start), @@ -148,39 +216,38 @@ class PartitionWindow: "%dM" %(size), "%s" %(ptype), "%s" %(mount)], part, - [LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT]) + [LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT]) part = disk.next_partition(part) def refresh(self): # XXX need some way to stay at the same place in the list after # repopulating - # XXXX - Backup some info which doPartitioning munges if it fails - origInfoDict = {} - for request in self.partitions.requests: - try: - origInfoDict[request.uniqueID] = (request.requestSize, request.currentDrive) - except: - pass + # XXXX - Backup some info which doPartitioning munges if it fails + origInfoDict = {} + for request in self.partitions.requests: + try: + origInfoDict[request.uniqueID] = (request.requestSize, request.currentDrive) + except: + pass try: doPartitioning(self.diskset, self.partitions) rc = 0 except PartitioningError, msg: - try: - for request in self.partitions.requests: - if request.uniqueID in origInfoDict.keys(): - (request.requestSize, request.currentDrive) = origInfoDict[request.uniqueID] - except: - log.error("Failed to restore original info") + try: + for request in self.partitions.requests: + if request.uniqueID in origInfoDict.keys(): + (request.requestSize, request.currentDrive) = origInfoDict[request.uniqueID] + except: + log.error("Failed to restore original info") self.intf.messageWindow(_("Error Partitioning"), - _("Could not allocate requested partitions: %s.") - % (msg)) + _("Could not allocate requested partitions: %s.") % (msg)) rc = -1 except PartitioningWarning, msg: rc = ButtonChoiceWindow(self.screen, _("Warning"), _("Warning: %s") %(msg), - buttons = [ (_("Modify Partition"), "modify"), (_("Add anyway"), "add") ]) + buttons = [ (_("Modify Partition"), "modify"), (_("Add anyway"), "add") ]) if rc == "modify": rc = -1 @@ -189,19 +256,20 @@ class PartitionWindow: reqs = self.partitions.getBootableRequest() if reqs: for req in reqs: - req.ignoreBootConstraints = 1 - + req.ignoreBootConstraints = 1 + self.populate() return rc + def fstypeSet(self, obj): (current, entry) = obj flag = FLAGS_RESET if not current.isMountable(): if entry.value() != _(""): self.oldMount = entry.value() - entry.set(_("")) - flag = FLAGS_SET + entry.set(_("")) + flag = FLAGS_SET elif entry.value() == _(""): if self.oldMount: entry.set(self.oldMount) @@ -230,8 +298,41 @@ class PartitionWindow: mount.set(_("")) return (mount, mountgrid) + # make the entry for the lv name and it's label + def makeLVNameEntry(self, request): + lvnamegrid = Grid(2, 1) + lvnameLbl = Label(_("Logical Volume Name:")) + lvnamegrid.setField(lvnameLbl, 0, 0, (0,0,0,0), anchorLeft = 1) + lvn = request.logicalVolumeName + if lvn: + lvname = Entry(20, lvn) + else: + lvname = Entry(20, "") + lvnamegrid.setField(lvname, 1, 0, anchorRight = 1, growx = 1) + if request.preexist: + lvname.setFlags(FLAG_DISABLED, FLAGS_SET) + return (lvname, lvnamegrid) + + # make the size entry for a logical volume + def makeLVSize(self, request): + grid = Grid(3, 1) + lbl = Label(_("Size (MB):")) + grid.setField(lbl, 0, 0, (0,0,0,0), anchorLeft = 1) + if request.size: + size = Entry(8, "%d" %(request.size,)) + else: + size = Entry(8, "") + grid.setField(size, 1, 0, anchorRight = 1, growx = 1) +# maxLbl = Label(_("(Max is %s MB") %(maxlv,)) +# grid.setField(maxLbl, 2, 0, anchorRight = 1) + if request.preexist: + size.setFlags(FLAG_DISABLED, FLAGS_SET) + return (size, grid) + + # make the list of available filesystems and its label - def makeFsList(self, request, usecallback=1, uselabel=1, usetypes=None, ignorefs = None): + def makeFsList(self, request, usecallback=1, uselabel=1, usetypes=None, + ignorefs = None): subgrid = Grid(1, 2) row = 0 # filesystem type selection @@ -239,7 +340,7 @@ class PartitionWindow: typeLbl = Label(_("File System type:")) subgrid.setField(typeLbl, 0, row) row = row + 1 - + fstype = Listbox(height=2, scroll=1) types = fileSystemTypeGetTypes() if usetypes: @@ -250,12 +351,14 @@ class PartitionWindow: for name in names: if not fileSystemTypeGet(name).isSupported(): continue + if ignorefs and name in ignorefs: continue + if fileSystemTypeGet(name).isFormattable(): fstype.append(name, types[name]) if request.fstype and request.fstype.getName() in names and \ - request.fstype.isFormattable() and request.fstype.isSupported(): + request.fstype.isFormattable() and request.fstype.isSupported(): fstype.setCurrent(request.fstype) else: fstype.setCurrent(fileSystemTypeGetDefault()) @@ -264,6 +367,7 @@ class PartitionWindow: fstype.setCallback(self.fstypeSetCB, (fstype, self.mount)) return (fstype, subgrid) + # make the list of drives def makeDriveList(self, request): subgrid = Grid(1, 2) @@ -299,12 +403,14 @@ class PartitionWindow: sizegrid.setField(size, 1, 0, growx = 1, anchorLeft = 1) return (size, sizegrid) + def sizeOptionsChange(self, (sizeopts, limitentry)): flag = FLAGS_RESET if sizeopts.getSelection() != "limit": flag = FLAGS_SET limitentry.setFlags(FLAG_DISABLED, flag) + def makeSizeOptions(self, request): # size options optiongrid = Grid(2, 3) @@ -335,15 +441,17 @@ class PartitionWindow: self.sizeOptionsChange((sizeopts, limitentry)) return (sizeopts, limitentry, optiongrid) + # the selected cylinder boundary type changed def cylOptionsChange(self, (cylopts, end, size)): if cylopts.getSelection() == "end": end.setFlags(FLAG_DISABLED, FLAGS_RESET) size.setFlags(FLAG_DISABLED, FLAGS_SET) elif cylopts.getSelection() == "size": - end.setFlags(FLAG_DISABLED, FLAGS_SET) + end.setFlags(FLAG_DISABLED, FLAGS_SET) size.setFlags(FLAG_DISABLED, FLAGS_RESET) + # make the list of cylinder stuff def makeCylEntries(self, request): subgrid = Grid(2, 4) @@ -378,9 +486,69 @@ class PartitionWindow: endrb.setCallback(self.cylOptionsChange, (cylopts, end, size)) sizerb.setCallback(self.cylOptionsChange, (cylopts, end, size)) self.cylOptionsChange((cylopts, end, size)) - + return (cylopts, start, end, size, subgrid) + # make the list of VGs + def makeVGList(self, request): + subgrid = Grid(1, 2) + vgLbl = Label(_("Volume Group:")) + subgrid.setField(vgLbl, 0, 0) + vgs = self.partitions.getLVMVGRequests() + if len(vgs) > 3: + scroll = 1 + else: + scroll = 0 + vgBox = Listbox(height=3, scroll=scroll) + current = None + for vg in vgs: + vgBox.append(vg.volumeGroupName, vg) + if vg.uniqueID == request.volumeGroup: + current = vg + if request.volumeGroup is not None: + vgBox.setCurrent(current) + + subgrid.setField(vgBox, 0, 1) + return (vgBox, subgrid) + + # make the list of RAID levels + def makeRaidList(self, request): + subgrid = Grid(1, 2) + raidLbl = Label(_("RAID Level:")) + subgrid.setField(raidLbl, 0, 0) + if len(availRaidLevels) > 3: + scroll = 1 + else: + scroll = 0 + raidBox = Listbox(height=3, scroll=scroll) + for level in availRaidLevels: + raidBox.append(level, level) + if request.raidlevel: + raidBox.setCurrent(request.raidlevel) + subgrid.setField(raidBox, 0, 1) + return (raidBox, subgrid) + + + # make the list of drives for the RAID + def makeRaidDriveList(self, request): + subgrid = Grid(1, 2) + driveLbl = Label(_("RAID Members:")) + subgrid.setField(driveLbl, 0, 0) + disks = self.diskset.disks.keys() + drivelist = CheckboxTree(height=2, scroll=1) + avail = self.partitions.getAvailRaidPartitions(request, self.diskset) + + # XXX + if not request.raidmembers: + for (part, size, used) in avail: + drivelist.append(part, part, 1) + else: + for (part, size, used) in avail: + drivelist.append(part, part, used) + subgrid.setField(drivelist, 0, 1) + return (drivelist, subgrid) + + def makeSpareEntry(self, request): subgrid = Grid(2, 1) label = Label(_("Number of spares?")) @@ -394,57 +562,55 @@ class PartitionWindow: return (entry, subgrid) def fsOptionsGrid(self, origrequest, newfstype): - subgrid = Grid(2, 4) - # filesystem type selection - srow = 0 - typeLbl = Label(_("File System Type:")) - subgrid.setField(typeLbl, 0, srow, (0,0,0,1), anchorLeft = 1) - ptype = origrequest.fstype.getName() - if ptype == "foreign": - part = get_partition_by_name(self.diskset.disks, origrequest.device) + subgrid = Grid(2, 4) + # filesystem type selection + srow = 0 + typeLbl = Label(_("File System Type:")) + subgrid.setField(typeLbl, 0, srow, (0,0,0,1), anchorLeft = 1) + ptype = origrequest.fstype.getName() + if ptype == "foreign": + part = get_partition_by_name(self.diskset.disks, origrequest.device) if part is not None: ptype = map_foreign_to_fsname(part.native_type) else: pytype = _("Foreign") - type = Label(ptype) - subgrid.setField(type, 1, srow, (0,0,0,1), anchorRight = 1) - srow = srow +1 - if origrequest.type != REQUEST_NEW and origrequest.fslabel: - fsLbl = Label(_("File System Label:")) - subgrid.setField(fsLbl, 0, srow, (0,0,0,1), anchorLeft = 1) - label = Label(origrequest.fslabel) - subgrid.setField(label, 1, srow, (0,0,0,1), anchorRight = 1) - srow = srow + 1 - - sizeLbl = Label(_("Size (MB):")) - subgrid.setField(sizeLbl, 0, srow, (0,0,0,1), anchorLeft = 1) - size = Label("%s" %(int(origrequest.size))) - subgrid.setField(size, 1, srow, (0,0,0,1), anchorRight = 1) - srow = srow + 1 - tmpLbl = Label(_("File System Option:")) - subgrid.setField(tmpLbl, 0, srow, (0,0,0,1), anchorLeft = 1) - if origrequest.format: - fsoptLbl = Label(_("Format as %s") % (newfstype.getName())) - elif origrequest.migrate: - fsoptLbl = Label(_("Migrate to %s") %(newfstype.getName())) - else: - fsoptLbl = Label(_("Leave unchanged")) - subgrid.setField(fsoptLbl, 1, srow, (0,0,0,1), anchorLeft = 1) - - return (subgrid, fsoptLbl, type) - - def fsOptionsDialog(self, origrequest, format, migrate, newfstype, badblocks, - showbadblocks=0): - - def formatChanged((formatrb, badblocksCB)): + type = Label(ptype) + subgrid.setField(type, 1, srow, (0,0,0,1), anchorRight = 1) + srow = srow +1 + if origrequest.type != REQUEST_NEW and origrequest.fslabel: + fsLbl = Label(_("File System Label:")) + subgrid.setField(fsLbl, 0, srow, (0,0,0,1), anchorLeft = 1) + label = Label(origrequest.fslabel) + subgrid.setField(label, 1, srow, (0,0,0,1), anchorRight = 1) + srow = srow + 1 + + sizeLbl = Label(_("Size (MB):")) + subgrid.setField(sizeLbl, 0, srow, (0,0,0,1), anchorLeft = 1) + size = Label("%s" %(int(origrequest.size))) + subgrid.setField(size, 1, srow, (0,0,0,1), anchorRight = 1) + srow = srow + 1 + tmpLbl = Label(_("File System Option:")) + subgrid.setField(tmpLbl, 0, srow, (0,0,0,1), anchorLeft = 1) + if origrequest.format: + fsoptLbl = Label(_("Format as %s") % (newfstype.getName())) + elif origrequest.migrate: + fsoptLbl = Label(_("Migrate to %s") %(newfstype.getName())) + else: + fsoptLbl = Label(_("Leave unchanged")) + subgrid.setField(fsoptLbl, 1, srow, (0,0,0,1), anchorLeft = 1) + + return (subgrid, fsoptLbl, type) + + + def fsOptionsDialog(self, origrequest, format, migrate, newfstype): + + def formatChanged((formatrb)): flag = FLAGS_SET if formatrb.selected(): flag = FLAGS_RESET - if badblocksCB: - badblocksCB.setFlags(FLAG_DISABLED, flag) poplevel = GridFormHelp(self.screen, _("File System Options"), - "fsoption", 1, 6) + "fsoption", 1, 6) row = 0 poplevel.add(TextboxReflowed(40, _("Please choose how you would " "like to prepare the file system " @@ -453,30 +619,26 @@ class PartitionWindow: subgrid = Grid(2, 5) srow = 0 - if showbadblocks: - badblocksCB = Checkbox(_("Check for bad blocks")) - else: - badblocksCB = None - noformatrb = SingleRadioButton(_("Leave unchanged (preserve data)"), None, not format and not migrate) subgrid.setField(noformatrb, 0, srow, (0,0,0,1),anchorLeft = 1) - + srow = srow + 1 if format: forflag = 1 else: forflag = 0 formatrb = SingleRadioButton(_("Format as:"), noformatrb, forflag) - formatrb.setCallback(formatChanged, (formatrb, badblocksCB)) - noformatrb.setCallback(formatChanged, (formatrb, badblocksCB)) - + formatrb.setCallback(formatChanged, (formatrb)) + noformatrb.setCallback(formatChanged, (formatrb)) + subgrid.setField(formatrb, 0, srow, (0,0,0,1), anchorLeft = 1) - (fortype, forgrid) = self.makeFsList(origrequest, usecallback = 0, uselabel = 0) + (fortype, forgrid) = self.makeFsList(origrequest, usecallback = 0, + uselabel = 0) if newfstype and newfstype.isFormattable() and \ - newfstype.getName() in fileSystemTypeGetTypes().keys() and \ - newfstype.isSupported(): + newfstype.getName() in fileSystemTypeGetTypes().keys() and \ + newfstype.isSupported(): fortype.setCurrent(newfstype) subgrid.setField(forgrid, 1, srow, (0,0,0,1)) @@ -487,17 +649,18 @@ class PartitionWindow: else: migflag = 0 migraterb = SingleRadioButton(_("Migrate to:"), formatrb, migflag) - migraterb.setCallback(formatChanged, (formatrb, badblocksCB)) + migraterb.setCallback(formatChanged, (formatrb)) subgrid.setField(migraterb, 0, srow, (0,0,0,1), anchorLeft = 1) migtypes = origrequest.origfstype.getMigratableFSTargets() (migtype, miggrid) = self.makeFsList(origrequest, usecallback = 0, - uselabel = 0, usetypes = migtypes) + uselabel = 0, + usetypes = migtypes) if newfstype and newfstype.getName() in migtypes: migtype.setCurrent(newfstype) - subgrid.setField(miggrid, 1, srow, (0,0,0,1)) + subgrid.setField(miggrid, 1, srow, (0,0,0,1)) else: migraterb = None @@ -505,23 +668,17 @@ class PartitionWindow: row = row + 1 - if badblocksCB: - poplevel.add(badblocksCB, 0, row, (0,1,0,1)) - if badblocks: - badblocksCB.setValue("*") - row = row + 1 - - formatChanged((formatrb, badblocksCB)) + formatChanged((formatrb)) popbb = ButtonBar(self.screen, (TEXT_OK_BUTTON, TEXT_CANCEL_BUTTON)) - poplevel.add(popbb, 0, row, (0,0,0,0), growx = 1) + poplevel.add(popbb, 0, row, (0,0,0,0), growx = 1) while 1: res = poplevel.run() if popbb.buttonPressed(res) == 'cancel': self.screen.popWindow() - return (format, migrate, newfstype, badblocks) + return (format, migrate, newfstype) if noformatrb.selected(): format = 0 @@ -538,12 +695,7 @@ class PartitionWindow: self.screen.popWindow() - if badblocksCB: - badblockstate = badblocksCB.selected() - else: - badblockstate = 0 - - return (format, migrate, newfstype, badblockstate) + return (format, migrate, newfstype) def shutdownUI(self): # XXX remove parted object refs @@ -555,7 +707,7 @@ class PartitionWindow: # isNew implies that this request has never been successfully used before def editPartitionRequest(self, origrequest, isNew = 0): self.oldMount = None - + poplevel = GridFormHelp(self.screen,_("Add Partition"),"addpart", 1, 6) # mount point entry @@ -594,7 +746,6 @@ class PartitionWindow: (cylopts, start, end, size, cylgrid) = self.makeCylEntries(origrequest) poplevel.add(cylgrid, 0, row, (0,1,0,0)) - # primary # XXX need to see if cylinder range is in extended or not row = row + 1 @@ -604,33 +755,36 @@ class PartitionWindow: poplevel.add(primary, 0, row, (0,1,0,0)) row = row + 1 - badblocksCB = Checkbox(_("Check for bad blocks")) - poplevel.add(badblocksCB, 0, row) - if origrequest.badblocks: - badblocksCB.setValue("*") - fsoptLbl = None - elif origrequest.type == REQUEST_PREEXIST and origrequest.fstype: + elif origrequest.type == REQUEST_VG: + self.intf.messageWindow(_("Not Supported"), + _("You can only edit LVM Volume Groups " + "in the graphical installer.")) + return + + elif (origrequest.type == REQUEST_LV or origrequest.type == REQUEST_PREEXIST) and origrequest.fstype: + # set some defaults format = origrequest.format migrate = origrequest.migrate newfstype = origrequest.fstype - badblocks = origrequest.badblocks (subgrid, fsoptLbl, fstypeLbl) = self.fsOptionsGrid(origrequest, newfstype) poplevel.add(subgrid, 0, row, (0,1,0,0)) + row = row + 1 if origrequest.type == REQUEST_NEW or origrequest.getProtected(): popbb = ButtonBar(self.screen, (TEXT_OK_BUTTON, TEXT_CANCEL_BUTTON)) else: popbb = ButtonBar(self.screen, (TEXT_OK_BUTTON, - (_("File System Options"), "fsopts"), - TEXT_CANCEL_BUTTON)) + (_("File System Options"), "fsopts"), + TEXT_CANCEL_BUTTON)) poplevel.add(popbb, 0, row, (0,1,0,0), growx = 1) while 1: + res = poplevel.run() # if the user hit cancel, do nothing @@ -639,11 +793,7 @@ class PartitionWindow: return if popbb.buttonPressed(res) == 'fsopts': - # we do not show the badblock option any longer as it is - # not supported. - showbad = 0 - (format, migrate, newfstype, badblocks) = self.fsOptionsDialog(origrequest, - format, migrate, newfstype, badblocks, showbadblocks = showbad) + (format, migrate, newfstype) = self.fsOptionsDialog(origrequest, format, migrate, newfstype) self.fstypeSet((newfstype, self.mount)) fstypeLbl.setText(newfstype.getName()) @@ -651,7 +801,7 @@ class PartitionWindow: if format: fsoptLbl.setText(_("Format as %s") % (newfstype.getName())) elif migrate: - fsoptLbl.setText(_("Migrate to %s") % (newfstype.getName())) + fsoptLbl.setText(_("Migrate to %s") %(newfstype.getName())) else: fsoptLbl.setText(_("Leave unchanged")) @@ -661,7 +811,7 @@ class PartitionWindow: filesystem = fstype.current() if primary.selected(): - primonly = 1 + primonly = True else: primonly = None @@ -671,14 +821,9 @@ class PartitionWindow: request.mountpoint = self.mount.value() else: request.mountpoint = None - request.format = 1 + request.format = True request.primary = primonly - if badblocksCB is not None: - request.badblocks = badblocksCB.selected() - else: - request.badblocks = 0 - if origrequest.start == None: if invalidInteger(size.value()): self.intf.messageWindow(_("Invalid Entry for Partition Size"), @@ -690,17 +835,16 @@ class PartitionWindow: if growtype == "fixed": grow = None else: - grow = 1 + grow = True if growtype == "limit": if invalidInteger(limitentry.value()): self.intf.messageWindow(_("Invalid Entry for Maximum Size"), - invalidInteger(limitentry.value())) + invalidInteger(limitentry.value())) continue - + maxsize = int(limitentry.value()) else: maxsize = None - request.grow = grow request.maxSizeMB = maxsize @@ -709,68 +853,64 @@ class PartitionWindow: else: allowdrives = [] for i in self.drivelist.getSelection(): - allowdrives.append(i) + allowdrives.append(i) request.drive = allowdrives else: if invalidInteger(start.value()): self.intf.messageWindow(_("Invalid Entry for Starting Cylinder"), - invalidInteger(start.value())) + invalidInteger(start.value())) continue request.start = int(start.value()) - if badblocksCB is not None: - request.badblocks = badblocksCB.selected() - else: - request.badblocks = 0 - cyltype = cylopts.getSelection() if cyltype == "end": if invalidInteger(end.value()): self.intf.messageWindow(_("Invalid Entry for End Cylinder"), - invalidInteger(end.value())) + invalidInteger(end.value())) continue - + request.end = int(end.value()) request.size = None elif cyltype == "size": if invalidInteger(size.value()): self.intf.messageWindow(_("Invalid Entry for Partition Size"), - invalidInteger(size.value())) + invalidInteger(size.value())) continue request.end = None request.size = int(size.value()) else: # can't ever get here raise RuntimeError, "Selected a way of partitioning by cylinder that's not supported" - + err = request.sanityCheckRequest(self.partitions) if err: - self.intf.messageWindow(_("Error With Request"), "%s" % (err)) + self.intf.messageWindow(_("Error With Request"), + "%s" % (err)) continue else: request = copy.copy(origrequest) - if request.type == REQUEST_PREEXIST: + if request.type == REQUEST_PREEXIST or request.type == REQUEST_LV: request.fstype = newfstype - + if request.fstype.isMountable(): request.mountpoint = self.mount.value() else: request.mountpoint = None - if request.type == REQUEST_PREEXIST: + if request.type == REQUEST_PREEXIST or request.type == REQUEST_LV: request.format = format request.migrate = migrate request.fstype = newfstype - request.badblocks = badblocks err = request.sanityCheckRequest(self.partitions) if err: - self.intf.messageWindow(_("Error With Request"), "%s" % (err)) + self.intf.messageWindow(_("Error With Request"), + "%s" % (err)) continue if (not request.format and request.mountpoint - and request.formatByDefault()): + and request.formatByDefault()): if not queryNoFormatPreExisting(self.intf): continue @@ -794,15 +934,449 @@ class PartitionWindow: self.shutdownUI() self.screen.popWindow() + # isNew implies that this request has never been successfully used before + def editRaidRequest(self, raidrequest, isNew = 0): + preexist = raidrequest and raidrequest.preexist + if preexist: + tmpstr = _("Edit RAID Device") + else: + tmpstr = _("Make RAID Device") + poplevel = GridFormHelp(self.screen, tmpstr, "makeraid", 1, 6) + + # mount point entry + row = 0 + (self.mount, mountgrid) = self.makeMountEntry(raidrequest) + poplevel.add(mountgrid, 0, row) + row = row + 1 + + # initialize holder for temporary mount point value + self.oldMount = None + + if preexist: + # set some defaults + format = raidrequest.format + migrate = raidrequest.migrate + newfstype = raidrequest.fstype + + (subgrid, fsoptLbl, fstypeLbl) = self.fsOptionsGrid(raidrequest, newfstype) + poplevel.add(subgrid, 0, row, (0,1,0,0)) + self.drivelist = None + else: + subgrid = Grid(2, 1) + (fstype, fsgrid) = self.makeFsList(raidrequest, ignorefs = ["software RAID", "PPC PReP Boot", "Apple Bootstrap"]) + subgrid.setField(fsgrid, 0, 0, anchorLeft = 1, anchorTop=1) + (raidtype, raidgrid) = self.makeRaidList(raidrequest) + subgrid.setField(raidgrid, 1, 0, (2,0,0,0), anchorRight=1, anchorTop=1) + poplevel.add(subgrid, 0, row, (0,1,0,0)) + + row = row + 1 + drivegrid = Grid(2, 1) + + #Let's see if we have any RAID partitions to make a RAID device with + avail = self.partitions.getAvailRaidPartitions(raidrequest, self.diskset) + + #If we don't, then tell the user that none exist + if len(avail) < 2: + ButtonChoiceWindow (self.screen, _("No RAID partitions"), + _("At least two software RAID partitions are needed."), + [ TEXT_OK_BUTTON ]) + return + + (self.drivelist, drivesubgrid) = self.makeRaidDriveList(raidrequest) + drivegrid.setField(drivesubgrid, 0, 0, (0,0,4,0), anchorLeft = 1, anchorTop = 1) + + miscgrid = Grid(1, 2) + (spares, sparegrid) = self.makeSpareEntry(raidrequest) + miscgrid.setField(sparegrid, 0, 0, anchorRight=1, anchorTop=1) + + if raidrequest.fstype and raidrequest.fstype.isFormattable(): + format = Checkbox(_("Format partition?")) + miscgrid.setField(format, 0, 1) + else: + format = None + + if raidrequest.format == 1 or raidrequest.format == None: + format.setValue("*") + + drivegrid.setField(miscgrid, 1, 0, anchorTop=1) + poplevel.add(drivegrid, 0, row, (0,1,0,0)) + + row = row + 1 + if preexist: + popbb = ButtonBar(self.screen, (TEXT_OK_BUTTON, + (_("File System Options"), "fsopts"), + TEXT_CANCEL_BUTTON)) + else: + popbb = ButtonBar(self.screen, (TEXT_OK_BUTTON,TEXT_CANCEL_BUTTON)) + poplevel.add(popbb, 0, row, (0,1,0,0), growx = 1) + + while 1: + res = poplevel.run() + + if popbb.buttonPressed(res) == 'cancel': + self.screen.popWindow() + return + + if popbb.buttonPressed(res) == 'fsopts': + (format, migrate, newfstype) = self.fsOptionsDialog(raidrequest, format, migrate, newfstype) + self.fstypeSet((newfstype, self.mount)) + fstypeLbl.setText(newfstype.getName()) + + if fsoptLbl: + if format: + fsoptLbl.setText(_("Format as %s") % (newfstype.getName())) + elif migrate: + fsoptLbl.setText(_("Migrate to %s") %(newfstype.getName())) + else: + fsoptLbl.setText(_("Leave unchanged")) + + continue + + request = copy.copy(raidrequest) + + if not preexist: + request.fstype = fstype.current() + else: + request.fstype = newfstype + + if request.fstype.isMountable(): + request.mountpoint = self.mount.value() + else: + request.mountpoint = None + + if not preexist: + raidmembers = [] + for drive in self.drivelist.getSelection(): + id = self.partitions.getRequestByDeviceName(drive).uniqueID + raidmembers.append(id) + + request.raidmembers = raidmembers + if invalidInteger(spares.value()): + self.intf.messageWindow(_("Invalid Entry for RAID Spares"), + invalidInteger(spares.value())) + continue + + request.raidspares = int(spares.value()) + request.raidlevel = raidtype.current() + request.raidminor = self._findFirstUnused(self.raidminors) + self.raidminors[request.raidminor] = True + + if format: + request.format = format.selected() + else: + request.format = 0 + + if request.raidlevel == "RAID0" and request.raidspares > 0: + self.intf.messageWindow(_("Too many spares"), + _("You may not use any spares " + "with a RAID0 array.")) + continue + else: + request.format = format + request.migrate = migrate + request.fstype = newfstype + + err = request.sanityCheckRequest(self.partitions) + if err: + self.intf.messageWindow(_("Error With Request"), + "%s" % (err)) + continue + + if not isNew: + self.partitions.removeRequest(raidrequest) + + self.partitions.addRequest(request) + + if self.refresh(): + # how can this fail? well, if it does, do the remove new, + # add old back in dance + self.partitions.removeRequest(request) + if not isNew: + self.partitions.addRequest(raidrequest) + if self.refresh(): + raise RuntimeError, "Returning partitions to state prior to RAID edit failed" + else: + break + + break + + # clean up + self.shutdownUI() + self.screen.popWindow() + + # isNew implies that this request has never been successfully used before + def editLVRequest(self, lvrequest, isNew = 0): + preexist = lvrequest and lvrequest.preexist + if preexist: + tmpstr = _("Edit Logical Volume") + else: + tmpstr = _("Make Logical Volume") + self.drivelist = None + poplevel = GridFormHelp(self.screen, tmpstr, "makelv", 1, 8) + + # mount point entry + row = 0 + (self.mount, mountgrid) = self.makeMountEntry(lvrequest) + poplevel.add(mountgrid, 0, row) + row = row + 1 + + (self.lvname, lvgrid) = self.makeLVNameEntry(lvrequest) + poplevel.add(lvgrid, 0, row) + row = row + 1 + + (lvsize, lvsizegrid) = self.makeLVSize(lvrequest) + poplevel.add(lvsizegrid, 0, row) + row = row + 1 + + # initialize holder for temporary mount point value + self.oldMount = None + + if preexist: + # set some defaults + format = lvrequest.format + migrate = lvrequest.migrate + newfstype = lvrequest.fstype + + (subgrid, fsoptLbl, fstypeLbl) = self.fsOptionsGrid(lvrequest, newfstype) + poplevel.add(subgrid, 0, row, (0,1,0,0)) + self.drivelist = None + else: + subgrid = Grid(2, 1) + (fstype, fsgrid) = self.makeFsList(lvrequest, ignorefs = ["software RAID", "efi", "PPC PReP Boot", "Apple Bootstrap"]) + subgrid.setField(fsgrid, 0, 0, anchorLeft = 1, anchorTop=1) + + vgs = self.partitions.getLVMVGRequests() + if len(vgs) < 1: + ButtonChoiceWindow (self.screen, _("No Volume Groups"), + _("No volume groups exist in which to create " + "a logical volume"),[ TEXT_OK_BUTTON ]) + return + + (vgtype, vggrid) = self.makeVGList(lvrequest) + subgrid.setField(vggrid, 1, 0, (2,0,0,0), anchorRight=1, anchorTop=1) + poplevel.add(subgrid, 0, row, (0,1,0,0)) + + row = row + 1 + + miscgrid = Grid(1, 3) + + if lvrequest.fstype and lvrequest.fstype.isFormattable(): + format = Checkbox(_("Format partition?")) + miscgrid.setField(format, 0, 1) + else: + format = None + + if lvrequest.format == 1 or lvrequest.format == None: + format.setValue("*") + + poplevel.add(miscgrid, 0, row, (0,1,0,0)) + + row = row + 1 + if preexist: + popbb = ButtonBar(self.screen, (TEXT_OK_BUTTON, + (_("File System Options"), "fsopts"), + TEXT_CANCEL_BUTTON)) + else: + popbb = ButtonBar(self.screen, (TEXT_OK_BUTTON,TEXT_CANCEL_BUTTON)) + poplevel.add(popbb, 0, row, (0,1,0,0), growx = 1) + + while 1: + res = poplevel.run() + + if popbb.buttonPressed(res) == 'cancel': + self.screen.popWindow() + return + + if popbb.buttonPressed(res) == 'fsopts': + (format, migrate, newfstype) = self.fsOptionsDialog(lvrequest, format, migrate, newfstype) + self.fstypeSet((newfstype, self.mount)) + fstypeLbl.setText(newfstype.getName()) + + if fsoptLbl: + if format: + fsoptLbl.setText(_("Format as %s") % (newfstype.getName())) + elif migrate: + fsoptLbl.setText(_("Migrate to %s") %(newfstype.getName())) + else: + fsoptLbl.setText(_("Leave unchanged")) + + continue + + request = copy.copy(lvrequest) + + if not preexist: + request.fstype = fstype.current() + else: + request.fstype = newfstype + + if request.fstype.isMountable(): + request.mountpoint = self.mount.value() + else: + request.mountpoint = None + + if not preexist: + if format: + request.format = format.selected() + else: + request.format = 0 + + # set the vg + vgreq = vgtype.current() + request.volumeGroup = vgreq.uniqueID + + if vgreq is None: + raise RuntimeError, "Somehow ended up with a None volume group!" + + # get the lv name, check for a "valid" name + lvn = self.lvname.value().strip() + err = sanityCheckLogicalVolumeName(lvn) + if err: + self.intf.messageWindow(_("Illegal Logical Volume Name"), err, custom_icon="error") + + continue + + # make sure we don't have an LV in this volume group by + # this name already + used = 0 + origlvname = lvrequest.logicalVolumeName + for lv in self.partitions.getLVMLVRequests(): + if origlvname and lvn == origlvname: + break + if ((lv.logicalVolumeName == lvn) and + (lv.volumeGroup == vgreq.uniqueID)): + used = 1 + break + + if used: + self.intf.messageWindow(_("Illegal logical volume name"), + _("The logical volume name \"%s\" " + "is already in use. Please " + "pick another.") % (lvn,), + custom_icon="error") + continue + + request.logicalVolumeName = lvn + + try: + size = int(lvsize.value().strip()) + except: + self.intf.messageWindow(_("Illegal size"), + _("The requested size as entered is " + "not a valid number greater " + "than 0."), custom_icon="error") + continue + + pesize = vgreq.pesize + size = lvm.clampLVSizeRequest(size, pesize, roundup=1) + + maxlv = lvm.getMaxLVSize(pesize) + if size > lvm.getMaxLVSize(pesize): + self.intf.messageWindow(_("Not enough space"), + _("The current requested size " + "(%10.2f MB) is larger than the " + "maximum logical volume " + "size (%10.2f MB). ") % (size, + maxlv), + custom_icon="error") + continue + + vgsize = vgreq.getActualSize(self.partitions, self.diskset) + avail = vgsize + for req in self.partitions.requests: + if ((req.type == REQUEST_LV) and + (req.volumeGroup == vgreq.uniqueID)): + avail -= req.size + if lvrequest.size: + avail += lvrequest.size + + if size > avail: + self.intf.messageWindow(_("Not enough space"), + _("The current requested size " + "(%10.2f MB) is larger than " + "the available size in " + "the volume group " + "(%10.2f MB).") %(size, avail), + custom_icon="error") + continue + + request.size = size + request.grow = 0 + request.dev = None + else: + request.format = format + request.migrate = migrate + request.fstype = newfstype + + err = request.sanityCheckRequest(self.partitions) + if err: + self.intf.messageWindow(_("Error With Request"), + "%s" % (err)) + continue + + if not isNew: + self.partitions.removeRequest(lvrequest) + + self.partitions.addRequest(request) + + if self.refresh(): + # how can this fail? well, if it does, do the remove new, + # add old back in dance + self.partitions.removeRequest(request) + if not isNew: + self.partitions.addRequest(lvrequest) + if self.refresh(): + raise RuntimeError, "Returning partitions to state prior to RAID edit failed" + else: + break + + break + + # clean up + self.shutdownUI() + self.screen.popWindow() + def newCb(self): - request = NewPartitionSpec(fileSystemTypeGetDefault(), 1) - self.editPartitionRequest(request, isNew = 1) + hasvg = 1 + dolv = 0 + for request in self.partitions.requests: + if request.type == REQUEST_VG: + hasvg = 1 + break + if hasvg: + rc = ListboxChoiceWindow(self.screen, + _("New Partition or Logical Volume?"), + _("Would you like to create a new " + "partition or a new logical volume?"), + [ _("partition"), _("logical volume") ], + [ TEXT_OK_BUTTON, TEXT_CANCEL_BUTTON ], + width = 30, scroll = 0, height = 2) + (button, choice) = rc + if button == TEXT_CANCEL_CHECK: + return + if choice == 1: + dolv = 1 + + if not dolv: + request = NewPartitionSpec(fileSystemTypeGetDefault(), 1) + self.editPartitionRequest(request, isNew = 1) + else: + request = LogicalVolumeRequestSpec(fileSystemTypeGetDefault(), + size=1) + self.editLVRequest(request, isNew = 1) + + def makeraidCb(self): + request = RaidRequestSpec(fileSystemTypeGetDefault()) + self.editRaidRequest(request, isNew = 1) def editCb(self): part = self.lb.current() (type, request) = doEditPartitionByRequest(self.intf, self.partitions, part) if request: - if type == "NEW": + if type == "RAID": + self.editRaidRequest(request) + elif type == "LVMLV": + self.editLVRequest(request) + elif type == "NEW": self.editPartitionRequest(request, isNew = 1) else: self.editPartitionRequest(request) @@ -810,15 +1384,19 @@ class PartitionWindow: def deleteCb(self): partition = self.lb.current() + req = self.partitions.getRequestByID(partition) + if req and isinstance(req, RaidRequestSpec): + del(self.raidminors[req.raidminor]) + if doDeletePartitionByRequest(self.intf, self.partitions, partition): self.refresh() def resetCb(self): if not confirmResetPartitionState(self.intf): return - + self.diskset.refreshDevices() - self.partitions.setFromDisk(self.diskset) + self.partitions.setFromDisk(self.diskset) self.populate() def shutdownMainUI(self): @@ -848,8 +1426,9 @@ class PartitionWindow: self.bb = ButtonBar (screen, ((_("New"), "new", "F2"), (_("Edit"), "edit", "F3"), (_("Delete"), "delete", "F4"), + (_("RAID"), "raid", "F11"), TEXT_OK_BUTTON, TEXT_BACK_BUTTON)) - + screen.pushHelpLine( _(" F1-Help F2-New F3-Edit F4-Delete F5-Reset F12-OK ")) self.g.add(self.bb, 0, 2, (0, 1, 0, 0)) @@ -859,13 +1438,15 @@ class PartitionWindow: while 1: rc = self.g.run() res = self.bb.buttonPressed(rc) - + if res == "new": self.newCb() elif res == "edit" or rc == self.lb.listbox: # XXX better way? self.editCb() elif res == "delete": self.deleteCb() + elif res == "raid": + self.makeraidCb() elif res == "reset" or rc == "F5": self.resetCb() elif res == TEXT_BACK_CHECK: @@ -874,26 +1455,27 @@ class PartitionWindow: self.diskset.refreshDevices() self.partitions.setFromDisk(self.diskset) - + screen.popHelpLine() screen.popWindow() return INSTALL_BACK else: if not self.partitions.getRequestByMountPoint("/"): self.intf.messageWindow(_("No Root Partition"), - _("Installation requires a / partition.")) + _("Installation requires a / partition.")) continue - + (errors, warnings) = self.partitions.sanityCheckAllRequests(self.diskset) rc = partitionSanityErrors(self.intf, errors) if rc != 1: continue - + rc = partitionSanityWarnings(self.intf, warnings) if rc != 1: continue - warnings = getPreExistFormatWarnings(self.partitions, self.diskset) + warnings = getPreExistFormatWarnings(self.partitions, + self.diskset) rc = partitionPreExistFormatWarnings(self.intf, warnings) if rc != 1: continue @@ -902,12 +1484,14 @@ class PartitionWindow: self.shutdownMainUI() screen.popHelpLine() - screen.popWindow() + screen.popWindow() return INSTALL_OK - + class PartitionTypeWindow: def typeboxChange(self, (typebox, drivelist)): flag = FLAGS_RESET + if typebox.current() == CLEARPART_TYPE_NONE: + flag = FLAGS_SET # XXX need a way to disable the checkbox tree def clearDrivelist(self): @@ -922,11 +1506,7 @@ class PartitionTypeWindow: while 1: g = GridFormHelp(screen, _("Partitioning Type"), "autopart", 1, 6) - txt = TextboxReflowed(65, _("Installation requires partitioning " - "of your hard drive. The default " - "layout is reasonable for most " - "users. You can either choose " - "to use this or create your own.")) + txt = TextboxReflowed(65, _("Installation requires partitioning of your hard drive. The default layout is suitable for most users. Select what space to use and which drives to use as the install target. You can also choose to create your own custom layout.")) g.add(txt, 0, 0, (0, 0, 0, 0)) opts = ((_("Remove all partitions on selected drives and create default layout"), CLEARPART_TYPE_ALL), @@ -938,14 +1518,15 @@ class PartitionTypeWindow: if pomona.dispatch.stepInSkipList("autopartitionexecute"): typebox.setCurrent(-1) else: - typebox.setCurrent(pomona.id.partitions.autoClearPartType) + typebox.setCurrent(pomona.id.partitions.autoClearPartType) g.add(typebox, 0, 1, (0, 1, 0, 0)) # list of drives to select which to clear subgrid = Grid(1, 2) subgrid.setField(TextboxReflowed(55, _("Which drive(s) do you want to " - "use for this installation?")), 0, 0) + "use for this installation?")), + 0, 0) drivelist = CheckboxTree(height=2, scroll=1) subgrid.setField(drivelist, 0, 1) g.add(subgrid, 0, 2, (0, 1, 0, 0)) @@ -953,11 +1534,11 @@ class PartitionTypeWindow: bb = ButtonBar(screen, [ TEXT_OK_BUTTON, TEXT_BACK_BUTTON ]) g.add(bb, 0, 5, (0,1,0,0)) - typebox.setCallback(self.typeboxChange, (typebox, drivelist)) + + typebox.setCallback(self.typeboxChange, (typebox, drivelist)) self.drivelist = drivelist - g.addHotKey("F2") - screen.pushHelpLine (_(",<+>,<-> selection | Add drive | next screen")) + screen.pushHelpLine (_(",<+>,<-> selection | next screen")) # restore the drive list each time disks = pomona.id.diskset.disks.keys() @@ -984,21 +1565,16 @@ class PartitionTypeWindow: rc = g.run() if len(self.drivelist.getSelection()) > 0: - sel = [self.drivelist.getSelection()[0].split()[0]] + sel = map(lambda s: s.split()[0], self.drivelist.getSelection()) else: sel = [] partmethod_ans = typebox.current() res = bb.buttonPressed(rc) - + self.clearDrivelist() screen.popHelpLine() screen.popWindow() - - if rc == "F2": - if self.addDriveDialog(screen) != INSTALL_BACK: - partitionObjectsInitialize(pomona) - continue - + if res == TEXT_BACK_CHECK: return INSTALL_BACK @@ -1017,21 +1593,18 @@ class PartitionTypeWindow: pomona.id.partitions.autoClearPartType = partmethod_ans pomona.id.partitions.autoClearPartDrives = sel + break # ask to review autopartition layout - but only if it's not custom partitioning pomona.dispatch.skipStep("partition", skip = 0) - pomona.dispatch.skipStep("bootloader", skip = 0) + pomona.dispatch.skipStep("bootloader", skip = 1) - if partmethod_ans != -1: + if partmethod_ans != -1: reviewLayout = pomona.intf.messageWindow(_("Review Partition Layout"), - _("Review and modify partitioning layout?"), - type = "yesno") + _("Review and modify partitioning layout?"), + type = "yesno") if reviewLayout != 1: pomona.dispatch.skipStep("partition", skip = 1) - pomona.dispatch.skipStep("bootloader", skip = 1) return INSTALL_OK - - def addDriveDialog(self, screen): - return INSTALL_BACK