Blob Blame History Raw
From 13acd1e7439fe6e587b0f01843755a493a83c88e Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@verbum.org>
Date: Thu, 27 Jul 2017 10:43:24 -0400
Subject: [PATCH] rpmostreepayload: Rollup backport of Fedora patches

This rolls up all of <https://github.com/rhinstaller/anaconda/pull/1129> which
is basically mount handling, plus the pull-local optimization for kickstarts.

We need to change the implementation for the mount setup, since
things work differently in RHEL7 vs Fedora since commit
664ef7b43f9102aa9332d0db5b7d13f8ece436f0.

The code that changed in `pyanaconda/install.py` only applied to ostree-based
payloads (where we introduced the physical vs sysroot distinction), so should be
safe for backporting.

Resolves: rhbz#1459623
---
 pyanaconda/install.py                    |  17 +---
 pyanaconda/packaging/rpmostreepayload.py | 129 ++++++++++++++++++++++++++-----
 2 files changed, 111 insertions(+), 35 deletions(-)

diff --git a/pyanaconda/install.py b/pyanaconda/install.py
index 26e1b2680..a711813af 100644
--- a/pyanaconda/install.py
+++ b/pyanaconda/install.py
@@ -253,24 +253,9 @@ def doInstall(storage, payload, ksdata, instClass):
         if iutil.getSysroot() != iutil.getTargetPhysicalRoot():
             blivet.setSysroot(iutil.getTargetPhysicalRoot(),
                               iutil.getSysroot())
-
-            # Now that we have the FS layout in the target, umount
-            # things that were in the legacy sysroot, and put them in
-            # the target root, except for the physical /.  First,
-            # unmount all target filesystems.
-            storage.umountFilesystems()
-
-            # Explicitly mount the root on the physical sysroot
-            rootmnt = storage.mountpoints.get('/')
-            rootmnt.setup()
-            rootmnt.format.setup(options=rootmnt.format.options, chroot=iutil.getTargetPhysicalRoot())
-
+            # Note this changed for RHEL 7.5; see comments in rpmostreepayload.py.
             payload.prepareMountTargets(storage)
 
-            # Everything else goes in the target root, including /boot
-            # since the bootloader code will expect to find /boot
-            # inside the chroot.
-            storage.mountFilesystems(skipRoot=True)
         storage.write()
 
     # Do bootloader.
diff --git a/pyanaconda/packaging/rpmostreepayload.py b/pyanaconda/packaging/rpmostreepayload.py
index da7650d3b..a472fa0bc 100644
--- a/pyanaconda/packaging/rpmostreepayload.py
+++ b/pyanaconda/packaging/rpmostreepayload.py
@@ -48,7 +48,7 @@ class RPMOSTreePayload(ArchivePayload):
         super(RPMOSTreePayload, self).__init__(data)
 
         self._base_remote_args = None
-        self._binds = []
+        self._internal_mounts = []
         self._sysroot_path = None
 
     @property
@@ -166,8 +166,21 @@ class RPMOSTreePayload(ArchivePayload):
         progress = OSTree.AsyncProgress.new()
         progress.connect('changed', self._pullProgressCb)
 
+        pull_opts = {'refs': GLib.Variant('as', [ostreesetup.ref])}
+        # If we're doing a kickstart, we can at least use the content as a reference:
+        # See <https://github.com/rhinstaller/anaconda/issues/1117>
+        # The first path here is used by <https://pagure.io/fedora-lorax-templates>
+        # and the second by <https://github.com/projectatomic/rpm-ostree-toolbox/>
+        if OSTree.check_version(2017, 8):
+            for path in ['/ostree/repo', '/install/ostree/repo']:
+                if os.path.isdir(path + '/objects'):
+                    pull_opts['localcache-repos'] = GLib.Variant('as', [path])
+                    break
+
         try:
-            repo.pull(ostreesetup.remote, [ostreesetup.ref], 0, progress, cancellable)
+            repo.pull_with_options(ostreesetup.remote,
+                                   GLib.Variant('a{sv}', pull_opts),
+                                   progress, cancellable)
         except GLib.GError as e:
             exn = PayloadInstallError("Failed to pull from repository: %s" % e)
             log.error(str(exn))
@@ -176,6 +189,7 @@ class RPMOSTreePayload(ArchivePayload):
                 iutil.ipmi_abort(scripts=self.data.scripts)
                 sys.exit(1)
 
+        log.info("ostree pull: " + (progress.get_status() or ""))
         progressQ.send_message(_("Preparing deployment of %s") % (ostreesetup.ref, ))
 
         self._safeExecWithRedirect("ostree",
@@ -213,25 +227,102 @@ class RPMOSTreePayload(ArchivePayload):
 
         mainctx.pop_thread_default()
 
+    def _setupInternalBindmount(self, src, dest=None,
+                                src_physical=True,
+                                bind_ro=False,
+                                recurse=True):
+        """Internal API for setting up bind mounts between the physical root and
+           sysroot, also ensures we track them in self._internal_mounts so we can
+           cleanly unmount them.
+
+           :param src: Source path, will be prefixed with physical or sysroot
+           :param dest: Destination, will be prefixed with sysroot (defaults to same as src)
+           :param src_physical: Prefix src with physical root
+           :param bind_ro: Make mount read-only
+           :param recurse: Use --rbind to recurse, otherwise plain --bind
+        """
+        # Default to the same basename
+        if dest is None:
+            dest = src
+        # Almost all of our mounts go from physical to sysroot
+        if src_physical:
+            src = iutil.getTargetPhysicalRoot() + src
+        else:
+            src = iutil.getSysroot() + src
+        # Canonicalize dest to the full path
+        dest = iutil.getSysroot() + dest
+        if bind_ro:
+            self._safeExecWithRedirect("mount",
+                                       ["--bind", src, src])
+            self._safeExecWithRedirect("mount",
+                                       ["--bind", "-o", "remount,ro", src, src])
+        else:
+            # Recurse for non-ro binds so we pick up sub-mounts
+            # like /sys/firmware/efi/efivars.
+            if recurse:
+                bindopt = '--rbind'
+            else:
+                bindopt = '--bind'
+            self._safeExecWithRedirect("mount",
+                                       [bindopt, src, dest])
+        self._internal_mounts.append(src if bind_ro else dest)
+
     def prepareMountTargets(self, storage):
         ostreesetup = self.data.ostreesetup
 
-        varroot = iutil.getTargetPhysicalRoot() + '/ostree/deploy/' + ostreesetup.osname + '/var'
-
-        # Set up bind mounts as if we've booted the target system, so
-        # that %post script work inside the target.
-        self._binds = [(iutil.getTargetPhysicalRoot(), iutil.getSysroot() + '/sysroot'),
-                       (iutil.getSysroot() + '/usr', None)]
-        # https://github.com/ostreedev/ostree/issues/855
+        # NOTE: This is different from Fedora. There, since since
+        # 664ef7b43f9102aa9332d0db5b7d13f8ece436f0 blivet now only sets up
+        # mounts in the physical root, and we set up bind mounts. But in RHEL7
+        # we tear down and set up the mounts in the sysroot, so this code
+        # doesn't need to do as much.
+
+        # Now that we have the FS layout in the target, umount
+        # things that were in the physical sysroot, and put them in
+        # the target root, *except* for the physical /.
+        storage.umountFilesystems()
+
+        # Explicitly mount the root on the physical sysroot, since that's
+        # how ostree works.
+        rootmnt = storage.mountpoints.get('/')
+        rootmnt.setup()
+        rootmnt.format.setup(options=rootmnt.format.options, chroot=iutil.getTargetPhysicalRoot())
+
+        # Everything else goes in the target root, including /boot
+        # since the bootloader code will expect to find /boot
+        # inside the chroot.
+        storage.mountFilesystems(skipRoot=True)
+
+        # We're done with blivet mounts; now set up binds as ostree does it at
+        # runtime.  We start with /usr being read-only.
+        self._setupInternalBindmount('/usr', bind_ro=True, src_physical=False)
+
+        # Handle /var; if the admin didn't specify a mount for /var, we need to
+        # do the default ostree one. See also
+        # <https://github.com/ostreedev/ostree/issues/855>.
+        varroot = '/ostree/deploy/' + ostreesetup.osname + '/var'
         if storage.mountpoints.get("/var") is None:
-            self._binds.append((varroot, iutil.getSysroot() + '/var'))
-
-        for (src, dest) in self._binds:
-            self._safeExecWithRedirect("mount",
-                                       ["--bind", src, dest if dest else src])
-            if dest is None:
-                self._safeExecWithRedirect("mount",
-                                           ["--bind", "-o", "ro", src, src])
+            self._setupInternalBindmount(varroot, dest='/var', recurse=False)
+
+        self._setupInternalBindmount("/", dest="/sysroot", recurse=False)
+
+        # Now that we have /var, start filling in any directories that may be
+        # required later there. We explicitly make /var/lib, since
+        # systemd-tmpfiles doesn't have a --prefix-only=/var/lib. We rely on
+        # 80-setfilecons.ks to set the label correctly.
+        iutil.mkdirChain(iutil.getSysroot() + '/var/lib')
+        # Next, run tmpfiles to make subdirectories of /var. We need this for
+        # both mounts like /home (really /var/home) and %post scripts might
+        # want to write to e.g. `/srv`, `/root`, `/usr/local`, etc. The
+        # /var/lib/rpm symlink is also critical for having e.g. `rpm -qa` work
+        # in %post. We don't iterate *all* tmpfiles because we don't have the
+        # matching NSS configuration inside Anaconda, and we can't "chroot" to
+        # get it because that would require mounting the API filesystems in the
+        # target.
+        for varsubdir in ('home', 'roothome', 'lib/rpm', 'opt', 'srv',
+                          'usrlocal', 'mnt', 'media', 'spool', 'spool/mail'):
+            self._safeExecWithRedirect("systemd-tmpfiles",
+                                       ["--create", "--boot", "--root=" + iutil.getSysroot(),
+                                        "--prefix=/var/" + varsubdir])
 
     def recreateInitrds(self, force=False):
         # For rpmostree payloads, we're replicating an initramfs from
@@ -312,6 +403,6 @@ class RPMOSTreePayload(ArchivePayload):
         # on inside either blivet (or systemd?) that's causing mounts inside
         # /mnt/sysimage/ostree/deploy/$x/sysroot/ostree/deploy
         # which is not what we want.
-        for (src, dest) in reversed(self._binds):
+        for path in reversed(self._internal_mounts):
             # Also intentionally ignore errors here
-            iutil.execWithRedirect("umount", ['-R', dest if dest else src])
+            iutil.execWithRedirect("umount", ['-R', path])
-- 
2.13.5