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,32 @@ 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+ if (
1258+ # check penultimate release component contains an "el*" version
1259+ len (release_parts ) > 1
1260+ and release_parts [- 2 ].startswith ("el" )
1261+ and (
1262+ # CloudLinux < 9 : "lve*" is set in kernel release
1263+ any (rc .startswith ("lve" ) for rc in release_parts )
1264+ # CloudLinux >= 9 : check whether "kmodlve" is loaded
1265+ or "kmodlve" in self ._kernel_modules
1266+ )
1267+ ):
1268+ props ["id" ] = "cloudlinux"
1269+ props ["name" ] = "CloudLinux"
1270+ # strip "el" prefix and replace underscores by dots
1271+ props ["release" ] = release_parts [- 2 ][2 :].replace ("_" , "." )
1272+ return props
12521273
12531274 # This is to prevent the Linux kernel version from
12541275 # appearing as the 'best' version on otherwise
@@ -1257,9 +1278,17 @@ def _parse_uname_content(lines: Sequence[str]) -> Dict[str, str]:
12571278 return {}
12581279 props ["id" ] = name .lower ()
12591280 props ["name" ] = name
1260- props ["release" ] = version
1281+ props ["release" ] = release . split ( "-" )[ 0 ] # only keep version part
12611282 return props
12621283
1284+ @cached_property
1285+ def _kernel_modules (self ) -> List [str ]:
1286+ try :
1287+ with open (os .path .join (self .proc_dir , "modules" ), encoding = "ascii" ) as fp :
1288+ return [line .split ()[0 ] for line in fp ]
1289+ except OSError :
1290+ return []
1291+
12631292 @staticmethod
12641293 def _to_str (bytestring : bytes ) -> str :
12651294 encoding = sys .getfilesystemencoding ()
0 commit comments