|
42 | 42 | Callable, |
43 | 43 | Dict, |
44 | 44 | Iterable, |
| 45 | + List, |
45 | 46 | Optional, |
46 | 47 | Sequence, |
47 | 48 | TextIO, |
@@ -73,6 +74,7 @@ class InfoDict(TypedDict): |
73 | 74 |
|
74 | 75 |
|
75 | 76 | _UNIXCONFDIR = os.environ.get("UNIXCONFDIR", "/etc") |
| 77 | +_UNIXPROCDIR = os.environ.get("UNIXPROCDIR", "/proc") |
76 | 78 | _UNIXUSRLIBDIR = os.environ.get("UNIXUSRLIBDIR", "/usr/lib") |
77 | 79 | _OS_RELEASE_BASENAME = "os-release" |
78 | 80 |
|
@@ -783,6 +785,7 @@ def __init__( |
783 | 785 | """ |
784 | 786 | self.root_dir = root_dir |
785 | 787 | self.etc_dir = os.path.join(root_dir, "etc") if root_dir else _UNIXCONFDIR |
| 788 | + self.proc_dir = os.path.join(root_dir, "proc") if root_dir else _UNIXPROCDIR |
786 | 789 | self.usr_lib_dir = ( |
787 | 790 | os.path.join(root_dir, "usr/lib") if root_dir else _UNIXUSRLIBDIR |
788 | 791 | ) |
@@ -1302,25 +1305,55 @@ def _armbian_version(self) -> str: |
1302 | 1305 | except FileNotFoundError: |
1303 | 1306 | return "" |
1304 | 1307 |
|
1305 | | - @staticmethod |
1306 | | - def _parse_uname_content(lines: Sequence[str]) -> Dict[str, str]: |
| 1308 | + def _parse_uname_content(self, lines: Sequence[str]) -> Dict[str, str]: |
1307 | 1309 | if not lines: |
1308 | 1310 | return {} |
1309 | 1311 | props = {} |
1310 | | - match = re.search(r"^([^\s]+)\s+([\d\.]+)", lines[0].strip()) |
| 1312 | + match = re.search(r"^([^\s]+)\s+([^\s]+)", lines[0].strip()) |
1311 | 1313 | if match: |
1312 | | - name, version = match.groups() |
| 1314 | + name, release = match.groups() |
1313 | 1315 |
|
1314 | 1316 | # This is to prevent the Linux kernel version from |
1315 | 1317 | # appearing as the 'best' version on otherwise |
1316 | 1318 | # identifiable distributions. |
1317 | 1319 | if name == "Linux": |
1318 | | - return {} |
| 1320 | + # Attempt various OS detection based on uname release information |
| 1321 | + return self._cloudlinux_detection(release.split(".")) |
| 1322 | + |
1319 | 1323 | props["id"] = name.lower() |
1320 | 1324 | props["name"] = name |
1321 | | - props["release"] = version |
| 1325 | + props["release"] = release.split("-")[0] # only keep version part |
1322 | 1326 | return props |
1323 | 1327 |
|
| 1328 | + def _cloudlinux_detection(self, release_parts: List[str]) -> Dict[str, str]: |
| 1329 | + if ( |
| 1330 | + # check penultimate release component contains an "el*" version |
| 1331 | + len(release_parts) > 1 |
| 1332 | + and release_parts[-2].startswith("el") |
| 1333 | + and ( |
| 1334 | + # CloudLinux < 9 : "lve*" is set in kernel release |
| 1335 | + any(rc.startswith("lve") for rc in release_parts) |
| 1336 | + # CloudLinux >= 9 : check whether "kmodlve" is loaded |
| 1337 | + or "kmodlve" in self._kernel_modules |
| 1338 | + ) |
| 1339 | + ): |
| 1340 | + return { |
| 1341 | + "id": "cloudlinux", |
| 1342 | + "name": "CloudLinux", |
| 1343 | + # strip "el" prefix and replace underscores by dots |
| 1344 | + "release": release_parts[-2][2:].replace("_", "."), |
| 1345 | + } |
| 1346 | + |
| 1347 | + return {} |
| 1348 | + |
| 1349 | + @cached_property |
| 1350 | + def _kernel_modules(self) -> List[str]: |
| 1351 | + try: |
| 1352 | + with open(os.path.join(self.proc_dir, "modules"), encoding="ascii") as fp: |
| 1353 | + return [line.split()[0] for line in fp] |
| 1354 | + except OSError: |
| 1355 | + return [] |
| 1356 | + |
1324 | 1357 | @staticmethod |
1325 | 1358 | def _to_str(bytestring: bytes) -> str: |
1326 | 1359 | encoding = sys.getfilesystemencoding() |
|
0 commit comments