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

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