Blame SOURCES/sos-bz1873185-estimate-only-option.patch

003633
From 5b245b1e449c6a05d09034bcb8290bffded79327 Mon Sep 17 00:00:00 2001
003633
From: Pavel Moravec <pmoravec@redhat.com>
003633
Date: Wed, 8 Sep 2021 17:04:58 +0200
003633
Subject: [PATCH] [report] Implement --estimate-only
003633
003633
Add report option --estimate-only to estimate disk space requirements
003633
when running a sos report.
003633
003633
Resolves: #2673
003633
003633
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
003633
---
003633
 man/en/sos-report.1    | 13 +++++++-
003633
 sos/report/__init__.py | 74 ++++++++++++++++++++++++++++++++++++++++--
003633
 2 files changed, 84 insertions(+), 3 deletions(-)
003633
003633
diff --git a/man/en/sos-report.1 b/man/en/sos-report.1
003633
index 36b337df..e8efc8f8 100644
003633
--- a/man/en/sos-report.1
003633
+++ b/man/en/sos-report.1
003633
@@ -14,7 +14,7 @@ sos report \- Collect and package diagnostic and support data
003633
           [--preset preset] [--add-preset add_preset]\fR
003633
           [--del-preset del_preset] [--desc description]\fR
003633
           [--batch] [--build] [--debug] [--dry-run]\fR
003633
-          [--label label] [--case-id id]\fR
003633
+          [--estimate-only] [--label label] [--case-id id]\fR
003633
           [--threads threads]\fR
003633
           [--plugin-timeout TIMEOUT]\fR
003633
           [--cmd-timeout TIMEOUT]\fR
003633
@@ -317,6 +317,17 @@ output, or string data from the system. The resulting logs may be used
003633
 to understand the actions that sos would have taken without the dry run
003633
 option.
003633
 .TP
003633
+.B \--estimate-only
003633
+Estimate disk space requirements when running sos report. This can be valuable
003633
+to prevent sosreport working dir to consume all free disk space. No plugin data
003633
+is available at the end.
003633
+
003633
+Plugins will be collected sequentially, size of collected files and commands outputs
003633
+will be calculated and the plugin files will be immediatelly deleted prior execution
003633
+of the next plugin. This still can consume whole free disk space, though. Please note,
003633
+size estimations may not be accurate for highly utilized systems due to changes between
003633
+an estimate and a real execution.
003633
+.TP
003633
 .B \--upload
003633
 If specified, attempt to upload the resulting archive to a vendor defined location.
003633
 
003633
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
003633
index 82484f1d..b033f621 100644
003633
--- a/sos/report/__init__.py
003633
+++ b/sos/report/__init__.py
003633
@@ -86,6 +86,7 @@ class SoSReport(SoSComponent):
003633
         'desc': '',
003633
         'domains': [],
003633
         'dry_run': False,
003633
+        'estimate_only': False,
003633
         'experimental': False,
003633
         'enable_plugins': [],
003633
         'keywords': [],
003633
@@ -137,6 +138,7 @@ class SoSReport(SoSComponent):
003633
         self._args = args
003633
         self.sysroot = "/"
003633
         self.preset = None
003633
+        self.estimated_plugsizes = {}
003633
 
003633
         self.print_header()
003633
         self._set_debug()
003633
@@ -223,6 +225,11 @@ class SoSReport(SoSComponent):
003633
                                 help="Description for a new preset",)
003633
         report_grp.add_argument("--dry-run", action="store_true",
003633
                                 help="Run plugins but do not collect data")
003633
+        report_grp.add_argument("--estimate-only", action="store_true",
003633
+                                help="Approximate disk space requirements for "
003633
+                                     "a real sos run; disables --clean and "
003633
+                                     "--collect, sets --threads=1 and "
003633
+                                     "--no-postproc")
003633
         report_grp.add_argument("--experimental", action="store_true",
003633
                                 dest="experimental", default=False,
003633
                                 help="enable experimental plugins")
003633
@@ -700,6 +700,33 @@ class SoSReport(SoSComponent):
003633
                 self.all_options.append((plugin, plugin_name, optname,
003633
                                          optparm))
003633
 
003633
+    def _set_estimate_only(self):
003633
+        # set estimate-only mode by enforcing some options settings
003633
+        # and return a corresponding log messages string
003633
+        msg = "\nEstimate-only mode enabled"
003633
+        ext_msg = []
003633
+        if self.opts.threads > 1:
003633
+            ext_msg += ["--threads=%s overriden to 1" % self.opts.threads, ]
003633
+            self.opts.threads = 1
003633
+        if not self.opts.build:
003633
+            ext_msg += ["--build enabled", ]
003633
+            self.opts.build = True
003633
+        if not self.opts.no_postproc:
003633
+            ext_msg += ["--no-postproc enabled", ]
003633
+            self.opts.no_postproc = True
003633
+        if self.opts.clean:
003633
+            ext_msg += ["--clean disabled", ]
003633
+            self.opts.clean = False
003633
+        if self.opts.upload:
003633
+            ext_msg += ["--upload* options disabled", ]
003633
+            self.opts.upload = False
003633
+        if ext_msg:
003633
+            msg += ", which overrides some options:\n  " + "\n  ".join(ext_msg)
003633
+        else:
003633
+            msg += "."
003633
+        msg += "\n\n"
003633
+        return msg
003633
+
003633
     def _report_profiles_and_plugins(self):
003633
         self.ui_log.info("")
003633
         if len(self.loaded_plugins):
003633
@@ -875,10 +909,12 @@ class SoSReport(SoSComponent):
003633
         return True
003633
 
003633
     def batch(self):
003633
+        msg = self.policy.get_msg()
003633
+        if self.opts.estimate_only:
003633
+            msg += self._set_estimate_only()
003633
         if self.opts.batch:
003633
-            self.ui_log.info(self.policy.get_msg())
003633
+            self.ui_log.info(msg)
003633
         else:
003633
-            msg = self.policy.get_msg()
003633
             msg += _("Press ENTER to continue, or CTRL-C to quit.\n")
003633
             try:
003633
                 input(msg)
003633
@@ -1011,6 +1047,22 @@ class SoSReport(SoSComponent):
003633
                 self.running_plugs.remove(plugin[1])
003633
                 self.loaded_plugins[plugin[0]-1][1].set_timeout_hit()
003633
                 pool._threads.clear()
003633
+        if self.opts.estimate_only:
003633
+            from pathlib import Path
003633
+            tmpdir_path = Path(self.archive.get_tmp_dir())
003633
+            self.estimated_plugsizes[plugin[1]] = sum(
003633
+                    [f.stat().st_size for f in tmpdir_path.glob('**/*')
003633
+                     if (os.path.isfile(f) and not os.path.islink(f))])
003633
+            # remove whole tmp_dir content - including "sos_commands" and
003633
+            # similar dirs that will be re-created on demand by next plugin
003633
+            # if needed; it is less error-prone approach than skipping
003633
+            # deletion of some dirs but deleting their content
003633
+            for f in os.listdir(self.archive.get_tmp_dir()):
003633
+                f = os.path.join(self.archive.get_tmp_dir(), f)
003633
+                if os.path.isdir(f):
003633
+                    rmtree(f)
003633
+                else:
003633
+                    os.unlink(f)
003633
         return True
003633
 
003633
     def collect_plugin(self, plugin):
003633
@@ -1330,6 +1382,24 @@ class SoSReport(SoSComponent):
003633
             self.policy.display_results(archive, directory, checksum,
003633
                                         map_file=map_file)
003633
 
003633
+        if self.opts.estimate_only:
003633
+            from sos.utilities import get_human_readable
003633
+            _sum = get_human_readable(sum(self.estimated_plugsizes.values()))
003633
+            self.ui_log.info("Estimated disk space requirement for whole "
003633
+                             "uncompressed sos report directory: %s" % _sum)
003633
+            bigplugins = sorted(self.estimated_plugsizes.items(),
003633
+                                key=lambda x: x[1], reverse=True)[:3]
003633
+            bp_out = ",  ".join("%s: %s" %
003633
+                                (p, get_human_readable(v, precision=0))
003633
+                                for p, v in bigplugins)
003633
+            self.ui_log.info("Three biggest plugins:  %s" % bp_out)
003633
+            self.ui_log.info("")
003633
+            self.ui_log.info("Please note the estimation is relevant to the "
003633
+                             "current options.")
003633
+            self.ui_log.info("Be aware that the real disk space requirements "
003633
+                             "might be different.")
003633
+            self.ui_log.info("")
003633
+
003633
         if self.opts.upload or self.opts.upload_url:
003633
             if not self.opts.build:
003633
                 try:
003633
-- 
003633
2.31.1
003633
003633
From 7ae47e6c0717c0b56c3368008dd99a87f7f436d5 Mon Sep 17 00:00:00 2001
003633
From: Pavel Moravec <pmoravec@redhat.com>
003633
Date: Wed, 13 Oct 2021 20:21:16 +0200
003633
Subject: [PATCH] [report] Count with sos_logs and sos_reports in
003633
 --estimate-only
003633
003633
Currently, we estimate just plugins' disk space and ignore sos_logs
003633
or sos_reports directories - although they can occupy nontrivial disk
003633
space as well.
003633
003633
Resolves: #2723
003633
003633
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
003633
---
003633
 sos/report/__init__.py | 8 ++++++++
003633
 1 file changed, 8 insertions(+)
003633
003633
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
003633
index e35c7e8d..7feb31ee 100644
003633
--- a/sos/report/__init__.py
003633
+++ b/sos/report/__init__.py
003633
@@ -1380,6 +1380,14 @@ class SoSReport(SoSComponent):
003633
 
003633
         if self.opts.estimate_only:
003633
             from sos.utilities import get_human_readable
003633
+            from pathlib import Path
003633
+            # add sos_logs, sos_reports dirs, etc., basically everything
003633
+            # that remained in self.tmpdir after plugins' contents removal
003633
+            # that still will be moved to the sos report final directory path
003633
+            tmpdir_path = Path(self.tmpdir)
003633
+            self.estimated_plugsizes['sos_logs_reports'] = sum(
003633
+                    [f.stat().st_size for f in tmpdir_path.glob('**/*')])
003633
+
003633
             _sum = get_human_readable(sum(self.estimated_plugsizes.values()))
003633
             self.ui_log.info("Estimated disk space requirement for whole "
003633
                              "uncompressed sos report directory: %s" % _sum)
003633
-- 
003633
2.31.1
003633
003633
From 4293f3317505661e8f32ba94ad87310996fa1626 Mon Sep 17 00:00:00 2001
003633
From: Eric Desrochers <eric.desrochers@canonical.com>
003633
Date: Tue, 19 Oct 2021 12:18:40 -0400
003633
Subject: [PATCH] [report] check for symlink before rmtree when opt
003633
 estimate-only is use
003633
003633
Check if the dir is also symlink before performing rmtree()
003633
method so that unlink() method can be used instead.
003633
003633
Traceback (most recent call last):
003633
  File "./bin/sos", line 22, in <module>
003633
    sos.execute()
003633
  File "/tmp/sos/sos/__init__.py", line 186, in execute
003633
    self._component.execute()
003633
OSError: Cannot call rmtree on a symbolic link
003633
003633
Closes: #2727
003633
003633
Signed-off-by: Eric Desrochers <eric.desrochers@canonical.com>
003633
---
003633
 sos/report/__init__.py | 2 +-
003633
 1 file changed, 1 insertion(+), 1 deletion(-)
003633
003633
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
003633
index 7feb31ee..1b5bc97d 100644
003633
--- a/sos/report/__init__.py
003633
+++ b/sos/report/__init__.py
003633
@@ -1059,7 +1059,7 @@ class SoSReport(SoSComponent):
003633
             # deletion of some dirs but deleting their content
003633
             for f in os.listdir(self.archive.get_tmp_dir()):
003633
                 f = os.path.join(self.archive.get_tmp_dir(), f)
003633
-                if os.path.isdir(f):
003633
+                if os.path.isdir(f) and not os.path.islink(f):
003633
                     rmtree(f)
003633
                 else:
003633
                     os.unlink(f)
003633
-- 
003633
2.31.1
003633
003633
From 589d47c93257b55bc796ef6ac25b88c974ee3d72 Mon Sep 17 00:00:00 2001
003633
From: Pavel Moravec <pmoravec@redhat.com>
003633
Date: Mon, 8 Nov 2021 16:38:24 +0100
003633
Subject: [PATCH] [report] Calculate sizes of dirs, symlinks and manifest in
003633
 estimate mode
003633
003633
Enhance --estimate-mode to calculate sizes of also:
003633
- symlinks
003633
- directories themselves
003633
- manifest.json file
003633
003633
Use os.lstat() method instead of os.stat() to properly calculate the
003633
sizes (and not destinations of symlinks, e.g.).
003633
003633
Print five biggest plugins instead of three as sos logs and reports do
003633
stand as one "plugin" in the list, often.
003633
003633
Resolves: #2752
003633
003633
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
003633
---
003633
 sos/report/__init__.py | 56 +++++++++++++++++++++---------------------
003633
 1 file changed, 28 insertions(+), 28 deletions(-)
003633
003633
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
003633
index 10952566..a4c92acc 100644
003633
--- a/sos/report/__init__.py
003633
+++ b/sos/report/__init__.py
003633
@@ -1050,8 +1050,7 @@ class SoSReport(SoSComponent):
003633
             from pathlib import Path
003633
             tmpdir_path = Path(self.archive.get_tmp_dir())
003633
             self.estimated_plugsizes[plugin[1]] = sum(
003633
-                    [f.stat().st_size for f in tmpdir_path.glob('**/*')
003633
-                     if (os.path.isfile(f) and not os.path.islink(f))])
003633
+                    [f.lstat().st_size for f in tmpdir_path.glob('**/*')])
003633
             # remove whole tmp_dir content - including "sos_commands" and
003633
             # similar dirs that will be re-created on demand by next plugin
003633
             # if needed; it is less error-prone approach than skipping
003633
@@ -1273,6 +1272,33 @@ class SoSReport(SoSComponent):
003633
                 short_name='manifest.json'
003633
             )
003633
 
003633
+        # print results in estimate mode (to include also just added manifest)
003633
+        if self.opts.estimate_only:
003633
+            from sos.utilities import get_human_readable
003633
+            from pathlib import Path
003633
+            # add sos_logs, sos_reports dirs, etc., basically everything
003633
+            # that remained in self.tmpdir after plugins' contents removal
003633
+            # that still will be moved to the sos report final directory path
003633
+            tmpdir_path = Path(self.tmpdir)
003633
+            self.estimated_plugsizes['sos_logs_reports'] = sum(
003633
+                    [f.lstat().st_size for f in tmpdir_path.glob('**/*')])
003633
+
003633
+            _sum = get_human_readable(sum(self.estimated_plugsizes.values()))
003633
+            self.ui_log.info("Estimated disk space requirement for whole "
003633
+                             "uncompressed sos report directory: %s" % _sum)
003633
+            bigplugins = sorted(self.estimated_plugsizes.items(),
003633
+                                key=lambda x: x[1], reverse=True)[:5]
003633
+            bp_out = ",  ".join("%s: %s" %
003633
+                                (p, get_human_readable(v, precision=0))
003633
+                                for p, v in bigplugins)
003633
+            self.ui_log.info("Five biggest plugins:  %s" % bp_out)
003633
+            self.ui_log.info("")
003633
+            self.ui_log.info("Please note the estimation is relevant to the "
003633
+                             "current options.")
003633
+            self.ui_log.info("Be aware that the real disk space requirements "
003633
+                             "might be different.")
003633
+            self.ui_log.info("")
003633
+
003633
         # package up and compress the results
003633
         if not self.opts.build:
003633
             old_umask = os.umask(0o077)
003633
@@ -1377,32 +1403,6 @@ class SoSReport(SoSComponent):
003633
             self.policy.display_results(archive, directory, checksum,
003633
                                         map_file=map_file)
003633
 
003633
-        if self.opts.estimate_only:
003633
-            from sos.utilities import get_human_readable
003633
-            from pathlib import Path
003633
-            # add sos_logs, sos_reports dirs, etc., basically everything
003633
-            # that remained in self.tmpdir after plugins' contents removal
003633
-            # that still will be moved to the sos report final directory path
003633
-            tmpdir_path = Path(self.tmpdir)
003633
-            self.estimated_plugsizes['sos_logs_reports'] = sum(
003633
-                    [f.stat().st_size for f in tmpdir_path.glob('**/*')])
003633
-
003633
-            _sum = get_human_readable(sum(self.estimated_plugsizes.values()))
003633
-            self.ui_log.info("Estimated disk space requirement for whole "
003633
-                             "uncompressed sos report directory: %s" % _sum)
003633
-            bigplugins = sorted(self.estimated_plugsizes.items(),
003633
-                                key=lambda x: x[1], reverse=True)[:3]
003633
-            bp_out = ",  ".join("%s: %s" %
003633
-                                (p, get_human_readable(v, precision=0))
003633
-                                for p, v in bigplugins)
003633
-            self.ui_log.info("Three biggest plugins:  %s" % bp_out)
003633
-            self.ui_log.info("")
003633
-            self.ui_log.info("Please note the estimation is relevant to the "
003633
-                             "current options.")
003633
-            self.ui_log.info("Be aware that the real disk space requirements "
003633
-                             "might be different.")
003633
-            self.ui_log.info("")
003633
-
003633
         if self.opts.upload or self.opts.upload_url:
003633
             if not self.opts.build:
003633
                 try:
003633
-- 
003633
2.31.1
003633
003633
From c6a5bbb8d75aadd5c7f76d3f469929aba2cf8060 Mon Sep 17 00:00:00 2001
003633
From: Pavel Moravec <pmoravec@redhat.com>
003633
Date: Wed, 5 Jan 2022 10:33:58 +0100
003633
Subject: [PATCH] [report] Provide better warning about estimate-mode
003633
003633
As --estimate-only calculates disk usage based on `stat` data that
003633
differs from outputs of other commands like `du`, enhance the warning
003633
about reliability of the calculated estimation.
003633
003633
Also add a rule-of-thumb recommendation of real disk space requirements.
003633
003633
Resolves: #2815
003633
003633
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
003633
---
003633
 man/en/sos-report.1    | 10 +++++++---
003633
 sos/report/__init__.py |  3 ++-
003633
 2 files changed, 9 insertions(+), 4 deletions(-)
003633
003633
diff --git a/man/en/sos-report.1 b/man/en/sos-report.1
003633
index 464a77e54..e34773986 100644
003633
--- a/man/en/sos-report.1
003633
+++ b/man/en/sos-report.1
003633
@@ -343,9 +343,13 @@ is available at the end.
003633
 
003633
 Plugins will be collected sequentially, size of collected files and commands outputs
003633
 will be calculated and the plugin files will be immediatelly deleted prior execution
003633
-of the next plugin. This still can consume whole free disk space, though. Please note,
003633
-size estimations may not be accurate for highly utilized systems due to changes between
003633
-an estimate and a real execution.
003633
+of the next plugin. This still can consume whole free disk space, though.
003633
+
003633
+Please note, size estimations may not be accurate for highly utilized systems due to
003633
+changes between an estimate and a real execution. Also some difference between
003633
+estimation (using `stat` command) and other commands used (i.e. `du`).
003633
+
003633
+A rule of thumb is to reserve at least double the estimation.
003633
 .TP
003633
 .B \--upload
003633
 If specified, attempt to upload the resulting archive to a vendor defined location.
003633
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
003633
index ef61fb344..e0617b45e 100644
003633
--- a/sos/report/__init__.py
003633
+++ b/sos/report/__init__.py
003633
@@ -1330,7 +1330,8 @@ def final_work(self):
003633
             self.ui_log.info("Please note the estimation is relevant to the "
003633
                              "current options.")
003633
             self.ui_log.info("Be aware that the real disk space requirements "
003633
-                             "might be different.")
003633
+                             "might be different. A rule of thumb is to "
003633
+                             "reserve at least double the estimation.")
003633
             self.ui_log.info("")
003633
 
003633
         # package up and compress the results
003633
From f22efe044f1f0565b57d6aeca2081a5227e0312c Mon Sep 17 00:00:00 2001
003633
From: Jake Hunsaker <jhunsake@redhat.com>
003633
Date: Mon, 14 Feb 2022 09:37:30 -0500
003633
Subject: [PATCH] [utilities] Don't try to chroot to /
003633
003633
With the recent fix for sysroot being `None` to always being (correctly)
003633
`/`, we should guard against situations where `sos_get_command_output()`
003633
would now try to chroot to `/` before running any command. Incidentally,
003633
this would also cause our unittests to fail if they were run by a
003633
non-root user.
003633
003633
Signed-off-by: Jake Hunsaker <jhunsake@redhat.com>
003633
---
003633
 sos/utilities.py | 2 +-
003633
 1 file changed, 1 insertion(+), 1 deletion(-)
003633
003633
diff --git a/sos/utilities.py b/sos/utilities.py
003633
index 6b13415b..d782123a 100644
003633
--- a/sos/utilities.py
003633
+++ b/sos/utilities.py
003633
@@ -120,7 +120,7 @@ def sos_get_command_output(command, timeout=TIMEOUT_DEFAULT, stderr=False,
003633
     # closure are caught in the parent (chroot and chdir are bound from
003633
     # the enclosing scope).
003633
     def _child_prep_fn():
003633
-        if (chroot):
003633
+        if chroot and chroot != '/':
003633
             os.chroot(chroot)
003633
         if (chdir):
003633
             os.chdir(chdir)
003633
-- 
003633
2.34.1
003633
From 3d064102f8ca6662fd9602512e1cb05cf8746dfd Mon Sep 17 00:00:00 2001
003633
From: Jake Hunsaker <jhunsake@redhat.com>
003633
Date: Mon, 27 Sep 2021 19:01:16 -0400
003633
Subject: [PATCH] [Systemd, Policy] Correct InitSystem chrooting when chroot is
003633
 needed
003633
003633
This commit resolves a situation in which `sos` is being run in a
003633
container but the `SystemdInit` InitSystem would not properly load
003633
information from the host, thus causing the `Plugin.is_service*()`
003633
methods to erroneously fail or return `False`.
003633
003633
Fix this scenario by pulling the `_container_init()` and related logic
003633
to check for a containerized host sysroot out of the Red Hat specific
003633
policy and into the base `LinuxPolicy` class so that the init system can
003633
be initialized with the correct sysroot, which is now used to chroot the
003633
calls to the relevant `systemctl` commands.
003633
003633
For now, this does impose the use of looking for the `container` env var
003633
(automatically set by docker, podman, and crio regardless of
003633
distribution) and the use of the `HOST` env var to read where the host's
003633
`/` filesystem is mounted within the container. If desired in the
003633
future, this can be changed to allow policy-specific overrides. For now
003633
however, this extends host collection via an sos container for all
003633
distributions currently shipping sos.
003633
003633
Note that this issue only affected the `InitSystem` abstraction for
003633
loading information about local services, and did not affect init system
003633
related commands called by plugins as part of those collections.
003633
003633
Signed-off-by: Jake Hunsaker <jhunsake@redhat.com>
003633
---
003633
 sos/policies/distros/__init__.py      | 28 ++++++++++++++++++++++++++-
003633
 sos/policies/distros/redhat.py        | 27 +-------------------------
003633
 sos/policies/init_systems/__init__.py | 13 +++++++++++--
003633
 sos/policies/init_systems/systemd.py  |  7 ++++---
003633
 4 files changed, 43 insertions(+), 32 deletions(-)
003633
003633
diff --git a/sos/policies/distros/__init__.py b/sos/policies/distros/__init__.py
003633
index f5b9fd5b01..c33a356a75 100644
003633
--- a/sos/policies/distros/__init__.py
003633
+++ b/sos/policies/distros/__init__.py
003633
@@ -29,6 +29,10 @@
003633
 except ImportError:
003633
     REQUESTS_LOADED = False
003633
 
003633
+# Container environment variables for detecting if we're in a container
003633
+ENV_CONTAINER = 'container'
003633
+ENV_HOST_SYSROOT = 'HOST'
003633
+
003633
 
003633
 class LinuxPolicy(Policy):
003633
     """This policy is meant to be an abc class that provides common
003633
@@ -69,10 +73,17 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True):
003633
                                           probe_runtime=probe_runtime)
003633
         self.init_kernel_modules()
003633
 
003633
+        # need to set _host_sysroot before PackageManager()
003633
+        if sysroot:
003633
+            self._container_init()
003633
+            self._host_sysroot = sysroot
003633
+        else:
003633
+            sysroot = self._container_init()
003633
+
003633
         if init is not None:
003633
             self.init_system = init
003633
         elif os.path.isdir("/run/systemd/system/"):
003633
-            self.init_system = SystemdInit()
003633
+            self.init_system = SystemdInit(chroot=sysroot)
003633
         else:
003633
             self.init_system = InitSystem()
003633
 
003633
@@ -130,6 +141,21 @@ def get_local_name(self):
003633
     def sanitize_filename(self, name):
003633
         return re.sub(r"[^-a-z,A-Z.0-9]", "", name)
003633
 
003633
+    def _container_init(self):
003633
+        """Check if sos is running in a container and perform container
003633
+        specific initialisation based on ENV_HOST_SYSROOT.
003633
+        """
003633
+        if ENV_CONTAINER in os.environ:
003633
+            if os.environ[ENV_CONTAINER] in ['docker', 'oci', 'podman']:
003633
+                self._in_container = True
003633
+        if ENV_HOST_SYSROOT in os.environ:
003633
+            self._host_sysroot = os.environ[ENV_HOST_SYSROOT]
003633
+        use_sysroot = self._in_container and self._host_sysroot is not None
003633
+        if use_sysroot:
003633
+            host_tmp_dir = os.path.abspath(self._host_sysroot + self._tmp_dir)
003633
+            self._tmp_dir = host_tmp_dir
003633
+        return self._host_sysroot if use_sysroot else None
003633
+
003633
     def init_kernel_modules(self):
003633
         """Obtain a list of loaded kernel modules to reference later for plugin
003633
         enablement and SoSPredicate checks
003633
diff --git a/sos/policies/distros/redhat.py b/sos/policies/distros/redhat.py
003633
index b3a84336be..3476e21fb2 100644
003633
--- a/sos/policies/distros/redhat.py
003633
+++ b/sos/policies/distros/redhat.py
003633
@@ -17,7 +17,7 @@
003633
 from sos.presets.redhat import (RHEL_PRESETS, ATOMIC_PRESETS, RHV, RHEL,
003633
                                 CB, RHOSP, RHOCP, RH_CFME, RH_SATELLITE,
003633
                                 ATOMIC)
003633
-from sos.policies.distros import LinuxPolicy
003633
+from sos.policies.distros import LinuxPolicy, ENV_HOST_SYSROOT
003633
 from sos.policies.package_managers.rpm import RpmPackageManager
003633
 from sos import _sos as _
003633
 
003633
@@ -56,12 +56,6 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True,
003633
         super(RedHatPolicy, self).__init__(sysroot=sysroot, init=init,
003633
                                            probe_runtime=probe_runtime)
003633
         self.usrmove = False
003633
-        # need to set _host_sysroot before PackageManager()
003633
-        if sysroot:
003633
-            self._container_init()
003633
-            self._host_sysroot = sysroot
003633
-        else:
003633
-            sysroot = self._container_init()
003633
 
003633
         self.package_manager = RpmPackageManager(chroot=sysroot,
003633
                                                  remote_exec=remote_exec)
003633
@@ -140,21 +134,6 @@ def transform_path(path):
003633
         else:
003633
             return files
003633
 
003633
-    def _container_init(self):
003633
-        """Check if sos is running in a container and perform container
003633
-        specific initialisation based on ENV_HOST_SYSROOT.
003633
-        """
003633
-        if ENV_CONTAINER in os.environ:
003633
-            if os.environ[ENV_CONTAINER] in ['docker', 'oci', 'podman']:
003633
-                self._in_container = True
003633
-        if ENV_HOST_SYSROOT in os.environ:
003633
-            self._host_sysroot = os.environ[ENV_HOST_SYSROOT]
003633
-        use_sysroot = self._in_container and self._host_sysroot is not None
003633
-        if use_sysroot:
003633
-            host_tmp_dir = os.path.abspath(self._host_sysroot + self._tmp_dir)
003633
-            self._tmp_dir = host_tmp_dir
003633
-        return self._host_sysroot if use_sysroot else None
003633
-
003633
     def runlevel_by_service(self, name):
003633
         from subprocess import Popen, PIPE
003633
         ret = []
003633
@@ -183,10 +162,6 @@ def get_tmp_dir(self, opt_tmp_dir):
003633
         return opt_tmp_dir
003633
 
003633
 
003633
-# Container environment variables on Red Hat systems.
003633
-ENV_CONTAINER = 'container'
003633
-ENV_HOST_SYSROOT = 'HOST'
003633
-
003633
 # Legal disclaimer text for Red Hat products
003633
 disclaimer_text = """
003633
 Any information provided to %(vendor)s will be treated in \
003633
diff --git a/sos/policies/init_systems/__init__.py b/sos/policies/init_systems/__init__.py
003633
index dd663e6522..beac44cee3 100644
003633
--- a/sos/policies/init_systems/__init__.py
003633
+++ b/sos/policies/init_systems/__init__.py
003633
@@ -29,9 +29,14 @@ class InitSystem():
003633
                       status of services
003633
     :type query_cmd: ``str``
003633
 
003633
+    :param chroot:  Location to chroot to for any command execution, i.e. the
003633
+                    sysroot if we're running in a container
003633
+    :type chroot:   ``str`` or ``None``
003633
+
003633
     """
003633
 
003633
-    def __init__(self, init_cmd=None, list_cmd=None, query_cmd=None):
003633
+    def __init__(self, init_cmd=None, list_cmd=None, query_cmd=None,
003633
+                 chroot=None):
003633
         """Initialize a new InitSystem()"""
003633
 
003633
         self.services = {}
003633
@@ -39,6 +44,7 @@ def __init__(self, init_cmd=None, list_cmd=None, query_cmd=None):
003633
         self.init_cmd = init_cmd
003633
         self.list_cmd = "%s %s" % (self.init_cmd, list_cmd) or None
003633
         self.query_cmd = "%s %s" % (self.init_cmd, query_cmd) or None
003633
+        self.chroot = chroot
003633
 
003633
     def is_enabled(self, name):
003633
         """Check if given service name is enabled
003633
@@ -108,7 +114,10 @@ def _query_service(self, name):
003633
         """Query an individual service"""
003633
         if self.query_cmd:
003633
             try:
003633
-                return sos_get_command_output("%s %s" % (self.query_cmd, name))
003633
+                return sos_get_command_output(
003633
+                    "%s %s" % (self.query_cmd, name),
003633
+                    chroot=self.chroot
003633
+                )
003633
             except Exception:
003633
                 return None
003633
         return None
003633
diff --git a/sos/policies/init_systems/systemd.py b/sos/policies/init_systems/systemd.py
003633
index 1b138f97b3..76dc57e27f 100644
003633
--- a/sos/policies/init_systems/systemd.py
003633
+++ b/sos/policies/init_systems/systemd.py
003633
@@ -15,11 +15,12 @@
003633
 class SystemdInit(InitSystem):
003633
     """InitSystem abstraction for SystemD systems"""
003633
 
003633
-    def __init__(self):
003633
+    def __init__(self, chroot=None):
003633
         super(SystemdInit, self).__init__(
003633
             init_cmd='systemctl',
003633
             list_cmd='list-unit-files --type=service',
003633
-            query_cmd='status'
003633
+            query_cmd='status',
003633
+            chroot=chroot
003633
         )
003633
         self.load_all_services()
003633
 
003633
@@ -30,7 +31,7 @@ def parse_query(self, output):
003633
         return 'unknown'
003633
 
003633
     def load_all_services(self):
003633
-        svcs = shell_out(self.list_cmd).splitlines()[1:]
003633
+        svcs = shell_out(self.list_cmd, chroot=self.chroot).splitlines()[1:]
003633
         for line in svcs:
003633
             try:
003633
                 name = line.split('.service')[0]
003633
From e869bc84c714bfc2249bbcb84e14908049ee42c4 Mon Sep 17 00:00:00 2001
003633
From: Jake Hunsaker <jhunsake@redhat.com>
003633
Date: Mon, 27 Sep 2021 12:07:08 -0400
003633
Subject: [PATCH] [Plugin,utilities] Add sysroot wrapper for os.path.join
003633
003633
Adds a wrapper for `os.path.join()` which accounts for non-/ sysroots,
003633
like we have done previously for other `os.path` methods. Further
003633
updates `Plugin()` to use this wrapper where appropriate.
003633
003633
Signed-off-by: Jake Hunsaker <jhunsake@redhat.com>
003633
---
003633
 sos/report/plugins/__init__.py | 43 +++++++++++++++++-----------------
003633
 sos/utilities.py               |  6 +++++
003633
 2 files changed, 28 insertions(+), 21 deletions(-)
003633
003633
diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py
003633
index c635b8de9..1f84bca49 100644
003633
--- a/sos/report/plugins/__init__.py
003633
+++ b/sos/report/plugins/__init__.py
003633
@@ -13,7 +13,7 @@
003633
 from sos.utilities import (sos_get_command_output, import_module, grep,
003633
                            fileobj, tail, is_executable, TIMEOUT_DEFAULT,
003633
                            path_exists, path_isdir, path_isfile, path_islink,
003633
-                           listdir)
003633
+                           listdir, path_join)
003633
 
003633
 import os
003633
 import glob
003633
@@ -708,19 +708,6 @@ def _log_info(self, msg):
003633
     def _log_debug(self, msg):
003633
         self.soslog.debug(self._format_msg(msg))
003633
 
003633
-    def join_sysroot(self, path):
003633
-        """Join a given path with the configured sysroot
003633
-
003633
-        :param path:    The filesystem path that needs to be joined
003633
-        :type path: ``str``
003633
-
003633
-        :returns: The joined filesystem path
003633
-        :rtype: ``str``
003633
-        """
003633
-        if path[0] == os.sep:
003633
-            path = path[1:]
003633
-        return os.path.join(self.sysroot, path)
003633
-
003633
     def strip_sysroot(self, path):
003633
         """Remove the configured sysroot from a filesystem path
003633
 
003633
@@ -1176,7 +1163,7 @@ def _copy_dir(self, srcpath):
003633
 
003633
     def _get_dest_for_srcpath(self, srcpath):
003633
         if self.use_sysroot():
003633
-            srcpath = self.join_sysroot(srcpath)
003633
+            srcpath = self.path_join(srcpath)
003633
         for copied in self.copied_files:
003633
             if srcpath == copied["srcpath"]:
003633
                 return copied["dstpath"]
003633
@@ -1284,7 +1271,7 @@ def add_forbidden_path(self, forbidden, recursive=False):
003633
             forbidden = [forbidden]
003633
 
003633
         if self.use_sysroot():
003633
-            forbidden = [self.join_sysroot(f) for f in forbidden]
003633
+            forbidden = [self.path_join(f) for f in forbidden]
003633
 
003633
         for forbid in forbidden:
003633
             self._log_info("adding forbidden path '%s'" % forbid)
003633
@@ -1438,7 +1425,7 @@ def add_copy_spec(self, copyspecs, sizelimit=None, maxage=None,
003633
             since = self.get_option('since')
003633
 
003633
         logarchive_pattern = re.compile(r'.*((\.(zip|gz|bz2|xz))|[-.][\d]+)$')
003633
-        configfile_pattern = re.compile(r"^%s/*" % self.join_sysroot("etc"))
003633
+        configfile_pattern = re.compile(r"^%s/*" % self.path_join("etc"))
003633
 
003633
         if not self.test_predicate(pred=pred):
003633
             self._log_info("skipped copy spec '%s' due to predicate (%s)" %
003633
@@ -1468,7 +1455,7 @@ def add_copy_spec(self, copyspecs, sizelimit=None, maxage=None,
003633
                 return False
003633
 
003633
             if self.use_sysroot():
003633
-                copyspec = self.join_sysroot(copyspec)
003633
+                copyspec = self.path_join(copyspec)
003633
 
003633
             files = self._expand_copy_spec(copyspec)
003633
 
003633
@@ -1683,7 +1670,7 @@ def _add_device_cmd(self, cmds, devices, timeout=None, sizelimit=None,
003633
                 if not _dev_ok:
003633
                     continue
003633
                 if prepend_path:
003633
-                    device = os.path.join(prepend_path, device)
003633
+                    device = self.path_join(prepend_path, device)
003633
                 _cmd = cmd % {'dev': device}
003633
                 self._add_cmd_output(cmd=_cmd, timeout=timeout,
003633
                                      sizelimit=sizelimit, chroot=chroot,
003633
@@ -2592,7 +2579,7 @@ def __expand(paths):
003633
                     if self.path_isfile(path) or self.path_islink(path):
003633
                         found_paths.append(path)
003633
                     elif self.path_isdir(path) and self.listdir(path):
003633
-                        found_paths.extend(__expand(os.path.join(path, '*')))
003633
+                        found_paths.extend(__expand(self.path_join(path, '*')))
003633
                     else:
003633
                         found_paths.append(path)
003633
                 except PermissionError:
003633
@@ -2608,7 +2595,7 @@ def __expand(paths):
003633
         if (os.access(copyspec, os.R_OK) and self.path_isdir(copyspec) and
003633
                 self.listdir(copyspec)):
003633
             # the directory exists and is non-empty, recurse through it
003633
-            copyspec = os.path.join(copyspec, '*')
003633
+            copyspec = self.path_join(copyspec, '*')
003633
         expanded = glob.glob(copyspec, recursive=True)
003633
         recursed_files = []
003633
         for _path in expanded:
003633
@@ -2877,6 +2864,20 @@ def listdir(self, path):
003633
         """
003633
         return listdir(path, self.commons['cmdlineopts'].sysroot)
003633
 
003633
+    def path_join(self, path, *p):
003633
+        """Helper to call the sos.utilities wrapper that allows the
003633
+        corresponding `os` call to account for sysroot
003633
+
003633
+        :param path:    The leading path passed to os.path.join()
003633
+        :type path:     ``str``
003633
+
003633
+        :param p:       Following path section(s) to be joined with ``path``,
003633
+                        an empty parameter will result in a path that ends with
003633
+                        a separator
003633
+        :type p:        ``str``
003633
+        """
003633
+        return path_join(path, *p, sysroot=self.sysroot)
003633
+
003633
     def postproc(self):
003633
         """Perform any postprocessing. To be replaced by a plugin if required.
003633
         """
003633
diff --git a/sos/utilities.py b/sos/utilities.py
003633
index c940e066d..b75751539 100644
003633
--- a/sos/utilities.py
003633
+++ b/sos/utilities.py
003633
@@ -242,6 +242,12 @@ def listdir(path, sysroot):
003633
     return _os_wrapper(path, sysroot, 'listdir', os)
003633
 
003633
 
003633
+def path_join(path, *p, sysroot=os.sep):
003633
+    if not path.startswith(sysroot):
003633
+        path = os.path.join(sysroot, path.lstrip(os.sep))
003633
+    return os.path.join(path, *p)
003633
+
003633
+
003633
 class AsyncReader(threading.Thread):
003633
     """Used to limit command output to a given size without deadlocking
003633
     sos.
003633
From 9596473d1779b9c48e9923c220aaf2b8d9b3bebf Mon Sep 17 00:00:00 2001
003633
From: Jake Hunsaker <jhunsake@redhat.com>
003633
Date: Thu, 18 Nov 2021 13:17:14 -0500
003633
Subject: [PATCH] [global] Align sysroot determination and usage across sos
003633
003633
The determination of sysroot - being automatic, user-specified, or
003633
controlled via environment variables in a container - has gotten muddied
003633
over time. This has resulted in different parts of the project;
003633
`Policy`, `Plugin`, `SoSComponent`, etc... to not always be in sync when
003633
sysroot is not `/`, thus causing varying and unexpected/unintended
003633
behavior.
003633
003633
Fix this by only determining sysroot within `Policy()` initialization,
003633
and then using that determination across all aspects of the project that
003633
use or reference sysroot.
003633
003633
This results in several changes:
003633
003633
- `PackageManager()` will now (again) correctly reference host package
003633
  lists when sos is run in a container.
003633
003633
- `ContainerRuntime()` is now able to activate when sos is running in a
003633
  container.
003633
003633
- Plugins will now properly use sysroot for _all_ plugin enablement
003633
  triggers.
003633
003633
- Plugins, Policy, and SoSComponents now all reference the
003633
  `self.sysroot` variable, rather than changing between `sysroot`.
003633
`_host_sysroot`, and `commons['sysroot']`. `_host_sysroot` has been
003633
removed from `Policy`.
003633
003633
Signed-off-by: Jake Hunsaker <jhunsake@redhat.com>
003633
---
003633
 sos/archive.py                    |  2 +-
003633
 sos/component.py                  |  2 +-
003633
 sos/policies/__init__.py          | 11 +----------
003633
 sos/policies/distros/__init__.py  | 33 +++++++++++++++++++------------
003633
 sos/policies/distros/debian.py    |  2 +-
003633
 sos/policies/distros/redhat.py    |  3 +--
003633
 sos/policies/runtimes/__init__.py | 15 +++++++++-----
003633
 sos/policies/runtimes/docker.py   |  4 ++--
003633
 sos/report/__init__.py            |  6 ++----
003633
 sos/report/plugins/__init__.py    | 22 +++++++++++----------
003633
 sos/report/plugins/unpackaged.py  |  7 ++++---
003633
 sos/utilities.py                  | 13 ++++++++----
003633
 12 files changed, 64 insertions(+), 56 deletions(-)
003633
003633
diff --git a/sos/archive.py b/sos/archive.py
003633
index b02b247595..e3c68b7789 100644
003633
--- a/sos/archive.py
003633
+++ b/sos/archive.py
003633
@@ -153,7 +153,7 @@ def dest_path(self, name):
003633
         return (os.path.join(self._archive_root, name))
003633
 
003633
     def join_sysroot(self, path):
003633
-        if path.startswith(self.sysroot):
003633
+        if not self.sysroot or path.startswith(self.sysroot):
003633
             return path
003633
         if path[0] == os.sep:
003633
             path = path[1:]
003633
diff --git a/sos/component.py b/sos/component.py
003633
index 5ac6e47f4f..dba0aabf2b 100644
003633
--- a/sos/component.py
003633
+++ b/sos/component.py
003633
@@ -109,7 +109,7 @@ def __init__(self, parser, parsed_args, cmdline_args):
003633
             try:
003633
                 import sos.policies
003633
                 self.policy = sos.policies.load(sysroot=self.opts.sysroot)
003633
-                self.sysroot = self.policy.host_sysroot()
003633
+                self.sysroot = self.policy.sysroot
003633
             except KeyboardInterrupt:
003633
                 self._exit(0)
003633
             self._is_root = self.policy.is_root()
003633
diff --git a/sos/policies/__init__.py b/sos/policies/__init__.py
003633
index fb8db1d724..ef9188deb4 100644
003633
--- a/sos/policies/__init__.py
003633
+++ b/sos/policies/__init__.py
003633
@@ -110,7 +110,6 @@ class Policy(object):
003633
     presets = {"": PresetDefaults()}
003633
     presets_path = PRESETS_PATH
003633
     _in_container = False
003633
-    _host_sysroot = '/'
003633
 
003633
     def __init__(self, sysroot=None, probe_runtime=True):
003633
         """Subclasses that choose to override this initializer should call
003633
@@ -124,7 +123,7 @@ def __init__(self, sysroot=None, probe_runtime=True):
003633
         self.package_manager = PackageManager()
003633
         self.valid_subclasses = [IndependentPlugin]
003633
         self.set_exec_path()
003633
-        self._host_sysroot = sysroot
003633
+        self.sysroot = sysroot
003633
         self.register_presets(GENERIC_PRESETS)
003633
 
003633
     def check(self, remote=''):
003633
@@ -177,14 +176,6 @@ def in_container(self):
003633
         """
003633
         return self._in_container
003633
 
003633
-    def host_sysroot(self):
003633
-        """Get the host's default sysroot
003633
-
003633
-        :returns: Host sysroot
003633
-        :rtype: ``str`` or ``None``
003633
-        """
003633
-        return self._host_sysroot
003633
-
003633
     def dist_version(self):
003633
         """
003633
         Return the OS version
003633
diff --git a/sos/policies/distros/__init__.py b/sos/policies/distros/__init__.py
003633
index 7bdc81b852..c69fc1e73c 100644
003633
--- a/sos/policies/distros/__init__.py
003633
+++ b/sos/policies/distros/__init__.py
003633
@@ -71,19 +71,18 @@ class LinuxPolicy(Policy):
003633
     def __init__(self, sysroot=None, init=None, probe_runtime=True):
003633
         super(LinuxPolicy, self).__init__(sysroot=sysroot,
003633
                                           probe_runtime=probe_runtime)
003633
-        self.init_kernel_modules()
003633
 
003633
-        # need to set _host_sysroot before PackageManager()
003633
         if sysroot:
003633
-            self._container_init()
003633
-            self._host_sysroot = sysroot
003633
+            self.sysroot = sysroot
003633
         else:
003633
-            sysroot = self._container_init()
003633
+            self.sysroot = self._container_init()
003633
+
003633
+        self.init_kernel_modules()
003633
 
003633
         if init is not None:
003633
             self.init_system = init
003633
         elif os.path.isdir("/run/systemd/system/"):
003633
-            self.init_system = SystemdInit(chroot=sysroot)
003633
+            self.init_system = SystemdInit(chroot=self.sysroot)
003633
         else:
003633
             self.init_system = InitSystem()
003633
 
003633
@@ -149,27 +148,30 @@ def _container_init(self):
003633
             if os.environ[ENV_CONTAINER] in ['docker', 'oci', 'podman']:
003633
                 self._in_container = True
003633
         if ENV_HOST_SYSROOT in os.environ:
003633
-            self._host_sysroot = os.environ[ENV_HOST_SYSROOT]
003633
-        use_sysroot = self._in_container and self._host_sysroot is not None
003633
+            _host_sysroot = os.environ[ENV_HOST_SYSROOT]
003633
+        use_sysroot = self._in_container and _host_sysroot is not None
003633
         if use_sysroot:
003633
-            host_tmp_dir = os.path.abspath(self._host_sysroot + self._tmp_dir)
003633
+            host_tmp_dir = os.path.abspath(_host_sysroot + self._tmp_dir)
003633
             self._tmp_dir = host_tmp_dir
003633
-        return self._host_sysroot if use_sysroot else None
003633
+        return _host_sysroot if use_sysroot else None
003633
 
003633
     def init_kernel_modules(self):
003633
         """Obtain a list of loaded kernel modules to reference later for plugin
003633
         enablement and SoSPredicate checks
003633
         """
003633
         self.kernel_mods = []
003633
+        release = os.uname().release
003633
 
003633
         # first load modules from lsmod
003633
-        lines = shell_out("lsmod", timeout=0).splitlines()
003633
+        lines = shell_out("lsmod", timeout=0, chroot=self.sysroot).splitlines()
003633
         self.kernel_mods.extend([
003633
             line.split()[0].strip() for line in lines[1:]
003633
         ])
003633
 
003633
         # next, include kernel builtins
003633
-        builtins = "/usr/lib/modules/%s/modules.builtin" % os.uname().release
003633
+        builtins = self.join_sysroot(
003633
+            "/usr/lib/modules/%s/modules.builtin" % release
003633
+        )
003633
         try:
003633
             with open(builtins, "r") as mfile:
003633
                 for line in mfile:
003633
@@ -186,7 +188,7 @@ def init_kernel_modules(self):
003633
             'dm_mod': 'CONFIG_BLK_DEV_DM'
003633
         }
003633
 
003633
-        booted_config = "/boot/config-%s" % os.uname().release
003633
+        booted_config = self.join_sysroot("/boot/config-%s" % release)
003633
         kconfigs = []
003633
         try:
003633
             with open(booted_config, "r") as kfile:
003633
@@ -200,6 +202,11 @@ def init_kernel_modules(self):
003633
             if config_strings[builtin] in kconfigs:
003633
                 self.kernel_mods.append(builtin)
003633
 
003633
+    def join_sysroot(self, path):
003633
+        if self.sysroot and self.sysroot != '/':
003633
+            path = os.path.join(self.sysroot, path.lstrip('/'))
003633
+        return path
003633
+
003633
     def pre_work(self):
003633
         # this method will be called before the gathering begins
003633
 
003633
diff --git a/sos/policies/distros/debian.py b/sos/policies/distros/debian.py
003633
index 95b389a65e..639fd5eba3 100644
003633
--- a/sos/policies/distros/debian.py
003633
+++ b/sos/policies/distros/debian.py
003633
@@ -27,7 +27,7 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True,
003633
                  remote_exec=None):
003633
         super(DebianPolicy, self).__init__(sysroot=sysroot, init=init,
003633
                                            probe_runtime=probe_runtime)
003633
-        self.package_manager = DpkgPackageManager(chroot=sysroot,
003633
+        self.package_manager = DpkgPackageManager(chroot=self.sysroot,
003633
                                                   remote_exec=remote_exec)
003633
         self.valid_subclasses += [DebianPlugin]
003633
 
003633
diff --git a/sos/policies/distros/redhat.py b/sos/policies/distros/redhat.py
003633
index eb44240736..4b14abaf3a 100644
003633
--- a/sos/policies/distros/redhat.py
003633
+++ b/sos/policies/distros/redhat.py
003633
@@ -42,7 +42,6 @@ class RedHatPolicy(LinuxPolicy):
003633
     _redhat_release = '/etc/redhat-release'
003633
     _tmp_dir = "/var/tmp"
003633
     _in_container = False
003633
-    _host_sysroot = '/'
003633
     default_scl_prefix = '/opt/rh'
003633
     name_pattern = 'friendly'
003633
     upload_url = None
003633
@@ -57,7 +56,7 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True,
003633
                                            probe_runtime=probe_runtime)
003633
         self.usrmove = False
003633
 
003633
-        self.package_manager = RpmPackageManager(chroot=sysroot,
003633
+        self.package_manager = RpmPackageManager(chroot=self.sysroot,
003633
                                                  remote_exec=remote_exec)
003633
 
003633
         self.valid_subclasses += [RedHatPlugin]
003633
diff --git a/sos/policies/runtimes/__init__.py b/sos/policies/runtimes/__init__.py
003633
index f28d6a1df3..2e60ad2361 100644
003633
--- a/sos/policies/runtimes/__init__.py
003633
+++ b/sos/policies/runtimes/__init__.py
003633
@@ -64,7 +64,7 @@ def check_is_active(self):
003633
         :returns: ``True`` if the runtime is active, else ``False``
003633
         :rtype: ``bool``
003633
         """
003633
-        if is_executable(self.binary):
003633
+        if is_executable(self.binary, self.policy.sysroot):
003633
             self.active = True
003633
             return True
003633
         return False
003633
@@ -78,7 +78,7 @@ def get_containers(self, get_all=False):
003633
         containers = []
003633
         _cmd = "%s ps %s" % (self.binary, '-a' if get_all else '')
003633
         if self.active:
003633
-            out = sos_get_command_output(_cmd)
003633
+            out = sos_get_command_output(_cmd, chroot=self.policy.sysroot)
003633
             if out['status'] == 0:
003633
                 for ent in out['output'].splitlines()[1:]:
003633
                     ent = ent.split()
003633
@@ -112,8 +112,10 @@ def get_images(self):
003633
         images = []
003633
         fmt = '{{lower .Repository}}:{{lower .Tag}} {{lower .ID}}'
003633
         if self.active:
003633
-            out = sos_get_command_output("%s images --format '%s'"
003633
-                                         % (self.binary, fmt))
003633
+            out = sos_get_command_output(
003633
+                "%s images --format '%s'" % (self.binary, fmt),
003633
+                chroot=self.policy.sysroot
003633
+            )
003633
             if out['status'] == 0:
003633
                 for ent in out['output'].splitlines():
003633
                     ent = ent.split()
003633
@@ -129,7 +131,10 @@ def get_volumes(self):
003633
         """
003633
         vols = []
003633
         if self.active:
003633
-            out = sos_get_command_output("%s volume ls" % self.binary)
003633
+            out = sos_get_command_output(
003633
+                "%s volume ls" % self.binary,
003633
+                chroot=self.policy.sysroot
003633
+            )
003633
             if out['status'] == 0:
003633
                 for ent in out['output'].splitlines()[1:]:
003633
                     ent = ent.split()
003633
diff --git a/sos/policies/runtimes/docker.py b/sos/policies/runtimes/docker.py
003633
index 759dfaf6a0..e81f580ec3 100644
003633
--- a/sos/policies/runtimes/docker.py
003633
+++ b/sos/policies/runtimes/docker.py
003633
@@ -18,9 +18,9 @@ class DockerContainerRuntime(ContainerRuntime):
003633
     name = 'docker'
003633
     binary = 'docker'
003633
 
003633
-    def check_is_active(self):
003633
+    def check_is_active(self, sysroot=None):
003633
         # the daemon must be running
003633
-        if (is_executable('docker') and
003633
+        if (is_executable('docker', sysroot) and
003633
                 (self.policy.init_system.is_running('docker') or
003633
                  self.policy.init_system.is_running('snap.docker.dockerd'))):
003633
             self.active = True
003633
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
003633
index a4c92accd3..a6c72778fc 100644
003633
--- a/sos/report/__init__.py
003633
+++ b/sos/report/__init__.py
003633
@@ -173,14 +173,12 @@ def __init__(self, parser, args, cmdline):
003633
         self._set_directories()
003633
 
003633
         msg = "default"
003633
-        host_sysroot = self.policy.host_sysroot()
003633
+        self.sysroot = self.policy.sysroot
003633
         # set alternate system root directory
003633
         if self.opts.sysroot:
003633
             msg = "cmdline"
003633
-            self.sysroot = self.opts.sysroot
003633
-        elif self.policy.in_container() and host_sysroot != os.sep:
003633
+        elif self.policy.in_container() and self.sysroot != os.sep:
003633
             msg = "policy"
003633
-            self.sysroot = host_sysroot
003633
         self.soslog.debug("set sysroot to '%s' (%s)" % (self.sysroot, msg))
003633
 
003633
         if self.opts.chroot not in chroot_modes:
003633
diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py
003633
index 46028bb124..e180ae1727 100644
003633
--- a/sos/report/plugins/__init__.py
003633
+++ b/sos/report/plugins/__init__.py
003633
@@ -724,7 +724,7 @@ def strip_sysroot(self, path):
003633
         """
003633
         if not self.use_sysroot():
003633
             return path
003633
-        if path.startswith(self.sysroot):
003633
+        if self.sysroot and path.startswith(self.sysroot):
003633
             return path[len(self.sysroot):]
003633
         return path
003633
 
003633
@@ -743,8 +743,10 @@ def tmp_in_sysroot(self):
003633
                   ``False``
003633
         :rtype: ``bool``
003633
         """
003633
-        paths = [self.sysroot, self.archive.get_tmp_dir()]
003633
-        return os.path.commonprefix(paths) == self.sysroot
003633
+        # if sysroot is still None, that implies '/'
003633
+        _sysroot = self.sysroot or '/'
003633
+        paths = [_sysroot, self.archive.get_tmp_dir()]
003633
+        return os.path.commonprefix(paths) == _sysroot
003633
 
003633
     def is_installed(self, package_name):
003633
         """Is the package $package_name installed?
003633
@@ -2621,7 +2623,7 @@ def __expand(paths):
003633
         return list(set(expanded))
003633
 
003633
     def _collect_copy_specs(self):
003633
-        for path in self.copy_paths:
003633
+        for path in sorted(self.copy_paths, reverse=True):
003633
             self._log_info("collecting path '%s'" % path)
003633
             self._do_copy_path(path)
003633
         self.generate_copyspec_tags()
003633
@@ -2749,7 +2751,7 @@ def _check_plugin_triggers(self, files, packages, commands, services,
003633
 
003633
         return ((any(self.path_exists(fname) for fname in files) or
003633
                 any(self.is_installed(pkg) for pkg in packages) or
003633
-                any(is_executable(cmd) for cmd in commands) or
003633
+                any(is_executable(cmd, self.sysroot) for cmd in commands) or
003633
                 any(self.is_module_loaded(mod) for mod in self.kernel_mods) or
003633
                 any(self.is_service(svc) for svc in services) or
003633
                 any(self.container_exists(cntr) for cntr in containers)) and
003633
@@ -2817,7 +2819,7 @@ def path_exists(self, path):
003633
         :returns:           True if the path exists in sysroot, else False
003633
         :rtype:             ``bool``
003633
         """
003633
-        return path_exists(path, self.commons['cmdlineopts'].sysroot)
003633
+        return path_exists(path, self.sysroot)
003633
 
003633
     def path_isdir(self, path):
003633
         """Helper to call the sos.utilities wrapper that allows the
003633
@@ -2830,7 +2832,7 @@ def path_isdir(self, path):
003633
         :returns:           True if the path is a dir, else False
003633
         :rtype:             ``bool``
003633
         """
003633
-        return path_isdir(path, self.commons['cmdlineopts'].sysroot)
003633
+        return path_isdir(path, self.sysroot)
003633
 
003633
     def path_isfile(self, path):
003633
         """Helper to call the sos.utilities wrapper that allows the
003633
@@ -2843,7 +2845,7 @@ def path_isfile(self, path):
003633
         :returns:           True if the path is a file, else False
003633
         :rtype:             ``bool``
003633
         """
003633
-        return path_isfile(path, self.commons['cmdlineopts'].sysroot)
003633
+        return path_isfile(path, self.sysroot)
003633
 
003633
     def path_islink(self, path):
003633
         """Helper to call the sos.utilities wrapper that allows the
003633
@@ -2856,7 +2858,7 @@ def path_islink(self, path):
003633
         :returns:           True if the path is a link, else False
003633
         :rtype:             ``bool``
003633
         """
003633
-        return path_islink(path, self.commons['cmdlineopts'].sysroot)
003633
+        return path_islink(path, self.sysroot)
003633
 
003633
     def listdir(self, path):
003633
         """Helper to call the sos.utilities wrapper that allows the
003633
@@ -2869,7 +2871,7 @@ def listdir(self, path):
003633
         :returns:           Contents of path, if it is a directory
003633
         :rtype:             ``list``
003633
         """
003633
-        return listdir(path, self.commons['cmdlineopts'].sysroot)
003633
+        return listdir(path, self.sysroot)
003633
 
003633
     def path_join(self, path, *p):
003633
         """Helper to call the sos.utilities wrapper that allows the
003633
diff --git a/sos/report/plugins/unpackaged.py b/sos/report/plugins/unpackaged.py
003633
index 772b1d1fbb..24203c4b13 100644
003633
--- a/sos/report/plugins/unpackaged.py
003633
+++ b/sos/report/plugins/unpackaged.py
003633
@@ -58,10 +58,11 @@ def format_output(files):
003633
             """
003633
             expanded = []
003633
             for f in files:
003633
-                if self.path_islink(f):
003633
-                    expanded.append("{} -> {}".format(f, os.readlink(f)))
003633
+                fp = self.path_join(f)
003633
+                if self.path_islink(fp):
003633
+                    expanded.append("{} -> {}".format(fp, os.readlink(fp)))
003633
                 else:
003633
-                    expanded.append(f)
003633
+                    expanded.append(fp)
003633
             return expanded
003633
 
003633
         # Check command predicate to avoid costly processing
003633
diff --git a/sos/utilities.py b/sos/utilities.py
003633
index b757515397..d66309334b 100644
003633
--- a/sos/utilities.py
003633
+++ b/sos/utilities.py
003633
@@ -96,11 +96,15 @@ def grep(pattern, *files_or_paths):
003633
     return matches
003633
 
003633
 
003633
-def is_executable(command):
003633
+def is_executable(command, sysroot=None):
003633
     """Returns if a command matches an executable on the PATH"""
003633
 
003633
     paths = os.environ.get("PATH", "").split(os.path.pathsep)
003633
     candidates = [command] + [os.path.join(p, command) for p in paths]
003633
+    if sysroot:
003633
+        candidates += [
003633
+            os.path.join(sysroot, c.lstrip('/')) for c in candidates
003633
+        ]
003633
     return any(os.access(path, os.X_OK) for path in candidates)
003633
 
003633
 
003633
@@ -216,8 +220,9 @@ def get_human_readable(size, precision=2):
003633
 
003633
 
003633
 def _os_wrapper(path, sysroot, method, module=os.path):
003633
-    if sysroot not in [None, '/']:
003633
-        path = os.path.join(sysroot, path.lstrip('/'))
003633
+    if sysroot and sysroot != os.sep:
003633
+        if not path.startswith(sysroot):
003633
+            path = os.path.join(sysroot, path.lstrip('/'))
003633
     _meth = getattr(module, method)
003633
     return _meth(path)
003633
 
003633
@@ -243,7 +248,7 @@ def listdir(path, sysroot):
003633
 
003633
 
003633
 def path_join(path, *p, sysroot=os.sep):
003633
-    if not path.startswith(sysroot):
003633
+    if sysroot and not path.startswith(sysroot):
003633
         path = os.path.join(sysroot, path.lstrip(os.sep))
003633
     return os.path.join(path, *p)
003633
 
003633
From a43124e1f6217107838eed4d70339d100cbbc77a Mon Sep 17 00:00:00 2001
003633
From: Pavel Moravec <pmoravec@redhat.com>
003633
Date: Wed, 9 Feb 2022 19:45:27 +0100
003633
Subject: [PATCH] [policies] Set fallback to None sysroot
003633
003633
9596473 commit added a regression allowing to set sysroot to None
003633
when running sos report on a regular system (outside a container). In
003633
such a case, we need to fallback to '/' sysroot.
003633
003633
Resolves: #2846
003633
003633
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
003633
---
003633
 sos/policies/distros/__init__.py | 2 +-
003633
 1 file changed, 1 insertion(+), 1 deletion(-)
003633
003633
diff --git a/sos/policies/distros/__init__.py b/sos/policies/distros/__init__.py
003633
index f3c1de11..9048f1c4 100644
003633
--- a/sos/policies/distros/__init__.py
003633
+++ b/sos/policies/distros/__init__.py
003633
@@ -78,7 +78,7 @@ class LinuxPolicy(Policy):
003633
         if sysroot:
003633
             self.sysroot = sysroot
003633
         else:
003633
-            self.sysroot = self._container_init()
003633
+            self.sysroot = self._container_init() or '/'
003633
003633
         self.init_kernel_modules()
003633
003633
-- 
003633
2.34.1
003633