From dc2bff9708d7dbb6447ed2c5f28eb9bfd52667a5 Mon Sep 17 00:00:00 2001 From: Jake Hunsaker Date: Fri, 19 Aug 2022 14:26:59 -0400 Subject: [process,PackageManager] Create a mapping of processes to packages Adds a new manual collection to the `process` plugin, that tries to compile a mapping of running processes' binaries to an owning package via the package manager. As such, package managers now have a new `pkg_by_path()` method that serves this purpose. Closes: #1350 Signed-off-by: Jake Hunsaker --- sos/policies/package_managers/__init__.py | 26 +++++++++++++-- sos/policies/package_managers/dpkg.py | 1 + sos/policies/package_managers/rpm.py | 1 + sos/report/plugins/__init__.py | 2 +- sos/report/plugins/process.py | 25 +++++++++++++++ .../plugin_tests/collect_manual_tests.py | 37 ++++++++++++++++++++++ .../plugin_tests/collet_manual_tests.py | 37 ---------------------- 7 files changed, 88 insertions(+), 41 deletions(-) create mode 100644 tests/report_tests/plugin_tests/collect_manual_tests.py delete mode 100644 tests/report_tests/plugin_tests/collet_manual_tests.py diff --git a/sos/policies/package_managers/__init__.py b/sos/policies/package_managers/__init__.py index 8a5c4ee5..f16a57d2 100644 --- a/sos/policies/package_managers/__init__.py +++ b/sos/policies/package_managers/__init__.py @@ -50,12 +50,13 @@ class PackageManager(): verify_command = None verify_filter = None files_command = None + query_path_command = None chroot = None files = None - def __init__(self, chroot=None, query_command=None, - verify_command=None, verify_filter=None, - files_command=None, remote_exec=None): + def __init__(self, chroot=None, query_command=None, verify_command=None, + verify_filter=None, files_command=None, + query_path_command=None, remote_exec=None): self._packages = {} self.files = [] @@ -63,6 +64,7 @@ class PackageManager(): self.verify_command = verify_command or self.verify_command self.verify_filter = verify_filter or self.verify_filter self.files_command = files_command or self.files_command + self.query_path_command = query_path_command or self.query_path_command # if needed, append the remote command to these so that this returns # the remote package details, not local @@ -192,6 +194,24 @@ class PackageManager(): self.files = files.splitlines() return self.files + def pkg_by_path(self, path): + """Given a path, return the package that owns that path. + + :param path: The filepath to check for package ownership + :type path: ``str`` + + :returns: The package name or 'unknown' + :rtype: ``str`` + """ + if not self.query_path_command: + return 'unknown' + try: + cmd = f"{self.query_path_command} {path}" + pkg = shell_out(cmd, timeout=5, chroot=self.chroot) + return pkg.splitlines() or 'unknown' + except Exception: + return 'unknown' + def build_verify_command(self, packages): """build_verify_command(self, packages) -> str Generate a command to verify the list of packages given diff --git a/sos/policies/package_managers/dpkg.py b/sos/policies/package_managers/dpkg.py index 7619e929..684c6f60 100644 --- a/sos/policies/package_managers/dpkg.py +++ b/sos/policies/package_managers/dpkg.py @@ -16,6 +16,7 @@ class DpkgPackageManager(PackageManager): """ query_command = "dpkg-query -W -f='${Package}|${Version}\\n'" + query_path_command = "dpkg -S" verify_command = "dpkg --verify" verify_filter = "" diff --git a/sos/policies/package_managers/rpm.py b/sos/policies/package_managers/rpm.py index 5150e1e0..0d16a1e3 100644 --- a/sos/policies/package_managers/rpm.py +++ b/sos/policies/package_managers/rpm.py @@ -16,6 +16,7 @@ class RpmPackageManager(PackageManager): """ query_command = 'rpm -qa --queryformat "%{NAME}|%{VERSION}|%{RELEASE}\\n"' + query_path_command = 'rpm -qf' files_command = 'rpm -qal' verify_command = 'rpm -V' verify_filter = ["debuginfo", "-devel"] diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py index 42032d50..0fb5dc20 100644 --- a/sos/report/plugins/__init__.py +++ b/sos/report/plugins/__init__.py @@ -3164,7 +3164,7 @@ class Plugin(): self._log_info(f"manual collection '{fname}' finished in {run}") if isinstance(tags, str): tags = [tags] - self.manifest.collections[fname.split('.')[0]] = { + self.manifest.collections[fname] = { 'filepath': _pfname, 'tags': tags } diff --git a/sos/report/plugins/process.py b/sos/report/plugins/process.py index 62705c4f..50279502 100644 --- a/sos/report/plugins/process.py +++ b/sos/report/plugins/process.py @@ -6,6 +6,7 @@ # # See the LICENSE file in the source distribution for further information. +import json import re from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt @@ -88,4 +89,28 @@ class Process(Plugin, IndependentPlugin): "pidstat -p ALL -rudvwsRU --human -h", "pidstat -tl" ]) + + def collect(self): + with self.collection_file('pids_to_packages.json') as pfile: + if not self.policy.package_manager.query_path_command: + pfile.write('Package manager not configured for path queries') + return + _ps = self.exec_cmd('ps --no-headers aex') + pidpkg = {} + paths = {} + if not _ps['status'] == 0: + pfile.write(f"Unable to get process list: {_ps['output']}") + return + for proc in _ps['output'].splitlines(): + proc = proc.strip().split() + pid = proc[0] + path = proc[4] + if not self.path_exists(path): + continue + if path not in paths: + paths[path] = self.policy.package_manager.pkg_by_path(path) + pidpkg[pid] = {'path': path, 'package': paths[path]} + + pfile.write(json.dumps(pidpkg, indent=4)) + # vim: set et ts=4 sw=4 : diff --git a/tests/report_tests/plugin_tests/collect_manual_tests.py b/tests/report_tests/plugin_tests/collect_manual_tests.py new file mode 100644 index 00000000..7000d5e0 --- /dev/null +++ b/tests/report_tests/plugin_tests/collect_manual_tests.py @@ -0,0 +1,37 @@ +# This file is part of the sos project: https://github.com/sosreport/sos +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# version 2 of the GNU General Public License. +# +# See the LICENSE file in the source distribution for further information. + + +from sos_tests import StageOneReportTest + + +class CollectManualTest(StageOneReportTest): + """Test to ensure that collect() is working for plugins that + directly call it as part of their collections + + :avocado: tags=stageone + """ + + sos_cmd = '-o unpackaged,python -k python.hashes' + # unpackaged is only a RedHatPlugin + redhat_only = True + + def test_unpackaged_list_collected(self): + self.assertFileCollected('sos_commands/unpackaged/unpackaged') + + def test_python_hashes_collected(self): + self.assertFileCollected('sos_commands/python/digests.json') + + def test_no_strings_dir(self): + self.assertFileNotCollected('sos_strings/') + + def test_manifest_collections_correct(self): + pkgman = self.get_plugin_manifest('unpackaged') + self.assertTrue(pkgman['collections']['unpackaged']) + pyman = self.get_plugin_manifest('python') + self.assertTrue(pyman['collections']['digests.json']) diff --git a/tests/report_tests/plugin_tests/collet_manual_tests.py b/tests/report_tests/plugin_tests/collet_manual_tests.py deleted file mode 100644 index fdcda526..00000000 --- a/tests/report_tests/plugin_tests/collet_manual_tests.py +++ /dev/null @@ -1,37 +0,0 @@ -# This file is part of the sos project: https://github.com/sosreport/sos -# -# This copyrighted material is made available to anyone wishing to use, -# modify, copy, or redistribute it subject to the terms and conditions of -# version 2 of the GNU General Public License. -# -# See the LICENSE file in the source distribution for further information. - - -from sos_tests import StageOneReportTest - - -class CollectManualTest(StageOneReportTest): - """Test to ensure that collect() is working for plugins that - directly call it as part of their collections - - :avocado: tags=stageone - """ - - sos_cmd = '-o unpackaged,python -k python.hashes' - # unpackaged is only a RedHatPlugin - redhat_only = True - - def test_unpackaged_list_collected(self): - self.assertFileCollected('sos_commands/unpackaged/unpackaged') - - def test_python_hashes_collected(self): - self.assertFileCollected('sos_commands/python/digests.json') - - def test_no_strings_dir(self): - self.assertFileNotCollected('sos_strings/') - - def test_manifest_collections_correct(self): - pkgman = self.get_plugin_manifest('unpackaged') - self.assertTrue(pkgman['collections']['unpackaged']) - pyman = self.get_plugin_manifest('python') - self.assertTrue(pyman['collections']['digests']) -- cgit