[PATCH 2/2] Move code related to finding existing installations to anaconda

Vratislav Podzimek vpodzime at redhat.com
Fri Jan 23 14:14:49 UTC 2015


Only anaconda uses this code that previously lived in blivet and it is
installer-specific. Thus it should live in anaconda's codebase not blivet's.

Signed-off-by: Vratislav Podzimek <vpodzime at redhat.com>
---
 anaconda                                |   4 +-
 pyanaconda/rescue.py                    |   6 +-
 pyanaconda/storage_utils.py             | 218 ++++++++++++++++++++++++++++++++
 pyanaconda/ui/gui/spokes/custom.py      |   2 +-
 pyanaconda/ui/gui/spokes/lib/refresh.py |   5 +-
 pyanaconda/ui/gui/spokes/storage.py     |   2 +
 pyanaconda/ui/tui/spokes/storage.py     |   3 +-
 7 files changed, 231 insertions(+), 9 deletions(-)

diff --git a/anaconda b/anaconda
index cd28667..72a86e6 100755
--- a/anaconda
+++ b/anaconda
@@ -1289,7 +1289,7 @@ if __name__ == "__main__":
     signal.signal(signal.SIGUSR2, lambda signum, frame: anaconda.dumpState())
     atexit.register(exitHandler, ksdata.reboot, anaconda.storage)
 
-    from blivet import storageInitialize
+    from pyanaconda.storage_utils import storageInitializeAndPopulate
     from pyanaconda.packaging import payloadMgr
     from pyanaconda.timezone import time_initialize
 
@@ -1299,7 +1299,7 @@ if __name__ == "__main__":
         cleanPStore()
 
     if not flags.dirInstall:
-        threadMgr.add(AnacondaThread(name=constants.THREAD_STORAGE, target=storageInitialize,
+        threadMgr.add(AnacondaThread(name=constants.THREAD_STORAGE, target=storageInitializeAndPopulate,
                                      args=(anaconda.storage, ksdata, anaconda.protected)))
         threadMgr.add(AnacondaThread(name=constants.THREAD_TIME_INIT, target=time_initialize,
                                      args=(ksdata.timezone, anaconda.storage, anaconda.bootloader)))
diff --git a/pyanaconda/rescue.py b/pyanaconda/rescue.py
index 6702e2b..ed17a18 100644
--- a/pyanaconda/rescue.py
+++ b/pyanaconda/rescue.py
@@ -35,6 +35,7 @@ from pyanaconda.flags import flags
 from pyanaconda.installinterfacebase import InstallInterfaceBase
 from pyanaconda.i18n import _
 from pyanaconda.kickstart import runPostScripts
+from pyanaconda.storage_utils import storageInitializeAndPopulate
 
 from blivet import mountExistingSystem
 from blivet.errors import StorageError
@@ -252,6 +253,7 @@ def _unlock_devices(intf, storage):
 
 def doRescue(intf, rescue_mount, ksdata):
     import blivet
+    from pyanaconda.storage_utils import findExistingInstallations
 
     # XXX: hook the exception handler wrapper that turns off snack first
     orig_hook = sys.excepthook
@@ -305,9 +307,9 @@ def doRescue(intf, rescue_mount, ksdata):
             break
 
     sto = blivet.Blivet(ksdata=ksdata)
-    blivet.storageInitialize(sto, ksdata, [])
+    storageInitializeAndPopulate(sto, ksdata, [])
     _unlock_devices(intf, sto)
-    roots = blivet.findExistingInstallations(sto.devicetree)
+    roots = findExistingInstallations(sto.devicetree)
 
     if not roots:
         root = None
diff --git a/pyanaconda/storage_utils.py b/pyanaconda/storage_utils.py
index d0906d3..aeabaa5 100644
--- a/pyanaconda/storage_utils.py
+++ b/pyanaconda/storage_utils.py
@@ -22,11 +22,15 @@
 
 import re
 import locale
+import os
+import shlex
 
 from contextlib import contextmanager
 
 from blivet import arch
 from blivet import util
+from blivet import storageInitialize
+from blivet import Root, BlkidTab, CryptTab
 from blivet.size import Size
 from blivet.platform import platform as _platform
 from blivet.devicefactory import DEVICE_TYPE_LVM
@@ -35,10 +39,12 @@ from blivet.devicefactory import DEVICE_TYPE_BTRFS
 from blivet.devicefactory import DEVICE_TYPE_MD
 from blivet.devicefactory import DEVICE_TYPE_PARTITION
 from blivet.devicefactory import DEVICE_TYPE_DISK
+from blivet.storage_log import log_exception_info
 
 from pyanaconda.i18n import _, N_
 from pyanaconda import isys
 from pyanaconda.constants import productName
+from pyanaconda.iutil import getTargetPhysicalRoot, getSysroot
 
 from pykickstart.constants import AUTOPART_TYPE_PLAIN, AUTOPART_TYPE_BTRFS
 from pykickstart.constants import AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP
@@ -380,3 +386,215 @@ def bound_size(size, device, old_size):
             size = min_size
 
     return size
+
+def releaseFromRedhatRelease(fn):
+    """
+    Attempt to identify the installation of a Linux distribution via
+    /etc/redhat-release.  This file must already have been verified to exist
+    and be readable.
+
+    :param fn: an open filehandle on /etc/redhat-release
+    :type fn: filehandle
+    :returns: The distribution's name and version, or None for either or both
+    if they cannot be determined
+    :rtype: (string, string)
+    """
+    relName = None
+    relVer = None
+
+    with open(fn) 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 releaseFromOsRelease(fn):
+    """
+    Attempt to identify the installation of a Linux distribution via
+    /etc/os-release.  This file must already have been verified to exist
+    and be readable.
+
+    :param fn: an open filehandle on /etc/os-release
+    :type fn: filehandle
+    :returns: The distribution's name and version, or None for either or both
+    if they cannot be determined
+    :rtype: (string, string)
+    """
+    relName = None
+    relVer = None
+
+    with open(fn, "r") as f:
+        parser = shlex.shlex(f)
+
+        while True:
+            key = parser.get_token()
+            if key == parser.eof:
+                break
+            elif key == "NAME":
+                # Throw away the "=".
+                parser.get_token()
+                relName = parser.get_token().strip("'\"")
+            elif key == "VERSION_ID":
+                # Throw away the "=".
+                parser.get_token()
+                relVer = parser.get_token().strip("'\"")
+
+    return (relName, relVer)
+
+def getReleaseString():
+    """
+    Attempt to identify the installation of a Linux distribution by checking
+    a previously mounted filesystem for several files.  The filesystem must
+    be mounted under the target physical root.
+
+    :returns: The machine's arch, distribution name, and distribution version
+    or None for any parts that cannot be determined
+    :rtype: (string, string, string)
+    """
+    relName = None
+    relVer = None
+
+    try:
+        relArch = util.capture_output(["arch"], root=getSysroot()).strip()
+    except OSError:
+        relArch = None
+
+    filename = "%s/etc/redhat-release" % getSysroot()
+    if os.access(filename, os.R_OK):
+        (relName, relVer) = releaseFromRedhatRelease(filename)
+    else:
+        filename = "%s/etc/os-release" % getSysroot()
+        if os.access(filename, os.R_OK):
+            (relName, relVer) = releaseFromOsRelease(filename)
+
+    return (relArch, relName, relVer)
+
+def parseFSTab(devicetree, chroot=None):
+    """ parse /etc/fstab and return a tuple of a mount dict and swap list """
+    if not chroot or not os.path.isdir(chroot):
+        chroot = getSysroot()
+
+    mounts = {}
+    swaps = []
+    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 (mounts, swaps)
+
+    blkidTab = BlkidTab(chroot=chroot)
+    try:
+        blkidTab.parse()
+        log.debug("blkid.tab devs: %s", list(blkidTab.devices.keys()))
+    except Exception: # pylint: disable=broad-except
+        log_exception_info(log.info, "error parsing blkid.tab")
+        blkidTab = None
+
+    cryptTab = CryptTab(devicetree, blkidTab=blkidTab, chroot=chroot)
+    try:
+        cryptTab.parse(chroot=chroot)
+        log.debug("crypttab maps: %s", list(cryptTab.mappings.keys()))
+    except Exception: # pylint: disable=broad-except
+        log_exception_info(log.info, "error parsing crypttab")
+        cryptTab = None
+
+    with open(path) as f:
+        log.debug("parsing %s", path)
+        for line in f.readlines():
+
+            (line, _pound, _comment) = line.partition("#")
+            fields = line.split(None, 4)
+
+            if len(fields) < 5:
+                continue
+
+            (devspec, mountpoint, fstype, options, _rest) = fields
+
+            # find device in the tree
+            device = devicetree.resolveDevice(devspec,
+                                              cryptTab=cryptTab,
+                                              blkidTab=blkidTab,
+                                              options=options)
+
+            if device is None:
+                continue
+
+            if fstype != "swap":
+                mounts[mountpoint] = device
+            else:
+                swaps.append(device)
+
+    return (mounts, swaps)
+
+def findExistingInstallations(devicetree):
+    if not os.path.exists(getTargetPhysicalRoot()):
+        util.makedirs(getTargetPhysicalRoot())
+
+    roots = []
+    for device in devicetree.leaves:
+        if not device.format.linuxNative or not device.format.mountable or \
+           not device.controllable:
+            continue
+
+        try:
+            device.setup()
+        except Exception: # pylint: disable=broad-except
+            log_exception_info(log.warning, "setup of %s failed", [device.name])
+            continue
+
+        options = device.format.options + ",ro"
+        try:
+            device.format.mount(options=options, mountpoint=getSysroot())
+        except Exception: # pylint: disable=broad-except
+            log_exception_info(log.warning, "mount of %s as %s failed", [device.name, device.format.type])
+            device.teardown()
+            continue
+
+        if not os.access(getSysroot() + "/etc/fstab", os.R_OK):
+            device.teardown(recursive=True)
+            continue
+
+        try:
+            (architecture, product, version) = getReleaseString()
+        except ValueError:
+            name = _("Linux on %s") % device.name
+        else:
+            # I'd like to make this finer grained, but it'd be very difficult
+            # to translate.
+            if not product or not version or not architecture:
+                name = _("Unknown Linux")
+            elif "linux" in product.lower():
+                name = _("%(product)s %(version)s for %(arch)s") % \
+                        {"product": product, "version": version, "arch": architecture}
+            else:
+                name = _("%(product)s Linux %(version)s for %(arch)s") % \
+                        {"product": product, "version": version, "arch": architecture}
+
+        (mounts, swaps) = parseFSTab(devicetree, chroot=getSysroot())
+        device.teardown()
+        if not mounts and not swaps:
+            # empty /etc/fstab. weird, but I've seen it happen.
+            continue
+        roots.append(Root(mounts=mounts, swaps=swaps, name=name))
+
+    return roots
+
+def populate_storage_roots(storage):
+    try:
+        storage.roots = findExistingInstallations(storage.devicetree)
+    except Exception: # pylint: disable=broad-except
+        log_exception_info(log.info, "failure detecting existing installations")
+
+def storageInitializeAndPopulate(storage, ksdata, protected):
+    storageInitialize(storage, ksdata, protected)
+    populate_storage_roots(storage)
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py
index 29528b8..58cea8d 100644
--- a/pyanaconda/ui/gui/spokes/custom.py
+++ b/pyanaconda/ui/gui/spokes/custom.py
@@ -54,7 +54,6 @@ from blivet.devicefactory import DEVICE_TYPE_MD
 from blivet.devicefactory import DEVICE_TYPE_DISK
 from blivet.devicefactory import DEVICE_TYPE_LVM_THINP
 from blivet.devicefactory import SIZE_POLICY_AUTO
-from blivet import findExistingInstallations
 from pyanaconda.autopart import doAutoPartition
 from blivet.errors import StorageError
 from blivet.errors import NoDisksError
@@ -67,6 +66,7 @@ from pyanaconda.storage_utils import DEVICE_TEXT_PARTITION, DEVICE_TEXT_MAP, DEV
 from pyanaconda.storage_utils import PARTITION_ONLY_FORMAT_TYPES, MOUNTPOINT_DESCRIPTIONS
 from pyanaconda.storage_utils import NAMED_DEVICE_TYPES, CONTAINER_DEVICE_TYPES
 from pyanaconda.storage_utils import SanityError, SanityWarning, LUKSDeviceWithoutKeyError
+from pyanaconda.storage_utils import findExistingInstallations
 from pyanaconda import storage_utils
 
 from pyanaconda.ui.communication import hubQ
diff --git a/pyanaconda/ui/gui/spokes/lib/refresh.py b/pyanaconda/ui/gui/spokes/lib/refresh.py
index 396c38f..6daee45 100644
--- a/pyanaconda/ui/gui/spokes/lib/refresh.py
+++ b/pyanaconda/ui/gui/spokes/lib/refresh.py
@@ -24,8 +24,7 @@ from gi.repository import GLib
 from pyanaconda.threads import threadMgr, AnacondaThread
 from pyanaconda.ui.gui import GUIObject
 from pyanaconda import constants
-
-from blivet import storageInitialize
+from pyanaconda.storage_utils import storageInitializeAndPopulate
 
 __all__ = ["RefreshDialog"]
 
@@ -89,7 +88,7 @@ class RefreshDialog(GUIObject):
         self._notebook.set_current_page(1)
 
         # And now to fire up the storage reinitialization.
-        threadMgr.add(AnacondaThread(name=constants.THREAD_STORAGE, target=storageInitialize,
+        threadMgr.add(AnacondaThread(name=constants.THREAD_STORAGE, target=storageInitializeAndPopulate,
                                      args=(self.storage, self.data, self.storage.devicetree.protectedDevNames)))
 
         self._elapsed = 0
diff --git a/pyanaconda/ui/gui/spokes/storage.py b/pyanaconda/ui/gui/spokes/storage.py
index d19a20b..e394bf4 100644
--- a/pyanaconda/ui/gui/spokes/storage.py
+++ b/pyanaconda/ui/gui/spokes/storage.py
@@ -68,6 +68,7 @@ from pyanaconda.i18n import _, C_, CN_, P_
 from pyanaconda import constants, iutil, isys
 from pyanaconda.bootloader import BootLoaderError
 from pyanaconda.autopart import swap_suggestion
+from pyanaconda.storage_utils import populate_storage_roots
 
 from pykickstart.constants import CLEARPART_TYPE_NONE, AUTOPART_TYPE_LVM
 from pykickstart.errors import KickstartValueError
@@ -333,6 +334,7 @@ class StorageSpoke(NormalSpoke, StorageChecker):
             self.data.ignoredisk.onlyuse = []
             self.storage.config.update(self.data)
             self.storage.reset()
+            populate_storage_roots(self.storage)
             self.disks = getDisks(self.storage.devicetree)
             # now set ksdata back to the user's specified config
             applyDiskSelection(self.storage, self.data, self.selected_disks)
diff --git a/pyanaconda/ui/tui/spokes/storage.py b/pyanaconda/ui/tui/spokes/storage.py
index 1c7b600..36c3562 100644
--- a/pyanaconda/ui/tui/spokes/storage.py
+++ b/pyanaconda/ui/tui/spokes/storage.py
@@ -27,7 +27,7 @@ from pyanaconda.ui.categories.system import SystemCategory
 from pyanaconda.ui.tui.spokes import NormalTUISpoke
 from pyanaconda.ui.tui.simpleline import TextWidget, CheckboxWidget
 from pyanaconda.ui.tui.tuiobject import YesNoDialog
-from pyanaconda.storage_utils import AUTOPART_CHOICES, sanity_check, SanityError, SanityWarning
+from pyanaconda.storage_utils import AUTOPART_CHOICES, sanity_check, SanityError, SanityWarning, populate_storage_roots
 
 from blivet import arch
 from blivet.size import Size
@@ -378,6 +378,7 @@ class StorageSpoke(NormalTUISpoke):
             self.storage.config.update(self.data)
             self.storage.autoPartType = self.data.autopart.type
             self.storage.reset()
+            populate_storage_roots(self.storage)
             # now set ksdata back to the user's specified config
             applyDiskSelection(self.storage, self.data, self.selected_disks)
         except BootLoaderError as e:
-- 
2.1.0



More information about the anaconda-patches mailing list