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

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