diff options
author | Chris Lumens <clumens@redhat.com> | 2009-03-27 15:25:48 -0400 |
---|---|---|
committer | Chris Lumens <clumens@redhat.com> | 2009-03-27 15:25:48 -0400 |
commit | 054b3ad62b8c88c86dfc873991bdc4416ec3f215 (patch) | |
tree | 32f83d5c241c402becfc4c26bdca10717a044f99 | |
parent | 1c66c7d2f0ad20d0a3cbf9d435d2b6c5ca082636 (diff) | |
parent | b74a30461c62520e08b5972607257149901ef473 (diff) | |
download | anaconda-054b3ad62b8c88c86dfc873991bdc4416ec3f215.tar.gz anaconda-054b3ad62b8c88c86dfc873991bdc4416ec3f215.tar.xz anaconda-054b3ad62b8c88c86dfc873991bdc4416ec3f215.zip |
Merge commit 'origin/anaconda-storage-branch'
120 files changed, 18534 insertions, 12476 deletions
diff --git a/70-anaconda.rules b/70-anaconda.rules new file mode 100644 index 000000000..374b63a3d --- /dev/null +++ b/70-anaconda.rules @@ -0,0 +1,57 @@ +ACTION!="add|change", GOTO="anaconda_end" +SUBSYSTEM!="block", GOTO="anaconda_end" + +ENV{ANACBIN}="/sbin" +TEST!="$env{ANACBIN}/dmsetup", ENV{ANACBIN}="/usr/sbin" + +KERNEL!="dm-*", GOTO="anaconda_mdraid" + +IMPORT{program}="$env{ANACBIN}/dmsetup info -c --nameprefixes --unquoted --rows --noheadings -o name,uuid,suspended,readonly,major,minor,open,tables_loaded -j%M -m%m" +ENV{DM_NAME}!="?*", GOTO="anaconda_end" + +SYMLINK+="disk/by-id/dm-name-$env{DM_NAME}" +ENV{DM_UUID}=="?*", SYMLINK+="disk/by-id/dm-uuid-$env{DM_UUID}" + +ENV{DM_STATE}=="SUSPENDED", GOTO="anaconda_end" + +IMPORT{program}="vol_id --export $tempnode" +OPTIONS="link_priority=-100" +ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_UUID_ENC}=="?*", SYMLINK+="disk/by-uuid/$env{ID_FS_UUID_ENC}" + +LABEL="anaconda_mdraid" +KERNEL!="md*", GOTO="anaconda_mdraid_member" + +# container devices have a metadata version of e.g. 'external:ddf' and +# never leave state 'inactive' +ATTR{md/metadata_version}=="external:[A-Za-z]*", ATTR{md/array_state}=="inactive", GOTO="md_ignore_state" +TEST!="md/array_state", GOTO="anaconda_mdraid_member" +ATTR{md/array_state}=="|clear|inactive", GOTO="anaconda_mdraid_member" +LABEL="md_ignore_state" + +IMPORT{program}="$env{ANACBIN}/mdadm --detail --export $tempnode" +ENV{DEVTYPE}=="disk", ENV{MD_NAME}=="?*", SYMLINK+="disk/by-id/md-name-$env{MD_NAME}", OPTIONS+="string_escape=replace" +ENV{DEVTYPE}=="disk", ENV{MD_UUID}=="?*", SYMLINK+="disk/by-id/md-uuid-$env{MD_UUID}" +ENV{DEVTYPE}=="disk", ENV{MD_DEVNAME}=="?*", SYMLINK+="md/$env{MD_DEVNAME}" +ENV{DEVTYPE}=="partition", ENV{MD_NAME}=="?*", SYMLINK+="disk/by-id/md-name-$env{MD_NAME}-part%n", OPTIONS+="string_escape=replace" +ENV{DEVTYPE}=="partition", ENV{MD_UUID}=="?*", SYMLINK+="disk/by-id/md-uuid-$env{MD_UUID}-part%n" +ENV{DEVTYPE}=="partition", ENV{MD_DEVNAME}=="*[^0-9]", SYMLINK+="md/$env{MD_DEVNAME}%n" +ENV{DEVTYPE}=="partition", ENV{MD_DEVNAME}=="*[0-9]", SYMLINK+="md/$env{MD_DEVNAME}p%n" + +IMPORT{program}="vol_id --export $tempnode" +OPTIONS+="link_priority=100" +OPTIONS+="watch" +ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_UUID_ENC}=="?*", SYMLINK+="disk/by-uuid/$env{ID_FS_UUID_ENC}" +ENV{ID_FS_USAGE}=="filesystem|other", ENV{ID_FS_LABEL_ENC}=="?*", SYMLINK+="disk/by-label/$env{ID_FS_LABEL_ENC}" + +LABEL="anaconda_mdraid_member" +# probe raid metadata of mdraid member devices +ENV{ID_FS_TYPE}=="linux_raid_member", IMPORT{program}="$env{ANACBIN}/mdadm --examine --export $tempnode" + +# probe metadata of LVM2 physical volumes +ENV{ID_FS_TYPE}=="LVM2_member", IMPORT{program}="$env{ANACBIN}/lvm pvs --units k --nosuffix --nameprefixes --rows --unquoted --noheadings -opv_name,pv_uuid,pv_size,vg_name,vg_uuid,pv_pe_count,pv_pe_alloc_count,pe_start $tempnode" +ENV{LVM2_VG_NAME}!="?*", GOTO="anaconda_end" +ENV{ID_FS_TYPE}=="LVM2_member", IMPORT{program}="$env{ANACBIN}/lvm vgs --units k --nosuffix --nameprefixes --rows --unquoted --noheadings -ouuid,size,free,extent_size,extent_count,free_count,pv_count $env{LVM2_VG_NAME}" +ENV{ID_FS_TYPE}=="LVM2_member", IMPORT{program}="$env{ANACBIN}/lvm lvs --units k --nosuffix --nameprefixes --rows --unquoted --noheadings -olv_name,lv_uuid,lv_size $env{LVM2_VG_NAME}" + +LABEL="anaconda_end" + @@ -23,7 +23,8 @@ VERSION := $(shell awk '/Version:/ { print $$2 }' anaconda.spec) RELEASE := $(shell awk '/Release:/ { print $$2 }' anaconda.spec) CVSROOT ?= ${CVSROOT:-$(shell cat CVS/Root 2>/dev/null)} -SUBDIRS = isys loader po \ +SUBDIRS = isys loader po booty \ + storage storage/formats storage/devicelibs \ textw utils scripts bootdisk installclasses \ iw pixmaps command-stubs ui docs # fonts aren't on s390/s390x @@ -40,7 +41,7 @@ ifneq (,$(filter i386 x86_64,$(ARCH))) SUBDIRS += gptsync endif -PYCHECKERPATH=isys:textw:iw:installclasses:/usr/lib/booty:/usr/share/system-config-date +PYCHECKERPATH=isys:textw:iw:installclasses:/usr/share/system-config-date PYCHECKEROPTS=-F pycheckrc-for-anaconda CATALOGS = po/anaconda.pot @@ -86,10 +87,13 @@ install: mkdir -p $(DESTDIR)/usr/bin mkdir -p $(DESTDIR)/usr/sbin mkdir -p $(DESTDIR)/etc/rc.d/init.d + mkdir -p $(DESTDIR)/lib/udev/rules.d mkdir -p $(DESTDIR)/$(PYTHONLIBDIR) mkdir -p $(DESTDIR)/$(RUNTIMEDIR) mkdir -p $(DESTDIR)/$(ANACONDADATADIR) + install -m 644 70-anaconda.rules $(DESTDIR)/lib/udev/rules.d + install -m 755 anaconda $(DESTDIR)/usr/sbin/anaconda install -m 755 mini-wm $(DESTDIR)/usr/bin/mini-wm @@ -134,7 +138,7 @@ src: archive @rm -f anaconda-$(VERSION).tar.bz2 pycheck: - PYTHONPATH=$(PYCHECKERPATH) pychecker $(PYCHECKEROPTS) *.py textw/*.py iw/*.py installclasses/*.py | grep -v "__init__() not called" + PYTHONPATH=$(PYCHECKERPATH) pychecker $(PYCHECKEROPTS) *.py textw/*.py iw/*.py installclasses/*.py storage/*.py | grep -v "__init__() not called" pycheck-file: PYTHONPATH=.:$(PYCHECKERPATH) pychecker $(PYCHECKEROPTS) $(CHECK) | grep -v "__init__() not called" @@ -165,3 +169,46 @@ bumpver: install-buildrequires: yum install $$(grep BuildRequires: anaconda.spec | cut -d ' ' -f 2) + +# Generate an updates.img based on the changed files since the release +# was tagged. Updates are copied to ./updates-img and then the image is +# created. By default, the updates subdirectory is removed after the +# image is made, but if you want to keep it around, run: +# make updates.img KEEP=y +# And since shell is both stupid and amusing, I only match the first +# character to be a 'y' or 'Y', so you can do: +# make updates.img KEEP=yosemite +# Ahh, shell. +updates: + @if [ ! -d updates-img ]; then \ + mkdir updates-img ; \ + fi ; \ + build_isys="$$(git diff --stat anaconda-11.5.0.35-1 isys | grep " | " | cut -d ' ' -f 2 | egrep "(Makefile|\.h|\.c)$$")" ; \ + git diff --stat $(ARCHIVE_TAG) | grep " | " | \ + grep -v "\.spec" | grep -v "Makefile" | grep -v "\.c\ " | \ + grep -v "\.h" | grep -v "\.sh" | \ + while read sourcefile stuff ; do \ + dn="$$(echo $$sourcefile | cut -d '/' -f 1)" ; \ + case $$dn in \ + installclasses|storage|booty) \ + rm -rf updates-img/$$dn ; \ + cp -a $$dn updates-img ; \ + find updates-img/$$dn -type f | grep Makefile | xargs rm -f ;; \ + loader|po|scripts|command-stubs|tests|bootdisk|docs|fonts|utils|gptsync) \ + continue ;; \ + *) \ + cp -a $$sourcefile updates-img ;; \ + esac ; \ + done ; \ + if [ ! -z "$$build_isys" ]; then \ + make -C isys ; \ + cp isys/_isys.so updates-img ; \ + fi ; \ + cd updates-img ; \ + echo -n "Creating updates.img..." ; \ + ( find . | cpio -c -o | gzip -9c ) > ../updates.img ; \ + cd .. ; \ + keep="$$(echo $(KEEP) | cut -c1 | tr [a-z] [A-Z])" ; \ + if [ ! "$$keep" = "Y" ]; then \ + rm -rf updates-img ; \ + fi @@ -170,6 +170,11 @@ def setupPythonUpdates(): f), "/tmp/updates/%s/%s" %(pypkg, f)) + if os.access("/tmp/updates/70-anaconda.rules", os.R_OK): + import shutil + shutil.copyfile("/tmp/updates/70-anaconda.rules", + "/etc/udev/rules.d/70-anaconda.rules") + def parseOptions(): def resolution_cb (option, opt_str, value, parser): parser.values.runres = value @@ -348,12 +353,6 @@ def setupPythonPath(): sys.path.insert(1, '/usr/lib/anaconda/textw') sys.path.insert(2, '/usr/lib/anaconda/iw') - if (os.path.exists('booty')): - sys.path.append('booty') - sys.path.append('booty/edd') - else: - sys.path.append('/usr/lib/booty') - sys.path.append('/usr/share/system-config-date') def addPoPath(dir): @@ -498,6 +497,7 @@ class Anaconda: self.rescue = False self.updateSrc = None self.mediaDevice = None + self.platform = None self.canReIPL = False self.reIPLMessage = None @@ -570,6 +570,9 @@ class Anaconda: if not tree.startswith("/"): tree = "/%s" %(tree,) + if device.startswith("/dev/"): + device = device[5:] + self.mediaDevice = device self.methodstr = "cdrom://%s" % tree else: @@ -632,6 +635,9 @@ if __name__ == "__main__": import gettext _ = lambda x: gettext.ldgettext("anaconda", x) + import platform + anaconda.platform = platform.getPlatform(anaconda) + if not iutil.isS390() and os.access("/dev/tty3", os.W_OK): logger.addFileHandler ("/dev/tty3", log) @@ -775,8 +781,17 @@ if __name__ == "__main__": if opts.ksfile: anaconda.isKickstart = True instClass.setInstallData(anaconda) + + #we need waitWindow valid in processKickstartFile. because storage uses it + from snack import * + screen = SnackScreen() + anaconda.intf = rescue.RescueInterface(screen) + kickstart.processKickstartFile(anaconda, opts.ksfile) + anaconda.intf = None + screen.finish() + # command line 'nomount' overrides kickstart /same for vnc/ anaconda.rescue_mount = not (opts.rescue_nomount or anaconda.id.ksdata.rescue.nomount) @@ -985,11 +1000,10 @@ if __name__ == "__main__": # Skip the disk options in rootpath mode if flags.rootpath: - anaconda.dispatch.skipStep("partitionobjinit", permanent = 1) anaconda.dispatch.skipStep("parttype", permanent = 1) anaconda.dispatch.skipStep("autopartitionexecute", permanent = 1) anaconda.dispatch.skipStep("partition", permanent = 1) - anaconda.dispatch.skipStep("partitiondone", permanent = 1) + anaconda.dispatch.skipStep("storagedone", permanent = 1) anaconda.dispatch.skipStep("bootloader", permanent = 1) anaconda.dispatch.skipStep("bootloaderadvanced", permanent = 1) anaconda.dispatch.skipStep("upgbootloader", permanent = 1) @@ -1019,10 +1033,12 @@ if __name__ == "__main__": handleException(anaconda, sys.exc_info()) if anaconda.isKickstart and anaconda.id.ksdata.reboot.eject: - isys.flushDriveDict() - for drive in isys.cdromList(): - log.info("attempting to eject %s" % drive) - isys.ejectCdrom(drive) + for drive in anaconda.id.storage.devicetree.devices.values(): + if drive.type != "cdrom": + continue + + log.info("attempting to eject %s" % drive.path) + drive.eject() del anaconda.intf diff --git a/anaconda.spec b/anaconda.spec index 8faa2f3f3..49f45ad44 100644 --- a/anaconda.spec +++ b/anaconda.spec @@ -2,7 +2,7 @@ Summary: Graphical system installer Name: anaconda -Version: 11.5.0.23 +Version: 11.5.0.38 Release: 1 License: GPLv2+ Group: Applications/System @@ -41,9 +41,9 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %define createrepover 0.4.7 %define yumutilsver 1.1.11-3 %define iscsiver 6.2.0.870-3 +%define pythoncryptsetupver 0.0.6 BuildRequires: audit-libs-devel -BuildRequires: booty BuildRequires: bzip2-devel BuildRequires: device-mapper-devel >= %{dmver} BuildRequires: e2fsprogs-devel >= %{e2fsver} @@ -84,7 +84,6 @@ Requires: policycoreutils Requires: rpm-python >= %{rpmpythonver} Requires: comps-extras Requires: rhpl >= %{rhplver} -Requires: booty Requires: parted >= %{partedver} Requires: pyparted >= %{pypartedver} Requires: yum >= %{yumver} @@ -110,6 +109,7 @@ Requires: authconfig Requires: gnome-python2-gtkhtml2 Requires: system-config-firewall Requires: cryptsetup-luks +Requires: python-cryptsetup >= %{pythoncryptsetupver} Requires: mdadm Requires: lvm2 Requires: util-linux-ng @@ -145,6 +145,7 @@ Obsoletes: anaconda-images <= 10 Provides: anaconda-images = %{version}-%{release} Obsoletes: anaconda-runtime < %{version}-%{release} Provides: anaconda-runtime = %{version}-%{release} +Obsoletes: booty %description The anaconda package contains the program which was used to install your @@ -189,6 +190,7 @@ update-desktop-database &> /dev/null || : %doc docs/kickstart-docs.txt %doc docs/mediacheck.txt %doc docs/anaconda-release-notes.txt +/lib/udev/rules.d/70-anaconda.rules %{_bindir}/mini-wm %{_sbindir}/anaconda %ifarch i386 i486 i586 i686 x86_64 @@ -208,6 +210,397 @@ update-desktop-database &> /dev/null || : %endif %changelog +* Wed Mar 25 2009 Chris Lumens <clumens@redhat.com> - 11.5.0.38-1 +- Fix pylint errors in iw/*.py (hdegoede) +- Rework CryptTab.parse (dlehman). +- Code fixes of errors shown by pylint (mgracik). +- Don't underflow on the busy cursor stack. (clumens) +- "vg" is not valide inside this if. (jgranado) +- Device is sometimes None. (jgranado) +- Fix typo. (#492042) (dlehman) + +* Tue Mar 24 2009 David Cantrell <dcantrell@redhat.com> - 11.5.0.37-1 +- Start with a basic /etc/hosts file (#491634) (dcantrell) +- Do not flag every existing partition for resize (#491803) (dcantrell) +- Remove unused noformatCB() function. (dcantrell) +- Remove unnecessary istruefalse() function. (dcantrell) +- Build new _isys.so for updates.img if needed. (dcantrell) +- Get the UUID of each md array we create. (#491796) (dlehman) +- Call udev_settle after committing changes to a disk (#491529) (hdegoede) +- Be a little bit smarter about allocating space to grow parts. (#491761) + (dlehman) +- Check that partition is on the disk before trying to remove it. (#491997) + (dlehman) +- Work around a bug in mdadm incremental assembly. (dlehman) +- Use the same units (MB) for extent size that we do for everything else. + (dlehman) +- Put line breaks in between crypttab entries. (#491938) (dlehman) +- Register the NoDevFS class. (clumens) +- fslabels -> labels. (clumens) +- NFSDevice does not take exists= as a parameter. (clumens) +- Override _setDevice and _getDevice in NFS. (clumens) +- Move resolveDevice into the DeviceTree class. (clumens) +- Move most of the parseFSTab logic into its own function. (clumens) +- We don't even use partedUtils in this module. (clumens) +- PReP formats can never be active. (#491865) (dlehman) +- Move protectedPartition setup into storageInitialize (#491781). (clumens) +- Use the mount and unmount methods on OpticalDevice.format now. (clumens) +- Add a format for ISO9660 filesystems. (clumens) +- getDeviceByName does not expect the CD device to start with "/dev/" + (#491768). (clumens) +- Write the same arch to .discinfo as iutil.getArch() gives us (#490977). + (clumens) +- Don't remove partitions twice. (jgranado) + +* Mon Mar 23 2009 David Cantrell <dcantrell@redhat.com> - 11.5.0.36-1 +- Add EFI, Apple Bootstrap, and PPC PReP Boot formats. (dlehman) +- Remove all implicit calls to self.format.destroy from Device classes. + (dlehman) +- Pop the busy cursor when we're done with the wait window (#491736). + (clumens) +- If the new size and old size are the same, treat as a no-op (#491496). + (clumens) +- Let mountFilesystems handling bind mounting /dev (#490772). (clumens) +- Not all FileDevices have parents, so don't assume. (clumens) +- Bind mount formats are mountable. (clumens) +- If a filesystem is already mounted, don't raise an error. (clumens) +- Fix a typo calling the superclass's constructor. (clumens) +- Add a fake device for bind mounting /dev. (clumens) +- If there was an exception leading to the urlgrabber error, log it. + (clumens) +- Fix the import of checkbootloader (#491574). (clumens) +- Add a missing import (#491605). (clumens) + +* Fri Mar 20 2009 David Cantrell <dcantrell@redhat.com> - 11.5.0.35-1 +- Fix traceback in FSSet.crypttab. (#491160) (dlehman) +- Fix traceback on upgrade. (#491446) (dlehman) +- Do not include .h and .sh files in updates.img (dcantrell) +- Make PartitionDevice resize work. (dcantrell) +- Reset mouse pointer if we find an unreadable disk. (dcantrell) +- Use label attr instead of non-existent fslabel attr. (#491120) (dlehman) +- Need to notify the kernel of changes before udev settle (katzj) +- Revert "mount and umount commands are in /sbin now, remove from /usr/sbin" + (dcantrell) +- Make some fixes to the rescue mode system selection UI (#489973, #489977). + (clumens) +- Fix text mode autopartitioning (#491282). (clumens) +- Do not use _rnetdev as fstab option for network based / (hdegoede) +- Make root= line in grub.conf and path spec in fstab consistent (hdegoede) +- Fix a reference to the partitions list (#491335). (clumens) +- Do not traceback at the very beginning of rescue mode (msivak) +- Fix traceback when editing encrypted mdraid device in UI. (rvykydal) + +* Thu Mar 19 2009 David Cantrell <dcantrell@redhat.com> - 11.5.0.34-1 +- Catch FSError when detecting storage, prevent user from continuing. + (dcantrell) +- If we have no error string, place None in the tuple. (dcantrell) +- Move OUTPUT_TERMINAL definition to isys.h (dcantrell) +- mount and umount commands are in /sbin now, remove from /usr/sbin + (dcantrell) +- Avoid SIGSEGV in doPwMount() when NULL is last parameter (#491192) + (dcantrell) +- Attempt disk commits 5 times before raising an exception. (dcantrell) +- Add boot partition size limit properties and size validation method. + (dlehman) +- Make sure boot flag gets set. (#491170) (dlehman) +- Make bootable a property of PartitionDevice. (dlehman) +- After setting up our random UUID, inform the storage layer (katzj) +- Handle system crappyness. (jgranado) +- Fix up checking for live image backing (katzj) +- Let's not remove our mountpoints (katzj) +- Fix writing the default= line in grub.conf (#490756). (clumens) +- Revert "Fix pruning of destroy actions for preexisting devices." (dlehman) +- Add more blacklisting (katzj) +- Blacklist the live image backing device (katzj) +- Move blockdev blacklisting to be a function (katzj) +- Inhibit devkit-disks during a live install (katzj) +- try to unmount everything from /media on live installs (katzj) +- Fix live installs to not traceback (katzj) +- Fix New partition in UI (rvykydal) + +* Thu Mar 19 2009 David Lehman <dlehman@redhat.com> - 11.5.0.33-1 +- Rework the lvm dialog. (#490301,#490966,#490681,#489870) (dlehman) +- Improve chances of uniqueness from Storage.createSuggestedLVName. (dlehman) +- Fix pruning of destroy actions for preexisting devices. (dlehman) +- Devices should not be resizable unless they exist. (dlehman) +- Try to activate an existing md array after adding each member. (dlehman) +- Indicate filesystem is mountable if we have a mount command. (dcantrell) +- Mount existing filesystems read-only when getting size. (dcantrell) +- Fix some errors in the updates target. (dcantrell) +- Place all mount.* commands in /sbin (dcantrell) +- Fix error message reading and writing in doPwMount() (dcantrell) +- Use booleans in isys.mount() and isys.umount() (dcantrell) +- Add a FIXME comment for setting uuid in VG / LV create (hdegoede) +- Do not traceback when writing anaconda.ks with iscsi with auth info. + (hdegoede) +- Do not write LV uuid to grub.conf, but the filesystem uuid (hdegoede) +- If a mountpoint depends on a network disk at _netdev to its fstab options + (hdegoede) +- Do not hang when creating raid array with member having filesystem + detected (#490891) (rvykydal) +- Destroy and create luks child of raid array too when editing in UI. + (rvykydal) +- Editing non-existent raid device by destroying and creating actions + (rvykydal) +- actionDestroyFormat call takes device, not format (rvykydal) +- Fix getChildren call in partition UI (rvykydal) +- Fix removing of devices with the same name from tree when adding + create action. (rvykydal) +- Do not duplicate requested minor number in edit raid UI list. (rvykydal) +- Offer available partitions when editing non-preexisting raid request. + (rvykydal) +- Don't try to fit the whole StorageDevice.__str__ output into the UI + (#490406). (clumens) +- Make PartitionDevice handle both normal and dmraid partitions (hdegoede) +- Stop overriding __init__ in DMRaidPartitionDevice (hdegoede) +- Set format UUID after creating a format (hdegoede) +- Fix result of updateSysfsPath to be consistent with initial sysfsPath + values (hdegoede) +- Use getDevicesByInstance() for storage.partitions (hdegoede) +- We no longer use iscsiadm anywhere (hdegoede) + +* Tue Mar 17 2009 Jesse Keating <jkeating@redhat.com> - 11.5.0.32-1 +- Typo fix. (clumens) +- Make platform.checkBootRequest work better and not use diskset anymore. (clumens) +- Fix a traceback when looking for PS3 boot partitions (#490738). (clumens) +- FormatArgs -> FormatOptions (#490737). (clumens) +- Fix ppoll() timeout=infinity usage in auditd (#484721). (pjones) +- Simplify kernel package selection. (clumens) +- Look at CPU flags instead of /proc/iomem to determine PAE-ness (#484941). (clumens) +- Tell NM not to touch interfaces when / is on a network disk (hdegoede) +- Get iscsi going with the new storage code (hdegoede) +- Use minihal instead of isys.hardDriveDict in list-harddrives (#488122). (clumens) +- storage.disks never includes disks without media present. (clumens) +- Changed the getDevicebyLabel() to getDeviceByLabel() in devicetree.py (mgracik) + +* Mon Mar 16 2009 David Cantrell <dcantrell@redhat.com> - 11.5.0.31-1 +- Don't use disk.maximizePartition anymore. (dlehman) +- Only schedule implicit format destruction if there is formatting to + destroy. (dlehman) +- Reset encryptionPassphrase when we reset the rest of storage. (dlehman) +- Do not create a LUKSDevice if we do not have a way to map the device. + (dlehman) +- Fix handling of new extended partitions during partition allocation. + (dlehman) +- Fix bug in dependency list for partitions. (dlehman) +- Fix inconsistency in variable use in search for free space. (dlehman) +- Check for disk name being in disk.name not in clearPartDisks (dcantrell) +- Create a Makefile target to generate updates.img automatically. (dcantrell) +- When creating free space, handle cases other than clearpart --drives= + (clumens) +- Ignore loop and ram devices (hdegoede) +- devicetree: fix slave addition of incomplete dm / md devices (hdegoede) +- Catch LVMErrors too when tearing down devices (hdegoede) +- Install udev rules in /lib/udev/rules.d instead of in runtime dir + (hdegoede) +- Ignore disk devices with missing media (#488800). (clumens) +- Use correct parse method for the upgrade command (#471232) (wwoods) +- Fix creation of fs options for preexisting encrypted devices. (dlehman) +- Fix lots of buggy behavior in the partition dialog. (dlehman) +- Handle FTP servers that both want and don't want PASS after USER + (#490350). (clumens) +- Fixed the names of the variables for lvm.py functions. (mgracik) +- editPartitionRequest -> editPartition in iw/partition_gui.py (#490384). + (clumens) +- clampPVSize -> clampSize in lvm.py (#490295). (clumens) +- Fix the obvious and stupid typo (#490296). (clumens) +- isys.umount removes mount directory by default (rvykydal) +- Fix tempfile.mkdtemp call. (rvykydal) +- Initialize attribute _mountpoint before using it (rvykydal) +- devicetree.py has _ignoredDisks instead of ignoredDisks. (jgranado) +- Create separate resize actions for formats and devices. (dcantrell) +- Use os.statvfs() to get existing filesystem size. (dcantrell) +- Add resizeArgs for Ext2FS and fix it for BtrFS. (dcantrell) +- Report when we cannot find any free space partitions. (dcantrell) +- Improve resizeDialog text. (dcantrell) +- Raise FSResizeError if filesystem cannot be resized. (dcantrell) +- Handle resizing when setting targetSize for PartitionDevice (dcantrell) +- Let users set the size property of StorageDevices. (dcantrell) +- Add support for kickstart's '--initlabel' option to clearpart. (dlehman) +- Fix display of LV format type for encrypted LVs. (dlehman) +- Make paths somewhat flexible so we'll work in normal environments. + (dlehman) + +* Fri Mar 13 2009 David Lehman <dlehman@redhat.com> - 11.5.0.30-1 +- Fix supportable attribute for cmdline-enabled fstypes. (dlehman) +- Access private attribute for luks dict. (dlehman) +- Schedule format create for newly encrypted preexisting partition. (dlehman) +- Don't traceback if vg.teardown fails in recursive teardown. (dlehman) +- Schedule format create action for newly encrypted preexisting LV. (dlehman) +- Make sure we return something other than None for new requests. (dlehman) +- Add __str__ methods to Device objects. (clumens) +- Add mediaPresent and eject to the OpticalDevice class. (clumens) +- Use the right import path for checkbootloader (#490049). (clumens) +- Rename /etc/modprobe.d/anaconda to /etc/modprobe.d/anaconda.conf (clumens) +- Don't clear partitions containing the install media. (dlehman) +- Wait til everyone knows the format/fs is no longer active. (dlehman) +- Save a copy of the device stack so we can destroy the format. (#489975) + (dlehman) +- Add a deep copy method to Device since we can't just use copy.deepcopy. + (dlehman) +- Fix infinite loops in partition screen populate. (#490051) (dlehman) +- Default to a name based on the uuid for existing luks mappings. (dlehman) +- Use the correct keyword for luks map names ('name', not 'mapName'). + (dlehman) +- Fix getting of number of total devices of sw raid. (rvykydal) +- Only select the Core group in text mode (#488754). (clumens) +- Added test case for devicelib mdraid.py. (mgracik) +- Add created user to default group created for the user. (rvykydal) +- Fix editing of existing logical volume. (rvykydal) +- Add a list that lvm should ignore. (jgranado) + +* Thu Mar 12 2009 David Lehman <dlehman@redhat.com> - 11.5.0.29-1 +- Don't create a PartitionDevice for devices that do not exist (#489122). + (clumens) +- A getter doesn't usually take a parameter (#489965). (clumens) +- Do not write "Running..." to stdout, as that could be tty1. (clumens) +- Call storage.exceptionDisks, not diskset.exceptionDisks. (#489615) + (dlehman) +- Fix typo. (jgranado) +- Fix typo. (dlehman) +- Add udev rules for handling for mdraid arrays. (dlehman) +- Honor the zerombr kickstart directive. (dlehman) +- currentSize is expected to be a float, so convert it to one (#489882). + (clumens) +- It's clearPartDisks, not clearPartDrives. (clumens) +- Get rid of the mappings and ksID as well. (clumens) +- Make sure the device has a diskType before attempting to check what it is. + (clumens) +- Update the volgroup command to work with the new storage code. (clumens) +- Update the raid command to work with the new storage code. (clumens) +- Update the part command to work with the new storage code. (clumens) +- Update the logvol command to work with the new storage code. (clumens) +- addPartRequest is no longer needed. (clumens) +- Don't set default partitioning in every kickstart case. (clumens) +- Clear partitions before scheduling requests. (clumens) +- Always go through doAutoPart. (clumens) +- Format modules import fix (mgracik) +- Fixed the format modules import (mgracik) +- Allow overriding the anaconda udev rules from an updates.img (hdegoede) +- If a pv somehow does not contain a vg_name, do not try to get other vg + info (hdegoede) + +* Wed Mar 11 2009 David Cantrell <dcantrell@redhat.com> - 11.5.0.28-1 +- Fix a few bugs in the lvm dialog. (#489022) (dlehman) +- Modify livecd.py to work with new storage backend. (dlehman) +- Be explicit about resetting Disks' partedDisk attribute. (#489678) + (dlehman) +- Deactivate devices after we've finished scanning them. (dlehman) +- Handle the case of removing an unallocated partition from the tree. + (dlehman) +- Try again to set up LVs when we've just added a new PV to the VG. (dlehman) +- Set partition flags in format create/destroy execute methods. (dlehman) +- Make sure we use the newly committed parted.Partition after create. + (dlehman) +- Make device teardown methods more resilient. (dlehman) +- Initialize storage in rescue mode so we can find roots (#488984). (clumens) +- We also need to pack up the extra args tuple, too. (clumens) +- doLoggingSetup keeps growing new arguments, so put them into a dict + (#489709). (clumens) +- Fix anaconda udev rules to not require pre-existing device nodes (hdegoede) +- Hook up 'Shrink current system' dialog to new storage code. (dcantrell) +- Fix _getCheckArgs() in class FS. (dcantrell) + +* Tue Mar 10 2009 David Cantrell <dcantrell@redhat.com> - 11.5.0.27-1 +- Fix action pruning to handle more complex scenarios. (dlehman) +- Schedule destruction of any existing formatting along with the device. + (dlehman) +- Add a size attribute to mdraid arrays. (dlehman) +- Speed up partitioning screen redraws by trimming workload where possible. + (dlehman) +- Create partitions with exactly the geometry we calculate. (dlehman) +- Fix name collision between formats.mdraid and devicelibs.mdraid. (dlehman) +- Destruction of the member device formatting will be handled elsewhere. + (dlehman) +- Fix a typo (jkeating) +- Fix pruning between two destroy actions on the same device (rvykydal) +- Use the pyblock functions when possible. (jgranado) +- We are searching a list, not a dict now (rvykydal) + +* Mon Mar 09 2009 David Cantrell <dcantrell@redhat.com> - 11.5.0.26-1 +- Move the recursive teardown of all devices out of processActions. (dlehman) +- Clean up handling of /proc, /sys, /dev/pts, /dev/shm entries. (dlehman) +- Fix several minor bugs preventing upgrade/rescue mount. (#488946) (dlehman) +- Only populate the device tree on demand. (dlehman) +- Prune actions by device based on path, not object-id. (dlehman) +- Rewrite action sort so it works correctly. (dlehman) +- Do a separate disk.commit for each partition add/remove. (dlehman) +- Fix bug keeping track of best free region/type/disk info. (dlehman) +- Return early if doAutoPart is False, but clearpart first if kickstart. + (dlehman) +- Recognize PS3 as a valid machine type (#489263). (clumens) +- Move the mdRaidBootArches logic into the platform module. (clumens) +- stdout and stderr may also need to be created. (clumens) +- Fix booty for dmraid (hdegoede) +- It's self.origrequest, not self.origreqest (#489036). (clumens) +- Added crypto.py unittest; Updated devicelibs tests baseclass.py and lvm.py + (mgracik) +- Start storage before parsing the kickstart file. (clumens) +- Make sure autopart without any clearpart command will fail. (clumens) +- Update storage flag on ks autopart (rvykydal) +- Use correct storage attribute for ks clearpart (rvykydal) +- Catch the new _ped.DiskLabelException for unrecognized disklabels. + (dlehman) +- Catch all failures from making parted objects in exceptionDisks. (dlehman) +- various dmraid fixes. (jgranado) +- Implement the format disk question as a callback. (jgranado) +- Add dmraid functionality to new storage code. (jgranado) +- Do not pass None values into nonmandatory arguments, you are screwing the + default values.. (msivak) + +* Thu Mar 05 2009 David Cantrell <dcantrell@redhat.com> - 11.5.0.25-1 +- Schedule device destroy actions for partitions last. (dlehman) +- Pass storage.disks, not storage, to createAllowed.... (#488860) (dlehman) +- Nodev filesystems always exist. And the device is arbitrary. (dlehman) +- Include proc, &c filesystems in fstab and FSSet.{mount/umount}Filesystems. + (dlehman) +- Remove FSSet.writeFSTab. That job is handled elsewhere. (dlehman) +- Add properties to FSSet to provide the nodev entries. (dlehman) +- Fix incomplete format in Storage.deviceImmutable. (dlehman) +- Make sure we use the same disk the free space is on. (#488807) (dlehman) +- Prevent clobbering of name 'mdraid' by qualifying it. (dlehman) +- Handle unformatted disks and cdroms in Storage.exceptionDisks. (dlehman) +- Add resizeArgs property for resizable filesystems. (dcantrell) +- Fill out class NTFS a bit more. (dcantrell) +- Add fsckProg property to class FS. (dcantrell) +- Ext2FS.migratable(self) -> Ext2FS.migratable (dcantrell) +- Fix StorageDevice.minSize() and PartitionDevice.maxSize() (dcantrell) +- Center resize window on the screen. (dcantrell) +- Do not raise DeviceError if not bootable device is found. (dcantrell) +- Do an even more thorough job of ignoring disks libparted doesn't like. + (clumens) +- Fix a couple problems on the "Change device" bootloader dialog. (clumens) +- Fix a typo when writing out the mdadm config file. (clumens) +- Remove all uses of isys.cdromList, which no longer exists. (clumens) +- Check to see if we're on S390 on the congrats screen (#488747). (clumens) +- Handle non-fatal errors more gracefully in addUdevDevice. (dlehman) +- partRequests no longer exists, so don't try to import it (#488743). + (clumens) +- When building the exceptionDisks list, skip devices libparted doesn't + like. (clumens) +- Iterate over devicetree.devices.values, not devicetree. (dlehman) +- Add a get() method to Flags, since it pretends to be a dictionary. + (clumens) +- Stop with the fsset usage. (dlehman) +- Format message string after translation not before (msivak) +- We need newer python-cryptsetup because of the default values for cipher + and keysize for luskFormat (msivak) +- If a drive is not initialized, offer reinitialization or ignoring the + drive to the user (msivak) +- More syntax errors / traceback fixes (hdegoede) +- Fix syntax errors (rvykydal) +- Implement Storage.sanityCheck, mostly from old partitions code. (dlehman) + +* Thu Mar 5 2009 Dave Lehman <dlehman@redhat.com> - 11.5.0.24-3 +- Fix booty's desire to import fsset. +- Fix attempt to set read-only attr "removable" in DiskDevice.__init__ + +* Thu Mar 05 2009 Peter Jones <pjones@redhat.com> - 11.5.0.24-2 +- Add EFI boot.iso generation. + +* Wed Mar 4 2009 Dave Lehman <dlehman@redhat.com> - 11.5.0.24-1 +- Storage test day. + * Fri Feb 20 2009 David Cantrell <dcantrell@redhat.com> - 11.5.0.23-1 - Remove old content from utils/ (dcantrell) - Ensure request.drive is always a list (#485622) (dcantrell) diff --git a/autopart.py b/autopart.py deleted file mode 100644 index 995bf9775..000000000 --- a/autopart.py +++ /dev/null @@ -1,1751 +0,0 @@ -# -# autopart.py - auto partitioning logic -# -# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 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 <http://www.gnu.org/licenses/>. -# -# Author(s): Jeremy Katz <katzj@redhat.com> -# - -import parted -import copy -import string, sys -import fsset -import lvm -import logging -from anaconda_log import logger, logFile -import cryptodev -import partedUtils -import partRequests -from constants import * -from errors import * - -import iutil -import isys - -log = logging.getLogger("anaconda") - -import gettext -_ = lambda x: gettext.ldgettext("anaconda", x) - - -PARTITION_FAIL = -1 -PARTITION_SUCCESS = 0 - -BOOT_NOT_EXT2 = -1 -BOOTEFI_NOT_VFAT = -2 -BOOTALPHA_NOT_BSD = -3 -BOOTALPHA_NO_RESERVED_SPACE = -4 -BOOTIPSERIES_TOO_HIGH = -5 - -DEBUG_LVM_GROW = 0 - -# Add another logger for the LVM debugging, since there's a lot of that. -# Set DEBUG_LVM_GROW if you want to spew all this information to the log -# file. Otherwise it'll get ignored. -logger.addLogger ("anaconda.lvm", minLevel=logging.DEBUG) -lvmLog = logging.getLogger("anaconda.lvm") - -if DEBUG_LVM_GROW: - logger.addFileHandler (logFile, lvmLog, minLevel=logging.DEBUG) -else: - lvmLog.setLevel (logging.CRITICAL) - logger.addFileHandler (logFile, lvmLog, minLevel=logging.CRITICAL) - -# check that our "boot" partitions meet necessary constraints unless -# the request has its ignore flag set -def bootRequestCheck(req, diskset): - if not req.device or req.ignoreBootConstraints: - return PARTITION_SUCCESS - - part = None - - if not hasattr(req, "drive"): - return PARTITION_SUCCESS - - for drive in req.drive: - part = diskset.disks[drive].getPartitionByPath("/dev/%s" % req.device) - - if not part: - return PARTITION_SUCCESS - - if iutil.isEfi(): - if req.mountpoint == "/boot": - if not part.fileSystem.type.startswith("ext"): - return BOOT_NOT_EXT2 - elif req.mountpoint == "/boot/efi": - if not part.fileSystem.type in ["fat16", "fat32"]: - return BOOTEFI_NOT_VFAT - elif iutil.isAlpha(): - return bootAlphaCheckRequirements(part) - elif (iutil.getPPCMachine() == "pSeries" or - iutil.getPPCMachine() == "iSeries"): - for drive in req.drive: - part = diskset.disks[drive].getPartitionByPath("/dev/%s" % req.device) - if part and ((part.geometry.end * part.geometry.device.sectorSize / - (1024.0 * 1024)) > 4096): - return BOOTIPSERIES_TOO_HIGH - - return PARTITION_SUCCESS - -# Alpha requires a BSD partition to boot. Since we can be called after: -# -# - We re-attached an existing /boot partition (existing dev.drive) -# - We create a new one from a designated disk (no dev.drive) -# - We auto-create a new one from a designated set of disks (dev.drive -# is a list) -# -# it's simpler to get disk the partition belong to through dev.device -# Some other tests pertaining to a partition where /boot resides are: -# -# - There has to be at least 1 MB free at the begining of the disk -# (or so says the aboot manual.) - -def bootAlphaCheckRequirements(part): - disk = part.disk - - # Disklabel check - if not disk.type == "bsd": - return BOOTALPHA_NOT_BSD - - # The first free space should start at the begining of the drive - # and span for a megabyte or more. - free = disk.getFirstPartition() - while free: - if free.type & parted.PARTITION_FREESPACE: - break - free = free.nextPartition() - if (not free or free.geometry.start != 1L or free.getSize(unit="MB") < 1): - return BOOTALPHA_NO_RESERVED_SPACE - - return PARTITION_SUCCESS - -def printFreespaceitem(part): - return part.getDeviceNodeName(), part.geometry.start, part.geometry.end, part.getSize(unit="MB") - -def printFreespace(free): - print("Free Space Summary:") - for drive in free.keys(): - print("On drive ", drive) - for part in free[drive]: - print("Freespace:", printFreespaceitem(part)) - - -def findFreespace(diskset): - free = {} - for drive in diskset.disks.keys(): - disk = diskset.disks[drive] - free[drive] = disk.getFreeSpacePartitions() - return free - - -def bestPartType(disk, request): - numPrimary = len(disk.getPrimaryPartitions()) - maxPrimary = disk.maxPrimaryPartitionCount - if numPrimary == maxPrimary: - raise PartitioningError, "Unable to create additional primary partitions on /dev/%s" % (disk.device.path[5:]) - if request.primary: - return parted.PARTITION_NORMAL - if ((numPrimary == (maxPrimary - 1)) and - not disk.getExtendedPartition() and - disk.supportsFeature(parted.DISK_TYPE_EXTENDED)): - return parted.PARTITION_EXTENDED - return parted.PARTITION_NORMAL - -class partlist: - def __init__(self): - self.parts = [] - - def __str__(self): - retval = "" - for p in self.parts: - retval = retval + "\t%s %s %s\n" % (p.getDeviceNodeName(), partedUtils.get_partition_file_system_type(p), p.getSize(unit="MB")) - - return retval - - def reset(self): - dellist = [] - for part in self.parts: - dellist.append(part) - - for part in dellist: - self.parts.remove(part) - del part - - self.parts = [] - -def getMinimumSector(disk): - if disk.type == 'sun': - (cylinders, heads, sectors) = disk.device.biosGeometry - start = long(sectors * heads) - start /= long(1024 / disk.device.sectorSize) - return start + 1 - return 0L - -# first step of partitioning voodoo -# partitions with a specific start and end cylinder requested are -# placed where they were asked to go -def fitConstrained(diskset, requests, primOnly=0, newParts = None): - for request in requests.requests: - if request.type != REQUEST_NEW: - continue - if request.device: - continue - if primOnly and not request.primary and not requests.isBootable(request): - continue - if request.drive and (request.start != None): - if not request.end and not request.size: - raise PartitioningError, "Tried to create constrained partition without size or end" - - fsType = request.fstype.getPartedFileSystemType() - disk = diskset.disks[request.drive[0]] - if not disk: # this shouldn't happen - raise PartitioningError, "Selected to put partition on non-existent disk!" - - startSec = disk.device.startCylinderToSector(request.start) - - if request.end: - endCyl = request.end - elif request.size: - endCyl = disk.device.endSectorToCylinder(((1024L * 1024L * request.size) / disk.device.sectorSize) + startSec) - - endSec = disk.device.endCylinderToSector(endCyl) - - if endSec > disk.device.length: - raise PartitioningError, "Unable to create partition which extends beyond the end of the disk." - - # XXX need to check overlaps properly here - minSec = getMinimumSector(disk) - if startSec < minSec: - startSec = minSec - - if disk.supportsFeature(parted.DISK_TYPE_EXTENDED) and disk.getExtendedPartition(): - extpart = disk.getExtendedPartition() - if (extpart.geometry.start < startSec) and (extpart.geometry.end >= endSec): - partType = parted.PARTITION_LOGICAL - if request.primary: # they've required a primary and we can't do it - raise PartitioningError, "Cannot create another primary partition for %s." % request.mountpoint - # check to make sure we can still create more logical parts - if (len(disk.getLogicalPartitions()) == - disk.getMaxLogicalPartitions()): - raise PartitioningError, "Cannot create another logical partition for %s." % request.mountpoint - else: - partType = parted.PARTITION_NORMAL - else: - # XXX need a better way to do primary vs logical stuff - ret = bestPartType(disk, request) - - if ret == parted.PARTITION_NORMAL: - partType = parted.PARTITION_NORMAL - elif ret == parted.PARTITION_EXTENDED: - geometry = parted.Geometry(device=disk.device, - start=startSec, - end=endSec) - newp = parted.Partition(disk=disk, - type=parted.PARTITION_EXTENDED, - geometry=geometry) - constraint = parted.Constraint(device=disk.device) - disk.addPartition(newp, constraint) - disk.maximizePartition(newp, constraint) - newParts.parts.append(newp) - requests.nextUniqueID = requests.nextUniqueID + 1 - partType = parted.PARTITION_LOGICAL - else: # shouldn't get here - raise PartitioningError, "Impossible partition type to create" - geometry = parted.Geometry(device=disk.device, - start=startSec, - end=endSec) - newp = parted.Partition(disk=disk, type=partType, - fs=fsType, geometry=geometry) - constraint = parted.Constraint(device=disk.device) - try: - disk.addPartition(newp, constraint) - - except Exception, msg: - raise PartitioningError, str(msg) - for flag in request.fstype.getPartedPartitionFlags(): - if not newp.isFlagAvailable(flag): - disk.deletePartition(newp) - raise PartitioningError, ("requested FileSystemType needs " - "a flag that is not available.") - newp.setFlag(flag) - request.device = fsset.PartedPartitionDevice(newp).getDevice() - request.currentDrive = request.drive[0] - newParts.parts.append(newp) - -# get the list of the "best" drives to try to use... -# if currentdrive is set, use that, else use the drive list, or use -# all the drives -def getDriveList(request, diskset): - if request.currentDrive: - drives = request.currentDrive - elif request.drive: - drives = request.drive - else: - drives = diskset.disks.keys() - - if not type(drives) == type([]): - drives = [ drives ] - - drives.sort(isys.compareDrives) - - return drives - - -# fit partitions of a specific size with or without a specific disk -# into the freespace -def fitSized(diskset, requests, primOnly = 0, newParts = None): - todo = {} - - for request in requests.requests: - if request.type != REQUEST_NEW: - continue - if request.device: - continue - if primOnly and not request.primary and not requests.isBootable(request): - continue - if request.size == 0 and request.requestSize == 0: - request.requestSize = 1 - 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) - if not todo.has_key(numDrives): - todo[numDrives] = [ request ] - else: - todo[numDrives].append(request) - - number = todo.keys() - number.sort() - free = findFreespace(diskset) - - for num in number: - for request in todo[num]: -# print("\nInserting ->", request) - if requests.isBootable(request): - isBoot = 1 - else: - isBoot = 0 - - largestPart = (0, None) - drives = getDriveList(request, diskset) - lvmLog.debug("Trying drives to find best free space out of %s" %(free,)) - for drive in drives: - # this request is bootable and we've found a large enough - # partition already, so we don't need to keep trying other - # drives. this keeps us on the first possible drive - if isBoot and largestPart[1]: - break -## print("Trying drive", drive) - disk = diskset.disks[drive] - numPrimary = len(disk.getPrimaryPartitions()) - numLogical = len(disk.getLogicalPartitions()) - - # if there is an extended partition add it in - if disk.getExtendedPartition(): - numPrimary = numPrimary + 1 - - maxPrimary = disk.maxPrimaryPartitionCount - maxLogical = disk.getMaxLogicalPartitions() - - for part in free[drive]: - # if this is a free space outside extended partition - # make sure we have free primary partition slots - if not part.type & parted.PARTITION_LOGICAL: - if numPrimary == maxPrimary: - continue - else: - if numLogical == maxLogical: - continue - - lvmLog.debug( "Trying partition %s" % (printFreespaceitem(part),)) - partSize = part.getSize(unit="MB") - # figure out what the request size will be given the - # geometry (#130885) - requestSectors = long((request.requestSize * 1024L * 1024L) / part.disk.device.sectorSize) - 1 - requestSizeMB = long((requestSectors * part.disk.device.sectorSize) / 1024L / 1024L) - lvmLog.debug("partSize %s request %s" % (partSize, request.requestSize)) - if partSize >= requestSizeMB and partSize > largestPart[0]: - if not request.primary or (not part.type & parted.PARTITION_LOGICAL): - largestPart = (partSize, part) - if isBoot: - break - - if not largestPart[1]: - # if the request has a size of zero, it can be allowed to not - # exist without any problems - if request.size > 0: - raise PartitioningError, "Not enough space left to create partition for %s" % request.mountpoint - else: - request.device = None - request.currentDrive = None - continue -# raise PartitioningError, "Can't fulfill request for partition: \n%s" %(request) - - lvmLog.debug("largestPart is %s" % (largestPart,)) - freespace = largestPart[1] - freeStartSec = freespace.geometry.start - freeEndSec = freespace.geometry.end - - dev = freespace.geometry.device - disk = freespace.disk - - startSec = freeStartSec - - # For alpha reserve space at the begining of disk - if iutil.isAlpha() and startSec < long((1024L * 1024L)/disk.device.sectorSize): - startSec = long((2 * 1024L * 1024L)/disk.device.sectorSize) - - endSec = startSec + long(((request.requestSize * 1024L * 1024L) / disk.device.sectorSize)) - 1 - - if endSec > freeEndSec: - endSec = freeEndSec - if startSec < freeStartSec: - startSec = freeStartSec - - if freespace.type & parted.PARTITION_LOGICAL: - partType = parted.PARTITION_LOGICAL - else: - # XXX need a better way to do primary vs logical stuff - ret = bestPartType(disk, request) - - if ret == parted.PARTITION_NORMAL: - partType = parted.PARTITION_NORMAL - elif ret == parted.PARTITION_EXTENDED: - geometry = parted.Geometry(device=disk.device, - start=startSec, - end=endSec) - newp = parted.Partition(disk=disk, - type=parted.PARTITION_EXTENDED, - geometry=geometry) - constraint = parted.Constraint(device=disk.device) - disk.addPartition(newp, constraint) - disk.maximizePartition(newp, constraint) - newParts.parts.append(newp) - requests.nextUniqueID = requests.nextUniqueID + 1 - partType = parted.PARTITION_LOGICAL - - # now need to update freespace since adding extended - # took some space - found = 0 - for part in disk.getFreeSpacePartitions(): - if part.geometry.start > freeStartSec and part.geometry.end <= freeEndSec: - found = 1 - freeStartSec = part.geometry.start - freeEndSec = part.geometry.end - break - - if not found: - raise PartitioningError, "Could not find free space after making new extended partition" - - startSec = freeStartSec - endSec = startSec + long(((request.requestSize * 1024L * 1024L) / disk.device.sectorSize)) - 1 - - if endSec > freeEndSec: - endSec = freeEndSec - if startSec < freeStartSec: - startSec = freeStartSec - - else: # shouldn't get here - raise PartitioningError, "Impossible partition to create" - - fsType = request.fstype.getPartedFileSystemType() - lvmLog.debug("creating newp with start=%s, end=%s, len=%s" % (startSec, endSec, endSec - startSec)) - geometry = parted.Geometry(device=disk.device, - start=startSec, - end=endSec) - fs=parted.FileSystem(type=fsType.name, geometry=geometry) - newp = parted.Partition(disk=disk, type=partType, - fs=fs, geometry=geometry) - constraint = parted.Constraint(device=disk.device) - - try: - disk.addPartition(newp, constraint) - except Exception, msg: - raise PartitioningError, str(msg) - for flag in request.fstype.getPartedPartitionFlags(): - if not newp.isFlagAvailable(flag): - disk.deletePartition(newp) - raise PartitioningError, ("requested FileSystemType needs " - "a flag that is not available.") - newp.setFlag(flag) - - request.device = fsset.PartedPartitionDevice(newp).getDevice() - drive = newp.geometry.device.path[5:] - request.currentDrive = drive - newParts.parts.append(newp) - free = findFreespace(diskset) - -# grow logical partitions -# -# do this ONLY after all other requests have been allocated -# we just go through and adjust the size for the logical -# volumes w/o rerunning process partitions -# -def growLogicalVolumes(diskset, requests): - - if requests is None or diskset is None: - return - - # iterate over each volume group, grow logical volumes in each - for vgreq in requests.requests: - if vgreq.type != REQUEST_VG: - continue - - lvmLog.info("In growLogicalVolumes, considering VG %s", vgreq) - lvreqs = requests.getLVMLVForVG(vgreq) - - if lvreqs is None or len(lvreqs) < 1: - lvmLog.info("Apparently it had no logical volume requests, skipping.") - continue - - # come up with list of logvol that are growable - growreqs = [] - for lvreq in lvreqs: - if lvreq.grow: - growreqs.append(lvreq) - - # bail if none defined - if len(growreqs) < 1: - lvmLog.info("No growable logical volumes defined in VG %s.", vgreq) - continue - - lvmLog.info("VG %s has these growable logical volumes: %s", vgreq.volumeGroupName, reduce(lambda x,y: x + [y.uniqueID], growreqs, [])) - - # get remaining free space - if DEBUG_LVM_GROW: - vgfree = lvm.getVGFreeSpace(vgreq, requests, diskset) - lvmLog.debug("Free space in VG after initial partition formation = %s", (vgfree,)) - - # store size we are starting at - initsize = {} - cursize = {} - for req in growreqs: - size = req.getActualSize(requests, diskset) - size = lvm.clampPVSize(size, vgreq.pesize) - initsize[req.logicalVolumeName] = size - cursize[req.logicalVolumeName] = size - if req.maxSizeMB: - req.maxSizeMB = lvm.clampPVSize(req.maxSizeMB, vgreq.pesize) - lvmLog.debug("init sizes for %s: %s",req.logicalVolumeName, size) - - # now dolly out free space to all growing LVs - bailcount = 0 - while 1: - nochange = 1 - completed = [] - for req in growreqs: - lvmLog.debug("considering %s, start size = %s",req.logicalVolumeName, req.getStartSize()) - - # get remaining free space - vgfree = lvm.getVGFreeSpace(vgreq, requests, diskset) - - lvmLog.debug("Free space in VG = %s",vgfree) - - # compute fraction of remaining requests this - # particular request represents - totsize = 0.0 - for otherreq in growreqs: - if otherreq in completed: - continue - - lvmLog.debug("adding in %s %s %s", otherreq.logicalVolumeName, otherreq.getStartSize(), otherreq.maxSizeMB) - - size = otherreq.getActualSize(requests, diskset) - if otherreq.maxSizeMB: - if size < otherreq.maxSizeMB: - totsize = totsize + otherreq.getStartSize() - else: - lvmLog.debug("%s is now at %s, and passed maxsize of %s", otherreq.logicalVolumeName, size, otherreq.maxSizeMB) - else: - totsize = totsize + otherreq.getStartSize() - - lvmLog.debug("totsize -> %s",totsize) - - # if totsize is zero we have no growable reqs left - if totsize == 0: - break - - fraction = float(req.getStartSize())/float(totsize) - - newsize = lvm.clampPVSize(vgfree*fraction, vgreq.pesize) - newsize += cursize[req.logicalVolumeName] - - if req.maxSizeMB: - newsize = min(newsize, req.maxSizeMB) - - req.size = newsize - if req.size != cursize[req.logicalVolumeName]: - nochange = 0 - - cursize[req.logicalVolumeName] = req.size - - lvmLog.debug("Name, size, cursize, vgfree, fraction = %s %s %s %s %s", req.logicalVolumeName, req.size, cursize[req.logicalVolumeName], vgfree, fraction) - - completed.append(req) - - if nochange: - lvmLog.info("In growLogicalVolumes, no changes in size so breaking") - break - - bailcount = bailcount + 1 - if bailcount > 10: - lvmLog.info("In growLogicalVolumes, bailing after 10 interations.") - break - -# grow partitions -def growParts(diskset, requests, newParts): - - # returns free space segments for each drive IN SECTORS - def getFreeSpace(diskset): - free = findFreespace(diskset) - freeSize = {} - largestFree = {} - - # find out the amount of free space on each drive - for key in free.keys(): - if len(free[key]) == 0: - del free[key] - continue - freeSize[key] = 0 - largestFree[key] = 0 - for part in free[key]: - sz = part.geometry.length - freeSize[key] += sz - if sz > largestFree[key]: - largestFree[key] = sz - - return (free, freeSize, largestFree) - - #### - # start of growParts - #### - newRequest = requests.copy() - - (free, freeSize, largestFree) = getFreeSpace(diskset) - - # find growable partitions - growable = {} - growSize = {} - origSize = {} - for request in newRequest.requests: - if request.type != REQUEST_NEW or not request.grow: - continue - - origSize[request.uniqueID] = request.requestSize - if not growable.has_key(request.currentDrive): - growable[request.currentDrive] = [ request ] - else: - growable[request.currentDrive].append(request) - - # there aren't any drives with growable partitions, this is easy! - if not growable.keys(): - return - - # loop over all drives, grow all growable partitions one at a time - grownList = [] - for drive in growable.keys(): - # no free space on this drive, so can't grow any of its parts - if not free.has_key(drive): - continue - - # process each request - # grow all growable partitions on this drive until all can grow no more - donegrowing = 0 - outer_iter = 0 - lastFreeSize = None - while not donegrowing and outer_iter < 20: - # if less than one sector left, we're done -# if drive not in freeSize.keys() or freeSize[drive] == lastFreeSize: - if drive not in freeSize.keys(): -# print("leaving outer loop because no more space on %s\n\n" % drive) - break -## print("\nAt start:") -## print(drive,freeSize.keys()) -## print(freeSize[drive], lastFreeSize) -## print("\n") - -## print(diskset.diskState()) - - - outer_iter = outer_iter + 1 - donegrowing = 1 - - # pull out list of requests we want to grow on this drive - growList = growable[drive] - - sectorSize = diskset.disks[drive].device.sectorSize - (cylinders, heads, sectors) = diskset.disks[drive].device.biosGeometry - cylsectors = sectors * heads - - # sort in order of request size, consider biggest first - n = 0 - while n < len(growList): - for request in growList: - if request.size < growList[n].size: - tmp = growList[n] - index = growList.index(request) - growList[n] = request - growList[index] = tmp - n = n + 1 - - # recalculate the total size of growable requests for this drive - # NOTE - we add up the ORIGINAL requested sizes, not grown sizes - growSize[drive] = 0 - for request in growList: - if request.uniqueID in grownList: - continue - growSize[drive] = growSize[drive] + origSize[request.uniqueID] - - thisFreeSize = getFreeSpace(diskset)[1] - # loop over requests for this drive - for request in growList: - # skip if we've finished growing this request - if request.uniqueID in grownList: - continue - - if drive not in freeSize.keys(): - donegrowing = 1 -# print("leaving inner loop because no more space on %s\n\n" % drive) - break - -## print("\nprocessing ID",request.uniqueID, request.mountpoint) -## print("growSize, freeSize = ",growSize[drive], freeSize[drive]) - - donegrowing = 0 - - # get amount of space actually used by current allocation - startSize = 0 - - for drive in request.drive: - part = diskset.disks[drive].getPartitionByPath("/dev/%s" % request.device) - if part: - startSize += part.geometry.length - - # compute fraction of freespace which to give to this - # request. Weight by original request size - percent = origSize[request.uniqueID] / (growSize[drive] * 1.0) - growby = long(percent * thisFreeSize[drive]) - if growby < cylsectors: - growby = cylsectors; - maxsect = startSize + growby - -## print(request) -## print("percent, growby, maxsect, free", percent, growby, maxsect,freeSize[drive], startSize, lastFreeSize) -## print("max is ", maxsect) - - imposedMax = 0 - if request.maxSizeMB: - # round down a cylinder, see comment below - tmpint = request.maxSizeMB*1024.0*1024.0/sectorSize - tmpint = long(tmpint / cylsectors) - maxUserSize = tmpint * cylsectors - if maxsect > maxUserSize: - maxsect = long(maxUserSize) - imposedMax = 1 - - else: - # XXX HACK enforce silent limit for swap otherwise it - # can grow up to 2TB! - if request.fstype.name == "swap": - (xxxint, tmpint) = iutil.swapSuggestion(quiet=1) - - # convert to sectors - tmpint = tmpint*1024*1024/sectorSize - tmpint = long(tmpint / cylsectors) - maxsugswap = tmpint * cylsectors - userstartsize = origSize[request.uniqueID]*1024*1024/sectorSize - if maxsugswap >= userstartsize: - maxsect = maxsugswap - imposedMax = 1 - lvmLog.warning("Enforced max swap size of %s based on suggested max swap", maxsect) - - - # round max fs limit down a cylinder, helps when growing - # so we don't end up with a free cylinder at end if - # maxlimit fell between cylinder boundaries - tmpint = request.fstype.getMaxSizeMB()*1024.0*1024.0/sectorSize - tmpint = long(tmpint / cylsectors) - maxFSSize = tmpint * cylsectors - if maxsect > maxFSSize: - maxsect = long(maxFSSize) - imposedMax = 1 - - maxfree = largestFree[drive] - if maxsect > maxfree + startSize: - maxsect = long(maxfree) + startSize - imposedMax = 1 - -# print("freesize, max, maxfree = ",freeSize[drive],maxsect, maxfree) -# print("freeSizeMB, maxMB = ", freeSize[drive] * sectorSize/(1024.0 * 1024.0), maxsect * sectorSize/(1024.0*1024.0), largestFree[drive] * sectorSize/(1024.0*1024.0)) -# print("startsize = ", startSize) - - min = startSize - max = maxsect - diff = max - min - cur = max - (diff / 2) - lastDiff = 0 - - # binary search -## print("start min, max, cur, diffs = ",min,max,cur,diff,lastDiff) - inner_iter = 0 - ret = PARTITION_SUCCESS # request succeeded with initial size - while (max != min) and (lastDiff != diff) and (inner_iter < 2000): - # XXX need to request in sectors preferably, more accurate -## print("trying cur=%s" % cur) - request.requestSize = (cur*sectorSize)/1024.0/1024.0 - - # try adding - try: - processPartitioning(diskset, newRequest, newParts) - min = cur - except PartitioningError, msg: - ret = PARTITION_FAIL - max = cur -## print("!!!!!!!!!!! processPartitioning failed - %s" % msg) - - lastDiff = diff - diff = max - min - -# print(min, max, diff, cylsectors) -# print(diskset.diskState()) - - cur = max - (diff / 2) - - inner_iter = inner_iter + 1 -# print("sizes at end of loop - cur: %s min:%s max:%s diff:%s lastDiff:%s" % (cur,min,max,diff,lastDiff)) - -# freeSize[drive] = freeSize[drive] - (min - startSize) -# print("shrinking freeSize to ",freeSize[drive], lastFreeSize) -# if freeSize[drive] < 0: -# print("freesize < 0!") -# freeSize[drive] = 0 - - # we could have failed on the last try, in which case we - # should go back to the smaller size - if ret == PARTITION_FAIL: -# print("growing finally failed at size", min) - request.requestSize = min*sectorSize/1024.0/1024.0 - processPartitioning(diskset, newRequest, newParts) - -# print("end min, max, cur, diffs = ",min,max,cur,diff,lastDiff) -# print("%s took %s loops" % (request.mountpoint, inner_iter)) - lastFreeSize = freeSize[drive] - (free, freeSize, largestFree) = getFreeSpace(diskset) -# print(Freespace(free)) - - if ret == PARTITION_FAIL or (max == maxsect and imposedMax): -# print("putting ",request.uniqueID,request.mountpoint," in grownList") - grownList.append(request.uniqueID) - growSize[drive] = growSize[drive] - origSize[request.uniqueID] - if growSize[drive] < 0: -# print("growsize < 0!") - growSize[drive] = 0 - -def setPreexistParts(diskset, requests): - for request in requests: - if request.type != REQUEST_PREEXIST: - continue - - disks = set() - for drive in request.drive: - if not diskset.disks.has_key(drive): - lvmLog.info("pre-existing partition on non-native disk %s, ignoring" %(drive,)) - continue - disks.add(diskset.disks[drive]) - - parts = set() - for disk in list(disks): - for part in disk.partitions: - parts.add(part) - - for part in list(parts): - if part.geometry.start == request.start and part.geometry.end == request.end: - if partedUtils.isEfiSystemPartition(part) and \ - request.fstype.name == "vfat": - request.fstype = fsset.fileSystemTypeGet("efi") - # if the partition is being resized, we do that now - if request.targetSize is not None: - startSec = part.geometry.start - endSec = part.geometry.start + long(((request.targetSize * 1024L * 1024L) / disk.device.sectorSize)) - 1 - - try: - g = copy.deepcopy(part.geometry) - g.end = endSec - constraint = parted.Constraint(exactGeom=g) - part.geometry = g - except Exception, msg: - log.error("error setting geometry for partition %s: %s" %(part.getDeviceNodeName(), msg)) - raise PartitioningError, _("Error resizing partition %s.\n\n%s") %(part.getDeviceNodeName(), msg) - - if startSec != part.geometry.start: - raise PartitioningError, _("Start of partition %s was moved when resizing") %(part.getDeviceNodeName(),) - - request.device = part.getDeviceNodeName() - if request.fstype: - if request.fstype.getName() != request.origfstype.getName(): - if part.isFlagAvailable(parted.PARTITION_RAID): - if request.fstype.getName() == "software RAID": - part.setFlag(parted.PARTITION_RAID) - else: - part.unsetFlag(parted.PARTITION_RAID) - if part.isFlagAvailable(parted.PARTITION_LVM): - if request.fstype.getName() == "physical volume (LVM)": - part.setFlag(parted.PARTITION_LVM) - else: - part.unsetFlag(parted.PARTITION_LVM) - - partedUtils.set_partition_file_system_type(part, request.fstype) - - break - -def deletePart(diskset, delete): - disk = diskset.disks[delete.drive] - for part in disk.partitions: - if part.geometry.start == delete.start and part.geometry.end == delete.end: - disk.deletePartition(part) - return - -def processPartitioning(diskset, requests, newParts): - # collect a hash of all the devices that we have created extended - # partitions on. When we remove these extended partitions the logicals - # (all of which we created) will be destroyed along with it. - extendeds = {} - - for part in newParts.parts: - if part.type == parted.PARTITION_EXTENDED: - extendeds[part.geometry.device.path] = None - - # Go through the list again and check for each logical partition we have. - # If we created the extended partition on the same device as the logical - # partition, remove it from out list, as it will be cleaned up for us - # when the extended partition gets removed. - dellist = [] - for part in newParts.parts: - if (part.type & parted.PARTITION_LOGICAL - and extendeds.has_key(part.geometry.device.path)): - dellist.append(part) - - for part in dellist: - newParts.parts.remove(part) - - # Finally, remove all of the partitions we added in the last try from - # the disks. We'll start again from there. - for part in newParts.parts: - part.disk.deletePartition(part) - - newParts.reset() - - for request in requests.requests: - if request.type == REQUEST_NEW: - request.device = None - - setPreexistParts(diskset, requests.requests) - - # sort requests by size - requests.sortRequests() - - # partitioning algorithm in simplistic terms - # - # we want to allocate partitions such that the most specifically - # spelled out partitions get what they want first in order to ensure - # they don't get preempted. first conflict found returns an error - # which must be handled by the caller by saying that the partition - # add is impossible (XXX can we get an impossible situation after delete?) - # - # potentially confusing terms - # type == primary vs logical - # - # order to allocate: - # start and end cylinders given (note that start + size & !grow is equivalent) - # drive, partnum - # drive, type - # drive - # priority partition (/boot or /) - # size - - # run through with primary only constraints first - try: - fitConstrained(diskset, requests, 1, newParts) - except PartitioningError, msg: - raise PartitioningError, _("Could not allocate cylinder-based partitions as primary partitions.\n") + str(msg) - - try: - fitSized(diskset, requests, 1, newParts) - except PartitioningError, msg: - raise PartitioningError, _("Could not allocate partitions as primary partitions.\n") + str(msg) - - try: - fitConstrained(diskset, requests, 0, newParts) - except PartitioningError, msg: - raise PartitioningError, _("Could not allocate cylinder-based partitions.\n") + str(msg) - - # Don't need to handle the exception here since we leave the message alone. - fitSized(diskset, requests, 0, newParts) - - for request in requests.requests: - # set the unique identifier for raid and lvm devices - if request.type == REQUEST_RAID and not request.device: - request.device = str(request.uniqueID) - if request.type == REQUEST_VG and not request.device: - request.device = str(request.uniqueID) - # anything better we can use for the logical volume? - if request.type == REQUEST_LV and not request.device: - request.device = str(request.uniqueID) - - if not request.device: - raise PartitioningError, "Unsatisfied partition request\n%s" % request - - # get the sizes for raid devices, vgs, and logical volumes - for request in requests.requests: - if request.type == REQUEST_RAID: - request.size = request.getActualSize(requests, diskset) - elif request.type == REQUEST_VG: - request.size = request.getActualSize(requests, diskset) - elif request.type == REQUEST_LV: - if request.grow: - request.setSize(request.getStartSize()) - else: - request.size = request.getActualSize(requests, diskset) - elif request.preexist: - # we need to keep track of the max size of preexisting partitions - # FIXME: we should also get the max size for LVs at some point - if request.maxResizeSize is None: - request.maxResizeSize = 0 - - for drive in request.drive: - part = diskset.disks[drive].getPartitionByPath("/dev/%s" % request.device) - if part: - request.maxResizeSize += part.getMaxAvailableSize(unit="MB") - -## print("disk layout after everything is done") -## print(diskset.diskState()) - -def doPartitioning(diskset, requests, doRefresh = 1): - for request in requests.requests: - request.requestSize = request.size - request.currentDrive = None - - if doRefresh: - diskset.refreshDevices() - # XXX - handle delete requests - for delete in requests.deletes: - if isinstance(delete, partRequests.DeleteSpec): - deletePart(diskset, delete) - # FIXME: do we need to do anything with other types of deletes?? - - newParts = partlist() - - try: - processPartitioning(diskset, requests, newParts) - except PartitioningError, msg: - raise PartitioningError, "Partitioning failed: %s" % msg - - growParts(diskset, requests, newParts) - - newParts.reset() - - for req in requests.getBootableRequest() or []: - ret = bootRequestCheck(req, diskset) - if ret == BOOTALPHA_NOT_BSD: - raise PartitioningWarning, _("Boot partition %s doesn't belong to a BSD disk label. SRM won't be able to boot from this partition. Use a partition belonging to a BSD disk label or change this device disk label to BSD.") %(req.mountpoint,) - elif ret == BOOTALPHA_NO_RESERVED_SPACE: - raise PartitioningWarning, _("Boot partition %s doesn't belong to a disk with enough free space at its beginning for the bootloader to live on. Make sure that there's at least 5MB of free space at the beginning of the disk that contains /boot") %(req.mountpoint,) - elif ret == BOOTEFI_NOT_VFAT: - raise PartitioningError, _("Boot partition %s isn't a VFAT partition. EFI won't be able to boot from this partition.") %(req.mountpoint,) - elif ret == BOOTIPSERIES_TOO_HIGH: - raise PartitioningError, _("The boot partition must entirely be in the first 4GB of the disk. OpenFirmware won't be able to boot this installation.") - elif req == BOOT_NOT_EXT2: - raise PartitioningError, _("Boot partition %s is not a Linux filesystem, such as ext3. The system won't be able to boot from this partition.") %(req.mountpoint,) - elif ret != PARTITION_SUCCESS: - # more specific message? - raise PartitioningWarning, _("Boot partition %s may not meet booting constraints for your architecture.") %(req.mountpoint,) - - # now grow the logical partitions - growLogicalVolumes(diskset, requests) - - # make sure our logical volumes still fit - # - # XXXX should make all this used lvm.getVGFreeSpace() and - # lvm.getVGUsedSpace() at some point - # - - vgused = {} - for request in requests.requests: - if request.type == REQUEST_LV: - size = int(request.getActualSize(requests, diskset, True)) - if vgused.has_key(request.volumeGroup): - vgused[request.volumeGroup] = (vgused[request.volumeGroup] + - size) - else: - vgused[request.volumeGroup] = size - - for vg in vgused.keys(): - request = requests.getRequestByID(vg) - lvmLog.info("Used size vs. available for vg %s: %s %s", request.volumeGroupName, vgused[vg], request.getActualSize(requests, diskset)) - if vgused[vg] > request.getActualSize(requests, diskset): - raise PartitioningError, _("Adding this partition would not " - "leave enough disk space for already " - "allocated logical volumes in " - "%s." % (request.volumeGroupName)) - -# given clearpart specification execute it -# probably want to reset diskset and partition request lists before calling -# this the first time -def doClearPartAction(anaconda, partitions, diskset): - type = partitions.autoClearPartType - cleardrives = partitions.autoClearPartDrives - initAll = partitions.reinitializeDisks - - if type == CLEARPART_TYPE_LINUX: - linuxOnly = 1 - elif type == CLEARPART_TYPE_ALL: - linuxOnly = 0 - elif type == CLEARPART_TYPE_NONE: - return - else: - raise ValueError, "Invalid clear part type in doClearPartAction" - - drives = diskset.disks.keys() - drives.sort() - - for drive in drives: - # skip drives not in clear drive list - if (cleardrives and len(cleardrives) > 0 and not drive in cleardrives) or \ - drive in diskset.skippedDisks: - continue - disk = diskset.disks[drive] - for part in disk.partitions: - if (not part.active or (part.type == parted.PARTITION_EXTENDED) or - (part.disk.type == "mac" and part.number == 1 and part.name == "Apple")): - continue - if part.fileSystem: - ptype = partedUtils.get_partition_file_system_type(part) - else: - ptype = None - # we want to do the clearing if - # 1) clearAll is set - # 2) there's a fsystem on the partition and it's a "native" fs - # 3) there's not fsystem but the numeric id of partition is native - # 4) the ptable doesn't support numeric ids, but it appears to be - # a RAID or LVM device (#107319) - # 5) the drive contains protected partitions and initAll is set - if ((linuxOnly == 0) or (ptype and ptype.isLinuxNativeFS()) or - (initAll and - partedUtils.hasProtectedPartitions(drive, anaconda)) or - (not ptype and - partedUtils.isLinuxNative(part)) or - ((part.fileSystem is None) and # the ptable doesn't have types - ((part.isFlagAvailable(parted.PARTITION_RAID) and part.getFlag(parted.PARTITION_RAID)) or # this is a RAID - (part.isFlagAvailable(parted.PARTITION_LVM) and part.getFlag(parted.PARTITION_LVM)) or # or an LVM - (iutil.isMactel() and not ptype)))): # or we're on a mactel and have a blank partition from bootcamp #FIXME: this could be dangerous... - old = partitions.getRequestByDeviceName(part.getDeviceNodeName()) - if old.getProtected(): - continue - - partitions.deleteDependentRequests(old) - partitions.removeRequest(old) - - drive = partedUtils.get_partition_drive(part) - delete = partRequests.DeleteSpec(drive, part.geometry.start, - part.geometry.end) - partitions.addDelete(delete) - - # EFI autopartitioning is strange as /boot/efi is efi (which is - # really vfat) -- - # if linuxonly and have an msdos partition and it has the - # bootable flag set, do not delete it and make it our - # /boot/efi as it could contain system utils. - # doesn't apply on kickstart installs or if no boot flag - if iutil.isEfi() and linuxOnly == 1 and (not anaconda.isKickstart): - if partedUtils.isEfiSystemPartition(part): - req = partitions.getRequestByDeviceName(part.getDeviceNodeName()) - other = partitions.getRequestByMountPoint("/boot/efi") - - if not other: - req.mountpoint = "/boot/efi" - req.format = 0 - req.fstype = fsset.fileSystemTypeGet("efi") - - request = None - for req in partitions.autoPartitionRequests: - if req.mountpoint == "/boot/efi": - request = req - break - if request: - partitions.autoPartitionRequests.remove(request) - # hey, what do you know, pseries is weird too. *grumble* - elif (((iutil.getPPCMachine() == "pSeries") or - (iutil.getPPCMachine() == "iSeries")) - and (linuxOnly == 1) - and (not anaconda.isKickstart) and - part.isFlagAvailable(parted.PARTITION_BOOT) and - (part.getFlag(parted.PARTITION_PREP)) and - part.getFlag(parted.PARTITION_BOOT)): - req = partitions.getRequestByDeviceName(part.getDeviceNodeName()) - req.mountpoint = None - req.format = 0 - request = None - for req in partitions.autoPartitionRequests: - if req.fstype == fsset.fileSystemTypeGet("PPC PReP Boot"): - request = req - break - if request: - partitions.autoPartitionRequests.remove(request) - - # set the diskset up - try: - doPartitioning(diskset, partitions, doRefresh = 1) - except PartitioningError: # if we get an error here, it isn't overly relevant - pass - - for drive in drives: - if (cleardrives and len(cleardrives) > 0 and not drive in cleardrives) or \ - drive in diskset.skippedDisks: - continue - - disk = diskset.disks[drive] - ext = disk.getExtendedPartition() - # if the extended is empty, blow it away - if ext and len(disk.getLogicalPartitions()) == 0: - delete = partRequests.DeleteSpec(drive, ext.geometry.start, - ext.geometry.end) - old = partitions.getRequestByDeviceName(ext.getDeviceNodeName()) - partitions.removeRequest(old) - partitions.addDelete(delete) - deletePart(diskset, delete) - continue - -def doAutoPartition(anaconda): - instClass = anaconda.id.instClass - diskset = anaconda.id.diskset - partitions = anaconda.id.partitions - - if anaconda.isKickstart: - partitions.setProtected(anaconda.dispatch) - - if anaconda.dir == DISPATCH_BACK: - diskset.refreshDevices() - partitions.setFromDisk(diskset) - partitions.setProtected(anaconda.dispatch) - partitions.autoPartitionRequests = [] - return - - # if no auto partition info in instclass we bail - if len(partitions.autoPartitionRequests) < 1: - #return DISPATCH_NOOP - # 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) - - # reset drive and request info to original state - # XXX only do this if we're dirty -## id.diskset.refreshDevices() -## id.partrequests = PartitionRequests(id.diskset) - doClearPartAction(anaconda, partitions, diskset) - - # XXX clearpartdrives is overloaded as drives we want to use for linux - drives = [] - initial_free = findFreespace(diskset) - initial_free_keys = initial_free.keys() - - if partitions.autoClearPartDrives: - for drive in filter (lambda d: d in initial_free_keys, partitions.autoClearPartDrives): - free = 0 - for f in initial_free[drive]: - size = f.geometry.end - f.geometry.start - # don't count any partition smaller than 1M - if (size > 2048): - free += size - for req in partitions.deletes: - if isinstance(req, partRequests.DeleteSpec) and (drive in req.drive): - size = req.end - req.start - # don't count any partition smaller than 1M - if (size > 2048): - free += size - - # If there's less than 10M free, forget it. - if free > 20480: - drives.append(drive) - del initial_free - - for request in partitions.autoPartitionRequests: - if (isinstance(request, partRequests.PartitionSpec) and - request.device): - # get the preexisting partition they want to use - req = partitions.getRequestByDeviceName(request.device) - if not req or not req.type or req.type != REQUEST_PREEXIST: - anaconda.intf.messageWindow(_("Requested Partition Does Not Exist"), - _("Unable to locate partition %s to use " - "for %s.\n\n" - "Press 'OK' to exit the installer.") - % (request.device, request.mountpoint), - custom_icon='error') - sys.exit(0) - - # now go through and set things from the request to the - # preexisting partition's request... ladeda - if request.mountpoint: - req.mountpoint = request.mountpoint - if request.uniqueID: # for raid to work - req.uniqueID = request.uniqueID - if request.fsopts: - req.fsopts = request.fsopts - if not request.format: - req.format = 0 - else: - req.format = 1 - req.fstype = request.fstype - # XXX whee! lots of cut and paste code lies below - elif (isinstance(request, partRequests.RaidRequestSpec) and - request.preexist == 1): - req = partitions.getRequestByDeviceName(request.device) - if not req or req.preexist == 0: - anaconda.intf.messageWindow(_("Requested Raid Device Does Not Exist"), - _("Unable to locate raid device %s to use " - "for %s.\n\n" - "Press 'OK' to exit the installer.") - % (request.device, - request.mountpoint), - custom_icon='error') - sys.exit(0) - - # now go through and set things from the request to the - # preexisting partition's request... ladeda - if request.mountpoint: - req.mountpoint = request.mountpoint - if request.uniqueID: # for raid to work - req.uniqueID = request.uniqueID - if request.fsopts: - req.fsopts = request.fsopts - if not request.format: - req.format = 0 - else: - req.format = 1 - req.fstype = request.fstype - # XXX not copying the raid bits because they should be handled - # automagically (actually, people probably aren't specifying them) - - elif (isinstance(request, partRequests.VolumeGroupRequestSpec) and - request.preexist == 1): - # get the preexisting partition they want to use - req = partitions.getRequestByVolumeGroupName(request.volumeGroupName) - if not req or req.preexist == 0 or req.format == 1: - anaconda.intf.messageWindow(_("Requested Volume Group Does Not Exist"), - _("Unable to locate volume group %s to use " - "for %s.\n\n" - "Press 'OK' to exit the installer.") - % (request.volumeGroupName, - request.mountpoint), - custom_icon='error') - sys.exit(0) - - oldid = None - # now go through and set things from the request to the - # preexisting partition's request... ladeda - if request.physicalVolumes: - req.physicalVolumes = request.physicalVolumes - if request.pesize: - req.pesize = request.pesize - if request.uniqueID: # for raid to work - oldid = req.uniqueID - req.uniqueID = request.uniqueID - if request.fsopts: - req.fsopts = request.fsopts - if not request.format: - req.format = 0 - else: - req.format = 1 - - # we also need to go through and remap everything which we - # previously found to our new id. yay! - if oldid is not None: - for lv in partitions.getLVMLVForVGID(oldid): - lv.volumeGroup = req.uniqueID - - elif (isinstance(request, partRequests.LogicalVolumeRequestSpec) and - request.preexist == 1): - # get the preexisting partition they want to use - req = partitions.getRequestByLogicalVolumeName(request.logicalVolumeName) - if not req or req.preexist == 0: - anaconda.intf.messageWindow(_("Requested Logical Volume Does Not Exist"), - _("Unable to locate logical volume %s to use " - "for %s.\n\n" - "Press 'OK' to exit the installer.") - % (request.logicalVolumeName, - request.mountpoint), - custom_icon='error') - sys.exit(0) - - # now go through and set things from the request to the - # preexisting partition's request... ladeda - if request.volumeGroup: - req.volumeGroup = request.volumeGroup - if request.mountpoint: - req.mountpoint = request.mountpoint - if request.uniqueID: # for raid to work - req.uniqueID = request.uniqueID - if request.fsopts: - req.fsopts = request.fsopts - if not request.format: - req.format = 0 - else: - req.format = 1 - req.fstype = request.fstype - else: - req = copy.copy(request) - - if req.type == REQUEST_NEW and not req.drive: - req.drive = drives - - # this is kind of a hack, but if we're doing autopart encryption - # and the request is a PV, encrypt it - if partitions.autoEncrypt and req.type == REQUEST_NEW and \ - isinstance(req.fstype, fsset.lvmPhysicalVolumeDummyFileSystem): - req.encryption = cryptodev.LUKSDevice(passphrase=partitions.encryptionPassphrase, format=1) - - # if this is a multidrive request, we need to create one per drive - if req.type == REQUEST_NEW and req.multidrive: - if not req.drive: - req.drive = diskset.disks.keys() - - for drive in req.drive: - r = copy.copy(req) - r.encryption = copy.deepcopy(req.encryption) - r.drive = [ drive ] - partitions.addRequest(r) - continue - - if (isinstance(req, partRequests.VolumeGroupRequestSpec)): - # if the number of physical volumes requested is zero, then - # add _all_ physical volumes we can find - if ((len(req.physicalVolumes) == 0) - or (not req.physicalVolumes)): - req.physicalVolumes = [] - for r in partitions.requests: - if isinstance(r.fstype, - fsset.lvmPhysicalVolumeDummyFileSystem): - valid = 0 - if ((not partitions.autoClearPartDrives) or - len(partitions.autoClearPartDrives) == 0): - valid = 1 - else: - if not isinstance(r, partRequests.RaidRequestSpec): - for d in r.drive: - if d in partitions.autoClearPartDrives: - valid = 1 - break - - if not isinstance(r, partRequests.RaidRequestSpec): - if not r.multidrive: - valid = 0 - - if valid: - req.physicalVolumes.append(r.uniqueID) - # FIXME: this is a hack so that autopartition'd vgs - # can have a unique name - if req.autoname == 1 and req.volumeGroupName == "lvm": - n = lvm.createSuggestedVGName(partitions, anaconda.id.network) - req.volumeGroupName = n - - if (isinstance(req, partRequests.LogicalVolumeRequestSpec)): - # if the volgroup is set to a string, we probably need - # to find that volgroup and use it's id - if type(req.volumeGroup) == type(""): - r = None - if req.volumeGroup == "lvm": - for p in partitions.requests: - if isinstance(p, partRequests.VolumeGroupRequestSpec) and p.autoname == 1: - r = p - break - else: - r = partitions.getRequestByVolumeGroupName(req.volumeGroup) - if r is not None: - req.volumeGroup = r.uniqueID - else: - raise RuntimeError, "Unable to find the volume group for logical volume %s" %(req.logicalVolumeName,) - - partitions.addRequest(req) - - # Remove all preexisting VG requests that reference nonexistant PV - # requests. These VGs should only be present on installs where we're - # using preexisting partitions that already have LVM information. We - # need to do the same thing for preexisting RAID requests, as well. - removeReqs = [] - - for req in partitions.requests: - if isinstance(req, partRequests.VolumeGroupRequestSpec): - lst = req.physicalVolumes - elif isinstance(req, partRequests.RaidRequestSpec): - lst = req.raidmembers - else: - continue - - if len(filter (lambda id: partitions.getRequestByID(id) != None, lst)) == 0: - removeReqs.append(req) - - for req in removeReqs: - partitions.removeRequest(req) - - removeReqs = [] - - # Now that we've removed bad VGs, remove all LVs that would have - # resided on those VGs. - for req in filter (lambda r: isinstance(r, partRequests.LogicalVolumeRequestSpec), partitions.requests): - if partitions.getRequestByID(req.volumeGroup) == None: - removeReqs.append(req) - - for req in removeReqs: - partitions.removeRequest(req) - - # sanity checks for the auto partitioning requests; mostly only useful - # for kickstart as our installclass defaults SHOULD be sane - for req in partitions.requests: - errors = req.sanityCheckRequest(partitions) - if errors: - anaconda.intf.messageWindow(_("Automatic Partitioning Errors"), - _("The following errors occurred with your " - "partitioning:\n\n%s\n\n" - "Press 'OK' to exit the installer.") % - (errors,), custom_icon='error') - sys.exit(0) - - try: - doPartitioning(diskset, partitions, doRefresh = 0) - except PartitioningWarning, 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: - lvmLog.warning(str(msg)) - except PartitioningError, msg: - # restore drives to original state - diskset.refreshDevices() - partitions.setFromDisk(diskset) - partitions.setProtected(anaconda.dispatch) - 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) - - # now do a full check of the requests - (errors, warnings) = partitions.sanityCheckAllRequests(diskset) - if warnings: - for warning in warnings: - lvmLog.warning(warning) - if errors: - errortxt = string.join(errors, '\n') - 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"), - _("Your system will now be rebooted.")) - sys.exit(0) - return DISPATCH_BACK - -def autoCreatePartitionRequests(autoreq): - """Return a list of requests created with a shorthand notation. - - Mainly used by installclasses; make a list of tuples of the form - (mntpt, fstype, minsize, maxsize, grow, format, asvol) - mntpt = None for non-mountable, otherwise is mount point - fstype = None to use default, otherwise a string - minsize = smallest size - maxsize = max size, or None means no max - grow = 0 or 1, should partition be grown - format = 0 or 1, whether to format - asvol = 0 or 1, whether or not it should be a logical volume (ignored) - """ - - requests = [] - for (mntpt, fstype, minsize, maxsize, grow, format, asvol) in autoreq: - if fstype: - ptype = fsset.fileSystemTypeGet(fstype) - else: - ptype = fsset.fileSystemTypeGetDefault() - - newrequest = partRequests.PartitionSpec(ptype, - mountpoint = mntpt, - size = minsize, - maxSizeMB = maxsize, - grow = grow, - format = format) - - requests.append(newrequest) - - return requests - -def autoCreateLVMPartitionRequests(autoreq): - """Return a list of requests created with a shorthand notation using LVM. - - Mainly used by installclasses; make a list of tuples of the form - (mntpt, fstype, minsize, maxsize, grow, format) - mntpt = None for non-mountable, otherwise is mount point - fstype = None to use default, otherwise a string - minsize = smallest size - maxsize = max size, or None means no max - grow = 0 or 1, should partition be grown - format = 0 or 1, whether to format - asvol = 0 or 1, whether or not it should be a logical volume - """ - - requests = [] - nr = partRequests.PartitionSpec(fsset.fileSystemTypeGet("physical volume (LVM)"), - mountpoint = None, - size = 0, - maxSizeMB = None, - grow = 1, - format = 1, - multidrive = 1) - - requests.append(nr) - nr = partRequests.VolumeGroupRequestSpec(fstype = None, - vgname = "lvm", - physvols = [], - format = 1) - nr.autoname = 1 - requests.append(nr) - - volnum = 0 - swapvol = 0 - totalswaps = 0 - - for (mntpt, fstype, minsize, maxsize, grow, format, asvol) in autoreq: - if fsset.fileSystemTypeGet(fstype) == fsset.fileSystemTypeGet("swap"): - totalswaps += 1 - - for (mntpt, fstype, minsize, maxsize, grow, format, asvol) in autoreq: - ptype = fsset.fileSystemTypeGet(fstype) - - if not asvol: - newrequest = partRequests.PartitionSpec(ptype, - mountpoint = mntpt, - size = minsize, - maxSizeMB = maxsize, - grow = grow, - format = format) - else: - # try to incorporate the mount point in to the logical volume name - if mntpt is not None and mntpt != '': - if mntpt == '/': - lvtemplate = 'lv_root' - else: - tmp = lvm.safeLvmName(mntpt) - lvtemplate = "lv_%s" % (tmp,) - else: - if ptype == fsset.fileSystemTypeGet("swap"): - if totalswaps > 1: - lvtemplate = "lv_swap%02d" % (swapvol,) - swapvol += 1 - else: - lvtemplate = "lv_swap" - else: - lvtemplate = "LogVol%02d" % (volnum,) - volnum += 1 - - newrequest = partRequests.LogicalVolumeRequestSpec(ptype, - mountpoint = mntpt, - size = minsize, - maxSizeMB = maxsize, - grow = grow, - format = format, - lvname = "%s" % (lvtemplate,), - volgroup = "lvm") - - requests.append(newrequest) - - return requests - -def getAutopartitionBoot(partitions): - """Return the proper shorthand for the boot dir (arch dependent).""" - fsname = fsset.fileSystemTypeGetDefaultBoot().getName() - if iutil.isEfi(): - ret = [ ["/boot/efi", "efi", 50, 200, 1, 1, 0] ] - for req in partitions.requests: - if req.fstype == fsset.fileSystemTypeGet("efi") \ - and not req.mountpoint: - req.mountpoint = "/boot/efi" - ret = [] - ret.append(("/boot", fsname, 200, None, 0, 1, 0)) - return ret - elif (iutil.getPPCMachine() == "pSeries"): - return [ (None, "PPC PReP Boot", 4, None, 0, 1, 0), - ("/boot", fsname, 200, None, 0, 1, 0) ] - elif (iutil.getPPCMachine() == "iSeries") and not iutil.hasiSeriesNativeStorage(): - return [ (None, "PPC PReP Boot", 16, None, 0, 1, 0) ] - elif (iutil.getPPCMachine() == "iSeries") and iutil.hasiSeriesNativeStorage(): - return [] - elif (iutil.getPPCMachine() == "PMac") and iutil.getPPCMacGen() == "NewWorld": - return [ ( None, "Apple Bootstrap", 1, 1, 0, 1, 0), - ("/boot", fsname, 200, None, 0, 1, 0) ] - else: - return [ ("/boot", fsname, 200, None, 0, 1, 0) ] - - -# XXX hack but these are common strings to TUI and GUI -PARTMETHOD_TYPE_DESCR_TEXT = N_("Automatic Partitioning sets partitions " - "based on the selected installation type. " - "You also " - "can customize the partitions once they " - "have been created.\n\n" - "The manual disk partitioning tool, Disk Druid, " - "allows you " - "to create partitions in an interactive " - "environment. You can set the file system " - "types, mount points, partition sizes, and more.") - -AUTOPART_DISK_CHOICE_DESCR_TEXT = N_("Before automatic partitioning can be " - "set up by the installation program, you " - "must choose how to use the space on " - "your hard drives.") - -CLEARPART_TYPE_ALL_DESCR_TEXT = N_("Remove all partitions on this system") -CLEARPART_TYPE_LINUX_DESCR_TEXT = N_("Remove all Linux partitions on this system") -CLEARPART_TYPE_NONE_DESCR_TEXT = N_("Keep all partitions and use existing free space") diff --git a/backend.py b/backend.py index e8bcf4f5c..680f05884 100644 --- a/backend.py +++ b/backend.py @@ -85,8 +85,10 @@ class AnacondaBackend: # the initrd might need iscsi-initiator-utils, and chances are # it was not installed yet the first time mkinitrd was run, as # mkinitrd does not require it. - for disk in anaconda.id.diskset.disks.keys(): - if isys.driveIsIscsi(disk): + root = anaconda.id.storage.fsset.rootDevice + disks = anaconda.id.storage.devicetree.getDevicesByType("iscsi") + for disk in disks: + if root.dependsOn(disk): has_iscsi_disk = True break @@ -156,8 +158,9 @@ class AnacondaBackend: if not anaconda.mediaDevice or not os.path.exists(installimg): return + free = anaconda.id.storage.fsset.fsFreeSpace(anaconda.rootPath) self._loopbackFile = "%s%s/rhinstall-install.img" % (anaconda.rootPath, - anaconda.id.fsset.filesystemSpace(anaconda.rootPath)[0][0]) + free[0][0]) try: win = anaconda.intf.waitWindow(_("Copying File"), diff --git a/bootloader.py b/bootloader.py index b78cfd701..b9d995e4d 100644 --- a/bootloader.py +++ b/bootloader.py @@ -36,8 +36,7 @@ import logging log = logging.getLogger("anaconda") import booty -import bootloaderInfo -from fsset import * +from booty import bootloaderInfo, checkbootloader def bootloaderSetupChoices(anaconda): if anaconda.dir == DISPATCH_BACK: @@ -60,49 +59,36 @@ def bootloaderSetupChoices(anaconda): anaconda.id.bootloader.updateDriveList(pref) if iutil.isEfi() and not anaconda.id.bootloader.device: - drives = anaconda.id.diskset.disks.keys() - drives.sort() bootPart = None - for drive in drives: - disk = anaconda.id.diskset.disks[drive] - for part in disk.partitions: - if part.active and partedUtils.isEfiSystemPartition(part): - bootPart = part.getDeviceNodeName() + partitions = anaconda.id.storage.partitions + for part in partitions: + if part.partedPartition.active and \ + partedUtils.isEfiSystemPartition(part.partedPartition): + bootPart = part.name break - if bootPart: - break if bootPart: anaconda.id.bootloader.setDevice(bootPart) - dev = Device() - dev.device = bootPart - anaconda.id.fsset.add(FileSystemSetEntry(dev, None, fileSystemTypeGet("efi"))) # iSeries bootloader on upgrades if iutil.getPPCMachine() == "iSeries" and not anaconda.id.bootloader.device: - drives = anaconda.id.diskset.disks.keys() - drives.sort() bootPart = None - for drive in drives: - disk = anaconda.id.diskset.disks[drive] - for part in disk.partitions: - if part.active and part.getFlag(parted.PARTITION_PREP): - bootPart = part.getDeviceNodeName() - break - if bootPart: + partitions = anaconda.id.storage.partitions + for part in partitions: + if part.partedPartition.active and \ + part.partedPartition.getFlag(parted.PARTITION_PREP): + bootPart = part.name break if bootPart: anaconda.id.bootloader.setDevice(bootPart) - dev = Device() - dev.device = bootPart - anaconda.id.fsset.add(FileSystemSetEntry(dev, None, fileSystemTypeGet("PPC PReP Boot"))) - choices = anaconda.id.fsset.bootloaderChoices(anaconda.id.diskset, anaconda.id.bootloader) + choices = anaconda.platform.bootloaderChoices(anaconda.id.bootloader) if not choices and iutil.getPPCMachine() != "iSeries": anaconda.dispatch.skipStep("instbootloader") else: anaconda.dispatch.skipStep("instbootloader", skip = 0) - anaconda.id.bootloader.images.setup(anaconda.id.diskset, anaconda.id.fsset) + # FIXME: ... + anaconda.id.bootloader.images.setup(anaconda.id.storage) if anaconda.id.bootloader.defaultDevice != None and choices: keys = choices.keys() @@ -134,13 +120,12 @@ def writeBootloader(anaconda): # now make the upgrade stuff work for kickstart too. ick. if anaconda.isKickstart and anaconda.id.bootloader.doUpgradeOnly: - import checkbootloader - (bootType, theDev) = checkbootloader.getBootloaderTypeAndBoot(anaconda.rootPath) + (bootType, theDev) = checkbootloader.getBootloaderTypeAndBoot(anaconda.rootPath, storage=anaconda.id.storage) anaconda.id.bootloader.doUpgradeonly = 1 if bootType == "GRUB": anaconda.id.bootloader.useGrubVal = 1 - anaconda.id.bootloader.setDevice(theDev) + anaconda.id.bootloader.setDevice(theDev.split("/")[-1]) else: anaconda.id.bootloader.doUpgradeOnly = 0 @@ -150,21 +135,18 @@ def writeBootloader(anaconda): kernelList = [] otherList = [] - root = anaconda.id.fsset.getEntryByMountPoint('/') - if root: - rootDev = root.device.getDevice() - else: - rootDev = None - defaultDev = anaconda.id.bootloader.images.getDefault() + # getDefault needs to return a device, but that's too invasive for now. + rootDev = anaconda.id.storage.fsset.rootDevice + defaultDev = anaconda.id.storage.devicetree.getDeviceByName(anaconda.id.bootloader.images.getDefault()) kernelLabel = None kernelLongLabel = None for (dev, (label, longlabel, type)) in anaconda.id.bootloader.images.getImages().items(): - if (dev == rootDev) or (rootDev is None and kernelLabel is None): + if (rootDev is None and kernelLabel is None) or (dev == rootDev.name): kernelLabel = label kernelLongLabel = longlabel - elif dev == defaultDev: + elif dev == defaultDev.name: otherList = [(label, longlabel, dev)] + otherList else: otherList.append((label, longlabel, dev)) @@ -193,7 +175,7 @@ def writeBootloader(anaconda): f.write("# UPDATEDEFAULT specifies if new-kernel-pkg should make\n" "# new kernels the default\n") # only update the default if we're setting the default to linux (#156678) - if rootDev == defaultDev: + if rootDev.name == defaultDev.name: f.write("UPDATEDEFAULT=yes\n") else: f.write("UPDATEDEFAULT=no\n") @@ -204,12 +186,12 @@ def writeBootloader(anaconda): dosync() try: - anaconda.id.bootloader.write(anaconda.rootPath, anaconda.id.fsset, anaconda.id.bootloader, - anaconda.id.instLanguage, kernelList, otherList, defaultDev, - justConfigFile, anaconda.intf) + anaconda.id.bootloader.write(anaconda.rootPath, anaconda.id.bootloader, + kernelList, otherList, defaultDev, + justConfigFile) if not justConfigFile: w.pop() - except bootloaderInfo.BootyNoKernelWarning: + except booty.BootyNoKernelWarning: if not justConfigFile: w.pop() if anaconda.intf: @@ -220,10 +202,6 @@ def writeBootloader(anaconda): dosync() -# return instance of the appropriate bootloader for our arch -def getBootloader(): - return booty.getBootloader() - def hasWindows(bl): foundWindows = False for (k,v) in bl.images.getImages().iteritems(): diff --git a/booty/Makefile b/booty/Makefile new file mode 100644 index 000000000..1730985e2 --- /dev/null +++ b/booty/Makefile @@ -0,0 +1,33 @@ +# +# Makefile +# +# Copyright (C) 2009 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +include ../Makefile.inc + +all: + echo "nothing to make" + +install: + mkdir -p $(DESTDIR)/$(PYTHONLIBDIR)/booty + install -p -m 644 *.py $(DESTDIR)/$(PYTHONLIBDIR)/booty + ../py-compile --basedir $(DESTDIR)/$(PYTHONLIBDIR)/booty $(DESTDIR)/$(PYTHONLIBDIR)/booty/*.py + +clean: + rm -f *.o *.so *.pyc + +depend: diff --git a/booty/__init__.py b/booty/__init__.py new file mode 100644 index 000000000..fc34a56e3 --- /dev/null +++ b/booty/__init__.py @@ -0,0 +1,55 @@ +# +# bootloader.py - generic boot loader handling backend for up2date and anaconda +# +# Jeremy Katz <katzj@redhat.com> +# Adrian Likins <alikins@redhat.com> +# Peter Jones <pjones@redhat.com> +# +# Copyright 2001-2005 Red Hat, Inc. +# +# This software may be freely redistributed under the terms of the GNU +# library public license. +# +# 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. +# +"""Module for manipulation and creation of boot loader configurations""" + +import rhpl +from bootloaderInfo import * +from bootloader import * + +class BootyNoKernelWarning: + def __init__ (self, value=""): + self.value = value + + def __str__ (self): + return self.value + +# return instance of the appropriate bootloader for our arch +def getBootloader(storage): + """Get the bootloader info object for your architecture""" + if rhpl.getArch() == 'i386': + import x86 + return x86.x86BootloaderInfo(storage) + elif rhpl.getArch() == 'ia64': + import ia64 + return ia64.ia64BootloaderInfo(storage) + elif rhpl.getArch() == 's390' or rhpl.getArch() == "s390x": + import s390 + return s390.s390BootloaderInfo(storage) + elif rhpl.getArch() == "alpha": + import alpha + return alpha.alphaBootloaderInfo(storage) + elif rhpl.getArch() == "x86_64": + import x86 + return x86.x86BootloaderInfo(storage) + elif rhpl.getArch() == "ppc": + import ppc + return ppc.ppcBootloaderInfo(storage) + elif rhpl.getArch() == "sparc": + import sparc + return sparc.sparcBootloaderInfo(storage) + else: + return bootloaderInfo(storage) diff --git a/booty/alpha.py b/booty/alpha.py new file mode 100644 index 000000000..461086c1e --- /dev/null +++ b/booty/alpha.py @@ -0,0 +1,145 @@ +import os +import iutil + +from booty import BootyNoKernelWarning +from bootloaderInfo import * +from util import getDiskPart + +class alphaBootloaderInfo(bootloaderInfo): + def wholeDevice (self, path): + (device, foo) = getDiskPart(path, self.storage) + return device + + def partitionNum (self, path): + # getDiskPart returns part numbers 0-based; we need it one based + # *sigh* + (foo, partitionNumber) = getDiskPart(path, self.storage) + return partitionNumber + 1 + + def writeAboot(self, instRoot, bl, kernelList, + chainList, defaultDev, justConfig): + rootDevice = self.storage.fsset.rootDevice + try: + bootDevice = self.storage.fsset.mountpoints["/boot"] + except KeyError: + bootDevice = rootDevice + + bootnotroot = bootDevice != rootDevice + + confFile = instRoot + self.configfile + + # If /etc/aboot.conf already exists we rename it + # /etc/aboot.conf.rpmsave. + if os.path.isfile(confFile): + os.rename (confFile, confFile + ".rpmsave") + + # Then we create the necessary files. If the root device isn't + # the boot device, we create /boot/etc/ where the aboot.conf + # will live, and we create /etc/aboot.conf as a symlink to it. + if bootnotroot: + # Do we have /boot/etc ? If not, create one + if not os.path.isdir (instRoot + '/boot/etc'): + os.mkdir(instRoot + '/boot/etc', 0755) + + # We install the symlink (/etc/aboot.conf has already been + # renamed in necessary.) + os.symlink("../boot" + self.configfile, confFile) + + cfPath = instRoot + "/boot" + self.configfile + # Kernel path is set to / because a boot partition will + # be a root on its own. + kernelPath = '/' + # Otherwise, we just need to create /etc/aboot.conf. + else: + cfPath = confFile + kernelPath = self.kernelLocation + + # If we already have an aboot.conf, rename it + if os.access (cfPath, os.R_OK): + self.perms = os.stat(cfPath)[0] & 0777 + os.rename(cfPath, cfPath + '.rpmsave') + + # Now we're going to create and populate cfPath. + f = open (cfPath, 'w+') + f.write ("# aboot default configurations\n") + + if bootnotroot: + f.write ("# NOTICE: You have a /boot partition. This means that\n") + f.write ("# all kernel paths are relative to /boot/\n") + + # bpn is the boot partition number. + bpn = self.partitionNum(bootDevice.path) + lines = 0 + + # We write entries line using the following format: + # <line><bpn><kernel-name> root=<rootdev> [options] + # We get all the kernels we need to know about in kernelList. + + for (kernel, tag, version) in kernelList: + kernelTag = "-" + version + kernelFile = "%svmlinuz%s" %(kernelPath, kernelTag) + + f.write("%d:%d%s" %(lines, bpn, kernelFile)) + + # See if we can come up with an initrd argument that exists + initrd = self.makeInitrd(kernelTag) + if os.path.isfile(instRoot + initrd): + f.write(" initrd=%sinitrd%s.img" %(kernelPath, kernelTag)) + + realroot = rootDevice.fstabSpec + f.write(" root=%s" %(realroot,)) + + args = self.args.get() + if args: + f.write(" %s" %(args,)) + + f.write("\n") + lines = lines + 1 + + # We're done writing the file + f.close () + del f + + if not justConfig: + # Now we're ready to write the relevant boot information. wbd + # is the whole boot device, bdpn is the boot device partition + # number. + wbd = self.wholeDevice (bootDevice.path) + bdpn = self.partitionNum (bootDevice.path) + + # Calling swriteboot. The first argument is the disk to write + # to and the second argument is a path to the bootstrap loader + # file. + args = ("swriteboot", ("/dev/%s" % wbd), "/boot/bootlx") + iutil.execWithRedirect ('/sbin/swriteboot', args, + root = instRoot, + stdout = "/dev/tty5", + stderr = "/dev/tty5") + + # Calling abootconf to configure the installed aboot. The + # first argument is the disk to use, the second argument is + # the number of the partition on which aboot.conf resides. + # It's always the boot partition whether it's / or /boot (with + # the mount point being omitted.) + args = ("abootconf", ("/dev/%s" % wbd), str (bdpn)) + iutil.execWithRedirect ('/sbin/abootconf', args, + root = instRoot, + stdout = "/dev/tty5", + stderr = "/dev/tty5") + + + def write(self, instRoot, bl, kernelList, chainList, + defaultDev, justConfig): + if len(kernelList) < 1: + raise BootyNoKernelWarning + + self.writeAboot(instRoot, bl, kernelList, + chainList, defaultDev, justConfig) + + def __init__(self, storage): + bootloaderInfo.__init__(self, storage) + self.useGrubVal = 0 + self.configfile = "/etc/aboot.conf" + # self.kernelLocation is already set to what we need. + self.password = None + self.pure = None diff --git a/booty/bootloaderInfo.py b/booty/bootloaderInfo.py new file mode 100644 index 000000000..ff9592ecb --- /dev/null +++ b/booty/bootloaderInfo.py @@ -0,0 +1,607 @@ +# +# bootloaderInfo.py - bootloader config object used in creation of new +# bootloader configs. Originally from anaconda +# +# Jeremy Katz <katzj@redhat.com> +# Erik Troan <ewt@redhat.com> +# Peter Jones <pjones@redhat.com> +# +# Copyright 2005-2008 Red Hat, Inc. +# +# This software may be freely redistributed under the terms of the GNU +# library public license. +# +# 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. +# + +import os, sys +import crypt +import random +import shutil +import string +import struct +from copy import copy + +from lilo import LiloConfigFile +import rhpl +from rhpl.translate import _, N_ + +from flags import flags +import iutil +import isys +from product import * + +import booty +import checkbootloader +from util import getDiskPart + +if rhpl.getArch() not in ("s390", "s390x"): + import block + +dosFilesystems = ('FAT', 'fat16', 'fat32', 'ntfs', 'hpfs') + +def doesDualBoot(): + if rhpl.getArch() == "i386" or rhpl.getArch() == "x86_64": + return 1 + return 0 + +def checkForBootBlock(device): + fd = os.open(device, os.O_RDONLY) + buf = os.read(fd, 512) + os.close(fd) + if len(buf) >= 512 and \ + struct.unpack("H", buf[0x1fe: 0x200]) == (0xaa55,): + return True + return False + +# hack and a half +# 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 = "/"): + isys.sync() + isys.sync() + isys.sync() + + # and xfs is even more "special" (#117968) + if isys.readFSType(dev) == "xfs": + iutil.execWithRedirect("/usr/sbin/xfs_freeze", + ["/usr/sbin/xfs_freeze", "-f", mntpt], + stdout = "/dev/tty5", + stderr = "/dev/tty5", + root = instRoot) + iutil.execWithRedirect("/usr/sbin/xfs_freeze", + ["/usr/sbin/xfs_freeze", "-u", mntpt], + stdout = "/dev/tty5", + stderr = "/dev/tty5", + root = instRoot) + +def rootIsDevice(dev): + if dev.startswith("LABEL=") or dev.startswith("UUID="): + return False + return True + +class KernelArguments: + + def get(self): + return self.args + + def set(self, args): + self.args = args + + def chandevget(self): + return self.cargs + + def chandevset(self, args): + self.cargs = args + + def append(self, args): + if self.args: + # don't duplicate the addition of an argument (#128492) + if self.args.find(args) != -1: + return + self.args = self.args + " " + self.args = self.args + "%s" % (args,) + + + def __init__(self): + newArgs = [] + cfgFilename = "/tmp/install.cfg" + + if rhpl.getArch() == "s390": + self.cargs = [] + f = open(cfgFilename) + for line in f: + try: + (vname,vparm) = line.split('=', 1) + vname = vname.strip() + vparm = vparm.replace('"','') + vparm = vparm.strip() + if vname == "DASD": + newArgs.append("dasd=" + vparm) + if vname == "CHANDEV": + self.cargs.append(vparm) + if vname == "QETHPARM": + self.cargs.append(vparm) + except Exception, e: + pass + f.close() + + # look for kernel arguments we know should be preserved and add them + ourargs = ["speakup_synth", "apic", "noapic", "apm", "ide", "noht", + "acpi", "video", "pci", "nodmraid", "nompath"] + for arg in ourargs: + if not flags.cmdline.has_key(arg): + continue + + val = flags.cmdline.get(arg, "") + if val: + newArgs.append("%s=%s" % (arg, val)) + else: + newArgs.append(arg) + + self.args = " ".join(newArgs) + + +class BootImages: + """A collection to keep track of boot images available on the system. + Examples would be: + ('linux', 'Red Hat Linux', 'ext2'), + ('Other', 'Other', 'fat32'), ... + """ + def __init__(self): + self.default = None + self.images = {} + + def getImages(self): + """returns dictionary of (label, longlabel, devtype) pairs + indexed by device""" + # return a copy so users can modify it w/o affecting us + return copy(self.images) + + def setDefault(self, default): + # default is a device + self.default = default + + def getDefault(self): + return self.default + + # Construct a dictionary mapping device names to (OS, product, type) + # tuples. + def setup(self, storage): + devices = {} + bootDevs = self.availableBootDevices(storage) + + for (dev, type) in bootDevs: + devices[dev.name] = 1 + + # These partitions have disappeared + for dev in self.images.keys(): + if not devices.has_key(dev): + del self.images[dev] + + # These have appeared + for (dev, type) in bootDevs: + if not self.images.has_key(dev.name): + if type in dosFilesystems and doesDualBoot(): + self.images[dev.name] = ("Other", "Other", type) + elif type in ("hfs", "hfs+") and rhpl.getPPCMachine() == "PMac": + self.images[dev.name] = ("Other", "Other", type) + else: + self.images[dev.name] = (None, None, type) + + if not self.images.has_key(self.default): + self.default = storage.fsset.rootDevice.name + (label, longlabel, type) = self.images[self.default] + if not label: + self.images[self.default] = ("linux", productName, type) + + # Return a list of (storage.Device, string) tuples that are bootable + # devices. The string is the type of the device, which is just a string + # like "vfat" or "swap" or "lvm". + def availableBootDevices(self, storage): + import parted + retval = [] + foundDos = False + + for part in [p for p in storage.partitions if p.exists]: + # Skip extended, metadata, freespace, etc. + if part.partType not in (parted.PARTITION_NORMAL, parted.PARTITION_LOGICAL) or not part.format: + continue + + type = part.format.type + + if type in dosFilesystems and not foundDos and doesDualBoot(): + try: + bootable = checkForBootBlock(part.path) + retval.append((part, type)) + foundDos = True + except: + pass + elif type in ["ntfs", "hpfs"] and not foundDos and doesDualBoot(): + retval.append((part, type)) + # maybe questionable, but the first ntfs or fat is likely to + # be the correct one to boot with XP using ntfs + foundDos = True + elif type in ["hfs", "hfs+"] and rhpl.getPPCMachine() == "PMac": + if self.partedFlags[parted.PARTITION_BOOT]: + retval.append((part, type)) + + rootDevice = storage.fsset.rootDevice + + if not rootDevice or not rootDevice.format: + raise ValueError, ("Trying to pick boot devices but do not have a " + "sane root partition. Aborting install.") + + retval.append((rootDevice, rootDevice.format.type)) + retval.sort() + return retval + +class bootloaderInfo: + def getConfigFileName(self): + if not self._configname: + raise NotImplementedError + return self._configname + configname = property(getConfigFileName, None, None, \ + "bootloader config file name") + + def getConfigFileDir(self): + if not self._configdir: + raise NotImplementedError + return self._configdir + configdir = property(getConfigFileDir, None, None, \ + "bootloader config file directory") + + def getConfigFilePath(self): + return "%s/%s" % (self.configdir, self.configname) + configfile = property(getConfigFilePath, None, None, \ + "full path and name of the real config file") + + def setUseGrub(self, val): + pass + + def useGrub(self): + return self.useGrubVal + + def setForceLBA(self, val): + pass + + def setPassword(self, val, isCrypted = 1): + pass + + def getPassword(self): + pass + + def getDevice(self): + return self.device + + def setDevice(self, device): + self.device = device + + (dev, part) = getDiskPart(device, self.storage) + if part is None: + self.defaultDevice = "mbr" + else: + self.defaultDevice = "partition" + + def makeInitrd(self, kernelTag): + return "/boot/initrd%s.img" % kernelTag + + def getBootloaderConfig(self, instRoot, bl, kernelList, + chainList, defaultDev): + images = bl.images.getImages() + + confFile = instRoot + self.configfile + + # on upgrade read in the lilo config file + lilo = LiloConfigFile () + self.perms = 0600 + if os.access (confFile, os.R_OK): + self.perms = os.stat(confFile)[0] & 0777 + lilo.read(confFile) + os.rename(confFile, confFile + ".rpmsave") + # if it's an absolute symlink, just get it out of our way + elif (os.path.islink(confFile) and os.readlink(confFile)[0] == '/'): + os.rename(confFile, confFile + ".rpmsave") + + # Remove any invalid entries that are in the file; we probably + # just removed those kernels. + for label in lilo.listImages(): + (fsType, sl, path, other) = lilo.getImage(label) + if fsType == "other": continue + + if not os.access(instRoot + sl.getPath(), os.R_OK): + lilo.delImage(label) + + lilo.addEntry("prompt", replace = 0) + lilo.addEntry("timeout", self.timeout or "20", replace = 0) + + rootDev = self.storage.fsset.rootDevice + + if rootDev.name == defaultDev.name: + lilo.addEntry("default", kernelList[0][0]) + else: + lilo.addEntry("default", chainList[0][0]) + + for (label, longlabel, version) in kernelList: + kernelTag = "-" + version + kernelFile = self.kernelLocation + "vmlinuz" + kernelTag + + try: + lilo.delImage(label) + except IndexError, msg: + pass + + sl = LiloConfigFile(imageType = "image", path = kernelFile) + + initrd = self.makeInitrd(kernelTag) + + sl.addEntry("label", label) + if os.access (instRoot + initrd, os.R_OK): + sl.addEntry("initrd", "%sinitrd%s.img" %(self.kernelLocation, + kernelTag)) + + sl.addEntry("read-only") + + append = "%s" %(self.args.get(),) + realroot = rootDev.fstabSpec + if rootIsDevice(realroot): + sl.addEntry("root", rootDev.path) + else: + if len(append) > 0: + append = "%s root=%s" %(append,realroot) + else: + append = "root=%s" %(realroot,) + + if len(append) > 0: + sl.addEntry('append', '"%s"' % (append,)) + + lilo.addImage (sl) + + for (label, longlabel, device) in chainList: + if ((not label) or (label == "")): + continue + try: + (fsType, sl, path, other) = lilo.getImage(label) + lilo.delImage(label) + except IndexError: + sl = LiloConfigFile(imageType = "other", + path = "/dev/%s" %(device)) + sl.addEntry("optional") + + sl.addEntry("label", label) + lilo.addImage (sl) + + # Sanity check #1. There could be aliases in sections which conflict + # with the new images we just created. If so, erase those aliases + imageNames = {} + for label in lilo.listImages(): + imageNames[label] = 1 + + for label in lilo.listImages(): + (fsType, sl, path, other) = lilo.getImage(label) + if sl.testEntry('alias'): + alias = sl.getEntry('alias') + if imageNames.has_key(alias): + sl.delEntry('alias') + imageNames[alias] = 1 + + # Sanity check #2. If single-key is turned on, go through all of + # the image names (including aliases) (we just built the list) and + # see if single-key will still work. + if lilo.testEntry('single-key'): + singleKeys = {} + turnOff = 0 + for label in imageNames.keys(): + l = label[0] + if singleKeys.has_key(l): + turnOff = 1 + singleKeys[l] = 1 + if turnOff: + lilo.delEntry('single-key') + + return lilo + + def write(self, instRoot, bl, kernelList, chainList, + defaultDev, justConfig): + if len(kernelList) >= 1: + config = self.getBootloaderConfig(instRoot, bl, + kernelList, chainList, + defaultDev) + config.write(instRoot + self.configfile, perms = self.perms) + else: + raise booty.BootyNoKernelWarning + + return "" + + def getArgList(self): + args = [] + + if self.defaultDevice is None: + args.append("--location=none") + return args + + args.append("--location=%s" % (self.defaultDevice,)) + args.append("--driveorder=%s" % (",".join(self.drivelist))) + + if self.args.get(): + args.append("--append=\"%s\"" %(self.args.get())) + + return args + + def writeKS(self, f): + f.write("bootloader") + for arg in self.getArgList(): + f.write(" " + arg) + f.write("\n") + + def updateDriveList(self, sortedList=[]): + self._drivelist = map(lambda x: x.name, self.storage.disks) + self._drivelist.sort(isys.compareDrives) + + # If we're given a sort order, make sure the drives listed in it + # are put at the head of the drivelist in that order. All other + # drives follow behind in whatever order they're found. + if sortedList != []: + revSortedList = sortedList + revSortedList.reverse() + + for i in revSortedList: + try: + ele = self._drivelist.pop(self._drivelist.index(i)) + self._drivelist.insert(0, ele) + except: + pass + + def _getDriveList(self): + if self._drivelist is not None: + return self._drivelist + self.updateDriveList() + return self._drivelist + def _setDriveList(self, val): + self._drivelist = val + drivelist = property(_getDriveList, _setDriveList) + + def __init__(self, storage): + self.args = KernelArguments() + self.images = BootImages() + self.device = None + self.defaultDevice = None # XXX hack, used by kickstart + self.useGrubVal = 0 # only used on x86 + self._configdir = None + self._configname = None + self.kernelLocation = "/boot/" + self.forceLBA32 = 0 + self.password = None + self.pure = None + self.above1024 = 0 + self.timeout = None + self.storage = storage + + # this has somewhat strange semantics. if 0, act like a normal + # "install" case. if 1, update lilo.conf (since grubby won't do that) + # and then run lilo or grub only. + # XXX THIS IS A HACK. implementation details are only there for x86 + self.doUpgradeOnly = 0 + self.kickstart = 0 + + self._drivelist = None + + if flags.serial != 0: + options = "" + device = "" + console = flags.get("console", "") + + # the options are everything after the comma + comma = console.find(",") + if comma != -1: + options = console[comma:] + device = console[:comma] + else: + device = console + + if not device and rhpl.getArch() != "ia64": + self.serialDevice = "ttyS0" + self.serialOptions = "" + else: + self.serialDevice = device + # don't keep the comma in the options + self.serialOptions = options[1:] + + if self.serialDevice: + self.args.append("console=%s%s" %(self.serialDevice, options)) + self.serial = 1 + self.timeout = 5 + else: + self.serial = 0 + self.serialDevice = None + self.serialOptions = None + + if flags.virtpconsole is not None: + if flags.virtpconsole.startswith("/dev/"): + con = flags.virtpconsole[5:] + else: + con = flags.virtpconsole + self.args.append("console=%s" %(con,)) + +class efiBootloaderInfo(bootloaderInfo): + def getBootloaderName(self): + return self._bootloader + bootloader = property(getBootloaderName, None, None, \ + "name of the bootloader to install") + + # XXX wouldn't it be nice to have a real interface to use efibootmgr from? + def removeOldEfiEntries(self, instRoot): + p = os.pipe() + iutil.execWithRedirect('/usr/sbin/efibootmgr', ["efibootmgr"], + root = instRoot, stdout = p[1]) + os.close(p[1]) + + c = os.read(p[0], 1) + buf = c + while (c): + c = os.read(p[0], 1) + buf = buf + c + os.close(p[0]) + lines = string.split(buf, '\n') + for line in lines: + fields = string.split(line) + if len(fields) < 2: + continue + if string.join(fields[1:], " ") == productName: + entry = fields[0][4:8] + iutil.execWithRedirect('/usr/sbin/efibootmgr', + ["efibootmgr", "-b", entry, "-B"], + root = instRoot, + stdout="/dev/tty5", stderr="/dev/tty5") + + def addNewEfiEntry(self, instRoot): + try: + bootdev = storage.fsset.mountpoints["/boot/efi"] + except KeyError: + bootdev = storage.devicetree.getDeviceByName("sda1") + + link = "%s%s/%s" % (instRoot, "/etc/", self.configname) + if not os.access(link, os.R_OK): + os.symlink("../%s" % (self.configfile), link) + + ind = len(bootdev) + try: + while (bootdev[ind-1] in string.digits): + ind = ind - 1 + except IndexError: + ind = len(bootdev) - 1 + + bootdisk = bootdev[:ind] + bootpart = bootdev[ind:] + if (bootdisk.startswith('ida/') or bootdisk.startswith('cciss/') or + bootdisk.startswith('rd/') or bootdisk.startswith('sx8/')): + bootdisk = bootdisk[:-1] + + argv = [ "/usr/sbin/efibootmgr", "-c" , "-w", "-L", + productName, "-d", "/dev/%s" % bootdisk, + "-p", bootpart, "-l", "\\EFI\\redhat\\" + self.bootloader ] + iutil.execWithRedirect(argv[0], argv, root = instRoot, + stdout = "/dev/tty5", + stderr = "/dev/tty5") + + def installGrub(self, instRoot, bootDevs, grubTarget, grubPath, + target, cfPath): + if not iutil.isEfi(): + raise EnvironmentError + self.removeOldEfiEntries(instRoot) + self.addNewEfiEntry(instRoot) + + def __init__(self, storage, initialize = True): + if initialize: + bootloaderInfo.__init__(self, storage) + else: + self.storage = storage + + if iutil.isEfi(): + self._configdir = "/boot/efi/EFI/redhat" + self._configname = "grub.conf" + self._bootloader = "grub.efi" + self.useGrubVal = 1 + self.kernelLocation = "" diff --git a/booty/checkbootloader.py b/booty/checkbootloader.py new file mode 100644 index 000000000..9508e14a6 --- /dev/null +++ b/booty/checkbootloader.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# +# Check to see whether it looks like GRUB or LILO is the boot loader +# being used on the system. +# +# Jeremy Katz <katzj@redhat.com> +# Peter Jones <pjones@redhat.com> +# +# Copyright 2001,2005 Red Hat, Inc. +# +# This software may be freely redistributed under the terms of the GNU +# library public license. +# +# 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. + +import os +import string +import rhpl + +from util import getDiskPart +import iutil + +grubConfigFile = "/etc/grub.conf" +liloConfigFile = "/etc/lilo.conf" +yabootConfigFile = "/etc/yaboot.conf" +siloConfigFile = "/etc/silo.conf" + +def getRaidDisks(raidDevice, storage, raidLevel=None, stripPart=1): + rc = [] + if raidLevel is not None: + try: + raidLevel = "raid%d" % (int(raidLevel),) + except ValueError: + pass + + try: + f = open("/proc/mdstat", "r") + lines = f.readlines() + f.close() + except: + return rc + + for line in lines: + fields = string.split(line, ' ') + if fields[0] == raidDevice: + if raidLevel is not None and fields[3] != raidLevel: + continue + for field in fields[4:]: + if string.find(field, "[") == -1: + continue + dev = string.split(field, '[')[0] + if len(dev) == 0: + continue + if stripPart: + disk = getDiskPart(dev, storage)[0] + rc.append(disk) + else: + rc.append(dev) + + return rc + + +def getBootBlock(bootDev, instRoot, storage, seekBlocks=0): + """Get the boot block from bootDev. Return a 512 byte string.""" + block = " " * 512 + if bootDev is None: + return block + + # get the devices in the raid device + if bootDev[5:7] == "md": + bootDevs = getRaidDisks(bootDev[5:], storage) + bootDevs.sort() + else: + bootDevs = [ bootDev[5:] ] + + # FIXME: this is kind of a hack + # look at all of the devs in the raid device until we can read the + # boot block for one of them. should do this better at some point + # by looking at all of the drives properly + for dev in bootDevs: + try: + fd = os.open("%s/dev/%s" % (instRoot, dev), os.O_RDONLY) + if seekBlocks > 0: + os.lseek(fd, seekBlocks * 512, 0) + block = os.read(fd, 512) + os.close(fd) + return block + except: + pass + return block + +# takes a line like #boot=/dev/hda and returns /dev/hda +# also handles cases like quoted versions and other nonsense +def getBootDevString(line): + dev = string.split(line, '=')[1] + dev = string.strip(dev) + dev = string.replace(dev, '"', '') + dev = string.replace(dev, "'", "") + return dev + +def getBootDevList(line): + devs = string.split(line, '=')[1] + rets = [] + for dev in devs: + dev = getBootDevString("=%s" % (dev,)) + rets.append(dev) + return string.join(rets) + +def getBootloaderTypeAndBoot(instRoot, storage): + haveGrubConf = 1 + haveLiloConf = 1 + haveYabootConf = 1 + haveSiloConf = 1 + + bootDev = None + + # make sure they have the config file, otherwise we definitely can't + # use that bootloader + if not os.access(instRoot + grubConfigFile, os.R_OK): + haveGrubConf = 0 + if not os.access(instRoot + liloConfigFile, os.R_OK): + haveLiloConf = 0 + if not os.access(instRoot + yabootConfigFile, os.R_OK): + haveYabootConf = 0 + if not os.access(instRoot + siloConfigFile, os.R_OK): + haveSiloConf = 0 + + if haveGrubConf: + bootDev = None + for (fn, stanza) in [ ("/etc/sysconfig/grub", "boot="), + (grubConfigFile, "#boot=") ]: + try: + f = open(instRoot + fn, "r") + except: + continue + + # the following bits of code are straight from checkbootloader.py + lines = f.readlines() + f.close() + for line in lines: + if line.startswith(stanza): + bootDev = getBootDevString(line) + break + if bootDev is not None: + break + + if iutil.isEfi(): + return ("GRUB", bootDev) + + if bootDev is not None: + block = getBootBlock(bootDev, instRoot, storage) + # XXX I don't like this, but it's what the maintainer suggested :( + if string.find(block, "GRUB") >= 0: + return ("GRUB", bootDev) + + if haveLiloConf: + f = open(instRoot + liloConfigFile, "r") + lines = f.readlines() + for line in lines: + if line[0:5] == "boot=": + bootDev = getBootDevString(line) + break + + block = getBootBlock(bootDev, instRoot, storage) + # this at least is well-defined + if block[6:10] == "LILO": + return ("LILO", bootDev) + + if haveYabootConf: + f = open(instRoot + yabootConfigFile, "r") + lines = f.readlines() + for line in lines: + if line[0:5] == "boot=": + bootDev = getBootDevList(line) + + if bootDev: + return ("YABOOT", bootDev) + + if haveSiloConf: + bootDev = None + # We've never done the /etc/sysconfig/silo thing, but maybe + # we should start... + for (fn, stanza) in [ ("/etc/sysconfig/silo", "boot="), + (grubConfigFile, "#boot=") ]: + try: + f = open(instRoot + fn, "r") + except: + continue + + lines = f.readlines() + f.close() + for line in lines: + if line.startswith(stanza): + bootDev = getBootDevString(line) + break + if bootDev is not None: + break + + if bootDev is not None: + # XXX SILO sucks just like grub. + if getDiskPart(bootDev, storage)[1] != 3: + block = getBootBlock(bootDev, instRoot, storage, 1) + if block[24:28] == "SILO": + return ("SILO", bootDev) + + return (None, None) diff --git a/booty/ia64.py b/booty/ia64.py new file mode 100644 index 000000000..b9646d3a5 --- /dev/null +++ b/booty/ia64.py @@ -0,0 +1,40 @@ +from booty import BootyNoKernelWarning +from bootloaderInfo import * + +class ia64BootloaderInfo(efiBootloaderInfo): + def getBootloaderConfig(self, instRoot, bl, kernelList, + chainList, defaultDev): + config = bootloaderInfo.getBootloaderConfig(self, instRoot, + bl, kernelList, chainList, + defaultDev) + # altix boxes need relocatable (#120851) + config.addEntry("relocatable") + + return config + + def writeLilo(self, instRoot, bl, kernelList, + chainList, defaultDev, justConfig): + config = self.getBootloaderConfig(instRoot, bl, + kernelList, chainList, defaultDev) + config.write(instRoot + self.configfile, perms = 0755) + + return "" + + def write(self, instRoot, bl, kernelList, chainList, + defaultDev, justConfig): + if len(kernelList) >= 1: + out = self.writeLilo(instRoot, bl, kernelList, + chainList, defaultDev, justConfig) + else: + raise BootyNoKernelWarning + + self.removeOldEfiEntries(instRoot) + self.addNewEfiEntry(instRoot) + + def makeInitrd(self, kernelTag): + return "/boot/efi/EFI/redhat/initrd%s.img" % kernelTag + + def __init__(self, storage): + efiBootloaderInfo.__init__(self, storage) + self._configname = "elilo.conf" + self._bootloader = "elilo.efi" diff --git a/booty/lilo.py b/booty/lilo.py new file mode 100644 index 000000000..dc2328e63 --- /dev/null +++ b/booty/lilo.py @@ -0,0 +1,308 @@ +#!/usr/bin/python +# +# Module for manipulation of lilo.conf files. Original found +# in the anaconda installer +# Copyright (c) 1999-2001 Red Hat, Inc. Distributed under GPL. +# +# Author: Matt Wilson <msw@redhat.com> +# Eric Troan <ewt@redhat.com> +# Adrian Likins <alikins@redhat.com> +"""Module for manipulation of lilo.conf files.""" +import string +import os + +from UserDict import UserDict + + +class UserDictCase(UserDict): + """A dictionary with case insensitive keys""" + def __init__(self, data = {}): + UserDict.__init__(self) + # if we are passed a dictionary transfer it over... + for k in data.keys(): + kl = string.lower(k) + self.data[kl] = data[k] + # some methods used to make the class work as a dictionary + def __setitem__(self, key, value): + key = string.lower(key) + self.data[key] = value + def __getitem__(self, key): + key = string.lower(key) + if not self.data.has_key(key): + return None + return self.data[key] + get = __getitem__ + def __delitem__(self, key): + key = string.lower(key) + del self.data[key] + def has_key(self, key): + key = string.lower(key) + return self.data.has_key(key) + # return this data as a real hash + def get_hash(self): + return self.data + # return the data for marshalling + def __getstate__(self): + return self.data + # we need a setstate because of the __getstate__ presence screws up deepcopy + def __setstate__(self, state): + self.__init__(state) + # get a dictionary out of this instance ({}.update doesn't get instances) + def dict(self): + return self.data + +class LiloConfigFile: + """class representing a lilo.conf lilo configuration file. Used to + manipulate the file directly""" + + def __repr__ (self, tab = 0): + s = "" + for n in self.order: + if (tab): + s = s + '\t' + if n[0] == '#': + s = s + n[1:] + else: + s = s + n + if self.items[n]: + s = s + "=" + self.items[n] + s = s + '\n' + for count in range(len(self.diskRemaps)): + s = s + "disk = %s\n" % self.diskRemaps[count][1] + s = s + "\tbios = %s\n" % self.biosRemaps[count][1] + for cl in self.images: + s = s + "\n%s=%s\n" % (cl.imageType, cl.path) + s = s + cl.__repr__(1) + return s + + def addEntry(self, item, val = None, replace = 1): + if not self.items.has_key(item): + self.order.append(item) + elif not replace: + return + + if (val): + self.items[item] = str(val) + else: + self.items[item] = None + + def getEntry(self, item): + if self.items.has_key(item): + return self.items[item] + else: + return None + + def delEntry(self, item): + newOrder = [] + for i in self.order: + if item != i: newOrder.append(i) + self.order = newOrder + + del self.items[item] + + def listEntries(self): + foo = self.items + return foo + + def testEntry(self, item): + if self.items.has_key(item): + return 1 + else: + return 0 + + def getImage(self, label): + for config in self.images: + # sanity check + if label is None: + break + if config.getEntry('label'): + if string.lower(config.getEntry('label')) == string.lower(label): + return (config.imageType, config, config.path, config.other) + if config.getEntry('alias'): + if string.lower(config.getEntry('alias')) == string.lower(label): + return (config.imageType, config, config.path, config.other) + + + raise IndexError, "unknown image %s" % (label) + + def addImage (self, config,first=None): + # make sure the config has a valid label + config.getEntry('label') + if not config.path or not config.imageType: + raise ValueError, "subconfig missing path or image type" + + if first: + self.images = [config] + self.images + else: + self.images.append(config) + + def delImage (self, label): + for config in self.images: + # sanity check + if label is None: + break + if config.getEntry('label'): + if string.lower(config.getEntry('label')) == string.lower(label): + self.images.remove (config) + return + + raise IndexError, "unknown image %s" % (label,) + + def getDefault (self): + default = None + try: + default = self.getEntry("default") + except: + pass + + if not default: + default = self.listImages()[0] + + theDefault = self.getImage(default) + + return theDefault[1] + + def getDefaultLinux (self): + defaultIsOther = None + + # XXX ick... this code badly needs work =\ + theDefault = self.getDefault() + + if theDefault.other: + defaultIsOther = 1 + + # if the default is other, look for the first linux image + if theDefault.other: + for image_label in self.listImages(): + image = self.getImage(image_label)[1] + if not image.other: + theDefault = image + break + + # if we get here and are *still* an other, then we have no linux + # images. ick + if theDefault.other: + return None + else: + return theDefault + + def listImages (self): + l = [] + for config in self.images: + l.append(config.getEntry('label')) + return l + + def listAliases (self): + l = [] + for config in self.images: + if config.getEntry('alias'): + l.append(config.getEntry('alias')) + return l + + def getPath (self): + return self.path + + def write(self, file, perms = 0644): + f = open(file, "w") + f.write(self.__repr__()) + f.close() + os.chmod(file, perms) + + def read (self, file): + f = open(file, "r") + image = None + for l in f.readlines(): + l = l[:-1] + orig = l + while (l and (l[0] == ' ' or l[0] == '\t')): + l = l[1:] + if not l: + continue + if l[0] == '#' and not image: + self.order.append('#' + orig) + continue + fields = string.split(l, '=', 1) + if l[0] == '#' and image: + args = ('#' + l,) + elif (len(fields) == 2): + f0 = string.strip (fields [0]) + f1 = string.strip (fields [1]) + if (f0 != "append"): + # people are silly and put quotes brokenly in their + # lilo.conf but you have to use them for append. ARGH! + f1 = string.replace(f1, '"', '') + f1 = string.replace(f1, "'", "") + if (f0 == "image" or f0 == "other"): + if image: self.addImage(image) + image = LiloConfigFile(imageType = f0, + path = f1) + if (f0 == "other"): + image.other = 1 + args = None + else: + args = (f0, f1) + if (f0 == "disk"): + self.diskRemaps.append((f0,f1)) + args = None + if (f0 == "bios"): + self.biosRemaps.append((f0,f1)) + args = None + + else: + args = (string.strip (l),) + + if (args and image): + apply(image.addEntry, args) + elif args: + apply(self.addEntry, args) + + if image: self.addImage(image) + + f.close() + + def __init__(self, imageType = None, path = None): + self.imageType = imageType + self.path = path + self.order = [] + self.images = [] + self.other = None + self.items = UserDictCase() + self.biosRemaps = [] + self.diskRemaps = [] + self.unsupported = [] + + +if __name__ == "__main__": + import sys + #sys.path.append("") + config = LiloConfigFile () + config.read ('/etc/lilo.conf') + print config + print "image list", config.listImages() + config.delImage ('linux') + print '----------------------------------' + config = LiloConfigFile () + config.read ('/etc/lilo.conf') + print config + print '----------------------------------' + print '----------------------------------' + print "list images" + print config.listImages() + print config.getImage('linux') + print "----------------------------------" + print "addimage (testlinux)" + blip = """ +read-only +blippy-blob=sdfsdf +append=\"sdfasdfasdf\" +root=/dev/hda6 +""" + sl = LiloConfigFile(imageType = "image", path="/boot/somevmlinuz-2.4.0") + sl.addEntry("label", "newkernel") + sl.addEntry("initrd", "blipppy") + config.addImage(sl) + + print '-------------------------------------' + print "writing out /tmp/lilo.conf" + print config.write("/tmp/lilo.conf") + print config diff --git a/booty/ppc.py b/booty/ppc.py new file mode 100644 index 000000000..706f1989b --- /dev/null +++ b/booty/ppc.py @@ -0,0 +1,179 @@ +import string +import os + +from booty import BootyNoKernelWarning +from util import getDiskPart +from bootloaderInfo import * +import iutil +import rhpl + +class ppcBootloaderInfo(bootloaderInfo): + def getBootDevs(self, bl): + import parted + + retval = [] + machine = rhpl.getPPCMachine() + + if machine == 'pSeries': + for dev in self.storage.fsset.devices: + if dev.partedFlags[parted.PARTITION_PREP] and not dev.format.exists: + retval.append(dev.path) + elif machine == 'PMac': + for dev in self.storage.fsset.devices: + if dev.format.type == "hfs" and dev.format.bootable and not dev.format.exists: + retval.append(dev.path) + + if len(retval) == 0: + # Try to get a boot device; bplan OF understands ext3 + if machine == 'Pegasos' or machine == 'Efika': + try: + device = self.storage.fsset.mountpoints["/boot"] + except KeyError: + # Try / if we don't have this we're not going to work + device = self.storage.fsset.rootDevice + + retval.append(device.path) + else: + if bl.getDevice(): + retval.append(bl.getDevice().path) + + return retval + + def writeYaboot(self, instRoot, bl, kernelList, + chainList, defaultDev, justConfigFile): + + yabootTarget = string.join(self.getBootDevs(bl)) + + try: + bootDev = self.storage.fsset.mountpoints["/boot"] + + cf = "/boot/etc/yaboot.conf" + cfPath = "" + if not os.path.isdir(instRoot + "/boot/etc"): + os.mkdir(instRoot + "/boot/etc") + except KeyError: + bootDevice = self.storage.fsset.rootDevice + + cfPath = "/boot" + cf = "/etc/yaboot.conf" + + f = open(instRoot + cf, "w+") + + f.write("# yaboot.conf generated by anaconda\n\n") + f.write("boot=%s\n" %(yabootTarget,)) + f.write("init-message=\"Welcome to %s!\\nHit <TAB> for boot options\"\n\n" + % productName) + + (name, partNum) = getDiskPart(bootDev, self.storage) + partno = partNum + 1 # 1 based + + f.write("partition=%s\n" %(partno,)) + + f.write("timeout=%s\n" % (self.timeout or 80)) + f.write("install=/usr/lib/yaboot/yaboot\n") + f.write("delay=5\n") + f.write("enablecdboot\n") + f.write("enableofboot\n") + f.write("enablenetboot\n") + + yabootProg = "/sbin/mkofboot" + if rhpl.getPPCMachine() == "PMac": + # write out the first hfs/hfs+ partition as being macosx + for (label, longlabel, device) in chainList: + if ((not label) or (label == "")): + continue + f.write("macosx=/dev/%s\n" %(device,)) + break + + f.write("magicboot=/usr/lib/yaboot/ofboot\n") + + elif rhpl.getPPCMachine() == "pSeries": + f.write("nonvram\n") + f.write("fstype=raw\n") + + else: # Default non-destructive case for anything else. + f.write("nonvram\n") + f.write("mntpoint=/boot/yaboot\n") + f.write("usemount\n") + if not os.access(instRoot + "/boot/yaboot", os.R_OK): + os.mkdir(instRoot + "/boot/yaboot") + yabootProg = "/sbin/ybin" + + if self.password: + f.write("password=%s\n" %(self.password,)) + f.write("restricted\n") + + f.write("\n") + + rootDev = self.storage.fsset.rootDevice + + for (label, longlabel, version) in kernelList: + kernelTag = "-" + version + kernelFile = "%s/vmlinuz%s" %(cfPath, kernelTag) + + f.write("image=%s\n" %(kernelFile,)) + f.write("\tlabel=%s\n" %(label,)) + f.write("\tread-only\n") + + initrd = self.makeInitrd(kernelTag) + if os.access(instRoot + initrd, os.R_OK): + f.write("\tinitrd=%s/initrd%s.img\n" %(cfPath,kernelTag)) + + append = "%s" %(self.args.get(),) + + realroot = rootDev.fstabSpec + if rootIsDevice(realroot): + f.write("\troot=%s\n" %(realroot,)) + else: + if len(append) > 0: + append = "%s root=%s" %(append,realroot) + else: + append = "root=%s" %(realroot,) + + if len(append) > 0: + f.write("\tappend=\"%s\"\n" %(append,)) + f.write("\n") + + f.close() + os.chmod(instRoot + cf, 0600) + + # FIXME: hack to make sure things are written to disk + import isys + isys.sync() + isys.sync() + isys.sync() + + ybinargs = [ yabootProg, "-f", "-C", cf ] + + if not flags.test: + iutil.execWithRedirect(ybinargs[0], + ybinargs, + stdout = "/dev/tty5", + stderr = "/dev/tty5", + root = instRoot) + + if (not os.access(instRoot + "/etc/yaboot.conf", os.R_OK) and + os.access(instRoot + "/boot/etc/yaboot.conf", os.R_OK)): + os.symlink("../boot/etc/yaboot.conf", + instRoot + "/etc/yaboot.conf") + + return "" + + def setPassword(self, val, isCrypted = 1): + # yaboot just handles the password and doesn't care if its crypted + # or not + self.password = val + + def write(self, instRoot, bl, kernelList, chainList, + defaultDev, justConfig): + if len(kernelList) >= 1: + out = self.writeYaboot(instRoot, bl, kernelList, + chainList, defaultDev, justConfig) + else: + raise BootyNoKernelWarning + + def __init__(self, storage): + bootloaderInfo.__init__(self, storage) + self.useYabootVal = 1 + self.kernelLocation = "/boot" + self.configfile = "/etc/yaboot.conf" diff --git a/booty/s390.py b/booty/s390.py new file mode 100644 index 000000000..575ab2813 --- /dev/null +++ b/booty/s390.py @@ -0,0 +1,174 @@ +import os + +from bootloaderInfo import * +import iutil + +class s390BootloaderInfo(bootloaderInfo): + def getBootloaderConfig(self, instRoot, bl, kernelList, + chainList, defaultDev): + # on upgrade read in the lilo config file + lilo = LiloConfigFile () + self.perms = 0600 + confFile = instRoot + self.configfile + + if os.access (confFile, os.R_OK): + self.perms = os.stat(confFile)[0] & 0777 + lilo.read(confFile) + os.rename(confFile, confFile + ".rpmsave") + + # Remove any invalid entries that are in the file; we probably + # just removed those kernels. + for label in lilo.listImages(): + (fsType, sl, path, other) = lilo.getImage(label) + if fsType == "other": continue + + if not os.access(instRoot + sl.getPath(), os.R_OK): + lilo.delImage(label) + + rootDev = self.storage.fsset.rootDevice + + if rootDev.name == defaultDev.name: + lilo.addEntry("default", kernelList[0][0]) + else: + lilo.addEntry("default", chainList[0][0]) + + for (label, longlabel, version) in kernelList: + kernelTag = "-" + version + kernelFile = self.kernelLocation + "vmlinuz" + kernelTag + + try: + lilo.delImage(label) + except IndexError, msg: + pass + + sl = LiloConfigFile(imageType = "image", path = kernelFile) + + initrd = self.makeInitrd(kernelTag) + + sl.addEntry("label", label) + if os.access (instRoot + initrd, os.R_OK): + sl.addEntry("initrd", + "%sinitrd%s.img" %(self.kernelLocation, kernelTag)) + + sl.addEntry("read-only") + sl.addEntry("root", rootDev.path) + sl.addEntry("ipldevice", rootDev.path[:-1]) + + if self.args.get(): + sl.addEntry('append', '"%s"' % self.args.get()) + + lilo.addImage (sl) + + for (label, longlabel, device) in chainList: + if ((not label) or (label == "")): + continue + try: + (fsType, sl, path, other) = lilo.getImage(label) + lilo.delImage(label) + except IndexError: + sl = LiloConfigFile(imageType = "other", + path = "/dev/%s" %(device)) + sl.addEntry("optional") + + sl.addEntry("label", label) + lilo.addImage (sl) + + # Sanity check #1. There could be aliases in sections which conflict + # with the new images we just created. If so, erase those aliases + imageNames = {} + for label in lilo.listImages(): + imageNames[label] = 1 + + for label in lilo.listImages(): + (fsType, sl, path, other) = lilo.getImage(label) + if sl.testEntry('alias'): + alias = sl.getEntry('alias') + if imageNames.has_key(alias): + sl.delEntry('alias') + imageNames[alias] = 1 + + # Sanity check #2. If single-key is turned on, go through all of + # the image names (including aliases) (we just built the list) and + # see if single-key will still work. + if lilo.testEntry('single-key'): + singleKeys = {} + turnOff = 0 + for label in imageNames.keys(): + l = label[0] + if singleKeys.has_key(l): + turnOff = 1 + singleKeys[l] = 1 + if turnOff: + lilo.delEntry('single-key') + + return lilo + + def writeChandevConf(self, bl, instroot): # S/390 only + cf = "/etc/chandev.conf" + self.perms = 0644 + if bl.args.chandevget(): + fd = os.open(instroot + "/etc/chandev.conf", + os.O_WRONLY | os.O_CREAT) + os.write(fd, "noauto\n") + for cdev in bl.args.chandevget(): + os.write(fd,'%s\n' % cdev) + os.close(fd) + return "" + + + def writeZipl(self, instRoot, bl, kernelList, chainList, + defaultDev, justConfigFile): + rootDev = self.storage.fsset.rootDevice + + cf = '/etc/zipl.conf' + self.perms = 0600 + if os.access (instRoot + cf, os.R_OK): + self.perms = os.stat(instRoot + cf)[0] & 0777 + os.rename(instRoot + cf, + instRoot + cf + '.rpmsave') + + f = open(instRoot + cf, "w+") + + f.write('[defaultboot]\n') + f.write('default=' + kernelList[0][0] + '\n') + f.write('target=%s\n' % (self.kernelLocation)) + + cfPath = "/boot/" + for (label, longlabel, version) in kernelList: + kernelTag = "-" + version + kernelFile = "%svmlinuz%s" % (cfPath, kernelTag) + + initrd = self.makeInitrd(kernelTag) + f.write('[%s]\n' % (label)) + f.write('\timage=%s\n' % (kernelFile)) + if os.access (instRoot + initrd, os.R_OK): + f.write('\tramdisk=%sinitrd%s.img\n' %(self.kernelLocation, + kernelTag)) + realroot = rootDev.fstabSpec + f.write('\tparameters="root=%s' %(realroot,)) + if bl.args.get(): + f.write(' %s' % (bl.args.get())) + f.write('"\n') + + f.close() + + if not justConfigFile: + argv = [ "/sbin/zipl" ] + iutil.execWithRedirect(argv[0], argv, root = instRoot, + stdout = "/dev/stdout", + stderr = "/dev/stderr") + + return "" + + def write(self, instRoot, bl, kernelList, chainList, + defaultDev, justConfig): + out = self.writeZipl(instRoot, bl, kernelList, + chainList, defaultDev, + justConfig | (not self.useZiplVal)) + out = self.writeChandevConf(bl, instRoot) + + def __init__(self, storage): + bootloaderInfo.__init__(self, storage) + self.useZiplVal = 1 # only used on s390 + self.kernelLocation = "/boot/" + self.configfile = "/etc/zipl.conf" diff --git a/booty/sparc.py b/booty/sparc.py new file mode 100644 index 000000000..b094a4512 --- /dev/null +++ b/booty/sparc.py @@ -0,0 +1,127 @@ +import os + +from booty import BootyNoKernelWarning +from bootloaderInfo import * + +class sparcBootloaderInfo(bootloaderInfo): + def writeSilo(self, instRoot, bl, kernelList, + chainList, defaultDev, justConfigFile): + + try: + bootDev = self.storage.fsset.mountpoints["/boot"] + + mf = '/silo.message' + cf = "/boot/silo.conf" + mfdir = '/boot' + cfPath = "" + if not os.path.isdir(instRoot + "/boot"): + os.mkdir(instRoot + "/boot") + except KeyError: + bootDev = self.storage.fsset.rootDevice + + cf = "/etc/silo.conf" + mfdir = '/etc' + cfPath = "/boot" + + f = open(instRoot + mfdir + mf, "w+") + f.write("Welcome to %s!\nHit <TAB> for boot options\n\n" % productName) + f.close() + os.chmod(instRoot + mfdir + mf, 0600) + + f = open(instRoot + cf, "w+") + f.write("# silo.conf generated by anaconda\n\n") + + f.write("#boot=%s\n" % (bootDev.path,)) + f.write("message=%s\n" % (mf,)) + f.write("timeout=%s\n" % (self.timeout or 50)) + + (name, partNum) = getDiskPart(bootDev, self.storage) + partno = partNum + 1 + f.write("partition=%s\n" % (partno,)) + + if self.password: + f.write("password=%s\n" % (self.password,)) + f.write("restricted\n") + + f.write("default=%s\n" % (kernelList[0][0],)) + f.write("\n") + + rootDev = self.storage.fsset.rootDevice + + for (label, longlabel, version) in kernelList: + kernelTag = "-" + version + kernelFile = "%s/vmlinuz%s" % (cfPath, kernelTag) + + f.write("image=%s\n" % (kernelFile,)) + f.write("\tlabel=%s\n" % (label,)) + f.write("\tread-only\n") + + initrd = self.makeInitrd(kernelTag) + if os.access(instRoot + initrd, os.R_OK): + f.write("\tinitrd=%s/initrd%s.img\n" % (cfPath, kernelTag)) + + append = "%s" % (self.args.get(),) + + realroot = rootDev.fstabSpec + if rootIsDevice(realroot): + f.write("\troot=%s\n" % (realroot,)) + else: + if len(append) > 0: + append = "%s root=%s" % (append, realroot) + else: + append = "root=%s" % (realroot,) + + if len(append) > 0: + f.write("\tappend=\"%s\"\n" % (append,)) + f.write("\n") + + f.close() + os.chmod(instRoot + cf, 0600) + + # FIXME: hack to make sure things are written to disk + import isys + isys.sync() + isys.sync() + isys.sync() + + backup = "%s/backup.b" % (cfPath,) + sbinargs = ["/sbin/silo", "-f", "-C", cf, "-S", backup] + # TODO!!! FIXME!!! XXX!!! + # butil is not defined!!! - assume this is in rhpl now? + if butil.getSparcMachine() == "sun4u": + sbinargs += ["-u"] + else: + sbinargs += ["-U"] + + if not flags.test: + iutil.execWithRedirect(sbinargs[0], + sbinargs, + stdout = "/dev/tty5", + stderr = "/dev/tty5", + root = instRoot) + + if (not os.access(instRoot + "/etc/silo.conf", os.R_OK) and + os.access(instRoot + "/boot/etc/silo.conf", os.R_OK)): + os.symlink("../boot/etc/silo.conf", + instRoot + "/etc/silo.conf") + + return "" + + def setPassword(self, val, isCrypted = 1): + # silo just handles the password unencrypted + self.password = val + + def write(self, instRoot, bl, kernelList, chainList, + defaultDev, justConfig): + if len(kernelList) >= 1: + self.writeSilo(instRoot, bl, kernelList, chainList, + defaultDev, justConfig) + else: + raise BootyNoKernelWarning + + def __init__(self, storage): + bootloaderInfo.__init__(self, storage) + self.useSiloVal = 1 + self.kernelLocation = "/boot" + self._configdir = "/etc" + self._configname = "silo.conf" diff --git a/booty/util.py b/booty/util.py new file mode 100644 index 000000000..74ba561ed --- /dev/null +++ b/booty/util.py @@ -0,0 +1,34 @@ +import string + +def getDiskPart(dev, storage): + path = storage.devicetree.getDeviceByName(dev).path[5:] + cut = len(dev) + if (path.startswith('rd/') or path.startswith('ida/') or + path.startswith('cciss/') or path.startswith('sx8/') or + path.startswith('mapper/') or path.startswith('mmcblk')): + if dev[-2] == 'p': + cut = -1 + elif dev[-3] == 'p': + cut = -2 + else: + if dev[-2] in string.digits: + cut = -2 + elif dev[-1] in string.digits: + cut = -1 + + name = dev[:cut] + + # hack off the trailing 'p' from /dev/cciss/*, for example + if name[-1] == 'p': + for letter in name: + if letter not in string.letters and letter != "/": + name = name[:-1] + break + + if cut < 0: + partNum = int(dev[cut:]) - 1 + else: + partNum = None + + return (name, partNum) + diff --git a/booty/x86.py b/booty/x86.py new file mode 100644 index 000000000..d710fbb8e --- /dev/null +++ b/booty/x86.py @@ -0,0 +1,563 @@ +import os +import string + +from booty import BootyNoKernelWarning +from util import getDiskPart +from bootloaderInfo import * +import checkbootloader +import iutil +import rhpl + +class x86BootloaderInfo(efiBootloaderInfo): + def setPassword(self, val, isCrypted = 1): + if not val: + self.password = val + self.pure = val + return + + if isCrypted and self.useGrubVal == 0: + self.pure = None + return + elif isCrypted: + self.password = val + self.pure = None + else: + salt = "$1$" + saltLen = 8 + + saltchars = string.letters + string.digits + './' + for i in range(saltLen): + salt += random.choice(saltchars) + + self.password = crypt.crypt(val, salt) + self.pure = val + + def getPassword (self): + return self.pure + + def setForceLBA(self, val): + self.forceLBA32 = val + + def setUseGrub(self, val): + self.useGrubVal = val + + def getPhysicalDevices(self, device): + # This finds a list of devices on which the given device name resides. + # Accepted values for "device" are raid1 md devices (i.e. "md0"), + # physical disks ("hda"), and real partitions on physical disks + # ("hda1"). Volume groups/logical volumes are not accepted. + path = self.storage.devicetree.getDeviceByName(device).path[5:] + + if device in map (lambda x: x.name, self.storage.lvs + self.storage.vgs): + return [] + + if path.startswith("mapper/luks-"): + return [] + + if path.startswith('md'): + bootable = 0 + parts = checkbootloader.getRaidDisks(device, self.storage, + raidLevel=1, stripPart=0) + parts.sort() + return parts + + return [device] + + def runGrubInstall(self, instRoot, bootDev, cmds, cfPath): + if cfPath == "/": + syncDataToDisk(bootDev, "/boot", instRoot) + else: + syncDataToDisk(bootDev, "/", instRoot) + + # copy the stage files over into /boot + iutil.execWithRedirect("/sbin/grub-install", + ["/sbin/grub-install", "--just-copy"], + stdout = "/dev/tty5", stderr = "/dev/tty5", + root = instRoot) + + # really install the bootloader + for cmd in cmds: + p = os.pipe() + os.write(p[1], cmd + '\n') + os.close(p[1]) + + # FIXME: hack to try to make sure everything is written + # to the disk + if cfPath == "/": + syncDataToDisk(bootDev, "/boot", instRoot) + else: + syncDataToDisk(bootDev, "/", instRoot) + + iutil.execWithRedirect('/sbin/grub' , + [ "grub", "--batch", "--no-floppy", + "--device-map=/boot/grub/device.map" ], + stdin = p[0], + stdout = "/dev/tty5", stderr = "/dev/tty5", + root = instRoot) + os.close(p[0]) + + def installGrub(self, instRoot, bootDevs, grubTarget, grubPath, + target, cfPath): + if iutil.isEfi(): + efiBootloaderInfo.installGrub(self, instRoot, bootDevs, grubTarget, + grubPath, target, cfPath) + return + + args = "--stage2=/boot/grub/stage2 " + if self.forceLBA32: + args = "%s--force-lba " % (args,) + + cmds = [] + for bootDev in bootDevs: + gtPart = self.getMatchingPart(bootDev, grubTarget) + gtDisk = self.grubbyPartitionName(getDiskPart(gtPart, self.storage)[0]) + bPart = self.grubbyPartitionName(bootDev) + cmd = "root %s\n" % (bPart,) + + stage1Target = gtDisk + if target == "partition": + stage1Target = self.grubbyPartitionName(gtPart) + + cmd += "install %s%s/stage1 d %s %s/stage2 p %s%s/grub.conf" % \ + (args, grubPath, stage1Target, grubPath, bPart, grubPath) + cmds.append(cmd) + + self.runGrubInstall(instRoot, bootDev, cmds, cfPath) + + def writeGrub(self, instRoot, bl, kernelList, chainList, + defaultDev, justConfigFile): + + rootDev = self.storage.fsset.rootDevice + + # XXX old config file should be read here for upgrade + + cf = "%s%s" % (instRoot, self.configfile) + self.perms = 0600 + if os.access (cf, os.R_OK): + self.perms = os.stat(cf)[0] & 0777 + os.rename(cf, cf + '.rpmsave') + + grubTarget = bl.getDevice() + path = self.storage.devicetree.getDeviceByName(grubTarget).path[5:] + target = "mbr" + if (path.startswith('rd/') or path.startswith('ida/') or + path.startswith('cciss/') or + path.startswith('sx8/') or + path.startswith('mapper/')): + if grubTarget[-1].isdigit(): + if grubTarget[-2] == 'p' or \ + (grubTarget[-2].isdigit() and grubTarget[-3] == 'p'): + target = "partition" + elif grubTarget[-1].isdigit() and not path.startswith('md'): + target = "partition" + + f = open(cf, "w+") + + f.write("# grub.conf generated by anaconda\n") + f.write("#\n") + f.write("# Note that you do not have to rerun grub " + "after making changes to this file\n") + + try: + bootDev = self.storage.fsset.mountpoints["/boot"] + grubPath = "/grub" + cfPath = "/" + f.write("# NOTICE: You have a /boot partition. This means " + "that\n") + f.write("# all kernel and initrd paths are relative " + "to /boot/, eg.\n") + except KeyError: + bootDev = self.storage.fsset.rootDevice + grubPath = "/boot/grub" + cfPath = "/boot/" + f.write("# NOTICE: You do not have a /boot partition. " + "This means that\n") + f.write("# all kernel and initrd paths are relative " + "to /, eg.\n") + + bootDevs = self.getPhysicalDevices(bootDev.name) + + f.write('# root %s\n' % self.grubbyPartitionName(bootDevs[0])) + f.write("# kernel %svmlinuz-version ro root=%s\n" % (cfPath, rootDev.path)) + f.write("# initrd %sinitrd-version.img\n" % (cfPath)) + f.write("#boot=/dev/%s\n" % (grubTarget)) + + # get the default image to boot... we have to walk and find it + # since grub indexes by where it is in the config file + if defaultDev.name == rootDev.name: + default = 0 + else: + # if the default isn't linux, it's the first thing in the + # chain list + default = len(kernelList) + + # keep track of which devices are used for the device.map + usedDevs = {} + + f.write('default=%s\n' % (default)) + f.write('timeout=%d\n' % (self.timeout or 0)) + + if self.serial == 1: + # grub the 0-based number of the serial console device + unit = self.serialDevice[-1] + + # and we want to set the speed too + speedend = 0 + for char in self.serialOptions: + if char not in string.digits: + break + speedend = speedend + 1 + if speedend != 0: + speed = self.serialOptions[:speedend] + else: + # reasonable default + speed = "9600" + + f.write("serial --unit=%s --speed=%s\n" %(unit, speed)) + f.write("terminal --timeout=%s serial console\n" % (self.timeout or 5)) + else: + # we only want splashimage if they're not using a serial console + if os.access("%s/boot/grub/splash.xpm.gz" %(instRoot,), os.R_OK): + f.write('splashimage=%s%sgrub/splash.xpm.gz\n' + % (self.grubbyPartitionName(bootDevs[0]), cfPath)) + f.write("hiddenmenu\n") + + for dev in self.getPhysicalDevices(grubTarget): + usedDevs[dev] = 1 + + if self.password: + f.write('password --md5 %s\n' %(self.password)) + + for (label, longlabel, version) in kernelList: + kernelTag = "-" + version + kernelFile = "%svmlinuz%s" % (cfPath, kernelTag) + + initrd = self.makeInitrd(kernelTag) + + f.write('title %s (%s)\n' % (longlabel, version)) + f.write('\troot %s\n' % self.grubbyPartitionName(bootDevs[0])) + + realroot = " root=%s" % rootDev.fstabSpec + + if version.endswith("xen0") or (version.endswith("xen") and not os.path.exists("/proc/xen")): + # hypervisor case + sermap = { "ttyS0": "com1", "ttyS1": "com2", + "ttyS2": "com3", "ttyS3": "com4" } + if self.serial and sermap.has_key(self.serialDevice) and \ + self.serialOptions: + hvs = "%s=%s" %(sermap[self.serialDevice], + self.serialOptions) + else: + hvs = "" + if version.endswith("xen0"): + hvFile = "%sxen.gz-%s %s" %(cfPath, + version.replace("xen0", ""), + hvs) + else: + hvFile = "%sxen.gz-%s %s" %(cfPath, + version.replace("xen", ""), + hvs) + f.write('\tkernel %s\n' %(hvFile,)) + f.write('\tmodule %s ro%s' %(kernelFile, realroot)) + if self.args.get(): + f.write(' %s' % self.args.get()) + f.write('\n') + + if os.access (instRoot + initrd, os.R_OK): + f.write('\tmodule %sinitrd%s.img\n' % (cfPath, kernelTag)) + else: # normal kernel + f.write('\tkernel %s ro%s' % (kernelFile, realroot)) + if self.args.get(): + f.write(' %s' % self.args.get()) + f.write('\n') + + if os.access (instRoot + initrd, os.R_OK): + f.write('\tinitrd %sinitrd%s.img\n' % (cfPath, kernelTag)) + + for (label, longlabel, device) in chainList: + if ((not longlabel) or (longlabel == "")): + continue + f.write('title %s\n' % (longlabel)) + f.write('\trootnoverify %s\n' % self.grubbyPartitionName(device)) +# f.write('\tmakeactive\n') + f.write('\tchainloader +1') + f.write('\n') + usedDevs[device] = 1 + + f.close() + + if not "/efi/" in cf: + os.chmod(cf, self.perms) + + try: + # make symlink for menu.lst (default config file name) + menulst = "%s%s/menu.lst" % (instRoot, self.configdir) + if os.access (menulst, os.R_OK): + os.rename(menulst, menulst + ".rpmsave") + os.symlink("./grub.conf", menulst) + except: + pass + + try: + # make symlink for /etc/grub.conf (config files belong in /etc) + etcgrub = "%s%s" % (instRoot, "/etc/grub.conf") + if os.access (etcgrub, os.R_OK): + os.rename(etcgrub, etcgrub + ".rpmsave") + os.symlink(".." + self.configfile, etcgrub) + except: + pass + + for dev in self.getPhysicalDevices(rootDev.name) + bootDevs: + usedDevs[dev] = 1 + + if os.access(instRoot + "/boot/grub/device.map", os.R_OK): + os.rename(instRoot + "/boot/grub/device.map", + instRoot + "/boot/grub/device.map.rpmsave") + + f = open(instRoot + "/boot/grub/device.map", "w+") + f.write("# this device map was generated by anaconda\n") + devs = usedDevs.keys() + usedDevs = {} + for dev in devs: + drive = getDiskPart(dev, self.storage)[0] + if usedDevs.has_key(drive): + continue + usedDevs[drive] = 1 + devs = usedDevs.keys() + devs.sort() + for drive in devs: + # XXX hack city. If they're not the sort of thing that'll + # be in the device map, they shouldn't still be in the list. + path = self.storage.devicetree.getDeviceByName(drive).path + if not drive.startswith('md'): + f.write("(%s) %s\n" % (self.grubbyDiskName(drive), path)) + f.close() + + sysconf = '/etc/sysconfig/grub' + if os.access (instRoot + sysconf, os.R_OK): + self.perms = os.stat(instRoot + sysconf)[0] & 0777 + os.rename(instRoot + sysconf, + instRoot + sysconf + '.rpmsave') + # if it's an absolute symlink, just get it out of our way + elif (os.path.islink(instRoot + sysconf) and + os.readlink(instRoot + sysconf)[0] == '/'): + os.rename(instRoot + sysconf, + instRoot + sysconf + '.rpmsave') + f = open(instRoot + sysconf, 'w+') + f.write("boot=/dev/%s\n" %(grubTarget,)) + # XXX forcelba never gets read back... + if self.forceLBA32: + f.write("forcelba=1\n") + else: + f.write("forcelba=0\n") + f.close() + + if not justConfigFile: + self.installGrub(instRoot, bootDevs, grubTarget, grubPath, \ + target, cfPath) + + return "" + + def getMatchingPart(self, bootDev, target): + bootName, bootPartNum = getDiskPart(bootDev, self.storage) + devices = self.getPhysicalDevices(target) + for device in devices: + name, partNum = getDiskPart(device, self.storage) + if name == bootName: + return device + return devices[0] + + def grubbyDiskName(self, name): + return "hd%d" % self.drivelist.index(name) + + def grubbyPartitionName(self, dev): + (name, partNum) = getDiskPart(dev, self.storage) + if partNum != None: + return "(%s,%d)" % (self.grubbyDiskName(name), partNum) + else: + return "(%s)" %(self.grubbyDiskName(name)) + + + def getBootloaderConfig(self, instRoot, bl, kernelList, + chainList, defaultDev): + config = bootloaderInfo.getBootloaderConfig(self, instRoot, + bl, kernelList, chainList, + defaultDev) + + liloTarget = bl.getDevice() + + config.addEntry("boot", '/dev/' + liloTarget, replace = 0) + config.addEntry("map", "/boot/map", replace = 0) + config.addEntry("install", "/boot/boot.b", replace = 0) + message = "/boot/message" + + if self.pure is not None and not self.useGrubVal: + config.addEntry("restricted", replace = 0) + config.addEntry("password", self.pure, replace = 0) + + if self.serial == 1: + # grab the 0-based number of the serial console device + unit = self.serialDevice[-1] + # FIXME: we should probably put some options, but lilo + # only supports up to 9600 baud so just use the defaults + # it's better than nothing :( + config.addEntry("serial=%s" %(unit,)) + else: + # message screws up serial console + if os.access(instRoot + message, os.R_OK): + config.addEntry("message", message, replace = 0) + + if not config.testEntry('lba32'): + if self.forceLBA32 or (bl.above1024 and + rhpl.getArch() != "x86_64"): + config.addEntry("lba32", replace = 0) + + return config + + # this is a hackish function that depends on the way anaconda writes + # out the grub.conf with a #boot= comment + # XXX this falls into the category of self.doUpgradeOnly + def upgradeGrub(self, instRoot, bl, kernelList, chainList, + defaultDev, justConfigFile): + if justConfigFile: + return "" + + theDev = None + for (fn, stanza) in [ ("/etc/sysconfig/grub", "boot="), + ("/boot/grub/grub.conf", "#boot=") ]: + try: + f = open(instRoot + fn, "r") + except: + continue + + # the following bits of code are straight from checkbootloader.py + lines = f.readlines() + f.close() + for line in lines: + if line.startswith(stanza): + theDev = checkbootloader.getBootDevString(line) + break + if theDev is not None: + break + + if theDev is None: + # we could find the dev before, but can't now... cry about it + return "" + + # migrate info to /etc/sysconfig/grub + self.writeSysconfig(instRoot, theDev) + + # more suckage. grub-install can't work without a valid /etc/mtab + # so we have to do shenanigans to get updated grub installed... + # steal some more code above + try: + bootDev = self.storage.fsset.mountpoints["/boot"].name + grubPath = "/grub" + cfPath = "/" + except KeyError: + bootDev = self.storage.fsset.rootDevice.name + grubPath = "/boot/grub" + cfPath = "/boot/" + + masterBootDev = bootDev + if masterBootDev[0:2] == 'md': + rootDevs = checkbootloader.getRaidDisks(masterBootDev, + self.storage, raidLevel=1, stripPart=0) + else: + rootDevs = [masterBootDev] + + if theDev[5:7] == 'md': + stage1Devs = checkbootloader.getRaidDisks(theDev[5:], self.storage, + raidLevel=1) + else: + stage1Devs = [theDev[5:]] + + for stage1Dev in stage1Devs: + # cross fingers; if we can't find a root device on the same + # hardware as this boot device, we just blindly hope the first + # thing in the list works. + + grubbyStage1Dev = self.grubbyPartitionName(stage1Dev) + + grubbyRootPart = self.grubbyPartitionName(rootDevs[0]) + + for rootDev in rootDevs: + testGrubbyRootDev = getDiskPart(rootDev, self.storage)[0] + testGrubbyRootDev = self.grubbyPartitionName(testGrubbyRootDev) + + if grubbyStage1Dev == testGrubbyRootDev: + grubbyRootPart = self.grubbyPartitionName(rootDev) + break + + args = "--stage2=/boot/grub/stage2 " + cmd ="root %s" % (grubbyRootPart,) + cmds = [ cmd ] + cmd = "install %s%s/stage1 d %s %s/stage2 p %s%s/grub.conf" \ + % (args, grubPath, grubbyStage1Dev, grubPath, grubbyRootPart, + grubPath) + cmds.append(cmd) + + if not justConfigFile: + self.runGrubInstall(instRoot, bootDev, cmds, cfPath) + + return "" + + def writeSysconfig(self, instRoot, installDev): + sysconf = '/etc/sysconfig/grub' + if not os.access(instRoot + sysconf, os.R_OK): + f = open(instRoot + sysconf, "w+") + f.write("boot=%s\n" %(installDev,)) + # XXX forcelba never gets read back at all... + if self.forceLBA32: + f.write("forcelba=1\n") + else: + f.write("forcelba=0\n") + f.close() + + def write(self, instRoot, bl, kernelList, chainList, + defaultDev, justConfig): + if self.timeout is None and chainList: + self.timeout = 5 + + # XXX HACK ALERT - see declaration above + if self.doUpgradeOnly: + if self.useGrubVal: + self.upgradeGrub(instRoot, bl, kernelList, + chainList, defaultDev, justConfig) + return + + if len(kernelList) < 1: + raise BootyNoKernelWarning + + out = self.writeGrub(instRoot, bl, kernelList, + chainList, defaultDev, + justConfig | (not self.useGrubVal)) + + # XXX move the lilo.conf out of the way if they're using GRUB + # so that /sbin/installkernel does a more correct thing + if self.useGrubVal and os.access(instRoot + '/etc/lilo.conf', os.R_OK): + os.rename(instRoot + "/etc/lilo.conf", + instRoot + "/etc/lilo.conf.anaconda") + + + def getArgList(self): + args = bootloaderInfo.getArgList(self) + + if self.forceLBA32: + args.append("--lba32") + if self.password: + args.append("--md5pass=%s" %(self.password)) + + return args + + def __init__(self, storage): + bootloaderInfo.__init__(self, storage) + efiBootloaderInfo.__init__(self, storage, initialize=False) + + self._configdir = "/boot/grub" + self._configname = "grub.conf" + # XXX use checkbootloader to determine what to default to + self.useGrubVal = 1 + self.kernelLocation = "/boot/" + self.password = None + self.pure = None diff --git a/cmdline.py b/cmdline.py index 7b925c9cf..f87550c46 100644 --- a/cmdline.py +++ b/cmdline.py @@ -114,10 +114,6 @@ class InstallInterface: pass def run(self, anaconda): - anaconda.id.fsset.registerMessageWindow(self.messageWindow) - anaconda.id.fsset.registerProgressWindow(self.progressWindow) - anaconda.id.fsset.registerWaitWindow(self.waitWindow) - (step, instance) = anaconda.dispatch.currentStep() while step: if stepToClasses.has_key(step): diff --git a/command-stubs/list-harddrives-stub b/command-stubs/list-harddrives-stub index a282984d6..ceb60368d 100755 --- a/command-stubs/list-harddrives-stub +++ b/command-stubs/list-harddrives-stub @@ -2,7 +2,7 @@ # # scan system for harddrives and output device name/size # -# Copyright (C) 2007 Red Hat, Inc. All rights reserved. +# Copyright (C) 2007, 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 @@ -18,42 +18,20 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # -import os import sys -# for testing -if (os.path.exists('isys')): - sys.path.append('isys') - sys.path.append('/usr/lib/anaconda') -import anaconda_log -import parted -import partedUtils -import isys - -drives = isys.hardDriveDict() - -driveList = drives.keys() -driveList.sort() - -for drive in driveList: - if not isys.mediaPresent(drive): - continue - - # try to open and get size - skip = 0 - deviceFile = "/dev/%s" % (drive,) +import minihal - try: - dev = parted.PedDevice.get(deviceFile) - except: - skip = 1 +lst = [] - if skip: - continue +for drive in minihal.get_devices_by_type("volume"): + if not drive.has_key("device") or not drive.has_key("volume.size"): + continue - sizeMB = (float(dev.heads * dev.cylinders * dev.sectors) / (1024 * 1024) - * dev.sectorSize) + lst.append("%s %s" % (drive["device"], drive["volume_size"]/(1024*1024))) - print drive, sizeMB +lst.sort() +for entry in lst: + print lst diff --git a/cryptodev.py b/cryptodev.py deleted file mode 100644 index 63dcd4a63..000000000 --- a/cryptodev.py +++ /dev/null @@ -1,256 +0,0 @@ -# -# 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 <http://www.gnu.org/licenses/>. -# -# Author(s): Dave Lehman <dlehman@redhat.com> -# - -import os -import iutil - -import logging -log = logging.getLogger("anaconda") - -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 hasPassphrase(self): - return self.__passphrase not in (None, "") - - 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.hasPassphrase(): - 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.hasPassphrase(): - 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/dispatch.py b/dispatch.py index 22015823a..d6fbcce2a 100644 --- a/dispatch.py +++ b/dispatch.py @@ -24,18 +24,17 @@ import string from types import * from constants import * from packages import writeKSConfiguration, turnOnFilesystems -from packages import doMigrateFilesystems from packages import doPostAction from packages import copyAnacondaLogs -from autopart import doAutoPartition from packages import firstbootConfiguration from packages import betaNagScreen from packages import setupTimezone from packages import setFileCons from packages import regKeyScreen from packages import writeRegKey -from partitions import partitionObjectsInitialize -from partitions import partitioningComplete +from storage import storageInitialize +from storage import storageComplete +from storage.partitioning import doAutoPartition from bootloader import writeBootloader, bootloaderSetupChoices from flags import flags from upgrade import upgradeMountFilesystems, queryUpgradeArch @@ -73,13 +72,13 @@ installSteps = [ ("keyboard", ), ("betanag", betaNagScreen, ), ("regkey", regKeyScreen, ), + ("storageinit", storageInitialize, ), ("findrootparts", findRootParts, ), ("findinstall", ), ("network", ), ("timezone", ), ("accounts", ), ("setuptime", setupTimezone, ), - ("partitionobjinit", partitionObjectsInitialize, ), ("parttype", ), ("autopartitionexecute", doAutoPartition, ), ("partition", ), @@ -90,8 +89,7 @@ installSteps = [ ("addswap", ), ("upgrademigfind", upgradeMigrateFind, ), ("upgrademigratefs", ), - ("partitiondone", partitioningComplete, ), - ("migratefilesystems", doMigrateFilesystems, ), + ("storagedone", storageComplete, ), ("enablefilesystems", turnOnFilesystems, ), ("upgbootloader", ), ("bootloadersetup", bootloaderSetupChoices, ), diff --git a/exception.py b/exception.py index 7b6fdea9d..ddf82b517 100644 --- a/exception.py +++ b/exception.py @@ -35,7 +35,6 @@ import inspect import iutil import types import bdb -import partedUtils from string import joinfields from cPickle import Pickler from flags import flags @@ -183,11 +182,11 @@ class AnacondaExceptionDump: "anaconda.id.instLanguage.tz", "anaconda.id.keyboard._mods._modelDict", "anaconda.id.keyboard.modelDict", - "anaconda.id.partitions.encryptionPassphrase", + "anaconda.id.storage.encryptionPassphrase", "anaconda.id.rootPassword", "anaconda.id.tmpData", "anaconda.intf.icw.buff", - "anaconda.intf.icw.currentWindow.partitions.encryptionPassphrase", + "anaconda.intf.icw.currentWindow.storage.encryptionPassphrase", "anaconda.intf.icw.stockButtons", "dispatch.sack.excludes", ] @@ -223,7 +222,7 @@ class AnacondaExceptionDump: for file in ("/tmp/syslog", "/tmp/anaconda.log", "/tmp/lvmout", "/tmp/resize.out", - "/tmp/program.log", + "/tmp/program.log", "/tmp/storage.log", anaconda.rootPath + "/root/install.log", anaconda.rootPath + "/root/upgrade.log"): try: @@ -36,6 +36,12 @@ class Flags: else: raise AttributeError, attr + def get(self, attr, val=None): + if self.__dict__['flags'].has_key(attr): + return self.__dict__['flags'][attr] + else: + return val + def createCmdlineDict(self): cmdlineDict = {} cmdline = open("/proc/cmdline", "r").read().strip() diff --git a/fsset.py b/fsset.py deleted file mode 100644 index 79ef2dd71..000000000 --- a/fsset.py +++ /dev/null @@ -1,3149 +0,0 @@ -# -# fsset.py: filesystem management -# -# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 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 <http://www.gnu.org/licenses/>. -# -# Author(s): Matt Wilson <msw@redhat.com> -# Jeremy Katz <katzj@redhat.com> -# - -import math -import string -import isys -import iutil -import os -import resource -import posix -import stat -import errno -import parted -import sys -import struct -import partitions -import partedUtils -import raid -import lvm -import time -import types -from flags import flags -from constants import * - -import gettext -_ = lambda x: gettext.ldgettext("anaconda", x) - -import logging -log = logging.getLogger("anaconda") - -class SuspendError(Exception): - pass - -class OldSwapError(Exception): - pass - -class ResizeError(Exception): - pass - -defaultMountPoints = ['/', '/boot', '/home', '/tmp', '/usr', '/var', '/usr/local', '/opt'] - -if iutil.isS390(): - # Many s390 have 2G DASDs, we recomment putting /usr/share on its own DASD - defaultMountPoints.insert(5, '/usr/share') - -if iutil.isEfi(): - defaultMountPoints.insert(2, '/boot/efi') - -fileSystemTypes = {} - -def fileSystemTypeGetDefault(): - if fileSystemTypeGet('ext4').isSupported(): - return fileSystemTypeGet('ext4') - if fileSystemTypeGet('ext3').isSupported(): - return fileSystemTypeGet('ext3') - elif fileSystemTypeGet('ext2').isSupported(): - return fileSystemTypeGet('ext2') - else: - raise ValueError, "None of ext4, ext3 or ext2 is supported in your kernel!" - -def fileSystemTypeGetDefaultBoot(): - if fileSystemTypeGet('ext3').isSupported(): - return fileSystemTypeGet('ext3') - elif fileSystemTypeGet('ext2').isSupported(): - return fileSystemTypeGet('ext2') - else: - raise ValueError, "You have neither ext3 or ext2 support in your kernel!" - -def fileSystemTypeGet(key): - if fileSystemTypes.has_key(key): - return fileSystemTypes[key] - else: - return fileSystemTypeGetDefault() - -def fileSystemTypeRegister(klass): - fileSystemTypes[klass.getName()] = klass - -def fileSystemTypeGetTypes(): - return fileSystemTypes.copy() - -def getUsableLinuxFs(): - rc = [] - for fsType in fileSystemTypes.keys(): - if fileSystemTypes[fsType].isMountable() and \ - fileSystemTypes[fsType].isLinuxNativeFS(): - rc.append(fsType) - - # make sure the default is first in the list, kind of ugly - default = fileSystemTypeGetDefault() - defaultName = default.getName() - if defaultName in rc: - del rc[rc.index(defaultName)] - rc.insert(0, defaultName) - return rc - -def devify(device): - if device in ["proc", "devpts", "sysfs", "tmpfs"] or device.find(":") != -1: - return device - elif device == "sys": - return "sysfs" - elif device == "shm": - return "tmpfs" - elif device == "spufs": - return "spufs" - elif device != "none" and device[0] != '/': - return "/dev/" + device - else: - return device - -class FileSystemType: - kernelFilesystems = {} - lostAndFoundContext = None - - def __init__(self): - self.deviceArguments = {} - self.formattable = 0 - self.checked = 0 - self.name = "" - self.linuxnativefs = 0 - self.fusefs = 0 - self.partedFileSystemType = None - self.partedPartitionFlags = [] - self.maxSizeMB = 8 * 1024 * 1024 - self.supported = -1 - self.defaultOptions = "defaults" - self.migratetofs = None - self.extraFormatArgs = [] - self.maxLabelChars = 16 - self.packages = [] - self.needProgram = [] - self.resizable = False - self.supportsFsProfiles = False - self.fsProfileSpecifier = None - self.fsprofile = None - self.bootable = False - - def createLabel(self, mountpoint, maxLabelChars, kslabel = None): - # If a label was specified in the kickstart file, return that as the - # label. - if kslabel: - return kslabel - - if len(mountpoint) > maxLabelChars: - return mountpoint[0:maxLabelChars] - else: - return mountpoint - - def isBootable(self): - return self.bootable - - def isResizable(self): - return self.resizable - def resize(self, entry, size, progress, chroot='/'): - pass - def getMinimumSize(self, device): - log.warning("Unable to determinine minimal size for %s", device) - return 1 - - def isKernelFS(self): - """Returns True if this is an in-kernel pseudo-filesystem.""" - return False - - def mount(self, device, mountpoint, readOnly=0, bindMount=0, - instroot=""): - if not self.isMountable(): - return - iutil.mkdirChain("%s/%s" %(instroot, mountpoint)) - if flags.selinux: - ret = isys.resetFileContext(mountpoint, instroot) - log.info("set SELinux context for mountpoint %s to %s" %(mountpoint, ret)) - log.debug("mounting %s on %s/%s as %s" %(device, instroot, - mountpoint, self.getMountName())) - isys.mount(device, "%s/%s" %(instroot, mountpoint), - fstype = self.getMountName(), - readOnly = readOnly, bindMount = bindMount, - options = self.defaultOptions) - - if flags.selinux: - ret = isys.resetFileContext(mountpoint, instroot) - log.info("set SELinux context for newly mounted filesystem root at %s to %s" %(mountpoint, ret)) - if FileSystemType.lostAndFoundContext is None: - FileSystemType.lostAndFoundContext = \ - isys.matchPathContext("/lost+found") - isys.setFileContext("%s/lost+found" % (mountpoint,), - FileSystemType.lostAndFoundContext, instroot) - - def umount(self, device, path): - isys.umount(path, removeDir = 0) - - def getName(self, quoted = 0): - """Return the name of the filesystem. Set quoted to 1 if this - should be quoted (ie, it's not for display).""" - if quoted: - if self.name.find(" ") != -1: - return "\"%s\"" %(self.name,) - return self.name - - def getMountName(self, quoted = 0): - return self.getName(quoted) - - def getNeededPackages(self): - return self.packages - - def registerDeviceArgumentFunction(self, klass, function): - self.deviceArguments[klass] = function - - def formatDevice(self, entry, progress, chroot='/'): - if self.isFormattable(): - raise RuntimeError, "formatDevice method not defined" - - def migrateFileSystem(self, device, message, chroot='/'): - if self.isMigratable(): - raise RuntimeError, "migrateFileSystem method not defined" - - def labelDevice(self, entry, chroot): - pass - - def clobberDevice(self, entry, chroot): - pass - - def isFormattable(self): - return self.formattable - - def isLinuxNativeFS(self): - return self.linuxnativefs - - def setFsProfile(self, fsprofile=None): - if not self.supportsFsProfiles: - raise RuntimeError, "%s does not support profiles" % (self,) - self.fsprofile = fsprofile - - def getFsProfileArgs(self): - if not self.supportsFsProfiles: - raise RuntimeError, "%s does not support profiles" % (self,) - args = None - if self.fsprofile: - args = [] - if self.fsProfileSpecifier: - args.extend(self.fsProfileSpecifier) - args.extend(self.fsprofile) - return args - - def readProcFilesystems(self): - f = open("/proc/filesystems", 'r') - if not f: - pass - lines = f.readlines() - for line in lines: - fields = string.split(line) - if fields[0] == "nodev": - fsystem = fields[1] - else: - fsystem = fields[0] - FileSystemType.kernelFilesystems[fsystem] = None - - def isMountable(self): - if not FileSystemType.kernelFilesystems: - self.readProcFilesystems() - if self.fusefs: - return FileSystemType.kernelFilesystems.has_key("fuse") - - return FileSystemType.kernelFilesystems.has_key(self.getMountName()) or self.getName() == "auto" - - def isSupported(self): - # check to ensure we have the binaries they need - for p in self.needProgram: - if len(filter(lambda d: os.path.exists("%s/%s" %(d, p)), - os.environ["PATH"].split(":"))) == 0: - return False - - if self.supported == -1: - return self.isMountable() - return self.supported - - def isChecked(self): - return self.checked - - def getDeviceArgs(self, device): - deviceArgsFunction = self.deviceArguments.get(device.__class__) - if not deviceArgsFunction: - return [] - return deviceArgsFunction(device) - - def getPartedFileSystemType(self): - return self.partedFileSystemType - - def getPartedPartitionFlags(self): - return self.partedPartitionFlags - - # note that this returns the maximum size of a filesystem in megabytes - def getMaxSizeMB(self): - return self.maxSizeMB - - def getDefaultOptions(self, mountpoint): - return self.defaultOptions - - def getMigratableFSTargets(self): - retval = [] - if not self.migratetofs: - return retval - - for fs in self.migratetofs: - if fileSystemTypeGet(fs).isSupported(): - retval.append(fs) - - return retval - - def isMigratable(self): - if len(self.getMigratableFSTargets()) > 0: - return 1 - else: - return 0 - - -class reiserfsFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = parted.fileSystemType["reiserfs"] - self.formattable = 1 - self.checked = 1 - self.linuxnativefs = 1 - self.bootable = True - # this is totally, 100% unsupported. Boot with "linux reiserfs" - # at the boot: prompt will let you make new reiserfs filesystems - # in the installer. Bugs filed when you use this will be closed - # WONTFIX. - if flags.cmdline.has_key("reiserfs"): - self.supported = -1 - else: - self.supported = 0 - - self.name = "reiserfs" - self.packages = [ "reiserfs-utils" ] - self.needProgram = [ "mkreiserfs", "reiserfstune" ] - - self.maxSizeMB = 8 * 1024 * 1024 - - def formatDevice(self, entry, progress, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - - p = os.pipe() - os.write(p[1], "y\n") - os.close(p[1]) - - rc = iutil.execWithRedirect("mkreiserfs", - [devicePath], - stdin = p[0], - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - - if rc: - raise SystemError - - def labelDevice(self, entry, chroot): - devicePath = entry.device.setupDevice(chroot) - label = self.createLabel(entry.mountpoint, self.maxLabelChars, - kslabel = entry.label) - rc = iutil.execWithRedirect("reiserfstune", - ["--label", label, devicePath], - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - if rc: - raise SystemError - entry.setLabel(label) - -fileSystemTypeRegister(reiserfsFileSystem()) - -class xfsFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = parted.fileSystemType["xfs"] - self.formattable = 1 - self.checked = 1 - self.linuxnativefs = 1 - self.name = "xfs" - self.maxSizeMB = 16 * 1024 * 1024 - self.maxLabelChars = 12 - self.supported = -1 - if not os.path.exists("/sbin/mkfs.xfs") and not os.path.exists("/usr/sbin/mkfs.xfs") and not os.path.exists("/usr/sbin/xfs_admin"): - self.supported = 0 - - self.packages = [ "xfsprogs" ] - self.needProgram = [ "mkfs.xfs", "xfs_admin" ] - - def formatDevice(self, entry, progress, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - - rc = iutil.execWithRedirect("mkfs.xfs", ["-f", devicePath], - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - - if rc: - raise SystemError - - def labelDevice(self, entry, chroot): - devicePath = entry.device.setupDevice(chroot) - label = self.createLabel(entry.mountpoint, self.maxLabelChars, - kslabel = entry.label) - rc = iutil.execWithRedirect("xfs_admin", - ["-L", label, devicePath], - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - if rc: - raise SystemError - entry.setLabel(label) - -fileSystemTypeRegister(xfsFileSystem()) - -class jfsFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = parted.fileSystemType["jfs"] - self.formattable = 1 - self.checked = 1 - self.linuxnativefs = 1 - self.maxLabelChars = 16 - self.bootable = True - # this is totally, 100% unsupported. Boot with "linux jfs" - # at the boot: prompt will let you make new reiserfs filesystems - # in the installer. Bugs filed when you use this will be closed - # WONTFIX. - if flags.cmdline.has_key("jfs"): - self.supported = -1 - else: - self.supported = 0 - - self.name = "jfs" - self.packages = [ "jfsutils" ] - self.needProgram = [ "mkfs.jfs", "jfs_tune" ] - - self.maxSizeMB = 8 * 1024 * 1024 - - def labelDevice(self, entry, chroot): - devicePath = entry.device.setupDevice(chroot) - label = self.createLabel(entry.mountpoint, self.maxLabelChars, - kslabel = entry.label) - rc = iutil.execWithRedirect("jfs_tune", - ["-L", label, devicePath], - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - if rc: - raise SystemError - entry.setLabel(label) - - def formatDevice(self, entry, progress, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - - rc = iutil.execWithRedirect("mkfs.jfs", - ["-q", devicePath], - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - - if rc: - raise SystemError - -fileSystemTypeRegister(jfsFileSystem()) - -class gfs2FileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = None - self.formattable = 1 - self.checked = 1 - self.linuxnativefs = 1 - if flags.cmdline.has_key("gfs2"): - self.supported = -1 - else: - self.supported = 0 - - self.name = "gfs2" - self.packages = [ "gfs2-utils" ] - self.needProgram = [ "mkfs.gfs2" ] - - self.maxSizeMB = 8 * 1024 * 1024 - - def formatDevice(self, entry, progress, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - rc = iutil.execWithRedirect("mkfs.gfs2", - ["-j", "1", "-p", "lock_nolock", - "-O", devicePath], - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - - if rc: - raise SystemError - -fileSystemTypeRegister(gfs2FileSystem()) - -class extFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = None - self.formattable = 1 - self.checked = 1 - self.linuxnativefs = 1 - self.maxSizeMB = 8 * 1024 * 1024 - self.packages = [ "e2fsprogs" ] - self.supportsFsProfiles = True - self.fsProfileSpecifier = "-T" - self.resizable = True - self.bootable = True - - def resize(self, entry, size, progress, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - - log.info("checking %s prior to resize" %(devicePath,)) - w = None - if progress: - w = progress(_("Checking"), - _("Checking filesystem on %s...") %(devicePath), - 100, pulse = True) - - rc = iutil.execWithPulseProgress("e2fsck", ["-f", "-p", "-C", "0", devicePath], - stdout="/tmp/resize.out", - stderr="/tmp/resize.out", - progress = w) - if rc >= 4: - raise ResizeError, ("Check of %s failed: %s" %(devicePath, rc), devicePath) - if progress: - w.pop() - w = progress(_("Resizing"), - _("Resizing filesystem on %s...") %(devicePath), - 100, pulse = True) - - log.info("resizing %s" %(devicePath,)) - rc = iutil.execWithPulseProgress("resize2fs", - ["-p", devicePath, "%sM" %(size,)], - stdout="/tmp/resize.out", - stderr="/tmp/resize.out", - progress = w) - if progress: - w.pop() - if rc: - raise ResizeError, ("Resize of %s failed: %s" %(devicePath, rc), devicePath) - - def getMinimumSize(self, device): - """Return the minimum filesystem size in megabytes""" - devicePath = "/dev/%s" % (device,) - - # FIXME: it'd be nice if we didn't have to parse this out ourselves - buf = iutil.execWithCapture("dumpe2fs", - ["-h", devicePath], - stderr = "/dev/tty5") - blocks = free = bs = 0 - for l in buf.split("\n"): - if l.startswith("Free blocks"): - try: - free = l.split()[2] - free = int(free) - except Exception, e: - log.warning("error determining free blocks on %s: %s" %(devicePath, e)) - free = 0 - elif l.startswith("Block size"): - try: - bs = l.split()[2] - bs = int(bs) - except Exception, e: - log.warning("error determining block size of %s: %s" %(devicePath, e)) - bs = 0 - elif l.startswith("Block count"): - try: - blocks = l.split()[2] - blocks = int(blocks) - except Exception, e: - log.warning("error determining block count of %s: %s" %(devicePath, e)) - blocks = 0 - - if free == 0 or bs == 0: - log.warning("Unable to determinine minimal size for %s", devicePath) - return 1 - - used = math.ceil((blocks - free) * bs / 1024.0 / 1024.0) - log.info("used size of %s is %s" %(devicePath, used)) - # FIXME: should we bump this beyond the absolute minimum? - return used - - def labelDevice(self, entry, chroot): - devicePath = entry.device.setupDevice(chroot) - label = self.createLabel(entry.mountpoint, self.maxLabelChars, - kslabel = entry.label) - - rc = iutil.execWithRedirect("e2label", - [devicePath, label], - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - if rc: - raise SystemError - entry.setLabel(label) - - def formatDevice(self, entry, progress, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - devArgs = self.getDeviceArgs(entry.device) - args = [ "mke2fs", devicePath ] - - fsProfileArgs = self.getFsProfileArgs() - if fsProfileArgs: - args.extend(fsProfileArgs) - args.extend(devArgs) - args.extend(self.extraFormatArgs) - - log.info("Format command: %s\n" % str(args)) - - rc = ext2FormatFilesystem(args, "/dev/tty5", - progress, - entry.mountpoint) - if rc: - raise SystemError - - def clobberDevice(self, entry, chroot): - device = entry.device.setupDevice(chroot) - isys.ext2Clobber(device) - - # this is only for ext3 filesystems, but migration is a method - # of the ext2 fstype, so it needs to be here. FIXME should be moved - def setExt3Options(self, entry, message, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - - # if no journal, don't turn off the fsck - if not isys.ext2HasJournal(devicePath): - return - - rc = iutil.execWithRedirect("tune2fs", - ["-c0", "-i0", - "-ouser_xattr,acl", devicePath], - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - -class ext2FileSystem(extFileSystem): - def __init__(self): - extFileSystem.__init__(self) - self.name = "ext2" - self.partedFileSystemType = parted.fileSystemType["ext2"] - self.migratetofs = ['ext3'] - - def migrateFileSystem(self, entry, message, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - - if not entry.fsystem or not entry.origfsystem: - raise RuntimeError, ("Trying to migrate fs w/o fsystem or " - "origfsystem set") - if entry.fsystem.getName() != "ext3": - raise RuntimeError, ("Trying to migrate ext2 to something other " - "than ext3") - - # if journal already exists skip - if isys.ext2HasJournal(devicePath): - log.info("Skipping migration of %s, has a journal already.\n" % devicePath) - return - - rc = iutil.execWithRedirect("tune2fs", - ["-j", devicePath ], - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - - if rc: - raise SystemError - - # XXX this should never happen, but appears to have done - # so several times based on reports in bugzilla. - # At least we can avoid leaving them with a system which won't boot - if not isys.ext2HasJournal(devicePath): - log.warning("Migration of %s attempted but no journal exists after " - "running tune2fs.\n" % (devicePath)) - if message: - rc = message(_("Error"), - _("An error occurred migrating %s to ext3. It is " - "possible to continue without migrating this " - "file system if desired.\n\n" - "Would you like to continue without migrating %s?") - % (devicePath, devicePath), type = "yesno") - if rc == 0: - sys.exit(0) - entry.fsystem = entry.origfsystem - else: - extFileSystem.setExt3Options(self, entry, message, chroot) - - -fileSystemTypeRegister(ext2FileSystem()) - -class ext3FileSystem(extFileSystem): - def __init__(self): - extFileSystem.__init__(self) - self.name = "ext3" - self.extraFormatArgs = [ "-t", "ext3" ] - self.partedFileSystemType = parted.fileSystemType["ext3"] - if flags.cmdline.has_key("ext4migrate"): - self.migratetofs = ['ext4'] - - def formatDevice(self, entry, progress, chroot='/'): - extFileSystem.formatDevice(self, entry, progress, chroot) - extFileSystem.setExt3Options(self, entry, progress, chroot) - - def migrateFileSystem(self, entry, message, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - - if not entry.fsystem or not entry.origfsystem: - raise RuntimeError, ("Trying to migrate fs w/o fsystem or " - "origfsystem set") - if entry.fsystem.getName() != "ext4": - raise RuntimeError, ("Trying to migrate ext3 to something other " - "than ext4") - - rc = iutil.execWithRedirect("tune2fs", ["-O", "extents", devicePath], - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - - if rc: - raise SystemError - -fileSystemTypeRegister(ext3FileSystem()) - -class ext4FileSystem(extFileSystem): - def __init__(self): - extFileSystem.__init__(self) - self.name = "ext4" - self.partedFileSystemType = parted.fileSystemType["ext3"] - self.extraFormatArgs = [ "-t", "ext4" ] - self.bootable = False - - def formatDevice(self, entry, progress, chroot='/'): - extFileSystem.formatDevice(self, entry, progress, chroot) - extFileSystem.setExt3Options(self, entry, progress, chroot) - -fileSystemTypeRegister(ext4FileSystem()) - -class btrfsFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.formattable = 1 - self.checked = 1 - self.linuxnativefs = 1 - self.bootable = False - self.maxLabelChars = 256 - # Wow, you must be brave! - # this is totally, 100% unsupported. Boot with "linux btrfs" - # at the boot: prompt will let you make new btrfs filesystems - # in the installer. - if flags.cmdline.has_key("icantbelieveitsnotbtr"): - self.supported = -1 - else: - self.supported = 0 - - self.name = "btrfs" - self.packages = [ "btrfs-progs" ] - self.needProgram = [ "mkfs.btrfs", "btrfsctl" ] - - # Bigger, really, depending on machine - self.maxSizeMB = 16 * 1024 * 1024 - - # We'll sneakily label it here, too. - def formatDevice(self, entry, progress, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - label = self.createLabel(entry.mountpoint, self.maxLabelChars, - kslabel = entry.label) - - rc = iutil.execWithRedirect("mkfs.btrfs", - ["-L", label, devicePath], - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - - if rc: - raise SystemError - entry.setLabel(label) - def labelDevice(self, entry, chroot): - # We did this on the initial format; no standalone labeler yet - pass - - def resize(self, entry, size, progress, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - log.info("resizing %s" %(devicePath,)) - - w = None - if progress: - w = progress(_("Resizing"), - _("Resizing filesystem on %s...") %(devicePath), - 100, pulse = True) - - rc = iutil.execWithPulseProgress("btrfsctl", - ["-r", "%sM" %(size,), devicePath], - stdout="/tmp/resize.out", - stderr="/tmp/resize.out", - progress = w) - if progress: - w.pop() - if rc: - raise ResizeError, ("Resize of %s failed: %s" %(devicePath, rc), devicePath) - - -fileSystemTypeRegister(btrfsFileSystem()) - -class raidMemberDummyFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = parted.fileSystemType["ext2"] - self.partedPartitionFlags = [ parted.PARTITION_RAID ] - self.formattable = 1 - self.checked = 0 - self.linuxnativefs = 1 - self.name = "software RAID" - self.maxSizeMB = 8 * 1024 * 1024 - self.supported = 1 - - if len(raid.availRaidLevels) == 0: - self.supported = 0 - - self.packages = [ "mdadm" ] - - def formatDevice(self, entry, progress, chroot='/'): - # mkraid did all we need to format this partition... - pass - -fileSystemTypeRegister(raidMemberDummyFileSystem()) - -class lvmPhysicalVolumeDummyFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = parted.fileSystemType["ext2"] - self.partedPartitionFlags = [ parted.PARTITION_LVM ] - self.formattable = 1 - self.checked = 0 - self.linuxnativefs = 1 - self.name = "physical volume (LVM)" - self.maxSizeMB = 8 * 1024 * 1024 - self.supported = 1 - self.packages = [ "lvm2" ] - - def isMountable(self): - return 0 - - def formatDevice(self, entry, progress, chroot='/'): - # already done by the pvcreate during volume creation - pass - -fileSystemTypeRegister(lvmPhysicalVolumeDummyFileSystem()) - -class lvmVolumeGroupDummyFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = parted.fileSystemType["ext2"] - self.formattable = 1 - self.checked = 0 - self.linuxnativefs = 0 - self.name = "volume group (LVM)" - self.supported = 0 - self.maxSizeMB = 8 * 1024 * 1024 - self.packages = [ "lvm2" ] - - def isMountable(self): - return 0 - - def formatDevice(self, entry, progress, chroot='/'): - # the vgcreate already did this - pass - -fileSystemTypeRegister(lvmVolumeGroupDummyFileSystem()) - -class swapFileSystem(FileSystemType): - enabledSwaps = {} - - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = parted.fileSystemType["linux-swap"] - self.formattable = 1 - self.name = "swap" - self.maxSizeMB = 8 * 1024 * 1024 - self.linuxnativefs = 1 - self.supported = 1 - self.maxLabelChars = 15 - - def mount(self, device, mountpoint, readOnly=0, bindMount=0, - instroot = 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: - pass - finally: - try: - os.close(fd) - except: - 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 - - isys.swapon (device) - - def umount(self, device, path): - if os.path.exists("/dev/" + device.device): - swapFile = os.path.realpath("/dev/" + device.device) - else: - # path is something like /mnt/sysimage/swap, which is not very - # useful. But since we don't have instPath anywhere else, so - # we have to pull it out of path and add the real swap file to - # the end of it. - swapFile = os.path.realpath(os.path.dirname(path) + "/" + device.device) - - try: - iutil.execWithRedirect("swapoff", [swapFile], stdout="/dev/tty5", - stderr="/dev/tty5", searchPath=1) - except: - raise RuntimeError, "unable to turn off swap" - - def formatDevice(self, entry, progress, chroot='/'): - file = entry.device.setupDevice(chroot) - rc = iutil.execWithRedirect ("mkswap", - ['-v1', file], - stdout = "/dev/tty5", - stderr = "/dev/tty5", - searchPath = 1) - if rc: - raise SystemError - - def labelDevice(self, entry, chroot): - file = entry.device.setupDevice(chroot) - devName = entry.device.getDevice() - # we'll keep the SWAP-* naming for all devs but Compaq SMART2 - # nodes (#176074) - if devName[0:6] == "cciss/": - swapLabel = "SW-%s" % (devName) - elif devName.startswith("mapper/"): - swapLabel = "SWAP-%s" % (devName[7:],) - else: - swapLabel = "SWAP-%s" % (devName) - label = self.createLabel(swapLabel, self.maxLabelChars) - rc = iutil.execWithRedirect ("mkswap", - ['-v1', "-L", label, file], - stdout = "/dev/tty5", - stderr = "/dev/tty5", - searchPath = 1) - if rc: - raise SystemError - entry.setLabel(label) - - def clobberDevice(self, entry, chroot): - pagesize = resource.getpagesize() - dev = entry.device.setupDevice(chroot) - try: - fd = os.open(dev, os.O_RDWR) - buf = "\0x00" * pagesize - os.write(fd, buf) - except: - pass - finally: - try: - os.close(fd) - except: - pass - -fileSystemTypeRegister(swapFileSystem()) - -class FATFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = parted.fileSystemType["fat32"] - self.formattable = 1 - self.checked = 0 - self.maxSizeMB = 1024 * 1024 - self.name = "vfat" - self.packages = [ "dosfstools" ] - self.defaultOptions = "umask=0077,shortname=winnt" - - def formatDevice(self, entry, progress, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - devArgs = self.getDeviceArgs(entry.device) - args = [ devicePath ] - args.extend(devArgs) - - rc = iutil.execWithRedirect("mkdosfs", args, - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - if rc: - raise SystemError - - def labelDevice(self, entry, chroot): - devicePath = entry.device.setupDevice(chroot) - label = self.createLabel(entry.mountpoint, self.maxLabelChars, - kslabel = entry.label) - - rc = iutil.execWithRedirect("dosfslabel", - [devicePath, label], - stdout = "/dev/tty5", - stderr = "/dev/tty5", - searchPath = 1) - if rc: - msg = iutil.execWithCapture("dosfslabel", [devicePath], - stderr="/dev/tty5") - raise SystemError, "dosfslabel failed on device %s: %s" % (devicePath, msg) - - newLabel = iutil.execWithCapture("dosfslabel", [devicePath], - stderr = "/dev/tty5") - newLabel = newLabel.strip() - if label != newLabel: - raise SystemError, "dosfslabel failed on device %s" % (devicePath,) - entry.setLabel(label) - -fileSystemTypeRegister(FATFileSystem()) - -class EFIFileSystem(FATFileSystem): - def __init__(self): - FATFileSystem.__init__(self) - self.name = "efi" - self.partedPartitionFlags = [ parted.PARTITION_BOOT ] - self.maxSizeMB = 256 - self.defaultOptions = "umask=0077,shortname=winnt" - self.bootable = True - if not iutil.isEfi(): - self.supported = 0 - - def getMountName(self, quoted = 0): - return "vfat" - - def formatDevice(self, entry, progress, chroot='/'): - FATFileSystem.formatDevice(self, entry, progress, chroot) - - # XXX need to set the name in GPT - # entry.device.something.part.set_name("EFI System Partition") - devicePath = entry.device.setupDevice(chroot) - -fileSystemTypeRegister(EFIFileSystem()) - -class NTFSFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = parted.fileSystemType["ntfs"] - self.formattable = 0 - self.checked = 0 - self.name = "ntfs" - if len(filter(lambda d: os.path.exists("%s/ntfsresize" %(d,)), - os.environ["PATH"].split(":"))) > 0: - self.resizable = True - if len(filter(lambda d: os.path.exists("%s/mount.ntfs-3g" %(d,)), - os.environ["PATH"].split(":"))) > 0: - self.fusefs = 1 - - def resize(self, entry, size, progress, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - log.info("resizing %s to %sM" %(devicePath, size)) - w = None - if progress: - w = progress(_("Resizing"), - _("Resizing filesystem on %s...") %(devicePath), - 100, pulse = True) - - p = os.pipe() - os.write(p[1], "y\n") - os.close(p[1]) - - # FIXME: we should call ntfsresize -c to ensure that we can resize - # before starting the operation - - rc = iutil.execWithPulseProgress("ntfsresize", ["-v", - "-s", "%sM" %(size,), - devicePath], - stdin = p[0], - stdout = "/tmp/resize.out", - stderr = "/tmp/resize.out", - progress = w) - if progress: - w.pop() - if rc: - raise ResizeError, ("Resize of %s failed" %(devicePath,), devicePath) - - def getMinimumSize(self, device): - """Return the minimum filesystem size in megabytes""" - devicePath = "/dev/%s" % (device,) - - buf = iutil.execWithCapture("ntfsresize", ["-m", devicePath], - stderr = "/dev/tty5") - for l in buf.split("\n"): - if not l.startswith("Minsize"): - continue - try: - min = l.split(":")[1].strip() - return int(min) + 250 - except Exception, e: - log.warning("Unable to parse output for minimum size on %s: %s" %(device, e)) - - log.warning("Unable to discover minimum size of filesystem on %s" %(device,)) - return 1 - - -fileSystemTypeRegister(NTFSFileSystem()) - -class hfsFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = parted.fileSystemType["hfs"] - self.formattable = 1 - self.checked = 0 - self.name = "hfs" - self.supported = 0 - self.needProgram = [ "hformat" ] - - def isMountable(self): - return 0 - - def formatDevice(self, entry, progress, chroot='/'): - devicePath = entry.device.setupDevice(chroot) - devArgs = self.getDeviceArgs(entry.device) - args = [ devicePath ] - args.extend(devArgs) - - rc = iutil.execWithRedirect("hformat", args, - stdout = "/dev/tty5", - stderr = "/dev/tty5", searchPath = 1) - if rc: - raise SystemError - -fileSystemTypeRegister(hfsFileSystem()) - -class HfsPlusFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = parted.fileSystemType["hfs+"] - self.formattable = 0 - self.checked = 0 - self.name = "hfs+" - -fileSystemTypeRegister(HfsPlusFileSystem()) - -class applebootstrapFileSystem(hfsFileSystem): - def __init__(self): - hfsFileSystem.__init__(self) - self.partedPartitionFlags = [ parted.PARTITION_BOOT ] - self.maxSizeMB = 1 - self.name = "Apple Bootstrap" - self.bootable = True - if iutil.getPPCMacGen() == "NewWorld": - self.linuxnativefs = 1 - self.supported = 1 - else: - self.linuxnativefs = 0 - self.supported = 0 - -fileSystemTypeRegister(applebootstrapFileSystem()) - -class prepbootFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.partedFileSystemType = None - self.partedPartitionFlags = [ parted.PARTITION_BOOT, parted.PARTITION_PREP ] - self.checked = 0 - self.name = "PPC PReP Boot" - self.maxSizeMB = 10 - self.bootable = True - - if iutil.getPPCMachine() == "iSeries": - self.maxSizeMB = 64 - - # supported for use on the pseries - if (iutil.getPPCMachine() == "pSeries" or - iutil.getPPCMachine() == "iSeries"): - self.linuxnativefs = 1 - self.supported = 1 - self.formattable = 1 - else: - self.linuxnativefs = 0 - self.supported = 0 - self.formattable = 0 - - def formatDevice(self, entry, progress, chroot='/'): - return - -fileSystemTypeRegister(prepbootFileSystem()) - -class networkFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.formattable = 0 - self.checked = 0 - self.name = "nfs" - - def isMountable(self): - return 0 - -fileSystemTypeRegister(networkFileSystem()) - -class nfsv4FileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.formattable = 0 - self.checked = 0 - self.name = "nfs4" - - def isMountable(self): - return 0 - -fileSystemTypeRegister(nfsv4FileSystem()) - -class ForeignFileSystem(FileSystemType): - def __init__(self): - FileSystemType.__init__(self) - self.formattable = 0 - self.checked = 0 - self.name = "foreign" - - def formatDevice(self, entry, progress, chroot='/'): - return - -fileSystemTypeRegister(ForeignFileSystem()) - -class PseudoFileSystem(FileSystemType): - def __init__(self, name): - FileSystemType.__init__(self) - self.formattable = 0 - self.checked = 0 - self.name = name - self.supported = 0 - - def isKernelFS(self): - return True - -class SpuFileSystem(PseudoFileSystem): - def __init__(self): - PseudoFileSystem.__init__(self, "spufs") - -fileSystemTypeRegister(SpuFileSystem()) - -class ProcFileSystem(PseudoFileSystem): - def __init__(self): - PseudoFileSystem.__init__(self, "proc") - -fileSystemTypeRegister(ProcFileSystem()) - -class SysfsFileSystem(PseudoFileSystem): - def __init__(self): - PseudoFileSystem.__init__(self, "sysfs") - -fileSystemTypeRegister(SysfsFileSystem()) - -class SelinuxfsFileSystem(PseudoFileSystem): - def __init__(self): - PseudoFileSystem.__init__(self, "selinuxfs") - -fileSystemTypeRegister(SelinuxfsFileSystem()) - -class DevptsFileSystem(PseudoFileSystem): - def __init__(self): - PseudoFileSystem.__init__(self, "devpts") - self.defaultOptions = "gid=5,mode=620" - - def isMountable(self): - return 0 - -fileSystemTypeRegister(DevptsFileSystem()) - -class DevshmFileSystem(PseudoFileSystem): - def __init__(self): - PseudoFileSystem.__init__(self, "tmpfs") - - def isMountable(self): - return 0 - -fileSystemTypeRegister(DevshmFileSystem()) - -class AutoFileSystem(PseudoFileSystem): - def __init__(self): - PseudoFileSystem.__init__(self, "auto") - - def mount(self, device, mountpoint, readOnly=0, bindMount=0, - instroot = None): - errNum = 0 - errMsg = "cannot mount auto filesystem on %s of this type" % device - - if not self.isMountable(): - return - iutil.mkdirChain("%s/%s" %(instroot, mountpoint)) - if flags.selinux: - ret = isys.resetFileContext(mountpoint, instroot) - log.info("set SELinux context for mountpoint %s to %s" %(mountpoint, ret)) - - fs = isys.readFSType(device) - if fs is not None: - try: - isys.mount (device, mountpoint, fstype = fs, readOnly = - readOnly, bindMount = bindMount) - return - except SystemError, (num, msg): - errNum = num - errMsg = msg - - raise SystemError (errNum, errMsg) - - def umount(self, device, path): - isys.umount(path, removeDir = 0) - -fileSystemTypeRegister(AutoFileSystem()) - -class BindFileSystem(PseudoFileSystem): - def __init__(self): - PseudoFileSystem.__init__(self, "bind") - - def isMountable(self): - return 1 - -fileSystemTypeRegister(BindFileSystem()) - -class FileSystemSet: - def __init__(self): - self.messageWindow = None - self.progressWindow = None - self.waitWindow = None - self.mountcount = 0 - self.migratedfs = 0 - self.reset() - self.volumesCreated = 0 - - def isActive(self): - return self.mountcount != 0 - - def registerMessageWindow(self, method): - self.messageWindow = method - - def registerProgressWindow(self, method): - self.progressWindow = method - - def registerWaitWindow(self, method): - self.waitWindow = method - - def reset (self): - self.entries = [] - proc = FileSystemSetEntry(Device(device="proc"), '/proc', - fileSystemTypeGet("proc")) - self.add(proc) - sys = FileSystemSetEntry(Device(device="sys"), '/sys', - fileSystemTypeGet("sysfs")) - self.add(sys) - pts = FileSystemSetEntry(Device(device="devpts"), '/dev/pts', - fileSystemTypeGet("devpts"), "gid=5,mode=620") - self.add(pts) - shm = FileSystemSetEntry(Device(device="shm"), '/dev/shm', - fileSystemTypeGet("tmpfs")) - self.add(shm) - - if iutil.isCell(): - spu = FileSystemSetEntry(Device(device="spufs"), '/spu', - fileSystemTypeGet("spufs")) - self.add(spu) - - def verify (self): - for entry in self.entries: - if type(entry.__dict__) != type({}): - raise RuntimeError, "fsset internals inconsistent" - - def add (self, newEntry): - # Should object A be sorted after object B? Take mountpoints and - # device names into account so bind mounts are sorted correctly. - def comesAfter (a, b): - mntA = a.mountpoint - mntB = b.mountpoint - devA = a.device.getDevice() - devB = b.device.getDevice() - - if not mntB: - return False - if mntA and mntA != mntB and mntA.startswith(mntB): - return True - if devA and devA != mntB and devA.startswith(mntB): - return True - return False - - def samePseudo (a, b): - return isinstance(a.fsystem, PseudoFileSystem) and isinstance (b.fsystem, PseudoFileSystem) and \ - not isinstance (a.fsystem, BindFileSystem) and not isinstance (b.fsystem, BindFileSystem) and \ - a.fsystem.getName() == b.fsystem.getName() - - def sameEntry (a, b): - return a.device.getDevice() == b.device.getDevice() and a.mountpoint == b.mountpoint - - # Remove preexisting duplicate entries - pseudo filesystems are - # duplicate if they have the same filesystem type as an existing one. - # Otherwise, they have to have the same device and mount point - # (required to check for bind mounts). - for existing in self.entries: - if samePseudo (newEntry, existing) or sameEntry (newEntry, existing): - self.remove(existing) - - # XXX debuggin' -## log.info ("fsset at %s\n" -## "adding entry for %s\n" -## "entry object %s, class __dict__ is %s", -## self, entry.mountpoint, entry, -## isys.printObject(entry.__dict__)) - - insertAt = 0 - - # Special case for /. - if newEntry.mountpoint == "/": - self.entries.insert(insertAt, newEntry) - return - - # doesn't matter where these get added, so just put them at the end - if not newEntry.mountpoint or not newEntry.mountpoint.startswith("/") or self.entries == []: - self.entries.append(newEntry) - return - - for entry in self.entries: - if comesAfter(newEntry, entry): - insertAt = self.entries.index(entry)+1 - - self.entries.insert(insertAt, newEntry) - - def remove (self, entry): - self.entries.remove(entry) - - def getEntryByMountPoint(self, mount): - for entry in self.entries: - if entry.mountpoint == mount: - return entry - return None - - def getEntryByDeviceName(self, dev): - for entry in self.entries: - if entry.device.getDevice() == dev: - return entry - - # getDevice() will return the mapped device if using LUKS - if entry.device.device == dev: - return entry - - return None - - def copy (self): - new = FileSystemSet() - for entry in self.entries: - new.add (entry) - return new - - 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() - - for entry in self.entries: - if entry.mountpoint: - if entry.getUuid() and entry.device.doLabel is not None: - device = "UUID=%s" %(entry.getUuid(),) - elif entry.getLabel() and entry.device.doLabel is not None: - device = "LABEL=%s" % (entry.getLabel(),) - else: - device = devify(entry.device.getDevice()) - fstab = fstab + entry.device.getComment() - fstab = fstab + format % (device, entry.mountpoint, - entry.fsystem.getMountName(), - entry.getOptions(), entry.fsck, - entry.order) - return fstab - - def mtab (self): - format = "%s %s %s %s 0 0\n" - mtab = "" - for entry in self.entries: - if not entry.isMounted(): - continue - if entry.mountpoint: - # swap doesn't end up in the mtab - if entry.fsystem.getName() == "swap": - continue - options = entry.getOptions() - if options: - options = "rw," + options - else: - options = "rw" - mtab = mtab + format % (devify(entry.device.getDevice()), - entry.mountpoint, - entry.fsystem.getName(), - options) - return mtab - - def raidtab(self): - # set up raidtab... - raidtab = "" - for entry in self.entries: - if entry.device.getName() == "RAIDDevice": - raidtab = raidtab + entry.device.raidTab() - - return raidtab - - def mdadmConf(self): - """Make the mdadm.conf file with mdadm command. - - This creates a conf file with active arrays. In other words - the arrays that we don't want included must be inactive. - """ - activeArrays = iutil.execWithCapture("mdadm", ["--detail", "--scan"]) - if len(activeArrays) == 0: - return - - cf = """ -# mdadm.conf written out by anaconda -DEVICE partitions -MAILADDR root - -%s -""" % activeArrays - return cf - - def crypttab(self): - """set up /etc/crypttab""" - crypttab = "" - for entry in self.entries: - if entry.device.crypto: - crypttab += entry.device.crypto.crypttab() - - return crypttab - - def write (self, prefix): - f = open (prefix + "/etc/fstab", "w") - f.write (self.fstab()) - f.close () - - cf = self.mdadmConf() - - if cf: - f = open (prefix + "/etc/mdadm.conf", "w") - f.write (cf) - f.close () - - crypttab = self.crypttab() - if crypttab: - f = open(prefix + "/etc/crypttab", "w") - f.write(crypttab) - f.close() - - # touch mtab - open (prefix + "/etc/mtab", "w+") - f.close () - - def mkDevRoot(self, instPath): - root = self.getEntryByMountPoint("/") - dev = "%s/dev/%s" % (instPath, root.device.getDevice()) - 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) - - # return the "boot" device - def getBootDev(self): - mntDict = {} - bootDev = None - for entry in self.entries: - mntDict[entry.mountpoint] = entry - - # FIXME: this ppc stuff feels kind of crufty -- the abstraction - # here needs a little bit of work - if iutil.getPPCMacGen() == "NewWorld": - for entry in self.entries: - if entry.fsystem.getName() == "Apple Bootstrap": - bootDev = entry - elif (iutil.getPPCMachine() == "pSeries" or - iutil.getPPCMachine() == "iSeries"): - # we want the first prep partition or the first newly formatted one - bestprep = None - for entry in self.entries: - if ((entry.fsystem.getName() == "PPC PReP Boot") - and ((bestprep is None) or - ((bestprep.format == 0) and (entry.format == 1)))): - bestprep = entry - if bestprep: - bootDev = bestprep - elif iutil.isEfi(): - if mntDict.has_key("/boot/efi"): - bootDev = mntDict['/boot/efi'] - elif mntDict.has_key("/boot"): - bootDev = mntDict['/boot'] - elif mntDict.has_key("/"): - bootDev = mntDict['/'] - - return bootDev - - def bootloaderChoices(self, diskSet, bl): - ret = {} - bootDev = self.getBootDev() - - if bootDev is None: - log.warning("no boot device set") - return ret - - if iutil.isEfi(): - ret['boot'] = (bootDev.device.getDevice(), N_("EFI System Partition")) - return ret - - if bootDev.device.getName() == "RAIDDevice": - ret['boot'] = (bootDev.device.getDevice(), N_("RAID Device")) - ret['mbr'] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) - return ret - - if iutil.getPPCMacGen() == "NewWorld": - ret['boot'] = (bootDev.device.getDevice(), N_("Apple Bootstrap")) - n = 1 - for entry in self.entries: - if ((entry.fsystem.getName() == "Apple Bootstrap") and ( - entry.device.getDevice() != bootDev.device.getDevice())): - ret['boot%d' %(n,)] = (entry.device.getDevice(), - N_("Apple Bootstrap")) - n = n + 1 - return ret - elif (iutil.getPPCMachine() == "pSeries" or - iutil.getPPCMachine() == "iSeries"): - ret['boot'] = (bootDev.device.getDevice(), N_("PPC PReP Boot")) - return ret - - ret['boot'] = (bootDev.device.getDevice(), N_("First sector of boot partition")) - ret['mbr'] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) - return ret - - # set active partition on disks - # if an active partition is set, leave it alone; if none set - # set either our boot partition or the first partition on the drive active - def setActive(self, diskset, requests): - bootDev = self.getBootDev() - - if bootDev is None: - return - - if bootDev.device.getName() != "RAIDDevice": - for request in requests: - if request.mountpoint == bootDev.mountpoint: - break - - for drive in request.drive: - part = diskset.disks[drive].getPartitionByPath("/dev/%s" % bootDev.device.device) - if part: - # on EFI systems, *only* /boot/efi should be marked bootable - # similarly, on pseries, we really only want the PReP partition - # active - if iutil.isEfi() or \ - iutil.getPPCMachine() in ("pSeries", "iSeries", "PMac") or \ - (iutil.isX86() and partedUtils.hasGptLabel(diskset, drive)): - if part and part.isFlagAvailable(parted.PARTITION_BOOT): - part.setFlag(parted.PARTITION_BOOT) - return - - for drive in diskset.disks.keys(): - foundActive = 0 - bootPart = None - if partedUtils.hasGptLabel(diskset, drive): - continue - disk = diskset.disks[drive] - for part in disk.partitions: - if not part.active: - continue - - if not part.isFlagAvailable(parted.PARTITION_BOOT): - foundActive = 1 - part = None - continue - - if part.getFlag(parted.PARTITION_BOOT): - foundActive = 1 - part = None - continue - - if not bootPart: - bootPart = part - - if part.getDeviceNodeName() == bootDev.device.device: - bootPart = part - - if bootPart and not foundActive: - bootPart.setFlag(parted.PARTITION_BOOT) - - if bootPart: - del bootPart - - def resizeFilesystems (self, diskset, chroot = '/', shrink = False, grow = False): - todo = [] - for entry in self.entries: - if not entry.fsystem or not entry.fsystem.isResizable(): - continue - if entry.fsystem.isFormattable() and entry.getFormat(): - continue - if entry.resizeTargetSize is None: - continue - if shrink and not (entry.resizeTargetSize < entry.resizeOrigSize): - continue - if grow and not (entry.resizeTargetSize > entry.resizeOrigSize): - continue - todo.append(entry) - if len(todo) == 0: - return - - # we have to have lvm activated to be able to do resizes of LVs - lvmActive = lvm.vgcheckactive() - devicesActive = diskset.devicesOpen - - if not devicesActive: - # should this not be diskset.openDevices() ? - diskset.startMPath() - diskset.startDmRaid() - diskset.startMdRaid() - - if not lvmActive: - lvm.vgscan() - lvm.vgactivate() - - for entry in todo: - entry.fsystem.resize(entry, entry.resizeTargetSize, - self.progressWindow, chroot) - if not lvmActive: - lvm.vgdeactivate() - - if not devicesActive: - # should this not be diskset.closeDevices() ? - diskset.stopMPath() - diskset.stopDmRaid() - diskset.stopMdRaid() - - def shrinkFilesystems (self, diskset, chroot): - self.resizeFilesystems(diskset, chroot, shrink = True) - def growFilesystems (self, diskset, chroot): - self.resizeFilesystems(diskset, chroot, grow = True) - - def formatSwap (self, chroot, forceFormat=False): - formatted = [] - notformatted = [] - - for entry in self.entries: - if (not entry.fsystem or not entry.fsystem.getName() == "swap" or - entry.isMounted()): - continue - if not entry.getFormat(): - if not forceFormat: - notformatted.append(entry) - continue - try: - self.formatEntry(entry, chroot) - formatted.append(entry) - except SystemError: - if self.messageWindow: - self.messageWindow(_("Error"), - _("An error occurred trying to " - "initialize swap on device %s. This " - "problem is serious, and the install " - "cannot continue.\n\n" - "Press <Enter> to exit the installer.") - % (entry.device.getDevice(),)) - sys.exit(0) - - for entry in formatted: - try: - self.labelEntry(entry, chroot) - except SystemError: - # should be OK, fall back to by device - pass - - # find if there's a label on the ones we're not formatting - for entry in notformatted: - dev = entry.device.getDevice() - if not dev or dev == "none": - continue - try: - label = isys.readFSLabel(dev) - except: - continue - if label: - entry.setLabel(label) - - def turnOnSwap (self, chroot, upgrading=False): - def swapErrorDialog (msg, format_button_text, entry): - buttons = [_("Skip"), format_button_text, _("_Exit installer")] - ret = self.messageWindow(_("Error"), msg, type="custom", - custom_buttons=buttons, - custom_icon="warning") - if ret == 0: - self.entries.remove(entry) - elif ret == 1: - self.formatEntry(entry, chroot) - entry.mount(chroot) - self.mountcount = self.mountcount + 1 - else: - sys.exit(0) - - for entry in self.entries: - if (entry.fsystem and entry.fsystem.getName() == "swap" - and not entry.isMounted()): - try: - entry.mount(chroot) - self.mountcount = self.mountcount + 1 - except OldSwapError: - if self.messageWindow: - msg = _("The swap device:\n\n /dev/%s\n\n" - "is a version 0 Linux swap partition. If you " - "want to use this device, you must reformat as " - "a version 1 Linux swap partition. If you skip " - "it, the installer will ignore it during the " - "installation.") % (entry.device.getDevice()) - - swapErrorDialog(msg, _("Reformat"), entry) - except SuspendError: - if self.messageWindow: - if upgrading: - msg = _("The swap device:\n\n /dev/%s\n\n" - "in your /etc/fstab file is currently in " - "use as a software suspend partition, " - "which means your system is hibernating. " - "To perform an upgrade, please shut down " - "your system rather than hibernating it.") \ - % (entry.device.getDevice()) - else: - msg = _("The swap device:\n\n /dev/%s\n\n" - "in your /etc/fstab file is currently in " - "use as a software suspend partition, " - "which means your system is hibernating. " - "If you are performing a new install, " - "make sure the installer is set " - "to format all swap partitions.") \ - % (entry.device.getDevice()) - - # choose your own adventure swap partitions... - msg = msg + _("\n\nChoose Skip if you want the " - "installer to ignore this partition during " - "the upgrade. Choose Format to reformat " - "the partition as swap space.") - - swapErrorDialog(msg, _("Format"), entry) - else: - sys.exit(0) - except SystemError, (num, msg): - if self.messageWindow: - if upgrading and not entry.getLabel(): - err = _("Error enabling swap device %s: %s\n\n" - "Devices in /etc/fstab should be specified " - "by label, not by device name.\n\nPress " - "OK to exit the installer.") % (entry.device.getDevice(), msg) - elif upgrading: - err = _("Error enabling swap device %s: %s\n\n" - "The /etc/fstab on your upgrade partition " - "does not reference a valid swap " - "partition.\n\nPress OK to exit the " - "installer") % (entry.device.getDevice(), msg) - else: - err = _("Error enabling swap device %s: %s\n\n" - "This most likely means this swap " - "partition has not been initialized.\n\n" - "Press OK to exit the installer.") % (entry.device.getDevice(), msg) - - self.messageWindow(_("Error"), err) - sys.exit(0) - - def labelEntry(self, entry, chroot, ignoreExisting = False): - label = entry.device.getLabel() - if label and not ignoreExisting: - entry.setLabel(label) - entry.device.doLabel = 1 - - if entry.device.doLabel is not None: - entry.fsystem.labelDevice(entry, chroot) - - def formatEntry(self, entry, chroot): - if entry.mountpoint: - log.info("formatting %s as %s" %(entry.mountpoint, entry.fsystem.name)) - entry.fsystem.clobberDevice(entry, chroot) - entry.fsystem.formatDevice(entry, self.progressWindow, chroot) - - def getMigratableEntries(self): - retval = [] - for entry in self.entries: - if entry.origfsystem and entry.origfsystem.isMigratable(): - retval.append(entry) - - return retval - - def formattablePartitions(self): - list = [] - for entry in self.entries: - if entry.fsystem.isFormattable(): - list.append (entry) - return list - - def createLogicalVolumes (self, chroot='/'): - vgs = {} - # first set up the volume groups - for entry in self.entries: - if entry.fsystem.name == "volume group (LVM)": - entry.device.setupDevice(chroot) - vgs[entry.device.name] = entry.device - - # then set up the logical volumes - for entry in self.entries: - if isinstance(entry.device, LogicalVolumeDevice): - vg = None - if vgs.has_key(entry.device.vgname): - vg = vgs[entry.device.vgname] - entry.device.setupDevice(chroot, vgdevice = vg) - self.volumesCreated = 1 - - - def makeFilesystems (self, chroot='/', skiprootfs=False): - formatted = [] - notformatted = [] - for entry in self.entries: - if (not entry.fsystem.isFormattable() or not entry.getFormat() - or entry.isMounted()): - notformatted.append(entry) - continue - # FIXME: this is a bit of a hack, but works - if (skiprootfs and entry.mountpoint == '/'): - formatted.append(entry) - continue - try: - self.formatEntry(entry, chroot) - formatted.append(entry) - except SystemError: - if self.messageWindow: - self.messageWindow(_("Error"), - _("An error occurred trying to " - "format %s. This problem is " - "serious, and the install cannot " - "continue.\n\n" - "Press <Enter> to exit the installer.") - % (entry.device.getDevice(),)) - sys.exit(0) - - for entry in formatted: - try: - self.labelEntry(entry, chroot) - except SystemError: - # should be OK, we'll still use the device name to mount. - pass - - # go through and have labels for the ones we don't format - for entry in notformatted: - dev = entry.device.getDevice() - if not dev or dev == "none": - continue - if not entry.mountpoint or entry.mountpoint == "swap": - continue - try: - label = isys.readFSLabel(dev) - except: - continue - - if label: - entry.setLabel(label) - elif entry.fsystem.isSupported(): - self.labelEntry(entry, chroot) - - def haveMigratedFilesystems(self): - return self.migratedfs - - def migrateFilesystems (self, anaconda): - if self.migratedfs: - return - - for entry in self.entries: - if not entry.origfsystem: - continue - - if not entry.origfsystem.isMigratable() or not entry.getMigrate(): - continue - try: - entry.origfsystem.migrateFileSystem(entry, self.messageWindow, - anaconda.rootPath) - except SystemError: - if self.messageWindow: - self.messageWindow(_("Error"), - _("An error occurred trying to " - "migrate %s. This problem is " - "serious, and the install cannot " - "continue.\n\n" - "Press <Enter> to exit the installer.") - % (entry.device.getDevice(),)) - sys.exit(0) - - # we need to unmount and remount so that we're mounted as the - # new fstype as we want to use the new filesystem type during - # the upgrade for ext3->ext4 migrations - if self.isActive(): - self.umountFilesystems(anaconda.rootPath) - self.mountFilesystems(anaconda) - self.turnOnSwap(anaconda.rootPath) - - self.migratedfs = 1 - - def mountFilesystems(self, anaconda, raiseErrors = 0, readOnly = 0, skiprootfs = 0): - protected = anaconda.id.partitions.protectedPartitions() - - for entry in self.entries: - # Don't try to mount a protected partition, since it will already - # have been mounted as the installation source. - if protected and entry.device.getDevice() in protected and os.path.ismount("/mnt/isodir"): - continue - - if not entry.fsystem.isMountable() or (skiprootfs and entry.mountpoint == '/'): - continue - - try: - log.info("trying to mount %s on %s" %(entry.device.setupDevice(), entry.mountpoint,)) - entry.mount(anaconda.rootPath, readOnly = readOnly) - self.mountcount = self.mountcount + 1 - except OSError, (num, msg): - if self.messageWindow: - if num == errno.EEXIST: - self.messageWindow(_("Invalid mount point"), - _("An error occurred when trying " - "to create %s. Some element of " - "this path is not a directory. " - "This is a fatal error and the " - "install cannot continue.\n\n" - "Press <Enter> to exit the " - "installer.") % (entry.mountpoint,)) - else: - self.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 <Enter> to exit the " - "installer.") % (entry.mountpoint, - msg)) - log.error("OSError: (%d) %s" % (num, msg) ) - sys.exit(0) - except SystemError, (num, msg): - if raiseErrors: - raise SystemError, (num, msg) - if self.messageWindow: - if not entry.fsystem.isLinuxNativeFS(): - ret = self.messageWindow(_("Unable to mount filesystem"), - _("An error occurred mounting " - "device %s as %s. You may " - "continue installation, but " - "there may be problems.") % - (entry.device.getDevice(), - entry.mountpoint), - type="custom", custom_icon="warning", - custom_buttons=[_("_Exit installer"), - _("_Continue")]) - - if ret == 0: - sys.exit(0) - else: - continue - else: - if anaconda.id.getUpgrade() and not (entry.getLabel() or entry.getUuid()) and entry.device.getDevice().startswith("/dev"): - errStr = _("Error mounting device %s as %s: " - "%s\n\n" - "Devices in /etc/fstab should be specified " - "by label or UUID, not by device name." - "\n\n" - "Press OK to exit the installer.") % (entry.device.getDevice(), entry.mountpoint, msg) - else: - errStr = _("Error mounting device %s as %s: " - "%s\n\n" - "Press OK to exit the installer.") % (entry.device.getDevice(), entry.mountpoint, msg) - - self.messageWindow(_("Error"), errStr) - - log.error("SystemError: (%d) %s" % (num, msg) ) - sys.exit(0) - - self.makeLVMNodes(anaconda.rootPath) - - def makeLVMNodes(self, instPath, trylvm1 = 0): - # XXX hack to make the device node exist for the root fs if - # it's a logical volume so that mkinitrd can create the initrd. - root = self.getEntryByMountPoint("/") - if not root: - if self.messageWindow: - self.messageWindow(_("Error"), - _("Error finding / entry.\n\n" - "This is most likely means that " - "your fstab is incorrect." - "\n\n" - "Press OK to exit the installer.")) - sys.exit(0) - - rootlvm1 = 0 - if trylvm1: - dev = root.device.getDevice() - # lvm1 major is 58 - if os.access("%s/dev/%s" %(instPath, dev), os.R_OK) and posix.major(os.stat("%s/dev/%s" %(instPath, dev)).st_rdev) == 58: - rootlvm1 = 1 - - if isinstance(root.device, LogicalVolumeDevice) or rootlvm1: - # now make sure all of the device nodes exist. *sigh* - rc = lvm.vgmknodes() - - rootDev = "/dev/%s" % (root.device.getDevice(),) - rootdir = instPath + os.path.dirname(rootDev) - if not os.path.isdir(rootdir): - os.makedirs(rootdir) - - if root.device.crypto is None: - dmdev = "/dev/mapper/" + root.device.getDevice().replace("-","--").replace("/", "-") - else: - dmdev = "/dev/" + root.device.getDevice() - - if os.path.exists(instPath + dmdev): - os.unlink(instPath + dmdev) - if not os.path.isdir(os.path.dirname(instPath + dmdev)): - os.makedirs(os.path.dirname(instPath + dmdev)) - iutil.copyDeviceNode(dmdev, instPath + dmdev) - - # unlink existing so that we dtrt on upgrades - if os.path.exists(instPath + rootDev) and not root.device.crypto: - os.unlink(instPath + rootDev) - if not os.path.isdir(rootdir): - os.makedirs(rootdir) - - if root.device.crypto is None: - os.symlink(dmdev, instPath + rootDev) - - if not os.path.isdir("%s/etc/lvm" %(instPath,)): - os.makedirs("%s/etc/lvm" %(instPath,)) - - def filesystemSpace(self, chroot='/'): - space = [] - for entry in self.entries: - if not entry.isMounted(): - continue - # we can't put swap files on swap partitions; that's nonsense - if entry.mountpoint == "swap": - continue - path = "%s/%s" % (chroot, entry.mountpoint) - try: - space.append((entry.mountpoint, isys.pathSpaceAvailable(path))) - except SystemError: - log.error("failed to get space available in filesystemSpace() for %s" %(entry.mountpoint,)) - - def spaceSort(a, b): - (m1, s1) = a - (m2, s2) = b - - if (s1 > s2): - return -1 - elif s1 < s2: - return 1 - - return 0 - - space.sort(spaceSort) - return space - - def hasDirtyFilesystems(self, mountpoint): - ret = [] - - for entry in self.entries: - # XXX - multifsify, virtualize isdirty per fstype - if entry.fsystem.getName() != "ext2": continue - if entry.getFormat(): continue - if isinstance(entry.device.getDevice(), BindMountDevice): continue - - try: - if isys.ext2IsDirty(entry.device.getDevice()): - log.info("%s is a dirty ext2 partition" % entry.device.getDevice()) - ret.append(entry.device.getDevice()) - except Exception, e: - log.error("got an exception checking %s for being dirty, hoping it's not" %(entry.device.getDevice(),)) - - return ret - - def umountFilesystems(self, instPath, ignoreErrors = 0, swapoff = True): - # Unmount things bind mounted into the instPath here because they're - # not tracked by self.entries. - if os.path.ismount("%s/dev" % instPath): - isys.umount("%s/dev" % instPath, removeDir=0) - - # take a slice so we don't modify self.entries - reverse = self.entries[:] - reverse.reverse() - - for entry in reverse: - if entry.mountpoint == "swap" and not swapoff: - continue - entry.umount(instPath) - entry.device.cleanupDevice(instPath) - -class FileSystemSetEntry: - def __init__ (self, device, mountpoint, - fsystem=None, options=None, - origfsystem=None, migrate=0, - order=-1, fsck=-1, format=0, - fsprofile=None): - if not fsystem: - fsystem = fileSystemTypeGet("ext2") - self.device = device - self.mountpoint = mountpoint - self.fsystem = fsystem - self.origfsystem = origfsystem - self.migrate = migrate - self.resizeTargetSize = None - self.resizeOrigSize = None - self.options = options - self.mountcount = 0 - self.label = None - if fsck == -1: - self.fsck = fsystem.isChecked() - else: - self.fsck = fsck - if order == -1: - if mountpoint == '/': - self.order = 1 - elif self.fsck: - self.order = 2 - else: - self.order = 0 - else: - self.order = order - if format and not fsystem.isFormattable(): - raise RuntimeError, ("file system type %s is not formattable, " - "but has been added to fsset with format " - "flag on" % fsystem.getName()) - self.format = format - self.fsprofile = fsprofile - - def mount(self, chroot='/', devPrefix='/dev', readOnly = 0): - device = self.device.setupDevice(chroot, devPrefix=devPrefix) - - self.fsystem.mount(device, "%s" % (self.mountpoint,), - readOnly = readOnly, - bindMount = isinstance(self.device, - BindMountDevice), - instroot = chroot) - - self.mountcount = self.mountcount + 1 - - def umount(self, chroot='/'): - if self.mountcount > 0: - try: - self.fsystem.umount(self.device, "%s/%s" % (chroot, - self.mountpoint)) - self.mountcount = self.mountcount - 1 - except RuntimeError: - pass - - def setFileSystemType(self, fstype): - self.fsystem = fstype - - def getMountPoint(self): - return self.mountpoint - - def getOptions(self): - options = self.options - if not options: - options = self.fsystem.getDefaultOptions(self.mountpoint) - return options + self.device.getDeviceOptions() - - def setFormat (self, state): - if self.migrate and state: - raise ValueError, "Trying to set format bit on when migrate is set!" - self.format = state - - def getFormat (self): - return self.format - - def setMigrate (self, state): - if self.format and state: - raise ValueError, "Trying to set migrate bit on when format is set!" - - self.migrate = state - - def getMigrate (self): - return self.migrate - - def setResizeTarget (self, targetsize, size): - if not self.fsystem.isResizable() and targetsize is not None: - raise ValueError, "Can't set a resize target for a non-resizable filesystem" - self.resizeTargetSize = targetsize - self.resizeOrigSize = size - - def getResizeTarget (self): - return self.resizeTargetSize - - def isMounted (self): - return self.mountcount > 0 - - def getLabel (self): - return self.label - - def getUuid (self): - return isys.readFSUuid(self.device.getDevice()) - - def setLabel (self, label): - self.label = label - - def __str__(self): - if not self.mountpoint: - mntpt = "None" - else: - mntpt = self.mountpoint - - str = ("fsentry -- device: %(device)s mountpoint: %(mountpoint)s\n" - " fsystem: %(fsystem)s format: %(format)s\n" - " ismounted: %(mounted)s options: '%(options)s'\n" - " label: %(label)s fsprofile: %(fsprofile)s\n"% - {"device": self.device.getDevice(), "mountpoint": mntpt, - "fsystem": self.fsystem.getName(), "format": self.format, - "mounted": self.mountcount, "options": self.getOptions(), - "label": self.label, "fsprofile": self.fsprofile}) - return str - - -class Device: - def __init__(self, device = "none", encryption=None): - self.device = device - self.label = None - self.isSetup = 0 - self.doLabel = 1 - self.deviceOptions = "" - if encryption: - self.crypto = encryption - # mount by device since the name is based only on UUID - self.doLabel = None - if device not in ("none", None): - self.crypto.setDevice(device) - else: - self.crypto = None - - def getComment (self): - return "" - - def getDevice (self, asBoot = 0): - if self.crypto: - return self.crypto.getDevice() - else: - return self.device - - def setupDevice (self, chroot='/', devPrefix='/dev/'): - return self.device - - def cleanupDevice (self, chroot, devPrefix='/dev/'): - if self.crypto: - self.crypto.closeDevice() - - def solidify (self): - pass - - def getName(self): - return self.__class__.__name__ - - def getLabel(self): - try: - return isys.readFSLabel(self.setupDevice()) - except: - return "" - - def setAsNetdev(self): - """Ensure we're set up so that _netdev is in our device options.""" - if "_netdev" not in self.deviceOptions: - self.deviceOptions += ",_netdev" - - def isNetdev(self): - """Check to see if we're set as a netdev""" - if "_netdev" in self.deviceOptions: - return True - return False - - def getDeviceOptions(self): - return self.deviceOptions - -class DevDevice(Device): - """Device with a device node rooted in /dev that we just always use - the pre-created device node for.""" - def __init__(self, dev): - Device.__init__(self, device=dev) - - def getDevice(self, asBoot = 0): - return self.device - - def setupDevice(self, chroot='/', devPrefix='/dev'): - #We use precreated device but we have to make sure that the device exists - path = '/dev/%s' % (self.getDevice(),) - return path - -class RAIDDevice(Device): - # XXX usedMajors does not take in account any EXISTING md device - # on the system for installs. We need to examine all partitions - # to investigate which minors are really available. - usedMajors = {} - - # members is a list of Device based instances that will be - # a part of this raid device - def __init__(self, level, members, minor=-1, spares=0, existing=0, - chunksize = 64, encryption=None): - Device.__init__(self, encryption=encryption) - self.level = level - self.members = members - self.spares = spares - self.numDisks = len(members) - spares - self.isSetup = existing - self.doLabel = None - if chunksize is not None: - self.chunksize = chunksize - else: - self.chunksize = 256 - - if len(members) < spares: - raise RuntimeError, ("you requested more spare devices " - "than online devices!") - - if level == 5: - if self.numDisks < 3: - raise RuntimeError, "RAID 5 requires at least 3 online members" - - # there are 32 major md devices, 0...31 - if minor == -1 or minor is None: - for I in range(32): - if not RAIDDevice.usedMajors.has_key(I): - minor = I - break - - if minor == -1: - raise RuntimeError, ("Unable to allocate minor number for " - "raid device") - - RAIDDevice.usedMajors[minor] = None - self.device = "md" + str(minor) - self.minor = minor - - if self.crypto: - self.crypto.setDevice(self.device) - - # make sure the list of raid members is sorted - self.members.sort(cmp=lambda x,y: cmp(x.getDevice(),y.getDevice())) - - def __del__ (self): - del RAIDDevice.usedMajors[self.minor] - - def ext2Args (self): - if self.level == 5: - return [ '-R', 'stride=%d' % ((self.numDisks - 1) * 16) ] - elif self.level == 0: - return [ '-R', 'stride=%d' % (self.numDisks * 16) ] - return [] - - def mdadmLine (self, devPrefix="/dev"): - levels = { 0: "raid0", - 1: "raid1", - 4: "raid5", - 5: "raid5", - 6: "raid6", - 10: "raid10" } - - # If we can't find the device for some reason, revert to old behavior. - try: - (dev, devices, level, numActive) = raid.lookup_raid_device (self.device) - except KeyError: - devices = [] - - # First loop over all the devices that make up the RAID trying to read - # the superblock off each. If we read a superblock, return a line that - # can go into the mdadm.conf. If we fail, fall back to the old method - # of using the super-minor. - for d in devices: - try: - (major, minor, uuid, level, nrDisks, totalDisks, mdMinor) = \ - isys.raidsb(d) - return "ARRAY %s/%s level=%s num-devices=%d uuid=%s\n" \ - %(devPrefix, self.device, levels[level], nrDisks, uuid) - except ValueError: - pass - - return "ARRAY %s/%s super-minor=%s\n" %(devPrefix, self.device, - self.minor) - - def raidTab (self, devPrefix='/dev'): - entry = "" - entry = entry + "raiddev %s/%s\n" % (devPrefix, - self.device,) - entry = entry + "raid-level %d\n" % (self.level,) - entry = entry + "nr-raid-disks %d\n" % (self.numDisks,) - entry = entry + "chunk-size %s\n" %(self.chunksize,) - entry = entry + "persistent-superblock 1\n" - entry = entry + "nr-spare-disks %d\n" % (self.spares,) - i = 0 - for device in [m.getDevice() for m in self.members[:self.numDisks]]: - entry = entry + " device %s/%s\n" % (devPrefix, - device) - entry = entry + " raid-disk %d\n" % (i,) - i = i + 1 - i = 0 - for device in [m.getDevice() for m in self.members[self.numDisks:]]: - entry = entry + " device %s/%s\n" % (devPrefix, - device) - entry = entry + " spare-disk %d\n" % (i,) - i = i + 1 - return entry - - def setupDevice (self, chroot="/", devPrefix='/dev'): - if not self.isSetup: - memberDevs = [] - for pd in self.members: - memberDevs.append(pd.setupDevice(chroot, devPrefix=devPrefix)) - if pd.isNetdev(): self.setAsNetdev() - - args = ["--create", "/dev/%s" %(self.device,), - "--run", "--chunk=%s" %(self.chunksize,), - "--level=%s" %(self.level,), - "--raid-devices=%s" %(self.numDisks,)] - - if self.spares > 0: - args.append("--spare-devices=%s" %(self.spares,),) - - args.extend(memberDevs) - log.info("going to run: %s" %(["mdadm"] + args,)) - iutil.execWithRedirect ("mdadm", args, - stderr="/dev/tty5", stdout="/dev/tty5", - searchPath = 1) - raid.register_raid_device(self.device, - [m.getDevice() for m in self.members], - self.level, self.numDisks) - self.isSetup = 1 - else: - isys.raidstart(self.device, self.members[0].getDevice()) - - if self.crypto: - self.crypto.formatDevice() - self.crypto.openDevice() - node = "%s/%s" % (devPrefix, self.crypto.getDevice()) - else: - node = "%s/%s" % (devPrefix, self.device) - - return node - - def getDevice (self, asBoot = 0): - if not asBoot and self.crypto: - return self.crypto.getDevice() - elif not asBoot: - return self.device - else: - return self.members[0].getDevice(asBoot=asBoot) - - def solidify(self): - return - -ext2 = fileSystemTypeGet("ext2") -ext2.registerDeviceArgumentFunction(RAIDDevice, RAIDDevice.ext2Args) - -class VolumeGroupDevice(Device): - def __init__(self, name, physvols, pesize = 32768, existing = 0): - """Creates a VolumeGroupDevice. - - name is the name of the volume group - physvols is a list of Device objects which are the physical volumes - pesize is the size of physical extents in kilobytes - existing is whether this vg previously existed. - """ - - Device.__init__(self) - self.physicalVolumes = physvols - self.isSetup = existing - self.name = name - self.device = name - self.isSetup = existing - - self.physicalextentsize = pesize - - def setupDevice (self, chroot="/", devPrefix='/dev/'): - nodes = [] - for volume in self.physicalVolumes: - # XXX the lvm tools are broken and will only work for /dev - node = volume.setupDevice(chroot, devPrefix="/dev") - if volume.isNetdev(): self.setAsNetdev() - - # XXX I should check if the pv is set up somehow so that we - # can have preexisting vgs and add new pvs to them. - if not self.isSetup: - lvm.pvcreate(node) - nodes.append(node) - - if not self.isSetup: - lvm.vgcreate(self.name, self.physicalextentsize, nodes) - self.isSetup = 1 - else: - lvm.vgscan() - lvm.vgactivate() - - return "/dev/%s" % (self.name,) - - def solidify(self): - return - -class LogicalVolumeDevice(Device): - # note that size is in megabytes! - def __init__(self, vgname, size, lvname, vg, existing = 0, encryption=None): - Device.__init__(self, encryption=encryption) - self.vgname = vgname - self.size = size - self.name = lvname - self.isSetup = 0 - self.isSetup = existing - self.doLabel = None - self.vg = vg - - # these are attributes we might want to expose. or maybe not. - # self.chunksize - # self.stripes - # self.stripesize - # self.extents - # self.readaheadsectors - - def setupDevice(self, chroot="/", devPrefix='/dev', vgdevice = None): - if self.crypto: - self.crypto.setDevice("mapper/%s-%s" % (self.vgname, self.name)) - - if not self.isSetup: - lvm.lvcreate(self.name, self.vgname, self.size) - self.isSetup = 1 - - if vgdevice and vgdevice.isNetdev(): - self.setAsNetdev() - - if self.crypto: - self.crypto.formatDevice() - self.crypto.openDevice() - - return "/dev/%s" % (self.getDevice(),) - - def getDevice(self, asBoot = 0): - if self.crypto and not asBoot: - device = self.crypto.getDevice() - else: - device = "%s/%s" % (self.vgname, self.name) - - return device - - def solidify(self): - return - - -class PartitionDevice(Device): - def __init__(self, partition, encryption=None): - if type(partition) != types.StringType: - raise ValueError, "partition must be a string" - Device.__init__(self, device=partition, encryption=encryption) - - (disk, pnum) = getDiskPart(partition) - if isys.driveIsIscsi(disk): - self.setAsNetdev() - - def getDevice(self, asBoot = 0): - if self.crypto and not asBoot: - return self.crypto.getDevice() - else: - return self.device - - def setupDevice(self, chroot="/", devPrefix='/dev'): - path = '%s/%s' % (devPrefix, self.device) - if self.crypto: - self.crypto.formatDevice() - self.crypto.openDevice() - path = "%s/%s" % (devPrefix, self.crypto.getDevice()) - return path - -class PartedPartitionDevice(PartitionDevice): - def __init__(self, partition): - Device.__init__(self) - self.device = None - self.partition = partition - - def getDevice(self, asBoot = 0): - if not self.partition: - return self.device - - return self.partition.getDeviceNodeName() - - def solidify(self): - # drop reference on the parted partition object and note - # the current minor number allocation - self.device = self.getDevice() - self.partition = None - -class BindMountDevice(Device): - def __init__(self, directory): - Device.__init__(self) - self.device = directory - - def setupDevice(self, chroot="/", devPrefix="/tmp"): - return chroot + self.device - -class SwapFileDevice(Device): - def __init__(self, file): - Device.__init__(self) - self.device = file - self.size = 0 - - def setSize (self, size): - self.size = size - - def setupDevice (self, chroot="/", devPrefix='/dev'): - file = os.path.normpath(chroot + self.getDevice()) - if not os.access(file, os.R_OK): - if self.size: - # make sure the permissions are set properly - fd = os.open(file, os.O_CREAT, 0600) - os.close(fd) - isys.ddfile(file, self.size, None) - else: - raise SystemError, (0, "swap file creation necessary, but " - "required size is unknown.") - return file - -# This is a device that describes a swap file that is sitting on -# the loopback filesystem host for partitionless installs. -# The piggypath is the place where the loopback file host filesystem -# will be mounted -class PiggybackSwapFileDevice(SwapFileDevice): - def __init__(self, piggypath, file): - SwapFileDevice.__init__(self, file) - self.piggypath = piggypath - - def setupDevice(self, chroot="/", devPrefix='/dev'): - return SwapFileDevice.setupDevice(self, self.piggypath, devPrefix) - -class LoopbackDevice(Device): - def __init__(self, hostPartition, hostFs): - Device.__init__(self) - self.host = "/dev/" + hostPartition - self.hostfs = hostFs - self.device = "loop1" - - def setupDevice(self, chroot="/", devPrefix='/dev/'): - if not self.isSetup: - isys.mount(self.host[5:], "/mnt/loophost", fstype = "vfat") - self.device = allocateLoopback("/mnt/loophost/redhat.img") - if not self.device: - raise SystemError, "Unable to allocate loopback device" - self.isSetup = 1 - path = '%s/%s' % (devPrefix, self.getDevice()) - else: - path = '%s/%s' % (devPrefix, self.getDevice()) - path = os.path.normpath(path) - return path - - def getComment (self): - return "# LOOP1: %s %s /redhat.img\n" % (self.host, self.hostfs) - -def makeDevice(dev): - cryptoDev = partitions.lookup_cryptodev(dev) - if cryptoDev and cryptoDev.getDevice() == dev: - dev = cryptoDev.getDevice(encrypted=True) - - if dev.startswith('md'): - try: - (mdname, devices, level, numActive) = raid.lookup_raid_device(dev) - # convert devices to Device instances and sort out encryption - devList = [] - for dev in devices: - cryptoMem = partitions.lookup_cryptodev(dev) - if cryptoMem and cryptoMem.getDevice() == dev: - dev = cryptoMem.getDevice(encrypted=True) - - devList.append(PartitionDevice(dev, encryption=cryptoMem)) - - device = RAIDDevice(level, devList, - minor=int(mdname[2:]), - spares=len(devices) - numActive, - existing=1, encryption=cryptoDev) - except KeyError: - device = PartitionDevice(dev, encryption=cryptoDev) - else: - device = PartitionDevice(dev, encryption=cryptoDev) - return device - -def findBackingDevInCrypttab(mappingName): - backingDev = None - try: - lines = open("/mnt/sysimage/etc/crypttab").readlines() - except IOError, e: - pass - else: - for line in lines: - fields = line.split() - if len(fields) < 2: - continue - if fields[0] == mappingName: - backingDev = fields[1] - break - - return backingDev - -# XXX fix RAID -def readFstab (anaconda): - def createMapping(dict): - mapping = {} - dupes = [] - - for device, info in dict.items(): - if not mapping.has_key(info): - mapping[info] = device - elif not info in dupes: - dupes.append(info) - - return (mapping, dupes) - - def showError(label, intf): - if intf: - intf.messageWindow(_("Duplicate Labels"), - _("Multiple devices on your system are " - "labelled %s. Labels across devices must be " - "unique for your system to function " - "properly.\n\n" - "Please fix this problem and restart the " - "installation process.") %(label,), - type="custom", custom_icon="error", - custom_buttons=[_("_Exit installer")]) - sys.exit(0) - else: - log.warning("Duplicate labels for %s, but no intf so trying " - "to continue" %(label,)) - - path = anaconda.rootPath + '/etc/fstab' - intf = anaconda.intf - fsset = FileSystemSet() - - # first, we look at all the disks on the systems and get any ext2/3 - # labels off of the filesystem. - # temporary, to get the labels - diskset = partedUtils.DiskSet(anaconda) - diskset.openDevices() - labels = diskset.getInfo() - uuids = diskset.getInfo(readFn=lambda d: isys.readFSUuid(d)) - - (labelToDevice, labelDupes) = createMapping(labels) - (uuidToDevice, uuidDupes) = createMapping(uuids) - - loopIndex = {} - - f = open (path, "r") - lines = f.readlines () - f.close() - - for line in lines: - fields = string.split (line) - - if not fields: continue - - if line[0] == "#": - # skip all comments - continue - - # all valid fstab entries have 6 fields; if the last two are missing - # they are assumed to be zero per fstab(5) - if len(fields) < 4: - continue - elif len(fields) == 4: - fields.append(0) - fields.append(0) - elif len(fields) == 5: - fields.append(0) - elif len(fields) > 6: - continue - if string.find(fields[3], "noauto") != -1: continue - - # shenanigans to handle ext3,ext2 format in fstab - fstotry = fields[2] - if fstotry.find(","): - fstotry = fstotry.split(",") - else: - fstotry = [ fstotry ] - - # ext4 used to be listed as ext4dev, so handle that possibility - for i in range(0, len(fstotry)): - if fstotry[i] == "ext4dev": - fstotry[i] = "ext4" - - fsystem = None - for fs in fstotry: - # if we don't support mounting the filesystem, continue - if not fileSystemTypes.has_key(fs): - continue - fsystem = fileSystemTypeGet(fs) - break - # "none" is valid as an fs type for bind mounts (#151458) - if fsystem is None and (string.find(fields[3], "bind") == -1): - continue - - label = None - if fields[0] == "none": - device = Device() - elif ((string.find(fields[3], "bind") != -1) and - fields[0].startswith("/")): - # it's a bind mount, they're Weird (tm) - device = BindMountDevice(fields[0]) - fsystem = fileSystemTypeGet("bind") - elif len(fields) >= 6 and fields[0].startswith('LABEL='): - label = fields[0][6:] - if label in labelDupes: - showError(label, intf) - - if labelToDevice.has_key(label): - device = makeDevice(labelToDevice[label]) - else: - log.warning ("fstab file has LABEL=%s, but this label " - "could not be found on any file system", label) - # bad luck, skip this entry. - continue - elif len(fields) >= 6 and fields[0].startswith('UUID='): - uuid = fields[0][5:] - if uuid in uuidDupes: - showError(uuid, intf) - - if uuidToDevice.has_key(uuid): - device = makeDevice(uuidToDevice[uuid]) - else: - log.warning ("fstab file has UUID=%s, but this UUID" - "could not be found on any file system", uuid) - # bad luck, skip this entry. - continue - elif fields[2] == "swap" and not fields[0].startswith('/dev/'): - # swap files - file = fields[0] - - if file.startswith('/initrd/loopfs/'): - file = file[14:] - device = PiggybackSwapFileDevice("/mnt/loophost", file) - else: - device = SwapFileDevice(file) - elif fields[0].startswith('/dev/loop'): - # look up this loop device in the index to find the - # partition that houses the filesystem image - # XXX currently we assume /dev/loop1 - if loopIndex.has_key(device): - (dev, fs) = loopIndex[device] - device = LoopbackDevice(dev, fs) - elif fields[0].startswith("/dev/mapper/luks-"): - backingDev = findBackingDevInCrypttab(fields[0][12:]) - log.debug("device %s has backing device %s" % (fields[0], - backingDev)) - if backingDev is None: - log.error("unable to resolve backing device for %s" % fields[0]) - continue - elif backingDev.startswith('LABEL='): - label = backingDev[6:] - if label in labelDupes: - showError(label, intf) - - if labelToDevice.has_key(label): - device = makeDevice(labelToDevice[label]) - else: - log.warning ("crypttab file has LABEL=%s, but this label " - "could not be found on any file system", label) - # bad luck, skip this entry. - continue - elif backingDev.startswith('UUID='): - uuid = backingDev[5:] - if uuid in uuidDupes: - showError(uuid, intf) - - if uuidToDevice.has_key(uuid): - device = makeDevice(uuidToDevice[uuid]) - else: - log.warning ("crypttab file has UUID=%s, but this UUID" - "could not be found on any file system", uuid) - # bad luck, skip this entry. - continue - else: - device = makeDevice(backingDev[5:]) - elif fields[0].startswith('/dev/'): - # Older installs may have lines starting with things like /dev/proc - # so watch out for that on upgrade. - if fsystem is not None and isinstance(fsystem, PseudoFileSystem): - device = Device(device = fields[0][5:]) - else: - device = makeDevice(fields[0][5:]) - else: - device = Device(device = fields[0]) - - # if they have a filesystem being mounted as auto, we need - # to sniff around a bit to figure out what it might be - # if we fail at all, though, just ignore it - if fsystem == "auto" and device.getDevice() != "none": - try: - tmp = isys.readFSType("/dev/%s" %(device.setupDevice(),)) - if tmp is not None: - fsystem = tmp - except: - pass - - entry = FileSystemSetEntry(device, fields[1], fsystem, fields[3], - origfsystem=fsystem) - if label: - entry.setLabel(label) - fsset.add(entry) - return fsset - -def allocateLoopback(file): - found = 1 - for i in range(8): - path = "/dev/loop%d" % (i,) - try: - isys.losetup(path, file) - found = 1 - except SystemError: - continue - break - if found: - return path - return None - -def ext2FormatFilesystem(argList, messageFile, windowCreator, mntpoint): - if windowCreator: - w = windowCreator(_("Formatting"), - _("Formatting %s file system...") % (mntpoint,), 100) - else: - w = None - - fd = os.open(messageFile, os.O_RDWR | os.O_CREAT | os.O_APPEND) - p = os.pipe() - childpid = os.fork() - if not childpid: - os.close(p[0]) - os.dup2(p[1], 1) - os.dup2(fd, 2) - os.close(p[1]) - os.close(fd) - - env = os.environ - configs = [ "/tmp/updates/mke2fs.conf", - "/etc/mke2fs.conf", - ] - for config in configs: - if os.access(config, os.R_OK): - env['MKE2FS_CONFIG'] = config - break - - os.execvpe(argList[0], argList, env) - log.critical("failed to exec %s", argList) - os._exit(1) - - os.close(p[1]) - - # ignoring SIGCHLD would be cleaner then ignoring EINTR, but - # we can't use signal() in this thread? - - s = 'a' - while s and s != '\b': - try: - s = os.read(p[0], 1) - except OSError, args: - (num, str) = args - if (num != 4): - raise IOError, args - - os.write(fd, s) - - num = '' - while s: - try: - s = os.read(p[0], 1) - os.write(fd, s) - - if s != '\b': - try: - num = num + s - except: - pass - else: - if num and len(num): - l = string.split(num, '/') - try: - val = (int(l[0]) * 100) / int(l[1]) - except (IndexError, TypeError): - pass - else: - w and w.set(val) - num = '' - except OSError, args: - (errno, str) = args - if (errno != 4): - raise IOError, args - - try: - (pid, status) = os.waitpid(childpid, 0) - except OSError, (num, msg): - log.critical("exception from waitpid while formatting: %s %s" %(num, msg)) - status = None - os.close(fd) - - w and w.pop() - - # *shrug* no clue why this would happen, but hope that things are fine - if status is None: - return 0 - - if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0): - return 0 - - return 1 - -# copy and paste job from booty/bootloaderInfo.py... -def getDiskPart(dev): - cut = len(dev) - if (dev.startswith('rd/') or dev.startswith('ida/') or - dev.startswith('cciss/') or dev.startswith('sx8/') or - dev.startswith('mapper/') or dev.startswith('mmcblk')): - if dev[-2] == 'p': - cut = -1 - elif dev[-3] == 'p': - cut = -2 - else: - if dev[-2] in string.digits: - cut = -2 - elif dev[-1] in string.digits: - cut = -1 - - name = dev[:cut] - - # hack off the trailing 'p' from /dev/cciss/*, for example - if name[-1] == 'p': - for letter in name: - if letter not in string.letters and letter != "/": - name = name[:-1] - break - - if cut < 0: - partNum = int(dev[cut:]) - 1 - else: - partNum = None - - return (name, partNum) @@ -741,7 +741,7 @@ class SaveExceptionWindow: store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) - dests = anaconda.id.diskset.exceptionDisks(anaconda) + dests = anaconda.id.storage.exceptionDisks() if flags.livecdInstall: self.destCombo.remove_text(0) @@ -751,7 +751,7 @@ class SaveExceptionWindow: elif len(dests) > 0: for d in dests: iter = store.append(None) - store[iter] = ("/dev/%s" % d[0], "/dev/%s - %s" % (d[0], d[1])) + store[iter] = (d[0], "%s - %s" % (d[0], d[1])) self.diskCombo.set_model(store) self.diskCombo.set_active(0) @@ -1266,10 +1266,6 @@ class InstallInterface: if anaconda.id.keyboard and not anaconda.id.x_already_set: anaconda.id.keyboard.activate() - anaconda.id.fsset.registerMessageWindow(self.messageWindow) - anaconda.id.fsset.registerProgressWindow(self.progressWindow) - anaconda.id.fsset.registerWaitWindow(self.waitWindow) - self.icw = InstallControlWindow (self.anaconda) self.icw.run (self.runres) @@ -47,7 +47,7 @@ def findIsoImages(path, messageWindow): try: isys.mount("/dev/loop2", "/mnt/cdimage", fstype = "iso9660", - readOnly = 1) + readOnly = True) for num in range(1, 10): if os.access("/mnt/cdimage/.discinfo", os.R_OK): f = open("/mnt/cdimage/.discinfo") @@ -95,7 +95,7 @@ def findIsoImages(path, messageWindow): discImages[num] = file - isys.umount("/mnt/cdimage", removeDir=0) + isys.umount("/mnt/cdimage", removeDir=False) except SystemError: pass @@ -179,7 +179,7 @@ def mountImage(isodir, tree, discnum, messageWindow, discImages={}): try: isoImage = "%s/%s" % (isodir, discImages[discnum]) isys.losetup("/dev/loop1", isoImage, readOnly = 1) - isys.mount("/dev/loop1", tree, fstype = 'iso9660', readOnly = 1); + isys.mount("/dev/loop1", tree, fstype = 'iso9660', readOnly = True); break except: ans = messageWindow(_("Missing ISO 9660 Image"), @@ -249,13 +249,13 @@ def presentRequiredMediaMessage(anaconda): # Find an attached CD/DVD drive with media in it that contains packages, # and return that device name. -def scanForMedia(tree): - drive = None +def scanForMedia(tree, storage): + for dev in storage.devicetree.devices.values(): + if dev.type != "cdrom": + continue - for cdr in map(lambda d: "/dev/%s" % d, isys.cdromList()): try: - if isys.mount(cdr, tree, fstype="iso9660", readOnly=1): - continue + dev.format.mount(mountpoint=tree) except: continue @@ -263,23 +263,22 @@ def scanForMedia(tree): isys.umount(tree) continue - drive = cdr - break + return dev.name - return drive + return None def umountImage(tree, currentMedia): if currentMedia is not None: - isys.umount(tree, removeDir=0) + isys.umount(tree, removeDir=False) isys.unlosetup("/dev/loop1") -def unmountCD(path, messageWindow): - if not path: +def unmountCD(dev, messageWindow): + if not dev: return while True: try: - isys.umount(path, removeDir=0) + dev.format.unmount() break except Exception, e: log.error("exception in _unmountCD: %s" %(e,)) @@ -288,7 +287,7 @@ def unmountCD(path, messageWindow): "Please make sure you're not accessing " "%s from the shell on tty2 " "and then click OK to retry.") - % (path,)) + % (dev.path,)) def verifyMedia(tree, discnum, timestamp=None): if os.access("%s/.discinfo" % tree, os.R_OK): diff --git a/installclass.py b/installclass.py index 832fe7d76..bca365e32 100644 --- a/installclass.py +++ b/installclass.py @@ -29,7 +29,6 @@ import imputil import types from instdata import InstallData -from autopart import getAutopartitionBoot, autoCreatePartitionRequests, autoCreateLVMPartitionRequests from constants import * from filer import * @@ -95,14 +94,14 @@ class BaseInstallClass(object): "language", "keyboard", "welcome", + "storageinit", "findrootparts", "betanag", "installtype", - "partitionobjinit", "parttype", "autopartitionexecute", "partition", - "partitiondone", + "storagedone", "bootloadersetup", "bootloader", "network", @@ -116,7 +115,6 @@ class BaseInstallClass(object): "reipl", "install", "enablefilesystems", - "migratefilesystems", "setuptime", "preinstallconfig", "installpackages", @@ -188,25 +186,24 @@ class BaseInstallClass(object): from backend import AnacondaBackend return AnacondaBackend - def setDefaultPartitioning(self, partitions, clear = CLEARPART_TYPE_LINUX, - doClear = 1, useLVM = True): - autorequests = [ ("/", None, 1024, None, 1, 1, 1) ] + def setDefaultPartitioning(self, storage, platform, + clear = CLEARPART_TYPE_LINUX, doClear = True): + autorequests = [ ("/", storage.defaultFSType, 1024, None, True, True) ] - bootreq = getAutopartitionBoot(partitions) + bootreq = platform.setDefaultPartitioning() if bootreq: autorequests.extend(bootreq) (minswap, maxswap) = iutil.swapSuggestion() - autorequests.append((None, "swap", minswap, maxswap, 1, 1, 1)) + autorequests.append((None, "swap", minswap, maxswap, True, True)) if doClear: - partitions.autoClearPartType = clear - partitions.autoClearPartDrives = [] - - if useLVM: - partitions.autoPartitionRequests = autoCreateLVMPartitionRequests(autorequests) + storage.clearPartType = clear + storage.clearPartDisks = [] else: - partitions.autoPartitionRequests = autoCreatePartitionRequests(autorequests) + storage.clearPartType = CLEARPART_TYPE_NONE + + storage.autoPartitionRequests = autorequests def setInstallData(self, anaconda): diff --git a/installclasses/fedora.py b/installclasses/fedora.py index 0eda0b5c8..d2ddafa1f 100644 --- a/installclasses/fedora.py +++ b/installclasses/fedora.py @@ -63,7 +63,9 @@ class InstallClass(BaseInstallClass): BaseInstallClass.setInstallData(self, anaconda) if not anaconda.isKickstart: - BaseInstallClass.setDefaultPartitioning(self, anaconda.id.partitions, + BaseInstallClass.setDefaultPartitioning(self, + anaconda.id.storage, + anaconda.platform, CLEARPART_TYPE_LINUX) def setSteps(self, anaconda): @@ -78,6 +80,9 @@ class InstallClass(BaseInstallClass): return yuminstall.YumBackend def productMatches(self, oldprod): + if oldprod is None: + return False + if oldprod.startswith(productName): return True diff --git a/installclasses/rhel.py b/installclasses/rhel.py index ddfb89700..da25946f3 100644 --- a/installclasses/rhel.py +++ b/installclasses/rhel.py @@ -87,9 +87,11 @@ class InstallClass(BaseInstallClass): def setInstallData(self, anaconda): BaseInstallClass.setInstallData(self, anaconda) + if not anaconda.isKickstart: BaseInstallClass.setDefaultPartitioning(self, - anaconda.id.partitions, + anaconda.id.storage, + anaconda.platform, CLEARPART_TYPE_LINUX) def setSteps(self, anaconda): diff --git a/installmethod.py b/installmethod.py index ce1dd7f99..c412c944a 100644 --- a/installmethod.py +++ b/installmethod.py @@ -34,22 +34,22 @@ def doMethodComplete(anaconda): return None if anaconda.mediaDevice: - return anaconda.mediaDevice + return anaconda.id.storage.devicetree.getDeviceByName(anaconda.mediaDevice) # If we booted off the boot.iso instead of disc 1, eject that as well. if anaconda.stage2 and anaconda.stage2.startswith("cdrom://"): dev = anaconda.stage2[8:].split(':')[0] - return dev + return anaconda.id.storage.devicetree.getDeviceByName(dev) anaconda.backend.complete(anaconda) dev = _ejectDevice() if dev: - isys.ejectCdrom(dev) + dev.eject() mtab = "/dev/root / ext3 ro 0 0\n" - for ent in anaconda.id.fsset.entries: - if ent.mountpoint == "/": - mtab = "/dev/root / %s ro 0 0\n" %(ent.fsystem.name,) + rootDevice = anaconda.id.storage.fsset.rootDevice + if rootDevice: + mtab = "/dev/root / %s ro 0 0\n" % rootDevice.format.type f = open(anaconda.rootPath + "/etc/mtab", "w+") f.write(mtab) diff --git a/instdata.py b/instdata.py index 25aee6157..0b6df52f6 100644 --- a/instdata.py +++ b/instdata.py @@ -30,12 +30,8 @@ import firewall import security import timezone import desktop -import fsset -import bootloader -import partitions -import partedUtils -import iscsi -import zfcp +import booty +import storage import urllib import iutil import isys @@ -67,8 +63,6 @@ class InstallData: self.instClass = None self.network = network.Network() - self.iscsi = iscsi.iscsi() - self.zfcp = zfcp.ZFCP() self.firewall = firewall.Firewall() self.security = security.Security() self.timezone = timezone.Timezone() @@ -80,11 +74,8 @@ class InstallData: self.upgrade = None if flags.cmdline.has_key("preupgrade"): self.upgrade = True - # XXX move fsset and/or diskset into Partitions object? - self.fsset.reset() - self.diskset = partedUtils.DiskSet(self.anaconda) - self.partitions = partitions.Partitions(self.anaconda) - self.bootloader = bootloader.getBootloader() + self.storage = storage.Storage(self.anaconda) + self.bootloader = booty.getBootloader(self.storage) self.upgradeRoot = None self.rootParts = None self.upgradeSwapInfo = None @@ -97,39 +88,6 @@ class InstallData: # XXX I still expect this to die when kickstart is the data store. self.ksdata = None - # We don't have install methods anymore, but put things that depend on - # the methodstr here. - if os.path.exists("/dev/live") and \ - stat.S_ISBLK(os.stat("/dev/live")[stat.ST_MODE]): - target = os.readlink("/dev/live") - self.partitions.protected = [target] - elif self.anaconda.methodstr and self.anaconda.methodstr.startswith("hd:"): - method = self.anaconda.methodstr[3:] - device = method.split(":", 3)[0] - - if device.startswith("LABEL="): - dev = isys.getDeviceByToken("LABEL", device[6:]) - elif device.startswith("UUID="): - dev = isys.getDeviceByToken("UUID", device[5:]) - else: - dev = device - - if dev is None: - if self.getUpgrade(): - return - else: - self.anaconda.intf.messageWindow(_("Unknown Device"), - _("The installation source given by device %s " - "could not be found. Please check your " - "parameters and try again.") % device, - type="custom", custom_buttons = [_("_Exit installer")]) - sys.exit(1) - - if dev.startswith("/dev/"): - dev = dev[5:] - - self.partitions.protected = [dev] - def setInstallProgressClass(self, c): self.instProgress = c @@ -286,7 +244,6 @@ class InstallData: if not self.isHeadless: self.keyboard.writeKS(f) self.network.writeKS(f) - self.zfcp.writeKS(f) if self.rootPassword["isCrypted"]: args = " --iscrypted %s" % self.rootPassword["password"] @@ -312,7 +269,8 @@ class InstallData: self.security.writeKS(f) self.timezone.writeKS(f) self.bootloader.writeKS(f) - self.partitions.writeKS(f) + self.storage.iscsi.writeKS(f) + self.storage.zfcp.writeKS(f) if self.backend is not None: self.backend.writeKS(f) @@ -339,6 +297,5 @@ class InstallData: self.videocard = None self.isHeadless = 0 self.extraModules = extraModules - self.fsset = fsset.FileSystemSet() self.reset() diff --git a/isys/iface.c b/isys/iface.c index fd9459dc7..1df5fa0cd 100644 --- a/isys/iface.c +++ b/isys/iface.c @@ -50,6 +50,7 @@ #include <nm-ip4-config.h> #include <nm-setting-ip4-config.h> +#include "isys.h" #include "iface.h" #include "str.h" diff --git a/isys/iface.h b/isys/iface.h index 59e384914..c95c39bd6 100644 --- a/isys/iface.h +++ b/isys/iface.h @@ -46,7 +46,6 @@ enum { IPV6_UNUSED_METHOD, IPV6_AUTO_METHOD, IPV6_DHCP_METHOD, /* Macros for starting NetworkManager */ #define NETWORKMANAGER "/usr/sbin/NetworkManager" -#define OUTPUT_TERMINAL "/dev/tty5" /* Per-interface configuration information */ typedef struct _iface_t { diff --git a/isys/imount.c b/isys/imount.c index e10220640..fd51fdefa 100644 --- a/isys/imount.c +++ b/isys/imount.c @@ -147,8 +147,10 @@ int doPwMount(char *dev, char *where, char *fs, char *options, char **err) { close(pipefd[1]); if (err != NULL) { - rc = readFD(pipefd[0], err); - rc = write(programLogFD, err, 4096); + if (*err != NULL) { + rc = readFD(pipefd[0], err); + rc = write(programLogFD, *err, 4096); + } } close(pipefd[0]); diff --git a/isys/isys.c b/isys/isys.c index 0813e5054..65ee688b3 100644 --- a/isys/isys.c +++ b/isys/isys.c @@ -291,7 +291,14 @@ static PyObject * doMount(PyObject * s, PyObject * args) { PyObject *tuple = PyTuple_New(2); PyTuple_SetItem(tuple, 0, PyInt_FromLong(rc)); - PyTuple_SetItem(tuple, 1, PyString_FromString(err)); + + if (err == NULL) { + Py_INCREF(Py_None); + PyTuple_SetItem(tuple, 1, Py_None); + } else { + PyTuple_SetItem(tuple, 1, PyString_FromString(err)); + } + PyErr_SetObject(PyExc_SystemError, tuple); } diff --git a/isys/isys.h b/isys/isys.h index 6b0695131..43355ca38 100644 --- a/isys/isys.h +++ b/isys/isys.h @@ -28,6 +28,8 @@ #define EARLY_SWAP_RAM 270000 #endif +#define OUTPUT_TERMINAL "/dev/tty5" + int insmod(char * modName, char * path, char ** args); int rmmod(char * modName); diff --git a/isys/isys.py b/isys/isys.py index e6c4b3476..1f4f22286 100755 --- a/isys/isys.py +++ b/isys/isys.py @@ -59,7 +59,6 @@ NM_STATE_CONNECTED = 3 NM_STATE_DISCONNECTED = 4 mountCount = {} -raidCount = {} MIN_RAM = _isys.MIN_RAM MIN_GUI_RAM = _isys.MIN_GUI_RAM @@ -71,194 +70,12 @@ EARLY_SWAP_RAM = _isys.EARLY_SWAP_RAM def pathSpaceAvailable(path): return _isys.devSpaceFree(path) -mdadmOutput = "/tmp/mdadmout" - -## An error occured when running mdadm. -class MdadmError(Exception): - ## The constructor. - # @param args The arguments passed to the mdadm command. - # @param name The the name of the RAID device used in the mdadm command. - def __init__(self, args, name=None): - self.args = args - self.name = name - self.log = self.getCmdOutput() - - ## Get the output of the last mdadm command run. - # @return The formatted output of the mdadm command which caused an error. - def getCmdOutput(self): - f = open(mdadmOutput, "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,) - command = "mdadm " + string.join(self.args, " ") - return "'%s' failed%s\nLog:\n%s" % (command, s, self.log) - -def _mdadm(*args): - try: - lines = iutil.execWithCapture("mdadm", args, stderr = mdadmOutput) - lines = string.split(lines, '\n') - lines = reduce(lambda x,y: x + [y.strip(),], lines, []) - return lines - except: - raise MdadmError, args - -def _getRaidInfo(drive): - log.info("mdadm -E %s" % (drive,)) - try: - lines = _mdadm("-E", drive) - except MdadmError: - ei = sys.exc_info() - ei[1].name = drive - raise ei[0], ei[1], ei[2] - - info = { - 'major': "-1", - 'minor': "-1", - 'uuid' : "", - 'level': -1, - 'nrDisks': -1, - 'totalDisks': -1, - 'mdMinor': -1, - } - - for line in lines: - vals = string.split(string.strip(line), ' : ') - if len(vals) != 2: - continue - if vals[0] == "Version": - vals = string.split(vals[1], ".") - info['major'] = vals[0] - info['minor'] = vals[1] - elif vals[0] == "UUID": - info['uuid'] = vals[1] - elif vals[0] == "Raid Level": - info['level'] = int(vals[1][4:]) - elif vals[0] == "Raid Devices": - info['nrDisks'] = int(vals[1]) - elif vals[0] == "Total Devices": - info['totalDisks'] = int(vals[1]) - elif vals[0] == "Preferred Minor": - info['mdMinor'] = int(vals[1]) - else: - continue - - if info['uuid'] == "": - raise ValueError, info - - return info - -def _stopRaid(mdDevice): - log.info("mdadm --stop %s" % (mdDevice,)) - try: - _mdadm("--stop", mdDevice) - except MdadmError: - ei = sys.exc_info() - ei[1].name = mdDevice - raise ei[0], ei[1], ei[2] - -def raidstop(mdDevice): - log.info("stopping raid device %s" %(mdDevice,)) - if raidCount.has_key (mdDevice): - if raidCount[mdDevice] > 1: - raidCount[mdDevice] = raidCount[mdDevice] - 1 - return - del raidCount[mdDevice] - - devInode = "/dev/%s" % mdDevice - - try: - _stopRaid(devInode) - except: - pass - -def _startRaid(mdDevice, mdMinor, uuid): - log.info("mdadm -A --uuid=%s --super-minor=%s %s" % (uuid, mdMinor, mdDevice)) - try: - _mdadm("-A", "--uuid=%s" % (uuid,), "--super-minor=%s" % (mdMinor,), \ - mdDevice) - except MdadmError: - ei = sys.exc_info() - ei[1].name = mdDevice - raise ei[0], ei[1], ei[2] - -def raidstart(mdDevice, aMember): - log.info("starting raid device %s" %(mdDevice,)) - if raidCount.has_key(mdDevice) and raidCount[mdDevice]: - raidCount[mdDevice] = raidCount[mdDevice] + 1 - return - - raidCount[mdDevice] = 1 - - mdInode = "/dev/%s" % mdDevice - mbrInode = "/dev/%s" % aMember - - if os.path.exists(mdInode): - minor = os.minor(os.stat(mdInode).st_rdev) - else: - minor = int(mdDevice[2:]) - try: - info = _getRaidInfo(mbrInode) - if info.has_key('mdMinor'): - minor = info['mdMinor'] - _startRaid(mdInode, minor, info['uuid']) - except: - pass - -## Remove the superblock from a RAID device. -# @param device The complete path to the RAID device name to wipe. -def wipeRaidSB(device): - try: - fd = os.open(device, os.O_WRONLY) - except OSError, e: - log.warning("error wiping raid device superblock for %s: %s", device, e) - return - - try: - _isys.wiperaidsb(fd) - finally: - os.close(fd) - return - -## Get the raw superblock from a RAID device. -# @param The basename of a RAID device to check. This device node does not -# need to exist to begin with. -# @return A RAID superblock in its raw on-disk format. -def raidsb(mdDevice): - return raidsbFromDevice("/dev/%s" % mdDevice) - -## Get the superblock from a RAID device. -# @param The full path to a RAID device name to check. This device node must -# already exist. -# @return A tuple of the contents of the RAID superblock, or ValueError on -# error. -def raidsbFromDevice(device): - try: - info = _getRaidInfo(device) - return (info['major'], info['minor'], info['uuid'], info['level'], - info['nrDisks'], info['totalDisks'], info['mdMinor']) - except: - raise ValueError - -def getRaidChunkFromDevice(device): - fd = os.open(device, os.O_RDONLY) - rc = 64 - try: - rc = _isys.getraidchunk(fd) - finally: - os.close(fd) - return rc - ## Set up an already existing device node to be used as a loopback device. # @param device The full path to a device node to set up as a loopback device. # @param file The file to mount as loopback on device. # @param readOnly Should this loopback device be used read-only? def losetup(device, file, readOnly = 0): + # FIXME: implement this as a storage.devices.Device subclass if readOnly: mode = os.O_RDONLY else: @@ -272,6 +89,7 @@ def losetup(device, file, readOnly = 0): os.close(targ) def lochangefd(device, file): + # FIXME: implement this as a storage.devices.Device subclass loop = os.open(device, os.O_RDONLY) targ = os.open(file, os.O_RDONLY) try: @@ -283,6 +101,7 @@ def lochangefd(device, file): ## Disable a previously setup loopback device. # @param device The full path to an existing loopback device node. def unlosetup(device): + # FIXME: implement this as a storage.devices.Device subclass loop = os.open(device, os.O_RDONLY) try: _isys.unlosetup(loop) @@ -311,7 +130,7 @@ def ddfile(file, megs, pw = None): os.close(fd) ## Mount a filesystem, similar to the mount system call. -# @param device The device to mount. If bindMount is 1, this should be an +# @param device The device to mount. If bindMount is True, this should be an # already mounted directory. Otherwise, it should be a device # name. # @param location The path to mount device on. @@ -322,7 +141,8 @@ def ddfile(file, megs, pw = None): # @param bindMount Is this a bind mount? (see the mount(8) man page) # @param remount Are we mounting an already mounted filesystem? # @return The return value from the mount system call. -def mount(device, location, fstype = "ext2", readOnly = 0, bindMount = 0, remount = 0, options = "defaults"): +def mount(device, location, fstype = "ext2", readOnly = False, + bindMount = False, remount = False, options = "defaults"): flags = None location = os.path.normpath(location) opts = string.split(options) @@ -361,7 +181,7 @@ def mount(device, location, fstype = "ext2", readOnly = 0, bindMount = 0, remoun # absolute path. # @param removeDir Should the mount point be removed after being unmounted? # @return The return value from the umount system call. -def umount(what, removeDir = 1): +def umount(what, removeDir = True): what = os.path.normpath(what) if not os.path.isdir(what): @@ -407,175 +227,6 @@ def swapon (path): def loadKeymap(keymap): return _isys.loadKeymap (keymap) -cachedDrives = None - -## Clear the drive dict cache. -# This method clears the drive dict cache. If the drive state changes (by -# loading and unloading modules, attaching removable devices, etc.) then this -# function must be called before any of the *DriveDict or *DriveList functions. -# If not, those functions will return information that does not reflect the -# current machine state. -def flushDriveDict(): - global cachedDrives - cachedDrives = None - -def driveDict(klassArg): - import parted - global cachedDrives - if cachedDrives is None: - new = {} - for dev in minihal.get_devices_by_type("storage"): - if dev['device'] is None: # none devices make no sense - continue - - device = dev['device'].replace('/dev/','') - # we can't actually use the sg devices, so ignore them - if device.startswith("sg"): - log.info("ignoring sg device %s" %(device,)) - continue - - # we can't actually use the st devices, so ignore them - if device.startswith("st"): - log.info("ignoring st device %s" %(device,)) - continue - - # we want to ignore md devices as they're not hard disks in our pov - if device.startswith("md"): - continue - - if dev['storage.drive_type'] != 'disk': - new[device] = dev - continue - try: - if not mediaPresent (device): - new[device] = dev - continue - - # blacklist the device which the live image is running from - # installing over that is almost certainly the wrong - # thing to do. - 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['device']): - log.info("%s looks to be the live device; ignoring" % (device,)) - continue - - if device.startswith("sd"): - peddev = parted.getDevice(dev['device']) - model = peddev.model - - # blacklist *STMF on power5 iSeries boxes - if iutil.isPPC() and \ - model.find("IBM *STMF KERNEL") != -1: - log.info("%s looks like STMF, ignoring" % (device,)) - del peddev - continue - - # blacklist PS3 flash - if iutil.isPPC() and \ - model.find("SCEI Flash-5") != -1: - log.info("%s looks like PS3 flash, ignoring" % \ - (device,)) - del peddev - continue - - # blacklist DGC/EMC LUNs for which we have no ACL. - # We should be ignoring LUN_Z for all vendors, but I - # don't know how (if) other vendors encode this into - # the model info. - # - # XXX I need to work some SCC2 LUN mode page detection - # into libbdevid, and then this should use that instead. - # -- pjones - if str(peddev.model) == "DGC LUNZ": - log.info("%s looks like a LUN_Z device, ignoring" % \ - (device,)) - del peddev - continue - - del peddev - new[device] = dev - except Exception, e: - log.debug("exception checking disk blacklist on %s: %s" % \ - (device, e)) - cachedDrives = new - - ret = {} - for key,dev in cachedDrives.items(): - # XXX these devices should have deviceclass attributes. Or they - # should all be subclasses in a device tree and we should be able - # to use isinstance on all of them. Not both. - if isinstance(dev, block.MultiPath) or isinstance(dev, block.RaidSet): - if klassArg == "disk": - ret[key] = dev - elif dev['storage.drive_type'] == klassArg: - ret[key] = dev - return ret - -## Get all the hard drives attached to the system. -# This method queries the drive dict cache for all hard drives. If the cache -# is empty, this will cause all disk devices to be probed. If the status of -# the devices has changed, flushDriveDict must be called first. -# -# @see flushDriveDict -# @see driveDict -# @return A dict of all the hard drive descriptions, keyed on device name. -def hardDriveDict(): - ret = {} - dict = driveDict("disk") - for item in dict.keys(): - try: - ret[item] = dict[item]['description'] - except AttributeError: - ret[item] = "" - return ret - -## Get all the removable drives attached to the system. -# This method queries the drive dict cache for all removable drives. If the cache -# is empty, this will cause all disk devices to be probed. If the status of -# the devices has changed, flushDriveDict must be run called first. -# -# @see flushDriveDict -# @see driveDict -# @return A dict of all the removable drive descriptions, keyed on device name. -def removableDriveDict(): - ret = {} - dict = driveDict("disk") - for item in dict.keys(): - if dict[item]['storage.removable'] != 0: - try: - ret[item] = dict[item]['description'] - except AttributeError: - ret[item] = "" - return ret - -## Get all CD/DVD drives attached to the system. -# This method queries the drive dict cache for all hard drives. If the cache -# is empty, this will cause all disk devices to be probed. If the status of -# the devices has changed, flushDriveDict must be called first. -# -# @see flushDriveDict -# @see driveDict -# @return A sorted list of all the CD/DVD drives, without any leading /dev/. -def cdromList(): - list = driveDict("cdrom").keys() - list.sort() - return list - -## Get all tape drives attached to the system. -# This method queries the drive dict cache for all hard drives. If the cache -# is empty, this will cause all disk devices to be probed. If the status of -# the devices has changed, flushDriveDict must be called first. -# -# @see flushDriveDict -# @see driveDict -# @return A sorted list of all the tape drives, without any leading /dev/. -def tapeDriveList(): - list = driveDict("tape").keys() - list.sort() - return list - def getDasdPorts(): return _isys.getDasdPorts() @@ -787,21 +438,6 @@ def ext2HasJournal(device): hasjournal = _isys.e2hasjournal(device); return hasjournal -def ejectCdrom(device): - if not os.path.exists(device): - device = "/dev/%s" % device - - fd = os.open(device, os.O_RDONLY|os.O_NONBLOCK) - - # this is a best effort - try: - _isys.ejectcdrom(fd) - except SystemError, e: - log.warning("error ejecting cdrom (%s): %s" %(device, e)) - pass - - os.close(fd) - def driveUsesModule(device, modules): """Returns true if a drive is using a prticular module. Only works for SCSI devices right now.""" @@ -830,31 +466,6 @@ def driveUsesModule(device, modules): pass return rc -## Check if a removable media drive (CD, USB key, etc.) has media present. -# @param device The basename of the device node. -# @return True if media is present in device, False otherwise. -def mediaPresent(device): - try: - fd = os.open("/dev/%s" % device, os.O_RDONLY) - except OSError, (errno, strerror): - # error 123 = No medium found - if errno == 123: - return False - else: - return True - else: - os.close(fd) - return True - -def driveIsIscsi(device): - # ewww. just ewww. - if not os.path.islink("/sys/block/%s/device" %(device,)): - return False - target = os.path.realpath("/sys/block/%s/device" %(device,)) - if re.search("/platform/host[0-9]*/session[0-9]*/target[0-9]*:[0-9]*:[0-9]*/[0-9]*:[0-9]*:[0-9]*:[0-9]*", target) is not None: - return True - return False - def vtActivate (num): if rhpl.getArch() == "s390": return @@ -1045,22 +656,14 @@ def isPaeAvailable(): if not iutil.isX86(): return isPAE - try: - f = open("/proc/iomem", "r") - lines = f.readlines() - for line in lines: - if line[0].isspace(): - continue - start = line.split(' ')[0].split('-')[0] - start = long(start, 16) - - if start >= 0x100000000L: - isPAE = True - break + f = open("/proc/cpuinfo", "r") + lines = f.readlines() + f.close() - f.close() - except: - pass + for line in lines: + if line.startswith("flags") and line.find("pae") != -1: + isPAE = True + break return isPAE @@ -24,6 +24,7 @@ import os, string, stat, sys import signal import os.path from errno import * +import inspect import rhpl import warnings import subprocess @@ -44,8 +45,8 @@ log = logging.getLogger("anaconda") # @param searchPath Should command be searched for in $PATH? # @param root The directory to chroot to before running command. # @return The return code of command. -def execWithRedirect(command, argv, stdin = 0, stdout = 1, stderr = 2, - searchPath = 0, root = '/'): +def execWithRedirect(command, argv, stdin = None, stdout = None, + stderr = None, searchPath = 0, root = '/'): def chroot (): os.chroot(root) @@ -53,22 +54,33 @@ def execWithRedirect(command, argv, stdin = 0, stdout = 1, stderr = 2, raise RuntimeError, command + " can not be run" argv = list(argv) - if type(stdin) == type("string"): + if isinstance(stdin, str): if os.access(stdin, os.R_OK): - stdin = open(stdin) + stdin = os.open(stdin, os.O_RDONLY) else: - stdin = 0 - if type(stdout) == type("string"): - stdout = open(stdout, "w") - if type(stderr) == type("string"): - stderr = open(stderr, "w") + stdin = sys.stdin.fileno() + elif isinstance(stdin, int): + pass + elif stdin is None or not isinstance(stdin, file): + stdin = sys.stdin.fileno() + + if isinstance(stdout, str): + stdout = os.open(stdout, os.O_RDWR|os.O_CREAT) + elif isinstance(stdout, int): + pass + elif stdout is None or not isinstance(stdout, file): + stdout = sys.stdout.fileno() + + if isinstance(stderr, str): + stderr = os.open(stderr, os.O_RDWR|os.O_CREAT) + elif isinstance(stderr, int): + pass + elif stderr is None or not isinstance(stderr, file): + stderr = sys.stderr.fileno() runningLog = open("/tmp/program.log", "a") runningLog.write("Running... %s\n" % ([command] + argv,)) - if stdout is not None and type(stdout) != int: - stdout.write("Running... %s\n" %([command] + argv,)) - try: proc = subprocess.Popen([command] + argv, stdin=stdin, stdout=subprocess.PIPE, @@ -78,10 +90,10 @@ def execWithRedirect(command, argv, stdin = 0, stdout = 1, stderr = 2, while True: (outStr, errStr) = proc.communicate() if outStr: - stdout.write(outStr) + os.write(stdout, outStr) runningLog.write(outStr) if errStr: - stderr.write(errStr) + os.write(stderr, errStr) runningLog.write(errStr) if proc.returncode is not None: @@ -104,21 +116,29 @@ def execWithRedirect(command, argv, stdin = 0, stdout = 1, stderr = 2, # @param stderr The file descriptor to redirect stderr to. # @param root The directory to chroot to before running command. # @return The output of command from stdout. -def execWithCapture(command, argv, stdin = 0, stderr = 2, root='/'): +def execWithCapture(command, argv, stdin = None, stderr = None, root='/'): def chroot(): os.chroot(root) rc = "" argv = list(argv) - if type(stdin) == type("string"): + if isinstance(stdin, str): if os.access(stdin, os.R_OK): - stdin = open(stdin) + stdin = os.open(stdin, os.O_RDONLY) else: - stdin = 0 - - if type(stderr) == type("string"): - stderr = open(stderr, "w") + stdin = sys.stdin.fileno() + elif isinstance(stdin, int): + pass + elif stdin is None or not isinstance(stdin, file): + stdin = sys.stdin.fileno() + + if isinstance(stderr, str): + stderr = os.open(stderr, os.O_RDWR|os.O_CREAT) + elif isinstance(stderr, int): + pass + elif stderr is None or not isinstance(stderr, file): + stderr = sys.stderr.fileno() runningLog = open("/tmp/program.log", "a") runningLog.write("Running... %s\n" % ([command] + argv,)) @@ -136,7 +156,7 @@ def execWithCapture(command, argv, stdin = 0, stderr = 2, root='/'): rc += outStr if errStr: runningLog.write(errStr) - stderr.write(errStr) + os.write(stderr, errStr) if proc.returncode is not None: break @@ -146,34 +166,49 @@ def execWithCapture(command, argv, stdin = 0, stderr = 2, root='/'): return rc -def execWithPulseProgress(command, argv, stdin = 0, stdout = 1, stderr = 2, - progress = None, root = '/'): +def execWithPulseProgress(command, argv, stdin = None, stdout = None, + stderr = None, progress = None, root = '/'): def chroot(): os.chroot(root) argv = list(argv) - if type(stdin) == type("string"): + if isinstance(stdin, str): if os.access(stdin, os.R_OK): - stdin = open(stdin) + stdin = os.open(stdin, os.O_RDONLY) else: - stdin = 0 - if type(stdout) == type("string"): - stdout = open(stdout, "w") - if type(stderr) == type("string"): - stderr = open(stderr, "w") - if stdout is not None and type(stdout) != int: - stdout.write("Running... %s\n" %([command] + argv,)) + stdin = sys.stdin.fileno() + elif isinstance(stdin, int): + pass + elif stdin is None or not isinstance(stdin, file): + stdin = sys.stdin.fileno() + + if isinstance(stdout, str): + stdout = os.open(stdout, os.O_RDWR|os.O_CREAT) + elif isinstance(stdout, int): + pass + elif stdout is None or not isinstance(stdout, file): + stdout = sys.stdout.fileno() + + if isinstance(stderr, str): + stderr = os.open(stderr, os.O_RDWR|os.O_CREAT) + elif isinstance(stderr, int): + pass + elif stderr is None or not isinstance(stderr, file): + stderr = sys.stderr.fileno() + + runningLog = open("/tmp/program.log", "a") + runningLog.write("Running... %s\n" % ([command] + argv,)) p = os.pipe() childpid = os.fork() if not childpid: os.close(p[0]) os.dup2(p[1], 1) - os.dup2(stderr.fileno(), 2) + os.dup2(stderr, 2) os.dup2(stdin, 0) os.close(stdin) os.close(p[1]) - stderr.close() + os.close(stderr) os.execvp(command, [command] + argv) os._exit(1) @@ -184,11 +219,12 @@ def execWithPulseProgress(command, argv, stdin = 0, stdout = 1, stderr = 2, try: s = os.read(p[0], 1) except OSError, args: - (num, str) = args + (num, _str) = args if (num != 4): raise IOError, args - stdout.write(s) + os.write(stdout, s) + runningLog.write(s) if progress: progress.pulse() if len(s) < 1: @@ -335,25 +371,6 @@ def copyDeviceNode(src, dest): os.mknod(dest, mode | type, filestat.st_rdev) -## Determine if the hardware supports iSeries storage devices. -# @return 1 if so, 0 otherwise. -def hasiSeriesNativeStorage(): - # this is disgusting and I feel very dirty - if not isPPC(): - return - - f = open("/proc/modules", "r") - lines = f.readlines() - f.close() - - for line in lines: - if line.startswith("ibmsis"): - return 1 - if line.startswith("ipr"): - return 1 - - return 0 - ## Get the PPC machine variety type. # @return The PPC machine type, or 0 if not PPC. def getPPCMachine(): @@ -665,6 +682,55 @@ def strip_markup(text): r += c return r.encode("utf-8") +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 + +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 + def writeReiplMethod(reipl_path, reipl_type): filename = "%s/reipl_type" % (reipl_path,) @@ -841,3 +907,4 @@ def reIPL(anaconda, loader_pid): # the final return is either None if reipl configuration worked (=> reboot), # or a two-item list with errorMessage and rebootInstr (=> shutdown) return message + diff --git a/iw/autopart_type.py b/iw/autopart_type.py index db6afb9f2..6bbca11b9 100644 --- a/iw/autopart_type.py +++ b/iw/autopart_type.py @@ -31,32 +31,28 @@ from netconfig_dialog import NetworkConfigurator from iw_gui import * from flags import flags import network -import partitions -import iscsi +from storage import iscsi +from storage.deviceaction import * import gettext _ = lambda x: gettext.ldgettext("anaconda", x) -def whichToResize(partitions, diskset, intf): +def whichToResize(storage, intf): def getActive(combo): act = combo.get_active_iter() return combo.get_model().get_value(act, 1) def comboCB(combo, resizeSB): # partition to resize changed, let's update our spinbutton - req = getActive(combo) - if req.targetSize is not None: - value = req.targetSize - else: - value = req.size - reqlower = req.getMinimumResizeMB(partitions) - requpper = req.getMaximumResizeMB(partitions) + part = getActive(combo) + reqlower = part.minSize + requpper = part.maxSize adj = resizeSB.get_adjustment() adj.lower = reqlower adj.upper = requpper - adj.value = value - adj.set_value(value) + adj.value = reqlower + adj.set_value(reqlower) (dxml, dialog) = gui.getGladeWidget("autopart.glade", "resizeDialog") @@ -69,25 +65,34 @@ def whichToResize(partitions, diskset, intf): combo.set_attributes(crt, text = 0) combo.connect("changed", comboCB, dxml.get_widget("resizeSB")) - found = False biggest = -1 - for req in partitions.requests: - if req.type != REQUEST_PREEXIST: + for part in storage.partitions: + if not part.exists: continue - if req.isResizable(partitions): + + # Resize the following storage types: + # resizable filesystem (e.g., ext3 or ntfs) on resizable partition + # resizable filesystem on a resizable logical volume + entry = None + if part.resizable and part.format.resizable: + entry = ("%s (%s, %d MB)" % (part.name, + part.format.name, + math.floor(part.format.size)), + part) + + if entry: i = store.append(None) - store[i] = ("%s (%s, %d MB)" %(req.device, - req.fstype.getName(), - math.floor(req.size)), - req) - if req.targetSize is not None: - combo.set_active_iter(i) - found = True + store[i] = entry + combo.set_active_iter(i) + + if biggest == -1: + biggest = i else: - if biggest < 0 or req.size > store.get_value(biggest, 1).size: + current = store.get_value(biggest, 1) + if part.format.targetSize > current.format.targetSize: biggest = i - if not found and biggest > 0: + if biggest > -1: combo.set_active_iter(biggest) if len(store) == 0: @@ -97,19 +102,43 @@ def whichToResize(partitions, diskset, intf): "physical partitions with specific filesystems " "can be resized."), type="warning", custom_icon="error") - return gtk.RESPONSE_CANCEL + return (gtk.RESPONSE_CANCEL, []) gui.addFrame(dialog) dialog.show_all() - rc = dialog.run() - if rc != gtk.RESPONSE_OK: - dialog.destroy() - return rc + runResize = True + + while runResize: + rc = dialog.run() + if rc != gtk.RESPONSE_OK: + dialog.destroy() + return (rc, []) + + request = getActive(combo) + newSize = dxml.get_widget("resizeSB").get_value_as_int() + actions = [] + + try: + actions.append(ActionResizeFormat(request, newSize)) + except ValueError as e: + intf.messageWindow(_("Resize FileSystem Error"), + _("%s: %s") % (request.format.device, + e.message,), + type="warning", custom_icon="error") + continue + + try: + actions.append(ActionResizeDevice(request, newSize)) + except ValueError as e: + intf.messageWindow(_("Resize Device Error"), + _("%s: %s") % (request.name, e.message,), + type="warning", custom_icon="error") + continue + + runResize = False - req = getActive(combo) - req.targetSize = dxml.get_widget("resizeSB").get_value_as_int() dialog.destroy() - return rc + return (rc, actions) class PartitionTypeWindow(InstallWindow): def __init__(self, ics): @@ -118,7 +147,7 @@ class PartitionTypeWindow(InstallWindow): ics.setNextEnabled(True) def getNext(self): - if self.diskset.checkNoDisks(): + if self.storage.checkNoDisks(): raise gui.StayOnScreen active = self.combo.get_active_iter() @@ -130,8 +159,11 @@ class PartitionTypeWindow(InstallWindow): self.dispatch.skipStep("bootloader", skip = 0) else: if val == -2: - rc = whichToResize(self.partitions, self.diskset, self.intf) - if rc != gtk.RESPONSE_OK: + (rc, actions) = whichToResize(self.storage, self.intf) + if rc == gtk.RESPONSE_OK: + for action in actions: + self.storage.devicetree.registerAction(action) + else: raise gui.StayOnScreen # we're not going to delete any partitions in the resize case @@ -140,14 +172,14 @@ class PartitionTypeWindow(InstallWindow): self.dispatch.skipStep("autopartitionexecute", skip = 0) if self.xml.get_widget("encryptButton").get_active(): - self.partitions.autoEncrypt = True + self.storage.encryptedAutoPart = True else: - self.partitions.encryptionPassphrase = "" - self.partitions.retrofitPassphrase = False - self.partitions.autoEncrypt = False + self.storage.encryptionPassphrase = "" + self.storage.retrofitPassphrase = False + self.storage.encryptedAutoPart = False - self.partitions.useAutopartitioning = 1 - self.partitions.autoClearPartType = val + self.storage.doAutoPart = True + self.storage.clearPartType = val allowdrives = [] model = self.drivelist.get_model() @@ -159,7 +191,7 @@ class PartitionTypeWindow(InstallWindow): mustHaveSelectedDrive(self.intf) raise gui.StayOnScreen - self.partitions.autoClearPartDrives = allowdrives + self.storage.clearPartDisks = allowdrives # pop the boot device to be first in the drive list defiter = self.bootcombo.get_active_iter() @@ -238,8 +270,8 @@ class PartitionTypeWindow(InstallWindow): # get the initiator name if it exists and don't allow changing # once set e = dxml.get_widget("iscsiInitiatorEntry") - e.set_text(self.anaconda.id.iscsi.initiator) - if self.anaconda.id.iscsi.initiatorSet: # this is uglyyyy.... + e.set_text(self.storage.iscsi.initiator) + if self.storage.iscsi.initiatorSet: # this is uglyyyy.... e.set_sensitive(False) while 1: @@ -254,7 +286,7 @@ class PartitionTypeWindow(InstallWindow): _("You must provide an initiator name.")) continue - self.anaconda.id.iscsi.initiator = initiator + self.storage.iscsi.initiator = initiator target = dxml.get_widget("iscsiAddrEntry").get_text().strip() user = dxml.get_widget("userEntry").get_text().strip() @@ -290,8 +322,8 @@ class PartitionTypeWindow(InstallWindow): continue try: - self.anaconda.id.iscsi.addTarget(ip, port, user, pw, user_in, pw_in, - self.intf) + self.storage.iscsi.addTarget(ip, port, user, pw, + user_in, pw_in, self.intf) except ValueError, e: self.intf.messageWindow(_("Error"), str(e)) continue @@ -323,7 +355,7 @@ class PartitionTypeWindow(InstallWindow): fcplun = dxml.get_widget("fcplunEntry").get_text().strip() try: - self.anaconda.id.zfcp.addFCP(devnum, wwpn, fcplun) + self.storage.zfcp.addFCP(devnum, wwpn, fcplun) except ValueError, e: self.intf.messageWindow(_("Error"), str(e)) continue @@ -365,9 +397,9 @@ class PartitionTypeWindow(InstallWindow): if rc != gtk.RESPONSE_CANCEL: w = self.intf.waitWindow(_("Rescanning disks"), _("Rescanning disks")) - partitions.partitionObjectsInitialize(self.anaconda) - createAllowedDrivesStore(self.diskset.disks, - self.partitions.autoClearPartDrives, + self.storage.reset() + createAllowedDrivesStore(self.storage.disks, + self.storage.clearPartDisks, self.drivelist, disallowDrives=[self.anaconda.updateSrc]) self._fillBootStore() @@ -380,14 +412,13 @@ class PartitionTypeWindow(InstallWindow): defaultBoot = self.anaconda.id.bootloader.drivelist[0] else: defaultBoot = None - for disk in self.diskset.disks.values(): - if not disk.device.path[5:] in self.anaconda.id.bootloader.drivelist: + for disk in self.storage.disks: + if disk.name not in self.anaconda.id.bootloader.drivelist: continue - size = disk.device.getSize(unit="MB") - dispstr = "%s %8.0f MB %s" %(disk.device.path[5:], size, disk.device.model) + dispstr = "%s %8.0f MB %s" %(disk.name, disk.size, disk.partedDisk.device.model) i = bootstore.append(None) - bootstore[i] = (dispstr, disk.device.path[5:]) - if disk.device.path[5:] == defaultBoot: + bootstore[i] = (dispstr, disk.name) + if disk.name == defaultBoot: self.bootcombo.set_active_iter(i) if len(bootstore) <= 1: @@ -396,8 +427,7 @@ class PartitionTypeWindow(InstallWindow): def getScreen(self, anaconda): self.anaconda = anaconda - self.diskset = anaconda.id.diskset - self.partitions = anaconda.id.partitions + self.storage = anaconda.id.storage self.intf = anaconda.intf self.dispatch = anaconda.dispatch @@ -427,15 +457,15 @@ class PartitionTypeWindow(InstallWindow): for (txt, val) in opts: iter = store.append(None) store[iter] = (txt, val) - if val == self.partitions.autoClearPartType: + if val == self.storage.clearPartType: self.combo.set_active_iter(iter) if ((self.combo.get_active() == -1) or self.dispatch.stepInSkipList("autopartitionexecute")): self.combo.set_active(len(opts) - 1) # yeah, it's a hack - self.drivelist = createAllowedDrivesList(self.diskset.disks, - self.partitions.autoClearPartDrives, + self.drivelist = createAllowedDrivesList(self.storage.disks, + self.storage.clearPartDisks, disallowDrives=[self.anaconda.updateSrc]) self.drivelist.set_size_request(375, 80) @@ -453,7 +483,7 @@ class PartitionTypeWindow(InstallWindow): self.review = not self.dispatch.stepInSkipList("partition") self.xml.get_widget("reviewButton").set_active(self.review) - self.xml.get_widget("encryptButton").set_active(self.partitions.autoEncrypt) + self.xml.get_widget("encryptButton").set_active(self.storage.encryptedAutoPart) active = self.combo.get_active_iter() val = self.combo.get_model().get_value(active, 1) diff --git a/iw/bootloader_main_gui.py b/iw/bootloader_main_gui.py index d11a73e89..a11e3b964 100644 --- a/iw/bootloader_main_gui.py +++ b/iw/bootloader_main_gui.py @@ -21,7 +21,6 @@ import gtk import gobject -import partedUtils import gui from iw_gui import * from constants import * @@ -81,7 +80,12 @@ class MainBootloaderWindow(InstallWindow): def __driveChange(combo, dxml, choices): if not choices.has_key("mbr"): return - first = combo.get_model()[combo.get_active_iter()][1] + + iter = combo.get_active_iter() + if not iter: + return + + first = combo.get_model()[iter][1] desc = choices["mbr"][1] dxml.get_widget("mbrRadio").set_label("%s - /dev/%s" %(desc, first)) dxml.get_widget("mbrRadio").set_data("bootDevice", first) @@ -93,16 +97,14 @@ class MainBootloaderWindow(InstallWindow): combo.pack_start(cell, True) combo.set_attributes(cell, text = 0) - keys = disks.keys() - keys.sort() - - for d in keys: - size = disks[d].device.getSize(unit="MB") - m = disks[d].device.model + for disk in disks: + size = disk.size + m = disk.partedDisk.device.model i = model.append(None) - model[i] = ("%s %8.0f MB %s" %(d, size, m), "%s" %(d,)) - if d == active: + model[i] = ("%s %8.0f MB %s" %(disk.name, size, m), + "%s" %(disk.name,)) + if disk.name == active: combo.set_active_iter(i) return model @@ -113,8 +115,7 @@ class MainBootloaderWindow(InstallWindow): dialog.set_transient_for(self.parent) dialog.show() - choices = anaconda.id.fsset.bootloaderChoices(anaconda.id.diskset, - self.bl) + choices = anaconda.platform.bootloaderChoices(self.bl) for t in ("mbr", "boot"): if not choices.has_key(t): continue @@ -127,8 +128,7 @@ class MainBootloaderWindow(InstallWindow): else: w.set_active(False) w.set_data("bootDevice", device) - - + for i in range(1, 5): if len(self.driveorder) < i: break @@ -136,7 +136,7 @@ class MainBootloaderWindow(InstallWindow): lbl = dxml.get_widget("bd%dLabel" %(i,)) combo.show() lbl.show() - m = __genStore(combo, anaconda.id.diskset.disks, self.driveorder[i - 1]) + m = __genStore(combo, anaconda.id.storage.disks, self.driveorder[i - 1]) dxml.get_widget("bd1Combo").connect("changed", __driveChange, dxml, choices) __driveChange(dxml.get_widget("bd1Combo"), dxml, choices) @@ -159,8 +159,13 @@ class MainBootloaderWindow(InstallWindow): for i in range(1, 5): if len(self.driveorder) < i: break + combo = dxml.get_widget("bd%dCombo" %(i,)) - act = combo.get_model()[combo.get_active_iter()][1] + iter = combo.get_active_iter() + if not iter: + continue + + act = combo.get_model()[iter][1] if act not in neworder: neworder.append(act) for d in self.driveorder: @@ -185,10 +190,10 @@ class MainBootloaderWindow(InstallWindow): self.bl = anaconda.id.bootloader self.intf = anaconda.intf - drives = anaconda.id.diskset.disks + disks = anaconda.id.storage.disks self.driveorder = self.bl.drivelist if len(self.driveorder) == 0: - self.driveorder = drives.keys() + self.driveorder = [d.name for d in disks] if self.bl.getPassword(): self.usePass = 1 @@ -206,7 +211,7 @@ class MainBootloaderWindow(InstallWindow): else: # we don't know what it is yet... if mbr is possible, we want # it, else we want the boot dev - choices = anaconda.id.fsset.bootloaderChoices(anaconda.id.diskset, self.bl) + choices = anaconda.platform.bootloaderChoices(self.bl) if choices.has_key('mbr'): self.bldev = choices['mbr'][0] else: diff --git a/iw/congrats_gui.py b/iw/congrats_gui.py index b5f0101cf..0df2d807e 100644 --- a/iw/congrats_gui.py +++ b/iw/congrats_gui.py @@ -23,6 +23,7 @@ import gui from iw_gui import * from constants import * import os +import platform import gettext _ = lambda x: gettext.ldgettext("anaconda", x) @@ -69,7 +70,7 @@ class CongratulationWindow (InstallWindow): a.set_size_request(200, -1) hbox.pack_start (a, False, False, 36) - if iutil.isS390(): + if isinstance(anaconda.platform, platform.S390): txt = _("Congratulations, your %s installation is complete.\n\n") % (productName,) if not anaconda.canReIPL: diff --git a/iw/examine_gui.py b/iw/examine_gui.py index 63abbade2..334f3d1e4 100644 --- a/iw/examine_gui.py +++ b/iw/examine_gui.py @@ -123,15 +123,11 @@ class UpgradeExamineWindow (InstallWindow): self.upgradecombo.pack_start(cell, True) self.upgradecombo.set_attributes(cell, markup=0) - for (part, filesystem, desc, label, uuid) in self.parts: + for (dev, desc) in self.parts: iter = model.append() if (desc is None) or len(desc) < 1: desc = _("Unknown Linux system") - if part[:5] != "/dev/": - devname = "/dev/" + part - else: - devname = part - model[iter][0] = "<small>%s (%s)</small>" %(desc, devname) + model[iter][0] = "<small>%s (%s)</small>" %(desc, dev.path) upboxtmp.pack_start(self.uplabel) diff --git a/iw/lvm_dialog_gui.py b/iw/lvm_dialog_gui.py index 25c0e6dda..2cee546ba 100644 --- a/iw/lvm_dialog_gui.py +++ b/iw/lvm_dialog_gui.py @@ -1,5 +1,5 @@ # -# lvm_dialog_gui.py: dialog for editting a volume group request +# lvm_dialog_gui.py: dialog for editing a volume group request # # Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Red Hat, Inc. # All rights reserved. @@ -27,12 +27,10 @@ import gtk import datacombo import gui -from fsset import * -from partRequests import * from partition_ui_helpers_gui import * from constants import * -import lvm -from cryptodev import LUKSDevice +from storage.devices import * +from storage.deviceaction import * import gettext _ = lambda x: gettext.ldgettext("anaconda", x) @@ -42,37 +40,35 @@ log = logging.getLogger("anaconda") class VolumeGroupEditor: - def numAvailableLVSlots(self): - return max(0, lvm.MAX_LV_SLOTS - len(self.logvolreqs)) - - def computeSpaceValues(self, alt_pvlist=None, usepe=None): - if usepe is None: - pesize = long(self.peCombo.get_active_value()) - else: - pesize = long(usepe) + def getTempVG(self): + pvs = [copy.deepcopy(pv) for pv in self.pvs] + vg = LVMVolumeGroupDevice('tmp-%s' % self.vg.name, + parents=pvs, peSize=self.peSize) + for lv in self.lvs.values(): + LVMLogicalVolumeDevice(lv['name'], vg, format=lv['format'], + size=lv['size'], exists=lv['exists']) + return vg - if alt_pvlist is None: - pvlist = self.getSelectedPhysicalVolumes(self.lvmlist.get_model()) - else: - pvlist = alt_pvlist - tspace = self.computeVGSize(pvlist, pesize) - uspace = self.computeLVSpaceNeeded(self.logvolreqs) - fspace = tspace - uspace + def numAvailableLVSlots(self): + return max(0, lvm.MAX_LV_SLOTS - len(self.lvs)) - return (tspace, uspace, fspace) + def computeSpaceValues(self): + vg = self.getTempVG() + vgsize = vg.size + vgfree = vg.freeSpace + vgused = vgsize - vgfree + return (vgsize, vgused, vgfree) def getPVWastedRatio(self, newpe): """ given a new pe value, return percentage of smallest PV wasted newpe - (int) new value of PE, in KB """ - pvlist = self.getSelectedPhysicalVolumes(self.lvmlist.get_model()) + pvlist = self.getSelectedPhysicalVolumes() waste = 0.0 - for id in pvlist: - pvreq = self.partitions.getRequestByID(id) - pvsize = pvreq.getActualSize(self.partitions, self.diskset) - waste = max(waste, (long(pvsize*1024) % newpe)/(pvsize*1024.0)) + for pv in pvlist: + waste = max(waste, (long(pv.size*1024) % newpe)/(pv.size*1024.0)) return waste @@ -80,15 +76,15 @@ class VolumeGroupEditor: """ finds the smallest PV and returns its size in MB """ first = 1 - pvlist = self.getSelectedPhysicalVolumes(self.lvmlist.get_model()) - for id in pvlist: + pvlist = self.getSelectedPhysicalVolumes() + for pv in pvlist: try: pesize = int(self.peCombo.get_active_value()) except: - pesize = 32768 - pvreq = self.partitions.getRequestByID(id) - pvsize = pvreq.getActualSize(self.partitions, self.diskset) - pvsize = lvm.clampPVSize(pvsize, pesize) - int(pesize/1024) + pesize = self.vg.peSize + + # FIXME: move this logic into a property of LVMVolumeGroupDevice + pvsize = lvm.clampSize(pv.size, pesize) - int(pesize/1024) if first: minpvsize = pvsize first = 0 @@ -104,17 +100,17 @@ class VolumeGroupEditor: newpe - (int) new value of PE, in KB """ - pvlist = self.getSelectedPhysicalVolumes(self.lvmlist.get_model()) + pvlist = self.getSelectedPhysicalVolumes() availSpaceMB = self.computeVGSize(pvlist, newpe) # see if total space is enough oldused = 0 used = 0 resize = 0 - for lv in self.logvolreqs: - osize = lv.getActualSize(self.partitions, self.diskset, True) + for lv in self.lvs.values(): + osize = lv['size'] oldused = oldused + osize - nsize = lvm.clampLVSizeRequest(osize, newpe, roundup=1) + nsize = lvm.clampSize(osize, newpe, roundup=1) if nsize != osize: resize = 1 @@ -144,11 +140,9 @@ class VolumeGroupEditor: custom_buttons=["gtk-cancel", _("C_ontinue")]) if not rc: return 0 - - for lv in self.logvolreqs: - osize = lv.getActualSize(self.partitions, self.diskset, True) - nsize = lvm.clampLVSizeRequest(osize, newpe, roundup=1) - lv.setSize(nsize) + + for lv in self.lvs.values(): + lv['size'] = lvm.clampSize(lv['size'], newpe, roundup=1) return 1 @@ -161,24 +155,26 @@ class VolumeGroupEditor: """ curval = int(widget.get_active_value()) + # this one's in MB so we can stop with all this dividing by 1024 + curpe = curval / 1024.0 lastval = widget.get_data("lastpe") lastidx = widget.get_data("lastidx") # see if PE is too large compared to smallest PV - # remember PE is in KB, PV size is in MB maxpvsize = self.getSmallestPVSize() - if curval > maxpvsize * 1024: + if curpe > maxpvsize: self.intf.messageWindow(_("Not enough space"), _("The physical extent size cannot be " "changed because the value selected " "(%10.2f MB) is larger than the smallest " "physical volume (%10.2f MB) in the " - "volume group.") % (curval/1024.0, maxpvsize), custom_icon="error") + "volume group.") % (curpe, maxpvsize), + custom_icon="error") widget.set_active(lastidx) return 0 # see if new PE will make any PV useless due to overhead - if lvm.clampPVSize(maxpvsize, curval) * 1024 < curval: + if lvm.clampSize(maxpvsize, curpe) < curpe: self.intf.messageWindow(_("Not enough space"), _("The physical extent size cannot be " "changed because the value selected " @@ -186,14 +182,14 @@ class VolumeGroupEditor: "to the size of the " "smallest physical volume " "(%10.2f MB) in the " - "volume group.") % (curval/1024.0, + "volume group.") % (curpe, maxpvsize), custom_icon="error") widget.set_active(lastidx) return 0 - if self.getPVWastedRatio(curval) > 0.10: + if self.getPVWastedRatio(curpe) > 0.10: rc = self.intf.messageWindow(_("Too small"), _("This change in the value of the " "physical extent will waste " @@ -215,10 +211,9 @@ class VolumeGroupEditor: else: self.updateLogVolStore() else: - maxlv = lvm.getMaxLVSize(curval) - for lv in self.logvolreqs: - lvsize = lv.getActualSize(self.partitions, self.diskset, True) - if lvsize > maxlv: + maxlv = lvm.getMaxLVSize() + for lv in self.lvs.values(): + if lv['size'] > maxlv: self.intf.messageWindow(_("Not enough space"), _("The physical extent size " "cannot be changed because the " @@ -234,12 +229,14 @@ class VolumeGroupEditor: widget.set_data("lastpe", curval) widget.set_data("lastidx", widget.get_active()) - self.updateAllowedLvmPartitionsList(self.availlvmparts, - self.partitions, - self.lvmlist) + + # now actually set the VG's extent size + self.peSize = curpe + self.updateAllowedLvmPartitionsList() self.updateVGSpaceLabels() def prettyFormatPESize(self, val): + """ Pretty print for PE size in KB """ if val < 1024: return "%s KB" % (val,) elif val < 1024*1024: @@ -276,7 +273,7 @@ class VolumeGroupEditor: def clickCB(self, row, data): model = self.lvmlist.get_model() - pvlist = self.getSelectedPhysicalVolumes(model) + pvlist = self.getSelectedPhysicalVolumes() # get the selected row iter = model.get_iter((string.atoi(data),)) @@ -285,28 +282,27 @@ class VolumeGroupEditor: # changes the toggle state val = not model.get_value(iter, 0) partname = model.get_value(iter, 1) - id = self.partitions.getRequestByDeviceName(partname).uniqueID - if val: - pvlist.append(id) - else: - pvlist.remove(id) - - (availSpaceMB, neededSpaceMB, fspace) = self.computeSpaceValues(alt_pvlist=pvlist) - if availSpaceMB < neededSpaceMB: - self.intf.messageWindow(_("Not enough space"), - _("You cannot remove this physical " - "volume because otherwise the " - "volume group will be too small to " - "hold the currently defined logical " - "volumes."), custom_icon="error") - return False - - self.updateVGSpaceLabels(alt_pvlist = pvlist) - return True - + pv = self.storage.devicetree.getDeviceByName(partname) + if val: + self.pvs.append(pv) + else: + self.pvs.remove(pv) + try: + vg = self.getTempVG() + except DeviceError as e: + self.intf.messageWindow(_("Not enough space"), + _("You cannot remove this physical " + "volume because otherwise the " + "volume group will be too small to " + "hold the currently defined logical " + "volumes."), custom_icon="error") + self.pvs.append(pv) + return False - def createAllowedLvmPartitionsList(self, alllvmparts, reqlvmpart, partitions, preexist = 0): + self.updateVGSpaceLabels() + return True + def createAllowedLvmPartitionsList(self): store = gtk.TreeStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, gobject.TYPE_STRING) @@ -317,53 +313,52 @@ class VolumeGroupEditor: sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) sw.set_shadow_type(gtk.SHADOW_IN) - for part in alllvmparts: - uid = part[0] - request = partitions.getRequestByID(uid) - - if request.type != REQUEST_RAID: - partname = "%s" % (request.device,) - else: - partname = "md%d" % (request.raidminor,) - - size = request.getActualSize (partitions, self.diskset) - used = part[2] - + origpvs = self.pvs[:] + for device in self.availlvmparts: # clip size to current PE - pesize = int(self.peCombo.get_active_value()) - size = lvm.clampPVSize(size, pesize) - partsize = "%10.2f MB" % size - if used or not reqlvmpart: - selected = 1 - else: - selected = 0 + pesize = int(self.peCombo.get_active_value()) / 1024.0 + size = lvm.clampSize(device.size, pesize) + size_string = "%10.2f MB" % size + include = True + selected = False + + # now see if the pv is in use either by a vg in the tree or by + # the vg we are editing now + if device in origpvs: + selected = True + include = True + else: + for vg in self.storage.vgs: + if vg.name == self.vg.name: + continue - if preexist == 0 or selected == 1: - partlist.append_row((partname, partsize), selected) + if device in vg.pvs: + include = False + break - return (partlist, sw) + if include and not origpvs: + selected = True - def updateAllowedLvmPartitionsList(self, alllvmparts, partitions, partlist): - """ update sizes in pv list + if include: + partlist.append_row((device.name, size_string), selected) + if selected and device not in self.pvs: + self.pvs.append(device) - alllvmparts - list of pv from partitions.getAvailLVMPartitions - partitions - object holding all partition requests - partlist - the checklist containing pv list - """ + return (partlist, sw) + def updateAllowedLvmPartitionsList(self): + """ update sizes in pv list """ row = 0 - for part in alllvmparts: - uid = part[0] - request = partitions.getRequestByID(uid) - size = request.getActualSize(partitions, self.diskset) + for part in self.availlvmparts: + size = part.size # clip size to current PE - pesize = int(self.peCombo.get_active_value()) - size = lvm.clampPVSize(size, pesize) + pesize = int(self.peCombo.get_active_value()) / 1024.0 + size = lvm.clampSize(size, pesize) partsize = "%10.2f MB" % size - iter = partlist.store.get_iter((int(row),)) - partlist.store.set_value(iter, 2, partsize) + iter = self.lvmlist.store.get_iter((int(row),)) + self.lvmlist.store.set_value(iter, 2, partsize) row = row + 1 def getCurrentLogicalVolume(self): @@ -371,16 +366,12 @@ class VolumeGroupEditor: (model, iter) = selection.get_selected() return iter - - def editLogicalVolume(self, logrequest, isNew = 0): + def editLogicalVolume(self, lv, isNew = 0): if isNew: tstr = _("Make Logical Volume") else: - try: - tstr = _("Edit Logical Volume: %s") % (logrequest.logicalVolumeName,) - except: - tstr = _("Edit Logical Volume") - + tstr = _("Edit Logical Volume: %s") % lv['name'] + dialog = gtk.Dialog(tstr, self.parent) gui.addFrame(dialog) dialog.add_button('gtk-cancel', 2) @@ -392,97 +383,118 @@ class VolumeGroupEditor: maintable.set_col_spacings(5) row = 0 + tempvg = self.getTempVG() + templv = None + templuks = None + usedev = None + for _lv in tempvg.lvs: + if _lv.lvname == lv['name']: + templv = _lv + usedev = templv + if templv.format.type == "luks": + templuks = LUKSDevice("luks-%s" % lv['name'], + parents=[templv], + format=self.luks[lv['name']], + exists=templv.format.exists) + usedev = templuks + break + + if lv['format'].type == "luks": + format = self.luks[lv['name']] + else: + format = lv['format'] + lbl = createAlignedLabel(_("_Mount Point:")) maintable.attach(lbl, 0, 1, row,row+1) - mountCombo = createMountPointCombo(logrequest, excludeMountPoints=["/boot"]) + mountCombo = createMountPointCombo(usedev, excludeMountPoints=["/boot"]) lbl.set_mnemonic_widget(mountCombo) maintable.attach(mountCombo, 1, 2, row, row + 1) row += 1 - if not logrequest or not logrequest.getPreExisting(): + if not lv['exists']: lbl = createAlignedLabel(_("_File System Type:")) maintable.attach(lbl, 0, 1, row, row + 1) - newfstypeCombo = createFSTypeMenu(logrequest.fstype, + newfstypeCombo = createFSTypeMenu(format, fstypechangeCB, mountCombo, - ignorefs = ["software RAID", "physical volume (LVM)", "vfat", "efi", "PPC PReP Boot", "Apple Bootstrap"]) + ignorefs = ["software RAID", "physical volume (LVM)", "efi", "PPC PReP Boot", "Apple Bootstrap"]) lbl.set_mnemonic_widget(newfstypeCombo) maintable.attach(newfstypeCombo, 1, 2, row, row + 1) row += 1 else: maintable.attach(createAlignedLabel(_("Original File System Type:")), 0, 1, row, row + 1) - if logrequest.origfstype and logrequest.origfstype.getName(): - newfstypeCombo = gtk.Label(logrequest.origfstype.getName()) + if format.type: + newfstypeCombo = gtk.Label(format.name) else: newfstypeCombo = gtk.Label(_("Unknown")) maintable.attach(newfstypeCombo, 1, 2, row, row + 1) row += 1 - if logrequest.fslabel: + if getattr(format, "label", None): maintable.attach(createAlignedLabel(_("Original File System " "Label:")), 0, 1, row, row + 1) - maintable.attach(gtk.Label(logrequest.fslabel), 1, 2, row, - row + 1) + maintable.attach(gtk.Label(format.label), + 1, 2, row, row + 1) row += 1 - - if not logrequest or not logrequest.getPreExisting(): + if not lv['exists']: lbl = createAlignedLabel(_("_Logical Volume Name:")) lvnameEntry = gtk.Entry(32) lbl.set_mnemonic_widget(lvnameEntry) - if logrequest and logrequest.logicalVolumeName: - lvnameEntry.set_text(logrequest.logicalVolumeName) + if lv['name']: + lvnameEntry.set_text(lv['name']) else: - lvnameEntry.set_text(lvm.createSuggestedLVName(self.logvolreqs)) + lvnameEntry.set_text(self.storage.createSuggestedLVName(self.getTempVG())) else: lbl = createAlignedLabel(_("Logical Volume Name:")) - lvnameEntry = gtk.Label(logrequest.logicalVolumeName) + lvnameEntry = gtk.Label(lv['name']) maintable.attach(lbl, 0, 1, row, row + 1) maintable.attach(lvnameEntry, 1, 2, row, row + 1) row += 1 - if not logrequest or not logrequest.getPreExisting(): + if not lv['exists']: lbl = createAlignedLabel(_("_Size (MB):")) sizeEntry = gtk.Entry(16) lbl.set_mnemonic_widget(sizeEntry) - if logrequest: - sizeEntry.set_text("%Ld" % (logrequest.getActualSize(self.partitions, self.diskset, True),)) + sizeEntry.set_text("%Ld" % lv['size']) else: lbl = createAlignedLabel(_("Size (MB):")) - sizeEntry = gtk.Label(str(logrequest.size)) + sizeEntry = gtk.Label(str(lv['size'])) maintable.attach(lbl, 0, 1, row, row+1) maintable.attach(sizeEntry, 1, 2, row, row + 1) row += 1 - if not logrequest or not logrequest.getPreExisting(): - pesize = int(self.peCombo.get_active_value()) - (tspace, uspace, fspace) = self.computeSpaceValues(usepe=pesize) - maxlv = min(lvm.getMaxLVSize(pesize), fspace) - - # add in size of current logical volume if it has a size - if logrequest and not isNew: - maxlv = maxlv + logrequest.getActualSize(self.partitions, self.diskset) + if not lv['exists']: + maxlv = min(lvm.getMaxLVSize(), lv['size']) maxlabel = createAlignedLabel(_("(Max size is %s MB)") % (maxlv,)) maintable.attach(maxlabel, 1, 2, row, row + 1) self.fsoptionsDict = {} - if logrequest.getPreExisting(): - (row, self.fsoptionsDict) = createPreExistFSOptionSection(logrequest, maintable, row, mountCombo, self.partitions, ignorefs = ["software RAID", "physical volume (LVM)", "vfat"]) + if lv['exists']: + templuks = None + reallv = None + for _lv in self.vg.lvs: + if _lv.lvname == lv['name']: + reallv = _lv + if _lv.format.type == "luks": + try: + templuks = self.storage.devicetree.getChildren(_lv)[0] + except IndexError: + templuks = None + break + + (row, self.fsoptionsDict) = createPreExistFSOptionSection(reallv, maintable, row, mountCombo, self.storage, ignorefs = ["software RAID", "physical volume (LVM)", "vfat"], luksdev=templuks) # checkbutton for encryption using dm-crypt/LUKS - if not logrequest.getPreExisting(): + if not lv['exists']: self.lukscb = gtk.CheckButton(_("_Encrypt")) - if logrequest.format or logrequest.type == REQUEST_NEW: - self.lukscb.set_data("formatstate", 1) - else: - self.lukscb.set_data("formatstate", 0) - - if logrequest.encryption: + self.lukscb.set_data("formatstate", 1) + if lv['format'].type == "luks": self.lukscb.set_active(1) else: self.lukscb.set_active(0) @@ -500,79 +512,71 @@ class VolumeGroupEditor: dialog.destroy() return - if not logrequest or not logrequest.getPreExisting(): - fsystem = newfstypeCombo.get_active_value() - format = 1 - migrate = 0 - targetSize = None - else: - if self.fsoptionsDict.has_key("formatcb"): - format = self.fsoptionsDict["formatcb"].get_active() - if format: - fsystem = self.fsoptionsDict["fstypeCombo"].get_active_value() - else: - format = 0 + actions = [] + luksdev = None + targetSize = None + migrate = None + format = None + newluks = None - if self.fsoptionsDict.has_key("migratecb"): - migrate = self.fsoptionsDict["migratecb"].get_active() - if migrate: - fsystem = self.fsoptionsDict["migfstypeCombo"].get_active_value() - else: - migrate = 0 + if templv.format.type == "luks": + format = self.luks[lv['name']] + else: + format = templv.format - if self.fsoptionsDict.has_key("resizecb") and self.fsoptionsDict["resizecb"].get_active(): - targetSize = self.fsoptionsDict["resizesb"].get_value_as_int() - else: - targetSize = None + if not templv.exists: + fmt_class = newfstypeCombo.get_active_value() + else: + # existing lv + fmt_class = self.fsoptionsDict["fstypeCombo"].get_active_value() - # set back if we are not formatting or migrating - origfstype = logrequest.origfstype - if not format and not migrate: - fsystem = origfstype + mountpoint = mountCombo.get_children()[0].get_text().strip() - mntpt = string.strip(mountCombo.get_children()[0].get_text()) + # validate logical volume name + lvname = lvnameEntry.get_text().strip() + if not templv.exists: + err = sanityCheckLogicalVolumeName(lvname) + if err: + self.intf.messageWindow(_("Illegal Logical Volume Name"), + err, custom_icon="error") + continue - if not logrequest or not logrequest.getPreExisting(): - # check size specification - badsize = 0 - try: - size = long(sizeEntry.get_text()) - except: - badsize = 1 + # check that the name is not already in use + used = 0 + for _lv in self.lvs.values(): + if _lv == lv: + continue - if badsize or size <= 0: - self.intf.messageWindow(_("Illegal size"), - _("The requested size as entered is " - "not a valid number greater " - "than 0."), custom_icon="error") - continue - else: - size = logrequest.size + if _lv['name'] == lvname: + used = 1 + break - # is this an existing logical volume or one we're editting - if logrequest: - preexist = logrequest.getPreExisting() - else: - preexist = 0 + if used: + self.intf.messageWindow(_("Illegal logical volume name"), + _("The logical volume name \"%s\" is " + "already in use. Please pick " + "another.") % (lvname,), custom_icon="error") + continue # test mount point # check in pending logical volume requests # these may not have been put in master list of requests # yet if we have not hit 'OK' for the volume group creation - if fsystem.isMountable(): + if fmt_class().mountable and mountpoint: used = 0 - if logrequest: - curmntpt = logrequest.mountpoint - else: - curmntpt = None + curmntpt = getattr(format, "mountpoint", None) - for lv in self.logvolreqs: - if curmntpt and lv.mountpoint == curmntpt: + for _lv in self.lvs.values(): + if _lv['format'].type == "luks": + _format = self.luks[_lv['name']] + else: + _format = _lv['format'] + + if not _format.mountable or curmntpt and \ + _format.mountpoint == curmntpt: continue - if len(mntpt) == 0: - continue - if lv.mountpoint == mntpt: + if _format.mountpoint == mountpoint: used = 1 break @@ -580,48 +584,31 @@ class VolumeGroupEditor: self.intf.messageWindow(_("Mount point in use"), _("The mount point \"%s\" is in " "use. Please pick another.") % - (mntpt,), custom_icon="error") + (mountpoint,), + custom_icon="error") continue - # check out logical volumne name - lvname = string.strip(lvnameEntry.get_text()) + # check that size specification is numeric and positive + if not templv.exists: + badsize = 0 + try: + size = long(sizeEntry.get_text()) + except: + badsize = 1 - if not logrequest or not logrequest.getPreExisting(): - err = sanityCheckLogicalVolumeName(lvname) - if err: - self.intf.messageWindow(_("Illegal Logical Volume Name"),err, custom_icon="error") + if badsize or size <= 0: + self.intf.messageWindow(_("Illegal size"), + _("The requested size as entered is " + "not a valid number greater " + "than 0."), custom_icon="error") continue + else: + size = templv.size - # is it in use? - used = 0 - if logrequest: - origlvname = logrequest.logicalVolumeName - else: - origlvname = None - - for lv in self.logvolreqs: - if origlvname and lv.logicalVolumeName == origlvname: - continue - - if lv.logicalVolumeName == lvname: - used = 1 - break - - if used: - self.intf.messageWindow(_("Illegal logical volume name"), - _("The logical volume name \"%s\" is " - "already in use. Please pick " - "another.") % (lvname,), custom_icon="error") - continue - - # create potential request - request = copy.copy(logrequest) - request.encryption = copy.deepcopy(logrequest.encryption) - pesize = int(self.peCombo.get_active_value()) - size = lvm.clampLVSizeRequest(size, pesize, roundup=1) - - # do some final tests - maxlv = lvm.getMaxLVSize(pesize) + # check that size specification is within limits + pesize = int(self.peCombo.get_active_value()) / 1024.0 + size = lvm.clampSize(size, pesize, roundup=True) + maxlv = lvm.getMaxLVSize() if size > maxlv: self.intf.messageWindow(_("Not enough space"), _("The current requested size " @@ -635,107 +622,76 @@ class VolumeGroupEditor: custom_icon="error") continue - request.fstype = fsystem - - if request.fstype.isMountable(): - request.mountpoint = mntpt - else: - request.mountpoint = None - - request.preexist = preexist - request.logicalVolumeName = lvname - request.size = size - request.format = format - request.migrate = migrate - request.targetSize = targetSize - request.grow = 0 - - # this is needed to clear out any cached info about the device - # only a workaround - need to change way this is handled in - # partRequest.py really. - request.dev = None - - if self.lukscb and self.lukscb.get_active(): - if not request.encryption: - request.encryption = LUKSDevice(passphrase=self.partitions.encryptionPassphrase, format=1) + # Ok -- now we've done all the checks to validate the + # user-specified parameters. Time to set up the device... + origname = templv.lvname + if not templv.exists: + templv._name = lvname + templv.size = size + format = fmt_class(mountpoint=mountpoint) + if self.lukscb and self.lukscb.get_active() and \ + templv.format.type != "luks": + newluks = format + format = getFormat("luks", + passphrase=self.storage.encryptionPassphrase) + templv.format = format else: - request.encryption = None - - # make list of original logvol requests so we can skip them - # in tests below. We check for mount point name conflicts - # above within the current volume group, so it is not - # necessary to do now. - err = request.sanityCheckRequest(self.partitions, skipMntPtExistCheck=1, pesize=pesize) - if err is None: - skiplist = [] - for lv in self.origvolreqs: - skiplist.append(lv.uniqueID) - - err = request.isMountPointInUse(self.partitions, requestSkipList=skiplist) + # existing lv + if self.fsoptionsDict.has_key("formatcb") and \ + self.fsoptionsDict["formatcb"].get_active(): + format = fmt_class(mountpoint=mountpoint) + if self.lukscb and self.lukscb.get_active() and \ + templv.format.type != "luks": + newluks = format + format = getFormat("luks", + device=templv.path, + passphrase=self.storage.encryptionPassphrase) + + templv.format = format + elif format.mountable: + templv.format.mountpoint = mountpoint + + if self.fsoptionsDict.has_key("migratecb") and \ + self.fsoptionsDict["migratecb"].get_active(): + format.migrate = True - if err: - self.intf.messageWindow(_("Error With Request"), - "%s" % (err), custom_icon="error") - continue - - if (not request.format and - request.mountpoint and request.formatByDefault()): - if not queryNoFormatPreExisting(self.intf): - continue + if self.fsoptionsDict.has_key("resizecb") and self.fsoptionsDict["resizecb"].get_active(): + targetSize = self.fsoptionsDict["resizesb"].get_value_as_int() + templv.targetSize = targetSize + format.targetSize = targetSize - # see if there is room for request - (availSpaceMB, neededSpaceMB, fspace) = self.computeSpaceValues(usepe=pesize) + templv.format = format - tmplogreqs = [] - for l in self.logvolreqs: - if origlvname and l.logicalVolumeName == origlvname: + if format.exists and format.mountable and format.mountpoint: + tempdev = StorageDevice('tmp', format=format) + if self.storage.formatByDefault(tempdev) and \ + not queryNoFormatPreExisting(self.intf): continue - - tmplogreqs.append(l) - - tmplogreqs.append(request) - neededSpaceMB = self.computeLVSpaceNeeded(tmplogreqs) - - if neededSpaceMB > availSpaceMB: - self.intf.messageWindow(_("Not enough space"), - _("The logical volumes you have " - "configured require %d MB, but the " - "volume group only has %d MB. Please " - "either make the volume group larger " - "or make the logical volume(s) smaller.") % (neededSpaceMB, availSpaceMB), custom_icon="error") - del tmplogreqs - continue # everything ok break - # now remove the previous request, insert request created above - if not isNew: - self.logvolreqs.remove(logrequest) - iter = self.getCurrentLogicalVolume() - self.logvolstore.remove(iter) - if request.targetSize is not None: - size = request.targetSize - # adjust the free space in the vg - if logrequest.targetSize is not None: - diff = request.targetSize - logrequest.targetSize - else: - diff = request.targetSize - request.size - self.origvgrequest.free -= diff - - self.logvolreqs.append(request) + if templv.format.type == "luks": + if newluks: + self.luks[templv.lvname] = newluks - iter = self.logvolstore.append() - self.logvolstore.set_value(iter, 0, lvname) - if request.fstype and request.fstype.isMountable(): - self.logvolstore.set_value(iter, 1, mntpt) - else: - self.logvolstore.set_value(iter, 1, "N/A") - - self.logvolstore.set_value(iter, 2, "%Ld" % (size,)) + if self.luks.has_key(origname) and origname != templv.lvname: + self.luks[templv.lvname] = self.luks[origname] + del self.luks[templv.lvname] + elif templv.format.type != "luks" and self.luks.has_key(origname): + del self.luks[origname] + self.lvs[templv.lvname] = {'name': templv.lvname, + 'size': templv.size, + 'format': templv.format, + 'exists': templv.exists} + if self.lvs.has_key(origname) and origname != templv.lvname: + del self.lvs[origname] + + self.updateLogVolStore() self.updateVGSpaceLabels() dialog.destroy() + return def editCurrentLogicalVolume(self): iter = self.getCurrentLogicalVolume() @@ -744,15 +700,8 @@ class VolumeGroupEditor: return logvolname = self.logvolstore.get_value(iter, 0) - logrequest = None - for lv in self.logvolreqs: - if lv.logicalVolumeName == logvolname: - logrequest = lv - - if logrequest is None: - return - - self.editLogicalVolume(logrequest) + lv = self.lvs[logvolname] + self.editLogicalVolume(lv) def addLogicalVolumeCB(self, widget): if self.numAvailableLVSlots() < 1: @@ -761,8 +710,8 @@ class VolumeGroupEditor: "volumes per volume group.") % (lvm.MAX_LV_SLOTS,), custom_icon="error") return - (tspace, uspace, fspace) = self.computeSpaceValues() - if fspace <= 0: + (total, used, free) = self.computeSpaceValues() + if free <= 0: self.intf.messageWindow(_("No free space"), _("There is no room left in the " "volume group to create new logical " @@ -772,11 +721,15 @@ class VolumeGroupEditor: "the currently existing " "logical volumes"), custom_icon="error") return - - request = LogicalVolumeRequestSpec(fileSystemTypeGetDefault(), - size = fspace) - self.editLogicalVolume(request, isNew = 1) - return + + tempvg = self.getTempVG() + name = self.storage.createSuggestedLVName(tempvg) + self.lvs[name] = {'name': name, + 'size': free, + 'format': getFormat(self.storage.defaultFSType), + 'exists': False} + self.editLogicalVolume(self.lvs[name], isNew = 1) + return def editLogicalVolumeCB(self, widget): self.editCurrentLogicalVolume() @@ -798,31 +751,27 @@ class VolumeGroupEditor: if not rc: return - for lv in self.logvolreqs: - if lv.logicalVolumeName == logvolname: - self.logvolreqs.remove(lv) - - self.logvolstore.remove(iter) - - self.updateVGSpaceLabels() - return + del self.lvs[logvolname] + self.logvolstore.remove(iter) + self.updateVGSpaceLabels() + return def logvolActivateCb(self, view, path, col): self.editCurrentLogicalVolume() - def getSelectedPhysicalVolumes(self, model): - pv = [] - next = model.get_iter_first() - currow = 0 - while next is not None: + def getSelectedPhysicalVolumes(self): + model = self.lvmlist.get_model() + pv = [] + next = model.get_iter_first() + currow = 0 + while next is not None: iter = next val = model.get_value(iter, 0) partname = model.get_value(iter, 1) if val: - pvreq = self.partitions.getRequestByDeviceName(partname) - id = pvreq.uniqueID - pv.append(id) + dev = self.storage.devicetree.getDeviceByName(partname) + pv.append(dev) next = model.iter_next(iter) currow = currow + 1 @@ -831,12 +780,11 @@ class VolumeGroupEditor: def computeVGSize(self, pvlist, curpe): availSpaceMB = 0L - for id in pvlist: - pvreq = self.partitions.getRequestByID(id) - pvsize = pvreq.getActualSize(self.partitions, self.diskset) - pvsize = lvm.clampPVSize(pvsize, curpe) - (curpe/1024) + for pv in pvlist: + # have to clamp pvsize to multiple of PE + # XXX why the subtraction? fudging metadata? + pvsize = lvm.clampSize(pv.size, curpe) - (curpe/1024) - # have to clamp pvsize to multiple of PE availSpaceMB = availSpaceMB + pvsize log.info("computeVGSize: vgsize is %s" % (availSpaceMB,)) @@ -845,51 +793,46 @@ class VolumeGroupEditor: def computeLVSpaceNeeded(self, logreqs): neededSpaceMB = 0 for lv in logreqs: - neededSpaceMB = neededSpaceMB + lv.getActualSize(self.partitions, self.diskset, True) + neededSpaceMB = neededSpaceMB + lv.size return neededSpaceMB def updateLogVolStore(self): self.logvolstore.clear() - for lv in self.logvolreqs: + for lv in self.lvs.values(): iter = self.logvolstore.append() - size = lv.getActualSize(self.partitions, self.diskset, True) - lvname = lv.logicalVolumeName - mntpt = lv.mountpoint - if lvname: - self.logvolstore.set_value(iter, 0, lvname) + if lv['format'].type == "luks": + format = self.luks[lv['name']] + else: + format = lv['format'] + + mntpt = getattr(format, "mountpoint", "") + if lv['name']: + self.logvolstore.set_value(iter, 0, lv['name']) - if lv.fstype and lv.fstype.isMountable(): - if mntpt: - self.logvolstore.set_value(iter, 1, mntpt) - else: - self.logvolstore.set_value(iter, 1, "") + if format.type and format.mountable: + self.logvolstore.set_value(iter, 1, mntpt) else: self.logvolstore.set_value(iter, 1, "N/A") - self.logvolstore.set_value(iter, 2, "%Ld" % (size,)) + self.logvolstore.set_value(iter, 2, "%Ld" % lv['size']) - def updateVGSpaceLabels(self, alt_pvlist=None): - if alt_pvlist == None: - pvlist = self.getSelectedPhysicalVolumes(self.lvmlist.get_model()) - else: - pvlist = alt_pvlist - - (tspace, uspace, fspace) = self.computeSpaceValues(alt_pvlist=pvlist) + def updateVGSpaceLabels(self): + (total, used, free) = self.computeSpaceValues() - self.totalSpaceLabel.set_text("%10.2f MB" % (tspace,)) - self.usedSpaceLabel.set_text("%10.2f MB" % (uspace,)) + self.totalSpaceLabel.set_text("%10.2f MB" % (total,)) + self.usedSpaceLabel.set_text("%10.2f MB" % (used,)) - if tspace > 0: - usedpercent = (100.0*uspace)/tspace + if total > 0: + usedpercent = (100.0*used)/total else: usedpercent = 0.0 self.usedPercentLabel.set_text("(%4.1f %%)" % (usedpercent,)) - self.freeSpaceLabel.set_text("%10.2f MB" % (fspace,)) - if tspace > 0: - freepercent = (100.0*fspace)/tspace + self.freeSpaceLabel.set_text("%10.2f MB" % (free,)) + if total > 0: + freepercent = (100.0*free)/total else: freepercent = 0.0 @@ -900,44 +843,30 @@ class VolumeGroupEditor: # def run(self): if self.dialog is None: - return None + return [] while 1: rc = self.dialog.run() if rc == 2: self.destroy() - return None + return [] - pvlist = self.getSelectedPhysicalVolumes(self.lvmlist.get_model()) - pesize = int(self.peCombo.get_active_value()) - availSpaceMB = self.computeVGSize(pvlist, pesize) - neededSpaceMB = self.computeLVSpaceNeeded(self.logvolreqs) - - if neededSpaceMB > availSpaceMB: - self.intf.messageWindow(_("Not enough space"), - _("The logical volumes you have " - "configured require %d MB, but the " - "volume group only has %d MB. Please " - "either make the volume group larger " - "or make the logical volume(s) smaller.") % (neededSpaceMB, availSpaceMB), custom_icon="error") - continue + pvlist = self.getSelectedPhysicalVolumes() # check volume name - volname = string.strip(self.volnameEntry.get_text()) + volname = self.volnameEntry.get_text().strip() err = sanityCheckVolumeGroupName(volname) if err: self.intf.messageWindow(_("Invalid Volume Group Name"), err, custom_icon="error") continue - if self.origvgrequest: - origvname = self.origvgrequest.volumeGroupName - else: - origname = None + origvname = self.vg.name if origvname != volname: - if self.partitions.isVolumeGroupNameInUse(volname): + # maybe we should see if _any_ device has this name + if volname in [vg.name for vg in self.storage.vgs]: self.intf.messageWindow(_("Name in use"), _("The volume group name \"%s\" is " "already in use. Please pick " @@ -946,39 +875,220 @@ class VolumeGroupEditor: continue # get physical extent - pesize = int(self.peCombo.get_active_value()) + pesize = int(self.peCombo.get_active_value()) / 1024.0 # everything ok break - request = VolumeGroupRequestSpec(physvols = pvlist, vgname = volname, - pesize = pesize) + # here we have to figure out what all was done and convert it to + # devices and actions + # + # set up the vg with the right pvs + # set up the lvs + # set up the lvs' formats + # + log.debug("finished editing vg") + log.debug("pvs: %s" % [p.name for p in self.pvs]) + log.debug("luks: %s" % self.luks.keys()) + for lv in self.lvs.itervalues(): + log.debug("lv %s" % lv) + _luks = self.luks.get(lv['name']) + if _luks: + log.debug(" luks: %s" % _luks) + + actions = [] + origlvs = self.vg.lvs + if not self.vg.exists: + log.debug("non-existing vg -- setting up lvs, pvs, name, pesize") + # remove all of the lvs + for lv in self.vg.lvs: + self.vg._removeLogVol(lv) + + # set up the pvs + for pv in self.vg.pvs: + if pv not in self.pvs: + self.vg._removePV(pv) + for pv in self.pvs: + if pv not in self.vg.pvs: + self.vg._addPV(pv) + + self.vg.name = volname + self.vg.pesize = pesize + + if self.isNew: + actions = [ActionCreateDevice(self.vg)] + + # Schedule destruction of all non-existing lvs, their formats, + # luks devices, &c. Also destroy devices that have been removed. + for lv in origlvs: + log.debug("old lv %s..." % lv.lvname) + if not lv.exists or lv.lvname not in self.lvs or \ + (not self.lvs[lv.lvname]['exists'] and lv.exists): + log.debug("removing lv %s" % lv.lvname) + if lv.format.type == "luks": + try: + _luksdev = self.storage.devicetree.getChildren(lv)[0] + except IndexError: + pass + else: + if _luksdev.format.type: + actions.append(ActionDestroyFormat(_luksdev)) + + actions.append(ActionDestroyDevice(_luksdev)) + + if lv.format.type: + actions.append(ActionDestroyFormat(lv)) + + if lv in self.vg.lvs: + self.vg._removeLogVol(lv) + + actions.append(ActionDestroyDevice(lv)) + + # schedule creation of all new lvs, their formats, luks devices, &c + tempvg = self.getTempVG() + for lv in tempvg.lvs: + log.debug("new lv %s" % lv) + if not lv.exists: + log.debug("creating lv %s" % lv.lvname) + # create the device + newlv = LVMLogicalVolumeDevice(lv.lvname, + self.vg, + size=lv.size) + actions.append(ActionCreateDevice(newlv)) + + # create the format + mountpoint = getattr(lv.format, "mountpoint", None) + format = getFormat(lv.format.type, + mountpoint=mountpoint, + device=newlv.path) + actions.append(ActionCreateFormat(newlv, format)) + + if lv.format.type == "luks": + # create the luks device + newluks = LUKSDevice("luks-%s" % newlv.name, + parents=[newlv]) + actions.append(ActionCreateDevice(newluks)) + + # create the luks format + oldfmt = self.luks[lv.lvname] + mountpoint = getattr(oldfmt, "mountpoint", None) + format = getFormat(oldfmt.type, + mountpoint=mountpoint, + device=newluks.path) + actions.append(ActionCreateFormat(newluks, format)) + else: + log.debug("lv %s already exists" % lv.lvname) + # this lv is preexisting. check for resize and reformat. + # first, get the real/original lv + origlv = None + for _lv in self.vg.lvs: + if _lv.lvname == lv.lvname: + origlv = _lv + break + + if lv.resizable and lv.targetSize != origlv.currentSize: + actions.append(ActionResizeDevice(origlv, lv.targetSize)) + + if lv.format.exists: + log.debug("format already exists") + if hasattr(origlv.format, "mountpoint"): + origlv.format.mountpoint = lv.format.mountpoint + + if lv.format.migratable and lv.format.migrate and \ + not origlv.format.migrate: + origlv.format.migrate = lv.format.migrate + actions.append(ActionMigrateFormat(origlv)) + + if lv.format.resizable and \ + lv.format.targetSize != lv.format.currentSize: + new_size = lv.format.targetSize + actions.append(ActionResizeFormat(origlv, new_size)) + elif lv.format.type: + log.debug("new format: %s" % lv.format.type) + # destroy old format and any associated luks devices + if origlv.format.type: + if origlv.format.type == "luks": + # destroy the luks device and its format + try: + _luksdev = self.storage.devicetree.getChildren(origlv)[0] + except IndexError: + pass + else: + if _luksdev.format.type: + # this is probably unnecessary + actions.append(ActionDestroyFormat(_luksdev)) + + actions.append(ActionDestroyDevice(_luksdev)) + + actions.append(ActionDestroyFormat(origlv)) + + # create the format + mountpoint = getattr(lv.format, "mountpoint", None) + format = getFormat(lv.format.type, + mountpoint=mountpoint, + device=origlv.path) + actions.append(ActionCreateFormat(origlv, format)) + + if lv.format.type == "luks": + # create the luks device + newluks = LUKSDevice("luks-%s" % origlv.name, + parents=[origlv]) + actions.append(ActionCreateDevice(newluks)) + + # create the luks format + tmpfmt = self.luks[lv.lvname] + mountpoint = getattr(tmpfmt, "mountpoint", None) + format = getFormat(tmpfmt.type, + mountpoint=mountpoint, + device=newluks.path) + actions.append(ActionCreateFormat(newluks, format)) + else: + log.debug("no format!?") - # if it was preexisting, it still should be - if self.origvgrequest and self.origvgrequest.getPreExisting(): - request.preexist = 1 - elif self.origvgrequest: - request.format = self.origvgrequest.format - - return (request, self.logvolreqs) + return actions def destroy(self): if self.dialog: self.dialog.destroy() self.dialog = None - def __init__(self, anaconda, partitions, diskset, intf, parent, origvgrequest, isNew = 0): - self.partitions = partitions - self.diskset = diskset - self.origvgrequest = origvgrequest - self.isNew = isNew - self.intf = intf - self.parent = parent + def __init__(self, anaconda, intf, parent, vg, isNew = 0): + self.storage = anaconda.id.storage + + # the vg instance we were passed + self.vg = vg + self.peSize = vg.peSize + self.pvs = self.vg.pvs[:] + + # a dict of dicts + # keys are lv names + # values are dicts representing the lvs + # name, size, format instance, exists + self.lvs = {} + + # a dict of luks devices + # keys are lv names + # values are formats of the mapped devices + self.luks = {} + + self.isNew = isNew + self.intf = intf + self.parent = parent + self.actions = [] + + for lv in self.vg.lvs: + self.lvs[lv.lvname] = {"name": lv.lvname, + "size": lv.size, + "format": copy.copy(lv.format), + "exists": lv.exists} + + if lv.format.type == "luks": + try: + self.luks[lv.lvname] = self.storage.devicetree.getChildren(lv)[0].format + except IndexError: + self.luks[lv.lvname] = lv.format - self.availlvmparts = self.partitions.getAvailLVMPartitions(self.origvgrequest, - self.diskset) - self.logvolreqs = self.partitions.getLVMLVForVG(self.origvgrequest) - self.origvolreqs = copy.copy(self.logvolreqs) + self.availlvmparts = self.storage.unusedPVs(vg=vg) # if no PV exist, raise an error message and return if len(self.availlvmparts) < 1: @@ -997,8 +1107,8 @@ class VolumeGroupEditor: tstr = _("Make LVM Volume Group") else: try: - tstr = _("Edit LVM Volume Group: %s") % (origvgrequest.volumeGroupName,) - except: + tstr = _("Edit LVM Volume Group: %s") % (vg.name,) + except AttributeError: tstr = _("Edit LVM Volume Group") dialog = gtk.Dialog(tstr, self.parent) @@ -1014,17 +1124,17 @@ class VolumeGroupEditor: row = 0 # volume group name - if not origvgrequest.getPreExisting(): + if not vg.exists: lbl = createAlignedLabel(_("_Volume Group Name:")) self.volnameEntry = gtk.Entry(16) lbl.set_mnemonic_widget(self.volnameEntry) if not self.isNew: - self.volnameEntry.set_text(self.origvgrequest.volumeGroupName) + self.volnameEntry.set_text(self.vg.name) else: - self.volnameEntry.set_text(lvm.createSuggestedVGName(self.partitions, anaconda.id.network)) + self.volnameEntry.set_text(self.storage.createSuggestedVGName(anaconda.id.network)) else: lbl = createAlignedLabel(_("Volume Group Name:")) - self.volnameEntry = gtk.Label(self.origvgrequest.volumeGroupName) + self.volnameEntry = gtk.Label(self.vg.name) maintable.attach(lbl, 0, 1, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.SHRINK) @@ -1032,9 +1142,9 @@ class VolumeGroupEditor: row = row + 1 lbl = createAlignedLabel(_("_Physical Extent:")) - self.peCombo = self.createPEOptionMenu(self.origvgrequest.pesize) + self.peCombo = self.createPEOptionMenu(self.vg.peSize * 1024) lbl.set_mnemonic_widget(self.peCombo) - if origvgrequest.getPreExisting(): + if vg.exists: self.peCombo.set_sensitive(False) maintable.attach(lbl, 0, 1, row, row + 1, @@ -1042,8 +1152,8 @@ class VolumeGroupEditor: maintable.attach(self.peCombo, 1, 2, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.SHRINK) row = row + 1 - (self.lvmlist, sw) = self.createAllowedLvmPartitionsList(self.availlvmparts, self.origvgrequest.physicalVolumes, self.partitions, origvgrequest.getPreExisting()) - if origvgrequest.getPreExisting(): + (self.lvmlist, sw) = self.createAllowedLvmPartitionsList() + if vg.exists: self.lvmlist.set_sensitive(False) self.lvmlist.set_size_request(275, 80) lbl = createAlignedLabel(_("Physical Volumes to _Use:")) @@ -1105,15 +1215,21 @@ class VolumeGroupEditor: gobject.TYPE_STRING, gobject.TYPE_STRING) - if self.logvolreqs: - for lvrequest in self.logvolreqs: + if self.vg.lvs: + for lv in self.vg.lvs: iter = self.logvolstore.append() - self.logvolstore.set_value(iter, 0, lvrequest.logicalVolumeName) - if lvrequest.mountpoint is not None: - self.logvolstore.set_value(iter, 1, lvrequest.mountpoint) + self.logvolstore.set_value(iter, 0, lv.lvname) + if lv.format.type == "luks": + format = self.storage.devicetree.getChildren(lv)[0].format + else: + format = lv.format + + if getattr(format, "mountpoint", None): + self.logvolstore.set_value(iter, 1, + format.mountpoint) else: self.logvolstore.set_value(iter, 1, "") - self.logvolstore.set_value(iter, 2, "%Ld" % (lvrequest.getActualSize(self.partitions, self.diskset, True))) + self.logvolstore.set_value(iter, 2, "%Ld" % lv.size) self.logvollist = gtk.TreeView(self.logvolstore) col = gtk.TreeViewColumn(_("Logical Volume Name"), diff --git a/iw/osbootwidget.py b/iw/osbootwidget.py index 5263b853e..37a5eba39 100644 --- a/iw/osbootwidget.py +++ b/iw/osbootwidget.py @@ -23,7 +23,6 @@ import gtk import gobject import iutil import parted -import partedUtils import gui import datacombo from constants import * @@ -36,8 +35,7 @@ class OSBootWidget: def __init__(self, anaconda, parent, blname = None): self.bl = anaconda.id.bootloader - self.fsset = anaconda.id.fsset - self.diskset = anaconda.id.diskset + self.storage = anaconda.id.storage self.parent = parent self.intf = anaconda.intf if blname is not None: @@ -153,26 +151,22 @@ class OSBootWidget: label = gui.MnemonicLabel(_("_Device")) table.attach(label, 0, 1, 2, 3, gtk.FILL, 0, 10) if not isRoot: - # XXX should potentially abstract this out into a function - pedparts = [] parts = [] - disks = self.diskset.disks - func = lambda part: (part.active and - part.getFlag(parted.PARTITION_LVM) != 1 and - part.getFlag(parted.PARTITION_RAID) != 1) - for drive in disks.keys(): - pedparts.extend(partedUtils.filter_partitions(disks[drive], func)) - for part in pedparts: - parts.append(part.getDeviceNodeName()) - del pedparts - parts.sort() + + for part in self.storage.partitions: + if part.partedPartition.getFlag(parted.PARTITION_LVM) or \ + part.partedPartition.getFlag(parted.PARTITION_RAID) or \ + not part.partedPartition.active: + continue + + parts.append(part) deviceCombo = datacombo.DataComboBox() defindex = 0 i = 0 - for part in parts: - deviceCombo.append("/dev/%s" %(part,), part) - if oldDevice and oldDevice == part: + for part in parts: + deviceCombo.append(part.path, part.name) + if oldDevice and oldDevice == part.name: defindex = i i = i + 1 @@ -367,8 +361,8 @@ class OSBootWidget: continue isRoot = 0 - fsentry = self.fsset.getEntryByDeviceName(dev) - if fsentry and fsentry.getMountPoint() == '/': + rootDev = self.storage.fsset.rootDevice + if rootDev and rootDev.name == dev: isRoot = 1 iter = self.osStore.append() diff --git a/iw/partition_dialog_gui.py b/iw/partition_dialog_gui.py index 04e45db6a..71c7333b3 100644 --- a/iw/partition_dialog_gui.py +++ b/iw/partition_dialog_gui.py @@ -26,9 +26,8 @@ import gobject import gtk import gui -from fsset import * -from cryptodev import LUKSDevice -from partRequests import * +from storage.devices import PartitionDevice, LUKSDevice +from storage.deviceaction import * from partition_ui_helpers_gui import * from constants import * @@ -50,13 +49,6 @@ class PartitionEditor: adj.clamp_page(size, adj.upper) fillmaxszsb.set_adjustment(adj) - def cylspinchangedCB(self, widget, data): - (dev, startcylspin, endcylspin, bycyl_sizelabel) = data - startsec = dev.startCylinderToSector(startcylspin.get_value_as_int()) - endsec = dev.endCylinderToSector(endcylspin.get_value_as_int()) - cursize = (endsec - startsec)/2048 - bycyl_sizelabel.set_text("%s" % (int(cursize))) - def fillmaxszCB(self, widget, spin): spin.set_sensitive(widget.get_active()) @@ -86,11 +78,11 @@ class PartitionEditor: # default to fixed, turn off max size spinbutton fillmaxszsb.set_sensitive(0) - if request.grow: - if request.maxSizeMB != None: + if request.req_grow: + if request.req_max_size: fillmaxszrb.set_active(1) fillmaxszsb.set_sensitive(1) - fillmaxszsb.set_value(request.maxSizeMB) + fillmaxszsb.set_value(request.req_max_size) else: fillunlimrb.set_active(1) else: @@ -107,154 +99,163 @@ class PartitionEditor: def run(self): if self.dialog is None: - return None + return [] while 1: rc = self.dialog.run() + actions = [] + luksdev = None # user hit cancel, do nothing if rc == 2: self.destroy() - return None + return [] - if self.origrequest.type == REQUEST_NEW: + if not self.origrequest.exists: # read out UI into a partition specification - filesystem = self.newfstypeCombo.get_active_value() - - request = copy.copy(self.origrequest) - request.fstype = filesystem - request.format = True - - if request.fstype.isMountable(): - request.mountpoint = self.mountCombo.get_children()[0].get_text() - else: - request.mountpoint = None + fmt_class = self.newfstypeCombo.get_active_value() + # there's nothing about origrequest we care about + #request = copy.copy(self.origrequest) + mountpoint = self.mountCombo.get_children()[0].get_text() if self.primonlycheckbutton.get_active(): - primonly = True + primary = True else: - primonly = None + primary = None - if self.lukscb and self.lukscb.get_active(): - if not request.encryption: - request.encryption = LUKSDevice(passphrase = self.partitions.encryptionPassphrase, format=1) + if self.fixedrb.get_active(): + grow = None else: - request.encryption = None - - if not self.newbycyl: - if self.fixedrb.get_active(): - grow = None - else: - grow = True + grow = True - self.sizespin.update() + self.sizespin.update() - if self.fillmaxszrb.get_active(): - self.fillmaxszsb.update() - maxsize = self.fillmaxszsb.get_value_as_int() - else: - maxsize = None - - allowdrives = [] - model = self.driveview.get_model() - iter = model.get_iter_first() - while iter: - val = model.get_value(iter, 0) - drive = model.get_value(iter, 1) - - if val: - allowdrives.append(drive) - - iter = model.iter_next(iter) - - if len(allowdrives) == len(self.diskset.disks.keys()): - allowdrives = None - - request.size = self.sizespin.get_value_as_int() - request.drive = allowdrives - request.grow = grow - request.primary = primonly - request.maxSizeMB = maxsize + if self.fillmaxszrb.get_active(): + self.fillmaxszsb.update() + maxsize = self.fillmaxszsb.get_value_as_int() else: - self.startcylspin.update() - self.endcylspin.update() - - request.start = self.startcylspin.get_value_as_int() - request.end = self.endcylspin.get_value_as_int() - request.primary = primonly - - if request.end <= request.start: - self.intf.messageWindow(_("Error With Request"), - _("The end cylinder must be " - "greater than the start " - "cylinder."), custom_icon="error") - - continue - - err = request.sanityCheckRequest(self.partitions) - if not err: - err = doUIRAIDLVMChecks(request, self.diskset) - - if err: - self.intf.messageWindow(_("Error With Request"), - "%s" % (err), custom_icon="error") - continue - else: - # preexisting partition, just set mount point and format flag - request = copy.copy(self.origrequest) - request.encryption = copy.deepcopy(self.origrequest.encryption) - - if self.fsoptionsDict.has_key("formatcb"): - request.format = self.fsoptionsDict["formatcb"].get_active() - if request.format: - request.fstype = self.fsoptionsDict["fstypeCombo"].get_active_value() + maxsize = None + + allowdrives = [] + model = self.driveview.get_model() + iter = model.get_iter_first() + while iter: + val = model.get_value(iter, 0) + drive = model.get_value(iter, 1) + + if val: + allowdrives.append(drive) + + iter = model.iter_next(iter) + + if len(allowdrives) == len(self.storage.disks): + allowdrives = None + + size = self.sizespin.get_value_as_int() + disks = [] + if allowdrives: + for drive in allowdrives: + for disk in self.storage.disks: + if disk.name == drive: + disks.append(disk) + + format = fmt_class(mountpoint=mountpoint) + if self.isNew: + request = self.storage.newPartition(size=size, + grow=grow, + maxsize=maxsize, + primary=primary, + format=format, + parents=disks) else: - request.format = 0 - - if self.fsoptionsDict.has_key("migratecb"): - request.migrate = self.fsoptionsDict["migratecb"].get_active() - if request.migrate: - request.fstype =self.fsoptionsDict["migfstypeCombo"].get_active_value() - else: - request.migrate = 0 - - if self.fsoptionsDict.has_key("resizecb") and self.fsoptionsDict["resizecb"].get_active(): - request.targetSize = self.fsoptionsDict["resizesb"].get_value_as_int() - else: - request.targetSize = None - - # set back if we are not formatting or migrating - origfstype = self.origrequest.origfstype - if not request.format and not request.migrate: - request.fstype = origfstype + request = self.origrequest + + if self.lukscb and self.lukscb.get_active() and \ + request.format.type != "luks": + luksformat = format + format = getFormat("luks", + passphrase=self.storage.encryptionPassphrase) + luksdev = LUKSDevice("luks%d" % self.storage.nextID, + format=luksformat, + parents=request) + elif self.lukscb and not self.lukscb.get_active() and \ + self.origrequest.format.type == "luks": + # destroy the luks format and the mapped device + try: + luksdev = self.storage.devicetree.getChildren(self.origrequest)[0] + except IndexError: + pass + else: + actions.append(ActionDestroyFormat(luksdev)) + actions.append(ActionDestroyDevice(luksdev)) + luksdev = None - if request.fstype.isMountable(): - request.mountpoint = self.mountCombo.get_children()[0].get_text() - else: - request.mountpoint = None + actions.append(ActionDestroyFormat(request)) - lukscb = self.fsoptionsDict.get("lukscb") - if lukscb and lukscb.get_active(): - if not request.encryption: - request.encryption = LUKSDevice(passphrase=self.partitions.encryptionPassphrase, format=1) + if self.isNew: + # we're all set, so create the actions + actions.append(ActionCreateDevice(request)) else: - request.encryption = None - - err = request.sanityCheckRequest(self.partitions) - if err: - self.intf.messageWindow(_("Error With Request"), - "%s" % (err), custom_icon="error") - continue - - if (not request.format and - request.mountpoint and request.formatByDefault()): + request.req_size = size + request.req_grow = grow + request.req_max_size = maxsize + request.req_primary = primary + request.req_disks = disks + + actions.append(ActionCreateFormat(request, format)) + if luksdev: + actions.append(ActionCreateDevice(luksdev)) + actions.append(ActionCreateFormat(luksdev)) + else: + # preexisting partition, just set mount point and format flag + request = self.origrequest + mountpoint = self.mountCombo.get_children()[0].get_text() + if self.fsoptionsDict.has_key("formatcb") and \ + self.fsoptionsDict["formatcb"].get_active(): + fmt_class = self.fsoptionsDict["fstypeCombo"].get_active_value() + format = fmt_class(mountpoint=mountpoint) + luksdev = None + if self.fsoptionsDict.has_key("lukscb") and \ + self.fsoptionsDict["lukscb"].get_active() and \ + request.format.type != "luks": + luksdev = LUKSDevice("luks%d" % self.storage.nextID, + format=format, + parents=request) + format = getFormat("luks", + device=self.origrequest.path, + passphrase=self.storage.encryptionPassphrase) + actions.append(ActionCreateFormat(request, format)) + if luksdev: + actions.append(ActionCreateDevice(luksdev)) + actions.append(ActionCreateFormat(luksdev)) + elif request.format.mountable: + request.format.mountpoint = mountpoint + + if self.fsoptionsDict.has_key("migratecb") and \ + self.fsoptionsDict["migratecb"].get_active(): + actions.append(ActionMigrateFormat(request)) + + if self.fsoptionsDict.has_key("resizecb") and \ + self.fsoptionsDict["resizecb"].get_active(): + size = self.fsoptionsDict["resizesb"].get_value_as_int() + + try: + actions.append(ActionResizeDevice(request, size)) + if request.format.type: + actions.append(ActionResizeFormat(request, size)) + except ValueError: + pass + + if request.format.exists and \ + getattr(request, "mountpoint", None) and \ + self.storage.formatByDefault(request): if not queryNoFormatPreExisting(self.intf): continue - - # everything ok, fall out of loop + + # everything ok, fall out of loop break - - return request + + return actions def destroy(self): if self.dialog: @@ -265,8 +266,7 @@ class PartitionEditor: def __init__(self, anaconda, parent, origrequest, isNew = 0, restrictfs = None): self.anaconda = anaconda - self.partitions = self.anaconda.id.partitions - self.diskset = self.anaconda.id.diskset + self.storage = self.anaconda.id.storage self.intf = self.anaconda.intf self.origrequest = origrequest self.isNew = isNew @@ -275,10 +275,7 @@ class PartitionEditor: if isNew: tstr = _("Add Partition") else: - try: - tstr = _("Edit Partition: /dev/%s") % (origrequest.device,) - except: - tstr = _("Edit Partition") + tstr = _("Edit Partition: %s") % (origrequest.path,) self.dialog = gtk.Dialog(tstr, self.parent) gui.addFrame(self.dialog) @@ -291,24 +288,34 @@ class PartitionEditor: maintable.set_col_spacings(5) row = 0 - # see if we are creating a floating request or by cylinder - if self.origrequest.type == REQUEST_NEW: - self.newbycyl = self.origrequest.start != None + # if this is a luks device we need to grab info from two devices + # to make it seem like one device. wee! + if self.origrequest.format.type == "luks": + try: + luksdev = self.storage.devicetree.getChildren(self.origrequest)[0] + except IndexError: + usereq = self.origrequest + luksdev = None + else: + usereq = luksdev + else: + luksdev = None + usereq = self.origrequest # Mount Point entry lbl = createAlignedLabel(_("_Mount Point:")) maintable.attach(lbl, 0, 1, row, row + 1) - self.mountCombo = createMountPointCombo(origrequest) + self.mountCombo = createMountPointCombo(usereq) lbl.set_mnemonic_widget(self.mountCombo) maintable.attach(self.mountCombo, 1, 2, row, row + 1) row = row + 1 # Partition Type - if self.origrequest.type == REQUEST_NEW: + if not self.origrequest.exists: lbl = createAlignedLabel(_("File System _Type:")) maintable.attach(lbl, 0, 1, row, row + 1) - self.newfstypeCombo = createFSTypeMenu(self.origrequest.fstype, + self.newfstypeCombo = createFSTypeMenu(usereq.format, fstypechangeCB, self.mountCombo, availablefstypes = restrictfs) @@ -320,103 +327,49 @@ class PartitionEditor: row = row + 1 # allowable drives - if self.origrequest.type == REQUEST_NEW: - if not self.newbycyl: - lbl = createAlignedLabel(_("Allowable _Drives:")) - maintable.attach(lbl, 0, 1, row, row + 1) - - self.driveview = createAllowedDrivesList(self.diskset.disks, - self.origrequest.drive, - selectDrives=False, - disallowDrives=[self.anaconda.updateSrc]) - lbl.set_mnemonic_widget(self.driveview) - sw = gtk.ScrolledWindow() - sw.add(self.driveview) - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.set_shadow_type(gtk.SHADOW_IN) - maintable.attach(sw, 1, 2, row, row + 1) - self.driveview.set_size_request(375, 80) - else: - maintable.attach(createAlignedLabel(_("Drive:")), - 0, 1, row, row + 1) - maintable.attach(createAlignedLabel(origrequest.drive[0]), - 1, 2, row, row + 1) + if not self.origrequest.exists: + lbl = createAlignedLabel(_("Allowable _Drives:")) + maintable.attach(lbl, 0, 1, row, row + 1) + + self.driveview = createAllowedDrivesList(self.storage.disks, + self.origrequest.req_disks, + selectDrives=False, + disallowDrives=[self.anaconda.updateSrc]) + lbl.set_mnemonic_widget(self.driveview) + sw = gtk.ScrolledWindow() + sw.add(self.driveview) + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + sw.set_shadow_type(gtk.SHADOW_IN) + maintable.attach(sw, 1, 2, row, row + 1) + self.driveview.set_size_request(375, 80) row = row + 1 # original fs label - if self.origrequest.type != REQUEST_NEW and self.origrequest.fslabel: + if usereq.format.exists and \ + getattr(usereq.format, "label", None): maintable.attach(createAlignedLabel(_("Original File System " "Label:")), 0, 1, row, row + 1) - fslabel = gtk.Label(self.origrequest.fslabel) + fslabel = gtk.Label(usereq.format.label) maintable.attach(fslabel, 1, 2, row, row + 1) - row = row + 1 # size - if self.origrequest.type == REQUEST_NEW: - if not self.newbycyl: - # Size specification - lbl = createAlignedLabel(_("_Size (MB):")) - maintable.attach(lbl, 0, 1, row, row + 1) - sizeAdj = gtk.Adjustment(value = 1, lower = 1, - upper = MAX_PART_SIZE, step_incr = 1) - self.sizespin = gtk.SpinButton(sizeAdj, digits = 0) - self.sizespin.set_property('numeric', True) - - if self.origrequest.size: - self.sizespin.set_value(self.origrequest.size) - - lbl.set_mnemonic_widget(self.sizespin) - maintable.attach(self.sizespin, 1, 2, row, row + 1) - bycyl_sizelabel = None - else: - # XXX need to add partition by size and - # wire in limits between start and end - dev = self.diskset.disks[origrequest.drive[0]].dev - maintable.attach(createAlignedLabel(_("Size (MB):")), - 0, 1, row, row + 1) - bycyl_sizelabel = createAlignedLabel("") - maintable.attach(bycyl_sizelabel, 1, 2, row, row + 1) - row = row + 1 + if not self.origrequest.exists: + # Size specification + lbl = createAlignedLabel(_("_Size (MB):")) + maintable.attach(lbl, 0, 1, row, row + 1) + sizeAdj = gtk.Adjustment(value = 1, lower = 1, + upper = MAX_PART_SIZE, step_incr = 1) + self.sizespin = gtk.SpinButton(sizeAdj, digits = 0) + self.sizespin.set_property('numeric', True) - lbl = createAlignedLabel(_("_Start Cylinder:")) - maintable.attach(lbl, 0, 1, row, row + 1) - - (maxcyl, h, s) = self.diskset.disks[origrequest.drive[0]].device.biosGeometry - cylAdj = gtk.Adjustment(value=origrequest.start, - lower=origrequest.start, - upper=maxcyl, - step_incr=1) - self.startcylspin = gtk.SpinButton(cylAdj, digits=0) - self.startcylspin.set_property('numeric', True) - lbl.set_mnemonic_widget(self.startcylspin) - maintable.attach(self.startcylspin, 1, 2, row, row + 1) - row = row + 1 - - endcylAdj = gtk.Adjustment(value=origrequest.end, - lower=origrequest.start, - upper=maxcyl, - step_incr=1) - lbl = createAlignedLabel(_("_End Cylinder:")) - maintable.attach(lbl, 0, 1, row, row + 1) - self.endcylspin = gtk.SpinButton(endcylAdj, digits = 0) - self.endcylspin.set_property('numeric', True) - lbl.set_mnemonic_widget(self.endcylspin) - maintable.attach(self.endcylspin, 1, 2, row, row + 1) - - self.startcylspin.connect("value-changed", self.cylspinchangedCB, - (dev, self.startcylspin, - self.endcylspin, bycyl_sizelabel)) - self.endcylspin.connect("value-changed", self.cylspinchangedCB, - (dev, self.startcylspin, - self.endcylspin, bycyl_sizelabel)) - - startsec = dev.startCylinderToSector(origrequest.start) - endsec = dev.endCylinderToSector(origrequest.end) - cursize = (endsec - startsec)/2048 - bycyl_sizelabel.set_text("%s" % (int(cursize))) + if self.origrequest.req_size: + self.sizespin.set_value(self.origrequest.req_size) + + lbl.set_mnemonic_widget(self.sizespin) + maintable.attach(self.sizespin, 1, 2, row, row + 1) else: self.sizespin = None @@ -425,45 +378,42 @@ class PartitionEditor: # format/migrate options for pre-existing partitions, as long as they # aren't protected (we'd still like to be able to mount them, though) self.fsoptionsDict = {} - if self.origrequest.type == REQUEST_PREEXIST and self.origrequest.fstype and not self.origrequest.getProtected(): - (row, self.fsoptionsDict) = createPreExistFSOptionSection(self.origrequest, maintable, row, self.mountCombo, self.partitions) + if self.origrequest.exists and \ + not self.storage.isProtected(self.origrequest): + (row, self.fsoptionsDict) = createPreExistFSOptionSection(self.origrequest, maintable, row, self.mountCombo, self.storage, luksdev=luksdev) # size options - if self.origrequest.type == REQUEST_NEW: - if not self.newbycyl: - (sizeframe, self.fixedrb, self.fillmaxszrb, - self.fillmaxszsb) = self.createSizeOptionsFrame(self.origrequest, - self.fillmaxszCB) - self.sizespin.connect("value-changed", self.sizespinchangedCB, - self.fillmaxszsb) - - maintable.attach(sizeframe, 0, 2, row, row + 1) - else: - # XXX need new by cyl options (if any) - pass + if not self.origrequest.exists: + (sizeframe, self.fixedrb, self.fillmaxszrb, + self.fillmaxszsb) = self.createSizeOptionsFrame(self.origrequest, + self.fillmaxszCB) + self.sizespin.connect("value-changed", self.sizespinchangedCB, + self.fillmaxszsb) + + maintable.attach(sizeframe, 0, 2, row, row + 1) row = row + 1 else: self.sizeoptiontable = None # create only as primary - if self.origrequest.type == REQUEST_NEW: + if not self.origrequest.exists: self.primonlycheckbutton = gtk.CheckButton(_("Force to be a _primary " "partition")) self.primonlycheckbutton.set_active(0) - if self.origrequest.primary: + if self.origrequest.req_primary: self.primonlycheckbutton.set_active(1) # only show if we have something other than primary - if not self.diskset.onlyPrimaryParts(): + if self.storage.extendedPartitionsSupported(): maintable.attach(self.primonlycheckbutton, 0, 2, row, row+1) row = row + 1 # checkbutton for encryption using dm-crypt/LUKS - if self.origrequest.type == REQUEST_NEW: + if not self.origrequest.exists: self.lukscb = gtk.CheckButton(_("_Encrypt")) self.lukscb.set_data("formatstate", 1) - if self.origrequest.encryption: + if self.origrequest.format.type == "luks": self.lukscb.set_active(1) else: self.lukscb.set_active(0) diff --git a/iw/partition_gui.py b/iw/partition_gui.py index 940c2525f..6ce1b11a1 100644 --- a/iw/partition_gui.py +++ b/iw/partition_gui.py @@ -27,15 +27,13 @@ try: except ImportError: import gnome.canvas as gnomecanvas import pango -import autopart import gui import parted import string import types -import raid from constants import * -import lvm +import storage from iw_gui import * from flags import flags @@ -44,10 +42,10 @@ import raid_dialog_gui import partition_dialog_gui from partIntfHelpers import * -from partedUtils import * -from fsset import * -from partRequests import * +from constants import * from partition_ui_helpers_gui import * +from storage.partitioning import doPartitioning +from storage.devicelibs import lvm import gettext _ = lambda x: gettext.ldgettext("anaconda", x) @@ -98,7 +96,7 @@ class DiskStripeSlice: if event.button == 1: self.parent.selectSlice(self.partition, 1) elif event.type == gtk.gdk._2BUTTON_PRESS: - self.editCb() + self.editCB() return True @@ -144,12 +142,12 @@ class DiskStripeSlice: if self.partition.type & parted.PARTITION_FREESPACE: rc = "Free\n" else: - rc = "%s\n" % (self.partition.getDeviceNodeName(),) + rc = "%s\n" % (self.partition.getDeviceNodeName().split("/")[-1],) rc = rc + "%Ld MB" % (self.partition.getSize(unit="MB"),) return rc def getDeviceName(self): - return self.partition.getDeviceNodeName() + return self.partition.getDeviceNodeName().split("/")[-1] def update(self): disk = self.parent.getDisk() @@ -182,12 +180,12 @@ class DiskStripeSlice: clip_width=xlength-1, clip_height=yheight-1) self.hideOrShowText() - def __init__(self, parent, partition, treeView, editCb): + def __init__(self, parent, partition, treeView, editCB): self.text = None self.partition = partition self.parent = parent self.treeView = treeView - self.editCb = editCb + self.editCB = editCB pgroup = parent.getGroup() self.group = pgroup.add(gnomecanvas.CanvasGroup) @@ -198,14 +196,14 @@ class DiskStripeSlice: self.update() class DiskStripe: - def __init__(self, drive, disk, group, tree, editCb): + def __init__(self, drive, disk, group, tree, editCB): self.disk = disk self.group = group self.tree = tree self.drive = drive self.slices = [] self.hash = {} - self.editCb = editCb + self.editCB = editCB self.selected = None # XXX hack but will work for now @@ -259,17 +257,17 @@ class DiskStripe: self.selected = None def add(self, partition): - stripe = DiskStripeSlice(self, partition, self.tree, self.editCb) + stripe = DiskStripeSlice(self, partition, self.tree, self.editCB) self.slices.append(stripe) self.hash[partition] = stripe class DiskStripeGraph: - def __init__(self, tree, editCb): + def __init__(self, tree, editCB): self.canvas = gnomecanvas.Canvas() self.diskStripes = [] self.textlabels = [] self.tree = tree - self.editCb = editCb + self.editCB = editCB self.next_ypos = 0.0 def __del__(self): @@ -334,7 +332,7 @@ class DiskStripeGraph: self.textlabels.append(text) group = self.canvas.root().add(gnomecanvas.CanvasGroup, x=0, y=yoff+textheight) - stripe = DiskStripe(drive, disk, group, self.tree, self.editCb) + stripe = DiskStripe(drive, disk, group, self.tree, self.editCB) self.diskStripes.append(stripe) self.next_ypos = self.next_ypos + STRIPE_HEIGHT+textheight+10 return stripe @@ -371,8 +369,6 @@ class DiskTreeModel(gtk.TreeStore): # (N_("Size (MB)"), gobject.TYPE_STRING, 1.0, 0, isLeaf), (N_("Format"), gobject.TYPE_OBJECT, 0.5, 0, isFormattable), (N_("Size (MB)"), gobject.TYPE_STRING, 1.0, 0, 0), - (N_("Start"), gobject.TYPE_STRING, 1.0, 0, 1), - (N_("End"), gobject.TYPE_STRING, 1.0, 0, 1), ("", gobject.TYPE_STRING, 0.0, 0, 0), # the following must be the last two ("IsLeaf", gobject.TYPE_BOOLEAN, 0.0, 1, 0), @@ -487,25 +483,7 @@ class DiskTreeModel(gtk.TreeStore): # not found the partition raise RuntimeError, "could not find partition" - """ returns partition 'id' of current selection in tree """ - def getCurrentPartition(self): - selection = self.view.get_selection() - model, iter = selection.get_selected() - if not iter: - return None - - pyobject = self.titleSlot['PyObject'] - try: - val = self.get_value(iter, pyobject) - if type(val) == type("/dev/"): - if val[:5] == '/dev/': - return None - - return val - except: - return None - - """ Return name of current selected drive (if a drive is highlighted) """ + """ Return the device representing the current selection """ def getCurrentDevice(self): selection = self.view.get_selection() model, iter = selection.get_selected() @@ -515,12 +493,10 @@ class DiskTreeModel(gtk.TreeStore): pyobject = self.titleSlot['PyObject'] try: val = self.get_value(iter, pyobject) - if type(val) == type("/dev/"): - if val[:5] == '/dev/': - return val - return None - except: - return None + except Exception: + val = None + + return val def resetSelection(self): pass @@ -631,8 +607,7 @@ class PartitionWindow(InstallWindow): return rc def getNext(self): - (errors, warnings) = self.partitions.sanityCheckAllRequests(self.diskset) - + (errors, warnings) = self.storage.sanityCheck() if errors: labelstr1 = _("The partitioning scheme you requested " "caused the following critical errors.") @@ -648,6 +623,7 @@ class PartitionWindow(InstallWindow): raise gui.StayOnScreen if warnings: + # "storage configuration" labelstr1 = _("The partitioning scheme you requested " "generated the following warnings.") labelstr2 = _("Would you like to continue with " @@ -662,8 +638,7 @@ class PartitionWindow(InstallWindow): if rc != 1: raise gui.StayOnScreen - formatWarnings = getPreExistFormatWarnings(self.partitions, - self.diskset) + formatWarnings = getPreExistFormatWarnings(self.storage) if formatWarnings: labelstr1 = _("The following pre-existing partitions have been " "selected to be formatted, destroying all data.") @@ -675,7 +650,7 @@ class PartitionWindow(InstallWindow): commentstr = "" for (dev, type, mntpt) in formatWarnings: commentstr = commentstr + \ - "/dev/%s %s %s\n" % (dev,type,mntpt) + "%s %s %s\n" % (dev,type,mntpt) rc = self.presentPartitioningComments(_("Format Warnings"), labelstr1, labelstr2, @@ -694,161 +669,173 @@ class PartitionWindow(InstallWindow): def getPrev(self): self.diskStripeGraph.shutDown() - self.diskset.refreshDevices() - self.partitions.setFromDisk(self.diskset) + self.storage.reset() self.tree.clear() del self.parent return None - def getShortFSTypeName(self, name): - if name == "physical volume (LVM)": - return "LVM PV" - - return name - def populate(self, initial = 0): - - drives = self.diskset.disks.keys() - drives.sort() - self.tree.resetSelection() self.tree.clearHiddenPartitionsList() # first do LVM - lvmrequests = self.partitions.getLVMRequests() - if lvmrequests: + vgs = self.storage.vgs + if vgs: lvmparent = self.tree.append(None) self.tree[lvmparent]['Device'] = _("LVM Volume Groups") - for vgname in lvmrequests.keys(): - vgrequest = self.partitions.getRequestByVolumeGroupName(vgname) - rsize = vgrequest.getActualSize(self.partitions, self.diskset) + for vg in vgs: + rsize = vg.size vgparent = self.tree.append(lvmparent) - self.tree[vgparent]['Device'] = "%s" % (vgname,) - if vgrequest and vgrequest.type != REQUEST_NEW and vgrequest.fslabel: - self.tree[vgparent]['Label'] = "%s" % (vgrequest.fslabel,) - else: - self.tree[vgparent]['Label'] = "" + self.tree[vgparent]['Device'] = "%s" % vg.name + self.tree[vgparent]['Label'] = "" self.tree[vgparent]['Mount Point'] = "" - self.tree[vgparent]['Start'] = "" - self.tree[vgparent]['End'] = "" self.tree[vgparent]['Size (MB)'] = "%Ld" % (rsize,) - self.tree[vgparent]['PyObject'] = str(vgrequest.uniqueID) - for lvrequest in lvmrequests[vgname]: + self.tree[vgparent]['PyObject'] = vg + for lv in vg.lvs: + if lv.format.type == "luks": + # we'll want to grab format info from the mapped + # device, not the encrypted one + try: + dm_dev = self.storage.devicetree.getChildren(lv)[0] + except IndexError: + format = lv.format + else: + format = dm_dev.format + else: + format = lv.format iter = self.tree.append(vgparent) - self.tree[iter]['Device'] = lvrequest.logicalVolumeName - if lvrequest.fstype and lvrequest.mountpoint: - self.tree[iter]['Mount Point'] = lvrequest.mountpoint + self.tree[iter]['Device'] = lv.lvname + if format.mountable and format.mountpoint: + self.tree[iter]['Mount Point'] = format.mountpoint else: self.tree[iter]['Mount Point'] = "" - self.tree[iter]['Size (MB)'] = "%Ld" % (lvrequest.getActualSize(self.partitions, self.diskset, True),) - self.tree[iter]['PyObject'] = str(lvrequest.uniqueID) + self.tree[iter]['Size (MB)'] = "%Ld" % lv.size + self.tree[iter]['PyObject'] = vg - ptype = lvrequest.fstype.getName() - if lvrequest.isEncrypted(self.partitions, True) and lvrequest.format: + if lv.format.type == "luks" and not lv.format.exists: + # we're creating the LUKS header self.tree[iter]['Format'] = self.lock_pixbuf - elif lvrequest.format: + elif not format.exists: + # we're creating a format on the device self.tree[iter]['Format'] = self.checkmark_pixbuf - self.tree[iter]['IsFormattable'] = lvrequest.fstype.isFormattable() + self.tree[iter]['IsFormattable'] = format.formattable self.tree[iter]['IsLeaf'] = True - self.tree[iter]['Type'] = ptype - self.tree[iter]['Start'] = "" - self.tree[iter]['End'] = "" + self.tree[iter]['Type'] = format.name + #self.tree[iter]['Start'] = "" + #self.tree[iter]['End'] = "" # handle RAID next - raidrequests = self.partitions.getRaidRequests() - if raidrequests: + mdarrays = self.storage.mdarrays + if mdarrays: raidparent = self.tree.append(None) self.tree[raidparent]['Device'] = _("RAID Devices") - for request in raidrequests: + for array in mdarrays: mntpt = None - if request and request.fstype and request.fstype.getName() == "physical volume (LVM)": - vgreq = self.partitions.getLVMVolumeGroupMemberParent(request) - if vgreq and vgreq.volumeGroupName: - if self.show_uneditable: - mntpt = vgreq.volumeGroupName - else: - self.tree.appendToHiddenPartitionsList(str(request.uniqueID)) - continue + if array.format.type == "luks": + # look up the mapped/decrypted device since that's + # where we'll find the format we want to display + try: + dm_dev = self.storage.devicetree.getChildren(array)[0] + except IndexError: + format = array.format + else: + format = dm_dev.format + else: + format = array.format + + if format.type == "lvmpv": + vg = None + for _vg in self.storage.vgs: + if _vg.dependsOn(array): + vg = _vg + break + if vg and self.show_uneditable: + mntpt = vg.name + elif vg: + self.tree.appendToHiddenPartitionsList(array.path) + continue else: mntpt = "" + elif format.mountable and format.mountpoint: + mntpt = format.mountpoint iter = self.tree.append(raidparent) if mntpt: self.tree[iter]["Mount Point"] = mntpt + else: + self.tree[iter]["Mount Point"] = "" - if request and request.mountpoint: - self.tree[iter]["Mount Point"] = request.mountpoint - - if request.fstype: - ptype = self.getShortFSTypeName(request.fstype.getName()) - - if request.isEncrypted(self.partitions, True) and request.format: + if format.type: + ptype = format.name + if array.format.type == "luks" and \ + not array.format.exists: self.tree[iter]['Format'] = self.lock_pixbuf - elif request.format: + elif not format.exists: self.tree[iter]['Format'] = self.checkmark_pixbuf - self.tree[iter]['IsFormattable'] = request.fstype.isFormattable() + self.tree[iter]['IsFormattable'] = format.formattable else: ptype = _("None") self.tree[iter]['IsFormattable'] = False - try: - device = "/dev/md%d" % (request.raidminor,) - except: + if array.minor is not None: + device = "%s" % array.path + else: device = "Auto" self.tree[iter]['IsLeaf'] = True self.tree[iter]['Device'] = device - if request and request.type != REQUEST_NEW and request.fslabel: - self.tree[iter]['Label'] = "%s" % (request.fslabel,) + if array.format.exists and getattr(format, "label", None): + self.tree[iter]['Label'] = "%s" % format.label else: self.tree[iter]['Label'] = "" self.tree[iter]['Type'] = ptype - self.tree[iter]['Start'] = "" - self.tree[iter]['End'] = "" - self.tree[iter]['Size (MB)'] = "%Ld" % (request.getActualSize(self.partitions, self.diskset),) - self.tree[iter]['PyObject'] = str(request.uniqueID) + self.tree[iter]['Size (MB)'] = "%Ld" % array.size + self.tree[iter]['PyObject'] = array # now normal partitions + disks = self.storage.disks drvparent = self.tree.append(None) self.tree[drvparent]['Device'] = _("Hard Drives") - for drive in drives: - disk = self.diskset.disks[drive] - + for disk in disks: # add a disk stripe to the graph - stripe = self.diskStripeGraph.add(drive, disk) + stripe = self.diskStripeGraph.add(disk.name, disk.partedDisk) # add a parent node to the tree parent = self.tree.append(drvparent) - self.tree[parent]['Device'] = '/dev/%s' % (drive,) - self.tree[parent]['PyObject'] = str('/dev/%s' % (drive,)) - (cylinders, heads, sectors) = disk.device.biosGeometry + self.tree[parent]['Device'] = "%s" % disk.path + self.tree[parent]['PyObject'] = disk + (cylinders, heads, sectors) = disk.partedDisk.device.biosGeometry sectorsPerCyl = heads * sectors + part = disk.partedDisk.getFirstPartition() extendedParent = None - part = disk.getFirstPartition() while part: if part.type & parted.PARTITION_METADATA: part = part.nextPartition() continue + + partName = part.getDeviceNodeName().split("/")[-1] + device = self.storage.devicetree.getDeviceByName(partName) + if not device and not part.type & parted.PARTITION_FREESPACE: + log.debug("can't find partition %s in device" + " tree" % partName) + # ignore the tiny < 1 MB partitions (#119479) if part.getSize(unit="MB") <= 1.0: - if not part.active or not part.getFlag(parted.PARTITION_BOOT): + if not part.active or not device.bootable: part = part.nextPartition() continue stripe.add(part) - device = part.getDeviceNodeName() - request = self.partitions.getRequestByDeviceName(device) - - if part.type == parted.PARTITION_EXTENDED: + if device and device.isExtended: if extendedParent: raise RuntimeError, ("can't handle more than " "one extended partition per disk") extendedParent = self.tree.append(parent) iter = extendedParent - elif part.type & parted.PARTITION_LOGICAL: + elif device and device.isLogical: if not extendedParent: raise RuntimeError, ("crossed logical partition " "before extended") @@ -858,43 +845,68 @@ class PartitionWindow(InstallWindow): iter = self.tree.append(parent) self.tree[iter]['IsLeaf'] = True - if request and request.mountpoint: - self.tree[iter]['Mount Point'] = request.mountpoint + if device and device.format.type == "luks": + # look up the mapped/decrypted device in the tree + # the format we care about will be on it + try: + dm_dev = self.storage.devicetree.getChildren(device)[0] + except IndexError: + format = device.format + else: + format = dm_dev.format + elif device: + format = device.format + else: + format = None + + if format and format.mountable and format.mountpoint: + self.tree[iter]['Mount Point'] = format.mountpoint else: self.tree[iter]['Mount Point'] = "" - if request and request.fstype and request.fstype.getName() == "physical volume (LVM)": - vgreq = self.partitions.getLVMVolumeGroupMemberParent(request) - if vgreq and vgreq.volumeGroupName: + if format and format.type == "lvmpv": + vg = None + for _vg in self.storage.vgs: + if _vg.dependsOn(part): + vg = _vg + break + if vg and vg.name: if self.show_uneditable: - self.tree[iter]['Mount Point'] = vgreq.volumeGroupName + self.tree[iter]['Mount Point'] = vg.name else: self.tree.appendToHiddenPartitionsList(part) self.tree.remove(iter) + part = part.nextPartition() continue else: self.tree[iter]['Mount Point'] = "" - if request.isEncrypted(self.partitions, True) and request.format: - self.tree[iter]['Format'] = self.lock_pixbuf - elif request.format: - self.tree[iter]['Format'] = self.checkmark_pixbuf + if device and device.format and \ + device.format.type == "luks" and \ + not device.format.exists: + self.tree[iter]['Format'] = self.lock_pixbuf + elif format and not format.exists: + self.tree[iter]['Format'] = self.checkmark_pixbuf - if request and request.fstype: - self.tree[iter]['IsFormattable'] = request.fstype.isFormattable() + if format and format.type: + self.tree[iter]['IsFormattable'] = device.format.formattable - if part.type & parted.PARTITION_FREESPACE: - ptype = _("Free space") - elif part.type == parted.PARTITION_EXTENDED: + if device and device.isExtended: ptype = _("Extended") - elif part.getFlag(parted.PARTITION_RAID) == 1: + elif format and format.type == "mdmember": ptype = _("software RAID") - parreq = self.partitions.getRaidMemberParent(request) - if parreq: + mds = self.storage.mdarrays + array = None + for _array in mds: + if _array.dependsOn(device): + array = _array + break + if array: if self.show_uneditable: - try: - mddevice = "/dev/md%d" % (parreq.raidminor,) - except: + if array.minor is not None: + mddevice = "%s" % array.path + else: + mddevice = "Auto" mddevice = "Auto" self.tree[iter]['Mount Point'] = mddevice else: @@ -905,48 +917,33 @@ class PartitionWindow(InstallWindow): else: self.tree[iter]['Mount Point'] = "" - if request and request.isEncrypted(self.partitions, True) and request.format: + if device.format.type == "luks" and \ + not device.format.exists: self.tree[iter]['Format'] = self.lock_pixbuf - elif part.fileSystem: - if request and request.fstype != None: - ptype = self.getShortFSTypeName(request.fstype.getName()) - if ptype == "foreign": - ptype = map_foreign_to_fsname(part) - else: - ptype = part.fileSystem.type - - if request and request.isEncrypted(self.partitions, True) and request.format: - self.tree[iter]['Format'] = self.lock_pixbuf - elif request and request.format: - self.tree[iter]['Format'] = self.checkmark_pixbuf else: - if request and request.fstype != None: - ptype = self.getShortFSTypeName(request.fstype.getName()) - - if ptype == "foreign": - ptype = map_foreign_to_fsname(part) + if format and format.type: + ptype = format.name else: ptype = _("None") if part.type & parted.PARTITION_FREESPACE: devname = _("Free") else: - devname = '/dev/%s' % (device,) + devname = "%s" % device.path self.tree[iter]['Device'] = devname - if request and request.type != REQUEST_NEW and request.fslabel: - self.tree[iter]['Label'] = "%s" % (request.fslabel,) + if format and format.exists and \ + getattr(format, "label", None): + self.tree[iter]['Label'] = "%s" % format.label else: self.tree[iter]['Label'] = "" self.tree[iter]['Type'] = ptype - self.tree[iter]['Start'] = str(disk.device.startSectorToCylinder(part.geometry.start)) - self.tree[iter]['End'] = str(disk.device.endSectorToCylinder(part.geometry.end)) size = part.getSize(unit="MB") if size < 1.0: sizestr = "< 1" else: sizestr = "%Ld" % (size) self.tree[iter]['Size (MB)'] = sizestr - self.tree[iter]['PyObject'] = part + self.tree[iter]['PyObject'] = device part = part.nextPartition() @@ -954,149 +951,155 @@ class PartitionWindow(InstallWindow): apply(canvas.set_scroll_region, canvas.root().get_bounds()) self.treeView.expand_all() - def treeActivateCb(self, view, path, col): - if self.tree.getCurrentPartition(): - self.editCb() + def treeActivateCB(self, view, path, col): + if isinstance(self.tree.getCurrentDevice(), + storage.devices.PartitionDevice): + self.editCB() - def treeSelectCb(self, selection, *args): + def treeSelectCB(self, selection, *args): model, iter = selection.get_selected() if not iter: return - partition = model[iter]['PyObject'] - if partition: + device = model[iter]['PyObject'] + if device: + # PyObject is always a Device but not always a PartitionDevice + try: + partition = device.partedPartition + except AttributeError: + return + self.diskStripeGraph.selectSlice(partition) def newCB(self, widget): - request = NewPartitionSpec(fileSystemTypeGetDefault(), size = 200) - - self.editPartitionRequest(request, isNew = 1) - - def deleteCb(self, widget): - curselection = self.tree.getCurrentPartition() - - if curselection: - if doDeletePartitionByRequest(self.intf, self.partitions, curselection): + device = self.storage.newPartition(fmt_type=self.storage.defaultFSType, + size=200) + self.editPartition(device, isNew=1) + + def deleteCB(self, widget): + """ Right now we can say that if the device is partitioned we + want to delete all of the devices it contains. At some point + we will want to support creation and removal of partitionable + devices. This will need some work when that time comes. + """ + device = self.tree.getCurrentDevice() + if hasattr(device, "partedDisk"): + if doDeleteDependentDevices(self.intf, + self.storage, + device): self.refresh() - else: - curdevice = self.tree.getCurrentDevice() - if curdevice and len(curdevice) > 5: - if doDeletePartitionsByDevice(self.intf, self.partitions, self.diskset, curdevice[5:]): - self.refresh() - else: - return + elif doDeleteDevice(self.intf, + self.storage, + device): + if isinstance(device, storage.devices.DiskDevice) or \ + isinstance(device, storage.devices.PartitionDevice): + justRedraw = False + else: + justRedraw = True + + self.refresh(justRedraw=justRedraw) - def resetCb(self, *args): + def resetCB(self, *args): if not confirmResetPartitionState(self.intf): return self.diskStripeGraph.shutDown() - self.diskset.refreshDevices() - self.partitions.setFromDisk(self.diskset) + self.storage.reset() self.tree.clear() self.populate() - def refresh(self): + def refresh(self, justRedraw=None): + log.debug("refresh: justRedraw=%s" % justRedraw) self.diskStripeGraph.shutDown() self.tree.clear() - # 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: - autopart.doPartitioning(self.diskset, self.partitions) + if justRedraw: 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") - - self.intf.messageWindow(_("Error Partitioning"), - _("Could not allocate requested partitions: %s.") % (msg), - custom_icon="error") - rc = -1 - except PartitioningWarning, msg: - # XXX somebody other than me should make this look better - # XXX this doesn't handle the 'delete /boot partition spec' case - # (it says 'add anyway') - dialog = gtk.MessageDialog(self.parent, 0, gtk.MESSAGE_WARNING, - gtk.BUTTONS_NONE, - _("Warning: %s.") % (msg)) - gui.addFrame(dialog) - button = gtk.Button(_("_Modify Partition")) - dialog.add_action_widget(button, 1) - button = gtk.Button(_("_Continue")) - dialog.add_action_widget(button, 2) - dialog.set_position(gtk.WIN_POS_CENTER) - - dialog.show_all() - rc = dialog.run() - dialog.destroy() - - if rc == 1: - rc = -1 - else: + else: + try: + doPartitioning(self.storage) rc = 0 - reqs = self.partitions.getBootableRequest() - if reqs: - for req in reqs: - req.ignoreBootConstraints = 1 + except PartitioningError, msg: + self.intf.messageWindow(_("Error Partitioning"), + _("Could not allocate requested partitions: %s.") % (msg), + custom_icon="error") + rc = -1 + except PartitioningWarning, msg: + # XXX somebody other than me should make this look better + # XXX this doesn't handle the 'delete /boot partition spec' case + # (it says 'add anyway') + dialog = gtk.MessageDialog(self.parent, 0, gtk.MESSAGE_WARNING, + gtk.BUTTONS_NONE, + _("Warning: %s.") % (msg)) + gui.addFrame(dialog) + button = gtk.Button(_("_Modify Partition")) + dialog.add_action_widget(button, 1) + button = gtk.Button(_("_Continue")) + dialog.add_action_widget(button, 2) + dialog.set_position(gtk.WIN_POS_CENTER) + + dialog.show_all() + rc = dialog.run() + dialog.destroy() + + if rc == 1: + rc = -1 + else: + rc = 0 + all_devices = self.storage.devicetree.devices.values() + bootDevs = [d for d in all_devices if d.bootable] + #if reqs: + # for req in reqs: + # req.ignoreBootConstraints = 1 if not rc == -1: self.populate() return rc - def editCb(self, *args): - part = self.tree.getCurrentPartition() - - (type, request) = doEditPartitionByRequest(self.intf, self.partitions, - part) - if request: - if type == "RAID": - self.editRaidRequest(request) - elif type == "LVMVG": - self.editLVMVolumeGroup(request) - elif type == "LVMLV": - vgrequest = self.partitions.getRequestByID(request.volumeGroup) - self.editLVMVolumeGroup(vgrequest) - elif type == "NEW": - self.editPartitionRequest(request, isNew = 1) - else: - self.editPartitionRequest(request) + def editCB(self, *args): + device = self.tree.getCurrentDevice() + if not device: + self.intf.messageWindow(_("Unable To Edit"), + _("You must select a device to edit"), + custom_icon="error") + return + + reason = self.storage.deviceImmutable(device) + if reason: + self.intf.messageWindow(_("Unable To Edit"), + _("You cannot edit this device:\n\n%s") + % reason, + custom_icon="error") + return + + if device.type == "mdarray": + self.editRaidArray(device) + elif device.type == "lvmvg": + self.editLVMVolumeGroup(device) + elif device.type == "lvmlv": + self.editLVMVolumeGroup(device) + elif isinstance(device, storage.devices.PartitionDevice): + self.editPartition(device) # isNew implies that this request has never been successfully used before - def editRaidRequest(self, raidrequest, isNew = 0): - raideditor = raid_dialog_gui.RaidEditor(self.partitions, - self.diskset, self.intf, - self.parent, raidrequest, - isNew) - origpartitions = self.partitions.copy() + def editRaidArray(self, raiddev, isNew = 0): + raideditor = raid_dialog_gui.RaidEditor(self.storage, + self.intf, + self.parent, + raiddev, + isNew) while 1: - request = raideditor.run() - - if request is None: - return + actions = raideditor.run() - if not isNew: - self.partitions.removeRequest(raidrequest) - if raidrequest.getPreExisting(): - delete = partRequests.DeleteRAIDSpec(raidrequest.raidminor) - self.partitions.addDelete(delete) + for action in actions: + # FIXME: this needs to handle exceptions + self.storage.devicetree.registerAction(action) - self.partitions.addRequest(request) - - if self.refresh(): - if not isNew: - self.partitions = origpartitions.copy() + if self.refresh(justRedraw=True): + actions.reverse() + for action in actions: + self.storage.devicetree.cancelAction(action) if self.refresh(): raise RuntimeError, ("Returning partitions to state " "prior to RAID edit failed") @@ -1107,29 +1110,27 @@ class PartitionWindow(InstallWindow): raideditor.destroy() - def editPartitionRequest(self, origrequest, isNew = 0, restrictfs = None): + def editPartition(self, device, isNew = 0, restrictfs = None): parteditor = partition_dialog_gui.PartitionEditor(self.anaconda, self.parent, - origrequest, + device, isNew = isNew, restrictfs = restrictfs) while 1: - request = parteditor.run() + actions = parteditor.run() - if request is None: - return 0 + for action in actions: + # XXX we should handle exceptions here + self.anaconda.id.storage.devicetree.registerAction(action) - if not isNew: - self.partitions.removeRequest(origrequest) + if self.refresh(justRedraw=not actions): + # autopart failed -- cancel the actions and try to get + # back to previous state + actions.reverse() + for action in actions: + self.anaconda.id.storage.devicetree.cancelAction(action) - self.partitions.addRequest(request) - if self.refresh(): - # the add failed; remove what we just added and put - # back what was there if we removed it - self.partitions.removeRequest(request) - if not isNew: - self.partitions.addRequest(origrequest) if self.refresh(): # this worked before and doesn't now... raise RuntimeError, ("Returning partitions to state " @@ -1140,66 +1141,30 @@ class PartitionWindow(InstallWindow): parteditor.destroy() return 1 - def editLVMVolumeGroup(self, origvgrequest, isNew = 0): - vgeditor = lvm_dialog_gui.VolumeGroupEditor(self.anaconda, - self.partitions, - self.diskset, - self.intf, self.parent, - origvgrequest, isNew) + def editLVMVolumeGroup(self, device, isNew = 0): + # we don't really need to pass in self.storage if we're passing + # self.anaconda already + vgeditor = lvm_dialog_gui.VolumeGroupEditor(self.anaconda, + self.intf, + self.parent, + device, + isNew) - origpartitions = self.partitions.copy() - origvolreqs = origpartitions.getLVMLVForVG(origvgrequest) - - while (1): - rc = vgeditor.run() - - # - # return code is either None or a tuple containing - # volume group request and logical volume requests - # - if rc is None: - return - - (vgrequest, logvolreqs) = rc - - # first add the volume group - if not isNew: - # if an lv was preexisting and isn't in the new lv requests, - # we need to add a delete for it. - for lv in origvolreqs: - if not lv.getPreExisting(): - continue - found = 0 - for newlv in logvolreqs: - if (newlv.getPreExisting() and - newlv.logicalVolumeName == lv.logicalVolumeName): - found = 1 - break - if found == 0: - delete = partRequests.DeleteLogicalVolumeSpec(lv.logicalVolumeName, - origvgrequest.volumeGroupName) - self.partitions.addDelete(delete) - - for lv in origvolreqs: - self.partitions.removeRequest(lv) - - self.partitions.removeRequest(origvgrequest) - - vgID = self.partitions.addRequest(vgrequest) - - # now add the logical volumes - for lv in logvolreqs: - lv.volumeGroup = vgID - if not lv.getPreExisting(): - lv.format = 1 - self.partitions.addRequest(lv) - - if self.refresh(): - if not isNew: - self.partitions = origpartitions.copy() - if self.refresh(): - raise RuntimeError, ("Returning partitions to state " - "prior to edit failed") + while True: + actions = vgeditor.run() + + for action in actions: + # FIXME: handle exceptions + self.storage.devicetree.registerAction(action) + + if self.refresh(justRedraw=True): + actions.reverse() + for action in actions: + self.storage.devicetree.cancelAction(action) + + if self.refresh(): + raise RuntimeError, ("Returning partitions to state " + "prior to edit failed") continue else: break @@ -1207,31 +1172,27 @@ class PartitionWindow(InstallWindow): vgeditor.destroy() - def makeLvmCB(self, widget): - if (not fileSystemTypeGet('physical volume (LVM)').isSupported() or - not lvm.has_lvm()): + if not getFormat("lvmpv").supported or not lvm.has_lvm(): self.intf.messageWindow(_("Not supported"), _("LVM is NOT supported on " "this platform."), type="ok", custom_icon="error") return - request = VolumeGroupRequestSpec(format=True) - self.editLVMVolumeGroup(request, isNew = 1) - + vg = self.storage.newVG() + self.editLVMVolumeGroup(vg, isNew = 1) return def makeraidCB(self, widget): - - if not fileSystemTypeGet('software RAID').isSupported(): + if not getFormat("software RAID").supported: self.intf.messageWindow(_("Not supported"), _("Software RAID is NOT supported on " "this platform."), type="ok", custom_icon="error") return - availminors = self.partitions.getAvailableRaidMinors() + availminors = self.storage.unusedMDMinors if len(availminors) < 1: self.intf.messageWindow(_("No RAID minor device numbers available"), _("A software RAID device cannot " @@ -1244,9 +1205,7 @@ class PartitionWindow(InstallWindow): # see if we have enough free software RAID partitions first # if no raid partitions exist, raise an error message and return - request = RaidRequestSpec(fileSystemTypeGetDefault()) - availraidparts = self.partitions.getAvailRaidPartitions(request, - self.diskset) + availraidparts = self.storage.unusedMDMembers() dialog = gtk.Dialog(_("RAID Options"), self.parent) gui.addFrame(dialog) @@ -1299,7 +1258,7 @@ class PartitionWindow(InstallWindow): createRAIDpart.set_active(1) doRAIDclone.set_sensitive(0) createRAIDdev.set_sensitive(0) - if len(availraidparts) > 0 and len(self.diskset.disks.keys()) > 1: + if len(availraidparts) > 0 and len(self.storage.disks) > 1: doRAIDclone.set_sensitive(1) if len(availraidparts) > 1: @@ -1321,13 +1280,16 @@ class PartitionWindow(InstallWindow): # see which option they choose if createRAIDpart.get_active(): - rdrequest = NewPartitionSpec(fileSystemTypeGet("software RAID"), size = 200) - rc = self.editPartitionRequest(rdrequest, isNew = 1, restrictfs=["software RAID"]) + member = self.storage.newPartition(fmt_type="software RAID", + size=200) + rc = self.editPartition(member, + isNew = 1, + restrictfs=["software RAID"]) elif createRAIDdev.get_active(): - self.editRaidRequest(request, isNew=1) + array = self.storage.newMDArray(fmt_type=self.storage.defaultFSType) + self.editRaidArray(array, isNew=1) else: - cloneDialog = raid_dialog_gui.RaidCloneDialog(self.partitions, - self.diskset, + cloneDialog = raid_dialog_gui.RaidCloneDialog(self.storage, self.intf, self.parent) if cloneDialog is None: @@ -1337,16 +1299,12 @@ class PartitionWindow(InstallWindow): custom_icon="error") return - while 1: - rc = cloneDialog.run() + if cloneDialog.run(): + self.refresh() - if rc: - self.refresh() - - cloneDialog.destroy() - return + cloneDialog.destroy() + return - def viewButtonCB(self, widget): self.show_uneditable = not widget.get_active() self.diskStripeGraph.shutDown() @@ -1355,13 +1313,9 @@ class PartitionWindow(InstallWindow): def getScreen(self, anaconda): self.anaconda = anaconda - self.fsset = anaconda.id.fsset - self.diskset = anaconda.id.diskset + self.storage = anaconda.id.storage self.intf = anaconda.intf - self.diskset.openDevices() - self.partitions = anaconda.id.partitions - self.show_uneditable = 1 checkForSwapNoMatch(anaconda) @@ -1375,9 +1329,9 @@ class PartitionWindow(InstallWindow): buttonBox.set_layout(gtk.BUTTONBOX_SPREAD) ops = ((_("Ne_w"), self.newCB), - (_("_Edit"), self.editCb), - (_("_Delete"), self.deleteCb), - (_("Re_set"), self.resetCb), + (_("_Edit"), self.editCB), + (_("_Delete"), self.deleteCB), + (_("Re_set"), self.resetCB), (_("R_AID"), self.makeraidCB), (_("_LVM"), self.makeLvmCB)) @@ -1388,12 +1342,12 @@ class PartitionWindow(InstallWindow): self.tree = DiskTreeModel() self.treeView = self.tree.getTreeView() - self.treeView.connect('row-activated', self.treeActivateCb) + self.treeView.connect('row-activated', self.treeActivateCB) self.treeViewSelection = self.treeView.get_selection() - self.treeViewSelection.connect("changed", self.treeSelectCb) + self.treeViewSelection.connect("changed", self.treeSelectCB) # set up the canvas - self.diskStripeGraph = DiskStripeGraph(self.tree, self.editCb) + self.diskStripeGraph = DiskStripeGraph(self.tree, self.editCB) # do the initial population of the tree and the graph self.populate(initial = 1) diff --git a/iw/partition_ui_helpers_gui.py b/iw/partition_ui_helpers_gui.py index 5697d2b1a..9d7f7c556 100644 --- a/iw/partition_ui_helpers_gui.py +++ b/iw/partition_ui_helpers_gui.py @@ -28,19 +28,13 @@ import datacombo import iutil from constants import * -from fsset import * from partIntfHelpers import * -from partRequests import * from partedUtils import * +from storage.formats import * import gettext _ = lambda x: gettext.ldgettext("anaconda", x) -def istruefalse(val): - if val is None or not val: - return False - return True - class WideCheckList(checklist.CheckList): def toggled_item(self, data, row): @@ -72,27 +66,37 @@ def createAlignedLabel(text): return label +defaultMountPoints = ['/', '/boot', '/home', '/tmp', '/usr', + '/var', '/usr/local', '/opt'] + +if iutil.isS390(): + # Many s390 have 2G DASDs, we recomment putting /usr/share on its own DASD + defaultMountPoints.insert(5, '/usr/share') + +if iutil.isEfi(): + defaultMountPoints.insert(2, '/boot/efi') + def createMountPointCombo(request, excludeMountPoints=[]): mountCombo = gtk.combo_box_entry_new_text() mntptlist = [] - - if request.type != REQUEST_NEW and request.fslabel: - mntptlist.append(request.fslabel) + label = getattr(request.format, "label", None) + if request.exists and label and label.startswith("/"): + mntptlist.append(label) idx = 0 - + for p in defaultMountPoints: - if p in excludeMountPoints: - continue - - if not p in mntptlist and (p[0] == "/"): - mntptlist.append(p) + if p in excludeMountPoints: + continue - map(mountCombo.append_text, mntptlist) + if not p in mntptlist and (p[0] == "/"): + mntptlist.append(p) - mountpoint = request.mountpoint + map(mountCombo.append_text, mntptlist) - if request.fstype and request.fstype.isMountable(): + if (request.format.type or request.format.migrate) and \ + request.format.mountable: + mountpoint = request.format.mountpoint mountCombo.set_sensitive(1) if mountpoint: mountCombo.get_children()[0].set_text(mountpoint) @@ -106,14 +110,15 @@ def createMountPointCombo(request, excludeMountPoints=[]): return mountCombo -def setMntPtComboStateFromType(fstype, mountCombo): +def setMntPtComboStateFromType(fmt_class, mountCombo): prevmountable = mountCombo.get_data("prevmountable") mountpoint = mountCombo.get_data("saved_mntpt") - if prevmountable and fstype.isMountable(): + format = fmt_class() + if prevmountable and format.mountable: return - if fstype.isMountable(): + if format.mountable: mountCombo.set_sensitive(1) if mountpoint != None: mountCombo.get_children()[0].set_text(mountpoint) @@ -125,7 +130,7 @@ def setMntPtComboStateFromType(fstype, mountCombo): mountCombo.get_children()[0].set_text(_("<Not Applicable>")) mountCombo.set_sensitive(0) - mountCombo.set_data("prevmountable", fstype.isMountable()) + mountCombo.set_data("prevmountable", format.mountable) def fstypechangeCB(widget, mountCombo): fstype = widget.get_active_value() @@ -134,24 +139,25 @@ def fstypechangeCB(widget, mountCombo): def createAllowedDrivesStore(disks, reqdrives, drivelist, selectDrives=True, disallowDrives=[]): drivelist.clear() - drives = disks.keys() - drives.sort() - for drive in drives: - size = disks[drive].device.getSize(unit="MB") + for disk in disks: selected = 0 if selectDrives: if reqdrives: - if drive in reqdrives: + if disk.name in reqdrives: selected = 1 else: - if drive not in disallowDrives: + if disk.name not in disallowDrives: selected = 1 - sizestr = "%8.0f MB" % size - drivelist.append_row((drive, sizestr, disks[drive].device.model), selected) + sizestr = "%8.0f MB" % disk.size + # TODO: abstract disk model so we don't have to reach into parted.Disk + drivelist.append_row((disk.name, + sizestr, + disk.partedDisk.device.model), + selected) - if len(disks.keys()) < 2: + if len(disks) < 2: drivelist.set_sensitive(False) else: drivelist.set_sensitive(True) @@ -170,36 +176,38 @@ def createAllowedDrivesList(disks, reqdrives, selectDrives=True, disallowDrives= # pass in callback for when fs changes because of python scope issues -def createFSTypeMenu(fstype, fstypechangeCB, mountCombo, +def createFSTypeMenu(format, fstypechangeCB, mountCombo, availablefstypes = None, ignorefs = None): store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) fstypecombo = datacombo.DataComboBox(store) - types = fileSystemTypeGetTypes() if availablefstypes: names = availablefstypes else: - names = types.keys() - if fstype and fstype.isSupported() and fstype.isFormattable(): - default = fstype + names = device_formats.keys() + if format and format.supported and format.formattable: + default = format.name else: - default = fileSystemTypeGetDefault() + default = get_default_filesystem_type() names.sort() defindex = 0 i = 0 for name in names: - if not fileSystemTypeGet(name).isSupported(): + # we could avoid instantiating them all if we made a static class + # method that does what the supported property does + format = device_formats[name]() + if not format.supported: continue if ignorefs and name in ignorefs: continue - if fileSystemTypeGet(name).isFormattable(): - fstypecombo.append(name, types[name]) - if default and default.getName() == name: + if format.formattable: + fstypecombo.append(format.name, device_formats[name]) + if default == name: defindex = i - defismountable = types[name].isMountable() + defismountable = format.mountable i = i + 1 fstypecombo.set_active(defindex) @@ -209,16 +217,16 @@ def createFSTypeMenu(fstype, fstypechangeCB, mountCombo, if mountCombo: mountCombo.set_data("prevmountable", - fstypecombo.get_active_value().isMountable()) + fstypecombo.get_active_value()().mountable) mountCombo.connect("changed", mountptchangeCB, fstypecombo) return fstypecombo def mountptchangeCB(widget, fstypecombo): if iutil.isEfi() and widget.get_children()[0].get_text() == "/boot/efi": - fstypecombo.set_active_text("efi") + fstypecombo.set_active_text(getFormat("efi").name) if widget.get_children()[0].get_text() == "/boot": - fstypecombo.set_active_text(fileSystemTypeGetDefaultBoot().getName()) + fstypecombo.set_active_text(get_default_filesystem_type(boot=True)) def resizeOptionCB(widget, resizesb): resizesb.set_sensitive(widget.get_active()) @@ -236,9 +244,21 @@ def formatOptionResizeCB(widget, resizesb): if resizesb.get_value_as_int() < lower: resizesb.set_value(adj.lower) -def formatOptionCB(widget, data): - (combowidget, mntptcombo, ofstype, lukscb) = data +def formatMigrateOptionCB(widget, data): + (sensitive,) = widget.get_properties('sensitive') + if not sensitive: + return + + (combowidget, mntptcombo, ofstype, lukscb, othercombo, othercb) = data combowidget.set_sensitive(widget.get_active()) + + if othercb is not None: + othercb.set_sensitive(not widget.get_active()) + othercb.set_active(False) + + if othercombo is not None: + othercombo.set_sensitive(not widget.get_active()) + if lukscb is not None: lukscb.set_data("formatstate", widget.get_active()) if not widget.get_active(): @@ -250,19 +270,14 @@ def formatOptionCB(widget, data): # inject event for fstype menu if widget.get_active(): - fstype = combowidget.get_active_value() - setMntPtComboStateFromType(fstype, mntptcombo) + fstype = combowidget.get_active_value() + setMntPtComboStateFromType(fstype, mntptcombo) combowidget.grab_focus() else: - setMntPtComboStateFromType(ofstype, mntptcombo) + if isinstance(ofstype, type(ofstype)): + ofstype = type(ofstype) -def noformatCB(widget, data): - (combowidget, mntptcombo, ofstype) = data - combowidget.set_sensitive(not widget.get_active()) - - # inject event for fstype menu - if widget.get_active(): - setMntPtComboStateFromType(ofstype, mntptcombo) + setMntPtComboStateFromType(ofstype, mntptcombo) """ createPreExistFSOptionSection: given inputs for a preexisting partition, @@ -270,7 +285,7 @@ def noformatCB(widget, data): Returns the value of row after packing into the maintable, and a dictionary consistenting of: - noformatcb - checkbutton for 'format as new fs' + formatcb - checkbutton for 'format as new fs' fstype - part of format fstype menu fstypeMenu - part of format fstype menu migratecb - checkbutton for migrate fs @@ -280,55 +295,63 @@ def noformatCB(widget, data): resizesb - spinbutton with resize target """ def createPreExistFSOptionSection(origrequest, maintable, row, mountCombo, - partitions, ignorefs=[]): + partitions, ignorefs=[], luksdev=None): rc = {} - ofstype = origrequest.fstype + + if luksdev: + origfs = luksdev.format + else: + origfs = origrequest.format formatcb = gtk.CheckButton(label=_("_Format as:")) maintable.attach(formatcb, 0, 1, row, row + 1) - formatcb.set_active(istruefalse(origrequest.format)) + formatcb.set_active(not origfs.exists) rc["formatcb"] = formatcb - fstypeCombo = createFSTypeMenu(ofstype, fstypechangeCB, + fstypeCombo = createFSTypeMenu(origfs, fstypechangeCB, mountCombo, ignorefs=ignorefs) fstypeCombo.set_sensitive(formatcb.get_active()) maintable.attach(fstypeCombo, 1, 2, row, row + 1) row += 1 rc["fstypeCombo"] = fstypeCombo - if not formatcb.get_active() and not origrequest.migrate: - mountCombo.set_data("prevmountable", ofstype.isMountable()) + if not formatcb.get_active() and not origfs.migrate: + mountCombo.set_data("prevmountable", origfs.mountable) # this gets added to the table a bit later on lukscb = gtk.CheckButton(_("_Encrypt")) - formatcb.connect("toggled", formatOptionCB, - (fstypeCombo, mountCombo, ofstype, lukscb)) - - - if origrequest.origfstype.isMigratable(): + if origfs.migratable: migratecb = gtk.CheckButton(label=_("Mi_grate filesystem to:")) - migratecb.set_active(istruefalse(origrequest.migrate)) + migratecb.set_active(origfs.migrate) - migtypes = origrequest.origfstype.getMigratableFSTargets() + migtypes = [origfs.migrationTarget] maintable.attach(migratecb, 0, 1, row, row + 1) - migfstypeCombo = createFSTypeMenu(ofstype, None, None, + migfstypeCombo = createFSTypeMenu(origfs, + None, None, availablefstypes = migtypes) migfstypeCombo.set_sensitive(migratecb.get_active()) maintable.attach(migfstypeCombo, 1, 2, row, row + 1) row = row + 1 rc["migratecb"] = migratecb rc["migfstypeCombo"] = migfstypeCombo - migratecb.connect("toggled", formatOptionCB, - (migfstypeCombo, mountCombo, ofstype, None)) + migratecb.connect("toggled", formatMigrateOptionCB, + (migfstypeCombo, mountCombo, origfs, None, + fstypeCombo, formatcb)) else: migratecb = None migfstypeCombo = None - if origrequest.isResizable(partitions): + formatcb.connect("toggled", formatMigrateOptionCB, + (fstypeCombo, mountCombo, origfs, lukscb, + migfstypeCombo, migratecb)) + + if origrequest.resizable: resizecb = gtk.CheckButton(label=_("_Resize")) - resizecb.set_active(origrequest.targetSize is not None) + resizecb.set_active(origrequest.resizable and \ + ((origrequest.targetSize != 0) and \ + (origrequest.targetSize != origrequest.currentSize))) rc["resizecb"] = resizecb maintable.attach(resizecb, 0, 1, row, row + 1) @@ -337,9 +360,9 @@ def createPreExistFSOptionSection(origrequest, maintable, row, mountCombo, else: value = origrequest.size - reqlower = origrequest.getMinimumResizeMB(partitions) - requpper = origrequest.getMaximumResizeMB(partitions) - if not origrequest.format: + reqlower = origrequest.minSize + requpper = origrequest.maxSize + if origfs.exists: lower = reqlower else: lower = 1 @@ -357,7 +380,7 @@ def createPreExistFSOptionSection(origrequest, maintable, row, mountCombo, formatcb.connect("toggled", formatOptionResizeCB, resizesb) - if origrequest.encryption: + if luksdev: lukscb.set_active(1) lukscb.set_data("encrypted", 1) else: @@ -372,17 +395,17 @@ def createPreExistFSOptionSection(origrequest, maintable, row, mountCombo, return (row, rc) # do tests we just want in UI for now, not kickstart -def doUIRAIDLVMChecks(request, diskset): - fstype = request.fstype - numdrives = len(diskset.disks.keys()) +def doUIRAIDLVMChecks(request, storage): + fstype = request.format.name + numdrives = len(storage.disks) ## if fstype and fstype.getName() == "physical volume (LVM)": ## if request.grow: ## return (_("Partitions of type '%s' must be of fixed size, and " ## "cannot be marked to fill to use available space.")) % (fstype.getName(),) - if fstype and fstype.getName() in ["physical volume (LVM)", "software RAID"]: - if numdrives > 1 and (request.drive is None or len(request.drive) > 1): + if fstype in ["physical volume (LVM)", "software RAID"]: + if numdrives > 1 and (not request.req_disks or len(request.disks) > 1): return (_("Partitions of type '%s' must be constrained to " "a single drive. To do this, select the " "drive in the 'Allowable Drives' checklist.")) % (fstype.getName(),) diff --git a/iw/raid_dialog_gui.py b/iw/raid_dialog_gui.py index 21cb2bcb8..177c4c366 100644 --- a/iw/raid_dialog_gui.py +++ b/iw/raid_dialog_gui.py @@ -28,10 +28,9 @@ import gtk import datacombo import gui -from fsset import * -from raid import availRaidLevels -from cryptodev import LUKSDevice -from partRequests import * +import storage.devicelibs.mdraid as mdraidlib +from storage.devices import * +from storage.deviceaction import * from partition_ui_helpers_gui import * from constants import * @@ -56,18 +55,17 @@ class RaidEditor: tempDevList = [] if not self.isNew: # We need this list if we are editing. - for id in reqraidpart: - tempDevList.append(self.partitions.getRequestByID(id).device) + for dev in reqraidpart: + tempDevList.append(dev) partrow = 0 - for part, size, used in allraidparts: - partname = "%s" % part - partsize = "%8.0f MB" % size + for part in allraidparts: + partname = "%s" % part.name + partsize = "%8.0f MB" % part.size - if self.isNew: + if not self.origrequest.exists: partlist.append_row((partname, partsize), False) else: - # Ask self.partitions what devices to list as selected. if part in tempDevList: #list the partition and put it as selected partlist.append_row((partname, partsize), True) @@ -115,9 +113,9 @@ class RaidEditor: def raidlevelchangeCB(self, widget, sparesb): raidlevel = widget.get_model()[widget.get_active()][0] numparts = sparesb.get_data("numparts") - maxspares = raid.get_raid_max_spares(raidlevel, numparts) + maxspares = mdraidlib.get_raid_max_spares(raidlevel, numparts) - if maxspares > 0 and raidlevel != "RAID0": + if maxspares > 0 and raidlevel != "raid0": adj = sparesb.get_adjustment() value = adj.value if adj.value > maxspares: @@ -135,7 +133,7 @@ class RaidEditor: def run(self): if self.dialog is None: - return None + return [] while 1: rc = self.dialog.run() @@ -143,106 +141,149 @@ class RaidEditor: # user hit cancel, do nothing if rc == 2: self.destroy() - return None - - # read out UI into a partition specification - request = copy.copy(self.origrequest) - request.encryption = copy.deepcopy(self.origrequest.encryption) - - # doesn't make sense for RAID device - if not self.origrequest.getPreExisting(): - filesystem = self.fstypeCombo.get_active_value() - request.fstype = filesystem - - if request.fstype.isMountable(): - request.mountpoint = self.mountCombo.get_children()[0].get_text() - else: - request.mountpoint = None + return [] + actions = [] + luksdev = None raidmembers = [] + migrate = None model = self.raidlist.get_model() iter = model.get_iter_first() + format = None while iter: val = model.get_value(iter, 0) part = model.get_value(iter, 1) if val: - req = self.partitions.getRequestByDeviceName(part) - raidmembers.append(req.uniqueID) + dev = self.storage.devicetree.getDeviceByName(part) + raidmembers.append(dev) iter = model.iter_next(iter) + mountpoint = self.mountCombo.get_children()[0].get_text() - if not self.origrequest.getPreExisting(): - request.raidminor = int(self.minorCombo.get_active_value()) + if not self.origrequest.exists: + # new device + fmt_class = self.fstypeCombo.get_active_value() + raidminor = int(self.minorCombo.get_active_value()) - request.raidmembers = raidmembers model = self.levelcombo.get_model() - request.raidlevel = model[self.levelcombo.get_active()][0] + raidlevel = model[self.levelcombo.get_active()][0] - if request.raidlevel != "RAID0": + if raidlevel != "RAID0": self.sparesb.update() - request.raidspares = self.sparesb.get_value_as_int() + spares = self.sparesb.get_value_as_int() else: - request.raidspares = 0 - - if self.formatButton: - request.format = self.formatButton.get_active() - else: - request.format = 0 + spares = 0 + + format = fmt_class(mountpoint=mountpoint) + members = len(raidmembers) - spares + level = int(raidlevel.lower().replace("raid", "")) + + request = self.storage.newMDArray(minor=raidminor, + level=level, + format=format, + parents=raidmembers, + totalDevices=len(raidmembers), + memberDevices=members) + + # we must destroy luks leaf before original raid request + if self.origrequest.format.type == "luks": + # => not self.isNew + # destroy luks format and mapped device + # XXX remove catching, it should always succeed + try: + luksdev = self.storage.devicetree.getChildren(self.origrequest)[0] + except IndexError: + pass + else: + actions.append(ActionDestroyFormat(luksdev)) + actions.append(ActionDestroyDevice(luksdev)) + luksdev = None if self.lukscb and self.lukscb.get_active(): - if not request.encryption: - request.encryption = LUKSDevice(passphrase=self.partitions.encryptionPassphrase, format=1) - else: - request.encryption = None + luksdev = LUKSDevice("luks-%s" % request.name, + format=format, + parents=request) + format = getFormat("luks", + passphrase=self.storage.encryptionPassphrase) + request.format = format + elif self.lukscb and not self.lukscb.get_active() and \ + self.origrequest.format.type == "luks": + + # XXXRV not needed as we destroy origrequest ? + actions.append(ActionDestroyFormat(self.origrequest)) + + if not self.isNew: + # This may be handled in devicetree.registerAction, + # but not in case when we change minor and thus + # device name/path (at least with current md) + actions.append(ActionDestroyDevice(self.origrequest)) + actions.append(ActionCreateDevice(request)) + actions.append(ActionCreateFormat(request)) + else: - if self.fsoptionsDict.has_key("formatcb"): - request.format = self.fsoptionsDict["formatcb"].get_active() - if request.format: - request.fsystem = self.fsoptionsDict["fstypeCombo"].get_active_value() - else: - request.format = 0 - - if self.fsoptionsDict.has_key("migratecb"): - request.migrate = self.fsoptionsDict["migratecb"].get_active() - if request.migrate: - request.fsystem = self.fsoptionsDict["migfstypeCombo"].get_active_value() - else: - request.migrate = 0 - - # set back if we are not formatting or migrating - origfstype = self.origrequest.origfstype - if not request.format and not request.migrate: - request.fstype = origfstype - - if request.fstype.isMountable(): - request.mountpoint = self.mountCombo.get_children()[0].get_text() - else: - request.mountpoint = None - - lukscb = self.fsoptionsDict.get("lukscb") - if lukscb and lukscb.get_active(): - if not request.encryption: - request.encryption = LUKSDevice(passphrase=self.partitions.encryptionPassphrase, format=1) - else: - request.encryption = None - - err = request.sanityCheckRequest(self.partitions) - if err: - self.intf.messageWindow(_("Error With Request"), - "%s" % (err), custom_icon="error") - continue - - if (not request.format and - request.mountpoint and request.formatByDefault()): - if not queryNoFormatPreExisting(self.intf): - continue + # existing device + fmt_class = self.fsoptionsDict["fstypeCombo"].get_active_value() + if self.fsoptionsDict.has_key("formatcb") and \ + self.fsoptionsDict["formatcb"].get_active(): + format = fmt_class(mountpoint=mountpoint) + if self.fsoptionsDict.has_key("lukscb") and \ + self.fsoptionsDict["lukscb"].get_active() and \ + self.origrequest.format.type != "luks": + luksdev = LUKSDevice("luks-%s" % self.origrequest.name, + format=format, + parents=self.origrequest) + format = getFormat("luks", + device=self.origrequest.path, + passphrase=self.storage.encryptionPassphrase) + elif self.fsoptionsDict.has_key("lukscb") and \ + not self.fsoptionsDict["lukscb"].get_active() and \ + self.origrequest.format.type == "luks": + # destroy luks format and mapped device + try: + luksdev = self.storage.devicetree.getChildren(self.origrequest)[0] + except IndexError: + pass + else: + actions.append(ActionDestroyFormat(luksdev)) + actions.append(ActionDestroyDevice(luksdev)) + luksdev = None + + actions.append(ActionDestroyFormat(self.origrequest)) + elif self.origrequest.format.mountable: + self.origrequest.format.mountpoint = mountpoint + + if self.fsoptionsDict.has_key("migratecb") and \ + self.fsoptionsDict["migratecb"].get_active(): + if self.origrequest.format.type == "luks": + try: + usedev = self.storage.devicetree.getChildren(self.origrequest)[0] + except IndexError: + usedev = self.origrequest + else: + usedev = self.origrequest + migrate = True + + if self.origrequest.format.exists and \ + self.storage.formatByDefault(self.origrequest): + if not queryNoFormatPreExisting(self.intf): + continue + + if format: + actions.append(ActionCreateFormat(self.origrequest, format)) # everything ok, break out break - return request + if luksdev: + actions.append(ActionCreateDevice(luksdev)) + actions.append(ActionCreateFormat(luksdev)) + + if migrate: + actions.append(ActionMigrateFormat(usedev)) + + return actions def destroy(self): if self.dialog: @@ -250,9 +291,8 @@ class RaidEditor: self.dialog = None - def __init__(self, partitions, diskset, intf, parent, origrequest, isNew = 0): - self.partitions = partitions - self.diskset = diskset + def __init__(self, storage, intf, parent, origrequest, isNew = 0): + self.storage = storage self.origrequest = origrequest self.isNew = isNew self.intf = intf @@ -263,8 +303,8 @@ class RaidEditor: # # start of editRaidRequest # - availraidparts = self.partitions.getAvailRaidPartitions(origrequest, - self.diskset) + availraidparts = self.storage.unusedMDMembers(array=self.origrequest) + # if no raid partitions exist, raise an error message and return if len(availraidparts) < 2: dlg = gtk.MessageDialog(self.parent, 0, gtk.MESSAGE_ERROR, @@ -285,9 +325,9 @@ class RaidEditor: if isNew: tstr = _("Make RAID Device") else: - try: - tstr = _("Edit RAID Device: /dev/md%s") % (origrequest.raidminor,) - except: + if origrequest.minor is not None: + tstr = _("Edit RAID Device: %s") % (origrequest.path,) + else: tstr = _("Edit RAID Device") dialog = gtk.Dialog(tstr, self.parent) @@ -301,23 +341,38 @@ class RaidEditor: maintable.set_col_spacings(5) row = 0 + # we'll maybe add this further down + self.lukscb = gtk.CheckButton(_("_Encrypt")) + self.lukscb.set_data("formatstate", 1) + + if origrequest.format.type == "luks": + try: + luksdev = self.storage.devicetree.getChildren(origrequest)[0] + except IndexError: + luksdev = None + usedev = origrequest + format = origrequest.format + else: + usedev = luksdev + format = usedev.format + else: + luksdev = None + usedev = origrequest + format = origrequest.format + # Mount Point entry lbl = createAlignedLabel(_("_Mount Point:")) maintable.attach(lbl, 0, 1, row, row + 1) - self.mountCombo = createMountPointCombo(origrequest) + self.mountCombo = createMountPointCombo(usedev) lbl.set_mnemonic_widget(self.mountCombo) maintable.attach(self.mountCombo, 1, 2, row, row + 1) row = row + 1 - # we'll maybe add this further down - self.lukscb = gtk.CheckButton(_("_Encrypt")) - self.lukscb.set_data("formatstate", 1) - # Filesystem Type - if not origrequest.getPreExisting(): + if not origrequest.exists: lbl = createAlignedLabel(_("_File System Type:")) maintable.attach(lbl, 0, 1, row, row + 1) - self.fstypeCombo = createFSTypeMenu(origrequest.fstype, + self.fstypeCombo = createFSTypeMenu(format, fstypechangeCB, self.mountCombo, ignorefs = ["software RAID", "efi", "PPC PReP Boot", "Apple Bootstrap"]) @@ -327,37 +382,37 @@ class RaidEditor: else: maintable.attach(createAlignedLabel(_("Original File System Type:")), 0, 1, row, row + 1) - if origrequest.fstype.getName(): - self.fstypeCombo = gtk.Label(origrequest.fstype.getName()) + if format.type: + self.fstypeCombo = gtk.Label(format.name) else: self.fstypeCombo = gtk.Label(_("Unknown")) maintable.attach(self.fstypeCombo, 1, 2, row, row + 1) row += 1 - if origrequest.fslabel: + if getattr(format, "label", None): maintable.attach(createAlignedLabel(_("Original File System " "Label:")), 0, 1, row, row + 1) - maintable.attach(gtk.Label(origrequest.fslabel), 1, 2, row, - row + 1) + maintable.attach(gtk.Label(format.label), + 1, 2, row, row + 1) row += 1 # raid minors lbl = createAlignedLabel(_("RAID _Device:")) maintable.attach(lbl, 0, 1, row, row + 1) - if not origrequest.getPreExisting(): - availminors = self.partitions.getAvailableRaidMinors()[:16] - reqminor = origrequest.raidminor - if reqminor is not None: + if not origrequest.exists: + availminors = self.storage.unusedMDMinors[:16] + reqminor = origrequest.minor + if reqminor is not None and reqminor not in availminors: availminors.append(reqminor) availminors.sort() self.minorCombo = self.createRaidMinorMenu(availminors, reqminor) lbl.set_mnemonic_widget(self.minorCombo) else: - self.minorCombo = gtk.Label("md%s" %(origrequest.raidminor,)) + self.minorCombo = gtk.Label("%s" %(origrequest.name,)) maintable.attach(self.minorCombo, 1, 2, row, row + 1) row = row + 1 @@ -365,16 +420,17 @@ class RaidEditor: lbl = createAlignedLabel(_("RAID _Level:")) maintable.attach(lbl, 0, 1, row, row + 1) - if not origrequest.getPreExisting(): + if not origrequest.exists: # Create here, pack below numparts = len(availraidparts) - if origrequest.raidspares: - nspares = origrequest.raidspares + if origrequest.spares: + nspares = origrequest.spares else: nspares = 0 - if origrequest.raidlevel: - maxspares = raid.get_raid_max_spares(origrequest.raidlevel, numparts) + if origrequest.level: + maxspares = mdraidlib.get_raid_max_spares(origrequest.level, + numparts) else: maxspares = 0 @@ -389,15 +445,15 @@ class RaidEditor: self.sparesb.set_value(0) self.sparesb.set_sensitive(0) else: - self.sparesb = gtk.Label(str(origrequest.raidspares)) + self.sparesb = gtk.Label(str(origrequest.spares)) - if not origrequest.getPreExisting(): - self.levelcombo = self.createRaidLevelMenu(availRaidLevels, - origrequest.raidlevel) + if not origrequest.exists: + self.levelcombo = self.createRaidLevelMenu(mdraidlib.raid_levels, + origrequest.level) lbl.set_mnemonic_widget(self.levelcombo) else: - self.levelcombo = gtk.Label(origrequest.raidlevel) + self.levelcombo = gtk.Label(origrequest.level) maintable.attach(self.levelcombo, 1, 2, row, row + 1) row = row + 1 @@ -408,15 +464,15 @@ class RaidEditor: # XXX need to pass in currently used partitions for this device (self.raidlist, sw) = self.createAllowedRaidPartitionsList(availraidparts, - origrequest.raidmembers, - origrequest.getPreExisting()) + origrequest.devices, + origrequest.exists) lbl.set_mnemonic_widget(self.raidlist) self.raidlist.set_size_request(275, 80) maintable.attach(sw, 1, 2, row, row + 1) row = row + 1 - if origrequest.getPreExisting(): + if origrequest.exists: self.raidlist.set_sensitive(False) # number of spares - created widget above @@ -429,26 +485,26 @@ class RaidEditor: # format or not? self.formatButton = None self.fsoptionsDict = {} - if (origrequest.fstype and origrequest.fstype.isFormattable()) and not origrequest.getPreExisting(): + if not format.exists: self.formatButton = gtk.CheckButton(_("_Format partition?")) - if origrequest.format == None or origrequest.format != 0: + if not format.type: self.formatButton.set_active(1) else: self.formatButton.set_active(0) # it only makes sense to show this for preexisting RAID - if origrequest.getPreExisting(): + if origrequest.exists: maintable.attach(self.formatButton, 0, 2, row, row + 1) row = row + 1 # checkbutton for encryption using dm-crypt/LUKS - if self.origrequest.encryption: + if origrequest.format.type == "luks": self.lukscb.set_active(1) else: self.lukscb.set_active(0) maintable.attach(self.lukscb, 0, 2, row, row + 1) row = row + 1 else: - (row, self.fsoptionsDict) = createPreExistFSOptionSection(self.origrequest, maintable, row, self.mountCombo, self.partitions) + (row, self.fsoptionsDict) = createPreExistFSOptionSection(origrequest, maintable, row, self.mountCombo, self.storage, luksdev=luksdev) # put main table into dialog dialog.vbox.pack_start(maintable) @@ -460,7 +516,7 @@ class RaidEditor: class RaidCloneDialog: - def createDriveList(self, diskset): + def createDriveList(self, disks): store = gtk.ListStore(gobject.TYPE_STRING) view = gtk.TreeView(store) @@ -470,12 +526,9 @@ class RaidCloneDialog: sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) sw.set_shadow_type(gtk.SHADOW_IN) - drives = diskset.disks.keys() - drives.sort() - - for drive in drives: + for disk in disks: iter = store.append() - store.set_value(iter, 0, drive) + store.set_value(iter, 0, disk.name) view.set_property("headers-visible", False) @@ -485,27 +538,18 @@ class RaidCloneDialog: return (sw, view) def getInterestingRequestsForDrive(self, drive): - allrequests = self.partitions.getRequestsByDevice(self.diskset, drive) + disk = self.storage.devicetree.getDeviceByName(drive) + allrequests = self.storage.getDependentDevices(disk) - if allrequests is None or len(allrequests) == 0: + if not allrequests: return allrequests # remove extended partitions requests = [] for req in allrequests: - try: - part = parted.getPartitionByName(req.device) - except: - part = None - - if part: - if part.type & parted.PARTITION_EXTENDED: - continue - elif part.type & parted.PARTITION_FREESPACE: - continue - elif part.type & parted.PARTITION_METADATA: - continue - else: + if req.type == "partition" and req.isExtended: + continue + elif req.type != "partition": continue requests.append(req) @@ -525,7 +569,7 @@ class RaidCloneDialog: return 1 for req in requests: - if not req.fstype or req.fstype.getName() != "software RAID": + if req.format.type != "mdmember": self.intf.messageWindow(_("Source Drive Error"), _("The source drive you selected has " "partitions which are not of " @@ -536,21 +580,23 @@ class RaidCloneDialog: custom_icon="error") return 1 + sourceDev = self.storage.devicetree.getDeviceByName(self.sourceDrive) for req in requests: - if not req.drive or req.drive[0] != self.sourceDrive or len(req.drive) > 1: + if not req.req_disks or len(req.req_disks) > 1 or \ + req.req_disks[0] != self.sourceDrive: self.intf.messageWindow(_("Source Drive Error"), _("The source drive you selected has " "partitions which are not " - "constrained to the drive /dev/%s.\n\n" + "constrained to the drive %s.\n\n" "You must remove these partitions " "or restrict them to this " "drive " "before this drive can be cloned. ") - %(self.sourceDrive,), custom_icon="error") + %(sourceDev.path,), custom_icon="error") return 1 for req in requests: - if self.partitions.isRaidMember(req): + if req not in self.storage.unusedMDMembers(): self.intf.messageWindow(_("Source Drive Error"), _("The source drive you selected has " "software RAID partition(s) which " @@ -564,6 +610,7 @@ class RaidCloneDialog: return 0 def sanityCheckTargetDrives(self): + sourceDev = self.storage.devicetree.getDeviceByName(self.sourceDrive) if self.targetDrives is None or len(self.targetDrives) < 1: self.intf.messageWindow(_("Target Drive Error"), _("Please select the target drives " @@ -572,8 +619,10 @@ class RaidCloneDialog: if self.sourceDrive in self.targetDrives: self.intf.messageWindow(_("Target Drive Error"), - _("The source drive /dev/%s cannot be " - "selected as a target drive as well.") % (self.sourceDrive,), custom_icon="error") + _("The source drive %s cannot be " + "selected as a target drive as well.") + % (sourceDev.path,), + custom_icon="error") return 1 for drive in self.targetDrives: @@ -581,24 +630,20 @@ class RaidCloneDialog: if requests is None: continue + targetDev = self.storage.devicetree.getDeviceByName(drive) for req in requests: - rc = partIntfHelpers.isNotChangable(req, self.partitions) - - # If the partition is protected, we also can't delete it so - # specify a reason why. - if rc is None and req.getProtected(): - rc = _("This partition is holding the data for the hard " - "drive install.") + rc = self.storage.deviceImmutable(req) if rc: self.intf.messageWindow(_("Target Drive Error"), - _("The target drive /dev/%s " + _("The target drive %s " "has a partition which cannot " "be removed for the following " "reason:\n\n\"%s\"\n\n" "You must remove this partition " "before " "this drive can be a target.") % - (drive, rc), custom_icon="error") + (targetDev.path, rc), + custom_icon="error") return 1 return 0 @@ -609,27 +654,28 @@ class RaidCloneDialog: requests = self.getInterestingRequestsForDrive(self.sourceDrive) # no requests to clone, bail out - if requests is None or len(requests) == 0: + if not requests: return 0 # now try to clear the target drives - for device in self.targetDrives: - rc = doDeletePartitionsByDevice(self.intf, self.partitions, - self.diskset, device, - confirm=0, quiet=1) + for devname in self.targetDrives: + device = self.storage.devicetree.getDeviceByName(devname) + doDeleteDependentDevices(self.intf, self.storage, + device, confirm=0, quiet=1) # now clone! for req in requests: for drive in self.targetDrives: - newreq = copy.copy(req) - newreq.drive = [drive] - newreq.uniqueID = None - newreq.device = None - newreq.preexist = 0 - newreq.dev = None - self.partitions.addRequest(newreq) + # this feels really dirty + device = self.storage.devicetree.getDeviceByName(drive) + newdev = copy.deepcopy(req) + newdev.req_disks = [device] + newdev.exists = False + newdev.format.exists = False + newdev.format.device = None + self.storage.createDevice(newdev) - return 0 + return def targetSelectFunc(self, model, path, iter): @@ -672,10 +718,10 @@ class RaidCloneDialog: continue # now give them last chance to bail - msgtxt = _("The drive /dev/%s will now be cloned to the " + msgtxt = _("The drive %s will now be cloned to the " "following drives:\n\n" % (self.sourceDrive,)) for drive in self.targetDrives: - msgtxt = msgtxt + "\t" + "/dev/%s" % (drive,) + msgtxt = msgtxt + "\t" + "%s" % (drive,) msgtxt = msgtxt + _("\n\nWARNING! ALL DATA ON THE TARGET DRIVES " "WILL BE DESTROYED.") @@ -708,9 +754,8 @@ class RaidCloneDialog: self.dialog = None - def __init__(self, partitions, diskset, intf, parent): - self.partitions = partitions - self.diskset = diskset + def __init__(self, storage, intf, parent): + self.storage = storage self.intf = intf self.parent = parent @@ -748,7 +793,7 @@ class RaidCloneDialog: lbl = gtk.Label(_("Source Drive:")) lbl.set_alignment(0.0, 0.0) box.pack_start(lbl, padding=5) - (sw, self.sourceView) = self.createDriveList(diskset) + (sw, self.sourceView) = self.createDriveList(storage.disks) selection = self.sourceView.get_selection() selection.set_mode(gtk.SELECTION_SINGLE) box.pack_start(sw) @@ -756,7 +801,7 @@ class RaidCloneDialog: lbl = gtk.Label(_("Target Drive(s):")) lbl.set_alignment(0.0, 0.0) box.pack_start(lbl, padding=5) - (sw, self.targetView) = self.createDriveList(diskset) + (sw, self.targetView) = self.createDriveList(storage.disks) selection = self.targetView.get_selection() selection.set_mode(gtk.SELECTION_MULTIPLE) box.pack_start(sw) diff --git a/iw/task_gui.py b/iw/task_gui.py index 04032d3ea..5f688f5c5 100644 --- a/iw/task_gui.py +++ b/iw/task_gui.py @@ -268,7 +268,7 @@ class RepoEditor: return True def _applyMedia(self, repo): - cdr = scanForMedia(self.anaconda.backend.ayum.tree) + cdr = scanForMedia(self.anaconda.backend.ayum.tree, self.anaconda.id.storage) if not cdr: self.intf.messageWindow(_("No Media Found"), _("No installation media was found. " @@ -393,7 +393,7 @@ class RepoMethodstrEditor(RepoEditor): return repourl def _applyMedia(self): - cdr = scanForMedia(self.anaconda.backend.ayum.tree) + cdr = scanForMedia(self.anaconda.backend.ayum.tree, self.anaconda.id.storage) if not cdr: self.intf.messageWindow(_("No Media Found"), _("No installation media was found. " diff --git a/iw/timezone_gui.py b/iw/timezone_gui.py index 2b6290901..27657a946 100644 --- a/iw/timezone_gui.py +++ b/iw/timezone_gui.py @@ -29,7 +29,7 @@ import sys from timezone_map_gui import TimezoneMap, Enum from iw_gui import * -from bootloaderInfo import dosFilesystems +from booty.bootloaderInfo import dosFilesystems from bootloader import hasWindows from constants import * diff --git a/iw/upgrade_bootloader_gui.py b/iw/upgrade_bootloader_gui.py index 63ebb9d64..08f2403df 100644 --- a/iw/upgrade_bootloader_gui.py +++ b/iw/upgrade_bootloader_gui.py @@ -23,7 +23,7 @@ from iw_gui import * import gtk -import checkbootloader +from booty import checkbootloader from constants import * import gettext @@ -61,7 +61,7 @@ class UpgradeBootloaderWindow (InstallWindow): self.bl.useGrubVal = 1 else: self.bl.useGrubVal = 0 - self.bl.setDevice(self.bootDev) + self.bl.setDevice(self.bootDev.split("/")[-1]) def _newToLibata(self, rootPath): # NOTE: any changes here need to be done in upgrade_bootloader_text too @@ -117,7 +117,7 @@ class UpgradeBootloaderWindow (InstallWindow): newToLibata = self._newToLibata(anaconda.rootPath) (self.type, self.bootDev) = \ - checkbootloader.getBootloaderTypeAndBoot(anaconda.rootPath) + checkbootloader.getBootloaderTypeAndBoot(anaconda.rootPath, storage=anaconda.id.storage) self.update_radio = gtk.RadioButton(None, _("_Update boot loader configuration")) updatestr = _("This will update your current boot loader.") diff --git a/iw/upgrade_migratefs_gui.py b/iw/upgrade_migratefs_gui.py index bd683b68e..428ead255 100644 --- a/iw/upgrade_migratefs_gui.py +++ b/iw/upgrade_migratefs_gui.py @@ -21,42 +21,48 @@ from iw_gui import * from constants import * +from storage.formats import getFormat +from storage.deviceaction import ActionMigrateFormat import string import isys import iutil -from fsset import * import gtk import gettext _ = lambda x: gettext.ldgettext("anaconda", x) +import logging +log = logging.getLogger("anaconda") + class UpgradeMigrateFSWindow (InstallWindow): windowTitle = N_("Migrate File Systems") def getNext (self): - for entry in self.migent: - entry.setFormat(0) - entry.setMigrate(0) - entry.fsystem = entry.origfsystem - + # I don't like this but I also don't see a better way right now for (cb, entry) in self.cbs: + action = self.devicetree.findActions(device=entry, + type="migrate") if cb.get_active(): - try: - newfs = entry.fsystem.migratetofs[0] - newfs = fileSystemTypeGet(newfs) - except Exception, e: - log.info("failed to get new filesystem type, defaulting to ext3: %s" %(e,)) - newfs = fileSystemTypeGet("ext3") - entry.setFileSystemType(newfs) - entry.setFormat(0) - entry.setMigrate(1) - + if action: + # the migrate action has already been scheduled + continue + + newfs = getFormat(entry.format.migrationTarget) + if not newfs: + log.warning("failed to get new filesystem type (%s)" + % entry.format.migrationTarget) + continue + action = ActionMigrateFormat(entry) + self.devicetree.registerAction(action) + elif action: + self.devicetree.cancelAction(action) + return None def getScreen (self, anaconda): - - self.fsset = anaconda.id.fsset - self.migent = self.fsset.getMigratableEntries() + self.devicetree = anaconda.id.storage.devicetree + self.fsset = anaconda.id.storage.fsset + self.migent = self.fsset.migratableDevices box = gtk.VBox (False, 5) box.set_border_width (5) @@ -79,17 +85,16 @@ class UpgradeMigrateFSWindow (InstallWindow): self.cbs = [] for entry in self.migent: # don't allow the user to migrate /boot to ext4 (#439944) - if entry.mountpoint == "/boot" and entry.origfsystem.getName() == "ext3": + if (getattr(entry.format, "mountpoint", None) == "/boot" and + not entry.format.migrate and entry.format.type == "ext3"): continue - if entry.fsystem.getName() != entry.origfsystem.getName(): - migrating = 1 - else: - migrating = 0 - cb = gtk.CheckButton("/dev/%s - %s - %s" % (entry.device.getDevice(), - entry.origfsystem.getName(), - entry.mountpoint)) - cb.set_active(migrating) + cb = gtk.CheckButton("%s - %s - %s" % (entry.path, + entry.format.name, + getattr(entry.format, + "mountpoint", + None))) + cb.set_active(entry.format.migrate) cbox.pack_start(cb, False) self.cbs.append((cb, entry)) diff --git a/iw/upgrade_swap_gui.py b/iw/upgrade_swap_gui.py index 83e27c6dd..5197d9ea4 100644 --- a/iw/upgrade_swap_gui.py +++ b/iw/upgrade_swap_gui.py @@ -50,9 +50,8 @@ class UpgradeSwapWindow (InstallWindow): selection = self.view.get_selection() (model, iter) = selection.get_selected() if iter: - mnt = model.get_value(iter, 0) - part = model.get_value(iter, 1) - size = int(model.get_value(iter, 2)) + dev = model.get_value(iter, 0) + size = model.get_value(iter, 1) val = int(self.entry.get_text()) else: raise RuntimeError, "unknown value for upgrade swap location" @@ -67,7 +66,7 @@ class UpgradeSwapWindow (InstallWindow): else: if flags.setupFilesystems: - upgrade.createSwapFile(self.instPath, self.fsset, mnt, val) + self.fsset.createSwapFile(self.instPath, dev, val) self.dispatch.skipStep("addswap", 1) return None @@ -80,7 +79,7 @@ class UpgradeSwapWindow (InstallWindow): def getScreen (self, anaconda): self.neededSwap = 0 - self.fsset = anaconda.id.fsset + self.fsset = anaconda.id.storage.fsset self.instPath = anaconda.rootPath self.intf = anaconda.intf self.dispatch = anaconda.dispatch @@ -129,11 +128,10 @@ class UpgradeSwapWindow (InstallWindow): gobject.TYPE_STRING, gobject.TYPE_STRING) - for (mnt, part, size) in fsList: + for (dev, size) in fsList: iter = self.store.append() - self.store.set_value(iter, 0, mnt) - self.store.set_value(iter, 1, part) - self.store.set_value(iter, 2, str(size)) + self.store.set_value(iter, 0, dev) + self.store.set_value(iter, 1, str(size)) self.view=gtk.TreeView(self.store) label.set_mnemonic_widget(self.view) diff --git a/kickstart.py b/kickstart.py index 82ae00b62..802cb4350 100644 --- a/kickstart.py +++ b/kickstart.py @@ -18,25 +18,26 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # +from storage.deviceaction import * +from storage.devices import LUKSDevice +from storage.devicelibs.lvm import getPossiblePhysicalExtents +from storage.formats import getFormat +from storage.partitioning import clearPartitions + from errors import * import iutil import isys import os import tempfile -from autopart import * -from fsset import * from flags import flags from constants import * import sys -import raid import string -import partRequests import urlgrabber.grabber as grabber -import lvm import warnings import upgrade import pykickstart.commands as commands -import cryptodev +from storage.devices import * import zonetab from pykickstart.constants import * from pykickstart.errors import * @@ -146,11 +147,12 @@ class AutoPart(commands.autopart.F9_AutoPart): # sets up default autopartitioning. use clearpart separately # if you want it - self.handler.id.instClass.setDefaultPartitioning(self.handler.id, doClear = 0) + self.handler.id.instClass.setDefaultPartitioning(self.handler.id.storage, self.handler.anaconda.platform, doClear=False) + self.handler.id.storage.doAutoPart = True if self.encrypted: - self.handler.id.partitions.autoEncrypt = True - self.handler.id.partitions.encryptionPassphrase = self.passphrase + self.handler.id.storage.encryptedAutoPart = True + self.handler.id.storage.encryptionPassphrase = self.passphrase self.handler.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) return retval @@ -181,7 +183,11 @@ class Bootloader(commands.bootloader.F8_Bootloader): self.handler.id.bootloader.doUpgradeOnly = 1 if self.driveorder: - hds = isys.hardDriveDict().keys() + # XXX I don't like that we are supposed to have scanned the + # storage devices already and yet we cannot know about + # ignoredDisks, exclusiveDisks, or iscsi disks before we + # have processed the kickstart config file. + hds = [d.name for d in self.handler.id.storage.disks] for disk in self.driveorder: if disk not in hds: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent disk %s in driveorder command" % disk) @@ -233,15 +239,17 @@ class ClearPart(commands.clearpart.FC3_ClearPart): if self.type is None: self.type = CLEARPART_TYPE_NONE - hds = isys.hardDriveDict().keys() + hds = map(lambda x: x.name, self.handler.id.storage.disks) for disk in self.drives: if disk not in hds: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent disk %s in clearpart command" % disk) - self.handler.id.partitions.autoClearPartType = self.type - self.handler.id.partitions.autoClearPartDrives = self.drives + self.handler.id.storage.clearPartType = self.type + self.handler.id.storage.clearPartDisks = self.drives if self.initAll: - self.handler.id.partitions.reinitializeDisks = self.initAll + self.handler.id.storage.reinitializeDisks = self.initAll + + clearPartitions(self.handler.id.storage) return retval @@ -269,15 +277,13 @@ class IgnoreDisk(commands.ignoredisk.F8_IgnoreDisk): def parse(self, args): retval = commands.ignoredisk.F8_IgnoreDisk.parse(self, args) - diskset = self.handler.id.diskset for drive in self.ignoredisk: - if not drive in diskset.skippedDisks: - diskset.skippedDisks.append(drive) + if not drive in self.handler.id.storage.ignoredDisks: + self.handler.id.storage.ignoredDisks.append(drive) - diskset = self.handler.id.diskset for drive in self.onlyuse: - if not drive in diskset.exclusiveDisks: - diskset.exclusiveDisks.append(drive) + if not drive in self.handler.id.storage.exclusiveDisks: + self.handler.id.storage.exclusiveDisks.append(drive) return retval @@ -304,8 +310,6 @@ class Iscsi(commands.iscsi.F10_Iscsi): if self.handler.id.iscsi.addTarget(**kwargs): log.info("added iscsi target: %s" %(target.ipaddr,)) - # FIXME: flush the drive dict so we figure drives out again - isys.flushDriveDict() return retval class IscsiName(commands.iscsiname.FC6_IscsiName): @@ -334,74 +338,104 @@ class LogVol(commands.logvol.F9_LogVol): def parse(self, args): lvd = commands.logvol.F9_LogVol.parse(self, args) + storage = self.handler.id.storage + devicetree = storage.devicetree + if lvd.mountpoint == "swap": - filesystem = fileSystemTypeGet("swap") + type = "swap" lvd.mountpoint = "" - if lvd.recommended: (lvd.size, lvd.maxSizeMB) = iutil.swapSuggestion() lvd.grow = True else: if lvd.fstype != "": - try: - filesystem = fileSystemTypeGet(lvd.fstype) - except KeyError: - raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % lvd.fstype) + type = lvd.fstype else: - filesystem = fileSystemTypeGetDefault() + type = storage.defaultFSType - # sanity check mountpoint + # Sanity check mountpoint if lvd.mountpoint != "" and lvd.mountpoint[0] != '/': raise KickstartValueError, formatErrorMsg(self.lineno, msg="The mount point \"%s\" is not valid." % (lvd.mountpoint,)) - try: - vgid = self.handler.ksVGMapping[lvd.vgname] - except KeyError: - raise KickstartValueError, formatErrorMsg(self.lineno, msg="No volume group exists with the name '%s'. Specify volume groups before logical volumes." % lvd.vgname) - - for areq in self.handler.id.partitions.autoPartitionRequests: - if areq.type == REQUEST_LV: - if areq.volumeGroup == vgid and areq.logicalVolumeName == lvd.name: - raise KickstartValueError, formatErrorMsg(self.lineno, msg="Logical volume name already used in volume group %s" % lvd.vgname) - elif areq.type == REQUEST_VG and areq.uniqueID == vgid: - # Store a reference to the VG so we can do the PE size check. - vg = areq - - if not self.handler.ksVGMapping.has_key(lvd.vgname): - raise KickstartValueError, formatErrorMsg(self.lineno, msg="Logical volume specifies a non-existent volume group" % lvd.name) - - if lvd.percent == 0 and not lvd.preexist: - if lvd.size == 0: - raise KickstartValueError, formatErrorMsg(self.lineno, msg="Size required") - elif not lvd.grow and lvd.size*1024 < vg.pesize: - raise KickstartValueError, formatErrorMsg(self.lineno, msg="Logical volume size must be larger than the volume group physical extent size.") - elif (lvd.percent <= 0 or lvd.percent > 100) and not lvd.preexist: - raise KickstartValueError, formatErrorMsg(self.lineno, msg="Percentage must be between 0 and 100") - - request = partRequests.LogicalVolumeRequestSpec(filesystem, - format = lvd.format, - mountpoint = lvd.mountpoint, - size = lvd.size, - percent = lvd.percent, - volgroup = vgid, - lvname = lvd.name, - grow = lvd.grow, - maxSizeMB = lvd.maxSizeMB, - preexist = lvd.preexist, - fsprofile = lvd.fsprofile) - - if lvd.fsopts != "": - request.fsopts = lvd.fsopts + # Check that the VG this LV is a member of has already been specified. + vg = devicetree.getDeviceByName(lvd.vgname) + if not vg: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="No volume group exists with the name \"%s\". Specify volume groups before logical volumes." % lvd.vgname) + + # If this specifies an existing request that we should not format, + # quit here after setting up enough information to mount it later. + if not lvd.format: + if not lvd.name: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="--noformat used without --name") + + dev = devicetree.getDeviceByName("%s-%s" % (vg.name, lvd.name)) + if not dev: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="No preexisting logical volume with the name \"%s\" was found." % lvd.name) + + dev.format.mountpoint = lvd.mountpoint + dev.format.mountopts = lvd.fsopts + self.handler.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) + return lvd + + # Make sure this LV name is not already used in the requested VG. + tmp = devicetree.getDeviceByName("%s-%s" % (vg.name, lvd.name)) + if tmp: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="Logical volume name already used in volume group %s" % vg.name) + + # Size specification checks + if not lvd.preexist: + if lvd.percent == 0: + if lvd.size == 0: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="Size required") + elif not lvd.grow and lvd.size*1024 < vg.peSize: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="Logical volume size must be larger than the volume group physical extent size.") + elif lvd.percent <= 0 or lvd.percent > 100: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="Percentage must be between 0 and 100") + + # Now get a format to hold a lot of these extra values. + format = getFormat(type, + mountpoint=lvd.mountpoint, + mountopts=lvd.fsopts) + if not format: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % type) + + # If we were given a pre-existing LV to create a filesystem on, we need + # to verify it and its VG exists and then schedule a new format action + # to take place there. Also, we only support a subset of all the + # options on pre-existing LVs. + if lvd.preexist: + device = devicetree.getDeviceByName("%s-%s" % (vg.name, lvd.name)) + if not device: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent LV %s in logvol command" % lvd.name) + + devicetree.registerAction(ActionCreateFormat(device, format)) + else: + request = storage.newLV(format=format, + name=lvd.name, + vg=vg, + size=lvd.size, + grow=lvd.grow, + maxsize=lvd.maxSizeMB, + percent=lvd.percent) + + # FIXME: no way to specify an fsprofile right now + # if lvd.fsprofile: + # request.format.fsprofile = lvd.fsprofile + + storage.createDevice(request) if lvd.encrypted: - if lvd.passphrase and \ - not self.handler.anaconda.id.partitions.encryptionPassphrase: - self.handler.anaconda.id.partitions.encryptionPassphrase = lvd.passphrase - request.encryption = cryptodev.LUKSDevice(passphrase=lvd.passphrase, format=lvd.format) + if lvd.passphrase and not storage.encryptionPassphrase: + storage.encryptionPassphrase = lvd.passphrase - addPartRequest(self.handler.anaconda, request) - self.handler.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) + luksformat = request.format + request.format = getFormat("luks", passphrase=lvd.passphrase, device=request.path) + luksdev = LUKSDevice("luks%d" % storage.nextID, + format=luksformat, + parents=request) + storage.createDevice(luksdev) + self.handler.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) return lvd class Logging(commands.logging.FC6_Logging): @@ -532,11 +566,9 @@ class Partition(commands.partition.F9_Partition): def parse(self, args): pd = commands.partition.F9_Partition.parse(self, args) - uniqueID = None - - fsopts = "" - if pd.fsopts: - fsopts = pd.fsopts + storage = self.handler.id.storage + devicetree = storage.devicetree + kwargs = {} if pd.onbiosdisk != "": pd.disk = isys.doGetBiosDisk(pd.onbiosdisk) @@ -545,7 +577,7 @@ class Partition(commands.partition.F9_Partition): raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified BIOS disk %s cannot be determined" % pd.onbiosdisk) if pd.mountpoint == "swap": - filesystem = fileSystemTypeGet('swap') + type = "swap" pd.mountpoint = "" if pd.recommended: (pd.size, pd.maxSizeMB) = iutil.swapSuggestion() @@ -555,102 +587,117 @@ class Partition(commands.partition.F9_Partition): elif pd.mountpoint == "None": pd.mountpoint = "" if pd.fstype: - try: - filesystem = fileSystemTypeGet(pd.fstype) - except KeyError: - raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % pd.fstype) + type = pd.fstype else: - filesystem = fileSystemTypeGetDefault() - elif pd.mountpoint == 'appleboot': - filesystem = fileSystemTypeGet("Apple Bootstrap") - pd.mountpoint = "" - elif pd.mountpoint == 'prepboot': - filesystem = fileSystemTypeGet("PPC PReP Boot") - pd.mountpoint = "" + type = storage.defaultFSType +# elif pd.mountpoint == 'appleboot': +# filesystem = fileSystemTypeGet("Apple Bootstrap") +# pd.mountpoint = "" +# elif pd.mountpoint == 'prepboot': +# filesystem = fileSystemTypeGet("PPC PReP Boot") +# pd.mountpoint = "" elif pd.mountpoint.startswith("raid."): - filesystem = fileSystemTypeGet("software RAID") - - if self.handler.ksRaidMapping.has_key(pd.mountpoint): - raise KickstartValueError, formatErrorMsg(self.lineno, msg="Defined RAID partition multiple times") - - # get a sort of hackish id - uniqueID = self.handler.ksID - self.handler.ksRaidMapping[pd.mountpoint] = uniqueID - self.handler.ksID += 1 + type = "mdmember" + kwargs["name"] = pd.mountpoint + + if devicetree.getDeviceByName(kwargs["name"]): + raise KickstartValueError, formatErrorMsg(self.lineno, msg="RAID partition defined multiple times") + pd.mountpoint = "" elif pd.mountpoint.startswith("pv."): - filesystem = fileSystemTypeGet("physical volume (LVM)") + type = "lvmpv" + kwargs["name"] = pd.mountpoint - if self.handler.ksPVMapping.has_key(pd.mountpoint): - raise KickstartValueError, formatErrorMsg(self.lineno, msg="Defined PV partition multiple times") + if devicetree.getDeviceByName(kwargs["name"]): + raise KickstartValueError, formatErrorMsg(self.lineno, msg="PV partition defined multiple times") - # get a sort of hackish id - uniqueID = self.handler.ksID - self.handler.ksPVMapping[pd.mountpoint] = uniqueID - self.handler.ksID += 1 pd.mountpoint = "" elif pd.mountpoint == "/boot/efi": - filesystem = fileSystemTypeGet("efi") - fsopts = "defaults,uid=0,gid=0,umask=0077,shortname=winnt" + type = "vfat" + pd.fsopts = "defaults,uid=0,gid=0,umask=0077,shortname=winnt" else: if pd.fstype != "": - try: - filesystem = fileSystemTypeGet(pd.fstype) - except KeyError: - raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % pd.fstype) + type = pd.fstype else: - filesystem = fileSystemTypeGetDefault() + type = storage.defaultFSType - if pd.size is None and (pd.start == 0 and pd.end == 0) and pd.onPart == "": + # If this specified an existing request that we should not format, + # quit here after setting up enough information to mount it later. + if not pd.format: + if not pd.onPart: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="--noformat used without --onpart") + + dev = devicetree.getDeviceByName(pd.onPart) + if not dev: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="No preexisting partition with the name \"%s\" was found." % pd.onPart) + + dev.format.mountpoint = pd.mountpoint + dev.format.mountopts = pd.fsopts + self.handler.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) + return pd + + # Size specification checks. + if pd.size is None and pd.onPart == "": raise KickstartValueError, formatErrorMsg(self.lineno, msg="Partition requires a size specification") - if pd.start != 0 and pd.disk == "": - raise KickstartValueError, formatErrorMsg(self.lineno, msg="Partition command with start cylinder requires a drive specification") - hds = isys.hardDriveDict() - if not hds.has_key(pd.disk) and hds.has_key('mapper/'+pd.disk): - pd.disk = 'mapper/' + pd.disk - if pd.disk != "" and pd.disk not in hds.keys(): - raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent disk %s in partition command" % pd.disk) - - request = partRequests.PartitionSpec(filesystem, - mountpoint = pd.mountpoint, - format = pd.format, - fslabel = pd.label, - fsprofile = pd.fsprofile) - - if pd.size is not None: - request.size = pd.size - if pd.start != 0: - request.start = pd.start - if pd.end != 0: - request.end = pd.end - if pd.grow: - request.grow = pd.grow - if pd.maxSizeMB != 0: - request.maxSizeMB = pd.maxSizeMB - if pd.disk != "": - request.drive = [ pd.disk ] - if pd.primOnly: - request.primary = pd.primOnly - if uniqueID: - request.uniqueID = uniqueID - if pd.onPart != "": - request.device = pd.onPart - for areq in self.handler.id.partitions.autoPartitionRequests: - if areq.device is not None and areq.device == pd.onPart: - raise KickstartValueError, formatErrorMsg(self.lineno, "Partition already used") - - if fsopts != "": - request.fsopts = fsopts + + # Now get a format to hold a lot of these extra values. + kwargs["format"] = getFormat(type, + mountpoint=pd.mountpoint, + label=pd.label, + mountopts=pd.fsopts) + if not kwargs["format"]: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % type) + + # If we were given a specific disk to create the partition on, verify + # that it exists first. If it doesn't exist, see if it exists with + # mapper/ on the front. If that doesn't exist either, it's an error. + if pd.disk: + disk = devicetree.getDeviceByName(pd.disk) + if not disk: + pd.disk = "mapper/" % pd.disk + disk = devicetree.getDeviceByName(pd.disk) + + if not disk: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent disk %s in partition command" % pd.disk) + + kwargs["disks"] = [disk] + + kwargs["grow"] = pd.grow + kwargs["size"] = pd.size + kwargs["maxsize"] = pd.maxSizeMB + kwargs["primary"] = pd.primOnly + + # If we were given a pre-existing partition to create a filesystem on, + # we need to verify it exists and then schedule a new format action to + # take place there. Also, we only support a subset of all the options + # on pre-existing partitions. + if pd.onPart: + device = devicetree.getDeviceByName(pd.onPart) + if not device: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent partition %s in partition command" % pd.onPart) + + devicetree.registerAction(ActionCreateFormat(device, kwargs["format"])) + else: + request = storage.newPartition(**kwargs) + + # FIXME: no way to specify an fsprofile right now + # if pd.fsprofile: + # request.format.fsprofile = pd.fsprofile + + storage.createDevice(request) if pd.encrypted: - if pd.passphrase and \ - not self.handler.anaconda.id.partitions.encryptionPassphrase: - self.handler.anaconda.id.partitions.encryptionPassphrase = pd.passphrase - request.encryption = cryptodev.LUKSDevice(passphrase=pd.passphrase, format=pd.format) + if pd.passphrase and not storage.encryptionPassphrase: + storage.encryptionPassphrase = pd.passphrase - addPartRequest(self.handler.anaconda, request) - self.handler.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) + luksformat = request.format + request.format = getFormat("luks", passphrase=pd.passphrase, device=request.path) + luksdev = LUKSDevice("luks%d" % storage.nextID, + format=luksformat, + parents=request) + storage.createDevice(luksdev) + self.handler.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) return pd class Reboot(commands.reboot.FC6_Reboot): @@ -662,77 +709,106 @@ class Reboot(commands.reboot.FC6_Reboot): class Raid(commands.raid.F9_Raid): def parse(self, args): rd = commands.raid.F9_Raid.parse(self, args) + raidmems = [] - uniqueID = None + storage = self.handler.id.storage + devicetree = storage.devicetree + kwargs = {} if rd.mountpoint == "swap": - filesystem = fileSystemTypeGet('swap') + type = "swap" rd.mountpoint = "" elif rd.mountpoint.startswith("pv."): - filesystem = fileSystemTypeGet("physical volume (LVM)") + type = "lvmpv" + kwargs["name"] = rd.mountpoint - if self.handler.ksPVMapping.has_key(rd.mountpoint): - raise KickstartValueError, formatErrorMsg(self.lineno, msg="Defined PV partition multiple times") + if devicetree.getDeviceByName(kwargs["name"]): + raise KickstartValueError, formatErrorMsg(self.lineno, msg="PV partition defined multiple times") - # get a sort of hackish id - uniqueID = self.handler.ksID - self.handler.ksPVMapping[rd.mountpoint] = uniqueID - self.handler.ksID += 1 rd.mountpoint = "" else: if rd.fstype != "": - try: - filesystem = fileSystemTypeGet(rd.fstype) - except KeyError: - raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % rd.fstype) + type = rd.fstype else: - filesystem = fileSystemTypeGetDefault() + type = storage.defaultFSType - # sanity check mountpoint + # Sanity check mountpoint if rd.mountpoint != "" and rd.mountpoint[0] != '/': raise KickstartValueError, formatErrorMsg(self.lineno, msg="The mount point is not valid.") - raidmems = [] + # If this specifies an existing request that we should not format, + # quit here after setting up enough information to mount it later. + if not rd.format: + if not rd.device: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="--noformat used without --device") - # get the unique ids of each of the raid members + dev = devicetree.getDeviceByName(rd.device) + if not dev: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="No preexisting RAID device with the name \"%s\" was found." % rd.device) + + dev.format.mountpoint = lvd.mountpoint + dev.format.mountopts = lvd.fsopts + self.handler.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) + return rd + + # Get a list of all the RAID members. for member in rd.members: - if member not in self.handler.ksRaidMapping.keys(): + dev = devicetree.getDeviceByName(member) + if not dev: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Tried to use undefined partition %s in RAID specification" % member) - if member in self.handler.ksUsedMembers: - raise KickstartValueError, formatErrorMsg(self.lineno, msg="Tried to use RAID member %s in two or more RAID specifications" % member) - - raidmems.append(self.handler.ksRaidMapping[member]) - self.handler.ksUsedMembers.append(member) - - if rd.level == "" and not rd.preexist: - raise KickstartValueError, formatErrorMsg(self.lineno, msg="RAID Partition defined without RAID level") - if len(raidmems) == 0 and not rd.preexist: - raise KickstartValueError, formatErrorMsg(self.lineno, msg="RAID Partition defined without any RAID members") - - request = partRequests.RaidRequestSpec(filesystem, - mountpoint = rd.mountpoint, - raidmembers = raidmems, - raidlevel = rd.level, - raidspares = rd.spares, - format = rd.format, - raidminor = rd.device, - preexist = rd.preexist, - fsprofile = rd.fsprofile) - - if uniqueID is not None: - request.uniqueID = uniqueID - if rd.preexist and rd.device != "": - request.device = "md%s" % rd.device - if rd.fsopts != "": - request.fsopts = rd.fsopts + + raidmems.append(dev) + + if not rd.preexist: + if len(raidmems) == 0: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="RAID Partition defined without any RAID members") + + if rd.level == "": + raise KickstartValueError, formatErrorMsg(self.lineno, msg="RAID Partition defined without RAID level") + + # Now get a format to hold a lot of these extra values. + kwargs["format"] = getFormat(type, + mountpoint=rd.mountpoint, + mountopts=rd.fsopts) + if not kwargs["format"]: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % type) + + kwargs["name"] = rd.device + kwargs["level"] = rd.level + kwargs["parents"] = raidmems + kwargs["memberDevices"] = len(raidmems) + kwargs["totalDevices"] = kwargs["memberDevices"]+rd.spares + + # If we were given a pre-existing RAID to create a filesystem on, + # we need to verify it exists and then schedule a new format action + # to take place there. Also, we only support a subset of all the + # options on pre-existing RAIDs. + if rd.preexist: + device = devicetree.getDeviceByName(rd.name) + if not device: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specifeid nonexisted RAID %s in raid command" % rd.name) + + devicetree.registerAction(ActionCreateFormat(device, kwargs["format"])) + else: + request = storage.newMDArray(**kwargs) + + # FIXME: no way to specify an fsprofile right now + # if pd.fsprofile: + # request.format.fsprofile = pd.fsprofile + + storage.createDevice(request) if rd.encrypted: - if rd.passphrase and \ - not self.handler.anaconda.id.partitions.encryptionPassphrase: - self.handler.anaconda.id.partitions.encryptionPassphrase = rd.passphrase - request.encryption = cryptodev.LUKSDevice(passphrase=rd.passphrase, format=rd.format) + if rd.passphrase and not storage.encryptionPassphrase: + storage.encryptionPassphrase = rd.passphrase + + luksformat = request.format + request.format = getFormat("luks", passphrase=rd.passphrase, device=request.path) + luksdev = LUKSDevice("luks%d" % storage.nextID, + format=luksformat, + parents=request) + storage.createDevice(luksdev) - addPartRequest(self.handler.anaconda, request) self.handler.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) return rd @@ -789,30 +865,51 @@ class VolGroup(commands.volgroup.FC3_VolGroup): vgd = commands.volgroup.FC3_VolGroup.parse(self, args) pvs = [] - # get the unique ids of each of the physical volumes + storage = self.handler.id.storage + devicetree = storage.devicetree + + # Get a list of all the physical volume devices that make up this VG. for pv in vgd.physvols: - if pv not in self.handler.ksPVMapping.keys(): + dev = devicetree.getDeviceByName(pv) + if not dev: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Tried to use undefined partition %s in Volume Group specification" % pv) - pvs.append(self.handler.ksPVMapping[pv]) + + pvs.append(dev) if len(pvs) == 0 and not vgd.preexist: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Volume group defined without any physical volumes. Either specify physical volumes or use --useexisting.") - if vgd.pesize not in lvm.getPossiblePhysicalExtents(floor=1024): + if vgd.pesize not in getPossiblePhysicalExtents(floor=1024): raise KickstartValueError, formatErrorMsg(self.lineno, msg="Volume group specified invalid pesize") - # get a sort of hackish id - uniqueID = self.handler.ksID - self.handler.ksVGMapping[vgd.vgname] = uniqueID - self.handler.ksID += 1 - - request = partRequests.VolumeGroupRequestSpec(vgname = vgd.vgname, - physvols = pvs, - preexist = vgd.preexist, - format = vgd.format, - pesize = vgd.pesize) - request.uniqueID = uniqueID - addPartRequest(self.handler.anaconda, request) + # If --noformat was given, there's really nothing to do. + if not vgd.format: + if not vgd.name: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="--noformat used without giving a name") + + dev = devicetree.getDeviceByName(vgd.name) + if not dev: + raise KickstartValueError, formatErrorMsg(self.lineno, msg="No preexisting VG with the name \"%s\" was found." % vgd.name) + + return vgd + + # If we were given a pre-existing VG to use, we need to verify it + # exists and then schedule a new format action to take place there. + # Also, we only support a subset of all the options on pre-existing + # VGs. + if vgd.preexist: + device = devicetree.getDeviceByName(vgd.name) + if not device: + raise KicsktartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent VG %s in volgroup command" % vgd.name) + + devicetree.registerAction(ActionCreateFormat(device)) + else: + request = storage.newVG(pvs=pvs, + name=vgd.vgname, + peSize=vgd.pesize/1024.0) + + storage.createDevice(request) + return vgd class XConfig(commands.xconfig.F10_XConfig): @@ -830,7 +927,7 @@ class XConfig(commands.xconfig.F10_XConfig): class ZeroMbr(commands.zerombr.FC3_ZeroMbr): def parse(self, args): retval = commands.zerombr.FC3_ZeroMbr.parse(self, args) - self.handler.id.partitions.zeroMbr = 1 + self.handler.id.storage.zeroMbr = 1 return retval class ZFCP(commands.zfcp.FC3_ZFCP): @@ -917,13 +1014,6 @@ class AnacondaKSHandler(superclass): self.permanentSkipSteps = [] self.skipSteps = [] self.showSteps = [] - self.ksRaidMapping = {} - self.ksUsedMembers = [] - self.ksPVMapping = {} - self.ksVGMapping = {} - # XXX hack to give us a starting point for RAID, LVM, etc unique IDs. - self.ksID = 100000 - self.anaconda = anaconda self.id = self.anaconda.id @@ -998,25 +1088,10 @@ class AnacondaKSParser(KickstartParser): KickstartParser.handleCommand(self, lineno, args) -# this adds a partition to the autopartition list replacing anything -# else with this mountpoint so that you can use autopart and override / -def addPartRequest(anaconda, request): - if not request.mountpoint: - anaconda.id.partitions.autoPartitionRequests.append(request) - return - - for req in anaconda.id.partitions.autoPartitionRequests: - if req.mountpoint and req.mountpoint == request.mountpoint: - anaconda.id.partitions.autoPartitionRequests.remove(req) - break - anaconda.id.partitions.autoPartitionRequests.append(request) - def processKickstartFile(anaconda, file): - # make sure our disks are alive - from partedUtils import DiskSet - ds = DiskSet(anaconda) - ds.startMPath() - ds.startDmRaid() + # We need to make sure storage is active before the kickstart file is read. + import storage + storage.storageInitialize(anaconda) # parse the %pre ksparser = KickstartPreParser(AnacondaKSHandler(anaconda)) @@ -1218,6 +1293,9 @@ def setSteps(anaconda): dispatch.skipStep("installtype") dispatch.skipStep("network") + # Storage is initialized for us right when kickstart processing starts. + dispatch.skipStep("storageinit") + # Don't show confirmation screens on non-interactive installs. if not interactive: dispatch.skipStep("confirminstall") @@ -29,6 +29,7 @@ import stat import shutil import time import subprocess +import storage import selinux @@ -41,7 +42,6 @@ _ = lambda x: gettext.ldgettext("anaconda", x) import backend import isys import iutil -import fsset import packages @@ -136,30 +136,30 @@ class LiveCDCopyBackend(backend.AnacondaBackend): def _unmountNonFstabDirs(self, anaconda): # unmount things that aren't listed in /etc/fstab. *sigh* - dirs = ["/dev"] + dirs = [] if flags.selinux: dirs.append("/selinux") for dir in dirs: try: - isys.umount("%s/%s" %(anaconda.rootPath,dir), removeDir = 0) + isys.umount("%s/%s" %(anaconda.rootPath,dir), removeDir = False) except Exception, e: log.error("unable to unmount %s: %s" %(dir, e)) def postAction(self, anaconda): self._unmountNonFstabDirs(anaconda) try: - anaconda.id.fsset.umountFilesystems(anaconda.rootPath, - swapoff = False) + anaconda.id.storage.fsset.umountFilesystems(anaconda.rootPath, + swapoff = False) os.rmdir(anaconda.rootPath) except Exception, e: - log.error("Unable to unmount filesystems.") + log.error("Unable to unmount filesystems: %s" % e) def doPreInstall(self, anaconda): if anaconda.dir == DISPATCH_BACK: self._unmountNonFstabDirs(anaconda) return - - anaconda.id.fsset.umountFilesystems(anaconda.rootPath, swapoff = False) + anaconda.id.storage.fsset.umountFilesystems(anaconda.rootPath, + swapoff = False) def doInstall(self, anaconda): log.info("Preparing to install packages") @@ -174,16 +174,9 @@ class LiveCDCopyBackend(backend.AnacondaBackend): osimg = self._getLiveBlockDevice() # the real image osfd = os.open(osimg, os.O_RDONLY) - r = anaconda.id.fsset.getEntryByMountPoint("/") - rootfs = r.device.setupDevice() - rootfd = os.open(rootfs, os.O_WRONLY) - - # set the rootfs to have the right type. this lets things work - # given ext2 or ext3 (and in the future, ext4) - # FIXME: should we try to migrate if there isn't a match? - roottype = isys.readFSType(osimg) - if roottype is not None: - r.fsystem = fsset.fileSystemTypeGet(roottype) + rootDevice = anaconda.id.storage.fsset.rootDevice + rootDevice.setup() + rootfd = os.open(rootDevice.path, os.O_WRONLY) readamt = 1024 * 1024 * 8 # 8 megs at a time size = self._getLiveSize() @@ -222,6 +215,7 @@ class LiveCDCopyBackend(backend.AnacondaBackend): anaconda.id.instProgress = None def _doFilesystemMangling(self, anaconda): + # FIXME: this whole method is a big fucking mess log.info("doing post-install fs mangling") wait = anaconda.intf.waitWindow(_("Doing post-installation"), _("Performing post-installation filesystem changes. This may take several minutes...")) @@ -230,29 +224,38 @@ class LiveCDCopyBackend(backend.AnacondaBackend): self._resizeRootfs(anaconda, wait) # remount filesystems - anaconda.id.fsset.mountFilesystems(anaconda) + anaconda.id.storage.fsset.mountFilesystems(anaconda) # restore the label of / to what we think it is - r = anaconda.id.fsset.getEntryByMountPoint("/") - anaconda.id.fsset.labelEntry(r, anaconda.rootPath, True) + rootDevice = anaconda.id.storage.fsset.rootDevice + rootDevice.setup() # ensure we have a random UUID on the rootfs # FIXME: this should be abstracted per filesystem type - iutil.execWithRedirect("tune2fs", ["-U", "random", "/dev/%s" % (r.device.getDevice())], - stdout="/dev/tty5", stderr="/dev/tty5", + iutil.execWithRedirect("tune2fs", + ["-U", + "random", + rootDevice.path], + stdout="/dev/tty5", + stderr="/dev/tty5", searchPath = 1) + # and now set the uuid in the storage layer + rootDevice.updateSysfsPath() + iutil.notify_kernel("/sys%s" %rootDevice.sysfsPath) + storage.udev.udev_settle() + rootDevice.updateSysfsPath() + info = storage.udev.udev_get_block_device("/sys%s" % rootDevice.sysfsPath) + rootDevice.format.uuid = storage.udev.udev_device_get_uuid(info) + log.info("reset the rootdev (%s) to have a uuid of %s" %(rootDevice.sysfsPath, rootDevice.format.uuid)) # for any filesystem that's _not_ on the root, we need to handle # moving the bits from the livecd -> the real filesystems. # this is pretty distasteful, but should work with things like # having a separate /usr/local - # get a list of fsset entries that are relevant - entries = sorted(filter(lambda e: not e.fsystem.isKernelFS() and \ - e.getMountPoint(), anaconda.id.fsset.entries)) # now create a tree so that we know what's mounted under where fsdict = {"/": []} - for entry in entries: - tocopy = entry.getMountPoint() + for entry in anaconda.id.storage.fsset.mountpoints.itervalues(): + tocopy = entry.format.mountpoint if tocopy.startswith("/mnt") or tocopy == "swap": continue keys = sorted(fsdict.keys(), reverse = True) @@ -268,8 +271,8 @@ class LiveCDCopyBackend(backend.AnacondaBackend): if tocopy in copied: continue copied.append(tocopy) - copied.extend(map(lambda x: x.getMountPoint(), fsdict[tocopy])) - entry = anaconda.id.fsset.getEntryByMountPoint(tocopy) + copied.extend(map(lambda x: x.format.mountpoint, fsdict[tocopy])) + entry = anaconda.id.storage.fsset.mountpoints[tocopy] # FIXME: all calls to wait.refresh() are kind of a hack... we # should do better about not doing blocking things in the @@ -279,9 +282,9 @@ class LiveCDCopyBackend(backend.AnacondaBackend): # unmount subdirs + this one and then remount under /mnt for e in fsdict[tocopy] + [entry]: - e.umount(anaconda.rootPath) + e.format.teardown() for e in [entry] + fsdict[tocopy]: - e.mount(anaconda.rootPath + "/mnt") + e.format.setup(chroot=anaconda.rootPath + "/mnt") copytree("%s/%s" %(anaconda.rootPath, tocopy), "%s/mnt/%s" %(anaconda.rootPath, tocopy), True, True, @@ -291,14 +294,14 @@ class LiveCDCopyBackend(backend.AnacondaBackend): # mount it back in the correct place for e in fsdict[tocopy] + [entry]: - e.umount(anaconda.rootPath + "/mnt") + e.format.teardown() try: os.rmdir("%s/mnt/%s" %(anaconda.rootPath, - e.getMountPoint())) + e.format.mountpoint)) except OSError, e: log.debug("error removing %s" %(tocopy,)) for e in [entry] + fsdict[tocopy]: - e.mount(anaconda.rootPath) + e.format.setup(chroot=anaconda.rootPath) wait.refresh() @@ -308,19 +311,17 @@ class LiveCDCopyBackend(backend.AnacondaBackend): isys.mount("/selinux", anaconda.rootPath + "/selinux", "selinuxfs") except Exception, e: log.error("error mounting selinuxfs: %s" %(e,)) - isys.mount("/dev", "%s/dev" %(anaconda.rootPath,), bindMount = 1) wait.pop() def _resizeRootfs(self, anaconda, win = None): log.info("going to do resize") - r = anaconda.id.fsset.getEntryByMountPoint("/") - rootdev = r.device.getDevice() + rootDevice = anaconda.id.storage.fsset.rootDevice # FIXME: we'd like to have progress here to give an idea of # how long it will take. or at least, to give an indefinite # progress window. but, not for this time - cmd = ["resize2fs", "/dev/%s" %(rootdev,), "-p"] + cmd = ["resize2fs", rootDevice.path, "-p"] out = open("/dev/tty5", "w") proc = subprocess.Popen(cmd, stdout=out, stderr=out) rc = proc.poll() @@ -345,9 +346,9 @@ class LiveCDCopyBackend(backend.AnacondaBackend): anaconda.id.desktop.setDefaultRunLevel(5) # now write out the "real" fstab and mtab - anaconda.id.fsset.write(anaconda.rootPath) + anaconda.id.storage.write(anaconda.rootPath) f = open(anaconda.rootPath + "/etc/mtab", "w+") - f.write(anaconda.id.fsset.mtab()) + f.write(anaconda.id.storage.fsset.mtab()) f.close() # copy over the modprobe.conf @@ -376,9 +377,8 @@ class LiveCDCopyBackend(backend.AnacondaBackend): # FIXME: really, this should be in the general sanity checking, but # trying to weave that in is a little tricky at present. ossize = self._getLiveSizeMB() - slash = anaconda.id.partitions.getRequestByMountPoint("/") - if slash and \ - slash.getActualSize(anaconda.id.partitions, anaconda.id.diskset) < ossize: + slash = anaconda.id.storage.fsset.rootDevice + if slash.size < ossize: rc = anaconda.intf.messageWindow(_("Error"), _("The root filesystem you created is " "not large enough for this live " @@ -391,7 +391,6 @@ class LiveCDCopyBackend(backend.AnacondaBackend): return DISPATCH_BACK else: sys.exit(1) - # package/group selection doesn't apply for this backend def groupExists(self, group): diff --git a/liveinst/liveinst.sh b/liveinst/liveinst.sh index cc6e3d5f8..cdba58f03 100644 --- a/liveinst/liveinst.sh +++ b/liveinst/liveinst.sh @@ -56,10 +56,14 @@ if [ ! -e /selinux/load ]; then ANACONDA="$ANACONDA --noselinux" fi +# devkit-disks is now mounting lots of stuff. for now, let's just try to unmount it all +umount /media/* /sbin/swapoff -a /sbin/lvm vgchange -an --ignorelockingfailure -if [ -x /usr/bin/hal-lock -a -e /var/lock/subsys/haldaemon ]; then +if [ -x /usr/bin/devkit-disks ]; then + /usr/bin/devkit-disks --inhibit -- /usr/bin/hal-lock --interface org.freedesktop.Hal.Device.Storage --exclusive --run "$ANACONDA $*" +elif [ -x /usr/bin/hal-lock -a -e /var/lock/subsys/haldaemon ]; then /usr/bin/hal-lock --interface org.freedesktop.Hal.Device.Storage --exclusive --run "$ANACONDA $*" else $ANACONDA $* diff --git a/lvm.py b/lvm.py deleted file mode 100644 index 520619f82..000000000 --- a/lvm.py +++ /dev/null @@ -1,614 +0,0 @@ -# -# lvm.py - lvm probing control -# -# Copyright (C) 2002 Red Hat, Inc. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# Author(s): Jeremy Katz <katzj@redhat.com> -# - -import iutil -import os,sys -import string -import math -import isys -import re - -from flags import flags - -import logging -log = logging.getLogger("anaconda") - -from constants import * - -MAX_LV_SLOTS=256 - -lvmDevicePresent = 0 - -from errors import * - -def has_lvm(): - global lvmDevicePresent - - if not (os.access("/usr/sbin/lvm", os.X_OK) or - os.access("/sbin/lvm", os.X_OK)): - return - - f = open("/proc/devices", "r") - lines = f.readlines() - f.close() - - for line in lines: - try: - (dev, name) = line[:-1].split(' ', 2) - except: - continue - if name == "device-mapper": - lvmDevicePresent = 1 - break - return lvmDevicePresent -# now check to see if lvm is available -has_lvm() - -def lvmExec(*args): - try: - return iutil.execWithRedirect("lvm", args, stdout = lvmErrorOutput, - stderr = lvmErrorOutput, searchPath = 1) - except Exception, e: - log.error("error running lvm command: %s" %(e,)) - raise LvmError, args[0] - -def lvmCapture(*args): - try: - lvmout = iutil.execWithCapture("lvm", args, stderr = lvmErrorOutput) - lines = [] - for line in lvmout.split("\n"): - lines.append(line.strip().split(':')) - return lines - except Exception, e: - log.error("error running lvm command: %s" %(e,)) - raise LvmError, args[0] - -def vgscan(): - """Runs vgscan.""" - global lvmDevicePresent - - if flags.test or lvmDevicePresent == 0: - return - - rc = lvmExec("vgscan", "-v") - if rc: - log.error("running vgscan failed: %s" %(rc,)) -# lvmDevicePresent = 0 - -def vgmknodes(volgroup=None): - # now make the device nodes - args = ["vgmknodes", "-v"] - if volgroup: - args.append(volgroup) - rc = lvmExec(*args) - if rc: - log.error("running vgmknodes failed: %s" %(rc,)) -# lvmDevicePresent = 0 - -def vgcheckactive(volgroup = None): - """Check if volume groups are active - - volgroup - optional parameter to inquire about a specific volume group. - """ - global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: - return False - - args = ["lvs", "--noheadings", "--units", "b", "--nosuffix", - "--separator", ":", "--options", "vg_name,lv_name,attr"] - for line in lvmCapture(*args): - try: - (vg, lv, attr) = line - except: - continue - - log.info("lv %s/%s, attr is %s" %(vg, lv, attr)) - if attr.find("a") == -1: - continue - - if volgroup is None or volgroup == vg: - return True - - return False - -def vgactivate(volgroup = None): - """Activate volume groups by running vgchange -ay. - - volgroup - optional single volume group to activate - """ - global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: - return - - args = ["vgchange", "-ay", "-v"] - if volgroup: - args.append(volgroup) - rc = lvmExec(*args) - if rc: - log.error("running vgchange failed: %s" %(rc,)) -# lvmDevicePresent = 0 - vgmknodes(volgroup) - -def vgdeactivate(volgroup = None): - """Deactivate volume groups by running vgchange -an. - - volgroup - optional single volume group to deactivate - """ - global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: - return - - args = ["vgchange", "-an", "-v"] - if volgroup: - args.append(volgroup) - rc = lvmExec(*args) - if rc: - log.error("running vgchange failed: %s" %(rc,)) -# lvmDevicePresent = 0 - -def lvcreate(lvname, vgname, size): - """Creates a new logical volume. - - lvname - name of logical volume to create. - vgname - name of volume group lv will be in. - size - size of lv, in megabytes. - """ - global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: - return - writeForceConf() - vgscan() - - args = ["lvcreate", "-v", "-L", "%dM" %(size,), "-n", lvname, "-An", vgname] - try: - rc = lvmExec(*args) - except: - rc = 1 - if rc: - raise LVCreateError(vgname, lvname, size) - unlinkConf() - -def lvremove(lvname, vgname): - """Removes a logical volume. - - lvname - name of logical volume to remove. - vgname - name of volume group lv is in. - """ - global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: - return - - args = ["lvremove", "-f", "-v"] - dev = "/dev/%s/%s" %(vgname, lvname) - args.append(dev) - - try: - rc = lvmExec(*args) - except: - rc = 1 - if rc: - raise LVRemoveError(vgname, lvname) - -def lvresize(lvname, vgname, size): - global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: - return - - args = ["lvresize", "-An", "-L", "%dM" %(size,), "-v", "--force", - "/dev/%s/%s" %(vgname, lvname,)] - - try: - rc = lvmExec(*args) - except: - rc = 1 - if rc: - raise LVResizeError(vgname, lvname) - - -def vgcreate(vgname, PESize, nodes): - """Creates a new volume group." - - vgname - name of volume group to create. - PESize - Physical Extent size, in kilobytes. - nodes - LVM Physical Volumes on which to put the new VG. - """ - global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: - return - - # rescan now that we've recreated pvs. ugh. - writeForceConf() - vgscan() - - args = ["vgcreate", "-v", "-An", "-s", "%sk" % (PESize,), vgname ] - args.extend(nodes) - - try: - rc = lvmExec(*args) - except: - rc = 1 - if rc: - raise VGCreateError(vgname, PESize, nodes) - unlinkConf() - -def vgremove(vgname): - """Removes a volume group. Deactivates the volume group first - - vgname - name of volume group. - """ - global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: - return - - # find the Physical Volumes which make up this Volume Group, so we - # can prune and recreate them. - pvs = [] - for pv in pvlist(): - if pv[1] == vgname: - pvs.append(pv[0]) - - # we'll try to deactivate... if it fails, we'll probably fail on - # the removal too... but it's worth a shot - try: - vgdeactivate(vgname) - except: - pass - - args = ["vgremove", "-v", vgname] - - log.info(string.join(args, ' ')) - try: - rc = lvmExec(*args) - except: - rc = 1 - if rc: - raise VGRemoveError, vgname - - # now iterate all the PVs we've just freed up, so we reclaim the metadata - # space. This is an LVM bug, AFAICS. - for pvname in pvs: - args = ["pvremove", "-ff", "-y", "-v", pvname] - - log.info(string.join(args, ' ')) - try: - rc = lvmExec(*args) - except: - rc = 1 - if rc: - raise PVRemoveError, pvname - - args = ["pvcreate", "-ff", "-y", "-v", pvname] - - log.info(string.join(args, ' ')) - try: - rc = lvmExec(*args) - except: - rc = 1 - if rc: - raise PVCreateError, pvname - wipeOtherMetadataFromPV(pvname) - -def pvcreate(node): - """Initializes a new Physical Volume." - - node - path to device node on which to create the new PV." - """ - global lvmDevicePresent - if flags.test or lvmDevicePresent == 0: - return - - # rescan now that we've recreated pvs. ugh. - writeForceConf() - - args = ["pvcreate", "-ff", "-y", "-v", node ] - - try: - rc = lvmExec(*args) - except: - rc = 1 - if rc: - raise PVCreateError, node - unlinkConf() - wipeOtherMetadataFromPV(node) - -def lvlist(): - global lvmDevicePresent - if lvmDevicePresent == 0: - return [] - - lvs = [] - # field names for "options" are in LVM2.2.01.01/lib/report/columns.h - args = ["lvdisplay", "-C", "--noheadings", "--units", "b", - "--nosuffix", "--separator", ":", "--options", - "vg_name,lv_name,lv_size,origin" - ] - lvscanout = iutil.execWithCapture("lvm", args, stderr = "/dev/tty6") - for line in lvmCapture(*args): - try: - (vg, lv, size, origin) = line - size = long(math.floor(long(size) / (1024 * 1024))) - if origin == '': - origin = None - except: - continue - - logmsg = "lv is %s/%s, size of %s" % (vg, lv, size) - if origin: - logmsg += ", snapshot from %s" % (origin,) - log.info(logmsg) - lvs.append( (vg, lv, size, origin) ) - - return lvs - -def pvlist(): - global lvmDevicePresent - if lvmDevicePresent == 0: - return [] - - pvs = [] - args = ["pvdisplay", "-C", "--noheadings", "--units", "b", - "--nosuffix", "--separator", ":", "--options", - "pv_name,vg_name,dev_size" - ] - for line in lvmCapture(*args): - try: - (dev, vg, size) = line - size = long(math.floor(long(size) / (1024 * 1024))) - except: - continue - - if dev.startswith("/dev/dm-"): - from block import dm - try: - sb = os.stat(dev) - (major, minor) = (os.major(sb.st_rdev), os.minor(sb.st_rdev)) - for map in dm.maps(): - if map.dev.major == major and map.dev.minor == minor: - dev = "/dev/mapper/%s" % map.name - break - except: - pass - - log.info("pv is %s in vg %s, size is %s" %(dev, vg, size)) - pvs.append( (dev, vg, size) ) - - return pvs - -def vglist(): - global lvmDevicePresent - if lvmDevicePresent == 0: - return [] - - vgs = [] - args = ["vgdisplay", "-C", "--noheadings", "--units", "b", - "--nosuffix", "--separator", ":", "--options", - "vg_name,vg_size,vg_extent_size,vg_free" - ] - for line in lvmCapture(*args): - try: - (vg, size, pesize, free) = line - size = long(math.floor(long(size) / (1024 * 1024))) - pesize = long(pesize)/1024 - free = math.floor(long(free) / (1024 * 1024)) - except: - continue - log.info("vg %s, size is %s, pesize is %s" %(vg, size, pesize)) - vgs.append( (vg, size, pesize, free) ) - return vgs - -def partialvgs(): - global lvmDevicePresent - if lvmDevicePresent == 0: - return [] - - vgs = [] - args = ["vgdisplay", "-C", "-P", "--noheadings", "--units", "b", - "--nosuffix", "--separator", ":"] - for line in lvmCapture(*args): - try: - (vg, numpv, numlv, numsn, attr, size, free) = line - except: - continue - if attr.find("p") != -1: - log.info("vg %s, attr is %s" %(vg, attr)) - vgs.append(vg) - - return vgs - -# FIXME: this is a hack. we really need to have a --force option. -def unlinkConf(): - lvmroot = "/etc/lvm" - if os.path.exists("%s/lvm.conf" %(lvmroot,)): - os.unlink("%s/lvm.conf" %(lvmroot,)) - -def writeForceConf(): - """Write out an /etc/lvm/lvm.conf that doesn't do much (any?) filtering""" - - lvmroot = "/etc/lvm" - try: - os.unlink("/etc/lvm/.cache") - except: - pass - if not os.path.isdir(lvmroot): - os.mkdir(lvmroot) - - unlinkConf() - - f = open("%s/lvm.conf" %(lvmroot,), "w+") - f.write(""" -# anaconda hacked lvm.conf to avoid filtering breaking things -devices { - sysfs_scan = 0 - md_component_detection = 1 -} -""") - -# FIXME: another hack. we need to wipe the raid metadata since pvcreate -# doesn't -def wipeOtherMetadataFromPV(node): - try: - isys.wipeRaidSB(node) - except Exception, e: - log.critical("error wiping raidsb from %s: %s", node, e) - - - -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 clampLVSizeRequest(size, pe, roundup=0): - """Given a size and a PE, returns the actual size of logical volumne. - - size - size (in MB) of logical volume request - pe - PE size (in KB) - roundup - round sizes up or not - """ - - if roundup: - func = math.ceil - else: - func = math.floor - return (long(func((size*1024L)/pe))*pe)/1024 - -def clampPVSize(pvsize, pesize): - """Given a PV size and a PE, returns the usable space of the PV. - Takes into account both overhead of the physical volume and 'clamping' - to the PE size. - - pvsize - size (in MB) of PV request - pesize - PE size (in KB) - """ - - # we want Kbytes as a float for our math - pvsize *= 1024.0 - return long((math.floor(pvsize / pesize) * pesize) / 1024) - -def getMaxLVSize(pe): - """Given a PE size in KB, returns maximum size (in MB) of a logical volume. - - pe - PE size in KB - """ - - if os.uname()[2][:4]=="2.4.": - return pe*64 #2.4 kernel - LVM1, max size is 2TiB and depends on extent size/count - - else: #newer kernel - LVM2, size limited by number of sectors - if productArch in ("x86_64", "ppc64"): #64bit architectures - return (8*1024*1024*1024*1024) #Max is 8EiB (very large number..) - else: - return (16*1024*1024) #Max is 16TiB - -def safeLvmName(str): - tmp = string.strip(str) - tmp = tmp.replace("/", "_") - tmp = re.sub("[^0-9a-zA-Z._]", "", str) - tmp = tmp.lstrip("_") - - return tmp - -def createSuggestedVGName(partitions, network): - """Given list of partition requests, come up with a reasonable VG name - - partitions - list of requests - """ - - # try to create a volume group name incorporating the hostname - hn = network.hostname - 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 not partitions.isVolumeGroupNameInUse(vgtemplate): - return vgtemplate - else: - i = 0 - while 1: - tmpname = "%s%02d" % (vgtemplate, i,) - if not partitions.isVolumeGroupNameInUse(tmpname): - break - - i += 1 - if i > 99: - tmpname = "" - - return tmpname - -def createSuggestedLVName(logreqs): - """Given list of LV requests, come up with a reasonable LV name - - partitions - list of LV requests for this VG - """ - - i = 0 - - lnames = [] - for lv in logreqs: - lnames.append(lv.logicalVolumeName) - - while 1: - tmpname = "LogVol%02d" % (i,) - if (logreqs is None) or (tmpname not in lnames): - break - - i += 1 - if i > 99: - tmpname = "" - - return tmpname - -def getVGUsedSpace(vgreq, requests, diskset): - vgused = 0 - for request in requests.requests: - if request.type == REQUEST_LV and request.volumeGroup == vgreq.uniqueID: - size = int(request.getActualSize(requests, diskset)) - vgused = vgused + size - - - return vgused - -def getVGFreeSpace(vgreq, requests, diskset): - used = getVGUsedSpace(vgreq, requests, diskset) - log.debug("used space is %s" % (used,)) - - total = vgreq.getActualSize(requests, diskset) - log.debug("actual space is %s" % (total,)) - return total - used diff --git a/network.py b/network.py index af6d858f7..5dc9060f0 100644 --- a/network.py +++ b/network.py @@ -581,13 +581,17 @@ class Network: f.write("MTU=%s\n" % dev.get('MTU')) # tell NetworkManager not to touch any interfaces used during - # installation when / is on a network device. Ideally we would only - # tell NM not to touch the interface(s) actually used for /, but we - # have no logic to determine that + # installation when / is on a network backed device. if anaconda is not None: - rootdev = anaconda.id.fsset.getEntryByMountPoint("/").device - if rootdev.isNetdev(): - f.write("NM_CONTROLLED=no\n") + import storage + rootdev = anaconda.id.storage.fsset.rootDevice + # FIXME: use device.host_address to only add "NM_CONTROLLED=no" + # for interfaces actually used enroute to the device + for d in anaconda.id.storage.devices: + if rootdev.dependsOn(d) and isinstance(d, + storage.devices.NetworkStorageDevice): + f.write("NM_CONTROLLED=no\n") + break f.close() os.chmod(newifcfg, 0644) diff --git a/packages.py b/packages.py index 12d98b76a..03e21b324 100644 --- a/packages.py +++ b/packages.py @@ -31,14 +31,13 @@ import time import sys import string import language -import fsset -import lvm import shutil import traceback from flags import flags from product import * from constants import * from upgrade import bindMountDevDirectory +from storage.errors import * import logging log = logging.getLogger("anaconda") @@ -80,7 +79,8 @@ def copyAnacondaLogs(anaconda): log.info("Copying anaconda logs") for (fn, dest) in (("/tmp/anaconda.log", "anaconda.log"), ("/tmp/syslog", "anaconda.syslog"), - ("/tmp/X.log", "anaconda.xlog")): + ("/tmp/X.log", "anaconda.xlog"), + ("/tmp/storage.log", "storage.log")): if os.access(fn, os.R_OK): try: shutil.copyfile(fn, "%s/var/log/%s" %(anaconda.rootPath, dest)) @@ -88,98 +88,123 @@ def copyAnacondaLogs(anaconda): except: pass -def doMigrateFilesystems(anaconda): - if anaconda.dir == DISPATCH_BACK: - return DISPATCH_NOOP - - if anaconda.id.fsset.haveMigratedFilesystems(): - return DISPATCH_NOOP - - anaconda.id.fsset.migrateFilesystems (anaconda) - - if anaconda.id.upgrade: - # if we're upgrading, we may need to do lvm device node hackery - anaconda.id.fsset.makeLVMNodes(anaconda.rootPath, trylvm1 = 1) - # and we should write out a new fstab with the migrated fstype - shutil.copyfile("%s/etc/fstab" % anaconda.rootPath, "%s/etc/fstab.anaconda" % anaconda.rootPath) - anaconda.id.fsset.write(anaconda.rootPath) - # and make sure /dev is mounted so we can read the bootloader - bindMountDevDirectory(anaconda.rootPath) - def turnOnFilesystems(anaconda): - def handleResizeError(e, dev): - if os.path.exists("/tmp/resize.out"): - details = open("/tmp/resize.out", "r").read() - else: - details = "%s" %(e,) - anaconda.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 anaconda.dir == DISPATCH_BACK: - log.info("unmounting filesystems") - anaconda.id.fsset.umountFilesystems(anaconda.rootPath) - return + if not anaconda.id.upgrade: + log.info("unmounting filesystems") + anaconda.id.storage.fsset.umountFilesystems(anaconda.rootPath) + return DISPATCH_NOOP if flags.setupFilesystems: if not anaconda.id.upgrade: - if not anaconda.id.fsset.isActive(): + if not anaconda.id.storage.fsset.active: # 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) - anaconda.id.partitions.doMetaDeletes(anaconda.id.diskset) - anaconda.id.fsset.setActive(anaconda.id.diskset, anaconda.id.partitions.requests) - try: - anaconda.id.fsset.shrinkFilesystems(anaconda.id.diskset, anaconda.rootPath) - except fsset.ResizeError, (e, dev): - handleResizeError(e, dev) - - if not anaconda.id.fsset.isActive(): - anaconda.id.diskset.savePartitions () - # this is somewhat lame, but we seem to be racing with - # device node creation sometimes. so wait for device nodes - # to settle - time.sleep(1) - w = anaconda.intf.waitWindow(_("Activating"), _("Activating new partitions. Please wait...")) - rc = iutil.execWithRedirect("/sbin/udevadm", [ "settle" ], - stdout = "/dev/tty5", - stderr = "/dev/tty5", - searchPath = 1) - w.pop() - - anaconda.id.partitions.doEncryptionRetrofits() - - try: - anaconda.id.partitions.doMetaResizes(anaconda.id.diskset) - except lvm.LVResizeError, e: - handleResizeError("%s" %(e,), "%s/%s" %(e.vgname, e.lvname)) - try: - anaconda.id.fsset.growFilesystems(anaconda.id.diskset, anaconda.rootPath) - except fsset.ResizeError, (e, dev): - handleResizeError(e, dev) - - if not anaconda.id.fsset.volumesCreated: - try: - anaconda.id.fsset.createLogicalVolumes(anaconda.rootPath) - except SystemError, e: - log.error("createLogicalVolumes failed with %s", str(e)) - anaconda.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) - - anaconda.id.fsset.formatSwap(anaconda.rootPath) - anaconda.id.fsset.turnOnSwap(anaconda.rootPath) - anaconda.id.fsset.makeFilesystems(anaconda.rootPath, - anaconda.backend.skipFormatRoot) - anaconda.id.fsset.mountFilesystems(anaconda,0,0, - anaconda.backend.skipFormatRoot) + anaconda.id.storage.devicetree.teardownAll() + + upgrade_migrate = False + if anaconda.id.upgrade: + for d in anaconda.id.storage.fsset.migratableDevices: + if d.format.migrate: + upgrade_migrate = True + + try: + anaconda.id.storage.doIt() + except Exception: + # better to get ful exceptions for debugging + raise + except DeviceResizeError as (msg, device): + # XXX does this make any sense? do we support resize of + # devices other than partitions? + anaconda.intf.detailedMessageWindow(_("Device Resize Failed"), + _("An error was encountered while " + "resizing device %s.") % (device,), + msg, + type = "custom", + custom_buttons = [_("_Exit installer")]) + sys.exit(1) + except DeviceCreateError as (msg, device): + anaconda.intf.detailedMessageWindow(_("Device Creation Failed"), + _("An error was encountered while " + "creating device %s.") % (device,), + msg, + type = "custom", + custom_buttons = [_("_Exit installer")]) + sys.exit(1) + except DeviceDestroyError as (msg, device): + anaconda.intf.detailedMessageWindow(_("Device Removal Failed"), + _("An error was encountered while " + "removing device %s.") % (device,), + msg, + type = "custom", + custom_buttons = [_("_Exit installer")]) + sys.exit(1) + except DeviceError as (msg, device): + anaconda.intf.detailedMessageWindow(_("Device Setup Failed"), + _("An error was encountered while " + "setting up device %s.") % (device,), + msg, + type = "custom", + custom_buttons = [_("_Exit installer")]) + sys.exit(1) + except FSResizeError as (msg, device): + if os.path.exists("/tmp/resize.out"): + details = open("/tmp/resize.out", "r").read() + else: + details = "%s" %(msg,) + anaconda.intf.detailedMessageWindow(_("Resizing Failed"), + _("There was an error encountered while " + "resizing the device %s.") %(device,), + details, + type = "custom", + custom_buttons = [_("_Exit installer")]) + sys.exit(1) + except FSMigrateError as (msg, device): + anaconda.intf.detailedMessageWindow(_("Migration Failed"), + _("An error was encountered while " + "migrating filesystem on device %s.") + % (device,), + msg, + type = "custom", + custom_buttons = [_("_Exit installer")]) + sys.exit(1) + except FormatCreateError as (msg, device): + anaconda.intf.detailedMessageWindow(_("Formatting Failed"), + _("An error was encountered while " + "formatting device %s.") % (device,), + msg, + type = "custom", + custom_buttons = [_("_Exit installer")]) + sys.exit(1) + except Exception as msg: + # catch-all + anaconda.intf.detailedMessageWindow(_("Storage Activation Failed"), + _("An error was encountered while " + "activating your storage configuration."), + msg, + type = "custom", + custom_buttons = [_("_Exit installer")]) + sys.exit(1) + + if not anaconda.id.upgrade: + anaconda.id.storage.fsset.turnOnSwap(anaconda.intf) + anaconda.id.storage.fsset.mountFilesystems(anaconda, + raiseErrors=False, + readOnly=False, + skipRoot=anaconda.backend.skipFormatRoot) + else: + if upgrade_migrate: + # we should write out a new fstab with the migrated fstype + shutil.copyfile("%s/etc/fstab" % anaconda.rootPath, + "%s/etc/fstab.anaconda" % anaconda.rootPath) + anaconda.id.storage.fsset.write(anaconda.rootPath) + + # and make sure /dev is mounted so we can read the bootloader + bindMountDevDirectory(anaconda.rootPath) + def setupTimezone(anaconda): # we don't need this on an upgrade or going backwards @@ -216,8 +241,6 @@ def setupTimezone(anaconda): # FIXME: this is a huge gross hack. hard coded list of files # created by anaconda so that we can not be killed by selinux def setFileCons(anaconda): - import partRequests - if flags.selinux: log.info("setting SELinux contexts for anaconda created files") @@ -234,10 +257,7 @@ def setFileCons(anaconda): "/etc/shadow", "/etc/shadow-", "/etc/gshadow"] + \ glob.glob('/etc/dhclient-*.conf') - vgs = [] - for entry in anaconda.id.partitions.requests: - if isinstance(entry, partRequests.VolumeGroupRequestSpec): - vgs.append("/dev/%s" %(entry.volumeGroupName,)) + vgs = ["/dev/%s" % vg.name for vg in anaconda.id.storage.vgs] # ugh, this is ugly for dir in ["/etc/sysconfig/network-scripts", "/var/lib/rpm", "/etc/lvm", "/dev/mapper", "/etc/iscsi", "/var/lib/iscsi", "/root", "/var/log", "/etc/modprobe.d", "/etc/sysconfig" ] + vgs: diff --git a/partIntfHelpers.py b/partIntfHelpers.py index 13c87220d..ed9dfe5c0 100644 --- a/partIntfHelpers.py +++ b/partIntfHelpers.py @@ -26,11 +26,9 @@ import string from constants import * -import partedUtils import parted -import fsset import iutil -import partRequests +from storage.formats import getFormat import gettext _ = lambda x: gettext.ldgettext("anaconda", x) @@ -106,331 +104,98 @@ def sanityCheckMountPoint(mntpt, fstype, preexisting, format): else: return None else: - if (fstype and fstype.isMountable() and (not preexisting or format)): + if (fstype and fstype.mountable and (not preexisting or format)): return _("Please specify a mount point for this partition.") else: # its an existing partition so don't force a mount point return None -def isNotChangable(request, requestlist): - if request: - if requestlist.isRaidMember(request): - parentreq = requestlist.getRaidMemberParent(request) - if parentreq.raidminor is not None: - return _("This partition is part of " - "the RAID device /dev/md%s.") % (parentreq.raidminor,) - else: - return _("This partition is part of a RAID device.") - - if requestlist.isLVMVolumeGroupMember(request): - parentreq = requestlist.getLVMVolumeGroupMemberParent(request) - if parentreq.volumeGroupName is not None: - return _("This partition is part of the " - "LVM volume group '%s'.") % (parentreq.volumeGroupName,) - else: - return _("This partition is part of a LVM volume group.") - - return None - - -def doDeletePartitionByRequest(intf, requestlist, partition, - confirm=1, quiet=0): +def doDeleteDevice(intf, storage, device, confirm=1, quiet=0): """Delete a partition from the request list. intf is the interface - requestlist is the list of requests - partition is either the part object or the uniqueID if not a part + storage is the storage instance + device is the device to delete """ - - if partition == None: + if not device: intf.messageWindow(_("Unable To Delete"), _("You must first select a partition to delete."), custom_icon="error") - return 0 + return False - if type(partition) == type("RAID"): - device = partition - elif partition.type & parted.PARTITION_FREESPACE: - intf.messageWindow(_("Unable To Delete"), - _("You cannot delete free space."), - custom_icon="error") - return 0 - elif partition.type & parted.PARTITION_PROTECTED: - # LDL formatted DASDs always have one partition, you'd have to reformat the - # DASD in CDL mode to get rid of it + reason = storage.deviceImmutable(device) + if reason: intf.messageWindow(_("Unable To Delete"), - _("You cannot delete a partition of a LDL formatted DASD."), - custom_icon="error") - return 0 - else: - device = partition.getDeviceNodeName() - - ret = requestlist.containsImmutablePart(partition) - if ret: - if not quiet: - intf.messageWindow(_("Unable To Delete"), - _("You cannot delete this " - "partition, as it is an extended partition " - "which contains %s") %(ret), - custom_icon="error") - return 0 - - # see if device is in our partition requests, remove - if type(partition) == type("RAID"): - request = requestlist.getRequestByID(device) - else: - request = requestlist.getRequestByDeviceName(device) - - if request: - state = isNotChangable(request, requestlist) - - # If the partition is protected, we also can't delete it so specify a - # reason why. - if state is None and request.getProtected(): - state = _("This partition is holding the data for the hard " - "drive install.") - - if state: - if not quiet: - intf.messageWindow(_("Unable To Delete"), - _("You cannot delete this partition:\n\n") + state, - custom_icon="error") - return 0 - - if confirm and not confirmDeleteRequest(intf, request): - return 0 - - if request.getPreExisting(): - if isinstance(request, partRequests.PartitionSpec): - # get the drive - drive = partedUtils.get_partition_drive(partition) - - if partition.type & parted.PARTITION_EXTENDED: - requestlist.deleteAllLogicalPartitions(partition) - - delete = partRequests.DeleteSpec(drive, - partition.geometry.start, - partition.geometry.end) - requestlist.addDelete(delete) - elif isinstance(request, partRequests.LogicalVolumeRequestSpec): - vgreq = requestlist.getRequestByID(request.volumeGroup) - delete = partRequests.DeleteLogicalVolumeSpec(request.logicalVolumeName, - vgreq.volumeGroupName) - requestlist.addDelete(delete) - elif isinstance(request, partRequests.VolumeGroupRequestSpec): - delete = partRequests.DeleteVolumeGroupSpec(request.volumeGroupName) - requestlist.addDelete(delete) - # FIXME: do we need to do anything with preexisting raids? - - # now remove the request - requestlist.deleteDependentRequests(request) - requestlist.removeRequest(request) - else: # is this a extended partition we made? - if partition.type & parted.PARTITION_EXTENDED: - requestlist.deleteAllLogicalPartitions(partition) - else: - #raise ValueError, "Deleting a non-existent partition" - return 0 + reason, + custom_icon="error") + return False + + if confirm and not confirmDelete(intf, device): + return False - del partition - return 1 + for dep in storage.deviceDeps(device): + storage.destroyDevice(dep) -def doDeletePartitionsByDevice(intf, requestlist, diskset, device, - confirm=1, quiet=0): - """ Remove all partitions currently on device """ + storage.destroyDevice(device) + return True + +def doDeleteDependentDevices(intf, storage, device, confirm=1, quiet=0): + """ Remove all devices/partitions currently on device """ if confirm: rc = intf.messageWindow(_("Confirm Delete"), _("You are about to delete all partitions on " - "the device '/dev/%s'.") % (device,), + "the device '%s'.") % (device.path,), type="custom", custom_icon="warning", custom_buttons=[_("Cancel"), _("_Delete")]) if not rc: - return - - requests = requestlist.getRequestsByDevice(diskset, device) - if not requests: - return - - # get list of unique IDs of these requests - reqIDs = set() - reqparts = {} - - for req in requests: - for drive in req.drive: - part = diskset.disks[drive].getPartitionByPath("/dev/%s" % req.device) - - if part.type & parted.PARTITION_FREESPACE or \ - part.type & parted.PARTITION_METADATA or \ - part.type & parted.PARTITION_PROTECTED: - continue - - reqIDs.add(req.uniqueID) - - if reqparts.has_key(req.uniqueID): - reqparts[req.uniqueID].append(part) - else: - reqparts[req.uniqueID] = [ part ] - - reqIDs = list(reqIDs) - - # now go thru and try to delete the unique IDs - for id in reqIDs: - try: - req = requestlist.getRequestByID(id) - if req is None: - continue - for partlist in reqparts[id]: - for part in partlist: - rc = doDeletePartitionByRequest(intf, requestlist, part, - confirm=0, quiet=1) - if not rc: - pass - except: - pass - - # see which partitions are left - notdeleted = [] - left_requests = requestlist.getRequestsByDevice(diskset, device) - if left_requests: - # get list of unique IDs of these requests - leftIDs = set() - - for req in left_requests: - for drive in req.drive: - part = diskset.disks[drive].getPartitionByPath("/dev/%s" % req.device) - - if part.type & parted.PARTITION_FREESPACE or \ - part.type & parted.PARTITION_METADATA or \ - part.type & parted.PARTITION_PROTECTED: - continue - - leftIDs.add(req.uniqueID) - - leftIDs = list(leftIDs) - - for id in leftIDs: - req = requestlist.getRequestByID(id) - notdeleted.append(req) - - # see if we need to report any failures - some were because we removed - # an extended partition which contained other members of our delete list - outlist = "" - for req in notdeleted: - newreq = requestlist.getRequestByID(req.uniqueID) - if newreq: - outlist = outlist + "\t/dev/%s\n" % (newreq.device,) - - if outlist != "" and not quiet: - intf.messageWindow(_("Notice"), - _("The following partitions were not deleted " - "because they are in use:\n\n%s") % outlist, - custom_icon="warning") - - return 1 + return False + deps = storage.deviceDeps(device) + if not deps: + # nothing to do + return False -def doEditPartitionByRequest(intf, requestlist, part): - """Edit a partition from the request list. + immutable = [] + for dep in deps: + if storage.deviceImmutable(dep): + immutable.append(dep.path) + continue + else: + storage.destroyDevice(dep) - intf is the interface - requestlist is the list of requests - partition is either the part object or the uniqueID if not a part - """ - - if part == None: - intf.messageWindow(_("Unable To Edit"), - _("You must select a partition to edit"), custom_icon="error") - - return (None, None) - - if type(part) == type("RAID"): - - # see if device is in our partition requests, remove - request = requestlist.getRequestByID(int(part)) - - if request: - state = isNotChangable(request, requestlist) - if state is not None: - intf.messageWindow(_("Unable To Edit"), _("You cannot edit this partition:\n\n") + state, - custom_icon="error") - return (None, None) - - if request.type == REQUEST_RAID: - return ("RAID", request) - elif request.type == REQUEST_VG: - return ("LVMVG", request) - elif request.type == REQUEST_LV: - return ("LVMLV", request) - else: - return (None, None) - elif part.type & parted.PARTITION_FREESPACE: - request = partRequests.PartitionSpec(fsset.fileSystemTypeGetDefault(), - start = part.geometry.device.startSectorToCylinder(part.geometry.start), - end = part.geometry.device.endSectorToCylinder(part.geometry.end), - drive = [ partedUtils.get_partition_drive(part) ]) - - return ("NEW", request) - elif part.type & parted.PARTITION_EXTENDED: - return (None, None) - - ret = requestlist.containsImmutablePart(part) - if ret: - intf.messageWindow(_("Unable To Edit"), - _("You cannot edit this " - "partition, as it is an extended partition " - "which contains %s") %(ret), custom_icon="error") - return (None, None) - - name = part.getDeviceNodeName() - request = requestlist.getRequestByDeviceName(name) - if request: - state = isNotChangable(request, requestlist) - if state is not None: - intf.messageWindow(_("Unable To Edit"), - _("You cannot edit this partition:\n\n") + state, custom_icon="error") - return (None, None) - - return ("PARTITION", request) - else: # shouldn't ever happen - raise ValueError, ("Trying to edit non-existent partition %s" - % (part.getDeviceNodeName(),)) + if immutable and not quiet: + remaining = "\t" + "\n\t".join(immutable) + "\n" + intf.messageWindow(_("Notice"), + _("The following partitions were not deleted " + "because they are in use:\n\n%s") % remaining, + custom_icon="warning") + return True def checkForSwapNoMatch(anaconda): """Check for any partitions of type 0x82 which don't have a swap fs.""" - diskset = anaconda.id.diskset - - for request in anaconda.id.partitions.requests: - if not hasattr(request, "drive") or not request.fstype: + for device in anaconda.id.storage.partitions: + if not device.exists: + # this is only for existing partitions continue - for drive in request.drive: - part = diskset.disks[drive].getPartitionByPath("/dev/%s" % request.device) - - if (part and (not part.type & parted.PARTITION_FREESPACE) - and (part.getFlag(parted.PARTITION_SWAP)) - and (request.fstype and request.fstype.getName() != "swap") - and (not request.format)): - rc = anaconda.intf.messageWindow(_("Format as Swap?"), - _("/dev/%s has a partition type of 0x82 " - "(Linux swap) but does not appear to " - "be formatted as a Linux swap " - "partition.\n\n" - "Would you like to format this " - "partition as a swap partition?") - % (request.device), type = "yesno", - custom_icon="question") - if rc == 1: - request.format = 1 - request.fstype = fsset.fileSystemTypeGet("swap") - if request.fstype.getName() == "software RAID": - part.setFlag(parted.PARTITION_RAID) - else: - part.unsetFlag(parted.PARTITION_RAID) - - partedUtils.set_partition_file_system_type(part, - request.fstype) + if device.getFlag(parted.PARTITION_SWAP) and \ + not device.format.type == "swap": + rc = anaconda.intf.messageWindow(_("Format as Swap?"), + _("%s has a partition type of 0x82 " + "(Linux swap) but does not appear to " + "be formatted as a Linux swap " + "partition.\n\n" + "Would you like to format this " + "partition as a swap partition?") + % device.path, type = "yesno", + custom_icon="question") + if rc == 1: + format = getFormat("swap", device=device.path) + anaconda.id.storage.formatDevice(device, format) + + return def mustHaveSelectedDrive(intf): txt =_("You need to select at least one hard drive to install %s.") % (productName,) @@ -498,63 +263,42 @@ def partitionPreExistFormatWarnings(intf, warnings): type="yesno", custom_icon="warning") return rc -def getPreExistFormatWarnings(partitions, diskset): - """Return a list of preexisting partitions being formatted.""" - - devs = [] - for request in partitions.requests: - if request.getPreExisting() == 1: - devs.append(request.uniqueID) +def getPreExistFormatWarnings(storage): + """Return a list of preexisting devices being formatted.""" + devices = [] + for device in storage.devicetree.devices.values(): + if device.exists and not device.format.exists: + devices.append(device) - devs.sort() - + devices.sort(key=lambda d: d.name) rc = [] - for dev in devs: - request = partitions.getRequestByID(dev) - if request.format: - if request.fstype.isMountable(): - mntpt = request.mountpoint - else: - mntpt = "" - - if isinstance(request, partRequests.PartitionSpec): - dev = request.device - elif isinstance(request, partRequests.RaidRequestSpec): - dev = "md%s" %(request.raidminor,) - elif isinstance(request, partRequests.VolumeGroupRequestSpec): - dev = request.volumeGroupName - elif isinstance(request, partRequests.LogicalVolumeRequestSpec): - vgreq = partitions.getRequestByID(request.volumeGroup) - dev = "%s/%s" %(vgreq.volumeGroupName, - request.logicalVolumeName) - - rc.append((dev, request.fstype.getName(), mntpt)) - - if len(rc) == 0: - return None - else: - return rc + for device in devices: + rc.append((device.path, + device.format.type, + getattr(device.format, "mountpoint", ""))) + return rc -def confirmDeleteRequest(intf, request): - """Confirm the deletion of a request.""" - if not request: +def confirmDelete(intf, device): + """Confirm the deletion of a device.""" + if not device: return - if request.type == REQUEST_VG: + if device.type == "lvmvg": errmsg = (_("You are about to delete the volume group \"%s\"." "\n\nALL logical volumes in this volume group " - "will be lost!") % (request.volumeGroupName,)) - elif request.type == REQUEST_LV: + "will be lost!") % device.name) + elif device.type == "lvmlv": errmsg = (_("You are about to delete the logical volume \"%s\".") - % (request.logicalVolumeName,)) - elif request.type == REQUEST_RAID: + % device.name) + elif device.type == "mdarray": errmsg = _("You are about to delete a RAID device.") + elif device.type == "partition": + errmsg = (_("You are about to delete the %s partition.") + % device.path) else: - if request.device: - errmsg = _("You are about to delete the /dev/%s partition.") % (request.device,) - else: - # XXX can this ever happen? - errmsg = _("The partition you selected will be deleted.") + # we may want something a little bit prettier than device.type + errmsg = (_("You are about to delete the %s %s") % (device.type, + device.name)) rc = intf.messageWindow(_("Confirm Delete"), errmsg, type="custom", custom_buttons=[_("Cancel"), _("_Delete")], diff --git a/partRequests.py b/partRequests.py deleted file mode 100644 index 6f95dc4f7..000000000 --- a/partRequests.py +++ /dev/null @@ -1,1057 +0,0 @@ -# -# partRequests.py: partition request objects and management thereof -# -# Copyright (C) 2002, 2003, 2004, 2005, 2006, 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 <http://www.gnu.org/licenses/>. -# -# Author(s): Matt Wilson <msw@redhat.com> -# Jeremy Katz <katzj@redhat.com> -# Mike Fulbright <msf@redhat.com> -# Harald Hoyer <harald@redhat.de> -# - -"""Partition request objects and management thereof.""" - -import parted -import iutil -import string -import os, sys, math - -from constants import * -from flags import * - -import gettext -_ = lambda x: gettext.ldgettext("anaconda", x) - -import fsset -import raid -import lvm -import partedUtils -import partIntfHelpers - -import logging -log = logging.getLogger("anaconda") - -class DeleteSpec: - """Defines a preexisting partition which is intended to be removed.""" - - def __init__(self, drive, start, end): - """Initializes a DeleteSpec. - - drive is the text form of the drive - start is the start sector of the deleted partition - end is the end sector of the deleted partition - """ - - self.drive = drive - self.start = start - self.end = end - - def __str__(self): - return "drive: %s start: %s end: %s" %(self.drive, self.start, - self.end) - -class DeleteLogicalVolumeSpec: - """Defines a preexisting logical volume which is intended to be removed.""" - - def __init__(self, name, vg): - """Initializes a DeleteLogicalVolumeSpec. - - name is the name of the lv - vg is the name of the volume group - """ - - self.name = name - self.vg = vg - self.deleted = 0 - - def __str__(self): - return "lvname: %s vgname: %s" %(self.name, self.vg) - - def beenDeleted(self): - return self.deleted - - def setDeleted(self, val): - self.deleted = val - -class DeleteVolumeGroupSpec: - """Defines a preexisting volume group which is intended to be removed.""" - - def __init__(self, name): - """Initializes a DeleteVolumeGroupSpec - - name is the name of the volume group - """ - - self.name = name - self.deleted = 0 - - def __str__(self): - return "vgname: %s" %(self.name,) - - def beenDeleted(self): - return self.deleted - - def setDeleted(self, val): - self.deleted = val - -class DeleteRAIDSpec: - """Defines a preexisting RAID device which is intended to be removed.""" - - def __init__(self, minor): - """Initializes a DeleteRAIDSpec. - - minor is the minor of the RAID device being removed - """ - - self.minor = minor - - def __str__(self): - return "minor: %s" %(self.minor,) - -class RequestSpec: - """Generic Request specification.""" - def __init__(self, fstype, size = None, mountpoint = None, format = None, - preexist = 0, fslabel = None, - migrate = None, origfstype = None, - fsprofile = None): - """Create a generic RequestSpec. - - This should probably never be externally used. - """ - - self.fstype = fstype - self.mountpoint = mountpoint - self.size = size - self.format = format - - self.migrate = migrate - self.origfstype = origfstype - self.fslabel = fslabel - self.fsopts = None - self.fsprofile = fsprofile - - self.device = None - """what we currently think the device is""" - - self.uniqueID = None - """uniqueID is an integer and *MUST* be unique.""" - - self.ignoreBootConstraints = 0 - """Booting constraints should be ignored for this request.""" - - self.preexist = preexist - """Did this partition exist before we started playing with things?""" - - self.protected = 0 - """Is this partitiion 'protected', ie does it contain install media.""" - - self.dev = None - """A Device() as defined in fsset.py to correspond to this request.""" - - self.encryption = None - """An optional LUKSDevice() describing block device encryption.""" - - self.targetSize = None - """Size to resize to""" - - self.resizable = False - """Is this a request that can be resized?""" - - def __str__(self): - if self.fstype: - fsname = self.fstype.getName() - else: - fsname = "None" - - str = ("Generic Request -- mountpoint: %(mount)s uniqueID: %(id)s\n" - " type: %(fstype)s format: %(format)s\n" - " device: %(dev)s migrate: %(migrate)s fslabel: %(fslabel)s\n" - " options: '%(fsopts)s'" - " fsprofile: %(fsprofile)s" % - {"mount": self.mountpoint, "id": self.uniqueID, - "fstype": fsname, "format": self.format, - "dev": self.device, "migrate": self.migrate, - "fslabel": self.fslabel, - "fsopts": self.fsopts, "fsprofile": self.fsprofile}) - return str - - def getActualSize(self, partitions, diskset): - """Return the actual size allocated for the request in megabytes.""" - - sys.stderr.write("WARNING: Abstract RequestSpec.getActualSize() called\n") - import traceback - traceback.print_stack() - - def getDevice(self, partitions): - """Return a device to solidify.""" - - sys.stderr.write("WARNING: Abstract RequestSpec.getDevice() called\n") - import traceback - traceback.print_stack() - - def isResizable(self, partitions): - if self.isEncrypted(partitions): # FIXME: can't resize crypted devs yet - return False - return self.resizable and self.fstype is not None and self.fstype.isResizable() - - def isEncrypted(self, partitions, parentOnly = False): - if self.encryption: - return True - return False - - def toEntry(self, partitions): - """Turn a request into a fsset entry and return the entry.""" - device = self.getDevice(partitions) - - # pin down our partitions so that we can reread the table - device.solidify() - - if self.fstype.getName() == "swap": - mountpoint = "swap" - else: - mountpoint = self.mountpoint - - entry = fsset.FileSystemSetEntry(device, mountpoint, self.fstype, - origfsystem=self.origfstype, - options=self.fsopts, - fsprofile=self.fsprofile) - if self.format: - entry.setFormat(self.format) - - if self.migrate: - entry.setMigrate(self.migrate) - - if self.fslabel: - entry.setLabel(self.fslabel) - - if self.targetSize and self.fstype.isResizable(): - entry.setResizeTarget(self.targetSize, self.size) - - return entry - - def setProtected(self, val): - """Set the protected value for this partition.""" - self.protected = val - - def getProtected(self): - """Return the protected value for this partition.""" - return self.protected - - def getPreExisting(self): - """Return whether the partition existed before we started playing.""" - return self.preexist - - def doMountPointLinuxFSChecks(self): - """Return an error string if the mountpoint is not valid for Linux FS.""" - mustbeonroot = ('/bin','/dev','/sbin','/etc','/lib','/root', - '/mnt', 'lost+found', '/proc') - mustbeonlinuxfs = ('/', '/boot', '/var', '/tmp', '/usr', '/home', - '/usr/share', '/usr/lib' ) - - # these are symlinks so you cant make them mount points - otherexcept = ('/var/mail', '/usr/bin/X11', '/usr/lib/X11', '/usr/tmp') - - if not self.mountpoint: - return None - - 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 ["ext4", "ext3", "ext2"]: - return _("The mount point %s must be formatted to match the live " - "rootfs 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 " - "be on the / file system.") % (self.mountpoint,) - elif self.mountpoint in otherexcept: - return _("The mount point %s cannot be used. It must " - "be a symbolic link for proper system " - "operation. Please select a different " - "mount point.") % (self.mountpoint,) - - return None - - if not self.fstype.isLinuxNativeFS(): - if self.mountpoint in mustbeonlinuxfs: - return _("This mount point must be on a linux file system.") - - return None - - # requestSkipList is a list of uids for requests to ignore when - # looking for a conflict on the mount point name. Used in lvm - # editting code in disk druid, for example. - def isMountPointInUse(self, partitions, requestSkipList=None): - """Return whether my mountpoint is in use by another request.""" - mntpt = self.mountpoint - if not mntpt: - return None - - if partitions and partitions.requests: - for request in partitions.requests: - if requestSkipList is not None and request.uniqueID in requestSkipList: - continue - - if request.mountpoint == mntpt: - if (not self.uniqueID or - request.uniqueID != self.uniqueID): - return _("The mount point \"%s\" is already in use, " - "please choose a different mount point." - %(mntpt)) - return None - - def doSizeSanityCheck(self): - """Sanity check that the size of the request is sane.""" - if not self.fstype: - return None - - if not self.format: - return None - - if self.size and self.size > self.fstype.getMaxSizeMB(): - return (_("The size of the %s partition (%10.2f MB) " - "exceeds the maximum size of %10.2f MB.") - % (self.fstype.getName(), self.size, - self.fstype.getMaxSizeMB())) - - return None - - # set skipMntPtExistCheck to non-zero if you want to handle this - # check yourself. Used in lvm volume group editting code, for example. - def sanityCheckRequest(self, partitions, skipMntPtExistCheck=0): - """Run the basic sanity checks on the request.""" - # see if mount point is valid if its a new partition request - mntpt = self.mountpoint - fstype = self.fstype - preexist = self.preexist - format = self.format - - rc = self.doSizeSanityCheck() - if rc: - return rc - - rc = partIntfHelpers.sanityCheckMountPoint(mntpt, fstype, preexist, format) - if rc: - return rc - - if not skipMntPtExistCheck: - rc = self.isMountPointInUse(partitions) - if rc: - return rc - - rc = self.doMountPointLinuxFSChecks() - if rc: - return rc - - return None - - - def formatByDefault(self): - """Return whether or not the request should be formatted by default.""" - def inExceptionList(mntpt): - exceptlist = ['/home', '/usr/local', '/opt', '/var/www'] - for q in exceptlist: - if os.path.commonprefix([mntpt, q]) == q: - return 1 - return 0 - - # check first to see if its a Linux filesystem or not - formatlist = ['/boot', '/var', '/tmp', '/usr'] - - if not self.fstype: - return 0 - - if not self.fstype.isLinuxNativeFS(): - return 0 - - if self.fstype.isMountable(): - mntpt = self.mountpoint - if mntpt == "/": - return 1 - - if mntpt in formatlist: - return 1 - - for p in formatlist: - if os.path.commonprefix([mntpt, p]) == p: - if inExceptionList(mntpt): - return 0 - else: - return 1 - - return 0 - else: - if self.fstype.getName() == "swap": - return 1 - - # be safe for anything else and default to off - return 0 - - -# XXX preexistings store start/end as sectors, new store as cylinders. ICK -class PartitionSpec(RequestSpec): - """Object to define a requested partition.""" - - # XXX eep, still a few too many options but a lot better - def __init__(self, fstype, size = None, mountpoint = None, - preexist = 0, migrate = None, grow = 0, maxSizeMB = None, - start = None, end = None, drive = None, primary = None, - format = None, multidrive = None, - fslabel = None, fsprofile=None): - """Create a new PartitionSpec object. - - fstype is the fsset filesystem type. - size is the requested size (in megabytes). - mountpoint is the mountpoint. - grow is whether or not the partition is growable. - maxSizeMB is the maximum size of the partition in megabytes. - start is the starting cylinder/sector (new/preexist). - end is the ending cylinder/sector (new/preexist). - drive is the drive the partition goes on. - primary is whether or not the partition should be forced as primary. - format is whether or not the partition should be formatted. - preexist is whether this partition is preexisting. - migrate is whether or not the partition should be migrated. - multidrive specifies if this is a request that should be replicated - across _all_ of the drives in drive - fslabel is the label to give to the filesystem. - fsprofile is the usage profile for the filesystem. - """ - - # if it's preexisting, the original fstype should be set - if preexist == 1: - origfs = fstype - else: - origfs = None - - RequestSpec.__init__(self, fstype = fstype, size = size, - mountpoint = mountpoint, format = format, - preexist = preexist, migrate = None, - origfstype = origfs, - fslabel = fslabel, fsprofile = fsprofile) - self.type = REQUEST_NEW - - self.grow = grow - self.maxSizeMB = maxSizeMB - self.requestSize = size - self.start = start - self.end = end - - if (type(drive) != type([])) and (drive is not None): - self.drive = [ drive ] - else: - self.drive = drive - - self.primary = primary - self.multidrive = multidrive - - # should be able to map this from the device =\ - self.currentDrive = None - """Drive that this request will currently end up on.""" - - - def __str__(self): - if self.fstype: - fsname = self.fstype.getName() - else: - fsname = "None" - - if self.origfstype: - oldfs = self.origfstype.getName() - else: - oldfs = "None" - - if self.preexist == 0: - pre = "New" - else: - pre = "Existing" - - if self.encryption is None: - crypto = "None" - else: - crypto = self.encryption.getScheme() - - str = ("%(n)s Part Request -- mountpoint: %(mount)s uniqueID: %(id)s\n" - " type: %(fstype)s format: %(format)s \n" - " device: %(dev)s drive: %(drive)s primary: %(primary)s\n" - " size: %(size)s grow: %(grow)s maxsize: %(max)s\n" - " start: %(start)s end: %(end)s migrate: %(migrate)s " - " fslabel: %(fslabel)s origfstype: %(origfs)s\n" - " options: '%(fsopts)s'\n" - " fsprofile: %(fsprofile)s encryption: %(encryption)s" % - {"n": pre, "mount": self.mountpoint, "id": self.uniqueID, - "fstype": fsname, "format": self.format, "dev": self.device, - "drive": self.drive, "primary": self.primary, - "size": self.size, "grow": self.grow, "max": self.maxSizeMB, - "start": self.start, "end": self.end, - "migrate": self.migrate, "fslabel": self.fslabel, - "origfs": oldfs, - "fsopts": self.fsopts, "fsprofile": self.fsprofile, - "encryption": crypto}) - return str - - - def getDevice(self, partitions): - """Return a device to solidify.""" - if self.dev: - return self.dev - - self.dev = fsset.PartitionDevice(self.device, - encryption = self.encryption) - - return self.dev - - def getActualSize(self, partitions, diskset): - """Return the actual size allocated for the request in megabytes.""" - size = 0 - - for drive in self.drive: - part = diskset.disks[drive].getPartitionByPath("/dev/%s" % self.device) - - if part: - size += part.getSize(unit="MB") - - if size == 0: - return self.requestSize - - return size - - def doSizeSanityCheck(self): - """Sanity check that the size of the partition is sane.""" - if not self.fstype: - return None - if not self.format: - return None - ret = RequestSpec.doSizeSanityCheck(self) - if ret is not None: - return ret - - if (self.size and self.maxSizeMB - and (self.size > self.maxSizeMB)): - return (_("The size of the requested partition (size = %s MB) " - "exceeds the maximum size of %s MB.") - % (self.size, self.maxSizeMB)) - - if self.size and self.size < 0: - return _("The size of the requested partition is " - "negative! (size = %s MB)") % (self.size) - - if self.start and self.start < 1: - return _("Partitions can't start below the first cylinder.") - - if self.end and self.end < 1: - return _("Partitions can't end on a negative cylinder.") - - return None - -class NewPartitionSpec(PartitionSpec): - """Object to define a NEW requested partition.""" - - # XXX eep, still a few too many options but a lot better - def __init__(self, fstype, size = None, mountpoint = None, - grow = 0, maxSizeMB = None, - start = None, end = None, - drive = None, primary = None, format = None): - """Create a new NewPartitionSpec object. - - fstype is the fsset filesystem type. - size is the requested size (in megabytes). - mountpoint is the mountpoint. - grow is whether or not the partition is growable. - maxSizeMB is the maximum size of the partition in megabytes. - start is the starting cylinder. - end is the ending cylinder. - drive is the drive the partition goes on. - primary is whether or not the partition should be forced as primary. - format is whether or not the partition should be formatted. - """ - - PartitionSpec.__init__(self, fstype = fstype, size = size, - mountpoint = mountpoint, grow = grow, - maxSizeMB = maxSizeMB, start = start, - end = end, drive = drive, primary = primary, - format = format, preexist = 0) - self.type = REQUEST_NEW - -class PreexistingPartitionSpec(PartitionSpec): - """Request to represent partitions which already existed.""" - - def __init__(self, fstype, size = None, start = None, end = None, - drive = None, format = None, migrate = None, - mountpoint = None): - """Create a new PreexistingPartitionSpec object. - - fstype is the fsset filesystem type. - size is the size (in megabytes). - start is the starting sector. - end is the ending sector. - drive is the drive which the partition is on. - format is whether or not the partition should be formatted. - migrate is whether or not the partition fs should be migrated. - mountpoint is the mountpoint. - """ - - PartitionSpec.__init__(self, fstype = fstype, size = size, - start = start, end = end, drive = drive, - format = format, migrate = migrate, - mountpoint = mountpoint, preexist = 1) - self.type = REQUEST_PREEXIST - self.resizable = True - - self.maxResizeSize = None - """Maximum size of this partition request""" - - def getMaximumResizeMB(self, partitions): - if self.maxResizeSize is not None: - return self.maxResizeSize - log.warning("%s doesn't have a max size set" %(self.device,)) - return MAX_PART_SIZE - - def getMinimumResizeMB(self, partitions): - return self.fstype.getMinimumSize(self.device) - -class RaidRequestSpec(RequestSpec): - """Request to represent RAID devices.""" - - def __init__(self, fstype, format = None, mountpoint = None, - raidlevel = None, raidmembers = None, - raidspares = None, raidminor = None, fslabel = None, - preexist = 0, chunksize = None, - fsprofile = None): - """Create a new RaidRequestSpec object. - - fstype is the fsset filesystem type. - format is whether or not the partition should be formatted. - mountpoint is the mountpoint. - raidlevel is the raidlevel (as 'RAID0', 'RAID1', 'RAID5'). - chunksize is the chunksize which should be used. - raidmembers is list of ids corresponding to the members of the RAID. - raidspares is the number of spares to setup. - raidminor is the minor of the device which should be used. - fslabel is the label of the filesystem. - fsprofile is the usage profile for the filesystem. - """ - - # if it's preexisting, the original fstype should be set - if preexist == 1: - origfs = fstype - else: - origfs = None - - RequestSpec.__init__(self, fstype = fstype, format = format, - mountpoint = mountpoint, preexist = preexist, - origfstype = origfs, - fslabel=fslabel, fsprofile=fsprofile) - self.type = REQUEST_RAID - - - self.raidlevel = raidlevel - self.raidmembers = raidmembers - self.raidspares = raidspares - self.raidminor = raidminor - self.chunksize = chunksize - - def __str__(self): - if self.fstype: - fsname = self.fstype.getName() - else: - fsname = "None" - raidmem = [] - if self.raidmembers: - for i in self.raidmembers: - raidmem.append(i) - - if self.encryption is None: - crypto = "None" - else: - crypto = self.encryption.getScheme() - - str = ("RAID Request -- mountpoint: %(mount)s uniqueID: %(id)s\n" - " type: %(fstype)s format: %(format)s\n" - " raidlevel: %(level)s raidspares: %(spares)s\n" - " raidmembers: %(members)s fsprofile: %(fsprofile)s\n" - " encryption: %(encryption)s" % - {"mount": self.mountpoint, "id": self.uniqueID, - "fstype": fsname, "format": self.format, - "level": self.raidlevel, "spares": self.raidspares, - "members": self.raidmembers, "fsprofile": self.fsprofile, - "encryption": crypto - }) - return str - - def getDevice(self, partitions): - """Return a device which can be solidified.""" - # Alway return a new device for minor changing - raidmems = [] - for member in self.raidmembers: - request = partitions.getRequestByID(member) - raidmems.append(request.getDevice(partitions)) - self.dev = fsset.RAIDDevice(int(self.raidlevel[4:]), - raidmems, minor = self.raidminor, - spares = self.raidspares, - existing = self.preexist, - chunksize = self.chunksize, - encryption = self.encryption) - return self.dev - - def isEncrypted(self, partitions, parentOnly = False): - if RequestSpec.isEncrypted(self, partitions) is True: - return True - if parentOnly: - return False - for member in self.raidmembers: - if partitions.getRequestByID(member).isEncrypted(partitions): - return True - return False - - def getActualSize(self, partitions, diskset): - """Return the actual size allocated for the request in megabytes.""" - - # this seems like a check which should never fail... - if not self.raidmembers or not self.raidlevel: - return 0 - nummembers = len(self.raidmembers) - self.raidspares - smallest = None - sum = 0 - for member in self.raidmembers: - req = partitions.getRequestByID(member) - partsize = req.getActualSize(partitions, diskset) - - if raid.isRaid0(self.raidlevel): - sum = sum + partsize - else: - if not smallest: - smallest = partsize - elif partsize < smallest: - smallest = partsize - - if raid.isRaid0(self.raidlevel): - return sum - elif raid.isRaid1(self.raidlevel): - return smallest - elif raid.isRaid5(self.raidlevel): - return (nummembers-1) * smallest - elif raid.isRaid6(self.raidlevel): - return (nummembers-2) * smallest - elif raid.isRaid10(self.raidlevel): - return (nummembers/2) * smallest - else: - raise ValueError, "Invalid raidlevel in RaidRequest.getActualSize" - - - # do RAID specific sanity checks; this is an internal function - def sanityCheckRaid(self, partitions): - if not self.raidmembers or not self.raidlevel: - return _("No members in RAID request, or not RAID " - "level specified.") - - minmembers = raid.get_raid_min_members(self.raidlevel) - if len(self.raidmembers) < minmembers: - return _("A RAID device of type %s " - "requires at least %s members.") % (self.raidlevel, - minmembers) - - if len(self.raidmembers) > 27: - return "RAID devices are limited to 27 members." - - if self.raidspares: - if (len(self.raidmembers) - self.raidspares) < minmembers: - return _("This RAID device can have a maximum of %s spares. " - "To have more spares you will need to add members to " - "the RAID device.") % (len(self.raidmembers) - - minmembers ) - return None - - def sanityCheckRequest(self, partitions): - """Run the basic sanity checks on the request.""" - rc = self.sanityCheckRaid(partitions) - if rc: - return rc - - return RequestSpec.sanityCheckRequest(self, partitions) - -class VolumeGroupRequestSpec(RequestSpec): - """Request to represent volume group devices.""" - - def __init__(self, fstype =None, format = None, - vgname = None, physvols = None, - pesize = 32768, preexist = 0, - preexist_size = 0): - """Create a new VolumeGroupRequestSpec object. - - fstype is the fsset filesystem type. - format is whether or not the volume group should be created. - vgname is the name of the volume group. - physvols is a list of the ids for the physical volumes in the vg. - pesize is the size of a physical extent in kilobytes. - preexist is whether the volume group is preexisting. - preexist_size is the size of a preexisting VG read from /proc - (note that this is unclamped) - """ - - if not fstype: - fstype = fsset.fileSystemTypeGet("volume group (LVM)") - RequestSpec.__init__(self, fstype = fstype, format = format) - self.type = REQUEST_VG - - self.volumeGroupName = vgname - self.physicalVolumes = physvols - self.pesize = pesize - self.preexist = preexist - self.free = 0 - - # FIXME: this is a hack so that we can set the vg name automagically - # with autopartitioning to not conflict with existing vgs - self.autoname = 0 - - if preexist and preexist_size: - self.preexist_size = preexist_size - else: - self.preexist_size = None - - def __str__(self): - physvols = [] - if self.physicalVolumes: - for i in self.physicalVolumes: - physvols.append(i) - - str = ("VG Request -- name: %(vgname)s uniqueID: %(id)s\n" - " format: %(format)s pesize: %(pesize)s \n" - " physvols: %(physvol)s" % - {"vgname": self.volumeGroupName, "id": self.uniqueID, - "format": self.format, "physvol": physvols, - "pesize": self.pesize}) - return str - - def getDevice(self, partitions): - """Return a device which can be solidified.""" - if self.dev: - # FIXME: this warning can probably be removed post-beta - log.warning("getting self.dev more than once for %s" %(self,)) - return self.dev - - pvs = [] - for pv in self.physicalVolumes: - r = partitions.getRequestByID(pv) - # a size of zero implies we did autopartitioning of - # pvs everywhere we could - if (r.size > 0) or (r.device is not None): - pvs.append(r.getDevice(partitions)) - self.dev = fsset.VolumeGroupDevice(self.volumeGroupName, pvs, - self.pesize, - existing = self.preexist) - return self.dev - - def isEncrypted(self, partitions, parentOnly = False): - if RequestSpec.isEncrypted(self, partitions) is True: - return True - if parentOnly: - return False - for pvid in self.physicalVolumes: - pv = partitions.getRequestByID(pvid) - if pv.isEncrypted(partitions): - return True - return False - - def getActualSize(self, partitions, diskset): - """Return the actual size allocated for the request in megabytes.""" - - # if we have a preexisting size, use it - if self.preexist and self.preexist_size: - totalspace = lvm.clampPVSize(self.preexist_size, self.pesize) - else: - totalspace = 0 - for pvid in self.physicalVolumes: - pvreq = partitions.getRequestByID(pvid) - size = pvreq.getActualSize(partitions, diskset) - #log.info("size for pv %s is %s" % (pvid, size)) - size = lvm.clampPVSize(size, self.pesize) - (self.pesize/1024) - #log.info(" clamped size is %s" % (size,)) - totalspace = totalspace + size - - return totalspace - -class PartialVolumeGroupSpec: - """Request to represent partial volume group devices.""" - # note, these are just used as placeholders so we don't collide on names - - def __init__(self, vgname = None): - """Create a new PartialVolumeGroupSpec object. - - vgname is the name of the volume group. - """ - - self.volumeGroupName = vgname - - def __str__(self): - str = ("Partial VG Request -- name: %(vgname)s" % - {"vgname": self.volumeGroupName}) - return str - -class LogicalVolumeRequestSpec(RequestSpec): - """Request to represent logical volume devices.""" - - def __init__(self, fstype, format = None, mountpoint = None, - size = None, volgroup = None, lvname = None, - preexist = 0, percent = None, grow=0, maxSizeMB=0, - fslabel = None, fsprofile = None): - """Create a new VolumeGroupRequestSpec object. - - fstype is the fsset filesystem type. - format is whether or not the volume group should be created. - mountpoint is the mountpoint for the request. - size is the size of the request in MB. - volgroup is the request ID of the volume group. - lvname is the name of the logical volume. - preexist is whether the logical volume previously existed or not. - percent is the percentage of the volume group's space this should use. - grow is whether or not to use free space remaining. - maxSizeMB is max size to grow to. - fslabel is the label of the filesystem on the logical volume. - fsprofile is the usage profile for the filesystem. - """ - - # if it's preexisting, the original fstype should be set - if preexist == 1: - origfs = fstype - else: - origfs = None - - RequestSpec.__init__(self, fstype = fstype, format = format, - mountpoint = mountpoint, size = size, - preexist = preexist, origfstype = origfs, - fslabel = fslabel, fsprofile = fsprofile) - - self.type = REQUEST_LV - - self.logicalVolumeName = lvname - self.volumeGroup = volgroup - self.percent = percent - self.grow = grow - self.maxSizeMB = maxSizeMB - self.startSize = size - - self.minResizeSize = None - self.resizable = True - - if not percent and not size and not preexist: - raise RuntimeError, "Error with Volume Group:Logical Volume %s:%s - Logical Volume must specify either percentage of vgsize or size" % (volgroup, lvname) - - if percent and grow: - raise RuntimeError, "Error with Volume Group:Logical Volume %s:%s - Logical Volume cannot grow if percentage given" % (volgroup, lvname) - - def __str__(self): - if self.fstype: - fsname = self.fstype.getName() - else: - fsname = "None" - - if self.size is not None: - size = self.size - else: - size = "%s percent" %(self.percent,) - - if self.encryption is None: - crypto = "None" - else: - crypto = self.encryption.getScheme() - - str = ("LV Request -- mountpoint: %(mount)s uniqueID: %(id)s\n" - " type: %(fstype)s format: %(format)s\n" - " size: %(size)s lvname: %(lvname)s volgroup: %(vgid)s\n" - " options: '%(fsopts)s' fsprofile: %(fsprofile)s" - " encryption: '%(crypto)s'" % - {"mount": self.mountpoint, "id": self.uniqueID, - "fstype": fsname, "format": self.format, - "lvname": self.logicalVolumeName, "vgid": self.volumeGroup, - "size": size, "crypto": crypto, - "fsopts": self.fsopts, "fsprofile": self.fsprofile}) - return str - - def getDevice(self, partitions): - """Return a device which can be solidified.""" - vg = partitions.getRequestByID(self.volumeGroup) - vgname = vg.volumeGroupName - self.dev = fsset.LogicalVolumeDevice(vgname, self.size, - self.logicalVolumeName, - vg = vg, - existing = self.preexist, - encryption = self.encryption) - return self.dev - - def isEncrypted(self, partitions, parentOnly = False): - if RequestSpec.isEncrypted(self, partitions) is True: - return True - if parentOnly: - return False - vg = partitions.getRequestByID(self.volumeGroup) - if vg.isEncrypted(partitions): - return True - return False - - def getActualSize(self, partitions = None, diskset = None, target = False): - """Return the actual size allocated for the request in megabytes.""" - if self.percent: - if partitions is None or diskset is None: - raise RuntimeError, "trying to get a percentage lv size on resize path" - vgreq = partitions.getRequestByID(self.volumeGroup) - vgsize = vgreq.getActualSize(partitions, diskset) - lvsize = int(self.percent * 0.01 * vgsize) - #lvsize = lvm.clampLVSizeRequest(lvsize, vgreq.pesize) - return lvsize - # FIXME: the target bit here is a bit of a hack... - elif self.targetSize is not None and target: - return self.targetSize - else: - return self.size - - def getStartSize(self): - """Return the starting size allocated for the request in megabytes.""" - return self.startSize - - def setSize(self, size): - """Set the size (in MB) of request (does not clamp to PE however) - - size - size in MB - """ - if self.percent: - self.percent = None - - self.size = size - - def sanityCheckRequest(self, partitions, skipMntPtExistCheck=0, pesize=32768): - """Run the basic sanity checks on the request.""" - if not self.grow and not self.percent and self.size*1024 < pesize: - return _("Logical volume size must be larger than the volume " - "group's physical extent size.") - - return RequestSpec.sanityCheckRequest(self, partitions, skipMntPtExistCheck) - - def getMaximumResizeMB(self, partitions): - vg = partitions.getRequestByID(self.volumeGroup) - print("max is", self.getActualSize(), vg.free, self.getActualSize() + vg.free) - return self.getActualSize() + vg.free - - def getMinimumResizeMB(self, partitions): - if self.minResizeSize is None: - log.warning("don't know the minimum size of %s" %(self.logicalVolumeName,)) - return 1 - return self.minResizeSize diff --git a/partedUtils.py b/partedUtils.py index 4f216b2c1..dde4d0b61 100644 --- a/partedUtils.py +++ b/partedUtils.py @@ -32,12 +32,8 @@ import os, sys, string, struct, resource from product import * import exception -import fsset import iutil, isys -import raid -import dmraid import block -import lvm import inspect from flags import flags from errors import * @@ -118,32 +114,6 @@ def filter_partitions(disk, func): rc.append(part) return rc -def getDefaultDiskType(): - """Get the default partition table type for this architecture.""" - if iutil.isEfi(): - return parted.diskType["gpt"] - elif iutil.isX86(): - return parted.diskType["msdos"] - elif iutil.isS390(): - # the "default" type is dasd, but we don't really do dasd - # formatting with parted and use dasdfmt directly for them - # so if we get here, it's an fcp disk and we should write - # an msdos partition table (#144199) - return parted.diskType["msdos"] - elif iutil.isAlpha(): - return parted.diskType["bsd"] - elif iutil.isSparc(): - return parted.diskType["sun"] - elif iutil.isPPC(): - ppcMachine = iutil.getPPCMachine() - - if ppcMachine == "PMac": - return parted.diskType["mac"] - else: - return parted.diskType["msdos"] - else: - return parted.diskType["msdos"] - def hasGptLabel(diskset, device): disk = diskset.disks[device] return disk.type == "gpt" @@ -157,9 +127,9 @@ def isEfiSystemPartition(part): part.fileSystem.type in ("fat16", "fat32") and isys.readFSLabel(part.getDeviceNodeName()) != "ANACONDA") -def labelDisk(deviceFile, forceLabelType=None): +def labelDisk(platform, deviceFile, forceLabelType=None): dev = parted.getDevice(deviceFile) - label = getDefaultDiskType() + label = platform.diskType if not forceLabelType is None: label = forceLabelType @@ -253,7 +223,7 @@ def hasProtectedPartitions(drive, anaconda): return rc try: - for protected in anaconda.id.partitions.protectedPartitions(): + for protected in anaconda.id.storage.protectedPartitions: if protected.startswith(drive): part = protected[len(drive):] if part[0] == "p": @@ -313,48 +283,6 @@ def isLinuxNative(part): else: return False -def getReleaseString(mountpoint): - relName = None - relVer = None - - if os.access(mountpoint + "/etc/redhat-release", os.R_OK): - f = open(mountpoint + "/etc/redhat-release", "r") - try: - lines = f.readlines() - except IOError: - try: - f.close() - except: - pass - return (relName, relVer) - - f.close() - - # return the first line with the newline at the end stripped - if len(lines) == 0: - return (relName, relVer) - - relstr = string.strip(lines[0][:-1]) - - # get the release name and version - # assumes that form is something - # like "Red Hat Linux release 6.2 (Zoot)" - if relstr.find("release") != -1: - try: - idx = relstr.find("release") - relName = relstr[:idx - 1] - relVer = "" - - for a in relstr[idx + 8:]: - if a in string.digits + ".": - relVer += a - else: - break - except: - pass # don't worry, just use the relstr as we have it - - return (relName, relVer) - class DiskSet: """The disks in the system.""" @@ -583,7 +511,8 @@ class DiskSet: fs = isys.readFSType(theDev) if fs is not None: try: - isys.mount(theDev, self.anaconda.rootPath, fs, readOnly = 1) + isys.mount(theDev, self.anaconda.rootPath, fs, + readOnly = True) found = 1 except SystemError: pass @@ -629,7 +558,8 @@ class DiskSet: fs = isys.readFSType(theDev) if fs is not None: try: - isys.mount(theDev, self.anaconda.rootPath, fs, readOnly = 1) + isys.mount(theDev, self.anaconda.rootPath, fs, + readOnly = True) found = 1 except SystemError: pass @@ -656,7 +586,7 @@ class DiskSet: drives = self.disks.keys() drives.sort() - protected = self.anaconda.id.partitions.protectedPartitions() + protected = self.anaconda.id.storage.protectedPartitions for drive in drives: disk = self.disks[drive] @@ -713,14 +643,10 @@ class DiskSet: def driveList (self): """Return the list of drives on the system.""" - drives = isys.hardDriveDict().keys() + drives = map(lambda x: x.name, self.anaconda.id.storage.disks) drives.sort (isys.compareDrives) return drives - def drivesByName (self): - """Return a dictionary of the drives on the system.""" - return isys.hardDriveDict() - def savePartitions (self): """Write the partition tables out to the disks.""" for disk in self.disks.values(): @@ -929,7 +855,7 @@ class DiskSet: dev = parted.getDevice(deviceFile) disk = parted.Disk(device=dev) else: - disk = labelDisk(deviceFile) + disk = labelDisk(self.anaconda.platform, deviceFile) except Exception, msg: log.error("parted error: %s" % (msg,)) raise diff --git a/partitions.py b/partitions.py deleted file mode 100644 index 59ad85767..000000000 --- a/partitions.py +++ /dev/null @@ -1,1899 +0,0 @@ -# -# partitions.py: partition object containing partitioning info -# -# Copyright (C) 2002, 2003, 2004, 2005, 2006 Red Hat, Inc. -# All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# Author(s): Matt Wilson <msw@redhat.com> -# Jeremy Katz <katzj@redhat.com> -# Mike Fulbright <msf@redhat.com> -# Harald Hoyer <harald@redhat.de> -# - -"""Overarching partition object.""" - -import parted -import iutil -import isys -import string -import os -import sys - -from constants import * -from flags import flags -from errors import * - -import fsset -import raid -import lvm -import partedUtils -import partRequests -import cryptodev - -import gettext -_ = lambda x: gettext.ldgettext("anaconda", x) - -import logging -log = logging.getLogger("anaconda") - -# dispatch.py helper function -def partitionObjectsInitialize(anaconda): - # shut down all dm devices - anaconda.id.diskset.closeDevices() - anaconda.id.diskset.stopMdRaid() - anaconda.id.zfcp.shutdown() - - # clean slate about drives - isys.flushDriveDict() - - if anaconda.dir == DISPATCH_BACK: - return - - # make ibft configured iscsi disks available when findrootparts was skipped - anaconda.id.iscsi.startup(anaconda.intf) - - # ensure zfcp devs are up - anaconda.id.zfcp.startup() - - # pull in the new iscsi drive - isys.flushDriveDict() - - # read in drive info - anaconda.id.diskset.refreshDevices() - - anaconda.id.partitions.setFromDisk(anaconda.id.diskset) - anaconda.id.partitions.setProtected(anaconda.dispatch) - -# dispatch.py helper function -def partitioningComplete(anaconda): - if anaconda.dir == DISPATCH_BACK and anaconda.id.fsset.isActive(): - rc = anaconda.intf.messageWindow(_("Installation cannot continue."), - _("The partitioning options you have chosen " - "have 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 - - anaconda.id.partitions.sortRequests() - anaconda.id.fsset.reset() - undoEncryption = False - partitions = anaconda.id.partitions - preexist = partitions.hasPreexistingCryptoDev() - for request in anaconda.id.partitions.requests: - # XXX improve sanity checking - if (not request.fstype or (request.fstype.isMountable() - and not request.mountpoint)): - continue - - if request.encryption and request.encryption.format: - if anaconda.isKickstart and request.encryption.hasPassphrase(): - # they set a passphrase for this device explicitly - pass - elif partitions.encryptionPassphrase: - request.encryption.setPassphrase(partitions.encryptionPassphrase) - elif undoEncryption: - request.encryption = None - if request.dev: - request.dev.crypto = None - else: - while True: - (passphrase, retrofit) = anaconda.intf.getLuksPassphrase(preexist=preexist) - if passphrase: - request.encryption.setPassphrase(passphrase) - partitions.encryptionPassphrase = passphrase - partitions.retrofitPassphrase = 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.") - request.encryption = None - if request.dev: - request.dev.encryption = None - undoEncryption = True - partitions.autoEncrypt = False - break - - entry = request.toEntry(anaconda.id.partitions) - if entry: - anaconda.id.fsset.add (entry) - else: - raise RuntimeError, ("Managed to not get an entry back from " - "request.toEntry") - - if anaconda.isKickstart: - return - - rc = anaconda.intf.messageWindow(_("Writing partitioning 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) - if rc == 0: - return DISPATCH_BACK - -def lookup_cryptodev(device): - for encryptedDev, cdev in Partitions.encryptedDevices.items(): - mappedDev = cdev.getDevice() - if device == encryptedDev or device == mappedDev: - return cdev - -class Partitions: - """Defines all of the partition requests and delete requests.""" - encryptedDevices = {} - - def __init__ (self, anaconda, readDisks=False): - """Initializes a Partitions object. - - Can pass in the diskset if it already exists. - """ - self.anaconda = anaconda - - self.requests = [] - """A list of RequestSpec objects for all partitions.""" - - self.deletes = [] - """A list of DeleteSpec objects for partitions to be deleted.""" - - self.autoPartitionRequests = [] - """A list of RequestSpec objects for autopartitioning. - These are setup by the installclass and folded into self.requests - by auto partitioning.""" - - self.autoClearPartType = CLEARPART_TYPE_NONE - """What type of partitions should be cleared?""" - - self.autoClearPartDrives = None - """Drives to clear partitions on (note that None is equiv to all).""" - - self.nextUniqueID = 1 - """Internal counter. Don't touch unless you're smarter than me.""" - - self.reinitializeDisks = 0 - """Should the disk label be reset on all disks?""" - - self.zeroMbr = 0 - """Should the mbr be zero'd?""" - - self.protected = [] - """A list of partitions that are the installation source for hard - drive or livecd installs. Partitions on this list may not be - formatted.""" - - self.autoEncrypt = False - - self.encryptionPassphrase = "" - self.retrofitPassphrase = False - - # partition method to be used. not to be touched externally - 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.anaconda.id.diskset.refreshDevices() - self.setFromDisk(self.anaconda.id.diskset) - - def protectedPartitions(self): - return self.protected - - def hasPreexistingCryptoDev(self): - rc = False - for request in self.requests: - if request.encryption and request.encryption.format == 0: - rc = True - break - - return rc - - def getCryptoDev(self, device): - log.info("going to get passphrase for encrypted device %s" % device) - luksDev = self.encryptedDevices.get(device) - if luksDev: - log.debug("passphrase for device %s already known" % device) - return luksDev - - intf = self.anaconda.intf - luksDev = cryptodev.LUKSDevice(device) - if self.encryptionPassphrase: - luksDev.setPassphrase(self.encryptionPassphrase) - if not luksDev.openDevice(): - self.encryptedDevices[device] = luksDev - return luksDev - else: - luksDev.setPassphrase("") - - if intf is None: - return - - buttons = [_("Back"), _("Continue")] - devname = os.path.basename(device) - while True: - (passphrase, isglobal) = intf.passphraseEntryWindow(devname) - 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.") % devname, - type = "custom", - default = 0, - custom_buttons = buttons) - if rc == 0: - continue - else: - log.info("skipping passphrase for %s" % (device,)) - break - - luksDev.setPassphrase(passphrase) - rc = luksDev.openDevice() - if rc: - luksDev.setPassphrase("") - continue - else: - self.encryptedDevices[device] = luksDev - if isglobal: - self.encryptionPassphrase = passphrase - break - - return self.encryptedDevices.get(device) - - def getEncryptedDevices(self, diskset): - """ find and obtain passphrase for any encrypted devices """ - drives = diskset.disks.keys() - drives.sort() - for drive in drives: - if diskset.anaconda.isKickstart and \ - ((self.autoClearPartType != CLEARPART_TYPE_NONE and \ - (not self.autoClearPartDrives or \ - drive in self.autoClearPartDrives)) or \ - drive in diskset.skippedDisks): - continue - - disk = diskset.disks[drive] - for part in disk.partitions: - if part.type & parted.PARTITION_METADATA: - continue - - device = part.getDeviceNodeName() - fs = isys.readFSType("/dev/%s" % (device,)) - if fs and fs.endswith("raid"): - continue - - if cryptodev.isLuks("/dev/%s" % device): - self.getCryptoDev(device) - - diskset.startMPath() - diskset.startDmRaid() - diskset.startMdRaid() - mdList = diskset.mdList - for raidDev in mdList: - (theDev, devices, level, numActive) = raidDev - if cryptodev.isLuks("/dev/%s" % theDev): - self.getCryptoDev(theDev) - - lvm.writeForceConf() - # now to read in pre-existing LVM stuff - lvm.vgscan() - lvm.vgactivate() - - for (vg, size, pesize, vgfree) in lvm.vglist(): - for (lvvg, lv, size, lvorigin) in lvm.lvlist(): - if lvorigin: - continue - if lvvg != vg: - continue - - theDev = "/dev/mapper/%s-%s" %(vg, lv) - if cryptodev.isLuks(theDev): - self.getCryptoDev("mapper/%s-%s" % (vg, lv)) - - lvm.vgdeactivate() - diskset.stopMdRaid() - for luksDev in self.encryptedDevices.values(): - luksDev.closeDevice() - # try again now that encryption mappings are closed - lvm.vgdeactivate() - diskset.stopMdRaid() - for luksDev in self.encryptedDevices.values(): - luksDev.closeDevice() - - # We shouldn't have any further need for the global passphrase - # except for new device creation, in which case we want to give - # the user a chance to establish a new global passphrase. - self.encryptionPassphrase = "" - - def setFromDisk(self, diskset): - """Clear the delete list and set self.requests to reflect disk.""" - self.deletes = [] - self.requests = [] - if diskset.anaconda.isKickstart and not diskset.anaconda.id.upgrade: - self.getEncryptedDevices(diskset) - labels = diskset.getInfo() - drives = diskset.disks.keys() - drives.sort() - for drive in drives: - disk = diskset.disks[drive] - part = disk.getFirstPartition() - while part: - if (part.type & parted.PARTITION_METADATA) or \ - (part.type & parted.PARTITION_FREESPACE) or \ - (part.type & parted.PARTITION_PROTECTED): - part = part.nextPartition() - continue - - format = None - if part.type & parted.PARTITION_EXTENDED: - ptype = None - elif part.getFlag(parted.PARTITION_RAID) == 1: - ptype = fsset.fileSystemTypeGet("software RAID") - elif part.getFlag(parted.PARTITION_LVM) == 1: - ptype = fsset.fileSystemTypeGet("physical volume (LVM)") - else: - ptype = partedUtils.get_partition_file_system_type(part) - - # FIXME: we don't handle ptype being None very well, so - # just say it's foreign. Should probably fix None - # handling instead some day. - if ptype is None: - ptype = fsset.fileSystemTypeGet("foreign") - - device = part.getDeviceNodeName() - - # parted doesn't tell ext4 from ext3 - if ptype == fsset.fileSystemTypeGet("ext3"): - fsname = isys.readFSType("/dev/%s" % (device,)) - try: - ptype = fsset.fileSystemTypeGet(fsname) - except: - ptype = fsset.fileSystemTypeGet("foreign") - - luksDev = self.encryptedDevices.get(device) - if luksDev and not luksDev.openDevice(): - mappedDev = luksDev.getDevice() - fsname = isys.readFSType("/dev/%s" % (mappedDev,)) - try: - ptype = fsset.fileSystemTypeGet(fsname) - except: - ptype = fsset.fileSystemTypeGet("foreign") - elif cryptodev.isLuks("/dev/%s" % device): - ptype = fsset.fileSystemTypeGet("foreign") - - start = part.geometry.start - end = part.geometry.end - size = part.getSize(unit="MB") - drive = partedUtils.get_partition_drive(part) - - spec = partRequests.PreexistingPartitionSpec(ptype, - size = size, - start = start, - end = end, - drive = drive, - format = format) - spec.device = fsset.PartedPartitionDevice(part).getDevice() - spec.encryption = luksDev - spec.maxResizeSize = part.getMaxAvailableSize(unit="MB") - - # set label if makes sense - if ptype and ptype.isMountable(): - if spec.device in labels.keys(): - if labels[spec.device] and len(labels[spec.device])>0: - spec.fslabel = labels[spec.device] - elif luksDev and not luksDev.getStatus() and mappedDev in labels.keys(): - if labels[mappedDev] and len(labels[mappedDev])>0: - spec.fslabel = labels[mappedDev] - self.addRequest(spec) - - part = part.nextPartition() - - # now we need to read in all pre-existing RAID stuff - diskset.startMPath() - diskset.startDmRaid() - diskset.startMdRaid() - mdList = diskset.mdList - for raidDev in mdList: - (theDev, devices, level, numActive) = raidDev - level = "RAID%s" %(level,) - - if level not in raid.availRaidLevels: - log.warning("raid level %s not supported, skipping %s" %(level, - theDev)) - continue - - try: - chunk = isys.getRaidChunkFromDevice("/dev/%s" %(devices[0],)) - except Exception, e: - log.error("couldn't get chunksize of %s: %s" %(theDev, e)) - chunk = None - - # is minor always mdN ? - minor = int(theDev[2:]) - raidvols = [] - for dev in devices: - req = self.getRequestByDeviceName(dev) - if not req: - log.error("RAID device %s using non-existent partition %s" - %(theDev, dev)) - continue - raidvols.append(req.uniqueID) - - - luksDev = self.encryptedDevices.get(theDev) - if luksDev and not luksDev.openDevice(): - device = luksDev.getDevice() - else: - device = theDev - - fs = isys.readFSType("/dev/%s" %(device,)) - try: - fsystem = fsset.fileSystemTypeGet(fs) - except: - fsystem = fsset.fileSystemTypeGet("foreign") - - try: - fslabel = isys.readFSLabel(device) - except: - fslabel = None - - mnt = None - format = 0 - - spares = len(devices) - numActive - spec = partRequests.RaidRequestSpec(fsystem, format = format, - raidlevel = level, - raidmembers = raidvols, - raidminor = minor, - raidspares = spares, - mountpoint = mnt, - preexist = 1, - chunksize = chunk, - fslabel = fslabel) - spec.size = spec.getActualSize(self, diskset) - spec.encryption = luksDev - self.addRequest(spec) - - lvm.writeForceConf() - # now to read in pre-existing LVM stuff - lvm.vgscan() - lvm.vgactivate() - - pvs = lvm.pvlist() - for (vg, size, pesize, vgfree) in lvm.vglist(): - try: - preexist_size = float(size) - except: - log.error("preexisting size for %s not a valid integer, ignoring" %(vg,)) - preexist_size = None - - pvids = [] - for (dev, pvvg, size) in pvs: - if vg != pvvg: - continue - req = self.getRequestByDeviceName(dev[5:]) - if not req: - log.error("Volume group %s using non-existent partition %s" - %(vg, dev)) - continue - pvids.append(req.uniqueID) - spec = partRequests.VolumeGroupRequestSpec(format = 0, - vgname = vg, - physvols = pvids, - pesize = pesize, - preexist = 1, - preexist_size = preexist_size) - spec.free = vgfree - vgid = self.addRequest(spec) - - for (lvvg, lv, size, lvorigin) in lvm.lvlist(): - if lvorigin: - continue - if lvvg != vg: - continue - - # size is number of bytes, we want size in megs - lvsize = float(size) - - theDev = "/dev/%s/%s" %(vg, lv) - - luksDev = self.encryptedDevices.get("mapper/%s-%s" % (vg, lv)) - if luksDev and not luksDev.openDevice(): - device = luksDev.getDevice() - else: - device = theDev - - fs = isys.readFSType(device) - fslabel = None - - try: - fsystem = fsset.fileSystemTypeGet(fs) - except: - fsystem = fsset.fileSystemTypeGet("foreign") - - try: - fslabel = isys.readFSLabel(device) - except: - fslabel = None - - mnt = None - format = 0 - - spec = partRequests.LogicalVolumeRequestSpec(fsystem, - format = format, size = lvsize, volgroup = vgid, - lvname = lv, mountpoint = mnt, fslabel = fslabel, - preexist = 1) - if fsystem.isResizable(): - spec.minResizeSize = fsystem.getMinimumSize("%s/%s" %(vg, lv)) - spec.encryption = luksDev - self.addRequest(spec) - - for vg in lvm.partialvgs(): - spec = partRequests.PartialVolumeGroupSpec(vgname = vg) - self.addDelete(spec) - - lvm.vgdeactivate() - diskset.stopMdRaid() - for luksDev in self.encryptedDevices.values(): - luksDev.closeDevice() - - # try again now that encryption mappings are closed - lvm.vgdeactivate() - diskset.stopMdRaid() - for luksDev in self.encryptedDevices.values(): - luksDev.closeDevice() - - def addRequest (self, request): - """Add a new request to the list.""" - if not request.uniqueID: - request.uniqueID = self.nextUniqueID - self.nextUniqueID = self.nextUniqueID + 1 - self.requests.append(request) - self.requests.sort() - - return request.uniqueID - - def addDelete (self, delete): - """Add a new DeleteSpec to the list.""" - self.deletes.append(delete) - self.deletes.sort() - - def removeRequest (self, request): - """Remove a request from the list.""" - self.requests.remove(request) - - def getRequestByMountPoint(self, mount): - """Find and return the request with the given mountpoint.""" - for request in self.requests: - if request.mountpoint == mount: - return request - - for request in self.requests: - if request.type == REQUEST_LV and request.mountpoint == mount: - return request - return None - - def getRequestByDeviceName(self, device): - """Find and return the request with the given device name.""" - if device is None: - return None - - for request in self.requests: - if request.type == REQUEST_RAID and request.raidminor is not None: - tmp = "md%d" % (request.raidminor,) - if tmp == device: - return request - elif request.device == device: - return request - elif request.encryption: - deviceUUID = cryptodev.luksUUID("/dev/" + device) - cryptoDev = request.encryption.getDevice() - cryptoUUID = request.encryption.getUUID() - if cryptoDev == device or \ - (cryptoUUID and cryptoUUID == deviceUUID): - return request - return None - - - def getRequestsByDevice(self, diskset, device): - """Find and return the requests on a given device (like 'hda').""" - if device is None: - return None - - drives = diskset.disks.keys() - if device not in drives: - return None - - rc = [] - disk = diskset.disks[device] - for part in disk.partitions: - dev = part.getDeviceNodeName() - request = self.getRequestByDeviceName(dev) - - if request: - rc.append(request) - - if len(rc) > 0: - return rc - else: - return None - - def getRequestByVolumeGroupName(self, volname): - """Find and return the request with the given volume group name.""" - if volname is None: - return None - - for request in self.requests: - if (request.type == REQUEST_VG and - request.volumeGroupName == volname): - return request - return None - - def getRequestByLogicalVolumeName(self, lvname): - """Find and return the request with the given logical volume name.""" - if lvname is None: - return None - for request in self.requests: - if (request.type == REQUEST_LV and - request.logicalVolumeName == lvname): - return request - return None - - def getRequestByID(self, id): - """Find and return the request with the given unique ID. - - Note that if id is a string, it will be converted to an int for you. - """ - if type(id) == type("a string"): - id = int(id) - for request in self.requests: - if request.uniqueID == id: - return request - return None - - def getUnderlyingRequests(self, request): - loop = True - requests = [ ] - requests.append(request) - - # loop over requests descending the storage stack until we - # are at the bottom - while loop: - loop_requests = requests - requests = [ ] - loop = False - - for request in loop_requests: - ids = [ ] - - if (request.type == REQUEST_NEW or \ - request.type == REQUEST_PREEXIST): - requests.append(request) - elif (request.type == REQUEST_RAID): - if request.raidmembers: - ids = request.raidmembers - else: - requests.append(request) - elif (request.type == REQUEST_VG): - ids = request.physicalVolumes - elif (request.type == REQUEST_LV): - ids.append(request.volumeGroup) - else: - log.error("getUnderlyingRequests unknown request type") - - for id in ids: - tmpreq = self.getRequestByID(id) - if tmpreq: - requests.append(tmpreq) - loop = True - else: - log.error("getUnderlyingRequests could not get request for id %s" % (id,)) - - return requests - - def getRaidRequests(self): - """Find and return a list of all of the RAID requests.""" - retval = [] - for request in self.requests: - if request.type == REQUEST_RAID: - retval.append(request) - - return retval - - def getRaidDevices(self): - """Find and return a list of all of the requests for use in RAID.""" - raidRequests = [] - for request in self.requests: - if isinstance(request, partRequests.RaidRequestSpec): - raidRequests.append(request) - - return raidRequests - - def getAvailableRaidMinors(self): - """Find and return a list of all of the unused minors for use in RAID.""" - raidMinors = range(0,32) - for request in self.requests: - if isinstance(request, partRequests.RaidRequestSpec) and request.raidminor in raidMinors: - raidMinors.remove(request.raidminor) - - return raidMinors - - - def getAvailRaidPartitions(self, request, diskset): - """Return a list of tuples of RAID partitions which can be used. - - Return value is (part, size, used) where used is 0 if not, - 1 if so, 2 if used for *this* request. - """ - rc = [] - drives = diskset.disks.keys() - raiddevs = self.getRaidDevices() - drives.sort() - for drive in drives: - disk = diskset.disks[drive] - for part in disk.getRaidPartitions(): - partname = part.getDeviceNodeName() - used = 0 - for raid in raiddevs: - if raid.raidmembers: - for raidmem in raid.raidmembers: - tmpreq = self.getRequestByID(raidmem) - if (partname == tmpreq.device): - if raid.uniqueID == request.uniqueID: - used = 2 - else: - used = 1 - break - if used: - break - size = part.getSize(unit="MB") - - if not used: - rc.append((partname, size, 0)) - elif used == 2: - rc.append((partname, size, 1)) - - return rc - - def getRaidMemberParent(self, request): - """Return RAID device request containing this request.""" - raiddev = self.getRaidRequests() - if not raiddev or not request.device: - return None - for dev in raiddev: - if not dev.raidmembers: - continue - for member in dev.raidmembers: - if request.device == self.getRequestByID(member).device: - return dev - return None - - def isRaidMember(self, request): - """Return whether or not the request is being used in a RAID device.""" - if self.getRaidMemberParent(request) is not None: - return 1 - else: - return 0 - - def getLVMLVForVGID(self, vgid): - """Find and return a list of all the LVs associated with a VG id.""" - retval = [] - for request in self.requests: - if request.type == REQUEST_LV: - if request.volumeGroup == vgid: - retval.append(request) - return retval - - def getLVMLVForVG(self, vgrequest): - """Find and return a list of all of the LVs in the VG.""" - vgid = vgrequest.uniqueID - return self.getLVMLVForVGID(vgid) - - def getLVMRequests(self): - """Return a dictionary of all of the LVM bits. - - The dictionary returned is of the form vgname: [ lvrequests ] - """ - retval = {} - for request in self.requests: - if request.type == REQUEST_VG: - retval[request.volumeGroupName] = self.getLVMLVForVG(request) - - return retval - - def getPartialLVMRequests(self): - """Return a list of all of the partial volume groups names.""" - retval = [] - for request in self.deletes: - if isinstance(request, partRequests.PartialVolumeGroupSpec): - retval.append(request.volumeGroupName) - - return retval - - def getLVMVGRequests(self): - """Find and return a list of all of the volume groups.""" - retval = [] - for request in self.requests: - if request.type == REQUEST_VG: - retval.append(request) - - return retval - - def getLVMLVRequests(self): - """Find and return a list of all of the logical volumes.""" - retval = [] - for request in self.requests: - if request.type == REQUEST_LV: - retval.append(request) - - return retval - - def getAvailLVMPartitions(self, request, diskset): - """Return a list of tuples of PV partitions which can be used. - - Return value is (part, size, used) where used is 0 if not, - 1 if so, 2 if used for *this* request. - """ - rc = [] - drives = diskset.disks.keys() - drives.sort() - volgroups = self.getLVMVGRequests() - pvlist = lvm.pvlist() - for drive in drives: - disk = diskset.disks[drive] - for part in disk.getLVMPartitions(): - partname = part.getDeviceNodeName() - partrequest = self.getRequestByDeviceName(partname) - if partrequest.encryption is None and \ - cryptodev.isLuks("/dev/%s" % partname) and \ - not self.encryptedDevices.get(partname): - log.debug("ignoring PV %s since we cannot access it's contents" % partname) - # We don't want to treat an encrypted PV like a PV if the - # user chose not to provide a passphrase for this device. - # However, if the LUKS device belongs to a just-deleted - # request then we know it is available. - continue - used = 0 - for volgroup in volgroups: - if volgroup.physicalVolumes: - if partrequest.uniqueID in volgroup.physicalVolumes: - if (request and request.uniqueID and - volgroup.uniqueID == request.uniqueID): - used = 2 - else: - used = 1 - - if used: - break - size = None - for pvpart, pvvg, pvsize in pvlist: - if pvpart == "/dev/%s" % (partname,): - size = pvsize - if size is None: - # if we get here, there's no PV data in the partition, - # so clamp the partition's size to 64M - size = part.getSize(unit="MB") - size = lvm.clampPVSize(size, 65536) - - if used == 0: - rc.append((partrequest.uniqueID, size, 0)) - elif used == 2: - rc.append((partrequest.uniqueID, size, 1)) - - # now find available RAID devices - raiddev = self.getRaidRequests() - if raiddev: - raidcounter = 0 - for dev in raiddev: - used = 0 - - if dev.fstype is None: - continue - if dev.fstype.getName() != "physical volume (LVM)": - continue - - for volgroup in volgroups: - if volgroup.physicalVolumes: - if dev.uniqueID in volgroup.physicalVolumes: - if (request and request.uniqueID and - volgroup.uniqueID == request.uniqueID): - used = 2 - else: - used = 1 - - if used: - break - - size = dev.getActualSize(self, diskset) - - if used == 0: - rc.append((dev.uniqueID, size, 0)) - elif used == 2: - rc.append((dev.uniqueID, size, 1)) - - raidcounter = raidcounter + 1 - return rc - - def getLVMVolumeGroupMemberParent(self, request): - """Return parent volume group of a physical volume""" - volgroups = self.getLVMVGRequests() - if not volgroups: - return None - - for volgroup in volgroups: - if volgroup.physicalVolumes: - if request.uniqueID in volgroup.physicalVolumes: - return volgroup - - return None - - def isLVMVolumeGroupMember(self, request): - """Return whether or not the request is being used in an LVM device.""" - if self.getLVMVolumeGroupMemberParent(request) is None: - return 0 - else: - return 1 - - def isVolumeGroupNameInUse(self, vgname): - """Return whether or not the requested volume group name is in use.""" - if not vgname: - return None - - lvmrequests = self.getLVMRequests() - if lvmrequests: - if vgname in lvmrequests.keys(): - return 1 - - lvmrequests = self.getPartialLVMRequests() - if lvmrequests: - if vgname in lvmrequests: - return 1 - - return 0 - - def getBootableRequest(self): - """Return the name of the current 'boot' mount point.""" - bootreq = None - - if iutil.isEfi(): - ret = None - for req in self.requests: - if req.fstype == fsset.fileSystemTypeGet("efi"): - ret = [ req ] - if ret: - req = self.getRequestByMountPoint("/boot") - if req: - ret.append(req) - return ret - elif iutil.getPPCMachine() == "iSeries": - for req in self.requests: - if req.fstype == fsset.fileSystemTypeGet("PPC PReP Boot"): - return [ req ] - return None - elif (iutil.getPPCMachine() == "pSeries"): - # pSeries and Mac bootable requests are odd. - # have to consider both the PReP or Bootstrap partition (with - # potentially > 1 existing) as well as /boot,/ - - ret = [] - for req in self.requests: - if req.fstype == fsset.fileSystemTypeGet("PPC PReP Boot"): - ret.append(req) - - # now add the /boot - bootreq = self.getRequestByMountPoint("/boot") - if not bootreq: - bootreq = self.getRequestByMountPoint("/") - if bootreq: - ret.append(bootreq) - - if len(ret) >= 1: - return ret - return None - elif (iutil.getPPCMachine() == "PMac"): - # for the bootstrap partition, we want either the first or the - # first non-preexisting one - bestprep = None - for req in self.requests: - if req.fstype == fsset.fileSystemTypeGet("Apple Bootstrap"): - if ((bestprep is None) or - (bestprep.getPreExisting() and - not req.getPreExisting())): - bestprep = req - - if bestprep: - ret = [ bestprep ] - else: - ret = [] - - # now add the /boot - bootreq = self.getRequestByMountPoint("/boot") - if not bootreq: - bootreq = self.getRequestByMountPoint("/") - if bootreq: - ret.append(bootreq) - - if len(ret) >= 1: - return ret - return None - - if not bootreq: - bootreq = self.getRequestByMountPoint("/boot") - if not bootreq: - bootreq = self.getRequestByMountPoint("/") - - if bootreq: - return [ bootreq ] - return None - - def getBootableMountpoints(self): - """Return a list of bootable valid mountpoints for this arch.""" - # FIXME: should be somewhere else, preferably some sort of arch object - - if iutil.isEfi(): - return [ "/boot/efi" ] - else: - return [ "/boot", "/" ] - - def isBootable(self, request): - """Returns if the request should be considered a 'bootable' request. - - This basically means that it should be sorted to the beginning of - the drive to avoid cylinder problems in most cases. - """ - bootreqs = self.getBootableRequest() - if not bootreqs: - return 0 - - for bootreq in bootreqs: - if bootreq == request: - return 1 - - if bootreq.type == REQUEST_RAID and \ - request.uniqueID in bootreq.raidmembers: - return 1 - - return 0 - - def sortRequests(self): - """Resort the requests into allocation order.""" - n = 0 - while n < len(self.requests): - # Ignore LVM Volume Group and Logical Volume requests, - # since these are not related to allocating disk partitions - if (self.requests[n].type == REQUEST_VG or self.requests[n].type == REQUEST_LV): - n = n + 1 - continue - - for request in self.requests: - # Ignore LVM Volume Group and Logical Volume requests, - # since these are not related to allocating disk partitions - if (request.type == REQUEST_VG or request.type == REQUEST_LV): - continue - # for raid requests, the only thing that matters for sorting - # is the raid device since ordering by size is mostly - # irrelevant. this also keeps things more consistent - elif (request.type == REQUEST_RAID or - self.requests[n].type == REQUEST_RAID): - if (request.type == self.requests[n].type and - (self.requests[n].raidminor != None) and - ((request.raidminor is None) or - request.raidminor > self.requests[n].raidminor)): - tmp = self.requests[n] - index = self.requests.index(request) - self.requests[n] = request - self.requests[index] = tmp - # for sized requests, we want the larger ones first - elif (request.size and self.requests[n].size and - (request.size < self.requests[n].size)): - tmp = self.requests[n] - index = self.requests.index(request) - self.requests[n] = request - self.requests[index] = tmp - # for cylinder-based, sort by order on the drive - elif (request.start and self.requests[n].start and - (request.drive in self.requests[n].drive) and - (request.type == self.requests[n].type) and - (request.start > self.requests[n].start)): - tmp = self.requests[n] - index = self.requests.index(request) - self.requests[n] = request - self.requests[index] = tmp - # finally just use when they defined the partition so - # there's no randomness thrown in - elif (request.size and self.requests[n].size and - (request.size == self.requests[n].size) and - (request.uniqueID < self.requests[n].uniqueID)): - tmp = self.requests[n] - index = self.requests.index(request) - self.requests[n] = request - self.requests[index] = tmp - n = n + 1 - - tmp = self.getBootableRequest() - - boot = [] - if tmp: - for req in tmp: - # if raid, we want all of the contents of the bootable raid - if req.type == REQUEST_RAID: - for member in req.raidmembers: - boot.append(self.getRequestByID(member)) - else: - boot.append(req) - - # remove the bootables from the request - for bootable in boot: - self.requests.pop(self.requests.index(bootable)) - - # move to the front of the list - boot.extend(self.requests) - self.requests = boot - - def sanityCheckAllRequests(self, diskset, baseChecks = 0): - """Do a sanity check of all of the requests. - - 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 or None if there are none. - If baseChecks is set, the basic sanity tests which the UI runs prior to - accepting a partition will be run on the requests as well. - """ - checkSizes = [('/usr', 250), ('/tmp', 50), ('/var', 384), - ('/home', 100), ('/boot', 75)] - warnings = [] - errors = [] - - slash = self.getRequestByMountPoint('/') - if not slash: - errors.append(_("You have not defined a root partition (/), " - "which is required for installation of %s " - "to continue.") % (productName,)) - - if slash and slash.getActualSize(self, diskset) < 250: - warnings.append(_("Your root partition is less than 250 " - "megabytes which is usually too small to " - "install %s.") % (productName,)) - - if (slash and self.anaconda and - (slash.getActualSize(self, diskset) < - self.anaconda.backend.getMinimumSizeMB("/"))): - errors.append(_("Your %s partition is less than %s " - "megabytes which is lower than recommended " - "for a normal %s install.") - %("/", self.anaconda.backend.getMinimumSizeMB("/"), - productName)) - - def getBaseReqs(reqs): - n = 0 - while not reduce(lambda x,y: x and (y.type not in [REQUEST_RAID, REQUEST_LV]), - reqs, True) \ - and len(reqs) > n: - req = reqs[n] - if req.type == REQUEST_RAID: - for id in req.raidmembers: - reqs.append(self.getRequestByID(id)) - del reqs[n] - continue - elif req.type == REQUEST_LV: - del reqs[n] - continue - n += 1 - return reqs - - if iutil.isEfi(): - bootreq = self.getRequestByMountPoint("/boot/efi") - if (not bootreq) or \ - bootreq.fstype != fsset.fileSystemTypeGet("efi") or \ - bootreq.getActualSize(self, diskset) < 10: - errors.append(_("You must create an EFI System Partition of " - "at least 10 megabytes.")) - elif iutil.isX86(): - if iutil.isMactel(): - # mactel checks - bootreqs = self.getBootableRequest() or [] - for br in getBaseReqs(bootreqs): - if not isinstance(br, partRequests.PartitionSpec): - continue - (dev, num) = fsset.getDiskPart(br.device) - - if partedUtils.hasGptLabel(diskset, dev) \ - and int(num) > 4: - errors.append( - _("Your boot partition isn't on one of " - "the first four partitions and thus " - "won't be bootable.")) - - if iutil.getPPCMacGen() == "NewWorld": - reqs = self.getBootableRequest() - found = 0 - - bestreq = None - if reqs: - for req in reqs: - if req.fstype == fsset.fileSystemTypeGet("Apple Bootstrap"): - found = 1 - # the best one is either the first or the first - # newly formatted one - if ((bestreq is None) or ((bestreq.format == 0) and - (req.format == 1))): - bestreq = req - break - - if not found: - errors.append(_("You must create an Apple Bootstrap partition.")) - - if (iutil.getPPCMachine() == "pSeries" or - iutil.getPPCMachine() == "iSeries"): - reqs = self.getBootableRequest() - found = 0 - - bestreq = None - if reqs: - for req in reqs: - if req.fstype == fsset.fileSystemTypeGet("PPC PReP Boot"): - found = 1 - # the best one is either the first or the first - # newly formatted one - if ((bestreq is None) or ((bestreq.format == 0) and - (req.format == 1))): - bestreq = req - break - if iutil.getPPCMachine() == "iSeries" and iutil.hasiSeriesNativeStorage(): - found = 1 - - if not found: - errors.append(_("You must create a PPC PReP Boot partition.")) - - if bestreq is not None: - if (iutil.getPPCMachine() == "pSeries"): - minsize = 2 - else: - minsize = 12 - if bestreq.getActualSize(self, diskset) < minsize: - warnings.append(_("Your %s partition is less than %s " - "megabytes which is lower than " - "recommended for a normal %s install.") - %(_("PPC PReP Boot"), minsize, productName)) - - - for (mount, size) in checkSizes: - req = self.getRequestByMountPoint(mount) - if not req: - continue - if req.getActualSize(self, diskset) < size: - warnings.append(_("Your %s partition is less than %s " - "megabytes which is lower than recommended " - "for a normal %s install.") - %(mount, size, productName)) - - foundSwap = 0 - swapSize = 0 - usesUSB = False - usesFireWire = False - - for request in self.requests: - if request.fstype and request.fstype.getName() == "swap": - foundSwap = foundSwap + 1 - swapSize = swapSize + request.getActualSize(self, diskset) - if baseChecks: - rc = request.doSizeSanityCheck() - if rc: - warnings.append(rc) - rc = request.doMountPointLinuxFSChecks() - if rc: - errors.append(rc) - if isinstance(request, partRequests.RaidRequestSpec): - rc = request.sanityCheckRaid(self) - if rc: - errors.append(rc) - if not hasattr(request,'drive'): - continue - for x in request.drive or []: - if isys.driveUsesModule(x, ["usb-storage", "ub"]): - usesUSB = True - elif isys.driveUsesModule(x, ["sbp2", "firewire-sbp2"]): - usesFireWire = True - - if usesUSB: - warnings.append(_("Installing on a USB device. This may " - "or may not produce a working system.")) - if usesFireWire: - warnings.append(_("Installing on a FireWire device. This may " - "or may not produce a working system.")) - - bootreqs = self.getBootableRequest() - if bootreqs: - for bootreq in bootreqs: - if not bootreq: - continue - if (isinstance(bootreq, partRequests.RaidRequestSpec) and - not raid.isRaid1(bootreq.raidlevel)): - errors.append(_("Bootable partitions can only be on RAID1 " - "devices.")) - - # can't have bootable partition on LV - if (isinstance(bootreq, partRequests.LogicalVolumeRequestSpec)): - errors.append(_("Bootable partitions cannot be on a " - "logical volume.")) - - # most arches can't have boot on RAID - if (isinstance(bootreq, partRequests.RaidRequestSpec) and - iutil.getArch() not in raid.raidBootArches): - errors.append(_("Bootable partitions cannot be on a RAID " - "device.")) - - # Lots of filesystems types don't support /boot. - if (bootreq.fstype and not bootreq.fstype.isBootable()): - errors.append(_("Bootable partitions cannot be on an %s " - "filesystem.")%(bootreq.fstype.getName(),)) - - # vfat /boot is insane. - if (bootreq.mountpoint and bootreq.mountpoint == "/" and - bootreq.fstype and bootreq.fstype.getName() == "vfat"): - errors.append(_("Bootable partitions cannot be on an %s " - "filesystem.")%(bootreq.fstype.getName(),)) - - if (bootreq.isEncrypted(self)): - errors.append(_("Bootable partitions cannot be on an " - "encrypted block device")) - - if foundSwap == 0: - warnings.append(_("You have not specified a swap partition. " - "Although not strictly required in all cases, " - "it will significantly improve performance for " - "most installations.")) - - # XXX number of swaps not exported from kernel and could change - if foundSwap >= 32: - warnings.append(_("You have specified more than 32 swap devices. " - "The kernel for %s only supports 32 " - "swap devices.") % (productName,)) - - mem = iutil.memInstalled() - rem = mem % 16384 - if rem: - mem = mem + (16384 - rem) - mem = mem / 1024 - - if foundSwap and (swapSize < (mem - 8)) and (mem < 1024): - warnings.append(_("You have allocated less swap space (%dM) than " - "available RAM (%dM) on your system. This " - "could negatively impact performance.") - %(swapSize, mem)) - - if warnings == []: - warnings = None - if errors == []: - errors = None - - return (errors, warnings) - - def setProtected(self, dispatch): - """Set any partitions which should be protected to be so.""" - for device in self.protectedPartitions(): - log.info("%s is a protected partition" % (device)) - request = self.getRequestByDeviceName(device) - if request is not None: - request.setProtected(1) - else: - log.info("no request, probably a removable drive") - - def copy (self): - """Deep copy the object.""" - new = Partitions(self.anaconda) - for request in self.requests: - new.addRequest(request) - for delete in self.deletes: - new.addDelete(delete) - new.autoPartitionRequests = self.autoPartitionRequests - new.autoClearPartType = self.autoClearPartType - new.autoClearPartDrives = self.autoClearPartDrives - new.nextUniqueID = self.nextUniqueID - new.useAutopartitioning = self.useAutopartitioning - new.useFdisk = self.useFdisk - new.reinitializeDisks = self.reinitializeDisks - new.protected = self.protected - return new - - def getClearPart(self): - """Get the kickstart directive related to the clearpart being used.""" - clearpartargs = [] - if self.autoClearPartType == CLEARPART_TYPE_LINUX: - clearpartargs.append('--linux') - elif self.autoClearPartType == CLEARPART_TYPE_ALL: - clearpartargs.append('--all') - else: - return None - - if self.reinitializeDisks: - clearpartargs.append('--initlabel') - - if self.autoClearPartDrives: - drives = string.join(self.autoClearPartDrives, ',') - clearpartargs.append('--drives=%s' % (drives)) - - return "#clearpart %s\n" %(string.join(clearpartargs)) - - def writeKS(self, f): - """Write out the partitioning information in kickstart format.""" - 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 = self.getClearPart() - if clearpart: - f.write(clearpart) - - # lots of passes here -- parts, raid, volgroup, logvol - # XXX what do we do with deleted partitions? - for request in self.requests: - args = [] - if request.type == REQUEST_RAID: - continue - - # no fstype, no deal (same with foreigns) - if not request.fstype or request.fstype.getName() == "foreign": - continue - - # first argument is mountpoint, which can also be swap or - # the unique RAID identifier. I hate kickstart partitioning - # syntax. a lot. too many special cases - if request.fstype.getName() == "swap": - args.append("swap") - elif request.fstype.getName() == "software RAID": - # since we guarantee that uniqueIDs are ints now... - args.append("raid.%s" % (request.uniqueID)) - elif request.fstype.getName() == "physical volume (LVM)": - # see above about uniqueIDs being ints - args.append("pv.%s" % (request.uniqueID)) - elif request.fstype.getName() == "PPC PReP Boot": - args.extend(["prepboot", "--fstype", "\"PPC PReP Boot\""]) - elif request.fstype.getName() == "Apple Bootstrap": - args.extend(["appleboot", "--fstype", "\"Apple Bootstrap\""]) - elif request.mountpoint: - args.extend([request.mountpoint, "--fstype", - request.fstype.getName(quoted = 1)]) - else: - continue - - # generic options - if not request.format: - args.append("--noformat") - - # device encryption - if request.encryption: - args.append("--encrypted") - - # preexisting only - if request.type == REQUEST_PREEXIST and request.device: - args.append("--onpart") - args.append(request.device) - # we have a billion ways to specify new partitions - elif request.type == REQUEST_NEW: - if request.size: - args.append("--size=%s" % (int(request.size),)) - if request.size == 0: - args.append("--size=0") - if request.grow: - args.append("--grow") - if request.start: - args.append("--start=%s" % (request.start)) - if request.end: - args.append("--end=%s" % (request.end)) - if request.maxSizeMB: - args.append("--maxsize=%s" % (request.maxSizeMB)) - if request.drive: - args.append("--ondisk=%s" % (request.drive[0])) - if request.primary: - args.append("--asprimary") - else: # how the hell did we get this? - continue - - f.write("#part %s\n" % (string.join(args))) - - - for request in self.requests: - args = [] - if request.type != REQUEST_RAID: - continue - - # no fstype, no deal (same with foreigns) - if not request.fstype or request.fstype.getName() == "foreign": - continue - - # also require a raidlevel and raidmembers for raid - if (request.raidlevel == None) or not request.raidmembers: - continue - - # first argument is mountpoint, which can also be swap - if request.fstype.getName() == "swap": - args.append("swap") - elif request.fstype.getName() == "physical volume (LVM)": - # see above about uniqueIDs being ints - args.append("pv.%s" % (request.uniqueID)) - elif request.mountpoint: - args.append(request.mountpoint) - else: - continue - - # generic options - if not request.format: - args.append("--noformat") - if request.preexist: - args.append("--useexisting") - if request.fstype: - args.extend(["--fstype", request.fstype.getName(quoted = 1)]) - - # device encryption - if request.encryption: - args.append("--encrypted") - - args.append("--level=%s" % (request.raidlevel)) - args.append("--device=md%s" % (request.raidminor)) - - if request.raidspares: - args.append("--spares=%s" % (request.raidspares)) - - # silly raid member syntax - raidmems = [] - for member in request.raidmembers: - if (type(member) != type("")) or (member[0:5] != "raid."): - raidmems.append("raid.%s" % (member)) - else: - raidmems.append(member) - args.append("%s" % (string.join(raidmems))) - - f.write("#raid %s\n" % (string.join(args))) - - for request in self.requests: - args = [] - if request.type != REQUEST_VG: - continue - - args.append(request.volumeGroupName) - - # generic options - if not request.format: - args.append("--noformat") - if request.preexist: - args.append("--useexisting") - - args.append("--pesize=%s" %(request.pesize,)) - - # silly pv syntax - pvs = [] - for member in request.physicalVolumes: - if (type(member) != type("")) or not member.startswith("pv."): - pvs.append("pv.%s" % (member)) - else: - pvs.append(member) - args.append("%s" % (string.join(pvs))) - - f.write("#volgroup %s\n" % (string.join(args))) - - for request in self.requests: - args = [] - if request.type != REQUEST_LV: - continue - - # no fstype, no deal (same with foreigns) - if not request.fstype or request.fstype.getName() == "foreign": - continue - - # require a vg name and an lv name - if (request.logicalVolumeName is None or - request.volumeGroup is None): - continue - - # first argument is mountpoint, which can also be swap - if request.fstype.getName() == "swap": - args.append("swap") - elif request.mountpoint: - args.append(request.mountpoint) - else: - continue - - # generic options - if not request.format: - args.append("--noformat") - if request.preexist: - args.append("--useexisting") - if request.fstype: - args.extend(["--fstype", request.fstype.getName(quoted = 1)]) - - # device encryption - if request.encryption: - args.append("--encrypted") - - vg = self.getRequestByID(request.volumeGroup) - if vg is None: - continue - - args.extend(["--name=%s" %(request.logicalVolumeName,), - "--vgname=%s" %(vg.volumeGroupName,)]) - - if request.grow: - if request.startSize: - args.append("--size=%s" % (int(request.startSize),)) - else: - # shouldnt happen - continue - - args.append("--grow") - if request.maxSizeMB and int(request.maxSizeMB) > 0: - args.append("--maxsize=%s" % (request.maxSizeMB,)) - else: - if request.percent: - args.append("--percent=%s" %(request.percent,)) - elif request.size: - args.append("--size=%s" %(int(request.size),)) - else: - continue - - f.write("#logvol %s\n" % (string.join(args))) - - def deleteAllLogicalPartitions(self, part): - """Add delete specs for all logical partitions in part.""" - for partition in part.disk.getLogicalPartitions(): - partName = partition.getDeviceNodeName() - request = self.getRequestByDeviceName(partName) - self.removeRequest(request) - if request.preexist: - drive = partedUtils.get_partition_drive(partition) - delete = partRequests.DeleteSpec(drive, - partition.geometry.start, - partition.geometry.end) - self.addDelete(delete) - - def containsImmutablePart(self, part): - """Returns whether the partition contains parts we can't delete.""" - if not part or (type(part) == type("RAID")) or (type(part) == type(1)): - return None - - if not part.type & parted.PARTITION_EXTENDED: - return None - - disk = part.disk - while part: - if not part.active: - part = disk.nextPartition(part) - continue - - device = part.getDeviceNodeName() - request = self.getRequestByDeviceName(device) - - if request: - if request.getProtected(): - return _("the partition in use by the installer.") - - if self.isRaidMember(request): - return _("a partition which is a member of a RAID array.") - - if self.isLVMVolumeGroupMember(request): - return _("a partition which is a member of a LVM Volume Group.") - - part = disk.nextPartition(part) - return None - - - def doMetaDeletes(self, diskset): - """Does the removal of all of the non-physical volumes in the delete list.""" - - # have to have lvm on, which requires raid to be started - diskset.startMPath() - diskset.startDmRaid() - diskset.startMdRaid() - for luksDev in self.encryptedDevices.values(): - luksDev.openDevice() - lvm.vgactivate() - - snapshots = {} - for (lvvg, lv, size, lvorigin) in lvm.lvlist(): - snapshots.setdefault(lv, []) - if lvorigin: - snapshots.setdefault(lvorigin, []) - snapshots[lvorigin].append((lv, lvvg)) - - lvm_parent_deletes = [] - tmp = {} - def addSnap(name, vg): - if not snapshots.has_key(name): - return - snaps = snapshots[name] - for snap, snapvg in snaps: - addSnap(snap, snapvg) - if not tmp.has_key((name, vg)): - tmp[(name, vg)] = 1 - lvm_parent_deletes.append((name,vg)) - - # now, go through and delete logical volumes - for delete in self.deletes: - if isinstance(delete, partRequests.DeleteLogicalVolumeSpec): - if not delete.beenDeleted(): - addSnap(delete.name, delete.vg) - delete.setDeleted(1) - - for name,vg in lvm_parent_deletes: - log.info("removing lv %s" % (name,)) - key = "mapper/%s-%s" % (vg, name) - if key in self.encryptedDevices.keys(): - self.encryptedDevices[key].closeDevice() - del self.encryptedDevices[key] - lvm.lvremove(name, vg) - - # now, go through and delete volume groups - for delete in self.deletes: - if isinstance(delete, partRequests.DeleteVolumeGroupSpec): - if not delete.beenDeleted(): - lvm.vgremove(delete.name) - delete.setDeleted(1) - - lvm.vgdeactivate() - - # now, remove obsolete cryptodev instances - for (device, luksDev) in self.encryptedDevices.items(): - luksDev.closeDevice() - found = 0 - for req in self.requests: - if req.encryption == luksDev: - found = 1 - - if not found: - del self.encryptedDevices[device] - - diskset.stopMdRaid() - - def doMetaResizes(self, diskset): - """Does resizing of non-physical volumes.""" - - # have to have lvm on, which requires raid to be started - devicesActive = diskset.devicesOpen - if not devicesActive: - # should this not be diskset.openDevices() ? - diskset.startMPath() - diskset.startDmRaid() - diskset.startMdRaid() - - for luksDev in self.encryptedDevices.values(): - luksDev.openDevice() - lvm.vgactivate() - - # we only support resizing LVM of these types of things currently - for lv in self.getLVMLVRequests(): - if not lv.preexist: - continue - if lv.targetSize is None: - continue - - vg = self.getRequestByID(lv.volumeGroup) - if vg is None: - continue - - lvm.lvresize(lv.logicalVolumeName, vg.volumeGroupName, - lv.targetSize) - - lvm.vgdeactivate() - for luksDev in self.encryptedDevices.values(): - luksDev.closeDevice() - - if not devicesActive: - # should this not be diskset.closeDevices() ? - diskset.stopMdRaid() - diskset.stopDmRaid() - diskset.stopMPath() - - def doEncryptionRetrofits(self): - if not self.retrofitPassphrase or not self.encryptionPassphrase: - return - - for request in self.requests: - if not request.encryption: - continue - - # XXX this will only work before the new LUKS devices are created - # since the format flag gets unset when they are formatted - if request.encryption.format: - continue - - if request.encryption.addPassphrase(self.encryptionPassphrase): - log.error("failed to add new passphrase to existing device %s" % (request.encryption.getDevice(encrypted=1),)) - - def deleteDependentRequests(self, request): - """Handle deletion of this request and all requests which depend on it. - - eg, delete all logical volumes from a volume group, all volume groups - which depend on the raid device. - - Side effects: removes all dependent requests from self.requests - adds needed dependent deletes to self.deletes - """ - - toRemove = [] - id = request.uniqueID - for req in self.requests: - if isinstance(req, partRequests.RaidRequestSpec): - if id in req.raidmembers: - toRemove.append(req) - # XXX do we need to do anything special with preexisting raids? - elif isinstance(req, partRequests.VolumeGroupRequestSpec): - if id in req.physicalVolumes: - toRemove.append(req) - if req.getPreExisting(): - delete = partRequests.DeleteVolumeGroupSpec(req.volumeGroupName) - self.addDelete(delete) - elif isinstance(req, partRequests.LogicalVolumeRequestSpec): - if id == req.volumeGroup: - toRemove.append(req) - tmp = self.getRequestByID(req.volumeGroup) - if not tmp: - log.error("Unable to find the vg for %s" - % (req.logicalVolumeName,)) - vgname = req.volumeGroup - else: - vgname = tmp.volumeGroupName - - if req.getPreExisting(): - delete = partRequests.DeleteLogicalVolumeSpec(req.logicalVolumeName, - vgname) - self.addDelete(delete) - - for req in toRemove: - self.deleteDependentRequests(req) - self.removeRequest(req) - diff --git a/platform.py b/platform.py new file mode 100644 index 000000000..59b87273b --- /dev/null +++ b/platform.py @@ -0,0 +1,414 @@ +# +# platform.py: Architecture-specific information +# +# Copyright (C) 2009 +# Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: Chris Lumens <clumens@redhat.com> +# + +import iutil +import parted +import storage +from storage.errors import * + +import gettext +_ = lambda x: gettext.ldgettext("anaconda", x) +N_ = lambda x: x + +class Platform(object): + """Platform + + A class containing platform-specific information and methods for use + during installation. The intent is to eventually encapsulate all the + architecture quirks in one place to avoid lots of platform checks + throughout anaconda.""" + _bootFSType = "ext3" + _bootloaderPackage = None + _diskType = parted.diskType["msdos"] + _minimumSector = 0 + _supportsMdRaidBoot = False + _minBootPartSize = 50 + _maxBootPartSize = 0 + + def __init__(self, anaconda): + """Creates a new Platform object. This is basically an abstract class. + You should instead use one of the platform-specific classes as + returned by getPlatform below. Not all subclasses need to provide + all the methods in this class.""" + self.anaconda = anaconda + + def _mntDict(self): + """Return a dictionary mapping mount points to devices.""" + ret = {} + for device in [d for d in self.anaconda.id.storage.devices if d.format.mountable]: + ret[device.format.mountpoint] = device + + return ret + + def bootDevice(self): + """Return the device where /boot is mounted.""" + if self.__class__ is Platform: + raise NotImplementedError("bootDevice not implemented for this platform") + + mntDict = self._mntDict() + return mntDict.get("/boot", mntDict.get("/")) + + @property + def bootFSType(self): + """Return the default filesystem type for the boot partition.""" + return self._bootFSType + + def bootloaderChoices(self, bl): + """Return the default list of places to install the bootloader. + This is returned as a dictionary of locations to (device, identifier) + tuples. If there is no boot device, an empty dictionary is + returned.""" + if self.__class__ is Platform: + raise NotImplementedError("bootloaderChoices not implemented for this platform") + + bootDev = self.bootDevice() + ret = {} + + if not bootDev: + return ret + + if bootDev.type == "mdarray": + ret["boot"] = (bootDev.name, N_("RAID Device")) + ret["mbr"] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) + else: + ret["boot"] = (bootDev.name, N_("First sector of boot partition")) + ret["mbr"] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) + + return ret + + @property + def bootloaderPackage(self): + return self._bootloaderPackage + + def checkBootRequest(self, req): + """Perform an architecture-specific check on the boot device. Not all + platforms may need to do any checks. Raises an exception if there + is a problem, or returns True otherwise.""" + return + + @property + def diskType(self): + """Return the disk label type as a parted.DiskType.""" + return self._diskType + + @diskType.setter + def diskType(self, value): + """Sets the disk label type.""" + self._diskType = value + + @property + def minimumSector(self, disk): + """Return the minimum starting sector for the provided disk.""" + return self._minimumSector + + def setDefaultPartitioning(self): + """Return the default platform-specific partitioning information.""" + return [("/boot", self.bootFSType, 200, None, 0, 0)] + + @property + def supportsMdRaidBoot(self): + """Does the platform support /boot on MD RAID?""" + return self._supportsMdRaidBoot + + @property + def minBootPartSize(self): + return self._minBootPartSize + + @property + def maxBootPartSize(self): + return self._maxBootPartSize + + def validBootPartSize(self, size): + """ Is the given size (in MB) acceptable for a boot device? """ + if not isinstance(size, int) and not isinstance(size, float): + return False + + return ((not self.minBootPartSize or size >= self.minBootPartSize) + and + (not self.maxBootPartSize or size <= self.maxBootPartSize)) + +class EFI(Platform): + _diskType = parted.diskType["gpt"] + _minBootPartSize = 50 + _maxBootPartSize = 256 + + def bootDevice(self): + mntDict = self._mntDict() + return mntDict.get("/boot/efi") + + def bootloaderChoices(self, bl): + bootDev = self.bootDevice() + ret = {} + + if not bootDev: + return ret + + ret["boot"] = (bootDev.name, N_("EFI System Partition")) + return ret + + def checkBootRequest(self, req): + if req.format.mountpoint == "/boot": + if not req.format.type.startswith("ext"): + raise FSError("/boot is not ext2") + elif req.format.mountpoint == "/boot/efi": + if req.format.type != "efi": + raise FSError("/boot/efi is not efi") + + def setDefaultPartitioning(self): + ret = Platform.setDefaultPartitioning(self) + + # Only add the EFI partition to the default set if there's not already + # one on the system. + if len(filter(lambda dev: dev.format.type == "efi" and self.validBootPartSize(dev.size), + self.anaconda.id.storage.partitions)) == 0: + ret.append(("/boot/efi", "efi", 50, 200, 1, 0)) + + return ret + +class Alpha(Platform): + _diskType = parted.diskType["bsd"] + + def checkBootRequest(self, req): + disk = req.disk + if not disk: + raise DeviceError("Boot partition has no disk") + + disk = disk.partedDisk + + # Check that we're a BSD disk label + if not disk.type == self.diskType: + raise DeviceError("Disk label is not %s" % self.diskType) + + # The first free space should start at the beginning of the drive and + # span for a megabyte or more. + free = disk.getFirstPartition() + while free: + if free.type & parted.PARTITION_FREESPACE: + break + + free = free.nextPartition() + + if not free or free.geoemtry.start != 1L or free.getSize(unit="MB") < 1: + raise DeviceError("Disk does not have enough free space at the beginning") + + return + +class IA64(EFI): + _bootloaderPackage = "elilo" + + def __init__(self, anaconda): + EFI.__init__(self, anaconda) + +class PPC(Platform): + _bootloaderPackage = "yaboot" + _ppcMachine = iutil.getPPCMachine() + _supportsMdRaidBoot = True + + @property + def ppcMachine(self): + return self._ppcMachine + +class IPSeriesPPC(PPC): + _minBootPartSize = 4 + _maxBootPartSize = 10 + + def bootDevice(self): + bootDev = None + + # We want the first PReP partition. + for device in self.anaconda.id.storage.partitions: + if device.format.type == "prepboot": + bootDev = device + break + + return bootDev + + def bootloaderChoices(self, bl): + ret = {} + + bootDev = self.bootDevice() + if not bootDev: + return ret + + if bootDev.type == "mdarray": + ret["boot"] = (bootDev.name, N_("RAID Device")) + ret["mbr"] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) + else: + ret["boot"] = (bootDev.name, N_("PPC PReP Boot")) + + return ret + + def checkBootRequest(self, req): + bootPart = getattr(req, "partedPartition", None) + if not bootPart: + raise DeviceError("Boot partition has no partedPartition") + + if bootPart.geometry.end * bootPart.geometry.device.sectorSize / (1024.0 * 1024) > 4096: + raise DeviceError("Boot partition is located too high") + + def setDefaultPartitioning(self): + ret = PPC.setDefaultPartitioning(self) + ret.insert(0, (None, "PPC PReP Boot", 4, None, 0, 0)) + return ret + +class NewWorldPPC(PPC): + _diskType = parted.diskType["mac"] + _minBootPartSize = (800.00 / 1024.00) + _maxBootPartSize = 1 + + def bootDevice(self): + bootDev = None + + for part in self.anaconda.id.storage.partitions: + if part.format.type == "appleboot" and self.validBootPartSize(part.size): + bootDev = part + # if we're only picking one, it might as well be the first + break + + return bootDev + + def bootloaderChoices(self, bl): + ret = {} + + bootDev = self.bootDevice() + if not bootDev: + return ret + + if bootDev.type == "mdarray": + ret["boot"] = (bootDev.name, N_("RAID Device")) + ret["mbr"] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) + else: + ret["boot"] = (bootDev.name, N_("Apple Bootstrap")) + for (n, device) in enumerate(self.anaconda.id.storage.partitions): + if device.format.type == "appleboot" and device.path != bootDev.path: + ret["boot%d" % n] = (device.path, N_("Apple Bootstrap")) + + return ret + + def setDefaultPartitioning(self): + ret = Platform.setDefaultPartitioning(self) + ret.insert(0, (None, "Apple Bootstrap", 1, 1, 0, 0)) + return ret + +class S390(Platform): + _bootloaderPackage = "s390utils" + + def __init__(self, anaconda): + Platform.__init__(self, anaconda) + +class Sparc(Platform): + _diskType = parted.diskType["sun"] + + @property + def minimumSector(self, disk): + (cylinders, heads, sector) = disk.device.biosGeometry + start = long(sectors * heads) + start /= long(1024 / disk.device.sectorSize) + return start+1 + +class X86(EFI): + _bootloaderPackage = "grub" + _isEfi = iutil.isEfi() + _supportsMdRaidBoot = True + + def __init__(self, anaconda): + EFI.__init__(self, anaconda) + + if self.isEfi: + self.diskType = parted.diskType["gpt"] + else: + self.diskType = parted.diskType["msdos"] + + def bootDevice(self): + if self.isEfi: + return EFI.bootDevice(self) + else: + return Platform.bootDevice(self) + + def bootloaderChoices(self, bl): + if self.isEfi: + return EFI.bootloaderChoices(self, bl) + + bootDev = self.bootDevice() + ret = {} + + if not bootDev: + return {} + + if bootDev.type == "mdarray": + ret["boot"] = (bootDev.name, N_("RAID Device")) + ret["mbr"] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) + else: + ret["boot"] = (bootDev.name, N_("First sector of boot partition")) + ret["mbr"] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) + + return ret + + @property + def isEfi(self): + return self._isEfi + + @property + def maxBootPartSize(self): + if self.isEfi: + return EFI._maxBootPartSize + else: + return Platform._maxBootPartSize + + @property + def minBootPartSize(self): + if self.isEFI: + return EFI._minBootPartSize + else: + return Platform._minBootPartSize + + def setDefaultPartitioning(self): + if self.isEfi: + return EFI.setDefaultPartitioning(self) + else: + return Platform.setDefaultPartitioning(self) + +def getPlatform(anaconda): + """Check the architecture of the system and return an instance of a + Platform subclass to match. If the architecture could not be determined, + raise an exception.""" + if iutil.isAlpha(): + return Alpha(anaconda) + elif iutil.isIA64(): + return IA64(anaconda) + elif iutil.isPPC(): + ppcMachine = iutil.getPPCMachine() + + if (ppcMachine == "PMac" and iutil.getPPCMacGen() == "NewWorld") or ppcMachine == "PS3": + return NewWorldPPC(anaconda) + elif ppcMachine in ["iSeries", "pSeries"]: + return IPSeriesPPC(anaconda) + else: + raise SystemError, "Unsupported PPC machine type" + elif iutil.isS390(): + return S390(anaconda) + elif iutil.isSparc(): + return Sparc(anaconda) + elif iutil.isX86(): + return X86(anaconda) + else: + raise SystemError, "Could not determine system architecture." diff --git a/raid.py b/raid.py deleted file mode 100644 index c0f2e692f..000000000 --- a/raid.py +++ /dev/null @@ -1,228 +0,0 @@ -# -# raid.py - raid probing control -# -# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 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 <http://www.gnu.org/licenses/>. -# -# Author(s): Erik Troan <ewt@redhat.com> -# - -"""Raid probing control.""" - -def getRaidLevels(): - avail = [] - try: - f = open("/proc/mdstat", "r") - except: - pass - else: - for l in f.readlines(): - if not l.startswith("Personalities"): - continue - - lst = l.split() - - for lev in ["RAID0", "RAID1", "RAID5", "RAID6", "RAID10"]: - if "[" + lev + "]" in lst or "[" + lev.lower() + "]" in lst: - avail.append(lev) - - f.close() - - avail.sort() - return avail - -# XXX define availraidlevels and defaultmntpts as arch characteristics -availRaidLevels = getRaidLevels() - -import parted -import isys -import os -import partitions -import partedUtils - -import logging -log = logging.getLogger("anaconda") - -# these arches can have their /boot on RAID and not have their -# boot loader blow up -raidBootArches = [ "i386", "x86_64", "ppc" ] - -def scanForRaid(drives): - """Scans for raid devices on drives. - - drives is a list of device names. - Returns a list of (mdMinor, devices, level, totalDisks) tuples. - """ - - raidSets = {} - raidDevices = {} - - for d in drives: - parts = [] - try: - dev = parted.getDevice("/dev/%s" % (d,)) - disk = parted.Disk(device=dev) - - raidParts = disk.getRaidPartitions() - for part in raidParts: - # if the part is encrypted, add the mapped dev instead - pname = part.getDeviceNodeName() - cryptoDev = partitions.Partitions.encryptedDevices.get(pname) - if cryptoDev and not cryptoDev.openDevice(): - dev = cryptoDev.getDevice() - else: - dev = pname - parts.append(dev) - except: - pass - - for dev in parts: - try: - (major, minor, raidSet, level, nrDisks, totalDisks, mdMinor) =\ - isys.raidsb(dev) - except ValueError: - # bad magic, this can't be part of our raid set - log.error("reading raid sb failed for %s",dev) - continue - - if raidSets.has_key(raidSet): - (knownLevel, knownDisks, knownMinor, knownDevices) = \ - raidSets[raidSet] - if knownLevel != level or knownDisks != totalDisks or \ - knownMinor != mdMinor: - # Raise hell - log.error("raid set inconsistency for md%d: " - "all drives in this raid set do not " - "agree on raid parameters. Skipping raid device", - mdMinor) - continue - knownDevices.append(dev) - raidSets[raidSet] = (knownLevel, knownDisks, knownMinor, - knownDevices) - else: - raidSets[raidSet] = (level, totalDisks, mdMinor, [dev,]) - - if raidDevices.has_key(mdMinor): - if (raidDevices[mdMinor] != raidSet): - log.error("raid set inconsistency for md%d: " - "found members of multiple raid sets " - "that claim to be md%d. Using only the first " - "array found.", mdMinor, mdMinor) - continue - else: - raidDevices[mdMinor] = raidSet - - raidList = [] - for key in raidSets.keys(): - (level, totalDisks, mdMinor, devices) = raidSets[key] - if len(devices) < totalDisks: - log.warning("missing components of raid device md%d. The " - "raid device needs %d drive(s) and only %d (was/were) " - "found. This raid device will not be started.", mdMinor, - totalDisks, len(devices)) - continue - raidList.append((mdMinor, devices, level, totalDisks)) - - return raidList - -def startAllRaid(driveList): - """Do a raid start on raid devices and return a list like scanForRaid.""" - rc = [] - mdList = scanForRaid(driveList) - for mdDevice, deviceList, level, numActive in mdList: - devName = "md%d" % (mdDevice,) - isys.raidstart(devName, deviceList[0]) - rc.append((devName, deviceList, level, numActive)) - return rc - -def stopAllRaid(mdList): - """Do a raid stop on each of the raid device tuples given.""" - for dev, devices, level, numActive in mdList: - isys.raidstop(dev) - -def isRaid10(raidlevel): - """Return whether raidlevel is a valid descriptor of RAID10.""" - if raidlevel in ("RAID10", "10", 10): - return True - return False - -def isRaid6(raidlevel): - """Return whether raidlevel is a valid descriptor of RAID6.""" - if raidlevel in ("RAID6", "6", 6): - return True - return False - -def isRaid5(raidlevel): - """Return whether raidlevel is a valid descriptor of RAID5.""" - if raidlevel in ("RAID5", "5", 5): - return True - return False - -def isRaid1(raidlevel): - """Return whether raidlevel is a valid descriptor of RAID1.""" - if raidlevel in ("mirror", "RAID1", "1", 1): - return True - return False - -def isRaid0(raidlevel): - """Return whether raidlevel is a valid descriptor of RAID0.""" - if raidlevel in ("stripe", "RAID0", "0", 0): - return True - return False - -def get_raid_min_members(raidlevel): - """Return the minimum number of raid members required for raid level""" - if isRaid0(raidlevel): - return 2 - elif isRaid1(raidlevel): - return 2 - elif isRaid5(raidlevel): - return 3 - elif isRaid6(raidlevel): - return 4 - elif isRaid10(raidlevel): - return 4 - else: - raise ValueError, "invalid raidlevel in get_raid_min_members" - -def get_raid_max_spares(raidlevel, nummembers): - """Return the maximum number of raid spares for raidlevel.""" - if isRaid0(raidlevel): - return 0 - elif isRaid1(raidlevel) or isRaid5(raidlevel) or isRaid6(raidlevel) or isRaid10(raidlevel): - return max(0, nummembers - get_raid_min_members(raidlevel)) - else: - raise ValueError, "invalid raidlevel in get_raid_max_spares" - -def register_raid_device(mdname, newdevices, newlevel, newnumActive): - """Register a new RAID device in the mdlist.""" - for dev, devices, level, numActive in partedUtils.DiskSet.mdList: - if mdname == dev: - if (devices != newdevices or level != newlevel or numActive != newnumActive): - raise ValueError, "%s is already in the mdList!" % (mdname,) - else: - return - partedUtils.DiskSet.mdList.append((mdname, newdevices[:], newlevel, - newnumActive)) - -def lookup_raid_device(mdname): - """Return the requested RAID device information.""" - for dev, devices, level, numActive in partedUtils.DiskSet.mdList: - if mdname == dev: - return (dev, devices, level, numActive) - raise KeyError, "md device not found" - - @@ -29,8 +29,8 @@ from flags import flags import sys import os import isys +from storage import mountExistingSystem import iutil -import fsset import shutil import time import network @@ -101,14 +101,17 @@ class RescueInterface: # XXX grub-install is stupid and uses df output to figure out # things when installing grub. make /etc/mtab be at least # moderately useful. -def makeMtab(instPath, theFsset): - child = os.fork() - if (not child): - os.chroot(instPath) - f = open("/etc/mtab", "w+") - f.write(theFsset.mtab()) +def makeMtab(instPath, fsset): + try: + f = open(instPath + "/etc/mtab", "w+") + except IOError, e: + log.info("failed to open /etc/mtab for write: %s" % e) + return + + try: + f.write(fsset.mtab()) + finally: f.close() - os._exit(0) # make sure they have a resolv.conf in the chroot def makeResolvConf(instPath): @@ -220,15 +223,15 @@ def runRescue(anaconda, instClass): sys.exit(0) + screen = SnackScreen() + anaconda.intf = RescueInterface(screen) + if anaconda.isKickstart: if anaconda.id.ksdata.rescue and anaconda.id.ksdata.rescue.romount: readOnly = 1 else: readOnly = 0 else: - screen = SnackScreen() - anaconda.intf = RescueInterface(screen) - # prompt to see if we should try and find root filesystem and mount # everything in /etc/fstab on that root rc = ButtonChoiceWindow(screen, _("Rescue"), @@ -252,6 +255,9 @@ def runRescue(anaconda, instClass): else: readOnly = 0 + import storage + storage.storageInitialize(anaconda) + disks = upgrade.findExistingRoots(anaconda, upgradeany = 1) if not disks: @@ -265,17 +271,17 @@ def runRescue(anaconda, instClass): else: scroll = 0 - partList = [] - for (drive, fs, relstr, label, uuid) in disks: - if label: - partList.append("%s (%s)" % (drive, label)) + devList = [] + for (device, relstr) in disks: + if getattr(device.format, "label", None): + devList.append("%s (%s) - %s" % (device.name, device.format.label, relstr)) else: - partList.append(drive) + devList.append("%s - %s" % (device.name, relstr)) (button, choice) = \ ListboxChoiceWindow(screen, _("System to Rescue"), - _("What partition holds the root partition " - "of your installation?"), partList, + _("Which device holds the root partition " + "of your installation?"), devList, [ _("OK"), _("Exit") ], width = 30, scroll = scroll, height = height, help = "multipleroot") @@ -289,13 +295,9 @@ def runRescue(anaconda, instClass): if root: try: - fs = fsset.FileSystemSet() - - # only pass first two parts of tuple for root, since third - # element is a comment we dont want - rc = upgrade.mountRootPartition(anaconda, root[:2], fs, - allowDirty = 1, warnDirty = 1, - readOnly = readOnly) + rc = mountExistingSystem(anaconda, root, + allowDirty = 1, warnDirty = 1, + readOnly = readOnly) if rc == -1: if anaconda.isKickstart: @@ -325,16 +327,10 @@ def runRescue(anaconda, instClass): # now turn on swap if not readOnly: try: - fs.turnOnSwap("/") + anaconda.id.storage.fsset.turnOnSwap(anaconda.intf) except: log.error("Error enabling swap") - # now that dev is udev, bind mount the installer dev there - isys.mount("/dev", "%s/dev" %(anaconda.rootPath,), bindMount = 1) - - # and /dev/pts - isys.mount("/dev/pts", "%s/dev/pts" %(anaconda.rootPath,), bindMount = 1) - # and /selinux too if flags.selinux and os.path.isdir("%s/selinux" %(anaconda.rootPath,)): try: @@ -412,7 +408,7 @@ def runRescue(anaconda, instClass): msgStr = "" if rootmounted and not readOnly: - makeMtab(anaconda.rootPath, fs) + makeMtab(anaconda.rootPath, anaconda.id.storage.fsset) try: makeResolvConf(anaconda.rootPath) except Exception, e: diff --git a/run_test.py b/run_test.py new file mode 100755 index 000000000..fd24fa972 --- /dev/null +++ b/run_test.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +#-*- coding:utf-8 -*- + +import sys + +REQUIRED_PATHS = ["/usr/lib/anaconda", + "/usr/share/system-config-date"] +sys.path.extend(REQUIRED_PATHS) + +import unittest +import tests +import string +from optparse import OptionParser + + +def getFullTestName(test, full_test_names): + tests = [] + for full_test_name in full_test_names: + if full_test_name.lower().find(test) != -1: + tests.append(full_test_name) + + return tests + + +if __name__ == "__main__": + usage = "usage: %prog [options] [test1 test2 ...]" + parser = OptionParser(usage) + parser.add_option("-l", "--list", action="store_true", default=False, + help="print all available tests and exit") + + (options, args) = parser.parse_args(sys.argv[1:]) + + print "Searching for test suites" + available_suites = tests.getAvailableSuites() + test_keys = available_suites.keys() + if not test_keys: + print "No test suites available, exiting" + sys.exit(1) + + test_keys.sort() + + if options.list: + print "\nAvailable tests:" + for test_name in test_keys: + print test_name + sys.exit(0) + + tests_to_run = [] + + if len(args) == 0: + # interactive mode + print "Running in interactive mode" + print "\nAvailable tests:" + test_num = 0 + for test_name in test_keys: + print "[%3d] %s" % (test_num, test_name) + test_num += 1 + print + + try: + input_string = raw_input("Type in the test you want to run, " + "or \"all\" to run all listed tests: ") + except KeyboardInterrupt as e: + print "\nAborted by user" + sys.exit(1) + + for arg in input_string.split(): + if arg.isdigit(): + arg = int(arg) + try: + args.append(test_keys[arg]) + except KeyError as e: + pass + else: + args.append(arg) + + args = map(string.lower, args) + if "all" in args: + tests_to_run = test_keys[:] + else: + for arg in args: + matching_tests = getFullTestName(arg, test_keys) + tests_to_run.extend(filter(lambda test: test not in tests_to_run, + matching_tests)) + + # run the tests + if tests_to_run: + tests_to_run.sort() + print "Running tests: %s" % tests_to_run + test_suite = unittest.TestSuite([available_suites[test] + for test in tests_to_run]) + + try: + result = unittest.TextTestRunner(verbosity=2).run(test_suite) + except KeyboardInterrupt as e: + print "\nAborted by user" + sys.exit(1) + + if result.wasSuccessful(): + print "\nAll tests OK" + sys.exit(0) + else: + print "\nTests finished with %d errors and %d failures" % (len(result.errors), + len(result.failures)) + sys.exit(2) + else: + print "No test suites matching your criteria found, exiting" + sys.exit(1) diff --git a/runpychecker.sh b/runpychecker.sh index 2a330ef85..033017493 100755 --- a/runpychecker.sh +++ b/runpychecker.sh @@ -37,7 +37,7 @@ if [ "`tail -c 1 pychecker-false-positives`" == "`echo`" ]; then exit 1 fi -export PYTHONPATH="isys:textw:iw:installclasses:/usr/lib/booty:/usr/share/system-config-date" +export PYTHONPATH="isys:textw:iw:installclasses:/usr/share/system-config-date" pychecker --only --limit 1000 \ --maxlines 500 --maxargs 20 --maxbranches 80 --maxlocals 60 --maxreturns 20 \ @@ -45,7 +45,7 @@ pychecker --only --limit 1000 \ --no-import --no-miximport --no-pkgimport --no-reimport \ --no-argsused --no-varargsused --no-override \ $NON_STRICT_OPTIONS \ - anaconda anaconda *.py textw/*.py iw/*.py installclasses/*.py isys/*.py | \ + anaconda anaconda *.py textw/*.py iw/*.py installclasses/*.py isys/*.py booty/*.py booty/*/*.py | \ egrep -v "`cat $FALSE_POSITIVES | tr '\n' '|'`" > pychecker-log if [ -s pychecker-log ]; then diff --git a/scripts/buildinstall b/scripts/buildinstall index ab8d2df49..71641ccb8 100755 --- a/scripts/buildinstall +++ b/scripts/buildinstall @@ -223,7 +223,7 @@ echo "Making images..." $MK_IMAGES $DEBUGSTR $NOGRSTR --imgdir $TREEDIR/install --arch $BASEARCH --product "$PRODUCTSTR" --version $VERSION --bugurl "$BUGURL" --output $OUTPUT $yumconf || die "image creation failed" echo "Writing .discinfo file" -$MK_STAMP --releasestr="$RELEASESTR" --arch=$BUILDARCH --discNum="ALL" --outfile=$OUTPUT/.discinfo +$MK_STAMP --releasestr="$RELEASESTR" --arch=$BASEARCH --discNum="ALL" --outfile=$OUTPUT/.discinfo rm -rf $TREEDIR $BUILDINSTDIR rm -f $yumconf diff --git a/scripts/mk-images b/scripts/mk-images index d4cac465f..e669a0b6c 100755 --- a/scripts/mk-images +++ b/scripts/mk-images @@ -596,11 +596,18 @@ makeinitrd() { install -m 644 $IMGPATH/etc/passwd $MBD_DIR/etc/passwd install -m 644 $IMGPATH/etc/group $MBD_DIR/etc/group install -m 644 $IMGPATH/etc/nsswitch.conf $MBD_DIR/etc/nsswitch.conf + install -m 644 $IMGPATH/etc/hosts $MBD_DIR/etc/hosts instbin $IMGPATH /usr/bin/mount $MBD_DIR /sbin/mount - instbin $IMGPATH /usr/sbin/mount.nfs $MBD_DIR /sbin/mount.nfs + for mountcmd in $IMGPATH/usr/sbin/mount.* ; do + cmd="$(basename $mountcmd)" + instbin $IMGPATH /usr/sbin/$cmd $MBD_DIR /sbin/$cmd + done instbin $IMGPATH /usr/bin/umount $MBD_DIR /sbin/umount - ln -s mount.nfs $MBD_DIR/sbin/umount.nfs + for umountcmd in $IMGPATH/usr/sbin/umount.* ; do + cmd="$(basename $umountcmd)" + instbin $IMGPATH /usr/sbin/$cmd $MBD_DIR /sbin/$cmd + done instbin $IMGPATH /usr/sbin/udevd $MBD_DIR /sbin/udevd instbin $IMGPATH /usr/sbin/udevadm $MBD_DIR /sbin/udevadm diff --git a/scripts/mk-images.efi b/scripts/mk-images.efi index 687d76c01..7c2595033 100644 --- a/scripts/mk-images.efi +++ b/scripts/mk-images.efi @@ -53,25 +53,25 @@ makeefibootimage() { MBD_BOOTTREE=${TMPDIR:-/tmp}/makebootdisk.tree.$$ MBD_BOOTTREE_TMP=$MBD_BOOTTREE'_tmp' while [ x$(echo $1 | cut -c1-2) = x"--" ]; do - if [ $1 = "--kernel" ]; then - KERNELFILE=$2 - shift; shift - continue - elif [ $1 = "--initrd" ]; then - INITRDFILE=$2 - shift; shift - continue - elif [ $1 = "--imagename" ]; then - MBD_FILENAME=$IMAGEPATH/$2 - shift; shift - continue - elif [ $1 = "--grubpkg" ]; then - grubpkg=$2 - shift; shift - continue - fi - echo "Unknown option passed to makebootdisk" - exit 1 + if [ $1 = "--kernel" ]; then + KERNELFILE=$2 + shift; shift + continue + elif [ $1 = "--initrd" ]; then + INITRDFILE=$2 + shift; shift + continue + elif [ $1 = "--imagename" ]; then + MBD_FILENAME=$IMAGEPATH/$2 + shift; shift + continue + elif [ $1 = "--grubpkg" ]; then + grubpkg=$2 + shift; shift + continue + fi + echo "Unknown option passed to makebootdisk" + exit 1 done if [ -z "$MBD_FILENAME" ]; then @@ -79,15 +79,6 @@ makeefibootimage() { exit 1 fi - if [ -z "$KERNELFILE" ]; then - echo "No kernel file passed" - exit 1 - fi - - if [ -z "$INITRDFILE" ]; then - echo "No initrd file passed" - exit 1 - fi MBD_FSIMAGE="$INITRDFILE" mkdir -p $MBD_BOOTTREE @@ -110,7 +101,7 @@ makeefibootimage() { mkdir -p `dirname $MBD_FILENAME` rm -rf $MBD_TMPIMAGE $MBD_MNTPOINT $MBD_BOOTTREE - if [ -z "$INITRDFILE" ]; then + if [ -z "$INITRDFILE" -a -n "$MBD_FSIMAGE" ]; then rm -f $MBD_FSIMAGE fi @@ -137,8 +128,8 @@ prepareEfiTree() { mkdir -p $MBD_BOOTTREE_TMP/EFI/boot cp -a $BOOTDISKDIR/* $MBD_BOOTTREE_TMP/EFI/boot/ - cp $INITRDFILE $MBD_BOOTTREE_TMP/EFI/boot/initrd.img - cp $KERNELFILE $MBD_BOOTTREE_TMP/EFI/boot/vmlinuz + [ -n "$INITRDFILE" ] && cp $INITRDFILE $MBD_BOOTTREE_TMP/EFI/boot/initrd.img + [ -n "$KERNELFILE" ] && cp $KERNELFILE $MBD_BOOTTREE_TMP/EFI/boot/vmlinuz sed -i "s/@PRODUCT@/$PRODUCT/g" $MBD_BOOTTREE_TMP/EFI/boot/grub.conf sed -i "s/@VERSION@/$VERSION/g" $MBD_BOOTTREE_TMP/EFI/boot/grub.conf @@ -165,10 +156,18 @@ prepareEfiTree() { yumdownloader -c ${yumconf} ${artpkg} rpm2cpio ${artpkg}.rpm | (cd $KERNELROOT; cpio --quiet -iumd) cp $KERNELROOT/boot/grub/splash.xpm.gz $MBD_BOOTTREE_TMP/EFI/boot/splash.xpm.gz + + # if we don't have a kernel or initrd, we're making a CD image and we need + # to mirror EFI/ to the cd. + if [ -z "$KERNELFILE" -a -z "$INITRDFILE" ]; then + cp -av $MBD_BOOTTREE_TMP/EFI/ $TOPDESTPATH/EFI/ + rm -f $TOPDESTPATH/EFI/boot/*.efi + fi } makeEfiImages() { yumconf="$1" + echo "Making EFI images ($PWD)" if [ "$kernelvers" != "$kernelxen" ]; then local grubarch=${efiarch} case ${efiarch} in @@ -190,10 +189,24 @@ makeEfiImages() { --initrd $TOPDESTPATH/images/pxeboot/initrd.img \ --grubpkg ${grubpkg} local ret=$? - [ $ret -eq 0 ] || return $ret + if [ $ret -ne 0 ]; then + echo "makeefibootimage (1) failed" >&2 + return $ret + fi makeefibootdisk $TOPDESTPATH/images/pxeboot/efiboot.img $TOPDESTPATH/images/efidisk.img - return $? + [ $ret -eq 0 ] || return $ret + local ret=$? + + # make a boot image with just boot*.efi in it... + makeefibootimage \ + --imagename pxeboot/efiboot.img \ + --grubpkg ${grubpkg} + local ret=$? + if [ $ret -ne 0 ]; then + echo "makeefibootimage (2) failed" >&2 + fi + return $ret fi - return 1 + return 0 } diff --git a/scripts/mk-images.x86 b/scripts/mk-images.x86 index 813d94185..fe7a978b9 100644 --- a/scripts/mk-images.x86 +++ b/scripts/mk-images.x86 @@ -158,11 +158,16 @@ __EOT__ doPostImages() { if [ -n "$BOOTISO" ]; then EFIARGS="" - if [ -f isolinux/efiboot.img ]; then + EFIGRAFT="" + if [ -f $TOPDESTPATH/images/pxeboot/efiboot.img ]; then + cp $TOPDESTPATH/images/pxeboot/efiboot.img $TOPDESTPATH/isolinux/efiboot.img EFIARGS="-eltorito-alt-boot -e isolinux/efiboot.img -no-emul-boot" + EFIGRAFT="EFI/boot=$TOPDESTPATH/EFI/boot" fi - mkisofs -o $TOPDESTPATH/images/$BOOTISO -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table $EFIARGS -R -J -V "$PRODUCT" -T -graft-points isolinux=$TOPDESTPATH/isolinux images/install.img=$TOPDESTPATH/images/install.img - implantisomd5 $TOPDESTPATH/images/$BOOTISO + BIOSARGS="-b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table" + echo $PWD:\$ mkisofs -o $TOPDESTPATH/images/$BOOTISO $BIOSARGS $EFIARGS -R -J -V "$PRODUCT" -T -graft-points isolinux=$TOPDESTPATH/isolinux images/install.img=$TOPDESTPATH/images/install.img $EFIGRAFT + mkisofs -o $TOPDESTPATH/images/$BOOTISO $BIOSARGS $EFIARGS -R -J -V "$PRODUCT" -T -graft-points isolinux=$TOPDESTPATH/isolinux images/install.img=$TOPDESTPATH/images/install.img $EFIGRAFT + implantisomd5 $TOPDESTPATH/images/$BOOTISO fi } diff --git a/scripts/upd-instroot b/scripts/upd-instroot index a33229252..8fab35324 100755 --- a/scripts/upd-instroot +++ b/scripts/upd-instroot @@ -152,7 +152,7 @@ die () { PACKAGES="GConf2 NetworkManager ORBit2 PolicyKit acl anaconda anaconda-yum-plugins at-spi atk attr audit-libs bash bitmap-fonts-cjk - booty btrfs-progs busybox-anaconda bzip2 bzip2-libs cairo cjkunifonts-uming + btrfs-progs busybox-anaconda bzip2 bzip2-libs cairo cjkunifonts-uming comps-extras coreutils cpio cracklib cracklib-dicts cracklib-python cryptsetup-luks db4 dbus dbus-python dejavu-sans-fonts dejavu-sans-mono-fonts device-mapper @@ -321,6 +321,7 @@ etc/fonts etc/group etc/gtk-2.0/gtkrc* etc/hal +etc/hosts etc/im_palette.pal etc/imrc etc/iscsid.conf @@ -496,7 +497,10 @@ usr/lib/anaconda-runtime usr/lib/anaconda/installclasses usr/lib/anaconda/iw usr/lib/anaconda/textw -usr/lib/booty +usr/lib/anaconda/booty +usr/lib/anaconda/storage +usr/lib/anaconda/storage/devicelibs +usr/lib/anaconda/storage/formats usr/lib/kernel-wrapper usr/lib/locale usr/lib/python?.?/site-packages/bugzilla* diff --git a/sortedtransaction.py b/sortedtransaction.py index e5b2f0276..d7384bbc6 100644 --- a/sortedtransaction.py +++ b/sortedtransaction.py @@ -50,12 +50,12 @@ class SplitMediaTransactionData(SortableTransactionData): return TransactionData.getMembers(self, pkgtup) if pkgtup is None: returnlist = [] - for key in self.reqmedia[self.curmedia]: - returnlist.extend(self.pkgdict[key]) + for ele in self.reqmedia[self.curmedia]: + returnlist.extend(self.pkgdict[ele]) return returnlist - if self.reqmedia[self.curmedia].has_key(pkgtup): + if pkgtup in self.reqmedia[self.curmedia]: return self.pkgdict[pkgtup] else: return [] diff --git a/storage/Makefile b/storage/Makefile new file mode 100644 index 000000000..5ef2e0bce --- /dev/null +++ b/storage/Makefile @@ -0,0 +1,33 @@ +# +# Makefile +# +# 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 <http://www.gnu.org/licenses/>. +# + +include ../Makefile.inc + +all: + echo "nothing to make" + +install: + mkdir -p $(DESTDIR)/$(PYTHONLIBDIR)/storage + install -p -m 644 *.py $(DESTDIR)/$(PYTHONLIBDIR)/storage + ../py-compile --basedir $(DESTDIR)/$(PYTHONLIBDIR)/storage $(DESTDIR)/$(PYTHONLIBDIR)/storage/*.py + +clean: + rm -f *.o *.so *.pyc + +depend: diff --git a/storage/__init__.py b/storage/__init__.py new file mode 100644 index 000000000..16a69222d --- /dev/null +++ b/storage/__init__.py @@ -0,0 +1,1717 @@ +# __init__.py +# Entry point for anaconda's storage configuration module. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +import os +import time +import stat +import errno +import sys + +import 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 udev import udev_trigger +import iscsi +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.protectedPartitions = [target] + elif anaconda.methodstr and anaconda.methodstr.startswith("hd:"): + method = anaconda.methodstr[3:] + devspec = method.split(":", 3)[0] + + device = storage.devicetree.resolveDevice(devspec) + if device is None: + 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) + + storage.protectedPartitions = [device.name] + + 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.retrofitPassphrase = retrofit + for dev in anaconda.id.storage.devices: + if dev.format.type == "luks" and not dev.format.exists: + dev.format.passphrase = passphrase + 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.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) + 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 + 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 = CLEARPART_TYPE_NONE + self.clearPartDisks = [] + self.encryptedAutoPart = False + self.encryptionPassphrase = None + self.encryptionRetrofit = False + self.reinitializeDisks = False + self.zeroMbr = None + self.protectedPartitions = [] + self.autoPartitionRequests = [] + + self.__luksDevs = {} + + self.iscsi = iscsi.iscsi() + 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, + clear=self.clearPartDisks, + reinitializeDisks=self.reinitializeDisks, + protected=self.protectedPartitions, + zeroMbr=self.zeroMbr, + passphrase=self.encryptionPassphrase, + luksDict=self.__luksDevs) + self.fsset = FSSet(self.devicetree) + + def doIt(self): + self.devicetree.processActions() + + # 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 + + try: + w = self.anaconda.intf.waitWindow(_("Finding Devices"), + _("Finding storage devices...")) + self.iscsi.startup(self.anaconda.intf) + self.zfcp.startup() + self.devicetree = DeviceTree(intf=self.anaconda.intf, + ignored=self.ignoredDisks, + exclusive=self.exclusiveDisks, + clear=self.clearPartDisks, + reinitializeDisks=self.reinitializeDisks, + protected=self.protectedPartitions, + zeroMbr=self.zeroMbr, + passphrase=self.encryptionPassphrase, + luksDict=self.__luksDevs) + self.devicetree.populate() + self.fsset = FSSet(self.devicetree) + except FSError as e: + self.anaconda.intf.messageWindow(_("Error"), + _("Filesystem error detected, cannot continue."), + custom_icon="error") + sys.exit(0) + finally: + if w: + w.pop() + + @property + def devices(self): + """ A list of all the devices in the device tree. """ + devices = self.devicetree.devices.values() + 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 = [] + devices = self.devicetree.devices + for d in devices: + if isinstance(devices[d], DiskDevice) and devices[d].mediaPresent: + disks.append(devices[d]) + 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.values() + 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.values() + 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.values() + swaps = [d for d in devices if d.format.type == "swap"] + swaps.sort(key=lambda d: d.name) + return swaps + + 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. + """ + dests = [] + for device in self.devices: + if not device.removable: + continue + + try: + dev = parted.Device(path=device.path) + disk = parted.Disk(device=dev) + except Exception: + continue + + for part in disk.partitions: + if part.active and \ + not part.getFlag(parted.PARTITION_RAID) and \ + not part.getFlag(parted.PARTITION_LVM) and \ + part.fileSystemType in ("ext3", "ext2", "fat16", "fat32"): + dests.append(part.path, device.name) + + if not disk.partitions: + dests.append(device.path, device.name) + + return dests + + def deviceImmutable(self, device): + """ 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 device.name in self.protectedPartitions: + 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 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 + + 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 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 = [] + + 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 " + "megabytes which is lower than recommended " + "for a normal %s install.") + %(self.anaconda.backend.getMinimumSizeMB("/"), + productName)) + + 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.")) + + if not boot: + errors.append(_("You have not created a boot partition.")) + + if (boot and boot.type == "mdarray" and + boot.level != 1): + errors.append(_("Bootable partitions can only be on RAID1 " + "devices.")) + + # can't have bootable partition on LV + if boot and boot.type == "lvmlv": + errors.append(_("Bootable partitions cannot be on a " + "logical volume.")) + + # most arches can't have boot on RAID + if boot and boot.type == "mdarray" and not self.anaconda.platform.supportsMdRaidBoot: + errors.append(_("Bootable partitions cannot be on a RAID " + "device.")) + + # Lots of filesystems types don't support /boot. + if boot and not boot.format.bootable: + errors.append(_("Bootable partitions cannot be on an %s " + "filesystem.") % boot.format.name) + + # vfat /boot is insane. + if (boot and boot == root and boot.format.type == "vfat"): + errors.append(_("Bootable partitions cannot be on an %s " + "filesystem.") % boot.format.type) + + if (boot and filter(lambda d: d.type == "luks/dm-crypt", + self.deviceDeps(boot))): + errors.append(_("Bootable partitions cannot be on an " + "encrypted block device")) + + if not swaps: + warnings.append(_("You have not specified a swap partition. " + "Although not strictly required in all cases, " + "it will significantly improve performance for " + "most installations.")) + + return (errors, warnings) + + def isProtected(self, device): + """ Return True is the device is protected. """ + return device.name in self.protectedPartitions + + 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.zfcp.write(instPath) + + def writeKS(self, f): + log.warning("Storage.writeKS not completely implemented") + self.iscsi.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.name in anaconda.id.storage.protectedPartitions: + # 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))) + + # 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.name in anaconda.id.storage.protectedPartitions 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("<device "): + continue + + line = line[len("<device "):-len("</device>\n")] + (data, sep, device) = line.partition(">") + if not device: + continue + + self.devices[device] = {} + for pair in data.split(): + try: + (key, value) = pair.split("=") + except ValueError: + continue + + self.devices[device][key] = value[1:-1] # strip off quotes + + def __getitem__(self, key): + return self.devices[key] + + def get(self, key, default=None): + return self.devices.get(key, default) + + +class CryptTab(object): + """ Dictionary-like interface to crypttab entries with map name keys """ + def __init__(self, devicetree, blkidTab=None, chroot=""): + self.devicetree = devicetree + self.blkidTab = blkidTab + self.chroot = chroot + self.mappings = {} + + def parse(self, chroot=""): + """ Parse /etc/crypttab from an existing installation. """ + if not chroot or not os.path.isdir(chroot): + chroot = "" + + path = "%s/etc/crypttab" % chroot + log.debug("parsing %s" % path) + with open(path) as f: + if not self.blkidTab: + try: + self.blkidTab = BlkidTab(chroot=chroot) + self.blkidTab.parse() + except Exception: + self.blkidTab = None + + for line in f.readlines(): + (line, pound, comment) = line.partition("#") + fields = line.split() + if not 2 <= len(fields) <= 4: + continue + elif len(fields) == 2: + fields.extend(['none', '']) + elif len(fields) == 3: + fields.append('') + + (name, devspec, keyfile, options) = fields + + # resolve devspec to a device in the tree + device = self.devicetree.resolveDevice(devspec, + blkidTab=self.blkidTab) + if device: + self.mappings[name] = {"device": device, + "keyfile": keyfile, + "options": options} + + def populate(self): + """ Populate the instance based on the device tree's contents. """ + for device in self.devicetree.devices.values(): + # 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 + + 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 + + @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): + devices = self.devicetree.devices.values() + devices.sort(key=lambda d: d.path) + return devices + + @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: + # 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), + 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" + device = FileDevice(devspec, + parents=get_containing_device(devspec), + 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 isinstance(format, get_device_format_class("nodev")): + device = NoDevice(format) + else: + device = Device(devspec) + + if device is None: + log.error("failed to resolve %s (%s) from fstab" % (devspec, + fstype)) + return None + + # 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 Exception as e: + raise Exception("fstab entry %s is malformed: %s" % (devspec, e)) + + if not device: + continue + + if device not in self.devicetree.devices.values(): + 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, intf=None, upgrading=None): + for device in self.swapDevices: + try: + device.setup() + device.format.setup() + except SuspendError: + if 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 + + intf.messageWindow(_("Error"), msg) + sys.exit(0) + except DeviceError as msg: + if 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") % (device.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.") % \ + (device.path, msg) + 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 + + 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 (num, msg): + if intf: + if num == errno.EEXIST: + intf.messageWindow(_("Invalid mount point"), + _("An error occurred when trying " + "to create %s. Some element of " + "this path is not a directory. " + "This is a fatal error and the " + "install cannot continue.\n\n" + "Press <Enter> to exit the " + "installer.") + % (device.format.mountpoint,)) + else: + 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 <Enter> to exit the " + "installer.") + % (device.format.mountpoint, msg)) + log.error("OSError: (%d) %s" % (num, msg) ) + 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 <Enter> 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 = "" + 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 + + fstype = device.format.type + if fstype == "swap": + mountpoint = "swap" + options = device.format.options + else: + mountpoint = device.format.mountpoint + options = device.format.mountopts + 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) + return fstab diff --git a/storage/deviceaction.py b/storage/deviceaction.py new file mode 100644 index 000000000..ba4456a6e --- /dev/null +++ b/storage/deviceaction.py @@ -0,0 +1,384 @@ +# deviceaction.py +# Device modification action classes for anaconda's storage configuration +# module. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +import copy +from parted import PARTITION_BOOT + +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("/sys%s" % 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.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 + diff --git a/storage/devicelibs/Makefile b/storage/devicelibs/Makefile new file mode 100644 index 000000000..2ecda8c8a --- /dev/null +++ b/storage/devicelibs/Makefile @@ -0,0 +1,33 @@ +# +# Makefile +# +# 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 <http://www.gnu.org/licenses/>. +# + +include ../../Makefile.inc + +all: + echo "nothing to make" + +install: + mkdir -p $(DESTDIR)/$(PYTHONLIBDIR)/storage/devicelibs + install -p -m 644 *.py $(DESTDIR)/$(PYTHONLIBDIR)/storage/devicelibs + ../../py-compile --basedir $(DESTDIR)/$(PYTHONLIBDIR)/storage/devicelibs $(DESTDIR)/$(PYTHONLIBDIR)/storage/devicelibs/*.py + +clean: + rm -f *.o *.so *.pyc + +depend: diff --git a/storage/devicelibs/__init__.py b/storage/devicelibs/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/storage/devicelibs/__init__.py diff --git a/storage/devicelibs/crypto.py b/storage/devicelibs/crypto.py new file mode 100644 index 000000000..28da47c54 --- /dev/null +++ b/storage/devicelibs/crypto.py @@ -0,0 +1,115 @@ +# +# crypto.py +# +# Copyright (C) 2009 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Author(s): Dave Lehman <dlehman@redhat.com> +# Martin Sivak <msivak@redhat.com> +# + +import os +from pycryptsetup import CryptSetup + +import iutil +from ..errors import * + +import gettext +_ = lambda x: gettext.ldgettext("anaconda", x) + +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): + cs = CryptSetup(yesDialog = askyes, logFunc = dolog) + return cs.addKey(device, new_passphrase, new_key_file, passphrase, key_file) + + +def luks_remove_key(device, + del_passphrase=None, del_key_file=None, + passphrase=None, key_file=None): + cs = CryptSetup(yesDialog = askyes, logFunc = dolog) + return cs.removeKey(device, del_passphrase, del_key_file, passphrase, key_file) + + diff --git a/storage/devicelibs/dm.py b/storage/devicelibs/dm.py new file mode 100644 index 000000000..29df1266e --- /dev/null +++ b/storage/devicelibs/dm.py @@ -0,0 +1,107 @@ +# +# dm.py +# device-mapper functions +# +# Copyright (C) 2009 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Author(s): Dave Lehman <dlehman@redhat.com> +# + +import os + +import block +import iutil +from ..errors import * + +import gettext +_ = lambda x: gettext.ldgettext("anaconda", x) + +import logging +log = logging.getLogger("storage") + +def name_from_dm_node(dm_node): + name = block.getNameFromDmNode(dm_node) + if name is not None: + return name + + st = os.stat("/dev/%s" % dm_node) + major = os.major(st.st_rdev) + minor = os.minor(st.st_rdev) + name = iutil.execWithCapture("dmsetup", + ["info", "--columns", + "--noheadings", "-o", "name", + "-j", str(major), "-m", str(minor)], + stderr="/dev/tty5") + log.debug("name_from_dm(%s) returning '%s'" % (dm_node, name.strip())) + return name.strip() + +def dm_node_from_name(map_name): + dm_node = block.getDmNodeFromName(map_name) + if dm_node is not None: + return dm_node + + devnum = iutil.execWithCapture("dmsetup", + ["info", "--columns", + "--noheadings", + "-o", "devno", + map_name], + stderr="/dev/tty5") + (major, sep, minor) = devnum.strip().partition(":") + if not sep: + raise DMError("dm device does not exist") + + dm_node = "dm-%d" % int(minor) + log.debug("dm_node_from_name(%s) returning '%s'" % (map_name, dm_node)) + return dm_node + +def _get_backing_devnums_from_map(map_name): + ret = [] + buf = iutil.execWithCapture("dmsetup", + ["info", "--columns", + "--noheadings", + "-o", "devnos_used", + map_name], + stderr="/dev/tty5") + dev_nums = buf.split() + for dev_num in dev_nums: + (major, colon, minor) = dev_num.partition(":") + ret.append((int(major), int(minor))) + + return ret + +def get_backing_devnums(dm_node): + #dm_node = dm_node_from_name(map_name) + if not dm_node: + return None + + top_dir = "/sys/block" + backing_devs = os.listdir("%s/%s/slaves/" % (top_dir, dm_node)) + dev_nums = [] + for backing_dev in backing_devs: + dev_num = open("%s/%s/dev" % (top_dir, backing_dev)).read().strip() + (_major, _minor) = dev_num.split(":") + dev_nums.append((int(_major), int(_minor))) + + return dev_nums + +def get_backing_devs_from_name(map_name): + dm_node = dm_node_from_name(map_name) + if not dm_node: + return None + + slave_devs = os.listdir("/sys/block/virtual/%s" % dm_node) + return slave_devs + diff --git a/storage/devicelibs/lvm.py b/storage/devicelibs/lvm.py new file mode 100644 index 000000000..0dedc8c24 --- /dev/null +++ b/storage/devicelibs/lvm.py @@ -0,0 +1,425 @@ +# +# lvm.py +# lvm functions +# +# Copyright (C) 2009 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Author(s): Dave Lehman <dlehman@redhat.com> +# + +import os +import math +import re + +import iutil + +from ..errors import * +from constants import * + +import gettext +_ = lambda x: gettext.ldgettext("anaconda", x) + +MAX_LV_SLOTS = 256 + +def has_lvm(): + has_lvm = False + for path in os.environ["PATH"].split(":"): + if os.access("%s/lvm" % path, os.X_OK): + has_lvm = True + break + + if has_lvm: + has_lvm = False + for line in open("/proc/devices").readlines(): + if "device-mapper" in line.split(): + has_lvm = True + break + + return has_lvm + +# Start config_args handling code +# +# Theoretically we can handle all that can be handled with the LVM --config +# argument. For every time we call an lvm_cc (lvm compose config) funciton +# we regenerate the config_args with all global info. +config_args = [] # Holds the final argument list +config_args_data = { "filterRejects": [], # regular expressions to reject. + "filterAccepts": [] } # regexp to accept + +def _composeConfig(): + """lvm command accepts lvm.conf type arguments preceded by --config. """ + global config_args, config_args_data + config_args = [] + + filter_string = "" + rejects = config_args_data["filterRejects"] + # we don't need the accept for now. + # accepts = config_args_data["filterAccepts"] + # if len(accepts) > 0: + # for i in range(len(rejects)): + # filter_string = filter_string + ("\"a|%s|\", " % accpets[i]) + + if len(rejects) > 0: + for i in range(len(rejects)): + filter_string = filter_string + ("\"r|%s|\"," % rejects[i]) + + + filter_string = " filter=[%s] " % filter_string.strip(",") + + # As we add config strings we should check them all. + if filter_string == "": + # Nothing was really done. + return + + # devices_string can have (inside the brackets) "dir", "scan", + # "preferred_names", "filter", "cache_dir", "write_cache_state", + # "types", "sysfs_scan", "md_component_detection". see man lvm.conf. + devices_string = " devices {%s} " % (filter_string) # strings can be added + config_string = devices_string # more strings can be added. + config_args = ["--config", config_string] + +def lvm_cc_addFilterRejectRegexp(regexp): + """ Add a regular expression to the --config string.""" + global config_args_data + config_args_data["filterRejects"].append(regexp) + + # compoes config once more. + _composeConfig() +# 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"): #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 getVGUsedSpace(vgreq, requests, diskset): + vgused = 0 + for request in requests.requests: + if request.type == REQUEST_LV and request.volumeGroup == vgreq.uniqueID: + size = int(request.getActualSize(requests, diskset)) + vgused = vgused + size + + + return vgused + +def getVGFreeSpace(vgreq, requests, diskset): + raise NotImplementedError + used = getVGUsedSpace(vgreq, requests, diskset) + log.debug("used space is %s" % (used,)) + + total = vgreq.getActualSize(requests, diskset) + log.debug("actual space is %s" % (total,)) + return total - used + +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"] + \ + 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) = line.split() + lvs[name] = {"size": size, + "uuid": uuid} + + 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"] + \ + ["-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) + diff --git a/storage/devicelibs/mdraid.py b/storage/devicelibs/mdraid.py new file mode 100644 index 000000000..dec5f2dc3 --- /dev/null +++ b/storage/devicelibs/mdraid.py @@ -0,0 +1,250 @@ +# +# mdraid.py +# mdraid functions +# +# Copyright (C) 2009 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Author(s): Dave Lehman <dlehman@redhat.com> +# + +import os + +import iutil +from ..errors import * + +import gettext +_ = lambda x: gettext.ldgettext("anaconda", x) + +import logging +log = logging.getLogger("storage") + +# raidlevels constants +RAID10 = 10 +RAID6 = 6 +RAID5 = 5 +RAID1 = 1 +RAID0 = 0 + +def getRaidLevels(): + avail = [] + try: + f = open("/proc/mdstat", "r") + except: + pass + else: + for l in f.readlines(): + if not l.startswith("Personalities"): + continue + + lst = l.split() + + for lev in ["RAID0", "RAID1", "RAID5", "RAID6", "RAID10"]: + if "[" + lev + "]" in lst or "[" + lev.lower() + "]" in lst: + avail.append(lev) + + f.close() + + avail.sort() + return avail + +raid_levels = getRaidLevels() + +def isRaid(raid, raidlevel): + """Return whether raidlevel is a valid descriptor of raid""" + raid_descriptors = {RAID10: ("RAID10", "10", 10), + RAID6: ("RAID6", "6", 6), + RAID5: ("RAID5", "5", 5), + RAID1: ("mirror", "RAID1", "1", 1), + RAID0: ("stripe", "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", str(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): + rc = iutil.execWithRedirect("mdadm", + ["--incremental", + "--quiet", + "--auto=md", + device], + 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 = "" + + filename = None + if members: + from tempfile import mkstemp + (fd, filename) = mkstemp(prefix="%s_devices." % device, + dir="/tmp", + text=True) + os.write(fd, "DEVICE %s\n" % " ".join(members)) + config_arg = "--config=%s" % filename + os.close(fd) + del mkstemp + else: + config_arg = "" + + rc = iutil.execWithRedirect("mdadm", + ["--assemble", + config_arg, + device, + identifier, + "--auto=md", + "--update=super-minor"], + stderr = "/dev/tty5", + stdout = "/dev/tty5", + searchPath=1) + + if filename and os.access(filename, os.R_OK): + try: + os.unlink(filename) + except OSError, e: + log.debug("unlink of %s failed: %s" % (filename, e)) + + 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): + # XXX NOTUSED: we grab metadata from udev, which ran 'mdadm -E --export' + # + # FIXME: this will not work with version >= 1 metadata + # + # We should use mdadm -Eb or mdadm -E --export for a more easily + # parsed output format. + lines = iutil.execWithCapture("mdadm", + ["--examine", device], + stderr="/dev/tty5").splitlines() + + info = { + 'major': "-1", + 'minor': "-1", + 'uuid' : "", + 'level': -1, + 'nrDisks': -1, + 'totalDisks': -1, + 'mdMinor': -1, + } + + for line in lines: + (key, sep, val) = line.strip().partition(" : ") + if not sep: + continue + if key == "Version": + (major, sep, minor) = val.partition(".") + info['major'] = major + info['minor'] = minor + elif key == "UUID": + info['uuid'] = val.split()[0] + elif key == "Raid Level": + info['level'] = int(val[4:]) + elif key == "Raid Devices": + info['nrDisks'] = int(val) + elif key == "Total Devices": + info['totalDisks'] = int(val) + elif key == "Preferred Minor": + info['mdMinor'] = int(val) + else: + continue + + if not info['uuid']: + raise MDRaidError("UUID missing from device info") + + return info + diff --git a/storage/devicelibs/swap.py b/storage/devicelibs/swap.py new file mode 100644 index 000000000..090c92eed --- /dev/null +++ b/storage/devicelibs/swap.py @@ -0,0 +1,108 @@ +# swap.py +# Python module for managing swap devices. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +import resource + +import iutil +import os + +from ..errors import * + +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: + pass + finally: + try: + os.close(fd) + except: + 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 + diff --git a/storage/devices.py b/storage/devices.py new file mode 100644 index 000000000..1e67e801e --- /dev/null +++ b/storage/devices.py @@ -0,0 +1,2805 @@ +# devices.py +# Device classes for anaconda's storage configuration module. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + + +""" + Device classes for use by anaconda. + + This is the hierarchy of device objects that anaconda will use for + managing storage devices in the system. These classes will + individually make use of external support modules as needed to + perform operations specific to the type of device they represent. + + TODO: + - see how to do network devices (NetworkManager may help) + - perhaps just a wrapper here + - document return values of all methods/functions + - find out what other kinds of wild and crazy devices we need to + represent here (iseries? xen? more mainframe? mac? ps?) + - PReP + - this is a prime candidate for a PseudoDevice + - DASD + - ZFCP + - XEN + + What specifications do we allow? new existing + partitions + usage + + + filesystem, partition type are implicit + mountpoint + + + size + exact + - + range + - + resize - + + format - + + encryption + + + + disk + exact + - + set + - + how will we specify this? + partition w/ multiple parents cannot otherwise occur + primary + - + + mdraid sets + filesystem (*) + + + mountpoint + + + size? + format - + + encryption + + + + level + ? + device minor + ? + member devices + ? + spares + ? + name? + bitmap? (boolean) + - + + volume groups + name + - + member pvs + + + pesize + ? + + logical volumes + filesystem + + + mountpoint + + + size + exact + ? + format - + + encryption + + + + name + ? + vgname + ? + + +""" + +import os +import math +import copy + +# device backend modules +from devicelibs import mdraid +from devicelibs import lvm +from devicelibs import dm +import parted +import _ped +import platform + +from errors import * +from iutil import log_method_call, notify_kernel, numeric_type +from udev import * +from formats import get_device_format_class, getFormat + +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() + + +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. + + """ + _type = "generic device" + _packages = [] + + def __init__(self, name, parents=None, description=''): + """ Create a Device instance. + + Arguments: + + name -- the device name (generally a device node's basename) + + Keyword Arguments: + + parents -- a list of required Device instances + description -- a string describing the device + + """ + 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 + self.description = description + + 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') + 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" + " description = %(descr)s name = %(name)s status = %(status)s" + " parents = %(parents)s\n" + " kids = %(kids)s\n" % + {"type": self.__class__.__name__, "id": "%#x" % id(self), + "name": self.name, "parents": self.parents, "kids": self.kids, + "descr": self.description, "status": self.status}) + return s + + 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") + #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 + + @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): + """ 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. + + Arguments: + + host_address -- host address of the backing server + """ + self.host_address = host_address + + +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): + """ 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 + description -- a string describing the device + + """ + # 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 + + # this may be handy for disk, dmraid, mpath, mdraid + self.diskLabel = None + + self.format = format + self.fstabComment = "" + self._targetSize = self._size + + 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 probe(self): + """ Probe for any missing information about this device. """ + raise NotImplementedError("probe method not defined for StorageDevice") + + def updateSysfsPath(self): + """ Update this device's sysfs path. """ + log_method_call(self, self.name, status=self.status) + path = os.path.join("/sys", self.sysfsBlockDir, self.name) + 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.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") + + if self.status and self.format.exists: + self.format.teardown() + + if recursive: + self.teardownParents(recursive=recursive) + + def _getSize(self): + """ Get the device's size, accounting for pending changes. """ + size = self._size + if 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._size = newsize + + size = property(lambda x: x._getSize(), + lambda x, y: x._setSize(y), + doc="The device's size, accounting for pending changes") + + @property + def currentSize(self): + """ The device's actual size. """ + size = 0 + if self.exists: + size = self._size + return size + + @property + def minSize(self): + """ The minimum size this device can be. """ + if self.exists: + self.setup() + + 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._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.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") + + if not self.isleaf: + raise DeviceError("Cannot destroy non-leaf device") + + 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.partedDevice = None + self.partedDisk = None + + 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 + + if self.partedDevice: + log.debug("creating parted Disk: %s" % self.path) + if 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 initcb is not None and initcb(): + self.partedDisk = parted.freshDisk(device=self.partedDevice, \ + ty = platform.getPlatform(None).diskType) + else: + raise DeviceUserDeniedFormatError("User prefered to not format.") + + # 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 + + self.probe() + + 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 size(self): + """ The disk's size in MB """ + if not self.mediaPresent: + return 0 + + if not self._size: + self._size = self.partedDisk.device.getSize() + return self._size + + 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) + + 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 %s which has no media" % self.name) + + 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 + """ + if not self.mediaPresent or not 'parted' in globals().keys(): + return + + log_method_call(self, self.name, size=self.size, partedDevice=self.partedDevice) + if not self.size: + self._size = self.partedDevice.getSize() + 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 %s which has no media" % self.name) + + 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 %s after %d attempts" % (self.name, maxTries,)) + + # 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 %s which has no media" % self.name) + + 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") + + +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): + """ 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 + """ + 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._bootable = False + + self._resize = 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") + + # 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 + + 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 _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 & parted.PARTITION_EXTENDED + + @property + def isLogical(self): + return self.partType & parted.PARTITION_LOGICAL + + @property + def isPrimary(self): + return self.partType == parted.PARTITION_NORMAL + + @property + def isProtected(self): + return self.partType & parted.PARTITION_PROTECTED + + def _getPartedPartition(self): + if self.disk and not self._partedPartition: + pdisk = self.disk.partedDisk + self._partedPartition = pdisk.getPartitionByPath(self.path) + + 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)) + + if partition is None: + self._partedPartition = None + # no need to clobber our name + else: + self._partedPartition = partition + self._name = partition.getDeviceNodeName().split("/")[-1] + + partedPartition = property(lambda d: d._getPartedPartition(), + lambda d,p: d._setPartedPartition(p)) + + 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 dependsOn(self, dep): + """ Return True if this device depends on dep. """ + if isinstance(dep, PartitionDevice) and dep.isExtended and self.isLogical: + 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 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._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.createParents() + self.setupParents() + + self.disk.addPartition(self) + self.disk.commit() + + # this will force a lookup on next access, which we want + self.partedPartition = None + + 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") + + if not self.sysfsPath: + return + + if not self.isleaf: + raise DeviceError("Cannot destroy non-leaf device") + + self.setupParents() + self.disk.removePartition(self) + self.disk.commit() + + self.exists = False + + def _getSize(self): + """ Get the device's size. """ + size = self._size + if len(self.parents) == 1: + # 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") + + 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() + + self.parents = [disk] + disk.addChild() + + 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 + + def probe(self): + """ Probe for any missing information about this device. + + target type, parents? + """ + raise NotImplementedError("probe method not defined for DMDevice") + + @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") + + 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") + + 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("device is active") + + 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) + + @property + def size(self): + # break off 2KB for the LUKS header + return float(self.slave.size) - (2.0 / 1024) + + 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.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.slave.setup() + self.slave.format.setup() + + 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") + + if self.status and self.format.exists: + self.format.teardown() + udev_settle() + + if self.slave.format.exists: + self.slave.format.teardown() + + if recursive: + self.teardownParents(recursive=recursive) + + def destroy(self): + log_method_call(self, self.name, status=self.status) + self.format.teardown() + 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 DeviceError("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 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") + + @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") + + 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") + + if self.status: + return + + if len(self.parents) < self.pvCount: + raise DeviceError("cannot activate VG with missing PV(s)") + + 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") + + 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") + + 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.teardown() + + # this sometimes fails for some reason. + try: + lvm.vgreduce(self.name, [], rm=True) + lvm.vgremove(self.name) + except lvm.LVMErorr: + raise DeviceError("Could not completely remove VG %s" % self.name) + 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") + + 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 + if not lv.exists and lv.size > self.freeSpace: + raise DeviceError("new lv is too large to fit in free space") + + 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.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.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) + + 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): + """ Align a size to a multiple of physical extent size. """ + size = numeric_type(size) + + # we want Kbytes as a float for our math + size *= 1024.0 + pesize = self.peSize * 1024.0 + return long((math.floor(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 probe(self): + """ Probe for any missing information about this device. + + size + """ + raise NotImplementedError("probe method not defined for StorageDevice") + + 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(lambda d: d._size, _setSize) + + @property + def vg(self): + """ This Logical Volume's Volume Group. """ + return self.parents[0] + + @property + def name(self): + """ This device's name. """ + return "%s-%s" % (self.vg.name, self._name) + + @property + def lvname(self): + """ The LV's name (not including VG name). """ + return self._name + + @property + def complete(self): + """ Test if vg exits and if it has all pvs. """ + return self.vg.complete + + def setup(self, intf=None): + """ 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") + + if self.status: + return + + self.vg.setup() + lvm.lvactivate(self.vg.name, self._name) + + 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") + + if self.status and self.format.exists: + self.format.teardown() + + 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.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.teardown() + lvm.lvremove(self.vg.name, self._name) + self.exists = False + + def resize(self, intf=None): + # XXX should we set up anything before doing this? + # 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.format.teardown() + 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 + self.uuid = uuid + self._totalDevices = numeric_type(totalDevices) + self._memberDevices = numeric_type(memberDevices) + self.sysfsPath = "/devices/virtual/block/%s" % name + + """ 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.probe() + if self.exists and self.uuid: + open("/etc/mdadm.conf", "a").write("ARRAY %s UUID=%s\n" + % (self.path, self.uuid)) + + @property + def size(self): + size = None + for device in self.devices: + if size is None or device.size < size: + size = device.size + 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 + + @property + def mdadmConfEntry(self): + """ This array's mdadm.conf entry. """ + if not self.level or self.memberDevices is None or not self.uuid: + raise DeviceError("array is not fully defined") + + fmt = "ARRAY level=%s num-devices=%d UUID=%s" + return fmt % (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") + + try: + self.devices[0].setup() + except Exception: + return + + info = mdraid.mdexamine(self.devices[0].path) + if self.level is None: + self.level = info['level'] + if self.memberDevices is None: + self.memberDevices = info['nrDisks'] + if self.totalDevices is None: + self.totalDevices = info['totalDisks'] + if self.uuid is None: + self.uuid = info['uuid'] + if self.minor is None: + self.minor = info['mdMinor'] + + @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") + + 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") + + 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) + except MDRaidError as e: + log.warning("failed to add member %s to md array %s: %s" + % (device.path, self.path, e)) + + 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"): + 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(state_file, os.R_OK): + val = open(state_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") + + 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() + + 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") + + if self.status and self.format.exists: + self.format.teardown() + + if self.status: + 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") + + 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("/sys%s" % self.sysfsPath) + self.uuid = udev_device_get_md_uuid(info) + + @property + def formatArgs(self): + formatArgs = [] + if self.format.type == "ext2": + if self.level == 5: + formatArgs = ['-R', + 'stride=%d' % ((self.memberDevices - 1) * 16)] + elif self.level == 0: + 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.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, level=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 + level -- the type of dmraid + 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 DeviceError("cannot find class for 'dmraidmember'") + + + self._raidSet = raidSet + self._level = level + + @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") + + 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 activate(self, mknod=True): + self._raidSet.activate(mknod=mknod) + + def deactivate(self): + self._raidSet.deactivate() + + 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") + + 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 = '' + + +class MultipathDevice(DMDevice): + """ A multipath device """ + _type = "dm-multipath" + _packages = ["device-mapper-multipath"] + + def __init__(self, name, format=None, size=None, + exists=None, parents=None, sysfsPath=''): + """ Create a MultipathDevice instance. + + Arguments: + + name -- the device name (generally a device node's basename) + + Keyword Arguments: + + sysfsPath -- sysfs device path + size -- the device's size + format -- a DeviceFormat instance + parents -- a list of the backing devices (Device instances) + exists -- indicates whether this is an existing device + """ + DMDevice.__init__(self, name, format=format, size=size, + parents=parents, sysfsPath=sysfsPath, + exists=exists) + + def probe(self): + """ Probe for any missing information about this device. + + size + """ + raise NotImplementedError("probe method not defined for MultipathDevice") + + +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 = "nodev" + + 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.path + + @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 + + 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): + 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") + + # 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) + + 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") + + 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.createParents() + self.setupParents() + try: + iutil.mkdirChain(self.path) + except Exception, e: + raise DeviceError, e + + 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") + + 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.iscsi_name = kwargs.pop("iscsi_name") + self.iscsi_address = kwargs.pop("iscsi_address") + self.iscsi_port = int(kwargs.pop("iscsi_port")) + DiskDevice.__init__(self, device, **kwargs) + NetworkStorageDevice.__init__(self, self.iscsi_address) + log.debug("created new iscsi disk %s %s:%d" % (self.iscsi_name, self.iscsi_address, self.iscsi_port)) + +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") + + 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") + + # 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 + + def probe(self): + """ Probe for any missing information about this device. + + devnum, wwpn, fcplun + """ + raise NotImplementedError("probe method not defined for StorageDevice") + + +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) + + def probe(self): + """ Probe for any missing information about this device. """ + raise NotImplementedError("probe method not defined for StorageDevice") + + +class PRePBootDevice(PartitionDevice): + """ A PPC PReP boot partition. + + XXX Would this be better represented by a DeviceFormat class? + """ + _type = "PReP" + #_partedFlags = parted.PARTITION_PREP + + def __init__(self, device, + size=None, grow=False, maxsize=None, + major=None, minor=None, + sysfsPath='', parents=None, + exists=None, primary=False): + """ Create a PRePBootDevice instance. + + Arguments: + + device -- the device name (generally a device node's basename) + + Keyword Arguments: + + grow -- whether or not to grow the partition (boolean ) + maxsize -- max size for growable partitions (units TBD) + size -- the device's size (units/format TBD) + major -- the device major + minor -- the device minor + sysfsPath -- sysfs device path + parents -- a list of required Device instances + exists -- indicates whether this is an existing device + """ + PartitionDevice.__init__(self, device, partType=self._partType, + size=size, grow=grow, maxsize=maxsize, + major=major, minor=minor, + sysfsPath=sysfsPath, exists=exists, + parents=parents, primary=primary) + + +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) + + diff --git a/storage/devicetree.py b/storage/devicetree.py new file mode 100644 index 000000000..b9552bd98 --- /dev/null +++ b/storage/devicetree.py @@ -0,0 +1,1705 @@ +# devicetree.py +# Device management for anaconda's storage configuration module. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +import os +import block +import re + +from errors import * +from devices import * +from deviceaction import * +import formats +from udev import * + +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): + retVal = False # The less destructive default + if not intf or not name: + pass + else: + rc = intf.messageWindow(_("Warning"), + _("Error processing drive %s.\n" + "Maybe it needs to be reinitialized." + "YOU WILL LOSE ALL DATA ON THIS DRIVE!") % (name,), + 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 = "%s Volume Group" % vg_name + elif lv_name is not None: + message = "%s Logical Volume" % lv_name + + + rc = intf.messageWindow(_("Warning"), + _("Error processing LVM.\n" + "It seems that there is inconsistent LVM data. " + "(%s) make(s) up %s. " + "You can reinitialize all related PVs, which will " + "erase all LVM metadata. Or ignore, which will " + "preserve contents.") + %(str(pv_names), message), + type="custom", + custom_buttons = [ _("_Ignore drive(s)"), + _("_Re-initialize drive(s)") ], + 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=[], clear=[], + zeroMbr=None, reinitializeDisks=None, protected=[], + passphrase=None, luksDict=None): + # internal data members + self._devices = [] + self._actions = [] + + self.intf = intf + self.exclusiveDisks = exclusive + self.clearPartDisks = clear + self.zeroMbr = zeroMbr + self.reinitializeDisks = reinitializeDisks + self.protectedPartitions = protected + self.__passphrase = passphrase + self.__luksDevs = {} + if luksDict and isinstance(luksDict, dict): + self.__luksDevs = luksDict + self._ignoredDisks = [] + for disk in ignored: + self.addIgnoredDisk(disk) + + 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(path=a.device.path, + type="destroy", + object="device") + + creates = self.findActions(path=a.device.path, + 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(path=a.device.path, + type="create", + object="device") + + destroys = self.findActions(path=a.device.path, + 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(path=a.device.path) + 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(path=a.device.path, + 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(path=a.device.path, + type="destroy", + object="format") + + creates = self.findActions(path=a.device.path, + 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(path=a.device.path, + 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(path=a.device.path, + type="create", + object="format") + + destroys = self.findActions(path=a.device.path, + 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(path=a.device.path, + 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(path=a.device.path, + 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(path=a.device.path, + 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): + 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 = cmp(a2.device.name, a1.device.name) + 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 and 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 = cmp(a1.device.name, a2.device.name) + 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 = cmp(a1.device.name, a2.device.name) + 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): + 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 = cmp(a1.device.name, a2.device.name) + 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 = cmp(a1.device.name, a2.device.name) + + 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.itervalues(): + 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]: + 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 + dev.disk.partedDisk.removePartition(dev.partedPartition) + + 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) + + def findActions(self, device=None, type=None, object=None, path=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: + 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 + + 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.values(): + 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 + return True + + # Ignore partitions found on the raw disks which are part of a + # dmraidset + for set in self.getDevicesByType("dm-raid array"): + for disk in set.parents: + if disk.name == os.path.basename(os.path.dirname(sysfs_path)): + 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 addUdevDevice(self, info): + # FIXME: this should be broken up into more discrete chunks + name = udev_device_get_name(info) + uuid = udev_device_get_uuid(info) + sysfs_path = udev_device_get_sysfs_path(info) + device = None + + if self.isIgnored(info): + log.debug("ignoring %s (%s)" % (name, sysfs_path)) + return + + log.debug("scanning %s (%s)..." % (name, sysfs_path)) + + # + # The first step is to either look up or create the device + # + if udev_device_is_dm(info): + log.debug("%s is a device-mapper device" % name) + # try to look up the device + device = self.getDeviceByName(name) + if device is None and uuid: + # try to find the device by uuid + device = self.getDeviceByUuid(uuid) + + if device is None: + for dmdev in self.devices: + if not isinstance(dmdev, DMDevice): + continue + + # 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 + + 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)) + 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 and \ + 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]) + 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("using generic DM device for %s" % name) + device = DMDevice(name, exists=True, parents=slaves) + self._addDevice(device) + elif udev_device_is_md(info): + log.debug("%s is an md device" % name) + # try to look up the device + device = self.getDeviceByName(name) + if device is None and uuid: + # try to find the device by uuid + device = self.getDeviceByUuid(uuid) + + if device is None: + # we didn't find a device instance, so we will create one + 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)) + 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) + elif udev_device_is_cdrom(info): + log.debug("%s is a cdrom" % name) + device = self.getDeviceByName(name) + if device is None: + # XXX should this be RemovableDevice instead? + # + # Looks like if it has ID_INSTANCE=0:1 we can ignore it. + device = OpticalDevice(name, + major=udev_device_get_major(info), + minor=udev_device_get_minor(info), + sysfsPath=sysfs_path) + self._addDevice(device) + elif udev_device_is_dmraid(info): + # This is just temporary as I need to differentiate between the + # device that has partitions and device that dont. + log.debug("%s is part of a dmraid" % name) + device = self.getDeviceByName(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): + kwargs = {} + if udev_device_is_iscsi(info): + diskType = iScsiDiskDevice + kwargs["iscsi_name"] = udev_device_get_iscsi_name(info) + kwargs["iscsi_address"] = udev_device_get_iscsi_address(info) + kwargs["iscsi_port"] = udev_device_get_iscsi_port(info) + log.debug("%s is an iscsi disk" % name) + else: + diskType = DiskDevice + log.debug("%s is a disk" % name) + device = self.getDeviceByName(name) + if device is None: + try: + 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.protectedPartitions: + _p = "/sys/%s/%s" % (sysfs_path, protected) + if os.path.exists(os.path.normpath(_p)): + initlabel = False + break + else: + initlabel = False + + device = diskType(name, + major=udev_device_get_major(info), + minor=udev_device_get_minor(info), + sysfsPath=sysfs_path, + initcb=cb, initlabel=initlabel, **kwargs) + self._addDevice(device) + except DeviceUserDeniedFormatError: #drive not initialized? + self.addIgnoredDisk(name) + elif udev_device_is_partition(info): + log.debug("%s is a partition" % name) + device = self.getDeviceByName(name) + if device is None: + disk_name = os.path.basename(os.path.dirname(sysfs_path)) + disk = self.getDeviceByName(disk_name) + + if disk is None: + # create a device instance for the disk + path = os.path.dirname(os.path.realpath(sysfs_path)) + new_info = udev_get_block_device(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) + + # + # now set the format + # + format = None + format_type = udev_device_get_format(info) + label = udev_device_get_label(info) + if device and format_type and not device.format.type: + args = [format_type] + kwargs = {"uuid": uuid, + "label": label, + "device": device.path, + "exists": True} + + if format_type == "crypto_LUKS": + # luks/dmcrypt + kwargs["name"] = "luks-%s" % uuid + elif format_type == "linux_raid_member": + # 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 == "isw_raid_member": + # We dont add any new args because we intend to use the same + # block.RaidSet object for all the related devices. + pass + 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" + + format = formats.getFormat(*args, **kwargs) + device.format = format + + # + # now lookup or create any compound devices we have discovered + # + if format: + if format.type == "luks": + if not 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(format.uuid) + if passphrase: + format.passphrase = passphrase + else: + (passphrase, isglobal) = getLUKSPassphrase(self.intf, + device, + self.__passphrase) + if isglobal and 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" % (format.mapName, + e)) + device.removeChild() + else: + self._addDevice(luks_device) + else: + log.warning("luks device %s already in the tree" + % format.mapName) + elif format.type == "mdmember": + # either look up or create the array device + md_array = self.getDeviceByUuid(format.mdUuid) + if 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 + + # find the first unused minor + minor = 0 + while True: + if self.getDeviceByName("md%d" % minor): + minor += 1 + else: + break + + md_name = "md%d" % minor + md_array = MDRaidArrayDevice(md_name, + level=md_level, + minor=minor, + memberDevices=md_devices, + uuid=md_uuid, + exists=True, + parents=[device]) + try: + md_array.setup() + except (DeviceError, MDRaidError) as e: + log.info("setup of md array %s failed: %s" + % (md_array.name, e)) + self._addDevice(md_array) + elif format.type == "dmraidmember": + major = udev_device_get_major(info) + minor = udev_device_get_minor(info) + # Have we already created the DMRaidArrayDevice? + rs = block.getRaidSetFromRelatedMem(uuid=uuid, name=name, + major=major, minor=minor) + if rs is None: + # we ignore the device in the hope that all the devices + # from this set will be ignored. + self.addIgnoredDisk(device.name) + return + + elif rs.name in self._ignoredDisks: + # If the rs is being ignored, we should ignore device too. + self.addIgnoredDisk(device.name) + return + + else: + 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) + + 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.protectedPartitions: + disk_name = re.sub(r'p\d+$', '', protected) + if disk_name != protected and \ + disk_name == rs.name: + initlabel = False + break + else: + initlabel = False + + try: + dm_array = DMRaidArrayDevice(rs.name, + major=major, minor=minor, + raidSet=rs, + level=rs.level, + 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 dmriad and its components + self.addIgnoredDisk(rs.name) + self.addIgnoredDisk(device.name) + rs.deactivate() + elif format.type == "lvmpv": + # 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 e: + log.info("setup of %s failed: %s" % (lv.name, e)) + 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" % (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" % (name, e)) + return + + if not lv_names: + log.debug("no LVs listed for VG %s" % 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 e: + log.info("setup of %s failed: %s" + % (lv_device.name, e)) + + def _handleInconsistencies(self, device): + 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) + pass + + # 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) + + 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: + self._removeDevice(parent, moddisk=False) + 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: + lv.destroy() + 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: + self._removeDevice(parent, moddisk=False) + lvm.lvm_cc_addFilterRejectRegexp(parent.name) + return + + def populate(self): + """ Locate all storage devices. """ + # each iteration scans any devices that have appeared since the + # previous iteration + old_devices = [] + ignored_devices = [] + while True: + devices = [] + new_devices = udev_get_block_devices() + + for new_device in new_devices: + found = False + for old_device in old_devices: + if old_device['name'] == new_device['name']: + found = True + break + + if not found: + devices.append(new_device) + + if len(devices) == 0: + # nothing is changing -- we are finished building devices + break + + old_devices = new_devices + log.info("devices to scan: %s" % [d['name'] for d in devices]) + for dev in devices: + self.addUdevDevice(dev) + + # After having the complete tree we make sure that the system + # inconsistencies are ignored or resolved. + for leaf in self.leaves: + self._handleInconsistencies(leaf) + + 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 e: + log.debug("setup of %s failed: %s" % (device.name, e)) + + def getDeviceBySysfsPath(self, path): + found = None + for device in self._devices: + if device.sysfsPath == path: + found = device + break + + return found + + def getDeviceByUuid(self, uuid): + 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 getDeviceByLabel(self, label): + 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) + found = None + for device in self._devices: + if device.name == name: + 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): + """ Dict with device path keys and Device values. """ + devices = {} + + for device in self._devices: + if device.path in devices: + raise DeviceTreeError("duplicate paths in device tree") + + devices[device.path] = 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.devices.get(devspec) + if device is None: + if blkidTab: + # try to use the blkid.tab to correlate the device + # path with a UUID + blkidTabEnt = blkidTab.get(devspec) + if blkidTabEnt: + log.debug("found blkid.tab entry for '%s'" % devspec) + uuid = blkidTabEnt.get("UUID") + if uuid: + device = self.getDeviceByUuid(uuid) + if device: + devstr = device.name + else: + devstr = "None" + log.debug("found device '%s' in tree" % devstr) + if device and device.format and \ + device.format.type == "luks": + map_name = device.format.mapName + log.debug("luks device; map name is '%s'" % map_name) + mapped_dev = self.getDeviceByName(map_name) + if mapped_dev: + device = mapped_dev + + if device is None and cryptTab and \ + devspec.startswith("/dev/mapper/"): + # try to use a dm-crypt mapping name to + # obtain the underlying device, possibly + # using blkid.tab + cryptTabEnt = cryptTab.get(devspec.split("/")[-1]) + if cryptTabEnt: + luks_dev = cryptTabEnt['device'] + try: + device = self.getChildren(luks_dev)[0] + except IndexError as e: + pass + elif device is None: + # dear lvm: can we please have a few more device nodes + # for each logical volume? + # three just doesn't seem like enough. + name = devspec[5:] # strip off leading "/dev/" + (vg_name, slash, lv_name) = name.partition("/") + if lv_name and not "/" in lv_name: + # looks like we may have one + lv = "%s-%s" % (vg_name, lv_name) + device = self.getDeviceByName(lv) + + if device: + log.debug("resolved '%s' to '%s' (%s)" % (devspec, device.name, device.type)) + else: + log.debug("failed to resolve '%s'" % devspec) + return device diff --git a/storage/errors.py b/storage/errors.py new file mode 100644 index 000000000..8fb57b140 --- /dev/null +++ b/storage/errors.py @@ -0,0 +1,108 @@ + +# Device +class DeviceError(Exception): + pass + +class DeviceCreateError(DeviceError): + pass + +class DeviceDestroyError(DeviceError): + pass + +class DeviceResizeError(DeviceError): + pass + +class DeviceSetupError(DeviceError): + pass + +class DeviceTeardownError(DeviceError): + pass + +class DeviceResizeError(DeviceError): + pass + +class DeviceUserDeniedFormatError(DeviceError): + pass + +# DeviceFormat +class DeviceFormatError(Exception): + pass + +class FormatCreateError(DeviceFormatError): + pass + +class FormatDestroyError(DeviceFormatError): + pass + +class FormatSetupError(DeviceFormatError): + pass + +class FormatTeardownError(DeviceFormatError): + pass + +class DMRaidMemberError(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(Exception): + pass + +class SuspendError(SwapError): + pass + +class OldSwapError(SwapError): + pass + +class MDRaidError(Exception): + pass + +class DMError(Exception): + pass + +class LVMError(Exception): + pass + +class CryptoError(Exception): + pass + +# DeviceTree +class DeviceTreeError(Exception): + pass + +# DeviceAction +class DeviceActionError(Exception): + pass + +# partitioning +class PartitioningError(Exception): + pass + +class PartitioningWarning(Exception): + pass + +# udev +class UdevError(Exception): + pass + + diff --git a/storage/formats/Makefile b/storage/formats/Makefile new file mode 100644 index 000000000..c99a94929 --- /dev/null +++ b/storage/formats/Makefile @@ -0,0 +1,33 @@ +# +# Makefile +# +# 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 <http://www.gnu.org/licenses/>. +# + +include ../../Makefile.inc + +all: + echo "nothing to make" + +install: + mkdir -p $(DESTDIR)/$(PYTHONLIBDIR)/storage/formats + install -p -m 644 *.py $(DESTDIR)/$(PYTHONLIBDIR)/storage/formats + ../../py-compile --basedir $(DESTDIR)/$(PYTHONLIBDIR)/storage/formats $(DESTDIR)/$(PYTHONLIBDIR)/storage/formats/*.py + +clean: + rm -f *.o *.so *.pyc + +depend: diff --git a/storage/formats/__init__.py b/storage/formats/__init__.py new file mode 100644 index 000000000..9ec099932 --- /dev/null +++ b/storage/formats/__init__.py @@ -0,0 +1,364 @@ +# __init__.py +# Entry point for anaconda storage formats subpackage. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +import os + +from iutil import notify_kernel, get_sysfs_path_by_name, 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 = ("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") + + 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) + 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)) + except Exception as e: + log.error("error zeroing out %s: %s" % (self.device, e)) + + 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) + pass + + @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 + + +collect_device_format_classes() + + diff --git a/storage/formats/dmraid.py b/storage/formats/dmraid.py new file mode 100644 index 000000000..f5f28084a --- /dev/null +++ b/storage/formats/dmraid.py @@ -0,0 +1,99 @@ +# dmraid.py +# dmraid device formats +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +import block + +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 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") + +register_device_format(DMRaidMember) + diff --git a/storage/formats/fs.py b/storage/formats/fs.py new file mode 100644 index 000000000..8864b3aab --- /dev/null +++ b/storage/formats/fs.py @@ -0,0 +1,1110 @@ +# filesystems.py +# Filesystem classes for anaconda's storage configuration module. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# David Cantrell <dcantrell@redhat.com> +# + +""" Filesystem classes for use by anaconda. + + TODO: + - migration + - bug 472127: allow creation of tmpfs filesystems (/tmp, /var/tmp, &c) +""" +import 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 +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 + _name = None + _mkfs = "" # mkfs utility + _resizefs = "" # resize utility + _labelfs = "" # labeling utility + _fsck = "" # fs check utility + _migratefs = "" # fs migration utility + _defaultFormatOptions = [] # default options passed to mkfs + _defaultMountOptions = ["defaults"] # default options passed to mount + _defaultLabelOptions = [] + _defaultCheckOptions = [] + _defaultMigrateOptions = [] + _migrationTarget = None + 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") + self._mountpoint = None # the current mountpoint when mounted + if self.exists: + self._size = self._getExistingSize() + + self._targetSize = self._size + + 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. + """ + size = 0 + + if self.mountable: + origMountPoint = self._mountpoint + + tmppath = tempfile.mkdtemp(prefix='getsize-', dir='/tmp') + self.mount(mountpoint=tmppath, options="ro") + buf = os.statvfs(tmppath) + self.unmount() + + self._mountpoint = origMountPoint + + size = (buf.f_frsize * buf.f_blocks) / 1024.0 / 1024.0 + + 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.type, 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) + + # 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 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) + mountpoint = os.path.normpath("%s/%s" % (chroot, mountpoint)) + iutil.mkdirChain(mountpoint) + if flags.selinux: + ret = isys.resetFileContext(mountpoint) + 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, mountpoint, + fstype=self.type, + 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) + 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) + + self._mountpoint = mountpoint + + 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 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]: + 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 + + def supported(self): + log_method_call(self, supported=self._supported) + return self._supported and self.utilsAvailable + + @property + def mountable(self): + return (self.type in kernel_filesystems) or \ + (os.access("/sbin/mount.%s" % (self.type,), 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) + + @property + def migratable(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) + + 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 + + """ 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 + + +class Ext2FS(FS): + """ ext2 filesystem. """ + _type = "ext2" + _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 = [] + _defaultMountOptions = ["defaults"] + _defaultCheckOptions = ["-f", "-p", "-C", "0"] + _dump = True + _check = True + _migratable = True + _migrationTarget = "ext3" + _migratefs = "tune2fs" + _defaultMigrateOptions = ["-j"] + + @property + def minSize(self): + """ Minimum size for this filesystem in MB. """ + size = self._minSize + if self.exists: + if not os.path.exists(self.device): + raise FSError("device does not exist") + + buf = iutil.execWithCapture(self.resizefsProg, + ["-P", self.device], + stderr="/dev/tty5") + size = None + 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: + raise FSError("failed to get minimum fs size") + + return size + + @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" + _defaultMigrateOptions = ["-O", "extents"] + + @property + def migratable(self): + """ Can filesystems of this type be migrated? """ + return (flags.cmdline.has_key("ext4migrate") and + Ext2FS.migratable) + +register_device_format(Ext3FS) + + +class Ext4FS(Ext3FS): + """ ext4 filesystem. """ + _type = "ext4" + _bootable = False + _defaultFormatOptions = ["-t", "ext4"] + _migratable = False + +register_device_format(Ext4FS) + + +class FATFS(FS): + """ FAT filesystem. """ + _type = "vfat" + _mkfs = "mkdosfs" + _labelfs = "dosfslabel" + _fsck = "dosfsck" + _formattable = True + _maxSize = 1024 * 1024 + _packages = [ "dosfstools" ] + _defaultMountOptions = ["umask=0077", "shortname=winnt"] + +register_device_format(FATFS) + + +class EFIFS(FATFS): + _type = "efi" + _name = "EFI System Partition" + _minSize = 50 + _maxSize = 256 + _bootable = True + + @property + def supported(self): + import platform + return (isinstance(platform.getPlatform(None), platform.EFI) + and self.utilsAvailable) + +register_device_format(EFIFS) + + +class BTRFS(FS): + """ btrfs filesystem """ + _type = "btrfs" + _mkfs = "mkfs.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" + _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" + _labelfs = "jfs_tune" + _defaultFormatOptions = ["-q"] + _defaultLabelOptions = ["-L"] + _maxLabelChars = 16 + _maxSize = 8 * 1024 * 1024 + _formattable = True + _linuxNative = True + _supported = False + _dump = True + _check = True + + @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" + _labelfs = "xfs_admin" + _defaultFormatOptions = ["-f"] + _defaultLabelOptions = ["-L"] + _maxLabelChars = 16 + _maxSize = 16 * 1024 * 1024 + _formattable = True + _linuxNative = True + _supported = False + _dump = True + _check = True + _packages = ["xfsprogs"] + +register_device_format(XFS) + + +class HFS(FS): + _type = "hfs" + _mkfs = "hformat" + _formattable = True + +register_device_format(HFS) + + +class AppleBootstrapFS(HFS): + _type = "appleboot" + _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) + +register_device_format(AppleBootstrapFS) + + +# this doesn't need to be here +class HFSPlus(FS): + _type = "hfs+" + _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"] + + @property + def minSize(self): + """ The minimum filesystem size in megabytes. """ + size = self._minSize + if self.exists: + 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 + + return size + + @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" + + def _deviceCheck(self, devspec): + if devspec is not None and ":" not in devspec: + raise ValueError("device must be of the form <host>:<path>") + + @property + def mountable(self): + return False + + def _setDevice(self, devspec): + self._deviceCheck(devspec) + self._device = devspec + + def _getDevice(self): + return self._device + + device = property(lambda f: f._getDevice(), + lambda f,d: f._setDevice(d), + doc="Full path the device this format occupies") + +register_device_format(NFS) + + +class NFSv4(NFS): + """ NFSv4 filesystem. """ + _type = "nfs4" + +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"] + +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 + +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 + +register_device_format(BindFS) + + diff --git a/storage/formats/luks.py b/storage/formats/luks.py new file mode 100644 index 000000000..f5c2363f7 --- /dev/null +++ b/storage/formats/luks.py @@ -0,0 +1,227 @@ +# luks.py +# Device format classes for anaconda's storage configuration module. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + + + +import os + +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" + _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") + + # 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) + + 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) + diff --git a/storage/formats/lvmpv.py b/storage/formats/lvmpv.py new file mode 100644 index 000000000..c9cbe295d --- /dev/null +++ b/storage/formats/lvmpv.py @@ -0,0 +1,124 @@ +# lvmpv.py +# Device format classes for anaconda's storage configuration module. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +import os + +from 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") + + pass + #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)) + +register_device_format(LVMPhysicalVolume) + diff --git a/storage/formats/mdraid.py b/storage/formats/mdraid.py new file mode 100644 index 000000000..ec1a61782 --- /dev/null +++ b/storage/formats/mdraid.py @@ -0,0 +1,97 @@ +# mdraid.py +# Device format classes for anaconda's storage configuration module. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +import os + +from iutil import log_method_call +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 + + +register_device_format(MDRaidMember) + diff --git a/storage/formats/prepboot.py b/storage/formats/prepboot.py new file mode 100644 index 000000000..fe0625f88 --- /dev/null +++ b/storage/formats/prepboot.py @@ -0,0 +1,56 @@ +# prepboot.py +# Format class for PPC PReP Boot. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +from ..errors import * +from . import DeviceFormat, register_device_format +from parted import PARTITION_PREP + +class PPCPRePBoot(DeviceFormat): + """ Generic device format. """ + _type = "prepboot" + _name = "PPC PReP Boot" + _udevTypes = [] + partedFlag = PARTITION_PREP + _formattable = True # can be formatted + _linuxNative = True # for clearpart + _bootable = True # can be used as boot + _maxSize = 4 # maximum size in MB + _minSize = 10 # minimum size in MB + + def __init__(self, *args, **kwargs): + """ Create a PRePBoot instance. + + Keyword Arguments: + + device -- path to the underlying device + exists -- indicates whether this is an existing format + + """ + DeviceFormat.__init__(self, *args, **kwargs) + + @property + def status(self): + return False + + +register_device_format(PPCPRePBoot) + diff --git a/storage/formats/swap.py b/storage/formats/swap.py new file mode 100644 index 000000000..bc5dbd90f --- /dev/null +++ b/storage/formats/swap.py @@ -0,0 +1,144 @@ +# swap.py +# Device format classes for anaconda's storage configuration module. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +from iutil import 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": + self.priority = numeric_type(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 + + +register_device_format(SwapSpace) + diff --git a/iscsi.py b/storage/iscsi.py index a621bd51d..29d89830e 100644 --- a/iscsi.py +++ b/storage/iscsi.py @@ -31,7 +31,6 @@ import shutil import time import hashlib import random -import partedUtils log = logging.getLogger("anaconda") import gettext @@ -46,8 +45,6 @@ except: # Note that stage2 copies all files under /sbin to /usr/sbin global ISCSID ISCSID="" -global ISCSIADM -ISCSIADM = "" INITIATOR_FILE="/etc/iscsi/initiatorname.iscsi" def find_iscsi_files(): @@ -57,20 +54,13 @@ def find_iscsi_files(): path="%s/iscsid" % (dir,) if os.access(path, os.X_OK): ISCSID=path - global ISCSIADM - if ISCSIADM == "": - for dir in ("/usr/sbin", "/tmp/updates", "/mnt/source/RHupdates"): - path="%s/iscsiadm" % (dir,) - if os.access(path, os.X_OK): - ISCSIADM=path def has_iscsi(): find_iscsi_files() - if ISCSID == "" or ISCSIADM == "" or not has_libiscsi: + if ISCSID == "" or not has_libiscsi: return False log.info("ISCSID is %s" % (ISCSID,)) - log.info("ISCSIADM is %s" % (ISCSIADM,)) # make sure the module is loaded if not os.access("/sys/module/iscsi_tcp", os.X_OK): @@ -88,19 +78,6 @@ def iscsi_get_node_record(node_settings, record): return None -# FIXME replace with libiscsi use -def iscsi_make_node_autostart(disk): - sysfs_path = os.path.realpath("/sys/block/%s/device" %(disk,)) - argv = [ "-m", "session", "-r", sysfs_path ] - log.debug("iscsiadm %s" %(string.join(argv),)) - node_settings = iutil.execWithCapture(ISCSIADM, argv, stderr="/dev/tty5").splitlines() - node_name = iscsi_get_node_record(node_settings, "node.name") - argv = [ "-m", "node", "-T", node_name, "-o", "update", "-n", - "node.startup", "-v", "automatic" ] - log.debug("iscsiadm %s" %(string.join(argv),)) - iutil.execWithRedirect(ISCSIADM, argv, - stdout = "/dev/tty5", stderr="/dev/tty5") - def randomIname(): """Generate a random initiator name the same way as iscsi-iname""" @@ -285,11 +262,11 @@ class iscsi(object): f.write("iscsi --ipaddr %s --port %s" %(n.address, n.port)) auth = n.getAuth() if auth: - f.write(" --user %s" %(n.username,)) - f.write(" --password %s" %(n.password,)) + f.write(" --user %s" % auth.username) + f.write(" --password %s" % auth.password) if len(auth.reverse_username): - f.write(" --reverse-user %s" % (n.reverse_username,)) - f.write(" --reverse-password %s" % (n.reverse_password,)) + f.write(" --reverse-user %s" % auth.reverse_username) + f.write(" --reverse-password %s" % auth.reverse_password) f.write("\n") def write(self, instPath, anaconda): @@ -297,25 +274,21 @@ class iscsi(object): return if not flags.test: - root_drives = [ ] - req = anaconda.id.partitions.getRequestByMountPoint("/") - root_requests = anaconda.id.partitions.getUnderlyingRequests(req) - for req in root_requests: - for drive in req.drive: - part = anaconda.id.diskset.disks[drive].getPartitionByPath("/dev/%s" % req.device) - if part: - break - if not part: - continue - if drive not in root_drives: - root_drives.append(drive) - - log.debug("iscsi.write: root_drives: %s" % (string.join(root_drives),)) + root = anaconda.id.storage.fsset.rootDevice + disks = anaconda.id.storage.devicetree.getDevicesByType("iscsi") - # set iscsi nodes not used for root to autostart - for disk in anaconda.id.diskset.disks.keys(): - if isys.driveIsIscsi(disk) and not disk in root_drives: - iscsi_make_node_autostart(disk) + # set iscsi nodes to autostart + for disk in disks: + # devices used for root get started by the initrd + if root.dependsOn(disk): + continue + # find the iscsi node matching this disk + for node in self.nodes: + if node.name == disk.iscsi_name and \ + node.address == disk.iscsi_address and \ + node.port == disk.iscsi_port: + node.setParameter("node.startup", "automatic") + break if not os.path.isdir(instPath + "/etc/iscsi"): os.makedirs(instPath + "/etc/iscsi", 0755) diff --git a/storage/miscutils.py b/storage/miscutils.py new file mode 100644 index 000000000..b9c9740f1 --- /dev/null +++ b/storage/miscutils.py @@ -0,0 +1,58 @@ +# iutil.py stubs +import sys +import os + +import logging +log = logging.getLogger("storage") + +def notify_kernel(path, action="change"): + """ Signal the kernel that the specified device has changed. """ + log.debug("notifying kernel of '%s' event on device %s" % (action, path)) + path = os.path.join(path, "uevent") + if not path.startswith("/sys/") or not os.access(path, os.W_OK): + log.debug("sysfs path '%s' invalid" % path) + raise ValueError("invalid sysfs path") + + f = open(path, "a") + f.write("%s\n" % action) + f.close() + +def get_sysfs_path_by_name(dev_name, class_name="block"): + dev_name = os.path.basename(dev_name) + sysfs_class_dir = "/sys/class/%s" % class_name + dev_path = os.path.join(sysfs_class_dir, dev_name) + if os.path.exists(dev_path): + return dev_path + +import inspect +def log_method_call(d, *args, **kwargs): + classname = d.__class__.__name__ + methodname = inspect.stack()[1][3] + fmt = "%s.%s:" + fmt_args = [classname, methodname] + for arg in args: + fmt += " %s ;" + fmt_args.append(arg) + + for k, v in kwargs.items(): + fmt += " %s: %s ;" + fmt_args.extend([k, v]) + + log.debug(fmt % tuple(fmt_args)) + +def numeric_type(num): + """ Verify that a value is given as a numeric data type. + + Return the number if the type is sensible or raise ValueError + if not. + """ + if num is None: + num = 0 + elif not (isinstance(num, int) or \ + isinstance(num, long) or \ + isinstance(num, float)): + raise ValueError("value (%s) must be either a number or None" % num) + + return num + + diff --git a/storage/partitioning.py b/storage/partitioning.py new file mode 100644 index 000000000..a71ba91de --- /dev/null +++ b/storage/partitioning.py @@ -0,0 +1,1119 @@ +# partitioning.py +# Disk partitioning functions. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +import sys +import os +import copy +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 + +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: + (mountpoint, fstype, size, maxsize, grow, asvol) = request + if asvol: + continue + + if fstype is None: + fstype = anaconda.id.storage.defaultFSType + + dev = anaconda.id.storage.newPartition(fmt_type=fstype, + size=size, + grow=grow, + maxsize=maxsize, + mountpoint=mountpoint, + disks=disks) + + # 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: + (mountpoint, fstype, size, maxsize, grow, asvol) = request + if not asvol: + continue + + if fstype is None: + fstype = anaconda.id.storage.defaultFSType + + # FIXME: move this to a function and handle exceptions + dev = anaconda.id.storage.newLV(vg=vg, + fmt_type=fstype, + mountpoint=mountpoint, + grow=grow, + maxsize=maxsize, + size=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.devicetree.devices.values()]) + if anaconda.dir == DISPATCH_BACK: + anaconda.id.storage.reset() + return + + disks = [] + devs = [] + + if anaconda.id.storage.doAutoPart and not anaconda.isKickstart: + # kickstart uses clearPartitions even without autopart + 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') + 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) + 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) + + if anaconda.id.storage.doAutoPart: + _scheduleLVs(anaconda, devs) + + # grow LVs + growLVM(anaconda.id.storage) + + # 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"), + _("Your system will now be rebooted.")) + sys.exit(0) + return DISPATCH_BACK + + +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 == 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) + clear = False # whether or not we will clear this partition + + # if we got a list of disks to clear, make sure this one's on it + if storage.clearPartDisks and \ + part.disk.name not in storage.clearPartDisks: + continue + + # don't clear partitions holding install media + if part.name in storage.protectedPartitions: + continue + + # we don't want to fool with extended partitions, freespace, &c + if part.partType not in (parted.PARTITION_NORMAL, + parted.PARTITION_LOGICAL): + continue + + if storage.clearPartType == CLEARPART_TYPE_ALL: + clear = True + else: + if part.format and part.format.linuxNative: + clear = True + elif part.partedPartition.getFlag(parted.PARTITION_LVM) or \ + part.partedPartition.getFlag(parted.PARTITION_RAID) or \ + part.partedPartition.getFlag(parted.PARTITION_SWAP): + clear = True + + # TODO: do platform-specific checks on ia64, pSeries, iSeries, mac + + if not clear: + 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 = 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 + + # 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.parents), len(part2.parents)) * 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.size, part2.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] + + partitions = storage.partitions + for part in partitions: + part.req_bootable = False + 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) + + # 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 or \ + extended.getDeviceNodeName() in [p.name for p in partitions]: + # this extended partition is preexisting + 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(extended.getDeviceNodeName(), 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])) + if not partedDisk.getPartitionByPath(_part.path): + continue + + partedDisk.removePartition(_part.partedPartition) + # 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 + + # TODO: On alpha we are supposed to reserve either one or two + # MB at the beginning of each disk. Awesome. + # -- maybe we do not care about alpha... + + 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: + # 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 + + if free and _part.req_bootable: + # 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 the partition and add it to the disk + partition = parted.Partition(disk=disk, + type=part_type, + geometry=new_geom) + constraint = parted.Constraint(exactGeom=new_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[part.name] = 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[part.name] = True + + if not limited[part.name]: + 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[part.name]: + 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_sectors + last_good_size = part.req_size + last_outcome = None + while (part.partedPartition.geometry.length < max_sectors and + 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: + 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 + diff --git a/storage/storage_log.py b/storage/storage_log.py new file mode 100644 index 000000000..a771154f0 --- /dev/null +++ b/storage/storage_log.py @@ -0,0 +1,17 @@ +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) + + diff --git a/storage/udev.py b/storage/udev.py new file mode 100644 index 000000000..11df62927 --- /dev/null +++ b/storage/udev.py @@ -0,0 +1,362 @@ +# udev.py +# Python module for querying the udev database for device information. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +import os +import re +import stat + +import iutil +from errors import * + +import logging +log = logging.getLogger("storage") + + +def udev_get_block_devices(): + udev_settle(timeout=2) + entries = [] + for path in 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"): + 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 enumerate_block_devices(): + top_dir = "/sys/class/block" + devices = [] + for dev_name in os.listdir(top_dir): + if __is_blacklisted_blockdev(dev_name): + continue + full_path = os.path.join(top_dir, dev_name) + link_ref = os.readlink(full_path) + real_path = os.path.join(top_dir, link_ref) + sysfs_path = os.path.normpath(real_path) + devices.append(sysfs_path) + return devices + +def udev_get_block_device(sysfs_path): + if not os.path.exists(sysfs_path): + log.debug("%s does not exist" % sysfs_path) + return None + + db_entry = sysfs_path[4:].replace("/", "\\x2f") + db_root = "/dev/.udev/db" + db_path = os.path.normpath("%s/%s" % (db_root, db_entry)) + if not os.access(db_path, os.R_OK): + log.debug("db entry %s does not exist" % db_path) + return None + + entry = open(db_path).read() + dev = udev_parse_block_entry(entry) + if dev: + # XXX why do we do this? is /sys going to move during installation? + dev['sysfs_path'] = sysfs_path[4:] # strip off the leading '/sys' + dev = udev_parse_uevent_file(dev) + + # now add in the contents of the uevent file since they're handy + return dev + +def udev_parse_uevent_file(dev): + path = os.path.normpath("/sys/%s/uevent" % dev['sysfs_path']) + if not os.access(path, os.R_OK): + return dev + + with open(path) as f: + for line in f.readlines(): + (key, equals, value) = line.strip().partition("=") + if not equals: + continue + + dev[key] = value + + return dev + +def udev_parse_block_entry(buf): + dev = {'name': None, + 'symlinks': []} + + for line in buf.splitlines(): + line.strip() + (tag, sep, val) = line.partition(":") + if not sep: + continue + + if tag == "N": + dev['name'] = val + elif tag == "S": + dev['symlinks'].append(val) + elif tag == "E": + if val.count("=") > 1 and val.count(" ") > 0: + # eg: LVM2_LV_NAME when querying the VG for its LVs + vars = val.split() + vals = [] + var_name = None + for (index, subval) in enumerate(vars): + (var_name, sep, var_val) = subval.partition("=") + if sep: + vals.append(var_val) + + dev[var_name] = vals + else: + (var_name, sep, var_val) = val.partition("=") + if not sep: + continue + + if var_val.count(" "): + # eg: DEVLINKS + var_val = var_val.split() + + dev[var_name] = var_val + + if dev.get("name"): + return dev + +def udev_settle(timeout=None): + argv = ["settle"] + if timeout: + argv.append("--timeout=%d" % int(timeout)) + + iutil.execWithRedirect("udevadm", argv, stderr="/dev/null", searchPath=1) + +def udev_trigger(subsystem=None): + argv = ["trigger"] + if subsystem: + argv.append("--subsystem-match=%s" % subsystem) + + iutil.execWithRedirect("udevadm", argv, stderr="/dev/null", searchPath=1) + + +""" These are functions for retrieving specific pieces of information from + udev database entries. +""" +def udev_device_get_name(udev_info): + """ Return the best name for a device based on the udev db data. """ + return udev_info.get("DM_NAME", udev_info["name"]) + +def udev_device_get_format(udev_info): + """ Return a device's format type as reported by udev. """ + return udev_info.get("ID_FS_TYPE") + +def udev_device_get_uuid(udev_info): + """ Get the UUID from the device's format as reported by udev. """ + md_uuid = udev_info.get("MD_UUID") + uuid = udev_info.get("ID_FS_UUID") + # we don't want to return the array's uuid as a member's uuid + if uuid and not md_uuid == uuid: + return udev_info.get("ID_FS_UUID") + +def udev_device_get_label(udev_info): + """ Get the label from the device's format as reported by udev. """ + return udev_info.get("ID_FS_LABEL") + +def udev_device_is_dm(info): + """ Return True if the device is a device-mapper device. """ + return info.has_key("DM_NAME") + +def udev_device_is_md(info): + """ Return True is the device is an mdraid array device. """ + return 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. """ + 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_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_dmraid(info): + # Note that this function does *not* identify raid sets. + # Tests to see if device is parto of a dmraid set. + # dmraid and mdriad 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 + if info.has_key("ID_FS_TYPE") and \ + info["ID_FS_TYPE"] in DMRaidMember._udevTypes: + return True + + return False + +def udev_device_get_dmraid_partition_disk(info): + try: + p_index = info["DM_NAME"].rindex("p") + except: + 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 + +# 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] diff --git a/zfcp.py b/storage/zfcp.py index e38b8186b..e38b8186b 100644 --- a/zfcp.py +++ b/storage/zfcp.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..d5b53a8af --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,29 @@ +import os + +# this has to be imported before running anything +import anaconda_log +import upgrade + + +def getAvailableSuites(): + root, tests_dir = os.path.split(os.path.dirname(__file__)) + modules = [] + + for root, dirs, files in os.walk(tests_dir): + for filename in files: + if filename.endswith(".py") and filename != "__init__.py": + basename, extension = os.path.splitext(filename) + modules.append(os.path.join(root, basename).replace("/", ".")) + + available_suites = {} + for module in modules: + imported = __import__(module, globals(), locals(), [module], -1) + try: + suite = getattr(imported, "suite") + except AttributeError as e: + continue + + if callable(suite): + available_suites[module] = suite() + + return available_suites diff --git a/tests/storage/__init__.py b/tests/storage/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/storage/__init__.py diff --git a/tests/storage/devicelibs/__init__.py b/tests/storage/devicelibs/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/storage/devicelibs/__init__.py diff --git a/tests/storage/devicelibs/baseclass.py b/tests/storage/devicelibs/baseclass.py new file mode 100644 index 000000000..c19bfc376 --- /dev/null +++ b/tests/storage/devicelibs/baseclass.py @@ -0,0 +1,55 @@ +import unittest +import os +import subprocess + + +def makeLoopDev(device_name, file_name): + proc = subprocess.Popen(["dd", "if=/dev/zero", "of=%s" % file_name, + "bs=1024", "count=102400"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + while True: + proc.communicate() + if proc.returncode is not None: + rc = proc.returncode + break + if rc: + raise OSError, "dd failed creating the file %s" % file_name + + proc = subprocess.Popen(["losetup", device_name, file_name], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + while True: + proc.communicate() + if proc.returncode is not None: + rc = proc.returncode + break + if rc: + raise OSError, "losetup failed setting up the loop device %s" % device_name + +def removeLoopDev(device_name, file_name): + proc = subprocess.Popen(["losetup", "-d", device_name], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + while True: + proc.communicate() + if proc.returncode is not None: + rc = proc.returncode + break + if rc: + raise OSError, "losetup failed removing the loop device %s" % device_name + + os.unlink(file_name) + + +class DevicelibsTestCase(unittest.TestCase): + + _LOOP_DEVICES = (("/dev/loop0", "/tmp/test-virtdev0"), + ("/dev/loop1", "/tmp/test-virtdev1")) + + ((_LOOP_DEV0, _LOOP_FILE0), (_LOOP_DEV1, _LOOP_FILE1)) = _LOOP_DEVICES + + def setUp(self): + for dev, file in self._LOOP_DEVICES: + makeLoopDev(dev, file) + + def tearDown(self): + for dev, file in self._LOOP_DEVICES: + removeLoopDev(dev, file) diff --git a/tests/storage/devicelibs/crypto.py b/tests/storage/devicelibs/crypto.py new file mode 100644 index 000000000..0f9f7bdb5 --- /dev/null +++ b/tests/storage/devicelibs/crypto.py @@ -0,0 +1,126 @@ +import baseclass +import unittest +import storage.devicelibs.crypto as crypto + +import tempfile +import os + +class CryptoTestCase(baseclass.DevicelibsTestCase): + + def testCrypto(self): + ## + ## is_luks + ## + # pass + self.assertEqual(crypto.is_luks(self._LOOP_DEV0), -22) + self.assertEqual(crypto.is_luks("/not/existing/device"), -22) + + ## + ## luks_format + ## + # pass + self.assertEqual(crypto.luks_format(self._LOOP_DEV0, passphrase="secret", cipher="aes-cbc-essiv:sha256", key_size=256), None) + + # make a key file + handle, keyfile = tempfile.mkstemp(prefix="key", text=False) + os.write(handle, "nobodyknows") + os.close(handle) + + # format with key file + self.assertEqual(crypto.luks_format(self._LOOP_DEV1, key_file=keyfile), None) + + # fail + self.assertRaises(crypto.CryptoError, crypto.luks_format, "/not/existing/device", passphrase="secret", cipher="aes-cbc-essiv:sha256", key_size=256) + # no passhprase or key file + self.assertRaises(ValueError, crypto.luks_format, self._LOOP_DEV1, cipher="aes-cbc-essiv:sha256", key_size=256) + + ## + ## is_luks + ## + # pass + self.assertEqual(crypto.is_luks(self._LOOP_DEV0), 0) # 0 = is luks + self.assertEqual(crypto.is_luks(self._LOOP_DEV1), 0) + + ## + ## luks_add_key + ## + # pass + self.assertEqual(crypto.luks_add_key(self._LOOP_DEV0, new_passphrase="another-secret", passphrase="secret"), None) + + # make another key file + handle, new_keyfile = tempfile.mkstemp(prefix="key", text=False) + os.write(handle, "area51") + os.close(handle) + + # add new key file + self.assertEqual(crypto.luks_add_key(self._LOOP_DEV1, new_key_file=new_keyfile, key_file=keyfile), None) + + # fail + self.assertRaises(RuntimeError, crypto.luks_add_key, self._LOOP_DEV0, new_passphrase="another-secret", passphrase="wrong-passphrase") + + ## + ## luks_remove_key + ## + # fail + self.assertRaises(RuntimeError, crypto.luks_remove_key, self._LOOP_DEV0, del_passphrase="another-secret", passphrase="wrong-pasphrase") + + # pass + self.assertEqual(crypto.luks_remove_key(self._LOOP_DEV0, del_passphrase="another-secret", passphrase="secret"), None) + + # remove key file + self.assertEqual(crypto.luks_remove_key(self._LOOP_DEV1, del_key_file=new_keyfile, key_file=keyfile), None) + + ## + ## luks_open + ## + # pass + self.assertEqual(crypto.luks_open(self._LOOP_DEV0, "crypted", passphrase="secret"), None) + self.assertEqual(crypto.luks_open(self._LOOP_DEV1, "encrypted", key_file=keyfile), None) + + # fail + self.assertRaises(crypto.CryptoError, crypto.luks_open, "/not/existing/device", "another-crypted", passphrase="secret") + self.assertRaises(crypto.CryptoError, crypto.luks_open, "/not/existing/device", "another-crypted", key_file=keyfile) + # no passhprase or key file + self.assertRaises(ValueError, crypto.luks_open, self._LOOP_DEV1, "another-crypted") + + ## + ## luks_status + ## + # pass + self.assertEqual(crypto.luks_status("crypted"), True) + self.assertEqual(crypto.luks_status("encrypted"), True) + self.assertEqual(crypto.luks_status("another-crypted"), False) + + ## + ## luks_uuid + ## + # pass + uuid = crypto.luks_uuid(self._LOOP_DEV0) + self.assertEqual(crypto.luks_uuid(self._LOOP_DEV0), uuid) + uuid = crypto.luks_uuid(self._LOOP_DEV1) + self.assertEqual(crypto.luks_uuid(self._LOOP_DEV1), uuid) + + ## + ## luks_close + ## + # pass + self.assertEqual(crypto.luks_close("crypted"), None) + self.assertEqual(crypto.luks_close("encrypted"), None) + + # fail + self.assertRaises(crypto.CryptoError, crypto.luks_close, "wrong-name") + # already closed + self.assertRaises(crypto.CryptoError, crypto.luks_close, "crypted") + self.assertRaises(crypto.CryptoError, crypto.luks_close, "encrypted") + + # cleanup + os.unlink(keyfile) + os.unlink(new_keyfile) + + +def suite(): + return unittest.TestLoader().loadTestsFromTestCase(CryptoTestCase) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/storage/devicelibs/lvm.py b/tests/storage/devicelibs/lvm.py new file mode 100644 index 000000000..e6ba1a628 --- /dev/null +++ b/tests/storage/devicelibs/lvm.py @@ -0,0 +1,230 @@ +import baseclass +import unittest +import storage.devicelibs.lvm as lvm + +class LVMTestCase(baseclass.DevicelibsTestCase): + + def testLVM(self): + ## + ## pvcreate + ## + # pass + for dev, file in self._LOOP_DEVICES: + self.assertEqual(lvm.pvcreate(dev), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.pvcreate, "/not/existing/device") + + ## + ## pvresize + ## + # pass + for dev, file in self._LOOP_DEVICES: + self.assertEqual(lvm.pvresize(dev, 50), None) + self.assertEqual(lvm.pvresize(dev, 100), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.pvresize, "/not/existing/device", 50) + + ## + ## vgcreate + ## + # pass + self.assertEqual(lvm.vgcreate("test-vg", [self._LOOP_DEV0, self._LOOP_DEV1], 4), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.vgcreate, "another-vg", ["/not/existing/device"], 4) + # vg already exists + self.assertRaises(lvm.LVMError, lvm.vgcreate, "test-vg", [self._LOOP_DEV0], 4) + # pe size must be power of 2 + self.assertRaises(lvm.LVMError, lvm.vgcreate, "another-vg", [self._LOOP_DEV0], 5) + + ## + ## pvremove + ## + # fail + # cannot remove pv now with vg created + self.assertRaises(lvm.LVMError, lvm.pvremove, self._LOOP_DEV0) + + ## + ## vgdeactivate + ## + # pass + self.assertEqual(lvm.vgdeactivate("test-vg"), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.vgdeactivate, "wrong-vg-name") + + ## + ## vgreduce + ## + # pass + self.assertEqual(lvm.vgreduce("test-vg", [self._LOOP_DEV1]), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.vgreduce, "wrong-vg-name", [self._LOOP_DEV1]) + self.assertRaises(lvm.LVMError, lvm.vgreduce, "test-vg", ["/not/existing/device"]) + + ## + ## vgactivate + ## + # pass + self.assertEqual(lvm.vgactivate("test-vg"), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.vgactivate, "wrong-vg-name") + + ## + ## pvinfo + ## + # pass + self.assertEqual(lvm.pvinfo(self._LOOP_DEV0)["pv_name"], self._LOOP_DEV0) + # no vg + self.assertEqual(lvm.pvinfo(self._LOOP_DEV1)["pv_name"], self._LOOP_DEV1) + + # fail + self.assertRaises(lvm.LVMError, lvm.pvinfo, "/not/existing/device") + + ## + ## vginfo + ## + # pass + self.assertEqual(lvm.vginfo("test-vg")["pe_size"], "4.00") + + # fail + self.assertRaises(lvm.LVMError, lvm.vginfo, "wrong-vg-name") + + ## + ## lvcreate + ## + # pass + self.assertEqual(lvm.lvcreate("test-vg", "test-lv", 10), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.lvcreate, "wrong-vg-name", "another-lv", 10) + + ## + ## lvdeactivate + ## + # pass + self.assertEqual(lvm.lvdeactivate("test-vg", "test-lv"), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.lvdeactivate, "test-vg", "wrong-lv-name") + self.assertRaises(lvm.LVMError, lvm.lvdeactivate, "wrong-vg-name", "test-lv") + self.assertRaises(lvm.LVMError, lvm.lvdeactivate, "wrong-vg-name", "wrong-lv-name") + + ## + ## lvresize + ## + # pass + self.assertEqual(lvm.lvresize("test-vg", "test-lv", 60), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.lvresize, "test-vg", "wrong-lv-name", 80) + self.assertRaises(lvm.LVMError, lvm.lvresize, "wrong-vg-name", "test-lv", 80) + self.assertRaises(lvm.LVMError, lvm.lvresize, "wrong-vg-name", "wrong-lv-name", 80) + # changing to same size + self.assertRaises(lvm.LVMError, lvm.lvresize, "test-vg", "test-lv", 60) + + ## + ## lvactivate + ## + # pass + self.assertEqual(lvm.lvactivate("test-vg", "test-lv"), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.lvactivate, "test-vg", "wrong-lv-name") + self.assertRaises(lvm.LVMError, lvm.lvactivate, "wrong-vg-name", "test-lv") + self.assertRaises(lvm.LVMError, lvm.lvactivate, "wrong-vg-name", "wrong-lv-name") + + ## + ## lvs + ## + # pass + self.assertEqual(lvm.lvs("test-vg")["test-lv"]["size"], "60.00") + + # fail + self.assertRaises(lvm.LVMError, lvm.lvs, "wrong-vg-name") + + ## + ## has_lvm + ## + # pass + self.assertEqual(lvm.has_lvm(), True) + + # fail + # TODO + + ## + ## lvremove + ## + # pass + self.assertEqual(lvm.lvdeactivate("test-vg", "test-lv"), None) # is deactivation needed? + self.assertEqual(lvm.lvremove("test-vg", "test-lv"), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.lvremove, "test-vg", "wrong-lv-name") + self.assertRaises(lvm.LVMError, lvm.lvremove, "wrong-vg-name", "test-lv") + self.assertRaises(lvm.LVMError, lvm.lvremove, "wrong-vg-name", "wrong-lv-name") + # lv already removed + self.assertRaises(lvm.LVMError, lvm.lvremove, "test-vg", "test-lv") + + ## + ## vgremove + ## + # pass + self.assertEqual(lvm.vgremove("test-vg"), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.vgremove, "wrong-vg-name") + # vg already removed + self.assertRaises(lvm.LVMError, lvm.vgremove, "test-vg") + + ## + ## pvremove + ## + # pass + for dev, file in self._LOOP_DEVICES: + self.assertEqual(lvm.pvremove(dev), None) + + # fail + self.assertRaises(lvm.LVMError, lvm.pvremove, "/not/existing/device") + # pv already removed + self.assertRaises(lvm.LVMError, lvm.pvremove, self._LOOP_DEV0) + + #def testGetPossiblePhysicalExtents(self): + # pass + self.assertEqual(lvm.getPossiblePhysicalExtents(4), + filter(lambda pe: pe > 4, map(lambda power: 2**power, xrange(3, 25)))) + self.assertEqual(lvm.getPossiblePhysicalExtents(100000), + filter(lambda pe: pe > 100000, map(lambda power: 2**power, xrange(3, 25)))) + + #def testGetMaxLVSize(self): + # pass + self.assertEqual(lvm.getMaxLVSize(), 16*1024**2) + + #def testSafeLVMName(self): + # pass + self.assertEqual(lvm.safeLvmName("/strange/lv*name5"), "strange_lvname5") + + #def testClampSize(self): + # pass + self.assertEqual(lvm.clampSize(10, 4), 8L) + self.assertEqual(lvm.clampSize(10, 4, True), 12L) + + #def testVGUsedSpace(self): + # TODO + pass + + #def testVGFreeSpace(self): + # TODO + pass + + +def suite(): + return unittest.TestLoader().loadTestsFromTestCase(LVMTestCase) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/storage/devicelibs/mdraid.py b/tests/storage/devicelibs/mdraid.py new file mode 100644 index 000000000..3c0ee7224 --- /dev/null +++ b/tests/storage/devicelibs/mdraid.py @@ -0,0 +1,107 @@ +import baseclass +import unittest +import storage.devicelibs.mdraid as mdraid + +import time + +class MDRaidTestCase(baseclass.DevicelibsTestCase): + + def testMDRaid(self): + ## + ## getRaidLevels + ## + # pass + self.assertEqual(mdraid.getRaidLevels(), mdraid.getRaidLevels()) + + ## + ## get_raid_min_members + ## + # pass + self.assertEqual(mdraid.get_raid_min_members(mdraid.RAID0), 2) + self.assertEqual(mdraid.get_raid_min_members(mdraid.RAID1), 2) + self.assertEqual(mdraid.get_raid_min_members(mdraid.RAID5), 3) + self.assertEqual(mdraid.get_raid_min_members(mdraid.RAID6), 4) + self.assertEqual(mdraid.get_raid_min_members(mdraid.RAID10), 2) + + # fail + # unsupported raid + self.assertRaises(ValueError, mdraid.get_raid_min_members, 4) + + ## + ## get_raid_max_spares + ## + # pass + self.assertEqual(mdraid.get_raid_max_spares(mdraid.RAID0, 5), 0) + self.assertEqual(mdraid.get_raid_max_spares(mdraid.RAID1, 5), 3) + self.assertEqual(mdraid.get_raid_max_spares(mdraid.RAID5, 5), 2) + self.assertEqual(mdraid.get_raid_max_spares(mdraid.RAID6, 5), 1) + self.assertEqual(mdraid.get_raid_max_spares(mdraid.RAID10, 5), 3) + + # fail + # unsupported raid + self.assertRaises(ValueError, mdraid.get_raid_max_spares, 4, 5) + + ## + ## mdcreate + ## + # pass + self.assertEqual(mdraid.mdcreate("/dev/md0", 1, [self._LOOP_DEV0, self._LOOP_DEV1]), None) + # wait for raid to settle + time.sleep(2) + + # fail + self.assertRaises(mdraid.MDRaidError, mdraid.mdcreate, "/dev/md1", 1, ["/not/existing/dev0", "/not/existing/dev1"]) + + ## + ## mddeactivate + ## + # pass + self.assertEqual(mdraid.mddeactivate("/dev/md0"), None) + + # fail + self.assertRaises(mdraid.MDRaidError, mdraid.mddeactivate, "/not/existing/md") + + ## + ## mdadd + ## + # pass + # TODO + + # fail + self.assertRaises(mdraid.MDRaidError, mdraid.mdadd, "/not/existing/device") + + ## + ## mdactivate + ## + # pass + self.assertEqual(mdraid.mdactivate("/dev/md0", [self._LOOP_DEV0, self._LOOP_DEV1], super_minor=0), None) + # wait for raid to settle + time.sleep(2) + + # fail + self.assertRaises(mdraid.MDRaidError, mdraid.mdactivate, "/not/existing/md", super_minor=1) + # requires super_minor or uuid + self.assertRaises(ValueError, mdraid.mdactivate, "/dev/md1") + + ## + ## mddestroy + ## + # pass + # deactivate first + self.assertEqual(mdraid.mddeactivate("/dev/md0"), None) + + self.assertEqual(mdraid.mddestroy(self._LOOP_DEV0), None) + self.assertEqual(mdraid.mddestroy(self._LOOP_DEV1), None) + + # fail + # not a component + self.assertRaises(mdraid.MDRaidError, mdraid.mddestroy, "/dev/md0") + self.assertRaises(mdraid.MDRaidError, mdraid.mddestroy, "/not/existing/device") + + +def suite(): + return unittest.TestLoader().loadTestsFromTestCase(MDRaidTestCase) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/storage/devicelibs/swap.py b/tests/storage/devicelibs/swap.py new file mode 100644 index 000000000..b99d1f67c --- /dev/null +++ b/tests/storage/devicelibs/swap.py @@ -0,0 +1,66 @@ +import baseclass +import unittest +import storage.devicelibs.swap as swap + +class SwapTestCase(baseclass.DevicelibsTestCase): + + def testSwap(self): + ## + ## mkswap + ## + # pass + self.assertEqual(swap.mkswap(self._LOOP_DEV0, "swap"), None) + + # fail + self.assertRaises(swap.SwapError, swap.mkswap, "/not/existing/device") + + ## + ## swapon + ## + # pass + self.assertEqual(swap.swapon(self._LOOP_DEV0, 1), None) + + # fail + self.assertRaises(swap.SwapError, swap.swapon, "/not/existing/device") + # not a swap partition + self.assertRaises(swap.SwapError, swap.swapon, self._LOOP_DEV1) + + # pass + # make another swap + self.assertEqual(swap.mkswap(self._LOOP_DEV1, "another-swap"), None) + self.assertEqual(swap.swapon(self._LOOP_DEV1), None) + + ## + ## swapstatus + ## + # pass + self.assertEqual(swap.swapstatus(self._LOOP_DEV0), True) + self.assertEqual(swap.swapstatus(self._LOOP_DEV1), True) + + # does not fail + self.assertEqual(swap.swapstatus("/not/existing/device"), False) + + ## + ## swapoff + ## + # pass + self.assertEqual(swap.swapoff(self._LOOP_DEV1), None) + + # check status + self.assertEqual(swap.swapstatus(self._LOOP_DEV0), True) + self.assertEqual(swap.swapstatus(self._LOOP_DEV1), False) + + self.assertEqual(swap.swapoff(self._LOOP_DEV0), None) + + # fail + self.assertRaises(swap.SwapError, swap.swapoff, "/not/existing/device") + # already off + self.assertRaises(swap.SwapError, swap.swapoff, self._LOOP_DEV0) + + +def suite(): + return unittest.TestLoader().loadTestsFromTestCase(SwapTestCase) + + +if __name__ == "__main__": + unittest.main() @@ -242,7 +242,7 @@ class SaveExceptionWindow: toplevel.add(scpGrid, 0, 5, (0, 0, 0, 1)) toplevel.add(buttons, 0, 6, growx=1) - dests = self.anaconda.id.diskset.exceptionDisks(self.anaconda) + dests = self.anaconda.id.storage.exceptionDisks() if len(dests) > 0: for (dev, desc) in dests: @@ -629,10 +629,6 @@ class InstallInterface: # draw the frame after setting up the fallback self.drawFrame() - anaconda.id.fsset.registerMessageWindow(self.messageWindow) - anaconda.id.fsset.registerProgressWindow(self.progressWindow) - anaconda.id.fsset.registerWaitWindow(self.waitWindow) - lastrc = INSTALL_OK (step, instance) = anaconda.dispatch.currentStep() while step: diff --git a/textw/complete_text.py b/textw/complete_text.py index 1793cab10..fc4e5c1f9 100644 --- a/textw/complete_text.py +++ b/textw/complete_text.py @@ -22,6 +22,7 @@ from snack import * from constants_text import * from constants import * import gettext +import platform _ = lambda x: gettext.ldgettext("anaconda", x) class FinishedWindow: @@ -34,7 +35,7 @@ class FinishedWindow: screen.pushHelpLine (string.center(bottomstr, screen.width)) - if iutil.isS390(): + if isinstance(anaconda.platform, platform.S390): txt = _("Congratulations, your %s installation is complete.\n\n") % (productName,) if not anaconda.canReIPL: diff --git a/textw/partition_text.py b/textw/partition_text.py index ded62f283..472a5cea3 100644 --- a/textw/partition_text.py +++ b/textw/partition_text.py @@ -27,13 +27,7 @@ import string import copy import network import parted -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_text import * from constants import * @@ -73,7 +67,7 @@ class PartitionTypeWindow: for (txt, val) in opts: typebox.append(txt, val) - typebox.setCurrent(anaconda.id.partitions.autoClearPartType) + typebox.setCurrent(anaconda.id.storage.clearPartType) g.add(typebox, 0, 1, (0, 1, 0, 0)) @@ -97,13 +91,11 @@ class PartitionTypeWindow: screen.pushHelpLine (_("<Space>,<+>,<-> selection | <F2> Add drive | <F12> next screen")) # restore the drive list each time - disks = anaconda.id.diskset.disks.keys() - disks.sort() - cleardrives = anaconda.id.partitions.autoClearPartDrives + disks = anaconda.id.storage.disks + cleardrives = anaconda.id.storage.clearPartDisks for disk in disks: - size = anaconda.id.diskset.disks[disk].device.getSize(unit="MB") - model = anaconda.id.diskset.disks[disk].device.model + model = disk.partedDisk.device.model if not cleardrives or len(cleardrives) < 1: selected = 1 @@ -113,8 +105,8 @@ class PartitionTypeWindow: else: selected = 0 - sizestr = "%8.0f MB" % (size,) - diskdesc = "%6s %s (%s)" % (disk, sizestr, model[:24],) + sizestr = "%8.0f MB" % (disk.size,) + diskdesc = "%6s %s (%s)" % (disk.name, sizestr, model[:23],) drivelist.append(diskdesc, selected = selected) @@ -133,13 +125,13 @@ class PartitionTypeWindow: if rc == "F2": if self.addDriveDialog(screen) != INSTALL_BACK: - partitions.partitionObjectsInitialize(anaconda) + anaconda.id.storage.reset() continue if res == TEXT_BACK_CHECK: return INSTALL_BACK - if anaconda.id.diskset.checkNoDisks(): + if anaconda.id.storage.checkNoDisks(): continue if len(sel) < 1: @@ -147,8 +139,9 @@ class PartitionTypeWindow: continue anaconda.dispatch.skipStep("autopartitionexecute", skip = 0) - anaconda.id.partitions.autoClearPartType = partmethod_ans - anaconda.id.partitions.autoClearPartDrives = sel + anaconda.id.storage.doAutoPart = True + anaconda.id.storage.clearPartType = partmethod_ans + anaconda.id.storage.clearPartDisks = sel break # ask to review autopartition layout - but only if it's not custom partitioning @@ -159,7 +152,7 @@ class PartitionTypeWindow: def addDriveDialog(self, screen): newdrv = [] - import iscsi + from storage import iscsi if iscsi.has_iscsi(): newdrv.append("Add iSCSI target") if iutil.isS390(): @@ -204,7 +197,7 @@ class PartitionTypeWindow: devnum = entries[0].strip() wwpn = entries[1].strip() fcplun = entries[2].strip() - self.anaconda.id.zfcp.addFCP(devnum, wwpn, fcplun) + self.anaconda.id.storage.zfcp.addFCP(devnum, wwpn, fcplun) return INSTALL_OK @@ -255,8 +248,9 @@ class PartitionTypeWindow: raise ValueError, msg iname = entries[1].strip() - if not self.anaconda.id.iscsi.initiatorSet: - self.anaconda.id.iscsi.initiator = iname - self.anaconda.id.iscsi.addTarget(ip, port, user, pw, user_in, pw_in) + if not self.anaconda.id.storage.iscsi.initiatorSet: + self.anaconda.id.storage.iscsi.initiator = iname + self.anaconda.id.storage.iscsi.addTarget(ip, port, user, pw, + user_in, pw_in) return INSTALL_OK diff --git a/textw/upgrade_bootloader_text.py b/textw/upgrade_bootloader_text.py index 636d78998..ab45075f8 100644 --- a/textw/upgrade_bootloader_text.py +++ b/textw/upgrade_bootloader_text.py @@ -23,7 +23,7 @@ from snack import * from constants_text import * from flags import flags import string -import checkbootloader +from booty import checkbootloader from constants import * import gettext @@ -86,7 +86,7 @@ class UpgradeBootloaderWindow: newToLibata = self._ideToLibata(anaconda.rootPath) (self.type, self.bootDev) = \ - checkbootloader.getBootloaderTypeAndBoot(anaconda.rootPath) + checkbootloader.getBootloaderTypeAndBoot(anaconda.rootPath, storage=anaconda.id.storage) blradio = RadioGroup() @@ -173,7 +173,7 @@ class UpgradeBootloaderWindow: self.bl.useGrubVal = 1 else: self.bl.useGrubVal = 0 - self.bl.setDevice(self.bootDev) + self.bl.setDevice(self.bootDev.split("/")[-1]) diff --git a/textw/upgrade_text.py b/textw/upgrade_text.py index 34c08238d..54c36edcb 100644 --- a/textw/upgrade_text.py +++ b/textw/upgrade_text.py @@ -22,7 +22,6 @@ import iutil import upgrade from constants_text import * from snack import * -from fsset import * from flags import flags from constants import * @@ -32,7 +31,7 @@ _ = lambda x: gettext.ldgettext("anaconda", x) class UpgradeMigrateFSWindow: def __call__ (self, screen, anaconda): - migent = anaconda.id.fsset.getMigratableEntries() + migent = anaconda.id.storage.fsset.getMigratableEntries() g = GridFormHelp(screen, _("Migrate File Systems"), "upmigfs", 1, 4) @@ -48,15 +47,17 @@ class UpgradeMigrateFSWindow: g.add(tb, 0, 0, anchorLeft = 1, padding = (0, 0, 0, 1)) partlist = CheckboxTree(height=4, scroll=1) - for entry in migent: - if entry.fsystem.getName() != entry.origfsystem.getName(): - migrating = 1 + for device in migent: + if not device.format.exists: + migrating = True else: - migrating = 0 + migrating = False - partlist.append("/dev/%s - %s - %s" % (entry.device.getDevice(), - entry.origfsystem.getName(), - entry.mountpoint), entry, migrating) + # FIXME: the fstype at least will be wrong here + partlist.append("%s - %s - %s" % (device.path, + device.format.type, + device.format.mountpoint), + device, migrating) g.add(partlist, 0, 1, padding = (0, 0, 0, 1)) @@ -74,29 +75,26 @@ class UpgradeMigrateFSWindow: return INSTALL_BACK # reset - for entry in migent: - entry.setFormat(0) - entry.setMigrate(0) - entry.fsystem = entry.origfsystem + # XXX the way to do this is by scheduling and cancelling actions + #for entry in migent: + # entry.setFormat(0) + # entry.setMigrate(0) + # entry.fsystem = entry.origfsystem for entry in partlist.getSelection(): try: - newfs = entry.fsystem.migratetofs[0] - newfs = fileSystemTypeGet(newfs) + newfs = getFormat(entry.format.migratetofs[0]) except Exception, e: log.info("failed to get new filesystem type, defaulting to ext3: %s" %(e,)) - newfs = fileSystemTypeGet("ext3") - entry.setFileSystemType(newfs) - entry.setFormat(0) - entry.setMigrate(1) - + newfs = getFormat("ext3") + anaconda.id.storage.migrateFormat(entry, newfs) screen.popWindow() return INSTALL_OK class UpgradeSwapWindow: def __call__ (self, screen, anaconda): - (fsList, suggSize, suggMntPoint) = anaconda.id.upgradeSwapInfo + (fsList, suggSize, suggDev) = anaconda.id.upgradeSwapInfo ramDetected = iutil.memInstalled()/1024 @@ -121,10 +119,13 @@ class UpgradeSwapWindow: _("Partition"), _("Free Space"))) count = 0 - for (mnt, part, size) in fsList: - listbox.append("%-25s /dev/%-10s %6dMB" % (mnt, part, size), count) + for (device, size) in fsList: + listbox.append("%-25s %-15s %6dMB" % (device.format.mountpoint, + device.path, + size), + count) - if (mnt == suggMntPoint): + if (device == suggDev): listbox.setCurrent(count) count = count + 1 @@ -176,7 +177,7 @@ class UpgradeSwapWindow: "valid number.")) if type(val) == type(1): - (mnt, part, size) = fsList[listbox.current()] + (dev, size) = fsList[listbox.current()] if size < (val + 16): anaconda.intf.messageWindow(_("Error"), _("There is not enough space on the " @@ -189,7 +190,7 @@ class UpgradeSwapWindow: else: screen.popWindow() if flags.setupFilesystems: - upgrade.createSwapFile(anaconda.rootPath, anaconda.id.fsset, mnt, val) + anaconda.id.storage.fsset.createSwapFile(anaconda.rootPath, dev, val) anaconda.dispatch.skipStep("addswap", 1) return INSTALL_OK @@ -212,12 +213,8 @@ class UpgradeExamineWindow: else: default = 0 - for (drive, fs, desc, label, uuid) in parts: - if drive[:5] != "/dev/": - devname = "/dev/" + drive - else: - devname = drive - partList.append("%s (%s)" %(desc, drive)) + for (device, desc) in parts: + partList.append("%s (%s)" %(desc, device.path)) (button, choice) = ListboxChoiceWindow(screen, _("System to Upgrade"), _("There seem to be one or more existing Linux installations " diff --git a/ui/autopart.glade b/ui/autopart.glade index 199c3591d..333527423 100644 --- a/ui/autopart.glade +++ b/ui/autopart.glade @@ -341,9 +341,9 @@ <widget class="GtkDialog" id="resizeDialog"> <property name="border_width">12</property> <property name="visible">True</property> - <property name="title" translatable="yes">Which Partition to resize</property> + <property name="title" translatable="yes">Volume to Resize</property> <property name="type">GTK_WINDOW_TOPLEVEL</property> - <property name="window_position">GTK_WIN_POS_NONE</property> + <property name="window_position">GTK_WIN_POS_CENTER</property> <property name="modal">False</property> <property name="resizable">True</property> <property name="destroy_with_parent">False</property> @@ -517,7 +517,7 @@ <child> <widget class="GtkLabel" id="label6"> <property name="visible">True</property> - <property name="label" translatable="yes"><b>Resize _target:</b></property> + <property name="label" translatable="yes"><b>Resize _target (in MB):</b></property> <property name="use_underline">True</property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_LEFT</property> diff --git a/upgrade.py b/upgrade.py index c1b7b83e7..6f6ddd05c 100644 --- a/upgrade.py +++ b/upgrade.py @@ -26,14 +26,14 @@ import iutil import time import sys import os.path -import partedUtils import string import selinux -import lvm from flags import flags -from fsset import * from constants import * from product import productName +from storage import findExistingRootDevices +from storage import mountExistingSystem +from storage.formats import getFormat import rhpl import rhpl.arch @@ -134,11 +134,8 @@ def findRootParts(anaconda): root_device=anaconda.id.ksdata.upgrade.root_device anaconda.id.upgradeRoot = [] - for (dev, fs, meta, label, uuid) in anaconda.id.rootParts: - if (root_device is not None) and ((dev == root_device) or (("UUID=%s" % uuid) == root_device) or (("LABEL=%s" % label) == root_device)): - anaconda.id.upgradeRoot.append( (dev, fs) ) - else: - anaconda.id.upgradeRoot.append( (dev, fs) ) + for (dev, label) in anaconda.id.rootParts: + anaconda.id.upgradeRoot.append( (dev, label) ) if anaconda.id.rootParts is not None and len(anaconda.id.rootParts) > 0: anaconda.dispatch.skipStep("findinstall", skip = 0) @@ -149,115 +146,24 @@ def findRootParts(anaconda): anaconda.dispatch.skipStep("installtype", skip = 0) def findExistingRoots(anaconda, upgradeany = 0): - # make ibft configured iscsi disks available - anaconda.id.iscsi.startup(anaconda.intf) - if not flags.setupFilesystems: (prod, ver) = partedUtils.getReleaseString (anaconda.rootPath) if flags.cmdline.has_key("upgradeany") or upgradeany == 1 or anaconda.id.instClass.productUpgradable(prod, ver): - return [(anaconda.rootPath, 'ext2', "")] + return [(anaconda.rootPath, "")] return [] - anaconda.id.diskset.openDevices() - anaconda.id.partitions.getEncryptedDevices(anaconda.id.diskset) - rootparts = anaconda.id.diskset.findExistingRootPartitions(upgradeany = upgradeany) - - # close the devices to make sure we don't leave things sitting open - anaconda.id.diskset.closeDevices() - - # this is a hack... need to clear the skipped disk list after this - partedUtils.DiskSet.skippedDisks = [] - partedUtils.DiskSet.exclusiveDisks = [] - + rootparts = findExistingRootDevices(anaconda, upgradeany=upgradeany) return rootparts -def getDirtyDevString(dirtyDevs): - ret = "" - for dev in dirtyDevs: - if dev != "loop": - ret = "/dev/%s\n" % (dev,) - else: - ret = "%s\n" % (dev,) - return ret - -def mountRootPartition(anaconda, rootInfo, oldfsset, allowDirty = 0, - warnDirty = 0, readOnly = 0): - (root, rootFs) = rootInfo - bindMount = 0 - - encryptedDevices = anaconda.id.partitions.encryptedDevices - diskset = partedUtils.DiskSet(anaconda) - diskset.openDevices() - for cryptoDev in encryptedDevices.values(): - cryptoDev.openDevice() - diskset.startMPath() - diskset.startDmRaid() - diskset.startMdRaid() - for cryptoDev in encryptedDevices.values(): - cryptoDev.openDevice() - lvm.vgscan() - lvm.vgactivate() - for cryptoDev in encryptedDevices.values(): - cryptoDev.openDevice() - - if root in anaconda.id.partitions.protectedPartitions() and os.path.ismount("/mnt/isodir"): - root = "/mnt/isodir" - bindMount = 1 - - log.info("going to mount %s on %s as %s" %(root, anaconda.rootPath, rootFs)) - isys.mount(root, anaconda.rootPath, rootFs, bindMount=bindMount) - - oldfsset.reset() - newfsset = readFstab(anaconda) - - for entry in newfsset.entries: - oldfsset.add(entry) - - if not bindMount: - isys.umount(anaconda.rootPath) - - dirtyDevs = oldfsset.hasDirtyFilesystems(anaconda.rootPath) - if not allowDirty and dirtyDevs != []: - lvm.vgdeactivate() - diskset.stopMdRaid() - diskset.stopDmRaid() - diskset.stopMPath() - anaconda.intf.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" %(getDirtyDevString(dirtyDevs),))) - sys.exit(0) - elif warnDirty and dirtyDevs != []: - rc = anaconda.intf.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" % (getDirtyDevString(dirtyDevs,))), - type = "yesno") - if rc == 0: - return -1 - - if flags.setupFilesystems: - for dev, crypto in encryptedDevices.items(): - if crypto.openDevice(): - log.error("failed to open encrypted device %s" % (dev,)) - - oldfsset.mountFilesystems(anaconda, readOnly = readOnly, - skiprootfs = bindMount) - - rootEntry = oldfsset.getEntryByMountPoint("/") - if (not rootEntry or not rootEntry.fsystem or not rootEntry.fsystem.isMountable()): - raise RuntimeError, "/etc/fstab did not list a fstype for the root partition which we support" - def bindMountDevDirectory(instPath): - fs = fileSystemTypeGet("bind") - fs.mount("/dev", "/dev", bindMount=1, instroot=instPath) + getFormat("bind", + device="/dev", + mountpoint="/dev", + exists=True).mount(chroot=instPath) # returns None if no filesystem exist to migrate def upgradeMigrateFind(anaconda): - migents = anaconda.id.fsset.getMigratableEntries() + migents = anaconda.id.storage.fsset.migratableDevices if not migents or len(migents) < 1: anaconda.dispatch.skipStep("upgrademigratefs") else: @@ -295,13 +201,15 @@ def upgradeSwapSuggestion(anaconda): fsList = [] - for entry in anaconda.id.fsset.entries: - if entry.fsystem.getName() in getUsableLinuxFs(): - if flags.setupFilesystems and not entry.isMounted(): + for device in anaconda.id.storage.fsset.devices: + if not device.format: + continue + if device.format.mountable and device.format.linuxNative: + if flags.setupFilesystems and not device.format.status: continue - space = isys.pathSpaceAvailable(anaconda.rootPath + entry.mountpoint) + space = isys.pathSpaceAvailable(anaconda.rootPath + device.format.mountpoint) if space > 16: - info = (entry.mountpoint, entry.device.getDevice(), space) + info = (device, space) fsList.append(info) suggestion = mem * 2 - swap @@ -311,57 +219,11 @@ def upgradeSwapSuggestion(anaconda): suggestion = 32 suggSize = 0 suggMnt = None - for (mnt, part, size) in fsList: + for (device, size) in fsList: if (size > suggSize) and (size > (suggestion + 100)): - suggMnt = mnt - - anaconda.id.upgradeSwapInfo = (fsList, suggestion, suggMnt) - -def swapfileExists(swapname): - try: - os.lstat(swapname) - return 1 - except: - return 0 + suggDev = device -def createSwapFile(instPath, theFsset, mntPoint, size): - fstabPath = instPath + "/etc/fstab" - prefix = "" - - if mntPoint != "/": - file = mntPoint + "/SWAP" - else: - file = "/SWAP" - - swapFileDict = {} - for entry in theFsset.entries: - if entry.fsystem.getName() == "swap": - swapFileDict[entry.device.getName()] = 1 - - count = 0 - while (swapfileExists(instPath + file) or - swapFileDict.has_key(file)): - count = count + 1 - tmpFile = "/SWAP-%d" % (count) - if mntPoint != "/": - file = mntPoint + tmpFile - else: - file = tmpFile - - device = SwapFileDevice(file) - device.setSize(size) - fsystem = fileSystemTypeGet("swap") - entry = FileSystemSetEntry(device, "swap", fsystem) - entry.setFormat(1) - theFsset.add(entry) - theFsset.formatEntry(entry, instPath) - theFsset.turnOnSwap(instPath, upgrading=True) - - # XXX generalize fstab modification - f = open(fstabPath, "a") - format = "%-23s %-23s %-7s %-15s %d %d\n"; - f.write(format % (prefix + file, "swap", "swap", "defaults", 0, 0)) - f.close() + anaconda.id.upgradeSwapInfo = (fsList, suggestion, suggDev) # XXX handle going backwards def upgradeMountFilesystems(anaconda): @@ -369,21 +231,31 @@ def upgradeMountFilesystems(anaconda): if flags.setupFilesystems: try: - mountRootPartition(anaconda, anaconda.id.upgradeRoot[0], anaconda.id.fsset, - allowDirty = 0) - except SystemError: + mountExistingSystem(anaconda, + anaconda.id.upgradeRoot[0], + allowDirty = 0) + except ValueError as e: + log.error("Error mounting filesystem: %s" % e) anaconda.intf.messageWindow(_("Mount failed"), - _("One or more of the file systems listed in the " - "/etc/fstab on your Linux system cannot be mounted. " - "Please fix this problem and try to upgrade again.")) + _("The following error occurred when mounting the file " + "systems listed in /etc/fstab. Please fix this problem " + "and try to upgrade again.\n%s" % e)) sys.exit(0) - except RuntimeError: - anaconda.intf.messageWindow(_("Mount failed"), - _("One or more of the file systems listed in the " - "/etc/fstab of your Linux system are inconsistent and " - "cannot be mounted. Please fix this problem and try to " - "upgrade again.")) - sys.exit(0) + except IndexError as e: + # The upgrade root is search earlier but we give the message here. + log.debug("No upgrade root was fond.") + rc = anaconda.intf.messageWindow(_("Upgrade root not found"), + _("The root for the previously installed system was not " + "found. You can exit installer or backtrack to choose " + "installation instead of upgrade."), + type="custom", + custom_buttons = [ _("_Back"), + _("_Exit installer") ], + custom_icon="question") + if rc == 0: + return DISPATCH_BACK + elif rc == 1: + sys.exit(0) checkLinks = ( '/etc', '/var', '/var/lib', '/var/lib/rpm', '/boot', '/tmp', '/var/tmp', '/root', @@ -421,8 +293,6 @@ def upgradeMountFilesystems(anaconda): message = message + '\t' + n + '\n' anaconda.intf.messageWindow(_("Invalid Directories"), message) sys.exit(0) - - bindMountDevDirectory(anaconda.rootPath) else: if not os.access (anaconda.rootPath + "/etc/fstab", os.R_OK): anaconda.intf.messageWindow(_("Warning"), @@ -430,15 +300,11 @@ def upgradeMountFilesystems(anaconda): % (anaconda.rootPath + "/etc/fstab",), type="ok") return DISPATCH_BACK - - newfsset = readFstab(anaconda) - for entry in newfsset.entries: - anaconda.id.fsset.add(entry) + + anaconda.id.storage.fsset.parseFSTab(chroot=anaconda.rootPath) if flags.setupFilesystems: - if iutil.isPPC(): - anaconda.id.fsset.formatSwap(anaconda.rootPath, forceFormat=True) - anaconda.id.fsset.turnOnSwap(anaconda.rootPath, upgrading=True) - anaconda.id.fsset.mkDevRoot(anaconda.rootPath) + anaconda.id.storage.fsset.turnOnSwap(upgrading=True) + anaconda.id.storage.fsset.mkDevRoot(anaconda.rootPath) # if they've been booting with selinux disabled, then we should # disable it during the install as well (#242510) @@ -458,13 +324,14 @@ def setSteps(anaconda): "keyboard", "welcome", "installtype", + "storageinit", "findrootparts", "findinstall", - "partitionobjinit", "upgrademount", "upgrademigfind", "upgrademigratefs", "upgradearchitecture", + "enablefilesystems", "upgradecontinue", "reposetup", "upgbootloader", @@ -474,7 +341,6 @@ def setSteps(anaconda): "postselection", "reipl", "install", - "migratefilesystems", "preinstallconfig", "installpackages", "postinstallconfig", diff --git a/yuminstall.py b/yuminstall.py index 8f50b4a87..b91172ac8 100644 --- a/yuminstall.py +++ b/yuminstall.py @@ -322,27 +322,29 @@ class AnacondaYum(YumSorter): self._timestamp = f.readline().strip() f.close() + dev = self.anaconda.id.storage.devicetree.getDeviceByName(self.anaconda.mediaDevice) + dev.format.mountpoint = self.tree + # If self.currentMedia is None, then there shouldn't be anything # mounted. Before going further, see if the correct disc is already # in the drive. This saves a useless eject and insert if the user # has for some reason already put the disc in the drive. if self.currentMedia is None: try: - isys.mount(self.anaconda.mediaDevice, self.tree, - fstype="iso9660", readOnly=1) + dev.format.mount() if verifyMedia(self.tree, discnum, None): self.currentMedia = discnum return - isys.umount(self.tree) + dev.format.unmount() except: pass else: - unmountCD(self.tree, self.anaconda.intf.messageWindow) + unmountCD(dev, self.anaconda.intf.messageWindow) self.currentMedia = None - isys.ejectCdrom(self.anaconda.mediaDevice) + dev.eject() while True: if self.anaconda.intf: @@ -353,8 +355,7 @@ class AnacondaYum(YumSorter): discnum)) try: - isys.mount(self.anaconda.mediaDevice, self.tree, - fstype = "iso9660", readOnly = 1) + dev.format.mount() if verifyMedia(self.tree, discnum, self._timestamp): self.currentMedia = discnum @@ -363,8 +364,9 @@ class AnacondaYum(YumSorter): self.anaconda.intf.messageWindow(_("Wrong Disc"), _("That's not the correct %s disc.") % (productName,)) - isys.umount(self.tree) - isys.ejectCdrom(self.anaconda.mediaDevice) + + dev.format.unmount() + dev.eject() except: self.anaconda.intf.messageWindow(_("Error"), _("Unable to access the disc.")) @@ -431,7 +433,7 @@ class AnacondaYum(YumSorter): # install instead. images = findIsoImages(self.tree, self.anaconda.intf.messageWindow) if images != {}: - isys.umount(self.tree, removeDir=0) + isys.umount(self.tree, removeDir=False) self.anaconda.methodstr = "nfsiso:%s" % m[4:] self.configBaseURL() return @@ -444,7 +446,7 @@ class AnacondaYum(YumSorter): # we should first check to see if there's a CD/DVD with packages # on it, and then default to the mirrorlist URL. The user can # always change the repo with the repo editor later. - cdr = scanForMedia(self.tree) + cdr = scanForMedia(self.tree, self.anaconda.id.storage) if cdr: self.mediagrabber = self.mediaHandler self.anaconda.mediaDevice = cdr @@ -693,7 +695,10 @@ class AnacondaYum(YumSorter): self.currentMedia = None def urlgrabberFailureCB (self, obj, *args, **kwargs): - log.warning("Try %s/%s for %s failed" % (obj.tries, obj.retry, obj.url)) + if hasattr(obj, "exception"): + log.warning("Try %s/%s for %s failed: %s" % (obj.tries, obj.retry, obj.url, obj.exception)) + else: + log.warning("Try %s/%s for %s failed" % (obj.tries, obj.retry, obj.url)) if obj.tries == obj.retry: return @@ -757,7 +762,7 @@ class AnacondaYum(YumSorter): if len(mkeys) > 1: stage2img = "%s/images/install.img" % self.tree if self.anaconda.backend.mountInstallImage(self.anaconda, stage2img): - self.anaconda.id.fsset.unmountFilesystems(self.anaconda.rootPath) + self.anaconda.id.storage.fsset.unmountFilesystems(self.anaconda.rootPath) return DISPATCH_BACK for i in mkeys: @@ -1228,89 +1233,43 @@ reposdir=/etc/anaconda.repos.d,/tmp/updates/anaconda.repos.d,/tmp/product/anacon return None return pkgs[0] - foundkernel = False - kpkg = getBestKernelByArch("kernel", self.ayum) - - # FIXME: this is a bit of a hack. we shouldn't hard-code and - # instead check by provides. but alas. - for k in ("kernel", "kernel-smp", "kernel-PAE"): - if len(self.ayum.tsInfo.matchNaevr(name=k)) > 0: - self.selectModulePackages(anaconda, k) - foundkernel = True - - if not foundkernel and (isys.smpAvailable() or isys.htavailable()): + def selectKernel(pkgname): try: - ksmp = getBestKernelByArch("kernel-smp", self.ayum) + pkg = getBestKernelByArch(pkgname, self.ayum) except PackageSackError: - ksmp = None - log.debug("no kernel-smp package") + log.debug("no %s package" % pkgname) + return False - if ksmp and ksmp.returnSimple("arch") == kpkg.returnSimple("arch"): - foundkernel = True - log.info("selected kernel-smp package for kernel") - self.ayum.install(po=ksmp) - self.selectModulePackages(anaconda, ksmp.name) + if not pkg: + return False - if len(self.ayum.tsInfo.matchNaevr(name="gcc")) > 0: - log.debug("selecting kernel-smp-devel") - self.selectPackage("kernel-smp-devel.%s" % (kpkg.arch,)) + log.info("selected %s package for kernel" % pkg.name) + self.ayum.install(po=pkg) + self.selectModulePackages(anaconda, pkg.name) - if not foundkernel and isys.isPaeAvailable(): - try: - kpae = getBestKernelByArch("kernel-PAE", self.ayum) - except PackageSackError: - kpae = None - log.debug("no kernel-PAE package") + if len(self.ayum.tsInfo.matchNaevr(name="gcc")) > 0: + log.debug("selecting %s-devel" % pkg.name) + self.selectPackage("%s-devel.%s" % (pkg.name, pkg.arch)) - if kpae and kpae.returnSimple("arch") == kpkg.returnSimple("arch"): - foundkernel = True - log.info("select kernel-PAE package for kernel") - self.ayum.install(po=kpae) - self.selectModulePackages(anaconda, kpae.name) + return True - if len(self.ayum.tsInfo.matchNaevr(name="gcc")) > 0: - log.debug("selecting kernel-PAE-devel") - self.selectPackage("kernel-PAE-devel.%s" % (kpkg.arch,)) + foundkernel = False - if not foundkernel: - log.info("selected kernel package for kernel") - self.ayum.install(po=kpkg) - self.selectModulePackages(anaconda, kpkg.name) + if isys.smpAvailable() or isys.htavailable(): + if selectKernel("kernel-smp"): + foundkernel = True - if len(self.ayum.tsInfo.matchNaevr(name="gcc")) > 0: - log.debug("selecting kernel-devel") - self.selectPackage("kernel-devel.%s" % (kpkg.arch,)) - - def selectBootloader(self): - if iutil.isX86(): - self.selectPackage("grub") - elif iutil.isS390(): - self.selectPackage("s390utils") - elif iutil.isPPC(): - self.selectPackage("yaboot") - # XXX this needs to become grub, and we need an upgrade path... - elif iutil.isIA64(): - self.selectPackage("elilo") - - def selectFSPackages(self, fsset, diskset): - for entry in fsset.entries: - map(self.selectPackage, entry.fsystem.getNeededPackages()) - if entry.device.crypto: - self.selectPackage("cryptsetup-luks") - - for disk in diskset.disks.keys(): - if isys.driveIsIscsi(disk): - log.info("ensuring iscsi is installed") - self.selectPackage("iscsi-initiator-utils") - break + if not foundkernel and isys.isPaeAvailable(): + if selectKernel("kernel-PAE"): + foundkernel = True - if diskset.__class__.mpList: - log.info("ensuring device-mapper-multipath is installed") - self.selectPackage("device-mapper-multipath") - if diskset.__class__.dmList: - log.info("ensuring dmraid is installed") - self.selectPackage("dmraid") + if not foundkernel: + selectKernel("kernel") + def selectFSPackages(self, storage): + for device in storage.fsset.devices: + # this takes care of device and filesystem packages + map(self.selectPackage, device.packages) # anaconda requires several programs on the installed system to complete # installation, but we have no guarantees that some of these will be @@ -1333,8 +1292,8 @@ reposdir=/etc/anaconda.repos.d,/tmp/updates/anaconda.repos.d,/tmp/product/anacon if not anaconda.id.getUpgrade(): # New installs only - upgrades will already have all this stuff. self.selectBestKernel(anaconda) - self.selectBootloader() - self.selectFSPackages(anaconda.id.fsset, anaconda.id.diskset) + self.selectPackage(anaconda.platform.bootloaderPackage) + self.selectFSPackages(anaconda.id.storage) self.selectAnacondaNeeds() if anaconda.id.getUpgrade(): @@ -1365,14 +1324,16 @@ reposdir=/etc/anaconda.repos.d,/tmp/updates/anaconda.repos.d,/tmp/product/anacon (self.dlpkgs, self.totalSize, self.totalFiles) = self.ayum.getDownloadPkgs() if not anaconda.id.getUpgrade(): - usrPart = anaconda.id.partitions.getRequestByMountPoint("/usr") + usrPart = None + for fs in anaconda.id.storage.devicetree.filesystems: + if fs.mountpoint == "/usr": + usrPart = fs if usrPart is not None: largePart = usrPart else: - largePart = anaconda.id.partitions.getRequestByMountPoint("/") + largePart = anaconda.id.storage.fsset.rootDevice - if largePart and \ - largePart.getActualSize(anaconda.id.partitions, anaconda.id.diskset) < self.totalSize / 1024: + if largePart and largePart.size < self.totalSize / 1024: rc = anaconda.intf.messageWindow(_("Error"), _("Your selected packages require %d MB " "of free space for installation, but " @@ -1410,7 +1371,7 @@ reposdir=/etc/anaconda.repos.d,/tmp/updates/anaconda.repos.d,/tmp/product/anacon if anaconda.dir == DISPATCH_BACK: for d in ("/selinux", "/dev"): try: - isys.umount(anaconda.rootPath + d, removeDir = 0) + isys.umount(anaconda.rootPath + d, removeDir = False) except Exception, e: log.error("unable to unmount %s: %s" %(d, e)) return @@ -1450,12 +1411,12 @@ reposdir=/etc/anaconda.repos.d,/tmp/updates/anaconda.repos.d,/tmp/product/anacon # If there are any protected partitions we want to mount, create their # mount points now. - protected = anaconda.id.partitions.protectedPartitions() + protected = anaconda.id.storage.protectedPartitions if protected: for protectedDev in protected: - request = anaconda.id.partitions.getRequestByDeviceName(protectedDev) - if request and request.mountpoint: - dirList.append(request.mountpoint) + request = anaconda.id.storage.devicetree.getDeviceByName(protectedDev) + if request and request.format.mountpoint: + dirList.append(request.format.mountpoint) for i in dirList: try: @@ -1497,30 +1458,21 @@ reposdir=/etc/anaconda.repos.d,/tmp/updates/anaconda.repos.d,/tmp/product/anacon except Exception, e: log.error("error mounting selinuxfs: %s" %(e,)) - # we need to have a /dev during install and now that udev is - # handling /dev, it gets to be more fun. so just bind mount the - # installer /dev - log.warning("no dev package, going to bind mount /dev") - isys.mount("/dev", "%s/dev" %(anaconda.rootPath,), bindMount = 1) - if not upgrade: - anaconda.id.fsset.mkDevRoot(anaconda.rootPath) - # write out the fstab if not upgrade: - anaconda.id.fsset.write(anaconda.rootPath) + anaconda.id.storage.fsset.write(anaconda.rootPath) # rootpath mode doesn't have this file around if os.access("/etc/modprobe.d/anaconda.conf", os.R_OK): shutil.copyfile("/etc/modprobe.d/anaconda.conf", anaconda.rootPath + "/etc/modprobe.d/anaconda.conf") anaconda.id.network.write(instPath=anaconda.rootPath, anaconda=anaconda) - anaconda.id.iscsi.write(anaconda.rootPath, anaconda) - anaconda.id.zfcp.write(anaconda.rootPath) + anaconda.id.storage.write(anaconda.rootPath) if not anaconda.id.isHeadless: anaconda.id.keyboard.write(anaconda.rootPath) # make a /etc/mtab so mkinitrd can handle certain hw (usb) correctly f = open(anaconda.rootPath + "/etc/mtab", "w+") - f.write(anaconda.id.fsset.mtab()) + f.write(anaconda.id.storage.fsset.mtab()) f.close() def checkSupportedUpgrade(self, anaconda): |