[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[PATCH 07/16] 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.
---
 anaconda                              |   29 +++++++-
 anaconda.spec.in                      |    1 +
 data/liveinst/liveinst                |  113 +++++++++++++++++++++-----------
 pyanaconda/anaconda_log.py            |    5 ++
 pyanaconda/flags.py                   |    1 +
 pyanaconda/iw/congrats_gui.py         |    3 +-
 pyanaconda/iw/network_gui.py          |    3 +-
 pyanaconda/network.py                 |   62 ++++++++++++-------
 pyanaconda/packages.py                |    2 +-
 pyanaconda/rescue.py                  |   51 +++++++++++----
 pyanaconda/storage/__init__.py        |   19 +++---
 pyanaconda/storage/devicelibs/loop.py |    6 +-
 pyanaconda/storage/devices.py         |    5 +-
 pyanaconda/storage/devicetree.py      |  111 +++++++++++++++++++++++++++++++--
 pyanaconda/storage/udev.py            |    2 +-
 scripts/Makefile.am                   |    2 +
 scripts/anaconda-image-cleanup        |   57 +++++++++++++++++
 17 files changed, 373 insertions(+), 99 deletions(-)
 create mode 100755 scripts/anaconda-image-cleanup

diff --git a/anaconda b/anaconda
index 6c37f12..2116571 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 e1a7efa..2b19f9a 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 e424a62..46213a8 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 2f48630..c298b4e 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 79a5e66..68a53b1 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 c80f2f8..e5a3af3 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 9f7c40d..4f1ec17 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 8b567d5..9cad759 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 eb01f23..064ba17 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 9a6b9f8..aea3c94 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 29f6cc6..ae700c8 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 298e613..e0dc4f7 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 dc4d930..85bfa1e 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 81cbc96..2981d35 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 1f4c3d5..9dfe37c 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 187d4c6..839567d 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 0000000..00f2c82
--- /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")
+
-- 
1.7.3.2


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]