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 )
@@ -1241,14 +1244,19 @@ def _armbian_version(self) -> str:
12411244 except FileNotFoundError :
12421245 return ""
12431246
1244- @staticmethod
1245- def _parse_uname_content (lines : Sequence [str ]) -> Dict [str , str ]:
1247+ def _parse_uname_content (self , lines : Sequence [str ]) -> Dict [str , str ]:
12461248 if not lines :
12471249 return {}
12481250 props = {}
1249- match = re .search (r"^([^\s]+)\s+([\d\. ]+)" , lines [0 ].strip ())
1251+ match = re .search (r"^([^\s]+)\s+([^\s ]+)" , lines [0 ].strip ())
12501252 if match :
1251- name , version = match .groups ()
1253+ name , release = match .groups ()
1254+
1255+ # CloudLinux detection relies on uname release information
1256+ release_parts = release .split ("." )
1257+ props = self ._cloudlinux_detection (release_parts )
1258+ if props :
1259+ return props
12521260
12531261 # This is to prevent the Linux kernel version from
12541262 # appearing as the 'best' version on otherwise
@@ -1257,9 +1265,38 @@ def _parse_uname_content(lines: Sequence[str]) -> Dict[str, str]:
12571265 return {}
12581266 props ["id" ] = name .lower ()
12591267 props ["name" ] = name
1260- props ["release" ] = version
1268+ props ["release" ] = release . split ( "-" )[ 0 ] # only keep version part
12611269 return props
12621270
1271+ def _cloudlinux_detection (self , release_parts : List [str ]) -> Dict [str , str ]:
1272+ if (
1273+ # check penultimate release component contains an "el*" version
1274+ len (release_parts ) > 1
1275+ and release_parts [- 2 ].startswith ("el" )
1276+ and (
1277+ # CloudLinux < 9 : "lve*" is set in kernel release
1278+ any (rc .startswith ("lve" ) for rc in release_parts )
1279+ # CloudLinux >= 9 : check whether "kmodlve" is loaded
1280+ or "kmodlve" in self ._kernel_modules
1281+ )
1282+ ):
1283+ return {
1284+ "id" : "cloudlinux" ,
1285+ "name" : "CloudLinux" ,
1286+ # strip "el" prefix and replace underscores by dots
1287+ "release" : release_parts [- 2 ][2 :].replace ("_" , "." ),
1288+ }
1289+
1290+ return {}
1291+
1292+ @cached_property
1293+ def _kernel_modules (self ) -> List [str ]:
1294+ try :
1295+ with open (os .path .join (self .proc_dir , "modules" ), encoding = "ascii" ) as fp :
1296+ return [line .split ()[0 ] for line in fp ]
1297+ except OSError :
1298+ return []
1299+
12631300 @staticmethod
12641301 def _to_str (bytestring : bytes ) -> str :
12651302 encoding = sys .getfilesystemencoding ()
0 commit comments