4242 Callable ,
4343 Dict ,
4444 Iterable ,
45+ List ,
4546 Optional ,
4647 Sequence ,
4748 TextIO ,
@@ -73,6 +74,7 @@ class InfoDict(TypedDict):
7374
7475
7576_UNIXCONFDIR = os .environ .get ("UNIXCONFDIR" , "/etc" )
77+ _UNIXPROCDIR = os .environ .get ("UNIXPROCDIR" , "/proc" )
7678_UNIXUSRLIBDIR = os .environ .get ("UNIXUSRLIBDIR" , "/usr/lib" )
7779_OS_RELEASE_BASENAME = "os-release"
7880
@@ -759,6 +761,7 @@ def __init__(
759761 """
760762 self .root_dir = root_dir
761763 self .etc_dir = os .path .join (root_dir , "etc" ) if root_dir else _UNIXCONFDIR
764+ self .proc_dir = os .path .join (root_dir , "proc" ) if root_dir else _UNIXPROCDIR
762765 self .usr_lib_dir = (
763766 os .path .join (root_dir , "usr/lib" ) if root_dir else _UNIXUSRLIBDIR
764767 )
@@ -1239,14 +1242,32 @@ def _armbian_version(self) -> str:
12391242 except FileNotFoundError :
12401243 return ""
12411244
1242- @staticmethod
1243- def _parse_uname_content (lines : Sequence [str ]) -> Dict [str , str ]:
1245+ def _parse_uname_content (self , lines : Sequence [str ]) -> Dict [str , str ]:
12441246 if not lines :
12451247 return {}
12461248 props = {}
1247- match = re .search (r"^([^\s]+)\s+([\d\. ]+)" , lines [0 ].strip ())
1249+ match = re .search (r"^([^\s]+)\s+([^\s ]+)" , lines [0 ].strip ())
12481250 if match :
1249- name , version = match .groups ()
1251+ name , release = match .groups ()
1252+
1253+ # CloudLinux detection relies on uname release information
1254+ release_parts = release .split ("." )
1255+ if (
1256+ # check penultimate release component contains an "el*" version
1257+ len (release_parts ) > 1
1258+ and release_parts [- 2 ].startswith ("el" )
1259+ and (
1260+ # CloudLinux < 9 : "lve*" is set in kernel release
1261+ any (rc .startswith ("lve" ) for rc in release_parts )
1262+ # CloudLinux >= 9 : check whether "kmodlve" is loaded
1263+ or "kmodlve" in self ._loaded_modules
1264+ )
1265+ ):
1266+ props ["id" ] = "cloudlinux"
1267+ props ["name" ] = "CloudLinux"
1268+ # strip "el" prefix and replace underscores by dots
1269+ props ["release" ] = release_parts [- 2 ][2 :].replace ("_" , "." )
1270+ return props
12501271
12511272 # This is to prevent the Linux kernel version from
12521273 # appearing as the 'best' version on otherwise
@@ -1255,9 +1276,17 @@ def _parse_uname_content(lines: Sequence[str]) -> Dict[str, str]:
12551276 return {}
12561277 props ["id" ] = name .lower ()
12571278 props ["name" ] = name
1258- props ["release" ] = version
1279+ props ["release" ] = release . split ( "-" )[ 0 ] # only keep version part
12591280 return props
12601281
1282+ @cached_property
1283+ def _loaded_modules (self ) -> List [str ]:
1284+ try :
1285+ with open (os .path .join (self .proc_dir , "modules" ), encoding = "ascii" ) as fp :
1286+ return [line .split ()[0 ] for line in fp ]
1287+ except OSError :
1288+ return []
1289+
12611290 @staticmethod
12621291 def _to_str (bytestring : bytes ) -> str :
12631292 encoding = sys .getfilesystemencoding ()
0 commit comments