1
0
mirror of https://github.com/HorlogeSkynet/archey4 synced 2025-06-04 04:00:13 +02:00

[CORE] Prevents inconsistent PATH from breaking subprocess executions

It appears "broken" `PATH` environment variable (e.g. ending with a
file instead of a directory) may lead to `NotADirectoryError` Python
exception. Let's get bulletproof by extending most of subprocess calls
expected exceptions to parent `OSError` (or at least by actually
catching `NotADirectoryError` explicitly).

> closes #164

Co-Authored-By: Michael Bromilow <developer@bromilow.uk>
This commit is contained in:
Samuel FORESTIER 2025-02-26 22:58:04 +01:00
parent c3018baac6
commit e45ff7bbc5
13 changed files with 30 additions and 27 deletions

@ -2,7 +2,7 @@
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project (partially) adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
@ -14,6 +14,9 @@ and this project (partially) adheres to [Semantic Versioning](https://semver.org
### Changed
- `Entry` behavior in boolean contexts ("truthy" when `value` is populated)
### Fixed
- Sub-process execution failure when `PATH` contains an invalid component
## [v4.15.0.0] - 2024-09-30
### Added
- `GPU` support for Raspberry Pi

@ -101,7 +101,7 @@ class CPU(Entry):
"""Same operation but from `lscpu` output"""
try:
cpu_info = check_output("lscpu", env={"LANG": "C"}, universal_newlines=True)
except FileNotFoundError:
except OSError:
return []
nb_threads = cls._THREADS_PER_CORE_REGEXP.findall(cpu_info)
@ -129,7 +129,7 @@ class CPU(Entry):
stderr=DEVNULL,
universal_newlines=True,
)
except (FileNotFoundError, CalledProcessError):
except (OSError, CalledProcessError):
# `-json` is not available before Catalina.
return []
@ -165,7 +165,7 @@ class CPU(Entry):
stderr=DEVNULL,
universal_newlines=True,
)
except (FileNotFoundError, CalledProcessError):
except (OSError, CalledProcessError):
return []
# `sysctl_output` should exactly contains two lines.
@ -179,7 +179,7 @@ class CPU(Entry):
sysctl_output = check_output(
["sysctl", "-n", "hw.model", "hw.ncpu"], stderr=DEVNULL, universal_newlines=True
)
except (FileNotFoundError, CalledProcessError):
except (OSError, CalledProcessError):
return []
# `sysctl_output` should exactly contains two lines.

@ -71,7 +71,7 @@ class Disk(Entry):
# Call `diskutil` to generate a property list (PList) of all APFS containers
try:
property_list = plistlib.loads(check_output(["diskutil", "apfs", "list", "-plist"]))
except FileNotFoundError:
except OSError:
self._logger.warning(
"APFS volumes cannot be deduplicated as diskutil program could not be found."
)
@ -186,7 +186,7 @@ class Disk(Entry):
check=False,
encoding="utf-8",
).stdout
except FileNotFoundError:
except OSError:
# `df` isn't available on this system.
return {}

@ -30,7 +30,7 @@ class Distro(Entry):
release = check_output(
["getprop", "ro.build.version.release"], universal_newlines=True
).rstrip()
except FileNotFoundError:
except OSError:
return None
return f"Android {release}"

@ -49,7 +49,7 @@ class GPU(Entry):
"""Based on `lspci` output, return a list of video controllers names"""
try:
lspci_output = check_output(["lspci", "-m"], universal_newlines=True).splitlines()
except (FileNotFoundError, CalledProcessError):
except (OSError, CalledProcessError):
return []
gpus_list = []
@ -122,7 +122,7 @@ class GPU(Entry):
profiler_output = check_output(
["system_profiler", "SPDisplaysDataType"], stderr=DEVNULL, universal_newlines=True
)
except FileNotFoundError:
except OSError:
return []
return re.findall(r"Chipset Model: (.*)", profiler_output, re.MULTILINE)
@ -134,7 +134,7 @@ class GPU(Entry):
pciconf_output = check_output(
["pciconf", "-lv"], stderr=DEVNULL, universal_newlines=True
)
except (FileNotFoundError, CalledProcessError):
except (OSError, CalledProcessError):
return []
gpus_list = []

@ -60,7 +60,7 @@ class Model(Entry):
except CalledProcessError:
# Not a virtual environment.
return None
except FileNotFoundError:
except OSError:
# If not available, let's query `virt-what` (privileges usually required).
try:
return (
@ -130,7 +130,7 @@ class Model(Entry):
model = check_output(
["sysctl", "-n", "hw.model"], stderr=DEVNULL, universal_newlines=True
)
except FileNotFoundError:
except OSError:
return None
except CalledProcessError:
pass
@ -144,7 +144,7 @@ class Model(Entry):
sysctl_output = check_output(
["sysctl", "-n", f"hw.{hw_oid}"], stderr=DEVNULL, universal_newlines=True
)
except FileNotFoundError:
except OSError:
return None
except CalledProcessError:
pass
@ -189,7 +189,7 @@ class Model(Entry):
try:
brand = check_output(["getprop", "ro.product.brand"], universal_newlines=True).rstrip()
model = check_output(["getprop", "ro.product.model"], universal_newlines=True).rstrip()
except FileNotFoundError:
except OSError:
return None
return f"{brand} ({model})"
@ -202,7 +202,7 @@ class Model(Entry):
product = check_output(
["kenv", "smbios.system.version"], universal_newlines=True
).rstrip()
except (FileNotFoundError, CalledProcessError):
except (OSError, CalledProcessError):
return None
return f"{vendor} ({product})"

@ -39,14 +39,14 @@ class RAM(Entry):
Tries a variety of methods, increasing compatibility for a wide range of systems.
"""
if platform.system() == "Linux":
with suppress(IndexError, FileNotFoundError):
with suppress(IndexError, OSError):
return self._run_free_dash_m()
elif platform.system() == "FreeBSD":
with suppress(FileNotFoundError):
with suppress(OSError):
return self._run_sysctl_mem()
else:
# Darwin or any other BSD-based system.
with suppress(FileNotFoundError):
with suppress(OSError):
return self._run_sysctl_and_vmstat()
with suppress(OSError):

@ -82,7 +82,7 @@ class Temperature(Entry):
sensors_output = run(
sensors_args, universal_newlines=True, stdout=PIPE, stderr=PIPE, check=True
)
except FileNotFoundError:
except OSError:
return None
except CalledProcessError as called_process_error:
error_message = called_process_error.stderr
@ -187,7 +187,7 @@ class Temperature(Entry):
stderr=PIPE,
universal_newlines=True,
)
except FileNotFoundError:
except OSError:
# `sysctl` does not seem to be available on this system.
return
except CalledProcessError as error_message:

@ -72,7 +72,7 @@ class Uptime(Entry):
"""Tries to get uptime by parsing the `uptime` command"""
try:
uptime_output = run("uptime", env={"LANG": "C"}, stdout=PIPE, stderr=PIPE, check=True)
except FileNotFoundError as error:
except (FileNotFoundError, NotADirectoryError) as error:
raise ArcheyException("Couldn't find `uptime` command on this system.") from error
# Log any `uptime` error messages at warning level.

@ -82,7 +82,7 @@ class WanIP(Entry):
stderr=DEVNULL,
universal_newlines=True,
).rstrip()
except (FileNotFoundError, TimeoutExpired, CalledProcessError):
except (OSError, TimeoutExpired, CalledProcessError):
return None
# `ip_address` might be empty here.

@ -76,7 +76,7 @@ class WindowManager(Entry):
r"(?<=Name: ).*",
check_output(["wmctrl", "-m"], stderr=DEVNULL, universal_newlines=True),
).group(0)
except (FileNotFoundError, CalledProcessError):
except (OSError, CalledProcessError):
processes = Processes().list
for wm_id, wm_name in WM_DICT.items():
if wm_id in processes:

@ -15,9 +15,9 @@ class Processes(metaclass=Singleton):
try:
ps_output = check_output(["ps", "-eo", "comm"], stderr=PIPE, universal_newlines=True)
except FileNotFoundError:
except OSError as os_error:
self._processes = []
logging.warning("`procps` (or `procps-ng`) couldn't be found on your system.")
logging.warning("`ps` failed or `procps`/`procps-ng` isn't installed : %s", os_error)
except CalledProcessError as process_error:
self._processes = []
logging.warning(

@ -232,7 +232,7 @@ class TestTemperatureEntry(unittest.TestCase, CustomAssertions):
# Second case (`iStats` OK).
"41.125\n",
# Third case (`iStats` KO, `OSX CPU Temp` OK).
FileNotFoundError(),
OSError(),
"61.8 °C\n",
# Fourth case (`OSX CPU Temp` KO, but with <= 1.1.0 output).
# See lavoiesl/osx-cpu-temp#22.