2020import fnmatch
2121import re
2222from types import GeneratorType
23+ import pickle
2324
2425__copyright__ = "(C) 2016-2025 Guido U. Draheim, licensed under the EUPL"
2526__version__ = "1.5.9063"
@@ -1351,6 +1352,7 @@ def __init__(self):
13511352 self ._loaded_file_sysd = {} # /etc/systemd/system/name.service => config data
13521353 self ._file_for_unit_sysv = None # name.service => /etc/init.d/name
13531354 self ._file_for_unit_sysd = None # name.service => /etc/systemd/system/name.service
1355+ self ._sysd_alias = None #map od aliases of services (aliased => realservice)
13541356 self ._preset_file_list = None # /etc/systemd/system-preset/* => file content
13551357 self ._default_target = DefaultTarget
13561358 self ._sysinit_target = None # stores a UnitConf()
@@ -1435,6 +1437,7 @@ def scan_unit_sysd_files(self, module = None): # -> [ unit-names,... ]
14351437 """ reads all unit files, returns the first filename for the unit given """
14361438 if self ._file_for_unit_sysd is None :
14371439 self ._file_for_unit_sysd = {}
1440+ self ._sysd_alias = {}
14381441 for folder in self .sysd_folders ():
14391442 if not folder :
14401443 continue
@@ -1448,6 +1451,12 @@ def scan_unit_sysd_files(self, module = None): # -> [ unit-names,... ]
14481451 service_name = name
14491452 if service_name not in self ._file_for_unit_sysd :
14501453 self ._file_for_unit_sysd [service_name ] = path
1454+ if os .path .islink (path ):
1455+ path_target = os .readlink (path )
1456+ if path_target .endswith (".service" ):
1457+ service_name_target = os .path .basename (path_target )
1458+ self ._sysd_alias [service_name ] = service_name_target
1459+ logg .debug ("alias found %s => %s" , service_name , service_name_target )
14511460 logg .debug ("found %s sysd files" , len (self ._file_for_unit_sysd ))
14521461 return list (self ._file_for_unit_sysd .keys ())
14531462 def scan_unit_sysv_files (self , module = None ): # -> [ unit-names,... ]
@@ -1469,10 +1478,18 @@ def scan_unit_sysv_files(self, module = None): # -> [ unit-names,... ]
14691478 self ._file_for_unit_sysv [service_name ] = path
14701479 logg .debug ("found %s sysv files" , len (self ._file_for_unit_sysv ))
14711480 return list (self ._file_for_unit_sysv .keys ())
1481+ def _getRealModuleName (self , module ):
1482+ if module is None :
1483+ return None
1484+ if self ._sysd_alias is None :
1485+ self .scan_unit_sysd_files ()
1486+ assert self ._sysd_alias is not None
1487+ return module if not module in self ._sysd_alias else self ._sysd_alias [module ]
14721488 def unit_sysd_file (self , module = None ): # -> filename?
14731489 """ file path for the given module (systemd) """
14741490 self .scan_unit_sysd_files ()
14751491 assert self ._file_for_unit_sysd is not None
1492+ module = self ._getRealModuleName (module )
14761493 if module and module in self ._file_for_unit_sysd :
14771494 return self ._file_for_unit_sysd [module ]
14781495 if module and unit_of (module ) in self ._file_for_unit_sysd :
@@ -1575,7 +1592,9 @@ def load_sysd_unit_conf(self, module): # -> conf?
15751592 for name in sorted (drop_in_files ):
15761593 path = drop_in_files [name ]
15771594 data .read_sysd (path )
1578- conf = SystemctlConf (data , module )
1595+ assert self ._sysd_alias is not None
1596+ module_real = module if not module in self ._sysd_alias else self ._sysd_alias [module ]
1597+ conf = SystemctlConf (data , module_real )
15791598 conf .masked = masked
15801599 conf .nonloaded_path = path # if masked
15811600 conf .drop_in_files = drop_in_files
@@ -1816,6 +1835,20 @@ def list_unit_files_modules(self, *modules): # -> [ (unit,enabled) ]
18161835 else :
18171836 result = self .list_target_unit_files ()
18181837 result += self .list_service_unit_files (* modules )
1838+
1839+ filterString = modules [0 ] if len (modules ) >= 1 else None
1840+ if filterString :
1841+ result = [
1842+ service for service in result
1843+ if fnmatch .fnmatch (service [0 ], filterString )
1844+ ]
1845+ if len (self ._only_state ) > 0 :
1846+ result = [
1847+ service for service in result
1848+ if service [1 ] in self ._only_state
1849+ ]
1850+
1851+
18191852 if self ._no_legend :
18201853 return result
18211854 found = "%s unit files listed." % len (result )
@@ -2991,8 +3024,10 @@ def do_start_service_from(self, conf):
29913024 exe , newcmd = self .exec_newcmd (cmd , env , conf )
29923025 logg .info (" pre-start %s" , shell_cmd (newcmd ))
29933026 forkpid = os .fork ()
2994- if not forkpid :
2995- self .execve_from (conf , newcmd , env ) # pragma: no cover
3027+ if not forkpid :
3028+ permissionsStartOnly = self .get_PermissionsStartOnly (conf )
3029+ runAsRoot = permissionsStartOnly
3030+ self .execve_from (conf , newcmd , env , runAsRoot ) # pragma: no cover
29963031 run = subprocess_waitpid (forkpid )
29973032 logg .debug (" pre-start done (%s) <-%s>" ,
29983033 run .returncode or "OK" , run .signal or "" )
@@ -3167,7 +3202,9 @@ def do_start_service_from(self, conf):
31673202 logg .info ("post-fail %s" , shell_cmd (newcmd ))
31683203 forkpid = os .fork ()
31693204 if not forkpid :
3170- self .execve_from (conf , newcmd , env ) # pragma: no cover
3205+ permissionsStartOnly = self .get_PermissionsStartOnly (conf )
3206+ runAsRoot = permissionsStartOnly
3207+ self .execve_from (conf , newcmd , env , runAsRoot ) # pragma: no cover
31713208 run = subprocess_waitpid (forkpid )
31723209 logg .debug ("post-fail done (%s) <-%s>" ,
31733210 run .returncode or "OK" , run .signal or "" )
@@ -3180,7 +3217,9 @@ def do_start_service_from(self, conf):
31803217 logg .info ("post-start %s" , shell_cmd (newcmd ))
31813218 forkpid = os .fork ()
31823219 if not forkpid :
3183- self .execve_from (conf , newcmd , env ) # pragma: no cover
3220+ permissionsStartOnly = self .get_PermissionsStartOnly (conf )
3221+ runAsRoot = permissionsStartOnly
3222+ self .execve_from (conf , newcmd , env , runAsRoot ) # pragma: no cover
31843223 run = subprocess_waitpid (forkpid )
31853224 logg .debug ("post-start done (%s) <-%s>" ,
31863225 run .returncode or "OK" , run .signal or "" )
@@ -3288,8 +3327,10 @@ def do_start_socket_from(self, conf):
32883327 exe , newcmd = self .exec_newcmd (cmd , env , conf )
32893328 logg .info (" pre-start %s" , shell_cmd (newcmd ))
32903329 forkpid = os .fork ()
3291- if not forkpid :
3292- self .execve_from (conf , newcmd , env ) # pragma: no cover
3330+ if not forkpid :
3331+ permissionsStartOnly = self .get_PermissionsStartOnly (conf )
3332+ runAsRoot = permissionsStartOnly
3333+ self .execve_from (conf , newcmd , env , runAsRoot ) # pragma: no cover
32933334 run = subprocess_waitpid (forkpid )
32943335 logg .debug (" pre-start done (%s) <-%s>" ,
32953336 run .returncode or "OK" , run .signal or "" )
@@ -3329,7 +3370,9 @@ def do_start_socket_from(self, conf):
33293370 logg .info ("post-fail %s" , shell_cmd (newcmd ))
33303371 forkpid = os .fork ()
33313372 if not forkpid :
3332- self .execve_from (conf , newcmd , env ) # pragma: no cover
3373+ permissionsStartOnly = self .get_PermissionsStartOnly (conf )
3374+ runAsRoot = permissionsStartOnly
3375+ self .execve_from (conf , newcmd , env , runAsRoot ) # pragma: no cover
33333376 run = subprocess_waitpid (forkpid )
33343377 logg .debug ("post-fail done (%s) <-%s>" ,
33353378 run .returncode or "OK" , run .signal or "" )
@@ -3340,7 +3383,9 @@ def do_start_socket_from(self, conf):
33403383 logg .info ("post-start %s" , shell_cmd (newcmd ))
33413384 forkpid = os .fork ()
33423385 if not forkpid :
3343- self .execve_from (conf , newcmd , env ) # pragma: no cover
3386+ permissionsStartOnly = self .get_PermissionsStartOnly (conf )
3387+ runAsRoot = permissionsStartOnly
3388+ self .execve_from (conf , newcmd , env , runAsRoot ) # pragma: no cover
33443389 run = subprocess_waitpid (forkpid )
33453390 logg .debug ("post-start done (%s) <-%s>" ,
33463391 run .returncode or "OK" , run .signal or "" )
@@ -3478,6 +3523,8 @@ def expand_list(self, group_lines, conf):
34783523 if item :
34793524 result .append (self .expand_special (item , conf ))
34803525 return result
3526+ def get_PermissionsStartOnly (self , conf ):
3527+ return conf .getbool ("Service" , "PermissionsStartOnly" , "no" )
34813528 def get_User (self , conf ):
34823529 return self .expand_special (conf .get (Service , "User" , "" ), conf )
34833530 def get_Group (self , conf ):
@@ -3565,7 +3612,7 @@ def dup2_journal_log(self, conf):
35653612 os .dup2 (inp .fileno (), sys .stdin .fileno ())
35663613 os .dup2 (out .fileno (), sys .stdout .fileno ())
35673614 os .dup2 (err .fileno (), sys .stderr .fileno ())
3568- def execve_from (self , conf , cmd , env ):
3615+ def execve_from (self , conf , cmd , env , runAsRoot = False ):
35693616 """ this code is commonly run in a child process // returns exit-code"""
35703617 # |
35713618 runs = conf .get (Service , "Type" , "simple" ).lower ()
@@ -3575,6 +3622,10 @@ def execve_from(self, conf, cmd, env):
35753622 #
35763623 runuser = self .get_User (conf )
35773624 rungroup = self .get_Group (conf )
3625+ if runAsRoot is True :
3626+ runuser = "root"
3627+ rungroup = "root"
3628+ """ logg.debug("Executing as %s:%s", runuser, rungroup)"""
35783629 xgroups = self .get_SupplementaryGroups (conf )
35793630 envs = shutil_setuid (runuser , rungroup , xgroups )
35803631 badpath = self .chdir_workingdir (conf ) # some dirs need setuid before
@@ -3774,7 +3825,9 @@ def do_stop_service_from(self, conf):
37743825 logg .info ("post-stop %s" , shell_cmd (newcmd ))
37753826 forkpid = os .fork ()
37763827 if not forkpid :
3777- self .execve_from (conf , newcmd , env ) # pragma: no cover
3828+ permissionsStartOnly = self .get_PermissionsStartOnly (conf )
3829+ runAsRoot = permissionsStartOnly
3830+ self .execve_from (conf , newcmd , env , runAsRoot ) # pragma: no cover
37783831 run = subprocess_waitpid (forkpid )
37793832 logg .debug ("post-stop done (%s) <-%s>" ,
37803833 run .returncode or "OK" , run .signal or "" )
@@ -3812,7 +3865,9 @@ def do_stop_socket_from(self, conf):
38123865 logg .info ("post-stop %s" , shell_cmd (newcmd ))
38133866 forkpid = os .fork ()
38143867 if not forkpid :
3815- self .execve_from (conf , newcmd , env ) # pragma: no cover
3868+ permissionsStartOnly = self .get_PermissionsStartOnly (conf )
3869+ runAsRoot = permissionsStartOnly
3870+ self .execve_from (conf , newcmd , env , runAsRoot ) # pragma: no cover
38163871 run = subprocess_waitpid (forkpid )
38173872 logg .debug ("post-stop done (%s) <-%s>" ,
38183873 run .returncode or "OK" , run .signal or "" )
@@ -5036,7 +5091,12 @@ def unmask_unit(self, unit):
50365091 _f = self ._force and "-f" or ""
50375092 logg .info ("rm {_f} '{target}'" .format (** locals ()))
50385093 if os .path .islink (target ):
5039- os .remove (target )
5094+ link_target = os .readlink (target )
5095+ if link_target == _dev_null :
5096+ os .remove (target )
5097+ logg .info ("Unit %s was masked and has been unmasked." , unit )
5098+ else :
5099+ logg .info ("Unit %s is not masked (symlink points to %s); nothing to do." )
50405100 return True
50415101 elif not os .path .exists (target ):
50425102 logg .debug ("Symlink did not exist anymore: %s" , target )
@@ -6451,6 +6511,50 @@ def version_info(self):
64516511 return [self .systemd_version (), self .systemd_features ()]
64526512 def test_float (self ):
64536513 return 0. # "Unknown result type"
6514+ def getEnvVarsFilePath (self ):
6515+ return '/run/systemd/systemd.envs'
6516+ def getEnvVars (self ):
6517+ fp = self .getEnvVarsFilePath ()
6518+ vars = {}
6519+ if os .path .isfile (fp ):
6520+ with open (fp , 'rb' ) as f :
6521+ vars = pickle .load (f )
6522+ return vars
6523+ def setEnvVar (self , varName , varValue = None ):
6524+ vars = self .getEnvVars ()
6525+ if varValue is None :
6526+ if varName in vars :
6527+ del vars [varName ]
6528+ else :
6529+ vars [varName ] = varValue
6530+ with open (self .getEnvVarsFilePath (), 'wb' ) as f :
6531+ pickle .dump (vars , f )
6532+ def get_environment_modules (self , * args ):
6533+ if len (args ) == 0 :
6534+ return 1
6535+ varName = args [0 ]
6536+ vars = self .getEnvVars ()
6537+ if varName in vars :
6538+ return vars [varName ]
6539+ return ''
6540+ def set_environment_modules (self , * args ):
6541+ if len (args ) == 0 :
6542+ return 1
6543+ boom = args [0 ].split ('=' , 2 )
6544+ if len (boom ) != 2 :
6545+ return 2
6546+ varName = boom [0 ]
6547+ varValue = boom [1 ]
6548+ logg .debug ("Set env variable %s to \" %s\" " , varName , varValue )
6549+ self .setEnvVar (varName , varValue )
6550+ return 0
6551+ def unset_environment_modules (self , * args ):
6552+ if len (args ) == 0 :
6553+ return 1
6554+ varName = args [0 ]
6555+ logg .debug ("Unset env variable %s" , varName )
6556+ self .setEnvVar (varName )
6557+ return 0
64546558
64556559def print_begin (argv , args ):
64566560 script = os .path .realpath (argv [0 ])
@@ -6560,6 +6664,15 @@ def runcommand(command, *modules):
65606664 exitcode = is_not_ok (systemctl .enable_modules (* modules ))
65616665 elif command in ["environment" ]:
65626666 print_str_dict (systemctl .environment_of_unit (* modules ))
6667+ elif command in ["get-environment" ]:
6668+ result = systemctl .get_environment_modules (* modules )
6669+ assert result is not None
6670+ if isinstance (result , int ): exitcode = result
6671+ elif isinstance (result , str ): print_str (result )
6672+ elif command in ["set-environment" ]:
6673+ exitcode = systemctl .set_environment_modules (* modules )
6674+ elif command in ["unset-environment" ]:
6675+ exitcode = systemctl .unset_environment_modules (* modules )
65636676 elif command in ["get-default" ]:
65646677 print_str (systemctl .get_default_target ())
65656678 elif command in ["get-preset" ]:
0 commit comments