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

1317 lines
54 KiB
Diff
Raw Normal View History

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