summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Lehman <dlehman@redhat.com>2010-11-16 13:21:44 -0500
committerDavid Lehman <dlehman@redhat.com>2010-12-02 12:29:55 -0600
commitcb387c67eb351f97664b7f0b3150c1619fe41425 (patch)
treeac0df95601ee8b7b7520fadcd96e225613a9b3ed
parent5be1233e815c836e2232499d5e6d4f0b951b29ef (diff)
downloadanaconda-cb387c67eb351f97664b7f0b3150c1619fe41425.tar.gz
anaconda-cb387c67eb351f97664b7f0b3150c1619fe41425.tar.xz
anaconda-cb387c67eb351f97664b7f0b3150c1619fe41425.zip
Add support for installing onto block device image files.
Multiple image files can be specified via "--image=/path/to/image[:name]" on the anaconda command line. The name cannot contain colons. Whenever disk images are specified, they automatically become the only disks visible to anaconda, as if "ignoredisks --only-use" had been used. Fow now, only normal disk images are supported. Do not try to build a fwraid or mpath in image files and expect anaconda to handle it correctly. Don't log to system log for disk image installs. For one thing, it adds a huge amount of text to /var/log/messages. It also has some problem that prevents subsequent attempts to connect to the syslog socket (from anaconda_log.py, anyway) to fail. Don't allow configuration of network devices during disk image installs. Also, don't write anything in /etc/sysconfig on the host system when doing image installs. Don't start auditd when doing an image install. Don't run setupTimezone if installing to disk image file(s). We don't want to change settings on the host system. Don't start or stop iscsi, fcoe, dasd, or zfcp during image installs.
-rwxr-xr-xanaconda29
-rw-r--r--anaconda.spec.in1
-rwxr-xr-xdata/liveinst/liveinst113
-rw-r--r--pyanaconda/anaconda_log.py5
-rw-r--r--pyanaconda/flags.py1
-rw-r--r--pyanaconda/iw/congrats_gui.py3
-rw-r--r--pyanaconda/iw/network_gui.py3
-rw-r--r--pyanaconda/network.py62
-rw-r--r--pyanaconda/packages.py2
-rw-r--r--pyanaconda/rescue.py51
-rw-r--r--pyanaconda/storage/__init__.py19
-rw-r--r--pyanaconda/storage/devicelibs/loop.py6
-rw-r--r--pyanaconda/storage/devices.py5
-rw-r--r--pyanaconda/storage/devicetree.py111
-rw-r--r--pyanaconda/storage/udev.py2
-rw-r--r--scripts/Makefile.am2
-rwxr-xr-xscripts/anaconda-image-cleanup57
17 files changed, 373 insertions, 99 deletions
diff --git a/anaconda b/anaconda
index 6c37f120c..2116571b8 100755
--- a/anaconda
+++ b/anaconda
@@ -231,6 +231,7 @@ def parseOptions(argv = None):
op.add_option("--updates", dest="updateSrc", action="store", type="string")
op.add_option("--dogtail", dest="dogtail", action="store", type="string")
op.add_option("--dlabel", action="store_true", default=False)
+ op.add_option("--image", action="append", dest="images", default=[])
# Deprecated, unloved, unused
op.add_option("-r", "--rootPath", dest="unsupportedMode",
@@ -471,6 +472,12 @@ if __name__ == "__main__":
# this handles setting up updates for pypackages to minimize the set needed
setupPythonUpdates()
+ # do this early so we can set flags before initializing logging
+ (opts, args) = parseOptions()
+ from pyanaconda.flags import flags
+ if opts.images:
+ flags.imageInstall = True
+
# Set up logging as early as possible.
import logging
from pyanaconda import anaconda_log
@@ -498,8 +505,6 @@ if __name__ == "__main__":
from pyanaconda import kickstart
import pyanaconda.storage.storage_log
- from pyanaconda.flags import flags
-
# the following makes me very sad. -- katzj
# we have a slightly different set of udev rules in the second
# stage than the first stage. why this doesn't get picked up
@@ -538,7 +543,6 @@ if __name__ == "__main__":
vncS = vnc.VncServer() # The vnc Server object.
vncS.anaconda = anaconda
- (opts, args) = parseOptions()
anaconda.opts = opts
# check memory, just the text mode for now:
@@ -606,6 +610,23 @@ if __name__ == "__main__":
(path, name) = string.split(mod, ":")
anaconda.extraModules.append((path, name))
+ image_count = 0
+ for image in opts.images:
+ image_spec = image.rsplit(":", 1)
+ path = image_spec[0]
+ if len(image_spec) == 2 and image_spec[1].strip():
+ name = image_spec[1].strip()
+ else:
+ name = os.path.splitext(os.path.basename(path))[0]
+
+ if "/" in name or name in anaconda.storage.config.diskImages.keys():
+ name = "diskimg%d" % image_count
+
+ log.info("naming disk image '%s' '%s'" % (path, name))
+ anaconda.storage.config.diskImages[name] = path
+ image_count += 1
+ flags.imageInstall = True
+
if opts.vnc:
flags.usevnc = 1
anaconda.displayMode = 'g'
@@ -642,7 +663,7 @@ if __name__ == "__main__":
anaconda.xdriver = opts.xdriver
anaconda.writeXdriver(root="/")
- if not flags.livecdInstall:
+ if not flags.livecdInstall and not flags.imageInstall:
startAuditDaemon()
# setup links required for all install types
diff --git a/anaconda.spec.in b/anaconda.spec.in
index e1a7efa78..2b19f9a71 100644
--- a/anaconda.spec.in
+++ b/anaconda.spec.in
@@ -216,6 +216,7 @@ update-desktop-database &> /dev/null || :
%{_libdir}/python*/site-packages/pyanaconda/*
%{_libdir}/python*/site-packages/log_picker/*
%{_libdir}/anaconda*
+%{_bindir}/anaconda-image-cleanup
%ifarch %livearches
%{_bindir}/liveinst
%{_sbindir}/liveinst
diff --git a/data/liveinst/liveinst b/data/liveinst/liveinst
index e424a62dc..46213a8a9 100755
--- a/data/liveinst/liveinst
+++ b/data/liveinst/liveinst
@@ -18,6 +18,30 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+if [ -n "$DISPLAY" -a -n "$LANG" ]; then
+ INSTLANG="--lang $LANG"
+fi
+
+LIVE_INSTALL=0
+IMAGE_INSTALL=0
+RESCUE=0
+if [[ "$LIVECMD $*" =~ "--rescue" ]]; then
+ RESCUE=1
+fi
+
+if [ -z "$LIVECMD" ]; then
+ LIVE_INSTALL=1
+fi
+
+if [[ "$LIVECMD $*" =~ "--image" ]]; then
+ IMAGE_INSTALL=1
+fi
+
+if [[ "$LIVECMD $*" =~ "--liveinst" ]]; then
+ LIVE_INSTALL=1
+fi
+
+
if [ -z "$LIVE_BLOCK" ]; then
if [ -b "/dev/mapper/live-osimg-min" ]; then
LIVE_BLOCK="/dev/mapper/live-osimg-min"
@@ -26,16 +50,25 @@ if [ -z "$LIVE_BLOCK" ]; then
fi
fi
-if [ ! -b $LIVE_BLOCK ]; then
+if [ $LIVE_INSTALL = 1 -a ! -b $LIVE_BLOCK ]; then
zenity --error --title="Not a Live image" --text "Can't do live image installation unless running from a live image"
exit 1
fi
+# Allow running another command in the place of anaconda, but in this same
+# environment. This allows storage testing to make use of all the module
+# loading and lvm control in this file, too.
+ANACONDA=${LIVECMD:=/usr/sbin/anaconda --liveinst --method=livecd://$LIVE_BLOCK $INSTLANG}
+
# load modules that would get loaded by the loader... (#230945)
for i in raid0 raid1 raid5 raid6 raid456 raid10 dm-mod dm-zero dm-mirror dm-snapshot dm-multipath dm-round-robin vfat dm-crypt cbc sha256 lrw xts iscsi_tcp iscsi_ibft; do /sbin/modprobe $i 2>/dev/null ; done
export ANACONDA_PRODUCTNAME=$( cat /etc/system-release | sed -r -e 's/ *release.*//' )
-export ANACONDA_PRODUCTVERSION=$( cat /etc/system-release | sed -r -e 's/^.* ([0-9\.]+).*$/\1/' )
+if [ $LIVE_INSTALL = 1 ]; then
+ export ANACONDA_PRODUCTVERSION=$( cat /etc/system-release | sed -r -e 's/^.* ([0-9\.]+).*$/\1/' )
+elif [ $IMAGE_INSTALL = 1 ]; then
+ export ANACONDA_PRODUCTVERSION=$(rpmquery -q --qf '%{VERSION}' anaconda | cut -d. -f1)
+fi
export ANACONDA_BUGURL=${ANACONDA_BUGURL:="https://bugzilla.redhat.com/bugzilla/"}
RELEASE=$(rpm -q --qf '%{Release}' fedora-release)
@@ -47,15 +80,6 @@ fi
export PATH=/sbin:/usr/sbin:$PATH
-if [ -n "$DISPLAY" -a -n "$LANG" ]; then
- INSTLANG="--lang $LANG"
-fi
-
-# Allow running another command in the place of anaconda, but in this same
-# environment. This allows storage testing to make use of all the module
-# loading and lvm control in this file, too.
-ANACONDA=${LIVECMD:=/usr/sbin/anaconda --liveinst --method=livecd://$LIVE_BLOCK $INSTLANG}
-
if [ -x /usr/sbin/setenforce -a -e /selinux/enforce ]; then
current=$(cat /selinux/enforce)
/usr/sbin/setenforce 0
@@ -74,34 +98,38 @@ for opt in `cat /proc/cmdline`; do
esac
done
-# devkit-disks is now mounting lots of stuff. for now, let's just try to unmount it all
-umount /media/* 2>/dev/null
-tac /proc/mounts | grep ^/dev | grep -v live | while read dev mntpoint rest; do
- # hack - don't unmount devices the storage test code requires
- if [ "$mntpoint" = "/mnt/anactest" ]; then
- continue
- fi
-
- if [ -b $dev ]; then
- umount $mntpoint 2>/dev/null
- fi
-done
-
-/sbin/swapoff -a
-/sbin/lvm vgchange -an --ignorelockingfailure
-for i in /dev/md*; do
- if [ ! -b $i ]; then
- continue
- fi
-
- case "$i" in
- /dev/md*p*)
- ;;
- *)
- mdadm --stop $i >/dev/null 2>&1
- ;;
- esac
-done
+if [ $IMAGE_INSTALL = 0 ]; then
+ # devkit-disks is now mounting lots of stuff. for now, let's just try to
+ # unmount it all
+ umount /media/* 2>/dev/null
+ tac /proc/mounts | grep ^/dev | grep -v live | \
+ while read dev mntpoint rest; do
+ # hack - don't unmount devices the storage test code requires
+ if [ "$mntpoint" = "/mnt/anactest" ]; then
+ continue
+ fi
+
+ if [ -b $dev ]; then
+ umount $mntpoint 2>/dev/null
+ fi
+ done
+
+ /sbin/swapoff -a
+ /sbin/lvm vgchange -an --ignorelockingfailure
+ for i in /dev/md*; do
+ if [ ! -b $i ]; then
+ continue
+ fi
+
+ case "$i" in
+ /dev/md*p*)
+ ;;
+ *)
+ mdadm --stop $i >/dev/null 2>&1
+ ;;
+ esac
+ done
+fi
/sbin/udevadm control --env=ANACONDA=1
@@ -113,6 +141,13 @@ else
$ANACONDA $*
fi
+# try to teardown the filesystems if this was an image install
+if [ $IMAGE_INSTALL = 1 -a $RESCUE = 0 ]; then
+ anaconda-image-cleanup
+fi
+
+rm -f /dev/.in_sysinit 2>/dev/null
+
if [ -n "$current" ]; then
/usr/sbin/setenforce $current
fi
diff --git a/pyanaconda/anaconda_log.py b/pyanaconda/anaconda_log.py
index 2f48630c7..c298b4ee0 100644
--- a/pyanaconda/anaconda_log.py
+++ b/pyanaconda/anaconda_log.py
@@ -30,6 +30,7 @@ from logging.handlers import SysLogHandler, SYSLOG_UDP_PORT
import types
import iutil
+from flags import flags
DEFAULT_TTY_LEVEL = logging.INFO
ENTRY_FORMAT = "%(asctime)s,%(msecs)03d %(levelname)s %(name)s: %(message)s"
@@ -129,6 +130,10 @@ class AnacondaLog:
def forwardToSyslog(self, logger):
"""Forward everything that goes in the logger to the syslog daemon.
"""
+ if flags.imageInstall:
+ # don't clutter up the system logs when doing an image install
+ return
+
syslogHandler = AnacondaSyslogHandler(
'/dev/log',
ANACONDA_SYSLOG_FACILITY,
diff --git a/pyanaconda/flags.py b/pyanaconda/flags.py
index 79a5e6612..68a53b12e 100644
--- a/pyanaconda/flags.py
+++ b/pyanaconda/flags.py
@@ -93,6 +93,7 @@ class Flags:
self.__dict__['flags']['sshd'] = 0
self.__dict__['flags']['preexisting_x11'] = False
self.__dict__['flags']['noverifyssl'] = False
+ self.__dict__['flags']['imageInstall'] = False
# for non-physical consoles like some ppc and sgi altix,
# we need to preserve the console device and not try to
# do things like bogl on them. this preserves what that
diff --git a/pyanaconda/iw/congrats_gui.py b/pyanaconda/iw/congrats_gui.py
index c80f2f8d3..e5a3af361 100644
--- a/pyanaconda/iw/congrats_gui.py
+++ b/pyanaconda/iw/congrats_gui.py
@@ -22,6 +22,7 @@ import gtk
from pyanaconda import gui
from iw_gui import *
from pyanaconda.constants import *
+from pyanaconda.flags import flags
import os
from pyanaconda import platform
@@ -45,7 +46,7 @@ class CongratulationWindow (InstallWindow):
# this mucks around a bit, but it's the weird case and it's
# better than adding a lot of complication to the normal
ics.cw.mainxml.get_widget("nextButton").hide()
- if os.path.exists(os.environ.get("LIVE_BLOCK", "/dev/mapper/live-osimg-min")):
+ if flags.livecdInstall or flags.imageInstall:
ics.cw.mainxml.get_widget("closeButton").show()
ics.cw.mainxml.get_widget("closeButton").grab_focus()
else:
diff --git a/pyanaconda/iw/network_gui.py b/pyanaconda/iw/network_gui.py
index 9f7c40d1d..4f1ec17f6 100644
--- a/pyanaconda/iw/network_gui.py
+++ b/pyanaconda/iw/network_gui.py
@@ -26,6 +26,7 @@ from iw_gui import *
from pyanaconda import gui
from pyanaconda import network
from pyanaconda import iutil
+from pyanaconda.flags import flags
import gobject
import subprocess
import gtk
@@ -50,7 +51,7 @@ class NetworkWindow(InstallWindow):
self.netconfButton = self.xml.get_widget("netconfButton")
self.netconfButton.connect("clicked", self._setupNetwork)
- if len(self.anaconda.network.netdevices) == 0:
+ if len(self.anaconda.network.netdevices) == 0 or flags.imageInstall:
self.netconfButton.set_sensitive(False)
# pressing Enter in confirm == clicking Next
diff --git a/pyanaconda/network.py b/pyanaconda/network.py
index 8b567d535..9cad75957 100644
--- a/pyanaconda/network.py
+++ b/pyanaconda/network.py
@@ -356,12 +356,14 @@ class Network:
self.setNMControlledDevices(self.netdevices.keys())
def update(self):
-
ifcfglog.debug("Network.update() called")
self.netdevices = {}
self.ksdevice = None
+ if flags.imageInstall:
+ return
+
# populate self.netdevices
devhash = isys.getDeviceProperties(dev=None)
for iface in devhash.keys():
@@ -643,6 +645,14 @@ class Network:
return True
def copyConfigToPath(self, instPath=''):
+ if flags.imageInstall and instPath:
+ # for image installs we only want to write out
+ # /etc/sysconfig/network
+ destfile = os.path.normpath(instPath + networkConfFile)
+ if not os.path.isdir(os.path.dirname(destfile)):
+ iutil.mkdirChain(os.path.dirname(destfile))
+ shutil.move("/tmp/sysconfig-network", destfile)
+ return
# /etc/sysconfig/network-scripts/ifcfg-DEVICE
# /etc/sysconfig/network-scripts/keys-DEVICE
@@ -682,29 +692,14 @@ class Network:
device.path)
def write(self):
-
ifcfglog.debug("Network.write() called")
- devices = self.netdevices.values()
-
- # /etc/sysconfig/network-scripts/ifcfg-*
- # /etc/sysconfig/network-scripts/keys-*
- for dev in devices:
-
- bootproto = dev.get('BOOTPROTO').lower()
- # write out the hostname as DHCP_HOSTNAME if given (#81613)
- if (bootproto == 'dhcp' and self.hostname and
- self.overrideDHCPhostname):
- dev.set(('DHCP_HOSTNAME', self.hostname))
-
- dev.writeIfcfgFile()
-
- if dev.wepkey:
- dev.writeWepkeyFile(dir=netscriptsDir, overwrite=False)
-
-
# /etc/sysconfig/network
- newnetwork = "%s.new" % (networkConfFile)
+ if flags.imageInstall:
+ # don't write files into host's /etc/sysconfig on image installs
+ newnetwork = "/tmp/sysconfig-network"
+ else:
+ newnetwork = "%s.new" % (networkConfFile)
f = open(newnetwork, "w")
f.write("NETWORKING=yes\n")
@@ -723,7 +718,30 @@ class Network:
f.write("IPV6_DEFAULTGW=%s\n" % self.ipv6_defaultgw)
f.close()
- shutil.move(newnetwork, networkConfFile)
+ if flags.imageInstall:
+ # for image installs, all we want to write out is the contents of
+ # /etc/sysconfig/network
+ ifcfglog.debug("not writing per-device configs for image install")
+ return
+ else:
+ shutil.move(newnetwork, networkConfFile)
+
+ devices = self.netdevices.values()
+
+ # /etc/sysconfig/network-scripts/ifcfg-*
+ # /etc/sysconfig/network-scripts/keys-*
+ for dev in devices:
+
+ bootproto = dev.get('BOOTPROTO').lower()
+ # write out the hostname as DHCP_HOSTNAME if given (#81613)
+ if (bootproto == 'dhcp' and self.hostname and
+ self.overrideDHCPhostname):
+ dev.set(('DHCP_HOSTNAME', self.hostname))
+
+ dev.writeIfcfgFile()
+
+ if dev.wepkey:
+ dev.writeWepkeyFile(dir=netscriptsDir, overwrite=False)
# /etc/resolv.conf is managed by NM
diff --git a/pyanaconda/packages.py b/pyanaconda/packages.py
index eb01f2352..064ba1762 100644
--- a/pyanaconda/packages.py
+++ b/pyanaconda/packages.py
@@ -157,7 +157,7 @@ def turnOnFilesystems(anaconda):
def setupTimezone(anaconda):
# we don't need this on an upgrade or going backwards
- if anaconda.upgrade or anaconda.dir == DISPATCH_BACK:
+ if anaconda.upgrade or flags.imageInstall or anaconda.dir == DISPATCH_BACK:
return
os.environ["TZ"] = anaconda.timezone.tz
diff --git a/pyanaconda/rescue.py b/pyanaconda/rescue.py
index 9a6b9f8e8..aea3c94c6 100644
--- a/pyanaconda/rescue.py
+++ b/pyanaconda/rescue.py
@@ -169,6 +169,9 @@ def makeFStab(instPath = ""):
# make sure they have a resolv.conf in the chroot
def makeResolvConf(instPath):
+ if flags.imageInstall:
+ return
+
if not os.access("/etc/resolv.conf", os.R_OK):
return
@@ -218,8 +221,13 @@ def runShell(screen = None, msg=""):
print
if msg:
print (msg)
- print(_("When finished please exit from the shell and your "
- "system will reboot."))
+
+ if flags.imageInstall:
+ print(_("Run anaconda-image-cleanup to unmount the system "
+ "when you are finished."))
+ else:
+ print(_("When finished please exit from the shell and your "
+ "system will reboot."))
print
proc = None
@@ -367,6 +375,13 @@ def runRescue(anaconda):
allowDirty = 1, warnDirty = 1,
readOnly = readOnly)
+ if not flags.imageInstall:
+ msg = _("The system will reboot automatically when you exit "
+ "from the shell.")
+ else:
+ msg = _("Run anaconda-image-cleanup to unmount the system "
+ "when you are finished.")
+
if rc == -1:
if anaconda.ksdata:
log.error("System had dirty file systems which you chose not to mount")
@@ -374,9 +389,8 @@ def runRescue(anaconda):
ButtonChoiceWindow(screen, _("Rescue"),
_("Your system had dirty file systems which you chose not "
"to mount. Press return to get a shell from which "
- "you can fsck and mount your partitions. The system "
- "will reboot automatically when you exit from the "
- "shell."), [_("OK")], width = 50)
+ "you can fsck and mount your partitions. %s") % msg,
+ [_("OK")], width = 50)
rootmounted = 0
else:
if anaconda.ksdata:
@@ -386,9 +400,9 @@ def runRescue(anaconda):
_("Your system has been mounted under %(rootPath)s.\n\n"
"Press <return> to get a shell. If you would like to "
"make your system the root environment, run the command:\n\n"
- "\tchroot %(rootPath)s\n\nThe system will reboot "
- "automatically when you exit from the shell.") %
- {'rootPath': anaconda.rootPath},
+ "\tchroot %(rootPath)s\n\n%(msg)s") %
+ {'rootPath': anaconda.rootPath,
+ 'msg': msg},
[_("OK")] )
rootmounted = 1
@@ -416,7 +430,7 @@ def runRescue(anaconda):
log.warning("cannot touch /.autorelabel")
# set a library path to use mounted fs
- libdirs = os.environ["LD_LIBRARY_PATH"].split(":")
+ libdirs = os.environ.get("LD_LIBRARY_PATH", "").split(":")
mounted = map(lambda dir: "/mnt/sysimage%s" % dir, libdirs)
os.environ["LD_LIBRARY_PATH"] = ":".join(libdirs + mounted)
@@ -467,11 +481,18 @@ def runRescue(anaconda):
if anaconda.ksdata:
log.error("An error occurred trying to mount some or all of your system")
else:
+ if not flags.imageInstall:
+ msg = _("The system will reboot automatically when you "
+ "exit from the shell.")
+ else:
+ msg = _("Run anaconda-image-cleanup to unmount the system "
+ "when you are finished.")
+
ButtonChoiceWindow(screen, _("Rescue"),
_("An error occurred trying to mount some or all of your "
"system. Some of it may be mounted under %s.\n\n"
- "Press <return> to get a shell. The system will reboot "
- "automatically when you exit from the shell.") % (anaconda.rootPath,),
+ "Press <return> to get a shell. %s")
+ % (anaconda.rootPath, msg),
[_("OK")] )
else:
if anaconda.ksdata and \
@@ -481,10 +502,14 @@ def runRescue(anaconda):
print(_("You don't have any Linux partitions. Rebooting.\n"))
sys.exit(0)
else:
+ if not flags.imageInstall:
+ msg = _(" The system will reboot automatically when you exit "
+ "from the shell.")
+ else:
+ msg = ""
ButtonChoiceWindow(screen, _("Rescue Mode"),
_("You don't have any Linux partitions. Press "
- "return to get a shell. The system will reboot "
- "automatically when you exit from the shell."),
+ "return to get a shell.%s") % msg,
[ _("OK") ], width = 50)
msgStr = ""
diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py
index 29f6cc68d..ae700c8c2 100644
--- a/pyanaconda/storage/__init__.py
+++ b/pyanaconda/storage/__init__.py
@@ -268,6 +268,7 @@ class StorageDiscoveryConfig(object):
self.reinitializeDisks = False
self.zeroMbr = None
self.protectedDevSpecs = []
+ self.diskImages = {}
def writeKS(self, f):
# clearpart
@@ -382,9 +383,10 @@ class Storage(object):
except Exception as e:
log.error("failure tearing down device tree: %s" % e)
- self.zfcp.shutdown()
+ if not flags.imageInstall:
+ self.zfcp.shutdown()
- # TODO: iscsi.shutdown()
+ # TODO: iscsi.shutdown()
def reset(self):
""" Reset storage configuration to reflect actual system state.
@@ -401,12 +403,13 @@ class Storage(object):
w = self.anaconda.intf.waitWindow(_("Examining Devices"),
_("Examining storage devices"))
- self.iscsi.startup(self.anaconda.intf)
- self.fcoe.startup(self.anaconda.intf)
- self.zfcp.startup(self.anaconda.intf)
- self.dasd.startup(self.anaconda.intf,
- self.config.exclusiveDisks,
- self.config.zeroMbr)
+ if not flags.imageInstall:
+ self.iscsi.startup(self.anaconda.intf)
+ self.fcoe.startup(self.anaconda.intf)
+ self.zfcp.startup(self.anaconda.intf)
+ self.dasd.startup(self.anaconda.intf,
+ self.config.exclusiveDisks,
+ self.config.zeroMbr)
clearPartType = self.config.clearPartType # save this before overriding it
if self.anaconda.upgrade:
self.config.clearPartType = CLEARPART_TYPE_NONE
diff --git a/pyanaconda/storage/devicelibs/loop.py b/pyanaconda/storage/devicelibs/loop.py
index 298e613b6..e0dc4f7c3 100644
--- a/pyanaconda/storage/devicelibs/loop.py
+++ b/pyanaconda/storage/devicelibs/loop.py
@@ -46,7 +46,7 @@ def losetup(args, capture=False):
stderr="/dev/tty5",
**exec_kwargs)
except RuntimeError as e:
- raise LoopError(e.message)
+ raise LoopError(str(e))
return ret
@@ -80,7 +80,7 @@ def loop_setup(path):
try:
msg = losetup(args)
except LoopError as e:
- msg = e.message
+ msg = str(e)
if msg:
raise LoopError("failed to set up loop for %s: %s" % (path, msg))
@@ -91,7 +91,7 @@ def loop_teardown(path):
try:
msg = losetup(args)
except LoopError as e:
- msg = e.message
+ msg = str(e)
if msg:
raise DeviceError("failed to tear down loop %s: %s" % (path, msg))
diff --git a/pyanaconda/storage/devices.py b/pyanaconda/storage/devices.py
index dc4d93051..85bfa1e07 100644
--- a/pyanaconda/storage/devices.py
+++ b/pyanaconda/storage/devices.py
@@ -1686,7 +1686,7 @@ class DMLinearDevice(DMDevice):
# information about it
self._size = self.currentSize
- def deactivate(self):
+ def deactivate(self, recursive=False):
if not self.exists:
raise DeviceError("device has not been created", self.name)
@@ -1702,6 +1702,9 @@ class DMLinearDevice(DMDevice):
dm.dm_remove(self.name)
udev_settle()
+ if recursive:
+ self.teardownParents(recursive=recursive)
+
def teardown(self, recursive=None):
""" Close, or tear down, a device. """
log_method_call(self, self.name, status=self.status)
diff --git a/pyanaconda/storage/devicetree.py b/pyanaconda/storage/devicetree.py
index 81cbc96a1..2981d35b3 100644
--- a/pyanaconda/storage/devicetree.py
+++ b/pyanaconda/storage/devicetree.py
@@ -24,6 +24,7 @@ import os
import stat
import block
import re
+import shutil
from errors import *
from devices import *
@@ -35,6 +36,7 @@ import devicelibs.mdraid
import devicelibs.dm
import devicelibs.lvm
import devicelibs.mpath
+import devicelibs.loop
from udev import *
from .storage_log import log_method_call
from pyanaconda import iutil
@@ -170,6 +172,11 @@ class DeviceTree(object):
self.iscsi = iscsi
self.dasd = dasd
+ # disk image files are automatically exclusive
+ self.diskImages = getattr(conf, "diskImages", {})
+ if self.diskImages:
+ self.exclusiveDisks = self.diskImages.keys()
+
# protected device specs as provided by the user
self.protectedDevSpecs = getattr(conf, "protectedDevSpecs", [])
@@ -527,6 +534,11 @@ class DeviceTree(object):
self.exclusiveDisks[i] = name
return False
+ # never ignore mapped disk images. if you don't want to use them,
+ # don't specify them in the first place
+ if udev_device_is_dm_anaconda(info):
+ return False
+
# We want exclusiveDisks to operate on anything that could be
# considered a directly usable disk, ie: fwraid array, mpath, or disk.
#
@@ -548,9 +560,14 @@ class DeviceTree(object):
# 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"):
+ if name.startswith("ram"):
return True
+ if name.startswith("loop"):
+ # ignore loop devices unless they're backed by a disk image file
+ backing_device = devicelibs.loop.get_device_path(name)
+ return (backing_device not in self.diskImages.values())
+
# FIXME: check for virtual devices whose slaves are on the ignore list
def addUdevDMDevice(self, info):
@@ -1521,11 +1538,99 @@ class DeviceTree(object):
return ret
+ def setupDiskImages(self):
+ for (name, path) in self.diskImages.items():
+ log.info("setting up disk image file '%s' as '%s'" % (path, name))
+ try:
+ filedev = FileDevice(path, exists=True)
+ filedev.setup()
+ log.debug("%s" % filedev)
+
+ loop_name = devicelibs.loop.get_loop_name(filedev.path)
+ loop_sysfs = None
+ if loop_name:
+ loop_sysfs = "/class/block/%s" % loop_name
+ loopdev = LoopDevice(name=loop_name,
+ parents=[filedev],
+ sysfsPath=loop_sysfs,
+ exists=True)
+ loopdev.setup()
+ log.debug("%s" % loopdev)
+ dmdev = DMLinearDevice(name,
+ parents=[loopdev],
+ exists=True)
+ dmdev.setup()
+ dmdev.updateSysfsPath()
+ log.debug("%s" % dmdev)
+ except (ValueError, DeviceError) as e:
+ log.error("failed to set up disk image: %s" % e)
+ else:
+ self._addDevice(filedev)
+ self._addDevice(loopdev)
+ self._addDevice(dmdev)
+ info = udev_get_block_device(dmdev.sysfsPath)
+ self.addUdevDevice(info)
+
+ def backupConfigs(self, restore=False):
+ """ Create a backup copies of some storage config files. """
+ configs = ["/etc/mdadm.conf", "/etc/multipath.conf"]
+ for cfg in configs:
+ if restore:
+ src = cfg + ".anacbak"
+ dst = cfg
+ func = os.rename
+ op = "restore from backup"
+ else:
+ src = cfg
+ dst = cfg + ".anacbak"
+ func = shutil.copy2
+ op = "create backup copy"
+
+ if os.access(dst, os.W_OK):
+ try:
+ os.unlink(dst)
+ except OSError as e:
+ msg = str(e)
+ log.info("failed to remove %s: %s" % (dst, msg))
+
+ if os.access(src, os.W_OK):
+ # copy the config to a backup with extension ".anacbak"
+ try:
+ func(src, dst)
+ except (IOError, OSError) as e:
+ msg = str(e)
+ log.error("failed to %s of %s: %s" % (op, cfg, msg))
+ elif restore:
+ # remove the config since we created it
+ log.info("removing anaconda-created %s" % cfg)
+ try:
+ os.unlink(cfg)
+ except OSError as e:
+ msg = str(e)
+ log.error("failed to remove %s: %s" % (cfg, msg))
+ else:
+ # don't try to backup non-existent configs
+ log.info("not going to %s of non-existent %s" % (op, cfg))
+
+ def restoreConfigs(self):
+ self.backupConfigs(restore=True)
+
def populate(self):
""" Locate all storage devices. """
+ self.backupConfigs()
+ try:
+ self._populate()
+ except Exception:
+ raise
+ finally:
+ self.restoreConfigs()
+
+ def _populate(self):
log.debug("DeviceTree.populate: ignoredDisks is %s ; exclusiveDisks is %s"
% (self._ignoredDisks, self.exclusiveDisks))
+ self.setupDiskImages()
+
# mark the tree as unpopulated so exception handlers can tell the
# exception originated while finding storage devices
self.populated = False
@@ -1622,10 +1727,6 @@ class DeviceTree(object):
self._handleInconsistencies()
self.teardownAll()
- try:
- os.unlink("/etc/mdadm.conf")
- except OSError:
- log.info("failed to unlink /etc/mdadm.conf")
def teardownAll(self):
""" Run teardown methods on all devices. """
diff --git a/pyanaconda/storage/udev.py b/pyanaconda/storage/udev.py
index 1f4c3d572..9dfe37c5e 100644
--- a/pyanaconda/storage/udev.py
+++ b/pyanaconda/storage/udev.py
@@ -107,7 +107,7 @@ def udev_get_block_devices():
def __is_blacklisted_blockdev(dev_name):
"""Is this a blockdev we never want for an install?"""
- if dev_name.startswith("loop") or dev_name.startswith("ram") or dev_name.startswith("fd"):
+ if dev_name.startswith("ram") or dev_name.startswith("fd"):
return True
if os.path.exists("/sys/class/block/%s/device/model" %(dev_name,)):
diff --git a/scripts/Makefile.am b/scripts/Makefile.am
index 187d4c66a..839567dfb 100644
--- a/scripts/Makefile.am
+++ b/scripts/Makefile.am
@@ -28,6 +28,8 @@ dist_noinst_SCRIPTS = getlangnames.py upd-bootimage upd-initrd upd-kernel \
analogdir = $(libexecdir)/$(PACKAGE_NAME)
dist_analog_SCRIPTS = analog
+dist_bin_SCRIPTS = anaconda-image-cleanup
+
stage2scriptsdir = $(datadir)/$(PACKAGE_NAME)
dist_stage2scripts_SCRIPTS = restart-anaconda
diff --git a/scripts/anaconda-image-cleanup b/scripts/anaconda-image-cleanup
new file mode 100755
index 000000000..00f2c82a9
--- /dev/null
+++ b/scripts/anaconda-image-cleanup
@@ -0,0 +1,57 @@
+#!/usr/bin/python
+import os
+import sys
+
+# set the imageInstall flag so the logger won't log to the syslog
+from pyanaconda.flags import flags
+flags.imageInstall = True
+
+import pyanaconda.anaconda_log
+pyanaconda.anaconda_log.init()
+
+from pyanaconda import iutil
+
+from pyanaconda.cmdline import InstallInterface
+from pyanaconda.storage import StorageDiscoveryConfig
+from pyanaconda.storage.devicetree import DeviceTree
+from pyanaconda.storage import devicelibs
+
+intf = InstallInterface()
+storage_config = StorageDiscoveryConfig()
+
+# unmount filesystems
+for mounted in reversed(open("/proc/mounts").readlines()):
+ (device, mountpoint, rest) = mounted.split(" ", 2)
+ if not mountpoint.startswith("/mnt/sysimage"):
+ continue
+ os.system("umount %s" % mountpoint)
+
+# tear down the devices representing the disk images
+sys_class_block = "/sys/class/block"
+for dev in os.listdir(sys_class_block):
+ if not dev.startswith("dm-"):
+ continue
+
+ name = open("%s/%s/dm/name" % (sys_class_block, dev)).read().strip()
+ uuid = open("%s/%s/dm/uuid" % (sys_class_block, dev)).read().strip()
+ if not name or not uuid.startswith("ANACONDA-"):
+ continue
+
+ loop = os.listdir("%s/%s/slaves" % (sys_class_block, dev))[0].strip()
+ path = devicelibs.loop.get_device_path(loop)
+ storage_config.diskImages[name] = path
+
+if not storage_config.diskImages:
+ sys.exit(1)
+
+os.system("udevadm control --env=ANACONDA=1")
+os.system("udevadm trigger --subsystem-match block")
+os.system("udevadm settle")
+devicetree = DeviceTree(intf=intf, conf=storage_config)
+devicetree.populate()
+devicetree.teardownAll()
+for name in devicetree.diskImages.keys():
+ device = devicetree.getDeviceByName(name)
+ device.deactivate(recursive=True)
+os.system("udevadm control --env=ANACONDA=0")
+