From 77fc670a84154d03553839cc262d8c94ce0a00f4 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Ramirez Date: Thu, 25 Apr 2024 18:16:54 +0200 Subject: [PATCH] Update systemctl3.py --- linux_files/systemctl3.py | 1960 ++++++++++++++++++++++--------------- 1 file changed, 1166 insertions(+), 794 deletions(-) diff --git a/linux_files/systemctl3.py b/linux_files/systemctl3.py index 1353196..c29ba2d 100644 --- a/linux_files/systemctl3.py +++ b/linux_files/systemctl3.py @@ -1,33 +1,46 @@ #! /usr/bin/python3 -## type hints are provided in 'types/systemctl3.pyi' - +# type hints are provided in 'types/systemctl3.pyi' from __future__ import print_function +import threading +import grp +import pwd +import hashlib +import select +import fcntl +import string +import datetime +import socket +import time +import signal +import sys +import os +import errno +import collections +import shlex +import fnmatch +import re +from types import GeneratorType + +__copyright__ = "(C) 2016-2024 Guido U. Draheim, licensed under the EUPL" +__version__ = "1.5.8066" -__copyright__ = "(C) 2016-2020 Guido U. Draheim, licensed under the EUPL" -__version__ = "1.5.4505" +# | +# | +# | +# | +# | +# | +# | +# | +# | +# | +# | +# | +# | import logging logg = logging.getLogger("systemctl") -from types import GeneratorType -import re -import fnmatch -import shlex -import collections -import errno -import os -import sys -import signal -import time -import socket -import datetime -import string -import fcntl -import select -import hashlib -import pwd -import grp -import threading if sys.version[0] == '3': basestring = str @@ -39,9 +52,17 @@ DEBUG_INITLOOP = False DEBUG_KILLALL = False DEBUG_FLOCK = False +DebugPrintResult = False TestListen = False TestAccept = False +HINT = (logging.DEBUG + logging.INFO) // 2 +NOTE = (logging.WARNING + logging.INFO) // 2 +DONE = (logging.WARNING + logging.ERROR) // 2 +logging.addLevelName(HINT, "HINT") +logging.addLevelName(NOTE, "NOTE") +logging.addLevelName(DONE, "DONE") + def logg_debug_flock(format, *args): if DEBUG_FLOCK: logg.debug(format, *args) # pragma: no cover @@ -67,42 +88,46 @@ def logg_debug_after(format, *args): _preset_mode = "all" _quiet = False _root = "" -_unit_type = None -_unit_state = None -_unit_property = None -_what_kind = "" _show_all = False _user_mode = False +_only_what = [] +_only_type = [] +_only_state = [] +_only_property = [] # common default paths -_system_folder1 = "/etc/systemd/system" -_system_folder2 = "/run/systemd/system" -_system_folder3 = "/var/run/systemd/system" -_system_folder4 = "/usr/local/lib/systemd/system" -_system_folder5 = "/usr/lib/systemd/system" -_system_folder6 = "/lib/systemd/system" -_system_folderX = None -_user_folder1 = "{XDG_CONFIG_HOME}/systemd/user" -_user_folder2 = "/etc/systemd/user" -_user_folder3 = "{XDG_RUNTIME_DIR}/systemd/user" -_user_folder4 = "/run/systemd/user" -_user_folder5 = "/var/run/systemd/user" -_user_folder6 = "{XDG_DATA_HOME}/systemd/user" -_user_folder7 = "/usr/local/lib/systemd/user" -_user_folder8 = "/usr/lib/systemd/user" -_user_folder9 = "/lib/systemd/user" -_user_folderX = None -_init_folder1 = "/etc/init.d" -_init_folder2 = "/run/init.d" -_init_folder3 = "/var/run/init.d" -_init_folderX = None -_preset_folder1 = "/etc/systemd/system-preset" -_preset_folder2 = "/run/systemd/system-preset" -_preset_folder3 = "/var/run/systemd/system-preset" -_preset_folder4 = "/usr/local/lib/systemd/system-preset" -_preset_folder5 = "/usr/lib/systemd/system-preset" -_preset_folder6 = "/lib/systemd/system-preset" -_preset_folderX = None +_system_folders = [ + "/etc/systemd/system", + "/run/systemd/system", + "/var/run/systemd/system", + "/usr/local/lib/systemd/system", + "/usr/lib/systemd/system", + "/lib/systemd/system", +] +_user_folders = [ + "{XDG_CONFIG_HOME}/systemd/user", + "/etc/systemd/user", + "{XDG_RUNTIME_DIR}/systemd/user", + "/run/systemd/user", + "/var/run/systemd/user", + "{XDG_DATA_HOME}/systemd/user", + "/usr/local/lib/systemd/user", + "/usr/lib/systemd/user", + "/lib/systemd/user", +] +_init_folders = [ + "/etc/init.d", + "/run/init.d", + "/var/run/init.d", +] +_preset_folders = [ + "/etc/systemd/system-preset", + "/run/systemd/system-preset", + "/var/run/systemd/system-preset", + "/usr/local/lib/systemd/system-preset", + "/usr/lib/systemd/system-preset", + "/lib/systemd/system-preset", +] # standard paths _dev_null = "/dev/null" @@ -112,12 +137,12 @@ def logg_debug_after(format, *args): _rc3_init_folder = "/etc/init.d/rc3.d" _rc5_boot_folder = "/etc/rc5.d" _rc5_init_folder = "/etc/init.d/rc5.d" -_proc_pid_stat = "/proc/{pid}/stat" +_proc_pid_stat = "/proc/{pid}/stat" _proc_pid_status = "/proc/{pid}/status" _proc_pid_cmdline= "/proc/{pid}/cmdline" -_proc_pid_dir = "/proc" +_proc_pid_dir = "/proc" _proc_sys_uptime = "/proc/uptime" -_proc_sys_stat = "/proc/stat" +_proc_sys_stat = "/proc/stat" # default values SystemCompatibilityVersion = 219 @@ -165,9 +190,9 @@ def logg_debug_after(format, *args): RESTART_FAILED_UNITS = True ACTIVE_IF_ENABLED=False -TAIL_CMD = "/usr/bin/tail" -LESS_CMD = "/usr/bin/less" -CAT_CMD = "/usr/bin/cat" +TAIL_CMDS = ["/bin/tail", "/usr/bin/tail", "/usr/local/bin/tail"] +LESS_CMDS = ["/bin/less", "/usr/bin/less", "/usr/local/bin/less"] +CAT_CMDS = ["/bin/cat", "/usr/bin/cat", "/usr/local/bin/cat"] # The systemd default was NOTIFY_SOCKET="/var/run/systemd/notify" _notify_socket_folder = "{RUN}/systemd" # alias /run/systemd @@ -176,15 +201,15 @@ def logg_debug_after(format, *args): SYSTEMCTL_DEBUG_LOG = "{LOG}/systemctl.debug.log" SYSTEMCTL_EXTRA_LOG = "{LOG}/systemctl.log" -_default_targets = [ "poweroff.target", "rescue.target", "sysinit.target", "basic.target", "multi-user.target", "graphical.target", "reboot.target" ] -_feature_targets = [ "network.target", "remote-fs.target", "local-fs.target", "timers.target", "nfs-client.target" ] -_all_common_targets = [ "default.target" ] + _default_targets + _feature_targets +_default_targets = ["poweroff.target", "rescue.target", "sysinit.target", "basic.target", "multi-user.target", "graphical.target", "reboot.target"] +_feature_targets = ["network.target", "remote-fs.target", "local-fs.target", "timers.target", "nfs-client.target"] +_all_common_targets = ["default.target"] + _default_targets + _feature_targets # inside a docker we pretend the following -_all_common_enabled = [ "default.target", "multi-user.target", "remote-fs.target" ] -_all_common_disabled = [ "graphical.target", "resue.target", "nfs-client.target" ] +_all_common_enabled = ["default.target", "multi-user.target", "remote-fs.target"] +_all_common_disabled = ["graphical.target", "resue.target", "nfs-client.target"] -target_requires = { "graphical.target": "multi-user.target", "multi-user.target": "basic.target", "basic.target": "sockets.target" } +target_requires = {"graphical.target": "multi-user.target", "multi-user.target": "basic.target", "basic.target": "sockets.target"} _runlevel_mappings = {} # the official list _runlevel_mappings["0"] = "poweroff.target" @@ -201,6 +226,18 @@ def logg_debug_after(format, *args): _sysv_mappings["$remote_fs"] = "remote-fs.target" _sysv_mappings["$timer"] = "timers.target" + +# sections from conf +Unit = "Unit" +Service = "Service" +Socket = "Socket" +Install = "Install" + +# https://tldp.org/LDP/abs/html/exitcodes.html +# https://freedesktop.org/software/systemd/man/systemd.exec.html#id-1.20.8 +EXIT_SUCCESS = 0 +EXIT_FAILURE = 1 + def strINET(value): if value == socket.SOCK_DGRAM: return "UDP" @@ -252,6 +289,14 @@ def to_list(value): if isinstance(value, tuple): return list(value) return str(value or "").split(",") +def commalist(value): + return list(_commalist(value)) +def _commalist(value): + for val in value: + if not val: + continue + for elem in val.strip().split(","): + yield elem def int_mode(value): try: return int(value, 8) except: return None # pragma: no cover @@ -277,6 +322,29 @@ def o77(part): return part return part[:20] + "..." + part[-54:] return part # pragma: no cover (is always str) +def path44(filename): + if not filename: + return "" + x = filename.find("/", 8) + if len(filename) <= 40: + if "/" not in filename: + return ".../" + filename + elif len(filename) <= 44: + return filename + if 0 < x and x < 14: + out = filename[:x+1] + out += "..." + else: + out = filename[:10] + out += "..." + remain = len(filename) - len(out) + y = filename.find("/", remain) + if 0 < y and y < remain+5: + out += filename[y:] + else: + out += filename[remain:] + return out + def unit_name_escape(text): # https://www.freedesktop.org/software/systemd/man/systemd.unit.html#id-1.6 esc = re.sub("([^a-z-AZ.-/])", lambda m: "\\x%02x" % ord(m.group(1)[0]), text) @@ -303,6 +371,11 @@ def path_replace_extension(path, old, new): if path.endswith(old): path = path[:-len(old)] return path + new +def get_exist_path(paths): + for p in paths: + if os.path.exists(p): + return p + return None def get_PAGER(): PAGER = os.environ.get("PAGER", "less") @@ -310,8 +383,8 @@ def get_PAGER(): options = os.environ.get("SYSTEMD_LESS", "FRSXMK") # see 'man timedatectl' if not pager: pager = "cat" if "less" in pager and options: - return [ pager, "-" + options ] - return [ pager ] + return [pager, "-" + options] + return [pager] def os_getlogin(): """ NOT using os.getlogin() """ @@ -325,7 +398,7 @@ def get_runtime_dir(): def get_RUN(root = False): tmp_var = get_TMP(root) if _root: - tmp_var = _root + tmp_var = _root if root: for p in ("/run", "/var/run", "{tmp_var}/run"): path = p.format(**locals()) @@ -422,7 +495,7 @@ def expand_path(path, root = False): XDG_DATA_HOME=get_DATA_HOME(root) XDG_CONFIG_HOME=get_CONFIG_HOME(root) XDG_RUNTIME_DIR=get_RUNTIME_DIR(root) - return os.path.expanduser(path.replace("${","{").format(**locals())) + return os.path.expanduser(path.replace("${", "{").format(**locals())) def shutil_chown(path, user, group): if user or group: @@ -448,7 +521,7 @@ def shutil_setuid(user = None, group = None, xgroups = None): gid = grp.getgrnam(group).gr_gid os.setgid(gid) logg.debug("setgid %s for %s", gid, strQ(group)) - groups = [ gid ] + groups = [gid] try: os.setgroups(groups) logg.debug("setgroups %s < (%s)", groups, group) @@ -468,7 +541,7 @@ def shutil_setuid(user = None, group = None, xgroups = None): if not groups: if group: gid = grp.getgrnam(group).gr_gid - groups = [ gid ] + groups = [gid] try: os.setgroups(groups) logg.debug("setgroups %s > %s ", groups, groupnames) @@ -480,7 +553,7 @@ def shutil_setuid(user = None, group = None, xgroups = None): home = pw.pw_dir shell = pw.pw_shell logname = pw.pw_name - return { "USER": user, "LOGNAME": logname, "HOME": home, "SHELL": shell } + return {"USER": user, "LOGNAME": logname, "HOME": home, "SHELL": shell} return {} def shutil_truncate(filename): @@ -551,11 +624,36 @@ def _pid_zombie(pid): return False return False -def checkstatus(cmd): - if cmd.startswith("-"): - return False, cmd[1:] - else: - return True, cmd +def checkprefix(cmd): + prefix = "" + for i, c in enumerate(cmd): + if c in "-+!@:": + prefix = prefix + c + else: + newcmd = cmd[i:] + return prefix, newcmd + return prefix, "" + +ExecMode = collections.namedtuple("ExecMode", ["mode", "check", "nouser", "noexpand", "argv0"]) +def exec_path(cmd): + """ Hint: exec_path values are usually not moved by --root (while load_path are)""" + prefix, newcmd = checkprefix(cmd) + check = "-" not in prefix + nouser = "+" in prefix or "!" in prefix + noexpand = ":" in prefix + argv0 = "@" in prefix + mode = ExecMode(prefix, check, nouser, noexpand, argv0) + return mode, newcmd +LoadMode = collections.namedtuple("LoadMode", ["mode", "check"]) +def load_path(ref): + """ Hint: load_path values are usually moved by --root (while exec_path are not)""" + prefix, filename = "", ref + while filename.startswith("-"): + prefix = prefix + filename[0] + filename = filename[1:] + check = "-" not in prefix + mode = LoadMode(prefix, check) + return mode, filename # https://github.com/phusion/baseimage-docker/blob/rel-0.9.16/image/bin/my_init def ignore_signals_and_raise_keyboard_interrupt(signame): @@ -571,6 +669,12 @@ class SystemctlConfData: that data is structured in sections and values. Actually the values are lists - the raw data is in .getlist(). Otherwise .get() will return the first line that was encountered. """ + # | + # | + # | + # | + # | + # | def __init__(self, defaults=None, dict_type=None, conf_type=None, allow_no_value=False): self._defaults = defaults or {} self._conf_type = conf_type or _default_conf_type @@ -597,7 +701,7 @@ def set(self, section, option, value): if value is None: self._conf[section][option] = [] elif option not in self._conf[section]: - self._conf[section][option] = [ value ] + self._conf[section][option] = [value] else: self._conf[section][option].append(value) def getstr(self, section, option, default = None, allow_no_value = False): @@ -718,11 +822,11 @@ def read_sysv(self, filename): for orig_line in open(filename): line = orig_line.strip() if line.startswith("#"): - if " BEGIN INIT INFO" in line: - initinfo = True - section = "init.d" - if " END INIT INFO" in line: - initinfo = False + if " BEGIN INIT INFO" in line: + initinfo = True + section = "init.d" + if " END INIT INFO" in line: + initinfo = False if initinfo: m = re.match(r"\S+\s*(\w[\w_-]*):(.*)", line) if m: @@ -733,34 +837,34 @@ def read_sysv(self, filename): return self def systemd_sysv_generator(self, filename): """ see systemd-sysv-generator(8) """ - self.set("Unit", "SourcePath", filename) + self.set(Unit, "SourcePath", filename) description = self.get("init.d", "Description", "") if description: - self.set("Unit", "Description", description) - check = self.get("init.d", "Required-Start","") + self.set(Unit, "Description", description) + check = self.get("init.d", "Required-Start", "") if check: for item in check.split(" "): if item.strip() in _sysv_mappings: - self.set("Unit", "Requires", _sysv_mappings[item.strip()]) + self.set(Unit, "Requires", _sysv_mappings[item.strip()]) provides = self.get("init.d", "Provides", "") if provides: - self.set("Install", "Alias", provides) + self.set(Install, "Alias", provides) # if already in multi-user.target then start it there. - runlevels = self.getstr("init.d", "Default-Start","3 5") + runlevels = self.getstr("init.d", "Default-Start", "3 5") for item in runlevels.split(" "): if item.strip() in _runlevel_mappings: - self.set("Install", "WantedBy", _runlevel_mappings[item.strip()]) - self.set("Service", "Restart", "no") - self.set("Service", "TimeoutSec", strE(DefaultMaximumTimeout)) - self.set("Service", "KillMode", "process") - self.set("Service", "GuessMainPID", "no") - # self.set("Service", "RemainAfterExit", "yes") - # self.set("Service", "SuccessExitStatus", "5 6") - self.set("Service", "ExecStart", filename + " start") - self.set("Service", "ExecStop", filename + " stop") + self.set(Install, "WantedBy", _runlevel_mappings[item.strip()]) + self.set(Service, "Restart", "no") + self.set(Service, "TimeoutSec", strE(DefaultMaximumTimeout)) + self.set(Service, "KillMode", "process") + self.set(Service, "GuessMainPID", "no") + # self.set(Service, "RemainAfterExit", "yes") + # self.set(Service, "SuccessExitStatus", "5 6") + self.set(Service, "ExecStart", filename + " start") + self.set(Service, "ExecStop", filename + " stop") if description: # LSB style initscript - self.set("Service", "ExecReload", filename + " reload") - self.set("Service", "Type", "forking") # not "sysv" anymore + self.set(Service, "ExecReload", filename + " reload") + self.set(Service, "Type", "forking") # not "sysv" anymore # UnitConfParser = ConfigParser.RawConfigParser UnitConfParser = SystemctlConfigParser @@ -781,13 +885,22 @@ def listen(self, backlog = None): def name(self): return self.conf.name() def addr(self): - stream = self.conf.get("Socket", "ListenStream", "") - dgram = self.conf.get("Socket", "ListenDatagram", "") + stream = self.conf.get(Socket, "ListenStream", "") + dgram = self.conf.get(Socket, "ListenDatagram", "") return stream or dgram def close(self): self.sock.close() class SystemctlConf: + # | + # | + # | + # | + # | + # | + # | + # | + # | def __init__(self, data, module = None): self.data = data # UnitConfParser self.env = {} @@ -815,7 +928,7 @@ def filename(self): return None def overrides(self): """ drop-in files are loaded alphabetically by name, not by full path """ - return [ self.drop_in_files[name] for name in sorted(self.drop_in_files) ] + return [self.drop_in_files[name] for name in sorted(self.drop_in_files)] def name(self): """ the unit id or defaults to the file name """ name = self.module or "" @@ -837,6 +950,8 @@ def getbool(self, section, name, default = None): return False class PresetFile: + # | + # | def __init__(self): self._files = [] self._lines = [] @@ -862,6 +977,9 @@ def get_preset(self, unit): ## with waitlock(conf): self.start() class waitlock: + # | + # | + # | def __init__(self, conf): self.conf = conf # currently unused self.opened = -1 @@ -905,7 +1023,7 @@ def __enter__(self): logg.error("[%s] not able to get the lock to %s", os.getpid(), lockname) except Exception as e: logg.warning("[%s] oops %s, %s", os.getpid(), str(type(e)), e) - #TODO# raise Exception("no lock for %s", self.unit or "global") + # TODO# raise Exception("no lock for %s", self.unit or "global") return False def __exit__(self, type, value, traceback): try: @@ -922,7 +1040,7 @@ def __exit__(self, type, value, traceback): except Exception as e: logg.warning("oops, %s", e) -waitpid_result = collections.namedtuple("waitpid", ["pid", "returncode", "signal" ]) +SystemctlWaitPID = collections.namedtuple("SystemctlWaitPID", ["pid", "returncode", "signal"]) def must_have_failed(waitpid, cmd): # found to be needed on ubuntu:16.04 to match test result from ubuntu:18.04 and other distros @@ -938,25 +1056,25 @@ def must_have_failed(waitpid, cmd): if pid is None: # unknown $MAINPID if not waitpid.returncode: logg.error("waitpid %s did return %s => correcting as 11", cmd, waitpid.returncode) - waitpid = waitpid_result(waitpid.pid, 11, waitpid.signal) + waitpid = SystemctlWaitPID(waitpid.pid, 11, waitpid.signal) return waitpid def subprocess_waitpid(pid): run_pid, run_stat = os.waitpid(pid, 0) - return waitpid_result(run_pid, os.WEXITSTATUS(run_stat), os.WTERMSIG(run_stat)) + return SystemctlWaitPID(run_pid, os.WEXITSTATUS(run_stat), os.WTERMSIG(run_stat)) def subprocess_testpid(pid): run_pid, run_stat = os.waitpid(pid, os.WNOHANG) if run_pid: - return waitpid_result(run_pid, os.WEXITSTATUS(run_stat), os.WTERMSIG(run_stat)) + return SystemctlWaitPID(run_pid, os.WEXITSTATUS(run_stat), os.WTERMSIG(run_stat)) else: - return waitpid_result(pid, None, 0) + return SystemctlWaitPID(pid, None, 0) -parse_result = collections.namedtuple("UnitName", ["fullname", "name", "prefix", "instance", "suffix", "component" ]) +SystemctlUnitName = collections.namedtuple("SystemctlUnitName", ["fullname", "name", "prefix", "instance", "suffix", "component"]) def parse_unit(fullname): # -> object(prefix, instance, suffix, ...., name, component) name, suffix = fullname, "" has_suffix = fullname.rfind(".") - if has_suffix > 0: + if has_suffix > 0: name = fullname[:has_suffix] suffix = fullname[has_suffix+1:] prefix, instance = name, "" @@ -966,9 +1084,9 @@ def parse_unit(fullname): # -> object(prefix, instance, suffix, ...., name, comp instance = name[has_instance+1:] component = "" has_component = prefix.rfind("-") - if has_component > 0: + if has_component > 0: component = prefix[has_component+1:] - return parse_result(fullname, name, prefix, instance, suffix, component) + return SystemctlUnitName(fullname, name, prefix, instance, suffix, component) def time_to_seconds(text, maximum): value = 0. @@ -1018,7 +1136,7 @@ def seconds_to_time(seconds): def getBefore(conf): result = [] - beforelist = conf.getlist("Unit", "Before", []) + beforelist = conf.getlist(Unit, "Before", []) for befores in beforelist: for before in befores.split(" "): name = before.strip() @@ -1028,7 +1146,7 @@ def getBefore(conf): def getAfter(conf): result = [] - afterlist = conf.getlist("Unit", "After", []) + afterlist = conf.getlist(Unit, "After", []) for afters in afterlist: for after in afters.split(" "): name = after.strip() @@ -1072,7 +1190,7 @@ class SortTuple: def __init__(self, rank, conf): self.rank = rank self.conf = conf - sortlist = [ SortTuple(0, conf) for conf in conflist] + sortlist = [SortTuple(0, conf) for conf in conflist] for check in xrange(len(sortlist)): # maxrank = len(sortlist) changed = 0 for A in xrange(len(sortlist)): @@ -1101,7 +1219,7 @@ def __init__(self, rank, conf): sortedlist = sorted(sortlist, key = lambda item: -item.rank) for item in sortedlist: logg_debug_after("[%s] %s", item.rank, item.conf.name()) - return [ item.conf for item in sortedlist ] + return [item.conf for item in sortedlist] class SystemctlListenThread(threading.Thread): def __init__(self, systemctl): @@ -1136,8 +1254,8 @@ def run(self): time.sleep(1) # accept signals atleast every second sleeping = InitLoopSleep - (time.time() - timestamp) if sleeping < MinimumYield: - sleeping = MinimumYield - break + sleeping = MinimumYield + break time.sleep(sleeping) # remainder waits less that 2 seconds if DEBUG_INITLOOP: # pragma: no cover logg.debug("[%s] listen: poll", me) @@ -1163,6 +1281,45 @@ def run(self): return class Systemctl: + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | + # | def __init__(self): self.error = NOT_A_PROBLEM # program exitcode or process returncode # from command line options or the defaults @@ -1177,9 +1334,10 @@ def __init__(self): self._quiet = _quiet self._root = _root self._show_all = _show_all - self._unit_property = _unit_property - self._unit_state = _unit_state - self._unit_type = _unit_type + self._only_what = commalist(_only_what) or [""] + self._only_property = commalist(_only_property) + self._only_state = commalist(_only_state) + self._only_type = commalist(_only_type) # some common constants that may be changed self._systemd_version = SystemCompatibilityVersion self._journal_log_folder = _journal_log_folder @@ -1222,49 +1380,29 @@ def preset_folders(self): for path in SYSTEMD_PRESET_PATH.split(":"): if path.strip(): yield expand_path(path.strip()) if SYSTEMD_PRESET_PATH.endswith(":"): - if _preset_folder1: yield _preset_folder1 - if _preset_folder2: yield _preset_folder2 - if _preset_folder3: yield _preset_folder3 - if _preset_folder4: yield _preset_folder4 - if _preset_folder5: yield _preset_folder5 - if _preset_folder6: yield _preset_folder6 - if _preset_folderX: yield _preset_folderX + for p in _preset_folders: + yield expand_path(p.strip()) def init_folders(self): SYSTEMD_SYSVINIT_PATH = self.get_SYSTEMD_SYSVINIT_PATH() for path in SYSTEMD_SYSVINIT_PATH.split(":"): if path.strip(): yield expand_path(path.strip()) if SYSTEMD_SYSVINIT_PATH.endswith(":"): - if _init_folder1: yield _init_folder1 - if _init_folder2: yield _init_folder2 - if _init_folder3: yield _init_folder3 - if _init_folderX: yield _init_folderX + for p in _init_folders: + yield expand_path(p.strip()) def user_folders(self): SYSTEMD_UNIT_PATH = self.get_SYSTEMD_UNIT_PATH() for path in SYSTEMD_UNIT_PATH.split(":"): if path.strip(): yield expand_path(path.strip()) if SYSTEMD_UNIT_PATH.endswith(":"): - if _user_folder1: yield expand_path(_user_folder1) - if _user_folder2: yield expand_path(_user_folder2) - if _user_folder3: yield expand_path(_user_folder3) - if _user_folder4: yield expand_path(_user_folder4) - if _user_folder5: yield expand_path(_user_folder5) - if _user_folder6: yield expand_path(_user_folder6) - if _user_folder7: yield expand_path(_user_folder7) - if _user_folder8: yield expand_path(_user_folder8) - if _user_folder9: yield expand_path(_user_folder9) - if _user_folderX: yield expand_path(_user_folderX) + for p in _user_folders: + yield expand_path(p.strip()) def system_folders(self): SYSTEMD_UNIT_PATH = self.get_SYSTEMD_UNIT_PATH() for path in SYSTEMD_UNIT_PATH.split(":"): if path.strip(): yield expand_path(path.strip()) if SYSTEMD_UNIT_PATH.endswith(":"): - if _system_folder1: yield _system_folder1 - if _system_folder2: yield _system_folder2 - if _system_folder3: yield _system_folder3 - if _system_folder4: yield _system_folder4 - if _system_folder5: yield _system_folder5 - if _system_folder6: yield _system_folder6 - if _system_folderX: yield _system_folderX + for p in _system_folders: + yield expand_path(p.strip()) def get_SYSTEMD_UNIT_PATH(self): if self._SYSTEMD_UNIT_PATH is None: self._SYSTEMD_UNIT_PATH = os.environ.get("SYSTEMD_UNIT_PATH", ":") @@ -1293,7 +1431,7 @@ def scan_unit_sysd_files(self, module = None): # -> [ unit-names,... ] if self._file_for_unit_sysd is None: self._file_for_unit_sysd = {} for folder in self.sysd_folders(): - if not folder: + if not folder: continue folder = os_path(self._root, folder) if not os.path.isdir(folder): @@ -1312,7 +1450,7 @@ def scan_unit_sysv_files(self, module = None): # -> [ unit-names,... ] if self._file_for_unit_sysv is None: self._file_for_unit_sysv = {} for folder in self.init_folders(): - if not folder: + if not folder: continue folder = os_path(self._root, folder) if not os.path.isdir(folder): @@ -1388,7 +1526,7 @@ def find_drop_in_files(self, unit): result = {} basename_d = unit + ".d" for folder in self.sysd_folders(): - if not folder: + if not folder: continue folder = os_path(self._root, folder) override_d = os_path(folder, basename_d) @@ -1471,13 +1609,13 @@ def default_unit_conf(self, module, description = None): # -> conf """ a unit conf that can be printed to the user where attributes are empty and loaded() is False """ data = UnitConfParser() - data.set("Unit", "Description", description or ("NOT-FOUND " + str(module))) + data.set(Unit, "Description", description or ("NOT-FOUND " + str(module))) # assert(not data.loaded()) conf = SystemctlConf(data, module) conf._root = self._root return conf def get_unit_conf(self, module): # -> conf (conf | default-conf) - """ accept that a unit does not exist + """ accept that a unit does not exist and return a unit conf that says 'not-loaded' """ conf = self.load_unit_conf(module) if conf is not None: @@ -1488,9 +1626,9 @@ def get_unit_type(self, module): if ext in [".service", ".socket", ".target"]: return ext[1:] return None - def get_unit_section(self, module, default = "Service"): + def get_unit_section(self, module, default = Service): return string.capwords(self.get_unit_type(module) or default) - def get_unit_section_from(self, conf, default = "Service"): + def get_unit_section_from(self, conf, default = Service): return self.get_unit_section(conf.name(), default) def match_sysd_templates(self, modules = None, suffix=".service"): # -> generate[ unit ] """ make a file glob on all known template units (systemd areas). @@ -1519,11 +1657,13 @@ def match_sysd_units(self, modules = None, suffix=".service"): # -> generate[ un self.scan_unit_sysd_files() assert self._file_for_unit_sysd is not None for item in sorted(self._file_for_unit_sysd.keys()): - if not modules: + if "." not in item: + pass + elif not modules: yield item - elif [ module for module in modules if fnmatch.fnmatchcase(item, module) ]: + elif [module for module in modules if fnmatch.fnmatchcase(item, module)]: yield item - elif [ module for module in modules if module+suffix == item ]: + elif [module for module in modules if module+suffix == item]: yield item def match_sysv_units(self, modules = None, suffix=".service"): # -> generate[ unit ] """ make a file glob on all known units (sysv areas). @@ -1535,13 +1675,13 @@ def match_sysv_units(self, modules = None, suffix=".service"): # -> generate[ un for item in sorted(self._file_for_unit_sysv.keys()): if not modules: yield item - elif [ module for module in modules if fnmatch.fnmatchcase(item, module) ]: + elif [module for module in modules if fnmatch.fnmatchcase(item, module)]: yield item - elif [ module for module in modules if module+suffix == item ]: + elif [module for module in modules if module+suffix == item]: yield item def match_units(self, modules = None, suffix=".service"): # -> [ units,.. ] """ Helper for about any command with multiple units which can - actually be glob patterns on their respective unit name. + actually be glob patterns on their respective unit name. It returns all modules if no modules pattern were given. Also a single string as one module pattern may be given. """ found = [] @@ -1562,9 +1702,9 @@ def list_service_unit_basics(self): assert self._file_for_unit_sysv is not None result = [] for name, value in self._file_for_unit_sysd.items(): - result += [ (name, "SysD", value) ] + result += [(name, "SysD", value)] for name, value in self._file_for_unit_sysv.items(): - result += [ (name, "SysV", value) ] + result += [(name, "SysV", value)] return result def list_service_units(self, *modules): # -> [ (unit,loaded+active+substate,description) ] """ show all the service units """ @@ -1577,7 +1717,7 @@ def list_service_units(self, *modules): # -> [ (unit,loaded+active+substate,desc active[unit] = "inactive" substate[unit] = "dead" description[unit] = "" - try: + try: conf = self.get_unit_conf(unit) result[unit] = "loaded" description[unit] = self.get_description_from(conf) @@ -1585,31 +1725,37 @@ def list_service_units(self, *modules): # -> [ (unit,loaded+active+substate,desc substate[unit] = self.get_substate_from(conf) or "unknown" except Exception as e: logg.warning("list-units: %s", e) - if self._unit_state: - if self._unit_state not in [ result[unit], active[unit], substate[unit] ]: + if self._only_state: + if result[unit] in self._only_state: + pass + elif active[unit] in self._only_state: + pass + elif substate[unit] in self._only_state: + pass + else: del result[unit] - return [ (unit, result[unit] + " " + active[unit] + " " + substate[unit], description[unit]) for unit in sorted(result) ] - def show_list_units(self, *modules): # -> [ (unit,loaded,description) ] + return [(unit, result[unit] + " " + active[unit] + " " + substate[unit], description[unit]) for unit in sorted(result)] + def list_units_modules(self, *modules): # -> [ (unit,loaded,description) ] """ [PATTERN]... -- List loaded units. - If one or more PATTERNs are specified, only units matching one of + If one or more PATTERNs are specified, only units matching one of them are shown. NOTE: This is the default command.""" hint = "To show all installed unit files use 'systemctl list-unit-files'." result = self.list_service_units(*modules) if self._no_legend: return result found = "%s loaded units listed." % len(result) - return result + [ ("", "", ""), (found, "", ""), (hint, "", "") ] + return result + [("", "", ""), (found, "", ""), (hint, "", "")] def list_service_unit_files(self, *modules): # -> [ (unit,enabled) ] """ show all the service units and the enabled status""" logg.debug("list service unit files for %s", modules) result = {} enabled = {} for unit in self.match_units(to_list(modules)): - if _unit_type and self.get_unit_type(unit) not in _unit_type.split(","): + if self._only_type and self.get_unit_type(unit) not in self._only_type: continue result[unit] = None enabled[unit] = "" - try: + try: conf = self.get_unit_conf(unit) if self.not_user_conf(conf): result[unit] = None @@ -1618,7 +1764,7 @@ def list_service_unit_files(self, *modules): # -> [ (unit,enabled) ] enabled[unit] = self.enabled_from(conf) except Exception as e: logg.warning("list-units: %s", e) - return [ (unit, enabled[unit]) for unit in sorted(result) if result[unit] ] + return [(unit, enabled[unit]) for unit in sorted(result) if result[unit]] def each_target_file(self): folders = self.system_folders() if self.user_mode(): @@ -1645,8 +1791,8 @@ def list_target_unit_files(self, *modules): # -> [ (unit,enabled) ] enabled[unit] = "enabled" if unit in _all_common_disabled: enabled[unit] = "disabled" - return [ (unit, enabled[unit]) for unit in sorted(targets) ] - def show_list_unit_files(self, *modules): # -> [ (unit,enabled) ] + return [(unit, enabled[unit]) for unit in sorted(targets)] + def list_unit_files_modules(self, *modules): # -> [ (unit,enabled) ] """[PATTERN]... -- List installed unit files List installed unit files and their enablement state (as reported by is-enabled). If one or more PATTERNs are specified, only units @@ -1656,20 +1802,19 @@ def show_list_unit_files(self, *modules): # -> [ (unit,enabled) ] result = [] if self._now: basics = self.list_service_unit_basics() - result = [ (name, sysv + " " + filename) for name, sysv, filename in basics ] - elif self._unit_type == "target": - result = self.list_target_unit_files() - elif self._unit_type == "service": - result = self.list_service_unit_files() - elif self._unit_type: - logg.warning("unsupported unit --type=%s", self._unit_type) + result = [(name, sysv + " " + filename) for name, sysv, filename in basics] + elif self._only_type: + if "target" in self._only_type: + result = self.list_target_unit_files() + if "service" in self._only_type: + result = self.list_service_unit_files() else: result = self.list_target_unit_files() result += self.list_service_unit_files(*modules) if self._no_legend: return result found = "%s unit files listed." % len(result) - return [ ("UNIT FILE", "STATE") ] + result + [ ("", ""), (found, "") ] + return [("UNIT FILE", "STATE")] + result + [("", ""), (found, "")] ## ## def get_description(self, unit, default = None): @@ -1677,7 +1822,7 @@ def get_description(self, unit, default = None): def get_description_from(self, conf, default = None): # -> text """ Unit.Description could be empty sometimes """ if not conf: return default or "" - description = conf.get("Unit", "Description", default or "") + description = conf.get(Unit, "Description", default or "") return self.expand_special(description, conf) def read_pid_file(self, pid_file, default = None): pid = default @@ -1690,7 +1835,7 @@ def read_pid_file(self, pid_file, default = None): try: # some pid-files from applications contain multiple lines for line in open(pid_file): - if line.strip(): + if line.strip(): pid = to_intN(line.strip()) break except Exception as e: @@ -1714,8 +1859,8 @@ def wait_pid_file(self, pid_file, timeout = None): # -> pid? continue return pid return None - def test_pid_file(self, unit): # -> text - """ support for the testsuite.py """ + def get_status_pid_file(self, unit): + """ actual file path of pid file (internal) """ conf = self.get_unit_conf(unit) return self.pid_file_from(conf) or self.get_status_file_from(conf) def pid_file_from(self, conf, default = ""): @@ -1723,7 +1868,7 @@ def pid_file_from(self, conf, default = ""): pid_file = self.get_pid_file(conf) or default return os_path(self._root, self.expand_special(pid_file, conf)) def get_pid_file(self, conf, default = None): - return conf.get("Service", "PIDFile", default) + return conf.get(Service, "PIDFile", default) def read_mainpid_from(self, conf, default = None): """ MAINPID is either the PIDFile content written from the application or it is the value in the status file written by this systemctl.py code """ @@ -1751,7 +1896,7 @@ def get_status_file_from(self, conf, default = None): return os_path(self._root, self.expand_special(status_file, conf)) def get_StatusFile(self, conf, default = None): # -> text """ file where to store a status mark """ - status_file = conf.get("Service", "StatusFile", default) + status_file = conf.get(Service, "StatusFile", default) if status_file: return status_file root = conf.root_mode() @@ -1809,13 +1954,13 @@ def read_status_from(self, conf): try: if DEBUG_STATUS: logg.debug("reading %s", status_file) for line in open(status_file): - if line.strip(): + if line.strip(): m = re.match(r"(\w+)[:=](.*)", line) if m: key, value = m.group(1), m.group(2) if key.strip(): status[key.strip()] = value.strip() - else: #pragma: no cover + else: # pragma: no cover logg.warning("ignored %s", line.strip()) except: logg.warning("bad read of status file '%s'", status_file) @@ -1865,7 +2010,7 @@ def get_boottime_from_old_proc(self): # ctime = os.path.getmtime(proc) ctime = self.path_proc_started(proc) if ctime < booted: - booted = ctime + booted = ctime except Exception as e: # pragma: no cover logg.warning("could not access %s: %s", proc, e) return booted @@ -1874,7 +2019,7 @@ def get_boottime_from_old_proc(self): # You can't use the modified timestamp of the status file because it isn't static. # ... using clock ticks it is known to be a linear time on Linux def path_proc_started(self, proc): - #get time process started after boot in clock ticks + # get time process started after boot in clock ticks with open(proc) as file_stat: data_stat = file_stat.readline() file_stat.close() @@ -1893,7 +2038,7 @@ def path_proc_started(self, proc): # Variant 1: system_uptime = _proc_sys_uptime - with open(system_uptime,"rb") as file_uptime: + with open(system_uptime, "rb") as file_uptime: data_uptime = file_uptime.readline() file_uptime.close() uptime_data = data_uptime.decode().split() @@ -1901,7 +2046,7 @@ def path_proc_started(self, proc): if DEBUG_BOOTTIME: logg.debug(" BOOT 1. System uptime secs: %.3f (%s)", uptime_secs, system_uptime) - #get time now + # get time now now = time.time() started_time = now - (uptime_secs - started_secs) if DEBUG_BOOTTIME: @@ -1910,7 +2055,7 @@ def path_proc_started(self, proc): # Variant 2: system_stat = _proc_sys_stat system_btime = 0. - with open(system_stat,"rb") as f: + with open(system_stat, "rb") as f: for line in f: assert isinstance(line, bytes) if line.startswith(b"btime"): @@ -1959,10 +2104,14 @@ def getsize(self, filename): # def read_env_file(self, env_file): # -> generate[ (name,value) ] """ EnvironmentFile= is being scanned """ - if env_file.startswith("-"): - env_file = env_file[1:] - if not os.path.isfile(os_path(self._root, env_file)): - return + mode, env_file = load_path(env_file) + real_file = os_path(self._root, env_file) + if not os.path.exists(real_file): + if mode.check: + logg.error("file does not exist: %s", real_file) + else: + logg.debug("file does not exist: %s", real_file) + return try: for real_line in open(os_path(self._root, env_file)): line = real_line.strip() @@ -1984,11 +2133,11 @@ def read_env_file(self, env_file): # -> generate[ (name,value) ] logg.info("while reading %s: %s", env_file, e) def read_env_part(self, env_part): # -> generate[ (name, value) ] """ Environment== is being scanned """ - ## systemd Environment= spec says it is a space-seperated list of - ## assignments. In order to use a space or an equals sign in a value - ## one should enclose the whole assignment with double quotes: - ## Environment="VAR1=word word" VAR2=word3 "VAR3=$word 5 6" - ## and the $word is not expanded by other environment variables. + # systemd Environment= spec says it is a space-separated list of + # assignments. In order to use a space or an equals sign in a value + # one should enclose the whole assignment with double quotes: + # Environment="VAR1=word word" VAR2=word3 "VAR3=$word 5 6" + # and the $word is not expanded by other environment variables. try: for real_line in env_part.split("\n"): line = real_line.strip() @@ -2000,23 +2149,36 @@ def read_env_part(self, env_part): # -> generate[ (name, value) ] yield name, value except Exception as e: logg.info("while reading %s: %s", env_part, e) - def show_environment(self, unit): + def command_of_unit(self, unit): + """ [UNIT]. -- show service settings (experimental) + or use -p VarName to show another property than 'ExecStart' """ + conf = self.load_unit_conf(unit) + if conf is None: + logg.error("Unit %s could not be found.", unit) + self.error |= NOT_FOUND + return None + if self._only_property: + found = [] + for prop in self._only_property: + found += conf.getlist(Service, prop) + return found + return conf.getlist(Service, "ExecStart") + def environment_of_unit(self, unit): """ [UNIT]. -- show environment parts """ conf = self.load_unit_conf(unit) if conf is None: logg.error("Unit %s could not be found.", unit) - return False - if _unit_property: - return conf.getlist("Service", _unit_property) + self.error |= NOT_FOUND + return None return self.get_env(conf) def extra_vars(self): return self._extra_vars # from command line def get_env(self, conf): env = os.environ.copy() - for env_part in conf.getlist("Service", "Environment", []): + for env_part in conf.getlist(Service, "Environment", []): for name, value in self.read_env_part(self.expand_special(env_part, conf)): env[name] = value # a '$word' is not special here (lazy expansion) - for env_file in conf.getlist("Service", "EnvironmentFile", []): + for env_file in conf.getlist(Service, "EnvironmentFile", []): for name, value in self.read_env_file(self.expand_special(env_file, conf)): env[name] = self.expand_env(value, env) # but nonlazy expansion here logg.debug("extra-vars %s", self.extra_vars()) @@ -2047,9 +2209,9 @@ def get_env2(m): return (EXPAND_KEEP_VARS and namevar or "") # maxdepth = EXPAND_VARS_MAXDEPTH - expanded = re.sub("[$](\w+)", lambda m: get_env1(m), cmd.replace("\\\n","")) + expanded = re.sub(r"[$](\w+)", lambda m: get_env1(m), cmd.replace("\\\n", "")) for depth in xrange(maxdepth): - new_text = re.sub("[$][{](\w+)[}]", lambda m: get_env2(m), expanded) + new_text = re.sub(r"[$][{](\w+)[}]", lambda m: get_env2(m), expanded) if new_text == expanded: return expanded expanded = new_text @@ -2062,9 +2224,9 @@ def expand_special(self, cmd, conf): def xx(arg): return unit_name_unescape(arg) def yy(arg): return arg def get_confs(conf): - confs={ "%": "%" } + confs={"%": "%"} if conf is None: # pragma: no cover (is never null) - return confs + return confs unit = parse_unit(conf.name()) # root = conf.root_mode() @@ -2117,37 +2279,50 @@ def get_conf1(m): result = "" if cmd: result = re.sub("[%](.)", lambda m: get_conf1(m), cmd) - #++# logg.info("expanded => %s", result) + # ++# logg.info("expanded => %s", result) return result - ExecMode = collections.namedtuple("ExecMode", ["check"]) def exec_newcmd(self, cmd, env, conf): - check, cmd = checkstatus(cmd) - mode = Systemctl.ExecMode(check) - newcmd = self.exec_cmd(cmd, env, conf) + mode, exe = exec_path(cmd) + if mode.noexpand: + newcmd = self.split_cmd(exe) + else: + newcmd = self.expand_cmd(exe, env, conf) + if mode.argv0: + if len(newcmd) > 1: + del newcmd[1] # TODO: keep but allow execve calls to pick it up return mode, newcmd - def exec_cmd(self, cmd, env, conf): + def split_cmd(self, cmd): + cmd2 = cmd.replace("\\\n", "") + newcmd = [] + for part in shlex.split(cmd2): + newcmd += [part] + return newcmd + def expand_cmd(self, cmd, env, conf): """ expand ExecCmd statements including %i and $MAINPID """ - cmd2 = cmd.replace("\\\n","") + cmd2 = cmd.replace("\\\n", "") # according to documentation, when bar="one two" then the expansion # of '$bar' is ["one","two"] and '${bar}' becomes ["one two"]. We # tackle that by expand $bar before shlex, and the rest thereafter. def get_env1(m): - if m.group(1) in env: - return env[m.group(1)] - logg.debug("can not expand $%s", m.group(1)) - return "" # empty string + name = m.group(1) + if name in env: + return env[name] + logg.debug("can not expand $%s", name) + return "" # empty string def get_env2(m): - if m.group(1) in env: - return env[m.group(1)] - logg.debug("can not expand ${%s}", m.group(1)) - return "" # empty string - cmd3 = re.sub("[$](\w+)", lambda m: get_env1(m), cmd2) + name = m.group(1) + if name in env: + return env[name] + logg.debug("can not expand $%s}}", name) + return "" # empty string + cmd3 = re.sub(r"[$](\w+)", lambda m: get_env1(m), cmd2) newcmd = [] for part in shlex.split(cmd3): - # newcmd += [ re.sub("[$][{](\w+)[}]", lambda m: get_env2(m), part) ] - newcmd += [ re.sub("[$][{](\w+)[}]", lambda m: get_env2(m), self.expand_special(part, conf)) ] + part2 = self.expand_special(part, conf) + newcmd += [re.sub(r"[$][{](\w+)[}]", lambda m: get_env2(m), part2)] # type: ignore[arg-type] return newcmd - def remove_service_directories(self, conf, section = "Service"): + def remove_service_directories(self, conf, section = Service): + # | ok = True nameRuntimeDirectory = self.get_RuntimeDirectory(conf, section) keepRuntimeDirectory = self.get_RuntimeDirectoryPreserve(conf, section) @@ -2174,7 +2349,7 @@ def do_rm_tree(self, path): for dirpath, dirnames, filenames in os.walk(path, topdown=False): for item in filenames: filepath = os.path.join(dirpath, item) - try: + try: os.remove(filepath) except Exception as e: # pragma: no cover logg.debug("not removed file: %s (%s)", filepath, e) @@ -2186,34 +2361,34 @@ def do_rm_tree(self, path): except Exception as e: # pragma: no cover logg.debug("not removed dir: %s (%s)", dir_path, e) ok = False - try: + try: os.rmdir(path) - except Exception as e: + except Exception as e: logg.debug("not removed top dir: %s (%s)", path, e) ok = False # pragma: no cover logg.debug("%s rm_tree %s", ok and "done" or "fail", path) return ok - def get_RuntimeDirectoryPreserve(self, conf, section = "Service"): + def get_RuntimeDirectoryPreserve(self, conf, section = Service): return conf.getbool(section, "RuntimeDirectoryPreserve", "no") - def get_RuntimeDirectory(self, conf, section = "Service"): + def get_RuntimeDirectory(self, conf, section = Service): return self.expand_special(conf.get(section, "RuntimeDirectory", ""), conf) - def get_StateDirectory(self, conf, section = "Service"): + def get_StateDirectory(self, conf, section = Service): return self.expand_special(conf.get(section, "StateDirectory", ""), conf) - def get_CacheDirectory(self, conf, section = "Service"): + def get_CacheDirectory(self, conf, section = Service): return self.expand_special(conf.get(section, "CacheDirectory", ""), conf) - def get_LogsDirectory(self, conf, section = "Service"): + def get_LogsDirectory(self, conf, section = Service): return self.expand_special(conf.get(section, "LogsDirectory", ""), conf) - def get_ConfigurationDirectory(self, conf, section = "Service"): + def get_ConfigurationDirectory(self, conf, section = Service): return self.expand_special(conf.get(section, "ConfigurationDirectory", ""), conf) - def get_RuntimeDirectoryMode(self, conf, section = "Service"): + def get_RuntimeDirectoryMode(self, conf, section = Service): return conf.get(section, "RuntimeDirectoryMode", "") - def get_StateDirectoryMode(self, conf, section = "Service"): + def get_StateDirectoryMode(self, conf, section = Service): return conf.get(section, "StateDirectoryMode", "") - def get_CacheDirectoryMode(self, conf, section = "Service"): + def get_CacheDirectoryMode(self, conf, section = Service): return conf.get(section, "CacheDirectoryMode", "") - def get_LogsDirectoryMode(self, conf, section = "Service"): + def get_LogsDirectoryMode(self, conf, section = Service): return conf.get(section, "LogsDirectoryMode", "") - def get_ConfigurationDirectoryMode(self, conf, section = "Service"): + def get_ConfigurationDirectoryMode(self, conf, section = Service): return conf.get(section, "ConfigurationDirectoryMode", "") def clean_service_directories(self, conf, which = ""): ok = True @@ -2379,7 +2554,7 @@ def make_service_directory(self, path, mode): ok = True dirpath = os_path(self._root, path) if not os.path.isdir(dirpath): - try: + try: os.makedirs(dirpath) logg.info("created directory path: %s", dirpath) except Exception as e: # pragma: no cover @@ -2436,19 +2611,19 @@ def do_chown_tree(self, path, user, group): for dirpath, dirnames, filenames in os.walk(path, topdown=False): for item in filenames: filepath = os.path.join(dirpath, item) - try: + try: os.chown(filepath, uid, gid) except Exception as e: # pragma: no cover logg.debug("could not set %s:%s on %s\n\t%s", user, group, filepath, e) ok = False for item in dirnames: dir_path = os.path.join(dirpath, item) - try: + try: os.chown(dir_path, uid, gid) except Exception as e: # pragma: no cover logg.debug("could not set %s:%s on %s\n\t%s", user, group, dir_path, e) ok = False - try: + try: os.chown(path, uid, gid) except Exception as e: # pragma: no cover logg.debug("could not set %s:%s on %s\n\t%s", user, group, path, e) @@ -2471,14 +2646,14 @@ def clean_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] lines = _log_lines follow = _force ok = self.clean_units(units) return ok and found_all def clean_units(self, units, what = ""): if not what: - what = _what_kind + what = self._only_what[0] ok = True for unit in units: ok = self.clean_unit(unit, what) and ok @@ -2507,7 +2682,7 @@ def log_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] lines = _log_lines follow = _force result = self.log_units(units, lines, follow) @@ -2529,23 +2704,44 @@ def log_unit(self, unit, lines = None, follow = False): if not conf: return -1 return self.log_unit_from(conf, lines, follow) def log_unit_from(self, conf, lines = None, follow = False): + cmd_args = [] log_path = self.get_journal_log_from(conf) if follow: - cmd = [ TAIL_CMD, "-n", str(lines or 10), "-F", log_path ] + tail_cmd = get_exist_path(TAIL_CMDS) + if tail_cmd is None: + print("tail command not found") + return 1 + cmd = [tail_cmd, "-n", str(lines or 10), "-F", log_path] logg.debug("journalctl %s -> %s", conf.name(), cmd) - return os.spawnvp(os.P_WAIT, cmd[0], cmd) # type: ignore + cmd_args = [arg for arg in cmd] # satisfy mypy + return os.execvp(cmd_args[0], cmd_args) elif lines: - cmd = [ TAIL_CMD, "-n", str(lines or 10), log_path ] + tail_cmd = get_exist_path(TAIL_CMDS) + if tail_cmd is None: + print("tail command not found") + return 1 + cmd = [tail_cmd, "-n", str(lines or 10), log_path] logg.debug("journalctl %s -> %s", conf.name(), cmd) - return os.spawnvp(os.P_WAIT, cmd[0], cmd) # type: ignore + cmd_args = [arg for arg in cmd] # satisfy mypy + return os.execvp(cmd_args[0], cmd_args) elif _no_pager: - cmd = [ CAT_CMD, log_path ] + cat_cmd = get_exist_path(CAT_CMDS) + if cat_cmd is None: + print("cat command not found") + return 1 + cmd = [cat_cmd, log_path] logg.debug("journalctl %s -> %s", conf.name(), cmd) - return os.spawnvp(os.P_WAIT, cmd[0], cmd) # type: ignore + cmd_args = [arg for arg in cmd] # satisfy mypy + return os.execvp(cmd_args[0], cmd_args) else: - cmd = [ LESS_CMD, log_path ] + less_cmd = get_exist_path(LESS_CMDS) + if less_cmd is None: + print("less command not found") + return 1 + cmd = [less_cmd, log_path] logg.debug("journalctl %s -> %s", conf.name(), cmd) - return os.spawnvp(os.P_WAIT, cmd[0], cmd) # type: ignore + cmd_args = [arg for arg in cmd] # satisfy mypy + return os.execvp(cmd_args[0], cmd_args) def get_journal_log_from(self, conf): return os_path(self._root, self.get_journal_log(conf)) def get_journal_log(self, conf): @@ -2554,7 +2750,7 @@ def get_journal_log(self, conf): unitname = (conf.name() or "default")+".unit" name = filename or unitname log_folder = expand_path(self._journal_log_folder, conf.root_mode()) - log_file = name.replace(os.path.sep,".") + ".log" + log_file = name.replace(os.path.sep, ".") + ".log" if log_file.startswith("."): log_file = "dot."+log_file return os.path.join(log_folder, log_file) @@ -2565,32 +2761,29 @@ def open_journal_log(self, conf): os.makedirs(log_folder) return open(os.path.join(log_file), "a") def get_WorkingDirectory(self, conf): - return conf.get("Service", "WorkingDirectory", "") + return conf.get(Service, "WorkingDirectory", "") def chdir_workingdir(self, conf): """ if specified then change the working directory """ # the original systemd will start in '/' even if User= is given if self._root: os.chdir(self._root) workingdir = self.get_WorkingDirectory(conf) + mode, workingdir = load_path(workingdir) if workingdir: - ignore = False - if workingdir.startswith("-"): - workingdir = workingdir[1:] - ignore = True into = os_path(self._root, self.expand_special(workingdir, conf)) - try: - logg.debug("chdir workingdir '%s'", into) - os.chdir(into) - return False + try: + logg.debug("chdir workingdir '%s'", into) + os.chdir(into) + return False except Exception as e: - if not ignore: - logg.error("chdir workingdir '%s': %s", into, e) - return into - else: - logg.debug("chdir workingdir '%s': %s", into, e) - return None + if mode.check: + logg.error("chdir workingdir '%s': %s", into, e) + return into + else: + logg.debug("chdir workingdir '%s': %s", into, e) + return None return None - NotifySocket = collections.namedtuple("NotifySocket", ["socket", "socketfile" ]) + NotifySocket = collections.namedtuple("NotifySocket", ["socket", "socketfile"]) def get_notify_socket_from(self, conf, socketfile = None, debug = False): """ creates a notify-socket for the (non-privileged) user """ notify_socket_folder = expand_path(_notify_socket_folder, conf.root_mode()) @@ -2609,7 +2802,7 @@ def get_notify_socket_from(self, conf, socketfile = None, debug = False): socketfile = os.path.join(notify_folder, notify_name77) if len(socketfile) > 100: socketfile = os.path.join(notify_folder, notify_name44) - pref = "zz.%i.%s" % (get_USER_ID(),o22(os.path.basename(notify_socket_folder))) + pref = "zz.%i.%s" % (get_USER_ID(), o22(os.path.basename(notify_socket_folder))) if len(socketfile) > 100: socketfile = os.path.join(get_TMP(), pref, notify_name) if len(socketfile) > 100: @@ -2642,7 +2835,7 @@ def read_notify_socket(self, notify, timeout): assert isinstance(result, bytes) if result: result = result.decode("utf-8") - result_txt = result.replace("\n","|") + result_txt = result.replace("\n", "|") result_len = len(result) logg.debug("read_notify_socket(%s):%s", result_len, result_txt) except socket.timeout as e: @@ -2654,7 +2847,7 @@ def wait_notify_socket(self, notify, timeout, pid = None, pid_file = None): logg.info("no $NOTIFY_SOCKET exists") return {} # - lapseTimeout = max(3, int(timeout / 100)) + lapseTimeout = max(3, int(timeout / 100)) mainpidTimeout = lapseTimeout # Apache sends READY before MAINPID status = "" logg.info("wait $NOTIFY_SOCKET, timeout %s (lapse %s)", timeout, lapseTimeout) @@ -2677,7 +2870,7 @@ def wait_notify_socket(self, notify, timeout, pid = None, pid_file = None): if name in ["STATUS", "ACTIVESTATE", "MAINPID", "READY"]: hint="seen notify %s " % (waiting) logg.debug("%s :%s=%s", hint, name, value) - if status != results.get("STATUS",""): + if status != results.get("STATUS", ""): mainpidTimeout = lapseTimeout status = results.get("STATUS", "") if "READY" not in results: @@ -2715,12 +2908,12 @@ def start_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] init = self._now or self._init return self.start_units(units, init) and found_all def start_units(self, units, init = None): """ fails if any unit does not start - /// SPECIAL: may run the init-loop and + /// SPECIAL: may run the init-loop and stop the named units afterwards """ self.wait_system() done = True @@ -2747,14 +2940,14 @@ def start_unit(self, unit): return False return self.start_unit_from(conf) def get_TimeoutStartSec(self, conf): - timeout = conf.get("Service", "TimeoutSec", strE(DefaultTimeoutStartSec)) - timeout = conf.get("Service", "TimeoutStartSec", timeout) + timeout = conf.get(Service, "TimeoutSec", strE(DefaultTimeoutStartSec)) + timeout = conf.get(Service, "TimeoutStartSec", timeout) return time_to_seconds(timeout, DefaultMaximumTimeout) def get_SocketTimeoutSec(self, conf): - timeout = conf.get("Socket", "TimeoutSec", strE(DefaultTimeoutStartSec)) + timeout = conf.get(Socket, "TimeoutSec", strE(DefaultTimeoutStartSec)) return time_to_seconds(timeout, DefaultMaximumTimeout) def get_RemainAfterExit(self, conf): - return conf.getbool("Service", "RemainAfterExit", "no") + return conf.getbool(Service, "RemainAfterExit", "no") def start_unit_from(self, conf): if not conf: return False if self.syntax_check(conf) > 100: return False @@ -2774,10 +2967,10 @@ def do_start_unit_from(self, conf): def do_start_service_from(self, conf): timeout = self.get_TimeoutStartSec(conf) doRemainAfterExit = self.get_RemainAfterExit(conf) - runs = conf.get("Service", "Type", "simple").lower() + runs = conf.get(Service, "Type", "simple").lower() env = self.get_env(conf) if not self._quiet: - okee = self.exec_check_unit(conf, env, "Service", "Exec") # all... + okee = self.exec_check_unit(conf, env, Service, "Exec") # all... if not okee and _no_reload: return False service_directories = self.create_service_directories(conf) env.update(service_directories) # atleast sshd did check for /run/sshd @@ -2785,30 +2978,30 @@ def do_start_service_from(self, conf): returncode = 0 service_result = "success" if True: - if runs in [ "simple", "forking", "notify", "idle" ]: + if runs in ["simple", "exec", "forking", "notify", "idle"]: env["MAINPID"] = strE(self.read_mainpid_from(conf)) - for cmd in conf.getlist("Service", "ExecStartPre", []): + for cmd in conf.getlist(Service, "ExecStartPre", []): exe, newcmd = self.exec_newcmd(cmd, env, conf) logg.info(" pre-start %s", shell_cmd(newcmd)) forkpid = os.fork() - if not forkpid: + if not forkpid: self.execve_from(conf, newcmd, env) # pragma: no cover run = subprocess_waitpid(forkpid) logg.debug(" pre-start done (%s) <-%s>", - run.returncode or "OK", run.signal or "") + run.returncode or "OK", run.signal or "") if run.returncode and exe.check: logg.error("the ExecStartPre control process exited with error code") active = "failed" - self.write_status_from(conf, AS=active ) - if _what_kind not in ["none", "keep"]: + self.write_status_from(conf, AS=active) + if self._only_what[0] not in ["none", "keep"]: self.remove_service_directories(conf) # cleanup that /run/sshd return False - if runs in [ "oneshot" ]: + if runs in ["oneshot"]: status_file = self.get_status_file_from(conf) if self.get_status_from(conf, "ActiveState", "unknown") == "active": logg.warning("the service was already up once") return True - for cmd in conf.getlist("Service", "ExecStart", []): + for cmd in conf.getlist(Service, "ExecStart", []): exe, newcmd = self.exec_newcmd(cmd, env, conf) logg.info("%s start %s", runs, shell_cmd(newcmd)) forkpid = os.fork() @@ -2816,19 +3009,19 @@ def do_start_service_from(self, conf): os.setsid() # detach child process from parent self.execve_from(conf, newcmd, env) run = subprocess_waitpid(forkpid) - if run.returncode and exe.check: + if run.returncode and exe.check: returncode = run.returncode service_result = "failed" - logg.error("%s start %s (%s) <-%s>", runs, service_result, - run.returncode or "OK", run.signal or "") + logg.error("%s start %s (%s) <-%s>", runs, service_result, + run.returncode or "OK", run.signal or "") break - logg.info("%s start done (%s) <-%s>", runs, - run.returncode or "OK", run.signal or "") + logg.info("%s start done (%s) <-%s>", runs, + run.returncode or "OK", run.signal or "") if True: self.set_status_from(conf, "ExecMainCode", strE(returncode)) active = returncode and "failed" or "active" self.write_status_from(conf, AS=active) - elif runs in [ "simple", "idle" ]: + elif runs in ["simple", "exec", "idle"]: status_file = self.get_status_file_from(conf) pid = self.read_mainpid_from(conf) if self.is_active_pid(pid): @@ -2837,7 +3030,7 @@ def do_start_service_from(self, conf): if doRemainAfterExit: logg.debug("%s RemainAfterExit -> AS=active", runs) self.write_status_from(conf, AS="active") - cmdlist = conf.getlist("Service", "ExecStart", []) + cmdlist = conf.getlist(Service, "ExecStart", []) for idx, cmd in enumerate(cmdlist): logg.debug("ExecStart[%s]: %s", idx, cmd) for cmd in cmdlist: @@ -2855,8 +3048,8 @@ def do_start_service_from(self, conf): time.sleep(MinimumYield) run = subprocess_testpid(forkpid) if run.returncode is not None: - logg.info("%s stopped PID %s (%s) <-%s>", runs, run.pid, - run.returncode or "OK", run.signal or "") + logg.info("%s stopped PID %s (%s) <-%s>", runs, run.pid, + run.returncode or "OK", run.signal or "") if doRemainAfterExit: self.set_status_from(conf, "ExecMainCode", strE(run.returncode)) active = run.returncode and "failed" or "active" @@ -2864,8 +3057,8 @@ def do_start_service_from(self, conf): if run.returncode and exe.check: service_result = "failed" break - elif runs in [ "notify" ]: - # "notify" is the same as "simple" but we create a $NOTIFY_SOCKET + elif runs in ["notify"]: + # "notify" is the same as "simple" but we create a $NOTIFY_SOCKET # and wait for startup completion by checking the socket messages pid_file = self.pid_file_from(conf) pid = self.read_mainpid_from(conf) @@ -2879,7 +3072,7 @@ def do_start_service_from(self, conf): if doRemainAfterExit: logg.debug("%s RemainAfterExit -> AS=active", runs) self.write_status_from(conf, AS="active") - cmdlist = conf.getlist("Service", "ExecStart", []) + cmdlist = conf.getlist(Service, "ExecStart", []) for idx, cmd in enumerate(cmdlist): logg.debug("ExecStart[%s]: %s", idx, cmd) mainpid = None @@ -2900,8 +3093,8 @@ def do_start_service_from(self, conf): time.sleep(MinimumYield) run = subprocess_testpid(forkpid) if run.returncode is not None: - logg.info("%s stopped PID %s (%s) <-%s>", runs, run.pid, - run.returncode or "OK", run.signal or "") + logg.info("%s stopped PID %s (%s) <-%s>", runs, run.pid, + run.returncode or "OK", run.signal or "") if doRemainAfterExit: self.set_status_from(conf, "ExecMainCode", strE(run.returncode)) active = run.returncode and "failed" or "active" @@ -2909,8 +3102,8 @@ def do_start_service_from(self, conf): if run.returncode and exe.check: service_result = "failed" break - if service_result in [ "success" ] and mainpid: - logg.debug("okay, wating on socket for %ss", timeout) + if service_result in ["success"] and mainpid: + logg.debug("okay, waiting on socket for %ss", timeout) results = self.wait_notify_socket(notify, timeout, mainpid, pid_file) if "MAINPID" in results: new_pid = to_intN(results["MAINPID"]) @@ -2924,9 +3117,9 @@ def do_start_service_from(self, conf): env["MAINPID"] = strE(pid) else: service_result = "timeout" # "could not start service" - elif runs in [ "forking" ]: + elif runs in ["forking"]: pid_file = self.pid_file_from(conf) - for cmd in conf.getlist("Service", "ExecStart", []): + for cmd in conf.getlist(Service, "ExecStart", []): exe, newcmd = self.exec_newcmd(cmd, env, conf) if not newcmd: continue logg.info("%s start %s", runs, shell_cmd(newcmd)) @@ -2939,9 +3132,9 @@ def do_start_service_from(self, conf): if run.returncode and exe.check: returncode = run.returncode service_result = "failed" - logg.info("%s stopped PID %s (%s) <-%s>", runs, run.pid, - run.returncode or "OK", run.signal or "") - if pid_file and service_result in [ "success" ]: + logg.info("%s stopped PID %s (%s) <-%s>", runs, run.pid, + run.returncode or "OK", run.signal or "") + if pid_file and service_result in ["success"]: pid = self.wait_pid_file(pid_file) # application PIDFile logg.info("%s start done PID %s [%s]", runs, pid, pid_file) if pid: @@ -2962,28 +3155,28 @@ def do_start_service_from(self, conf): # according to the systemd documentation, a failed start-sequence # should execute the ExecStopPost sequence allowing some cleanup. env["SERVICE_RESULT"] = service_result - for cmd in conf.getlist("Service", "ExecStopPost", []): + for cmd in conf.getlist(Service, "ExecStopPost", []): exe, newcmd = self.exec_newcmd(cmd, env, conf) logg.info("post-fail %s", shell_cmd(newcmd)) forkpid = os.fork() if not forkpid: self.execve_from(conf, newcmd, env) # pragma: no cover run = subprocess_waitpid(forkpid) - logg.debug("post-fail done (%s) <-%s>", - run.returncode or "OK", run.signal or "") - if _what_kind not in ["none", "keep"]: + logg.debug("post-fail done (%s) <-%s>", + run.returncode or "OK", run.signal or "") + if self._only_what[0] not in ["none", "keep"]: self.remove_service_directories(conf) return False else: - for cmd in conf.getlist("Service", "ExecStartPost", []): + for cmd in conf.getlist(Service, "ExecStartPost", []): exe, newcmd = self.exec_newcmd(cmd, env, conf) logg.info("post-start %s", shell_cmd(newcmd)) forkpid = os.fork() if not forkpid: self.execve_from(conf, newcmd, env) # pragma: no cover run = subprocess_waitpid(forkpid) - logg.debug("post-start done (%s) <-%s>", - run.returncode or "OK", run.signal or "") + logg.debug("post-start done (%s) <-%s>", + run.returncode or "OK", run.signal or "") return True def listen_modules(self, *modules): """ [UNIT]... -- listen socket units""" @@ -2998,7 +3191,7 @@ def listen_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.listen_units(units) and found_all def listen_units(self, units): """ fails if any socket does not start """ @@ -3044,7 +3237,7 @@ def do_accept_socket_from(self, conf, sock): logg.debug("%s: accepting %s", conf.name(), sock.fileno()) service_unit = self.get_socket_service_from(conf) service_conf = self.load_unit_conf(service_unit) - if service_conf is None or TestAccept: #pragma: no cover + if service_conf is None or TestAccept: # pragma: no cover if sock.type == socket.SOCK_STREAM: conn, addr = sock.accept() data = conn.recv(1024) @@ -3062,17 +3255,17 @@ def do_accept_socket_from(self, conf, sock): return self.do_start_service_from(service_conf) def get_socket_service_from(self, conf): socket_unit = conf.name() - accept = conf.getbool("Socket", "Accept", "no") + accept = conf.getbool(Socket, "Accept", "no") service_type = accept and "@.service" or ".service" service_name = path_replace_extension(socket_unit, ".socket", service_type) - service_unit = conf.get("Socket", "Service", service_name) + service_unit = conf.get(Socket, Service, service_name) logg.debug("socket %s -> service %s", socket_unit, service_unit) return service_unit def do_start_socket_from(self, conf): runs = "socket" timeout = self.get_SocketTimeoutSec(conf) - accept = conf.getbool("Socket", "Accept", "no") - stream = conf.get("Socket", "ListenStream", "") + accept = conf.getbool(Socket, "Accept", "no") + stream = conf.get(Socket, "ListenStream", "") service_unit = self.get_socket_service_from(conf) service_conf = self.load_unit_conf(service_unit) if service_conf is None: @@ -3081,22 +3274,22 @@ def do_start_socket_from(self, conf): return False env = self.get_env(conf) if not self._quiet: - okee = self.exec_check_unit(conf, env, "Socket", "Exec") # all... + okee = self.exec_check_unit(conf, env, Socket, "Exec") # all... if not okee and _no_reload: return False if True: - for cmd in conf.getlist("Socket", "ExecStartPre", []): + for cmd in conf.getlist(Socket, "ExecStartPre", []): exe, newcmd = self.exec_newcmd(cmd, env, conf) logg.info(" pre-start %s", shell_cmd(newcmd)) forkpid = os.fork() - if not forkpid: + if not forkpid: self.execve_from(conf, newcmd, env) # pragma: no cover run = subprocess_waitpid(forkpid) logg.debug(" pre-start done (%s) <-%s>", - run.returncode or "OK", run.signal or "") + run.returncode or "OK", run.signal or "") if run.returncode and exe.check: logg.error("the ExecStartPre control process exited with error code") active = "failed" - self.write_status_from(conf, AS=active ) + self.write_status_from(conf, AS=active) return False # service_directories = self.create_service_directories(conf) # env.update(service_directories) @@ -3116,7 +3309,7 @@ def do_start_socket_from(self, conf): if not self.is_active_from(service_conf): service_result = "failed" state = service_result - if service_result in ["success"]: + if service_result in ["success"]: state = "active" self.write_status_from(conf, AS=state) # POST sequence @@ -3124,37 +3317,37 @@ def do_start_socket_from(self, conf): # according to the systemd documentation, a failed start-sequence # should execute the ExecStopPost sequence allowing some cleanup. env["SERVICE_RESULT"] = service_result - for cmd in conf.getlist("Socket", "ExecStopPost", []): + for cmd in conf.getlist(Socket, "ExecStopPost", []): exe, newcmd = self.exec_newcmd(cmd, env, conf) logg.info("post-fail %s", shell_cmd(newcmd)) forkpid = os.fork() if not forkpid: self.execve_from(conf, newcmd, env) # pragma: no cover run = subprocess_waitpid(forkpid) - logg.debug("post-fail done (%s) <-%s>", - run.returncode or "OK", run.signal or "") + logg.debug("post-fail done (%s) <-%s>", + run.returncode or "OK", run.signal or "") return False else: - for cmd in conf.getlist("Socket", "ExecStartPost", []): + for cmd in conf.getlist(Socket, "ExecStartPost", []): exe, newcmd = self.exec_newcmd(cmd, env, conf) logg.info("post-start %s", shell_cmd(newcmd)) forkpid = os.fork() if not forkpid: self.execve_from(conf, newcmd, env) # pragma: no cover run = subprocess_waitpid(forkpid) - logg.debug("post-start done (%s) <-%s>", - run.returncode or "OK", run.signal or "") + logg.debug("post-start done (%s) <-%s>", + run.returncode or "OK", run.signal or "") return True def create_socket(self, conf): unsupported = ["ListenUSBFunction", "ListenMessageQueue", "ListenNetlink"] - unsupported += [ "ListenSpecial", "ListenFIFO", "ListenSequentialPacket"] + unsupported += ["ListenSpecial", "ListenFIFO", "ListenSequentialPacket"] for item in unsupported: - if conf.get("Socket", item, ""): + if conf.get(Socket, item, ""): logg.warning("%s: %s sockets are not implemented", conf.name(), item) self.error |= NOT_OK return None - vListenDatagram = conf.get("Socket", "ListenDatagram", "") - vListenStream = conf.get("Socket", "ListenStream", "") + vListenDatagram = conf.get(Socket, "ListenDatagram", "") + vListenStream = conf.get(Socket, "ListenStream", "") address = vListenStream or vListenDatagram m = re.match(r"(/.*)", address) if m: @@ -3194,11 +3387,11 @@ def create_unix_socket(self, conf, path, dgram): sock_stream = dgram and socket.SOCK_DGRAM or socket.SOCK_STREAM sock = socket.socket(socket.AF_UNIX, sock_stream) try: - dirmode = conf.get("Socket", "DirectoryMode", "0755") - mode = conf.get("Socket", "SocketMode", "0666") - user = conf.get("Socket", "SocketUser", "") - group = conf.get("Socket", "SocketGroup", "") - symlinks = conf.getlist("Socket", "SymLinks", []) + dirmode = conf.get(Socket, "DirectoryMode", "0755") + mode = conf.get(Socket, "SocketMode", "0666") + user = conf.get(Socket, "SocketUser", "") + group = conf.get(Socket, "SocketGroup", "") + symlinks = conf.getlist(Socket, "SymLinks", []) dirpath = os.path.dirname(path) if not os.path.isdir(dirpath): os.makedirs(dirpath, int(dirmode, 8)) @@ -3279,16 +3472,16 @@ def expand_list(self, group_lines, conf): result.append(self.expand_special(item, conf)) return result def get_User(self, conf): - return self.expand_special(conf.get("Service", "User", ""), conf) + return self.expand_special(conf.get(Service, "User", ""), conf) def get_Group(self, conf): - return self.expand_special(conf.get("Service", "Group", ""), conf) + return self.expand_special(conf.get(Service, "Group", ""), conf) def get_SupplementaryGroups(self, conf): - return self.expand_list(conf.getlist("Service", "SupplementaryGroups", []), conf) + return self.expand_list(conf.getlist(Service, "SupplementaryGroups", []), conf) def skip_journal_log(self, conf): - if self.get_unit_type(conf.name()) not in [ "service" ]: - return True - std_out = conf.get("Service", "StandardOutput", DefaultStandardOutput) - std_err = conf.get("Service", "StandardError", DefaultStandardError) + if self.get_unit_type(conf.name()) not in ["service"]: + return True + std_out = conf.get(Service, "StandardOutput", DefaultStandardOutput) + std_err = conf.get(Service, "StandardError", DefaultStandardError) out, err = False, False if std_out in ["null"]: out = True if std_out.startswith("file:"): out = True @@ -3299,9 +3492,9 @@ def skip_journal_log(self, conf): return out and err def dup2_journal_log(self, conf): msg = "" - std_inp = conf.get("Service", "StandardInput", DefaultStandardInput) - std_out = conf.get("Service", "StandardOutput", DefaultStandardOutput) - std_err = conf.get("Service", "StandardError", DefaultStandardError) + std_inp = conf.get(Service, "StandardInput", DefaultStandardInput) + std_out = conf.get(Service, "StandardOutput", DefaultStandardOutput) + std_err = conf.get(Service, "StandardError", DefaultStandardError) inp, out, err = None, None, None if std_inp in ["null"]: inp = open(_dev_null, "r") @@ -3367,9 +3560,11 @@ def dup2_journal_log(self, conf): os.dup2(err.fileno(), sys.stderr.fileno()) def execve_from(self, conf, cmd, env): """ this code is commonly run in a child process // returns exit-code""" - runs = conf.get("Service", "Type", "simple").lower() + # | + runs = conf.get(Service, "Type", "simple").lower() # logg.debug("%s process for %s => %s", runs, strE(conf.name()), strQ(conf.filename())) self.dup2_journal_log(conf) + cmd_args = [] # runuser = self.get_User(conf) rungroup = self.get_Group(conf) @@ -3383,7 +3578,7 @@ def execve_from(self, conf, cmd, env): env.update(envs) # set $HOME to ~$USER try: if EXEC_SPAWN: - cmd_args = [ arg for arg in cmd ] # satisfy mypy + cmd_args = [arg for arg in cmd] # satisfy mypy exitcode = os.spawnvpe(os.P_WAIT, cmd[0], cmd_args, env) sys.exit(exitcode) else: # pragma: no cover @@ -3397,7 +3592,7 @@ def test_start_unit(self, unit): conf = self.load_unit_conf(unit) if not conf: return None env = self.get_env(conf) - for cmd in conf.getlist("Service", "ExecStart", []): + for cmd in conf.getlist(Service, "ExecStart", []): exe, newcmd = self.exec_newcmd(cmd, env, conf) self.execve_from(conf, newcmd, env) return None @@ -3414,7 +3609,7 @@ def stop_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.stop_units(units) and found_all def stop_units(self, units): """ fails if any unit fails to stop """ @@ -3435,8 +3630,8 @@ def stop_unit(self, unit): return self.stop_unit_from(conf) def get_TimeoutStopSec(self, conf): - timeout = conf.get("Service", "TimeoutSec", strE(DefaultTimeoutStartSec)) - timeout = conf.get("Service", "TimeoutStopSec", timeout) + timeout = conf.get(Service, "TimeoutSec", strE(DefaultTimeoutStartSec)) + timeout = conf.get(Service, "TimeoutStopSec", timeout) return time_to_seconds(timeout, DefaultMaximumTimeout) def stop_unit_from(self, conf): if not conf: return False @@ -3455,29 +3650,30 @@ def do_stop_unit_from(self, conf): logg.error("stop not implemented for unit type: %s", conf.name()) return False def do_stop_service_from(self, conf): + # | timeout = self.get_TimeoutStopSec(conf) - runs = conf.get("Service", "Type", "simple").lower() + runs = conf.get(Service, "Type", "simple").lower() env = self.get_env(conf) if not self._quiet: - okee = self.exec_check_unit(conf, env, "Service", "ExecStop") + okee = self.exec_check_unit(conf, env, Service, "ExecStop") if not okee and _no_reload: return False service_directories = self.env_service_directories(conf) env.update(service_directories) returncode = 0 service_result = "success" - if runs in [ "oneshot" ]: + if runs in ["oneshot"]: status_file = self.get_status_file_from(conf) if self.get_status_from(conf, "ActiveState", "unknown") == "inactive": logg.warning("the service is already down once") return True - for cmd in conf.getlist("Service", "ExecStop", []): + for cmd in conf.getlist(Service, "ExecStop", []): exe, newcmd = self.exec_newcmd(cmd, env, conf) logg.info("%s stop %s", runs, shell_cmd(newcmd)) forkpid = os.fork() if not forkpid: self.execve_from(conf, newcmd, env) # pragma: no cover run = subprocess_waitpid(forkpid) - if run.returncode and exe.check: + if run.returncode and exe.check: returncode = run.returncode service_result = "failed" break @@ -3487,19 +3683,19 @@ def do_stop_service_from(self, conf): self.write_status_from(conf, AS="failed") else: self.clean_status_from(conf) # "inactive" - ### fallback Stop => Kill for ["simple","notify","forking"] - elif not conf.getlist("Service", "ExecStop", []): + # fallback Stop => Kill for ["simple","notify","forking"] + elif not conf.getlist(Service, "ExecStop", []): logg.info("no ExecStop => systemctl kill") if True: self.do_kill_unit_from(conf) self.clean_pid_file_from(conf) self.clean_status_from(conf) # "inactive" - elif runs in [ "simple", "notify", "idle" ]: + elif runs in ["simple", "exec", "notify", "idle"]: status_file = self.get_status_file_from(conf) size = os.path.exists(status_file) and os.path.getsize(status_file) logg.info("STATUS %s %s", status_file, size) pid = 0 - for cmd in conf.getlist("Service", "ExecStop", []): + for cmd in conf.getlist(Service, "ExecStop", []): env["MAINPID"] = strE(self.read_mainpid_from(conf)) exe, newcmd = self.exec_newcmd(cmd, env, conf) logg.info("%s stop %s", runs, shell_cmd(newcmd)) @@ -3525,10 +3721,10 @@ def do_stop_service_from(self, conf): if not pid or not pid_exists(pid) or pid_zombie(pid): self.clean_pid_file_from(conf) self.clean_status_from(conf) # "inactive" - elif runs in [ "forking" ]: + elif runs in ["forking"]: status_file = self.get_status_file_from(conf) pid_file = self.pid_file_from(conf) - for cmd in conf.getlist("Service", "ExecStop", []): + for cmd in conf.getlist(Service, "ExecStop", []): # active = self.is_active_from(conf) if pid_file: new_pid = self.read_mainpid_from(conf) @@ -3566,22 +3762,22 @@ def do_stop_service_from(self, conf): # POST sequence if not self.is_active_from(conf): env["SERVICE_RESULT"] = service_result - for cmd in conf.getlist("Service", "ExecStopPost", []): + for cmd in conf.getlist(Service, "ExecStopPost", []): exe, newcmd = self.exec_newcmd(cmd, env, conf) logg.info("post-stop %s", shell_cmd(newcmd)) forkpid = os.fork() if not forkpid: self.execve_from(conf, newcmd, env) # pragma: no cover run = subprocess_waitpid(forkpid) - logg.debug("post-stop done (%s) <-%s>", - run.returncode or "OK", run.signal or "") - if _what_kind not in ["none", "keep"]: + logg.debug("post-stop done (%s) <-%s>", + run.returncode or "OK", run.signal or "") + if self._only_what[0] not in ["none", "keep"]: self.remove_service_directories(conf) return service_result == "success" def do_stop_socket_from(self, conf): runs = "socket" timeout = self.get_SocketTimeoutSec(conf) - accept = conf.getbool("Socket", "Accept", "no") + accept = conf.getbool(Socket, "Accept", "no") service_unit = self.get_socket_service_from(conf) service_conf = self.load_unit_conf(service_unit) if service_conf is None: @@ -3590,7 +3786,7 @@ def do_stop_socket_from(self, conf): return False env = self.get_env(conf) if not self._quiet: - okee = self.exec_check_unit(conf, env, "Socket", "ExecStop") + okee = self.exec_check_unit(conf, env, Socket, "ExecStop") if not okee and _no_reload: return False if not accept: # we do not listen but have the service started right away @@ -3604,15 +3800,15 @@ def do_stop_socket_from(self, conf): # POST sequence if not self.is_active_from(conf): env["SERVICE_RESULT"] = service_result - for cmd in conf.getlist("Socket", "ExecStopPost", []): + for cmd in conf.getlist(Socket, "ExecStopPost", []): exe, newcmd = self.exec_newcmd(cmd, env, conf) logg.info("post-stop %s", shell_cmd(newcmd)) forkpid = os.fork() if not forkpid: self.execve_from(conf, newcmd, env) # pragma: no cover run = subprocess_waitpid(forkpid) - logg.debug("post-stop done (%s) <-%s>", - run.returncode or "OK", run.signal or "") + logg.debug("post-stop done (%s) <-%s>", + run.returncode or "OK", run.signal or "") return service_result == "success" def wait_vanished_pid(self, pid, timeout): if not pid: @@ -3641,7 +3837,7 @@ def reload_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.reload_units(units) and found_all def reload_units(self, units): """ fails if any unit fails to reload """ @@ -3683,10 +3879,10 @@ def do_reload_unit_from(self, conf): logg.error("reload not implemented for unit type: %s", conf.name()) return False def do_reload_service_from(self, conf): - runs = conf.get("Service", "Type", "simple").lower() + runs = conf.get(Service, "Type", "simple").lower() env = self.get_env(conf) if not self._quiet: - okee = self.exec_check_unit(conf, env, "Service", "ExecReload") + okee = self.exec_check_unit(conf, env, Service, "ExecReload") if not okee and _no_reload: return False initscript = conf.filename() if self.is_sysv_file(initscript): @@ -3708,11 +3904,11 @@ def do_reload_service_from(self, conf): return True service_directories = self.env_service_directories(conf) env.update(service_directories) - if runs in [ "simple", "notify", "forking", "idle" ]: + if runs in ["simple", "exec", "notify", "forking", "idle"]: if not self.is_active_from(conf): logg.info("no reload on inactive service %s", conf.name()) return True - for cmd in conf.getlist("Service", "ExecReload", []): + for cmd in conf.getlist(Service, "ExecReload", []): env["MAINPID"] = strE(self.read_mainpid_from(conf)) exe, newcmd = self.exec_newcmd(cmd, env, conf) logg.info("%s reload %s", runs, shell_cmd(newcmd)) @@ -3720,13 +3916,13 @@ def do_reload_service_from(self, conf): if not forkpid: self.execve_from(conf, newcmd, env) # pragma: no cover run = subprocess_waitpid(forkpid) - if run.returncode and exe.check: - logg.error("Job for %s failed because the control process exited with error code. (%s)", - conf.name(), run.returncode) + if run.returncode and exe.check: + logg.error("Job for %s failed because the control process exited with error code. (%s)", + conf.name(), run.returncode) return False time.sleep(MinimumYield) return True - elif runs in [ "oneshot" ]: + elif runs in ["oneshot"]: logg.debug("ignored run type '%s' for reload", runs) return True else: @@ -3745,7 +3941,7 @@ def restart_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.restart_units(units) and found_all def restart_units(self, units): """ fails if any unit fails to restart """ @@ -3793,7 +3989,7 @@ def try_restart_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.try_restart_units(units) and found_all def try_restart_units(self, units): """ fails if any module fails to try-restart """ @@ -3830,7 +4026,7 @@ def reload_or_restart_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.reload_or_restart_units(units) and found_all def reload_or_restart_units(self, units): """ fails if any unit does not reload-or-restart """ @@ -3861,7 +4057,7 @@ def do_reload_or_restart_unit_from(self, conf): # try: self.stop_unit_from(conf) # except Exception as e: pass return self.do_start_unit_from(conf) - elif conf.getlist("Service", "ExecReload", []): + elif conf.getlist(Service, "ExecReload", []): logg.info("found service to have ExecReload -> 'reload'") return self.do_reload_unit_from(conf) else: @@ -3880,7 +4076,7 @@ def reload_or_try_restart_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.reload_or_try_restart_units(units) and found_all def reload_or_try_restart_units(self, units): """ fails if any unit fails to reload-or-try-restart """ @@ -3904,7 +4100,7 @@ def reload_or_try_restart_unit_from(self, conf): logg.info(" reload-or-try-restart unit %s => %s", conf.name(), strQ(conf.filename())) return self.do_reload_or_try_restart_unit_from(conf) def do_reload_or_try_restart_unit_from(self, conf): - if conf.getlist("Service", "ExecReload", []): + if conf.getlist(Service, "ExecReload", []): return self.do_reload_unit_from(conf) elif not self.is_active_from(conf): return True @@ -3923,7 +4119,7 @@ def kill_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.kill_units(units) and found_all def kill_units(self, units): """ fails if any unit could not be killed """ @@ -3981,7 +4177,7 @@ def do_kill_unit_from(self, conf): for pid in pidlist: if pid != mainpid: self._kill_pid(pid, kill_signal) - if doSendSIGHUP: + if doSendSIGHUP: logg.info("stop SendSIGHUP to PIDs %s", pidlist) for pid in pidlist: self._kill_pid(pid, signal.SIGHUP) @@ -4001,7 +4197,7 @@ def do_kill_unit_from(self, conf): if dead or not doSendSIGKILL: logg.info("done kill PID %s %s", mainpid, dead and "OK") return dead - if useKillMode in [ "control-group", "mixed" ]: + if useKillMode in ["control-group", "mixed"]: logg.info("hard kill PIDs %s", pidlist) for pid in pidlist: if pid != mainpid: @@ -4016,7 +4212,7 @@ def do_kill_unit_from(self, conf): logg.info("done hard kill PID %s %s", mainpid, dead and "OK") return dead def _kill_pid(self, pid, kill_signal = None): - try: + try: sig = kill_signal or signal.SIGTERM os.kill(pid, sig) except OSError as e: @@ -4041,6 +4237,7 @@ def is_active_modules(self, *modules): # However: # TODO! BUG in original systemctl! # documentation says " exit code 0 if at least one is active" # and "Unless --quiet is specified, print the unit state" + # | units = [] results = [] for module in modules: @@ -4049,19 +4246,19 @@ def is_active_modules(self, *modules): logg.error("Unit %s not found.", unit_of(module)) # self.error |= NOT_FOUND self.error |= NOT_ACTIVE - results += [ "inactive" ] + results += ["inactive"] continue for unit in units: - active = self.get_active_unit(unit) + active = self.get_active_unit(unit) enabled = self.enabled_unit(unit) if enabled != "enabled" and ACTIVE_IF_ENABLED: active = "inactive" # "unknown" - results += [ active ] + results += [active] break - ## how it should work: + # how it should work: status = "active" in results - ## how 'systemctl' works: - non_active = [ result for result in results if result != "active" ] + # how 'systemctl' works: + non_active = [result for result in results if result != "active"] if non_active: self.error |= NOT_ACTIVE if non_active: @@ -4133,7 +4330,7 @@ def get_active_target(self, target): """ returns 'active' 'inactive' 'failed' 'unknown' """ if target in self.get_active_target_list(): status = self.is_system_running() - if status in [ "running" ]: + if status in ["running"]: return "active" return "inactive" else: @@ -4151,8 +4348,8 @@ def get_active_target(self, target): def get_active_target_list(self): current_target = self.get_default_target() target_list = self.get_target_list(current_target) - target_list += [ DefaultUnit ] # upper end - target_list += [ SysInitTarget ] # lower end + target_list += [DefaultUnit] # upper end + target_list += [SysInitTarget] # lower end return target_list def get_substate_from(self, conf): """ returns 'running' 'exited' 'dead' 'failed' 'plugged' 'mounted' """ @@ -4165,7 +4362,7 @@ def get_substate_from(self, conf): if self.getsize(status_file): state = self.get_status_from(conf, "ActiveState", "") if state: - if state in [ "active" ]: + if state in ["active"]: return self.get_status_from(conf, "SubState", "running") else: return self.get_status_from(conf, "SubState", "dead") @@ -4188,14 +4385,14 @@ def is_failed_modules(self, *modules): if not units: logg.error("Unit %s not found.", unit_of(module)) # self.error |= NOT_FOUND - results += [ "inactive" ] + results += ["inactive"] continue for unit in units: - active = self.get_active_unit(unit) + active = self.get_active_unit(unit) enabled = self.enabled_unit(unit) - if enabled != "enabled" and ACTIVE_IF_ENABLED: + if enabled != "enabled" and ACTIVE_IF_ENABLED: active = "inactive" - results += [ active ] + results += [active] break if "failed" in results: self.error = 0 @@ -4267,7 +4464,7 @@ def status_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] result = self.status_units(units) # if not found_all: # self.error |= NOT_OK | NOT_ACTIVE # 3 @@ -4276,7 +4473,7 @@ def status_modules(self, *modules): def status_units(self, units): """ concatenates the status output of all units and the last non-successful statuscode """ - status = 0 + status = 0 result = "" for unit in units: status1, result1 = self.status_unit(unit) @@ -4284,7 +4481,7 @@ def status_units(self, units): if result: result += "\n\n" result += result1 if status: - self.error |= NOT_OK | NOT_ACTIVE # 3 + self.error |= NOT_OK | NOT_ACTIVE # 3 return result def status_unit(self, unit): conf = self.get_unit_conf(unit) @@ -4320,7 +4517,7 @@ def cat_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] result = self.cat_units(units) if not found_all: self.error |= NOT_OK @@ -4333,9 +4530,9 @@ def cat_units(self, units): if not text: done = False else: - if result: - result += "\n\n" - result += text + if result: + result += "\n\n" + result += text if not done: self.error = NOT_OK return result @@ -4344,7 +4541,7 @@ def cat_unit(self, unit): unit_file = self.unit_file(unit) if unit_file: return open(unit_file).read() - logg.error("No files found for %s", unit) + logg.error("No files found for %s", unit) except Exception as e: print("Unit {} is not-loaded: {}".format(unit, e)) self.error |= NOT_OK @@ -4357,7 +4554,7 @@ def load_preset_files(self, module = None): # -> [ preset-file-names,... ] self._preset_file_list = {} assert self._preset_file_list is not None for folder in self.preset_folders(): - if not folder: + if not folder: continue if self._root: folder = os_path(self._root, folder) @@ -4401,7 +4598,7 @@ def preset_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.preset_units(units) and found_all def preset_units(self, units): """ fails if any unit could not be changed """ @@ -4425,7 +4622,7 @@ def preset_units(self, units): logg.warning("failed to disable %s", unit) fails += 1 return not fails and not not found - def system_preset_all(self, *modules): + def preset_all_modules(self, *modules): """ 'preset' all services enable or disable services according to *.preset files """ @@ -4437,14 +4634,14 @@ def system_preset_all(self, *modules): return self.preset_units(units) and found_all def wanted_from(self, conf, default = None): if not conf: return default - return conf.get("Install", "WantedBy", default, True) + return conf.get(Install, "WantedBy", default, True) def enablefolders(self, wanted): if self.user_mode(): for folder in self.user_folders(): - yield self.default_enablefolder(wanted, folder) + yield self.default_enablefolder(wanted, folder) if True: for folder in self.system_folders(): - yield self.default_enablefolder(wanted, folder) + yield self.default_enablefolder(wanted, folder) def enablefolder(self, wanted): if self.user_mode(): user_folder = self.user_folder() @@ -4453,7 +4650,7 @@ def enablefolder(self, wanted): return self.default_enablefolder(wanted) def default_enablefolder(self, wanted, basefolder = None): basefolder = basefolder or self.system_folder() - if not wanted: + if not wanted: return wanted if not wanted.endswith(".wants"): wanted = wanted + ".wants" @@ -4470,9 +4667,9 @@ def enable_modules(self, *modules): found_all = False continue for unit in matched: - logg.info("matched %s", unit) #++ + logg.info("matched %s", unit) # ++ if unit not in units: - units += [ unit ] + units += [unit] return self.enable_units(units) and found_all def enable_units(self, units): self.wait_system() @@ -4481,7 +4678,7 @@ def enable_units(self, units): if not self.enable_unit(unit): done = False elif self._now: - self.start_unit(unit) + self.start_unit(unit) return done def enable_unit(self, unit): conf = self.load_unit_conf(unit) @@ -4503,7 +4700,7 @@ def enable_unit(self, unit): return self.enable_unit_from(conf) def enable_unit_from(self, conf): wanted = self.wanted_from(conf) - if not wanted and not self._force: + if not wanted and not self._force: logg.debug("%s has no target", conf.name()) return False # "static" is-enabled target = wanted or self.get_default_target() @@ -4547,7 +4744,7 @@ def _enable_unit_sysv(self, unit_file, rc_folder): nameS = "S50"+name nameK = "K50"+name if not os.path.isdir(rc_folder): - os.makedirs(rc_folder) + os.makedirs(rc_folder) # do not double existing entries for found in os.listdir(rc_folder): m = re.match(r"S\d\d(.*)", found) @@ -4576,14 +4773,16 @@ def disable_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.disable_units(units) and found_all def disable_units(self, units): self.wait_system() done = True for unit in units: if not self.disable_unit(unit): - done = False + done = False + elif self._now: + self.stop_unit(unit) return done def disable_unit(self, unit): conf = self.load_unit_conf(unit) @@ -4643,19 +4842,19 @@ def _disable_unit_sysv(self, unit_file, rc_folder): nameK = found target = os.path.join(rc_folder, nameS) if os.path.exists(target): - os.unlink(target) + os.unlink(target) target = os.path.join(rc_folder, nameK) if os.path.exists(target): - os.unlink(target) + os.unlink(target) return True def is_enabled_sysv(self, unit_file): name = os.path.basename(unit_file) target = os.path.join(self.rc3_root_folder(), "S50%s" % name) if os.path.exists(target): - return True + return True return False def is_enabled_modules(self, *modules): - """ [UNIT]... -- check if these units are enabled + """ [UNIT]... -- check if these units are enabled returns True if any of them is enabled.""" found_all = True units = [] @@ -4668,16 +4867,16 @@ def is_enabled_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.is_enabled_units(units) # and found_all def is_enabled_units(self, units): """ true if any is enabled, and a list of infos """ result = False infos = [] for unit in units: - infos += [ self.enabled_unit(unit) ] + infos += [self.enabled_unit(unit)] if self.is_enabled(unit): - result = True + result = True if not result: self.error |= NOT_OK return infos @@ -4703,7 +4902,7 @@ def enabled_from(self, conf): unit_file = strE(conf.filename()) if self.is_sysv_file(unit_file): state = self.is_enabled_sysv(unit_file) - if state: + if state: return "enabled" return "disabled" return self.get_enabled_from(conf) @@ -4734,14 +4933,14 @@ def mask_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.mask_units(units) and found_all def mask_units(self, units): self.wait_system() done = True for unit in units: if not self.mask_unit(unit): - done = False + done = False return done def mask_unit(self, unit): unit_file = self.unit_file(unit) @@ -4784,10 +4983,10 @@ def mask_folder(self): def mask_folders(self): if self.user_mode(): for folder in self.user_folders(): - yield folder + yield folder if True: for folder in self.system_folders(): - yield folder + yield folder def unmask_modules(self, *modules): """ [UNIT]... -- unmask non-startable units """ found_all = True @@ -4801,14 +5000,14 @@ def unmask_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.unmask_units(units) and found_all def unmask_units(self, units): self.wait_system() done = True for unit in units: if not self.unmask_unit(unit): - done = False + done = False return done def unmask_unit(self, unit): unit_file = self.unit_file(unit) @@ -4851,21 +5050,19 @@ def list_dependencies_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.list_dependencies_units(units) # and found_all def list_dependencies_units(self, units): - if self._now: - return self.list_start_dependencies_units(units) result = [] for unit in units: if result: - result += [ "", "" ] + result += ["", ""] result += self.list_dependencies_unit(unit) return result def list_dependencies_unit(self, unit): result = [] for line in self.list_dependencies(unit, ""): - result += [ line ] + result += [line] return result def list_dependencies(self, unit, indent = None, mark = None, loop = []): mapping = {} @@ -4878,8 +5075,8 @@ def list_dependencies(self, unit, indent = None, mark = None, loop = []): mapping[".wants"] = ".wanted to start" mapping["PropagateReloadTo"] = "(to be reloaded as well)" mapping["Conflicts"] = "(to be stopped on conflict)" - restrict = ["Requires", "Requisite", "ConsistsOf", "Wants", - "BindsTo", ".requires", ".wants"] + restrict = ["Requires", "Requisite", "ConsistsOf", "Wants", + "BindsTo", ".requires", ".wants"] indent = indent or "" mark = mark or "" deps = self.get_dependencies_unit(unit) @@ -4890,7 +5087,7 @@ def list_dependencies(self, unit, indent = None, mark = None, loop = []): yield "%s(%s): %s" % (indent, unit, mark) else: yield "%s%s: %s" % (indent, unit, mark) - for stop_recursion in [ "Conflict", "conflict", "reloaded", "Propagate" ]: + for stop_recursion in ["Conflict", "conflict", "reloaded", "Propagate"]: if stop_recursion in mark: return for dep in deps: @@ -4905,19 +5102,19 @@ def list_dependencies(self, unit, indent = None, mark = None, loop = []): continue if new_mark in mapping: new_mark = mapping[new_mark] - restrict = ["Requires", "Wants", "Requisite", "BindsTo", "PartOf", "ConsistsOf", - ".requires", ".wants"] + restrict = ["Requires", "Wants", "Requisite", "BindsTo", "PartOf", "ConsistsOf", + ".requires", ".wants"] for line in self.list_dependencies(dep, new_indent, new_mark, new_loop): yield line def get_dependencies_unit(self, unit, styles = None): - styles = styles or [ "Requires", "Wants", "Requisite", "BindsTo", "PartOf", "ConsistsOf", - ".requires", ".wants", "PropagateReloadTo", "Conflicts", ] + styles = styles or ["Requires", "Wants", "Requisite", "BindsTo", "PartOf", "ConsistsOf", + ".requires", ".wants", "PropagateReloadTo", "Conflicts", ] conf = self.get_unit_conf(unit) deps = {} for style in styles: if style.startswith("."): for folder in self.sysd_folders(): - if not folder: + if not folder: continue require_path = os.path.join(folder, unit + style) if self._root: @@ -4927,27 +5124,27 @@ def get_dependencies_unit(self, unit, styles = None): if required not in deps: deps[required] = style else: - for requirelist in conf.getlist("Unit", style, []): + for requirelist in conf.getlist(Unit, style, []): for required in requirelist.strip().split(" "): deps[required.strip()] = style return deps def get_required_dependencies(self, unit, styles = None): - styles = styles or [ "Requires", "Wants", "Requisite", "BindsTo", - ".requires", ".wants" ] + styles = styles or ["Requires", "Wants", "Requisite", "BindsTo", + ".requires", ".wants"] return self.get_dependencies_unit(unit, styles) def get_start_dependencies(self, unit, styles = None): # pragma: no cover """ the list of services to be started as well / TODO: unused """ styles = styles or ["Requires", "Wants", "Requisite", "BindsTo", "PartOf", "ConsistsOf", - ".requires", ".wants"] + ".requires", ".wants"] deps = {} unit_deps = self.get_dependencies_unit(unit) for dep_unit, dep_style in unit_deps.items(): if dep_style in styles: if dep_unit in deps: if dep_style not in deps[dep_unit]: - deps[dep_unit].append( dep_style) + deps[dep_unit].append(dep_style) else: - deps[dep_unit] = [ dep_style ] + deps[dep_unit] = [dep_style] next_deps = self.get_start_dependencies(dep_unit) for dep, styles in next_deps.items(): for style in styles: @@ -4955,8 +5152,12 @@ def get_start_dependencies(self, unit, styles = None): # pragma: no cover if style not in deps[dep]: deps[dep].append(style) else: - deps[dep] = [ style ] + deps[dep] = [style] return deps + def list_start_dependencies_modules(self, *modules): + """ [UNIT]... show the dependency tree (experimental)" + """ + return self.list_start_dependencies_units(list(modules)) def list_start_dependencies_units(self, units): unit_order = [] deps = {} @@ -4969,9 +5170,9 @@ def list_start_dependencies_units(self, units): for dep_style in dep_styles: if dep_unit in deps: if dep_style not in deps[dep_unit]: - deps[dep_unit].append( dep_style) + deps[dep_unit].append(dep_style) else: - deps[dep_unit] = [ dep_style ] + deps[dep_unit] = [dep_style] deps_conf = [] for dep in deps: if dep in unit_order: @@ -4980,19 +5181,19 @@ def list_start_dependencies_units(self, units): if conf.loaded(): deps_conf.append(conf) for unit in unit_order: - deps[unit] = [ "Requested" ] + deps[unit] = ["Requested"] conf = self.get_unit_conf(unit) if conf.loaded(): deps_conf.append(conf) result = [] sortlist = conf_sortedAfter(deps_conf, cmp=compareAfter) for item in sortlist: - line = (item.name(), "(%s)" % (" ".join(deps[item.name()]))) + line = (item.name(), "(%s)" % (" ".join(deps[item.name()]))) result.append(line) return result def sortedAfter(self, unitlist): """ get correct start order for the unit list (ignoring masked units) """ - conflist = [ self.get_unit_conf(unit) for unit in unitlist ] + conflist = [self.get_unit_conf(unit) for unit in unitlist] if True: conflist = [] for unit in unitlist: @@ -5002,10 +5203,10 @@ def sortedAfter(self, unitlist): continue conflist.append(conf) sortlist = conf_sortedAfter(conflist) - return [ item.name() for item in sortlist ] + return [item.name() for item in sortlist] def sortedBefore(self, unitlist): """ get correct start order for the unit list (ignoring masked units) """ - conflist = [ self.get_unit_conf(unit) for unit in unitlist ] + conflist = [self.get_unit_conf(unit) for unit in unitlist] if True: conflist = [] for unit in unitlist: @@ -5015,8 +5216,8 @@ def sortedBefore(self, unitlist): continue conflist.append(conf) sortlist = conf_sortedAfter(reversed(conflist)) - return [ item.name() for item in reversed(sortlist) ] - def system_daemon_reload(self): + return [item.name() for item in reversed(sortlist)] + def daemon_reload_target(self): """ reload does will only check the service files here. The returncode will tell the number of warnings, and it is over 100 if it can not continue even @@ -5026,8 +5227,8 @@ def system_daemon_reload(self): try: conf = self.get_unit_conf(unit) except Exception as e: - logg.error("%s: can not read unit file %s\n\t%s", - unit, strQ(conf.filename()), e) + logg.error("%s: can not read unit file %s\n\t%s", + unit, strQ(conf.filename()), e) continue errors += self.syntax_check(conf) if errors: @@ -5038,80 +5239,95 @@ def syntax_check(self, conf): if filename and filename.endswith(".service"): return self.syntax_check_service(conf) return 0 - def syntax_check_service(self, conf): + def syntax_check_service(self, conf, section = Service): unit = conf.name() - if not conf.data.has_section("Service"): - logg.error(" %s: a .service file without [Service] section", unit) - return 101 + if not conf.data.has_section(Service): + logg.error(" %s: a .service file without [Service] section", unit) + return 101 errors = 0 - haveType = conf.get("Service", "Type", "simple") - haveExecStart = conf.getlist("Service", "ExecStart", []) - haveExecStop = conf.getlist("Service", "ExecStop", []) - haveExecReload = conf.getlist("Service", "ExecReload", []) + haveType = conf.get(section, "Type", "simple") + haveExecStart = conf.getlist(section, "ExecStart", []) + haveExecStop = conf.getlist(section, "ExecStop", []) + haveExecReload = conf.getlist(section, "ExecReload", []) usedExecStart = [] usedExecStop = [] usedExecReload = [] - if haveType not in [ "simple", "forking", "notify", "oneshot", "dbus", "idle"]: + if haveType not in ["simple", "exec", "forking", "notify", "oneshot", "dbus", "idle"]: logg.error(" %s: Failed to parse service type, ignoring: %s", unit, haveType) errors += 100 for line in haveExecStart: - if not line.startswith("/") and not line.startswith("-/"): - logg.error(" %s: Executable path is not absolute, ignoring: %s", unit, line.strip()) + mode, exe = exec_path(line) + if not exe.startswith("/"): + if mode.check: + logg.error(" %s: %s Executable path is not absolute.", unit, section) + else: + logg.warning("%s: %s Executable path is not absolute.", unit, section) + logg.info("%s: %s exe = %s", unit, section, exe) errors += 1 usedExecStart.append(line) for line in haveExecStop: - if not line.startswith("/") and not line.startswith("-/"): - logg.error(" %s: Executable path is not absolute, ignoring: %s", unit, line.strip()) + mode, exe = exec_path(line) + if not exe.startswith("/"): + if mode.check: + logg.error(" %s: %s Executable path is not absolute.", unit, section) + else: + logg.warning("%s: %s Executable path is not absolute.", unit, section) + logg.info("%s: %s exe = %s", unit, section, exe) errors += 1 usedExecStop.append(line) for line in haveExecReload: - if not line.startswith("/") and not line.startswith("-/"): - logg.error(" %s: Executable path is not absolute, ignoring: %s", unit, line.strip()) + mode, exe = exec_path(line) + if not exe.startswith("/"): + if mode.check: + logg.error(" %s: %s Executable path is not absolute.", unit, section) + else: + logg.warning("%s: %s Executable path is not absolute.", unit, section) + logg.info("%s: %s exe = %s", unit, section, exe) errors += 1 usedExecReload.append(line) - if haveType in ["simple", "notify", "forking", "idle"]: + if haveType in ["simple", "exec", "notify", "forking", "idle"]: if not usedExecStart and not usedExecStop: - logg.error(" %s: Service lacks both ExecStart and ExecStop= setting. Refusing.", unit) + logg.error(" %s: %s lacks both ExecStart and ExecStop= setting. Refusing.", unit, section) errors += 101 elif not usedExecStart and haveType != "oneshot": - logg.error(" %s: Service has no ExecStart= setting, which is only allowed for Type=oneshot services. Refusing.", unit) + logg.error(" %s: %s has no ExecStart= setting, which is only allowed for Type=oneshot services. Refusing.", unit, section) errors += 101 if len(usedExecStart) > 1 and haveType != "oneshot": - logg.error(" %s: there may be only one ExecStart statement (unless for 'oneshot' services)." - + "\n\t\t\tYou can use ExecStartPre / ExecStartPost to add additional commands.", unit) + logg.error(" %s: there may be only one %s ExecStart statement (unless for 'oneshot' services)." + + "\n\t\t\tYou can use ExecStartPre / ExecStartPost to add additional commands.", unit, section) errors += 1 if len(usedExecStop) > 1 and haveType != "oneshot": - logg.info(" %s: there should be only one ExecStop statement (unless for 'oneshot' services)." - + "\n\t\t\tYou can use ExecStopPost to add additional commands (also executed on failed Start)", unit) + logg.info(" %s: there should be only one %s ExecStop statement (unless for 'oneshot' services)." + + "\n\t\t\tYou can use ExecStopPost to add additional commands (also executed on failed Start)", unit, section) if len(usedExecReload) > 1: - logg.info(" %s: there should be only one ExecReload statement." - + "\n\t\t\tUse ' ; ' for multiple commands (ExecReloadPost or ExedReloadPre do not exist)", unit) + logg.info(" %s: there should be only one %s ExecReload statement." + + "\n\t\t\tUse ' ; ' for multiple commands (ExecReloadPost or ExedReloadPre do not exist)", unit, section) if len(usedExecReload) > 0 and "/bin/kill " in usedExecReload[0]: - logg.warning(" %s: the use of /bin/kill is not recommended for ExecReload as it is asychronous." - + "\n\t\t\tThat means all the dependencies will perform the reload simultanously / out of order.", unit) - if conf.getlist("Service", "ExecRestart", []): #pragma: no cover - logg.error(" %s: there no such thing as an ExecRestart (ignored)", unit) - if conf.getlist("Service", "ExecRestartPre", []): #pragma: no cover - logg.error(" %s: there no such thing as an ExecRestartPre (ignored)", unit) - if conf.getlist("Service", "ExecRestartPost", []): #pragma: no cover - logg.error(" %s: there no such thing as an ExecRestartPost (ignored)", unit) - if conf.getlist("Service", "ExecReloadPre", []): #pragma: no cover - logg.error(" %s: there no such thing as an ExecReloadPre (ignored)", unit) - if conf.getlist("Service", "ExecReloadPost", []): #pragma: no cover - logg.error(" %s: there no such thing as an ExecReloadPost (ignored)", unit) - if conf.getlist("Service", "ExecStopPre", []): #pragma: no cover - logg.error(" %s: there no such thing as an ExecStopPre (ignored)", unit) - for env_file in conf.getlist("Service", "EnvironmentFile", []): + logg.warning(" %s: the use of /bin/kill is not recommended for %s ExecReload as it is asynchronous." + + "\n\t\t\tThat means all the dependencies will perform the reload simultaneously / out of order.", unit, section) + if conf.getlist(Service, "ExecRestart", []): # pragma: no cover + logg.error(" %s: there no such thing as an %s ExecRestart (ignored)", unit, section) + if conf.getlist(Service, "ExecRestartPre", []): # pragma: no cover + logg.error(" %s: there no such thing as an %s ExecRestartPre (ignored)", unit, section) + if conf.getlist(Service, "ExecRestartPost", []): # pragma: no cover + logg.error(" %s: there no such thing as an %s ExecRestartPost (ignored)", unit, section) + if conf.getlist(Service, "ExecReloadPre", []): # pragma: no cover + logg.error(" %s: there no such thing as an %s ExecReloadPre (ignored)", unit, section) + if conf.getlist(Service, "ExecReloadPost", []): # pragma: no cover + logg.error(" %s: there no such thing as an %s ExecReloadPost (ignored)", unit, section) + if conf.getlist(Service, "ExecStopPre", []): # pragma: no cover + logg.error(" %s: there no such thing as an %s ExecStopPre (ignored)", unit, section) + for env_file in conf.getlist(Service, "EnvironmentFile", []): if env_file.startswith("-"): continue if not os.path.isfile(os_path(self._root, self.expand_special(env_file, conf))): logg.error(" %s: Failed to load environment files: %s", unit, env_file) errors += 101 return errors - def exec_check_unit(self, conf, env, section = "Service", exectype = ""): + def exec_check_unit(self, conf, env, section = Service, exectype = ""): if conf is None: # pragma: no cover (is never null) return True if not conf.data.has_section(section): - return True #pragma: no cover + return True # pragma: no cover haveType = conf.get(section, "Type", "simple") if self.is_sysv_file(conf.filename()): return True # we don't care about that @@ -5120,7 +5336,7 @@ def exec_check_unit(self, conf, env, section = "Service", exectype = ""): notexists = 0 badusers = 0 badgroups = 0 - for execs in [ "ExecStartPre", "ExecStart", "ExecStartPost", "ExecStop", "ExecStopPost", "ExecReload" ]: + for execs in ["ExecStartPre", "ExecStart", "ExecStartPost", "ExecStop", "ExecStopPost", "ExecReload"]: if not execs.startswith(exectype): continue for cmd in conf.getlist(section, execs, []): @@ -5143,8 +5359,8 @@ def exec_check_unit(self, conf, env, section = "Service", exectype = ""): logg.error(" %s: but this does exist: %s %s", unit, " " * len(execs), newexe1) elif os.path.exists(newexe2): logg.error(" %s: but this does exist: %s %s", unit, " " * len(execs), newexe2) - users = [ conf.get(section, "User", ""), conf.get(section, "SocketUser", "") ] - groups = [ conf.get(section, "Group", ""), conf.get(section, "SocketGroup", "") ] + conf.getlist(section, "SupplementaryGroups") + users = [conf.get(section, "User", ""), conf.get(section, "SocketUser", "")] + groups = [conf.get(section, "Group", ""), conf.get(section, "SocketGroup", "")] + conf.getlist(section, "SupplementaryGroups") for user in users: if user: try: pwd.getpwnam(self.expand_special(user, conf)) @@ -5152,20 +5368,20 @@ def exec_check_unit(self, conf, env, section = "Service", exectype = ""): logg.error(" %s: User does not exist: %s (%s)", unit, user, getattr(e, "__doc__", "")) badusers += 1 for group in groups: - if group: + if group: try: grp.getgrnam(self.expand_special(group, conf)) except Exception as e: logg.error(" %s: Group does not exist: %s (%s)", unit, group, getattr(e, "__doc__", "")) badgroups += 1 tmpproblems = 0 for setting in ("RootDirectory", "RootImage", "BindPaths", "BindReadOnlyPaths", - "ReadWritePaths", "ReadOnlyPaths", "TemporaryFileSystem"): + "ReadWritePaths", "ReadOnlyPaths", "TemporaryFileSystem"): setting_value = conf.get(section, setting, "") if setting_value: logg.info("%s: %s private directory remounts ignored: %s=%s", unit, section, setting, setting_value) tmpproblems += 1 - for setting in ("PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", "DynamicUser", - "ProtectSystem", "ProjectHome", "ProtectHostname", "PrivateMounts", "MountAPIVFS"): + for setting in ("PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", "DynamicUser", + "ProtectSystem", "ProjectHome", "ProtectHostname", "PrivateMounts", "MountAPIVFS"): setting_yes = conf.getbool(section, setting, "no") if setting_yes: logg.info("%s: %s private directory option is ignored: %s=yes", unit, section, setting) @@ -5203,33 +5419,33 @@ def show_modules(self, *modules): / NOTE: only a subset of properties is implemented """ notfound = [] - found_all = True units = [] + found_all = True for module in modules: matched = self.match_units(to_list(module)) if not matched: logg.error("Unit %s could not be found.", unit_of(module)) - units += [ module ] + units += [module] # self.error |= NOT_FOUND found_all = False continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] return self.show_units(units) + notfound # and found_all def show_units(self, units): - logg.debug("show --property=%s", self._unit_property) + logg.debug("show --property=%s", ",".join(self._only_property)) result = [] for unit in units: - if result: result += [ "" ] + if result: result += [""] for var, value in self.show_unit_items(unit): - if self._unit_property: - if self._unit_property != var: + if self._only_property: + if var not in self._only_property: continue else: if not value and not self._show_all: continue - result += [ "%s=%s" % (var, value) ] + result += ["%s=%s" % (var, value)] return result def show_unit_items(self, unit): """ [UNIT]... -- show properties of a unit. @@ -5244,10 +5460,10 @@ def each_unit_items(self, unit, conf): loaded = "not-loaded" if "NOT-FOUND" in self.get_description_from(conf): loaded = "not-found" - names = { unit: 1, conf.name(): 1 } + names = {unit: 1, conf.name(): 1} yield "Id", conf.name() yield "Names", " ".join(sorted(names.keys())) - yield "Description", self.get_description_from(conf) # conf.get("Unit", "Description") + yield "Description", self.get_description_from(conf) # conf.get(Unit, "Description") yield "PIDFile", self.get_pid_file(conf) # not self.pid_file_from w/o default location yield "PIDFilePath", self.pid_file_from(conf) yield "MainPID", strE(self.active_pid_from(conf)) # status["MainPID"] or PIDFile-read @@ -5276,30 +5492,30 @@ def each_unit_items(self, unit, conf): yield "RemainAfterExit", strYes(self.get_RemainAfterExit(conf)) yield "WorkingDirectory", strE(self.get_WorkingDirectory(conf)) env_parts = [] - for env_part in conf.getlist("Service", "Environment", []): + for env_part in conf.getlist(Service, "Environment", []): env_parts.append(self.expand_special(env_part, conf)) - if env_parts: + if env_parts: yield "Environment", " ".join(env_parts) env_files = [] - for env_file in conf.getlist("Service", "EnvironmentFile", []): + for env_file in conf.getlist(Service, "EnvironmentFile", []): env_files.append(self.expand_special(env_file, conf)) if env_files: yield "EnvironmentFile", " ".join(env_files) def get_SendSIGKILL(self, conf): - return conf.getbool("Service", "SendSIGKILL", "yes") + return conf.getbool(Service, "SendSIGKILL", "yes") def get_SendSIGHUP(self, conf): - return conf.getbool("Service", "SendSIGHUP", "no") + return conf.getbool(Service, "SendSIGHUP", "no") def get_KillMode(self, conf): - return conf.get("Service", "KillMode", "control-group") + return conf.get(Service, "KillMode", "control-group") def get_KillSignal(self, conf): - return conf.get("Service", "KillSignal", "SIGTERM") + return conf.get(Service, "KillSignal", "SIGTERM") # - igno_centos = [ "netconsole", "network" ] - igno_opensuse = [ "raw", "pppoe", "*.local", "boot.*", "rpmconf*", "postfix*" ] - igno_ubuntu = [ "mount*", "umount*", "ondemand", "*.local" ] - igno_always = [ "network*", "dbus*", "systemd-*", "kdump*" ] - igno_always += [ "purge-kernels.service", "after-local.service", "dm-event.*" ] # as on opensuse - igno_targets = [ "remote-fs.target" ] + igno_centos = ["netconsole", "network"] + igno_opensuse = ["raw", "pppoe", "*.local", "boot.*", "rpmconf*", "postfix*"] + igno_ubuntu = ["mount*", "umount*", "ondemand", "*.local"] + igno_always = ["network*", "dbus*", "systemd-*", "kdump*", "kmod*"] + igno_always += ["purge-kernels.service", "after-local.service", "dm-event.*"] # as on opensuse + igno_targets = ["remote-fs.target"] def _ignored_unit(self, unit, ignore_list): for ignore in ignore_list: if fnmatch.fnmatchcase(unit, ignore): @@ -5308,14 +5524,14 @@ def _ignored_unit(self, unit, ignore_list): return True # ignore return False def default_services_modules(self, *modules): - """ show the default services + """ show the default services This is used internally to know the list of service to be started in the 'get-default' target runlevel when the container is started through default initialisation. It will ignore a number of services - use '--all' to show a longer list of services and use '--all --force' if not even a minimal filter shall be used. """ results = [] - targets = modules or [ self.get_default_target() ] + targets = modules or [self.get_default_target()] for target in targets: units = self.target_default_services(target) logg.debug(" %s # %s", " ".join(units), target) @@ -5428,7 +5644,7 @@ def enabled_target_user_system_units(self, target, unit_kind = ".service", igno if conf is None: pass elif self.not_user_conf(conf): - pass + pass else: units.append(unit) return units @@ -5467,13 +5683,13 @@ def enabled_target_configured_system_units(self, target, unit_type = ".service", def enabled_target_sysv_units(self, target, sysv = "S", igno = []): units = [] folders = [] - if target in [ "multi-user.target", DefaultUnit ]: - folders += [ self.rc3_root_folder() ] - if target in [ "graphical.target" ]: - folders += [ self.rc5_root_folder() ] + if target in ["multi-user.target", DefaultUnit]: + folders += [self.rc3_root_folder()] + if target in ["graphical.target"]: + folders += [self.rc5_root_folder()] for folder in folders: if not os.path.isdir(folder): - logg.warning("non-existant %s", folder) + logg.warning("non-existent %s", folder) continue for unit in sorted(os.listdir(folder)): path = os.path.join(folder, unit) @@ -5497,33 +5713,33 @@ def required_target_units(self, target, unit_type, igno): units.append(unit) return units def get_target_conf(self, module): # -> conf (conf | default-conf) - """ accept that a unit does not exist + """ accept that a unit does not exist and return a unit conf that says 'not-loaded' """ conf = self.load_unit_conf(module) if conf is not None: return conf target_conf = self.default_unit_conf(module) if module in target_requires: - target_conf.set("Unit", "Requires", target_requires[module]) + target_conf.set(Unit, "Requires", target_requires[module]) return target_conf def get_target_list(self, module): """ the Requires= in target units are only accepted if known """ target = module if "." not in target: target += ".target" - targets = [ target ] + targets = [target] conf = self.get_target_conf(module) - requires = conf.get("Unit", "Requires", "") + requires = conf.get(Unit, "Requires", "") while requires in target_requires: - targets = [ requires ] + targets - requires = target_requires[requires] + targets = [requires] + targets + requires = target_requires[requires] logg.debug("the %s requires %s", module, targets) return targets - def system_default(self, arg = True): + def default_system(self, arg = True): """ start units for default system level This will go through the enabled services in the default 'multi-user.target'. However some services are ignored as being known to be installation garbage from unintended services. Use '--all' so start all of the installed services - and with '--all --force' even those services that are otherwise wrong. + and with '--all --force' even those services that are otherwise wrong. /// SPECIAL: with --now or --init the init-loop is run and afterwards a system_halt is performed with the enabled services to be stopped.""" self.sysinit_status(SubState = "initializing") @@ -5582,11 +5798,11 @@ def reload_target_system(self, target): services = self.target_default_services(target, "S") units = [service for service in services if self.is_running_unit(service)] return self.reload_units(units) - def system_halt(self, arg = True): + def halt_target(self, arg = True): """ stop units from default system level """ logg.info("system halt requested - %s", arg) done = self.stop_system_default() - try: + try: os.kill(1, signal.SIGQUIT) # exit init-loop on no_more_procs except Exception as e: logg.warning("SIGQUIT to init-loop on PID-1: %s", e) @@ -5637,15 +5853,15 @@ def set_default_modules(self, *modules): return msg def init_modules(self, *modules): """ [UNIT*] -- init loop: '--init default' or '--init start UNIT*' - The systemctl init service will start the enabled 'default' services, + The systemctl init service will start the enabled 'default' services, and then wait for any zombies to be reaped. When a SIGINT is received then a clean shutdown of the enabled services is ensured. A Control-C in in interactive mode will also run 'stop' on all the enabled services. // When a UNIT name is given then only that one is started instead of the - services in the 'default.target'. Using 'init UNIT' is better than - '--init start UNIT' because the UNIT is also stopped cleanly even when + services in the 'default.target'. Using 'init UNIT' is better than + '--init start UNIT' because the UNIT is also stopped cleanly even when it was never enabled in the system. - /// SPECIAL: when using --now then only the init-loop is started, + /// SPECIAL: when using --now then only the init-loop is started, with the reap-zombies function and waiting for an interrupt. (and no unit is started/stoppped wether given or not). """ @@ -5674,9 +5890,9 @@ def init_modules(self, *modules): continue for unit in matched: if unit not in units: - units += [ unit ] + units += [unit] logg.info("init %s -> start %s", ",".join(modules), ",".join(units)) - done = self.start_units(units, init = True) + done = self.start_units(units, init = True) logg.info("-- init is done") return done # and found_all def start_log_files(self, units): @@ -5694,7 +5910,10 @@ def start_log_files(self, units): except Exception as e: logg.error("can not open %s log: %s\n\t%s", unit, log_path, e) def read_log_files(self, units): + self.print_log_files(units) + def print_log_files(self, units, stdout = 1): BUFSIZE=8192 + printed = 0 for unit in units: if unit in self._log_file: new_text = b"" @@ -5712,9 +5931,16 @@ def read_log_files(self, units): for line in lines: prefix = unit.encode("utf-8") content = prefix+b": "+line+b"\n" - os.write(1, content) - try: os.fsync(1) - except: pass + try: + os.write(stdout, content) + try: + os.fsync(stdout) + except Exception: + pass + printed += 1 + except BlockingIOError: + pass + return printed def stop_log_files(self, units): for unit in units: try: @@ -5728,22 +5954,22 @@ def stop_log_files(self, units): def get_StartLimitBurst(self, conf): defaults = DefaultStartLimitBurst - return to_int(conf.get("Service", "StartLimitBurst", strE(defaults)), defaults) # 5 + return to_int(conf.get(Service, "StartLimitBurst", strE(defaults)), defaults) # 5 def get_StartLimitIntervalSec(self, conf, maximum = None): maximum = maximum or 999 defaults = DefaultStartLimitIntervalSec - interval = conf.get("Service", "StartLimitIntervalSec", strE(defaults)) # 10s + interval = conf.get(Service, "StartLimitIntervalSec", strE(defaults)) # 10s return time_to_seconds(interval, maximum) def get_RestartSec(self, conf, maximum = None): maximum = maximum or DefaultStartLimitIntervalSec - delay = conf.get("Service", "RestartSec", strE(DefaultRestartSec)) + delay = conf.get(Service, "RestartSec", strE(DefaultRestartSec)) return time_to_seconds(delay, maximum) def restart_failed_units(self, units, maximum = None): - """ This function will retart failed units. + """ This function will restart failed units. / NOTE that with standard settings the LimitBurst implementation has no effect. If - the InitLoopSleep is ticking at the Default of 5sec and the LimitBurst Default - is 5x within a Default 10secs time frame then within those 10sec only 2 loop + the InitLoopSleep is ticking at the Default of 5sec and the LimitBurst Default + is 5x within a Default 10secs time frame then within those 10sec only 2 loop rounds have come here checking for possible restarts. You can directly shorten the interval ('-c InitLoopSleep=1') or have it indirectly shorter from the service descriptor's RestartSec ("RestartSec=2s"). @@ -5757,21 +5983,21 @@ def restart_failed_units(self, units, maximum = None): try: conf = self.load_unit_conf(unit) if not conf: continue - restartPolicy = conf.get("Service", "Restart", "no") + restartPolicy = conf.get(Service, "Restart", "no") if restartPolicy in ["no", "on-success"]: logg.debug("[%s] [%s] Current NoCheck (Restart=%s)", me, unit, restartPolicy) continue restartSec = self.get_RestartSec(conf) if restartSec == 0: if InitLoopSleep > 1: - logg.warning("[%s] set InitLoopSleep from %ss to 1 (caused by RestartSec=0!)", - unit, InitLoopSleep) + logg.warning("[%s] set InitLoopSleep from %ss to 1 (caused by RestartSec=0!)", + unit, InitLoopSleep) InitLoopSleep = 1 elif restartSec > 0.9 and restartSec < InitLoopSleep: restartSleep = int(restartSec + 0.2) if restartSleep < InitLoopSleep: - logg.warning("[%s] set InitLoopSleep from %ss to %s (caused by RestartSec=%.3fs)", - unit, InitLoopSleep, restartSleep, restartSec) + logg.warning("[%s] set InitLoopSleep from %ss to %s (caused by RestartSec=%.3fs)", + unit, InitLoopSleep, restartSleep, restartSec) InitLoopSleep = restartSleep isUnitState = self.get_active_from(conf) isUnitFailed = isUnitState in ["failed"] @@ -5788,13 +6014,13 @@ def restart_failed_units(self, units, maximum = None): self._restarted_unit[unit] = [] # we want to register restarts from now on restarted = self._restarted_unit[unit] - logg.debug("[%s] [%s] Current limitSecs=%ss limitBurst=%sx (restarted %sx)", - me, unit, limitSecs, limitBurst, len(restarted)) + logg.debug("[%s] [%s] Current limitSecs=%ss limitBurst=%sx (restarted %sx)", + me, unit, limitSecs, limitBurst, len(restarted)) oldest = 0. interval = 0. if len(restarted) >= limitBurst: - logg.debug("[%s] [%s] restarted %s", - me, unit, [ "%.3fs" % (t - now) for t in restarted ]) + logg.debug("[%s] [%s] restarted %s", + me, unit, ["%.3fs" % (t - now) for t in restarted]) while len(restarted): oldest = restarted[0] interval = time.time() - oldest @@ -5803,12 +6029,12 @@ def restart_failed_units(self, units, maximum = None): continue break self._restarted_unit[unit] = restarted - logg.debug("[%s] [%s] ratelimit %s", - me, unit, [ "%.3fs" % (t - now) for t in restarted ]) + logg.debug("[%s] [%s] ratelimit %s", + me, unit, ["%.3fs" % (t - now) for t in restarted]) # all values in restarted have a time below limitSecs if len(restarted) >= limitBurst: - logg.info("[%s] [%s] Blocking Restart - oldest %s is %s ago (allowed %s)", - me, unit, oldest, interval, limitSecs) + logg.info("[%s] [%s] Blocking Restart - oldest %s is %s ago (allowed %s)", + me, unit, oldest, interval, limitSecs) self.write_status_from(conf, AS="error") unit = "" # dropped out continue @@ -5817,10 +6043,10 @@ def restart_failed_units(self, units, maximum = None): if unit: # not dropped out if unit not in self._restart_failed_units: self._restart_failed_units[unit] = now + restartSec - logg.debug("[%s] [%s] restart scheduled in %+.3fs", - me, unit, (self._restart_failed_units[unit] - now)) + logg.debug("[%s] [%s] restart scheduled in %+.3fs", + me, unit, (self._restart_failed_units[unit] - now)) except Exception as e: - logg.error("[%s] [%s] An error ocurred while restart checking: %s", me, unit, e) + logg.error("[%s] [%s] An error occurred while restart checking: %s", me, unit, e) if not self._restart_failed_units: self.error |= NOT_OK return [] @@ -5828,8 +6054,8 @@ def restart_failed_units(self, units, maximum = None): # let's check if any of the restart_units has its restartSec expired now = time.time() restart_done = [] - logg.debug("[%s] Restart checking %s", - me, [ "%+.3fs" % (t - now) for t in self._restart_failed_units.values() ]) + logg.debug("[%s] Restart checking %s", + me, ["%+.3fs" % (t - now) for t in self._restart_failed_units.values()]) for unit in sorted(self._restart_failed_units): restartAt = self._restart_failed_units[unit] if restartAt > now: @@ -5848,23 +6074,24 @@ def restart_failed_units(self, units, maximum = None): if unit in self._restarted_unit: self._restarted_unit[unit].append(time.time()) except Exception as e: - logg.error("[%s] [%s] An error ocurred while restarting: %s", me, unit, e) + logg.error("[%s] [%s] An error occurred while restarting: %s", me, unit, e) for unit in restart_done: if unit in self._restart_failed_units: del self._restart_failed_units[unit] - logg.debug("[%s] Restart remaining %s", - me, [ "%+.3fs" % (t - now) for t in self._restart_failed_units.values() ]) + logg.debug("[%s] Restart remaining %s", + me, ["%+.3fs" % (t - now) for t in self._restart_failed_units.values()]) return restart_done def init_loop_until_stop(self, units): """ this is the init-loop - it checks for any zombies to be reaped and waits for an interrupt. When a SIGTERM /SIGINT /Control-C signal - is received then the signal name is returned. Any other signal will + is received then the signal name is returned. Any other signal will just raise an Exception like one would normally expect. As a special the 'systemctl halt' emits SIGQUIT which puts it into no_more_procs mode.""" signal.signal(signal.SIGQUIT, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt("SIGQUIT")) signal.signal(signal.SIGINT, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt("SIGINT")) signal.signal(signal.SIGTERM, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt("SIGTERM")) + result = None # self.start_log_files(units) logg.debug("start listen") @@ -5874,7 +6101,6 @@ def init_loop_until_stop(self, units): logg.debug("started listen") self.sysinit_status(ActiveState = "active", SubState = "running") timestamp = time.time() - result = None while True: try: if DEBUG_INITLOOP: # pragma: no cover @@ -5887,8 +6113,8 @@ def init_loop_until_stop(self, units): time.sleep(1) # accept signals atleast every second sleeping = InitLoopSleep - (time.time() - timestamp) if sleeping < MinimumYield: - sleeping = MinimumYield - break + sleeping = MinimumYield + break time.sleep(sleeping) # remainder waits less that 2 seconds timestamp = time.time() self.loop.acquire() @@ -5897,7 +6123,7 @@ def init_loop_until_stop(self, units): self.read_log_files(units) if DEBUG_INITLOOP: # pragma: no cover logg.debug("reap zombies - check current processes") - running = self.system_reap_zombies() + running = self.reap_zombies() if DEBUG_INITLOOP: # pragma: no cover logg.debug("reap zombies - init-loop found %s running procs", running) if self.doExitWhenNoMoreServices: @@ -5941,7 +6167,11 @@ def init_loop_until_stop(self, units): self.stop_log_files(units) logg.debug("done - init loop") return result - def system_reap_zombies(self): + def reap_zombies_target(self): + """ -- check to reap children (internal) """ + running = self.reap_zombies() + return "remaining {running} process".format(**locals()) + def reap_zombies(self): """ check to reap children """ selfpid = os.getpid() running = 0 @@ -5967,7 +6197,7 @@ def system_reap_zombies(self): if zombie and ppid == os.getpid(): logg.info("reap zombie %s", pid) try: os.waitpid(pid, os.WNOHANG) - except OSError as e: + except OSError as e: logg.warning("reap zombie %s: %s", e.strerror) if os.path.isfile(proc_status): if pid > 1: @@ -5989,9 +6219,9 @@ def is_system_running(self): return "offline" status = self.read_status_from(conf) return status.get("SubState", "unknown") - def system_is_system_running(self): + def is_system_running_info(self): state = self.is_system_running() - if state not in [ "running" ]: + if state not in ["running"]: self.error |= NOT_OK # 1 if self._quiet: return None @@ -6001,12 +6231,12 @@ def wait_system(self, target = None): for attempt in xrange(int(SysInitWait)): state = self.is_system_running() if "init" in state: - if target in [ SysInitTarget, "basic.target" ]: + if target in [SysInitTarget, "basic.target"]: logg.info("system not initialized - wait %s", target) time.sleep(1) continue if "start" in state or "stop" in state: - if target in [ "basic.target" ]: + if target in ["basic.target"]: logg.info("system not running - wait %s", target) time.sleep(1) continue @@ -6015,15 +6245,16 @@ def wait_system(self, target = None): break def is_running_unit_from(self, conf): status_file = self.get_status_file_from(conf) - return self.getsize(status_file) > 0 + pid_file = self.pid_file_from(conf) + return self.getsize(status_file) > 0 or self.getsize(pid_file) > 0 def is_running_unit(self, unit): conf = self.get_unit_conf(unit) return self.is_running_unit_from(conf) def pidlist_of(self, pid): if not pid: return [] - pidlist = [ pid ] - pids = [ pid ] + pidlist = [pid] + pids = [pid] for depth in xrange(PROC_MAX_DEPTH): for pid_entry in os.listdir(_proc_pid_dir): pid = to_intN(pid_entry) @@ -6038,7 +6269,7 @@ def pidlist_of(self, pid): try: ppid = int(ppid_text) except: continue if ppid in pidlist and pid not in pids: - pids += [ pid ] + pids += [pid] except IOError as e: logg.warning("%s : %s", proc_status, e) continue @@ -6077,7 +6308,7 @@ def killall(self, *targets): cmd_exe = os.path.basename(cmd[0]) if DEBUG_KILLALL: logg.debug("cmd.exe '%s'", cmd_exe) if fnmatch.fnmatchcase(cmd_exe, target): found = "exe" - if len(cmd) > 1 and cmd_exe.startswith("python"): + if len(cmd) > 1 and cmd_exe.startswith("python"): X = 1 while cmd[X].startswith("-"): X += 1 # atleast '-u' unbuffered cmd_arg = os.path.basename(cmd[X]) @@ -6090,7 +6321,7 @@ def killall(self, *targets): if DEBUG_KILLALL: logg.debug("cmd.run '%s'", cmd_run) if fnmatch.fnmatchcase(cmd_run, target): found = "run" if found: - if DEBUG_KILLALL: logg.debug("%s found %s %s", found, pid, [ c for c in cmd ]) + if DEBUG_KILLALL: logg.debug("%s found %s %s", found, pid, [c for c in cmd]) if pid != os.getpid(): logg.debug(" kill -%s %s # %s", sig, pid, target) os.kill(pid, sig) @@ -6129,7 +6360,7 @@ def force_ipv6(self, *args): for line in lines: f.write(line) f.close() - def show_help(self, *args): + def help_modules(self, *args): """[command] -- show this help """ lines = [] @@ -6140,15 +6371,15 @@ def show_help(self, *args): for name in dir(self): arg = None if name.startswith("system_"): - arg = name[len("system_"):].replace("_","-") + arg = name[len("system_"):].replace("_", "-") if name.startswith("show_"): - arg = name[len("show_"):].replace("_","-") + arg = name[len("show_"):].replace("_", "-") if name.endswith("_of_unit"): - arg = name[:-len("_of_unit")].replace("_","-") + arg = name[:-len("_of_unit")].replace("_", "-") if name.endswith("_modules"): - arg = name[:-len("_modules")].replace("_","-") + arg = name[:-len("_modules")].replace("_", "-") if arg: - argz[arg] = name + argz[arg] = name lines.append("%s command [options]..." % prog) lines.append("") lines.append("Commands:") @@ -6168,7 +6399,7 @@ def show_help(self, *args): lines.append(" %s %s" % (arg, firstline.strip())) return lines for arg in args: - arg = arg.replace("-","_") + arg = arg.replace("-", "_") func1 = getattr(self.__class__, arg+"_modules", None) func2 = getattr(self.__class__, arg+"_of_unit", None) func3 = getattr(self.__class__, "show_"+arg, None) @@ -6182,18 +6413,18 @@ def show_help(self, *args): okay = False else: doc_text = "..." - doc = getattr(func, "__doc__", None) + doc = getattr(func, "__doc__", "") if doc: - doc_text = doc.replace("\n","\n\n", 1).strip() + doc_text = doc.replace("\n", "\n\n", 1).strip() if "--" not in doc_text: doc_text = "-- " + doc_text - else: + else: func_name = arg # FIXME logg.debug("__doc__ of %s is none", func_name) if not self._show_all: continue lines.append("%s %s %s" % (prog, arg, doc_text)) if not okay: - self.show_help() + self.help_modules() self.error |= NOT_OK return [] return lines @@ -6206,144 +6437,318 @@ def systemd_features(self): features2 = " +SYSVINIT -UTMP -LIBCRYPTSETUP -GCRYPT -GNUTLS" features3 = " -ACL -XZ -LZ4 -SECCOMP -BLKID -ELFUTILS -KMOD -IDN" return features1+features2+features3 - def systems_version(self): - return [ self.systemd_version(), self.systemd_features() ] + def version_info(self): + return [self.systemd_version(), self.systemd_features()] def test_float(self): return 0. # "Unknown result type" -def print_result(result): - # logg_info = logg.info - # logg_debug = logg.debug - def logg_info(*msg): pass - def logg_debug(*msg): pass - exitcode = 0 +def print_begin(argv, args): + script = os.path.realpath(argv[0]) + system = _user_mode and " --user" or " --system" + init = _init and " --init" or "" + logg.info("EXEC BEGIN %s %s%s%s", script, " ".join(args), system, init) + if _root and not is_good_root(_root): + root44 = path44(_root) + logg.warning("the --root=%s should have atleast three levels /tmp/test_123/root", root44) + +def print_begin2(args): + logg.debug("======= systemctl.py %s", " ".join(args)) + +def is_not_ok(result): + if DebugPrintResult: + logg.log(HINT, "EXEC END %s", result) + if result is False: + return NOT_OK + return 0 + +def print_str(result): if result is None: - logg_info("EXEC END None") - elif result is True: - logg_info("EXEC END True") - exitcode = 0 - elif result is False: - logg_info("EXEC END False") - exitcode = NOT_OK # the only case that exitcode gets set - elif isinstance(result, int): - logg_info("EXEC END %s", result) - # exitcode = result # we do not do that anymore - elif isinstance(result, basestring): - print(result) + if DebugPrintResult: + logg.debug(" END %s", result) + return + print(result) + if DebugPrintResult: result1 = result.split("\n")[0][:-20] if result == result1: - logg_info("EXEC END '%s'", result) + logg.log(HINT, "EXEC END '%s'", result) else: - logg_info("EXEC END '%s...'", result1) - logg_debug(" END '%s'", result) - elif isinstance(result, list) or isinstance(result, GeneratorType): - shown = 0 - for element in result: - if isinstance(element, tuple): - print("\t".join([ str(elem) for elem in element] )) - else: - print(element) - shown += 1 - logg_info("EXEC END %s items", shown) - logg_debug(" END %s", result) - elif isinstance(result, dict): - shown = 0 - for key in sorted(result.keys()): - element = result[key] - if isinstance(element, tuple): - print(key,"=","\t".join([ str(elem) for elem in element])) - else: - print("%s=%s" % (key,element)) - shown += 1 - logg_info("EXEC END %s items", shown) - logg_debug(" END %s", result) + logg.log(HINT, "EXEC END '%s...'", result1) + logg.debug(" END '%s'", result) +def print_str_list(result): + if result is None: + if DebugPrintResult: + logg.debug(" END %s", result) + return + shown = 0 + for element in result: + print(element) + shown += 1 + if DebugPrintResult: + logg.log(HINT, "EXEC END %i items", shown) + logg.debug(" END %s", result) +def print_str_list_list(result): + shown = 0 + for element in result: + print("\t".join([str(elem) for elem in element])) + shown += 1 + if DebugPrintResult: + logg.log(HINT, "EXEC END %i items", shown) + logg.debug(" END %s", result) +def print_str_dict(result): + if result is None: + if DebugPrintResult: + logg.debug(" END %s", result) + return + shown = 0 + for key in sorted(result.keys()): + element = result[key] + print("%s=%s" % (key, element)) + shown += 1 + if DebugPrintResult: + logg.log(HINT, "EXEC END %i items", shown) + logg.debug(" END %s", result) +def print_str_dict_dict(result): + if result is None: + if DebugPrintResult: + logg.debug(" END %s", result) + return + shown = 0 + for key in sorted(result): + element = result[key] + for name in sorted(element): + value = element[name] + print("%s [%s] %s" % (key, value, name)) + shown += 1 + if DebugPrintResult: + logg.log(HINT, "EXEC END %i items", shown) + logg.debug(" END %s", result) + +def run(command, *modules): + exitcode = 0 + if command in ["help"]: + print_str_list(systemctl.help_modules(*modules)) + elif command in ["cat"]: + print_str(systemctl.cat_modules(*modules)) + elif command in ["clean"]: + exitcode = is_not_ok(systemctl.clean_modules(*modules)) + elif command in ["command"]: + print_str_list(systemctl.command_of_unit(*modules)) + elif command in ["daemon-reload"]: + exitcode = is_not_ok(systemctl.daemon_reload_target()) + elif command in ["default"]: + exitcode = is_not_ok(systemctl.default_system()) + elif command in ["default-services"]: + print_str_list(systemctl.default_services_modules(*modules)) + elif command in ["disable"]: + exitcode = is_not_ok(systemctl.disable_modules(*modules)) + elif command in ["enable"]: + exitcode = is_not_ok(systemctl.enable_modules(*modules)) + elif command in ["environment"]: + print_str_dict(systemctl.environment_of_unit(*modules)) + elif command in ["get-default"]: + print_str(systemctl.get_default_target()) + elif command in ["get-preset"]: + print_str(systemctl.get_preset_of_unit(*modules)) + elif command in ["halt"]: + exitcode = is_not_ok(systemctl.halt_target()) + elif command in ["init"]: + exitcode = is_not_ok(systemctl.init_modules(*modules)) + elif command in ["is-active"]: + print_str_list(systemctl.is_active_modules(*modules)) + elif command in ["is-enabled"]: + print_str_list(systemctl.is_enabled_modules(*modules)) + elif command in ["is-failed"]: + print_str_list(systemctl.is_failed_modules(*modules)) + elif command in ["is-system-running"]: + print_str(systemctl.is_system_running_info()) + elif command in ["kill"]: + exitcode = is_not_ok(systemctl.kill_modules(*modules)) + elif command in ["list-start-dependencies"]: + print_str_list_list(systemctl.list_start_dependencies_modules(*modules)) + elif command in ["list-dependencies"]: + print_str_list(systemctl.list_dependencies_modules(*modules)) + elif command in ["list-unit-files"]: + print_str_list_list(systemctl.list_unit_files_modules(*modules)) + elif command in ["list-units"]: + print_str_list_list(systemctl.list_units_modules(*modules)) + elif command in ["listen"]: + exitcode = is_not_ok(systemctl.listen_modules(*modules)) + elif command in ["log", "logs"]: + exitcode = is_not_ok(systemctl.log_modules(*modules)) + elif command in ["mask"]: + exitcode = is_not_ok(systemctl.mask_modules(*modules)) + elif command in ["preset"]: + exitcode = is_not_ok(systemctl.preset_modules(*modules)) + elif command in ["preset-all"]: + exitcode = is_not_ok(systemctl.preset_all_modules()) + elif command in ["reap-zombies"]: + print_str(systemctl.reap_zombies_target()) + elif command in ["reload"]: + exitcode = is_not_ok(systemctl.reload_modules(*modules)) + elif command in ["reload-or-restart"]: + exitcode = is_not_ok(systemctl.reload_or_restart_modules(*modules)) + elif command in ["reload-or-try-restart"]: + exitcode = is_not_ok(systemctl.reload_or_try_restart_modules(*modules)) + elif command in ["reset-failed"]: + exitcode = is_not_ok(systemctl.reset_failed_modules(*modules)) + elif command in ["restart"]: + exitcode = is_not_ok(systemctl.restart_modules(*modules)) + elif command in ["set-default"]: + print_str(systemctl.set_default_modules(*modules)) + elif command in ["show"]: + print_str_list(systemctl.show_modules(*modules)) + elif command in ["start"]: + exitcode = is_not_ok(systemctl.start_modules(*modules)) + elif command in ["status"]: + print_str(systemctl.status_modules(*modules)) + elif command in ["stop"]: + exitcode = is_not_ok(systemctl.stop_modules(*modules)) + elif command in ["try-restart"]: + exitcode = is_not_ok(systemctl.try_restart_modules(*modules)) + elif command in ["unmask"]: + exitcode = is_not_ok(systemctl.unmask_modules(*modules)) + elif command in ["version"]: + print_str_list(systemctl.version_info()) + elif command in ["__cat_unit"]: + print_str(systemctl.cat_unit(*modules)) + elif command in ["__get_active_unit"]: + print_str(systemctl.get_active_unit(*modules)) + elif command in ["__get_description"]: + print_str(systemctl.get_description(*modules)) + elif command in ["__get_status_file"]: + print_str(systemctl.get_status_file(modules[0])) + elif command in ["__get_status_pid_file", "__get_pid_file"]: + print_str(systemctl.get_status_pid_file(modules[0])) + elif command in ["__disable_unit"]: + exitcode = is_not_ok(systemctl.disable_unit(*modules)) + elif command in ["__enable_unit"]: + exitcode = is_not_ok(systemctl.enable_unit(*modules)) + elif command in ["__is_enabled"]: + exitcode = is_not_ok(systemctl.is_enabled(*modules)) + elif command in ["__killall"]: + exitcode = is_not_ok(systemctl.killall(*modules)) + elif command in ["__kill_unit"]: + exitcode = is_not_ok(systemctl.kill_unit(*modules)) + elif command in ["__load_preset_files"]: + print_str_list(systemctl.load_preset_files(*modules)) + elif command in ["__mask_unit"]: + exitcode = is_not_ok(systemctl.mask_unit(*modules)) + elif command in ["__read_env_file"]: + print_str_list_list(list(systemctl.read_env_file(*modules))) + elif command in ["__reload_unit"]: + exitcode = is_not_ok(systemctl.reload_unit(*modules)) + elif command in ["__reload_or_restart_unit"]: + exitcode = is_not_ok(systemctl.reload_or_restart_unit(*modules)) + elif command in ["__reload_or_try_restart_unit"]: + exitcode = is_not_ok(systemctl.reload_or_try_restart_unit(*modules)) + elif command in ["__reset_failed_unit"]: + exitcode = is_not_ok(systemctl.reset_failed_unit(*modules)) + elif command in ["__restart_unit"]: + exitcode = is_not_ok(systemctl.restart_unit(*modules)) + elif command in ["__start_unit"]: + exitcode = is_not_ok(systemctl.start_unit(*modules)) + elif command in ["__stop_unit"]: + exitcode = is_not_ok(systemctl.stop_unit(*modules)) + elif command in ["__try_restart_unit"]: + exitcode = is_not_ok(systemctl.try_restart_unit(*modules)) + elif command in ["__test_start_unit"]: + systemctl.test_start_unit(*modules) + elif command in ["__unmask_unit"]: + exitcode = is_not_ok(systemctl.unmask_unit(*modules)) + elif command in ["__show_unit_items"]: + print_str_list_list(list(systemctl.show_unit_items(*modules))) else: - logg.warning("EXEC END Unknown result type %s", str(type(result))) + logg.error("Unknown operation %s", command) + return EXIT_FAILURE + # + exitcode |= systemctl.error return exitcode if __name__ == "__main__": import optparse - _o = optparse.OptionParser("%prog [options] command [name...]", - epilog="use 'help' command for more information") + _o = optparse.OptionParser("%prog [options] command [name...]", + epilog="use 'help' command for more information") _o.add_option("--version", action="store_true", - help="Show package version") + help="Show package version") _o.add_option("--system", action="store_true", default=False, - help="Connect to system manager (default)") # overrides --user + help="Connect to system manager (default)") # overrides --user _o.add_option("--user", action="store_true", default=_user_mode, - help="Connect to user service manager") + help="Connect to user service manager") # _o.add_option("-H", "--host", metavar="[USER@]HOST", # help="Operate on remote host*") # _o.add_option("-M", "--machine", metavar="CONTAINER", # help="Operate on local container*") - _o.add_option("-t","--type", metavar="TYPE", dest="unit_type", default=_unit_type, - help="List units of a particual type") - _o.add_option("--state", metavar="STATE", default=_unit_state, - help="List units with particular LOAD or SUB or ACTIVE state") - _o.add_option("-p", "--property", metavar="NAME", dest="unit_property", default=_unit_property, - help="Show only properties by this name") - _o.add_option("--what", metavar="TYPE", dest="what_kind", default=_what_kind, - help="Defines the service directories to be cleaned (configuration, state, cache, logs, runtime)") + _o.add_option("-t", "--type", metavar="TYPE", action="append", dest="only_type", default=_only_type, + help="List units of a particual type") + _o.add_option("--state", metavar="STATE", action="append", dest="only_state", default=_only_state, + help="List units with particular LOAD or SUB or ACTIVE state") + _o.add_option("-p", "--property", metavar="NAME", action="append", dest="only_property", default=_only_property, + help="Show only properties by this name") + _o.add_option("--what", metavar="TYPE", action="append", dest="only_what", default=_only_what, + help="Defines the service directories to be cleaned (configuration, state, cache, logs, runtime)") _o.add_option("-a", "--all", action="store_true", dest="show_all", default=_show_all, - help="Show all loaded units/properties, including dead empty ones. To list all units installed on the system, use the 'list-unit-files' command instead") - _o.add_option("-l","--full", action="store_true", default=_full, - help="Don't ellipsize unit names on output (never ellipsized)") + help="Show all loaded units/properties, including dead empty ones. To list all units installed on the system, use the 'list-unit-files' command instead") + _o.add_option("-l", "--full", action="store_true", default=_full, + help="Don't ellipsize unit names on output (never ellipsized)") _o.add_option("--reverse", action="store_true", - help="Show reverse dependencies with 'list-dependencies' (ignored)") + help="Show reverse dependencies with 'list-dependencies' (ignored)") _o.add_option("--job-mode", metavar="MODE", - help="Specifiy how to deal with already queued jobs, when queuing a new job (ignored)") + help="Specify how to deal with already queued jobs, when queuing a new job (ignored)") _o.add_option("--show-types", action="store_true", - help="When showing sockets, explicitly show their type (ignored)") - _o.add_option("-i","--ignore-inhibitors", action="store_true", - help="When shutting down or sleeping, ignore inhibitors (ignored)") + help="When showing sockets, explicitly show their type (ignored)") + _o.add_option("-i", "--ignore-inhibitors", action="store_true", + help="When shutting down or sleeping, ignore inhibitors (ignored)") _o.add_option("--kill-who", metavar="WHO", - help="Who to send signal to (ignored)") + help="Who to send signal to (ignored)") _o.add_option("-s", "--signal", metavar="SIG", - help="Which signal to send (ignored)") + help="Which signal to send (ignored)") _o.add_option("--now", action="store_true", default=_now, - help="Start or stop unit in addition to enabling or disabling it") - _o.add_option("-q","--quiet", action="store_true", default=_quiet, - help="Suppress output") + help="Start or stop unit in addition to enabling or disabling it") + _o.add_option("-q", "--quiet", action="store_true", default=_quiet, + help="Suppress output") _o.add_option("--no-block", action="store_true", default=False, - help="Do not wait until operation finished (ignored)") + help="Do not wait until operation finished (ignored)") _o.add_option("--no-legend", action="store_true", default=_no_legend, - help="Do not print a legend (column headers and hints)") + help="Do not print a legend (column headers and hints)") _o.add_option("--no-wall", action="store_true", default=False, - help="Don't send wall message before halt/power-off/reboot (ignored)") + help="Don't send wall message before halt/power-off/reboot (ignored)") _o.add_option("--no-reload", action="store_true", default=_no_reload, - help="Don't reload daemon after en-/dis-abling unit files") + help="Don't reload daemon after en-/dis-abling unit files") _o.add_option("--no-ask-password", action="store_true", default=_no_ask_password, - help="Do not ask for system passwords") + help="Do not ask for system passwords") # _o.add_option("--global", action="store_true", dest="globally", default=_globally, # help="Enable/disable unit files globally") # for all user logins # _o.add_option("--runtime", action="store_true", # help="Enable unit files only temporarily until next reboot") _o.add_option("-f", "--force", action="store_true", default=_force, - help="When enabling unit files, override existing symblinks / When shutting down, execute action immediately") + help="When enabling unit files, override existing symblinks / When shutting down, execute action immediately") _o.add_option("--preset-mode", metavar="TYPE", default=_preset_mode, - help="Apply only enable, only disable, or all presets [%default]") + help="Apply only enable, only disable, or all presets [%default]") _o.add_option("--root", metavar="PATH", default=_root, - help="Enable unit files in the specified root directory (used for alternative root prefix)") - _o.add_option("-n","--lines", metavar="NUM", - help="Number of journal entries to show") - _o.add_option("-o","--output", metavar="CAT", - help="change journal output mode [short, ..., cat] (ignored)") + help="Enable unit files in the specified root directory (used for alternative root prefix)") + _o.add_option("-n", "--lines", metavar="NUM", + help="Number of journal entries to show") + _o.add_option("-o", "--output", metavar="CAT", + help="change journal output mode [short, ..., cat] (ignored)") _o.add_option("--plain", action="store_true", - help="Print unit dependencies as a list instead of a tree (ignored)") + help="Print unit dependencies as a list instead of a tree (ignored)") _o.add_option("--no-pager", action="store_true", - help="Do not pipe output into pager (mostly ignored)") + help="Do not pipe output into pager (mostly ignored)") + _o.add_option("--no-warn", action="store_true", + help="Do not generate certain warnings (ignored)") # - _o.add_option("-c","--config", metavar="NAME=VAL", action="append", default=[], - help="..override internal variables (InitLoopSleep,SysInitTarget) {%default}") - _o.add_option("-e","--extra-vars", "--environment", metavar="NAME=VAL", action="append", default=[], - help="..override settings in the syntax of 'Environment='") - _o.add_option("-v","--verbose", action="count", default=0, - help="..increase debugging information level") - _o.add_option("-4","--ipv4", action="store_true", default=False, - help="..only keep ipv4 localhost in /etc/hosts") - _o.add_option("-6","--ipv6", action="store_true", default=False, - help="..only keep ipv6 localhost in /etc/hosts") - _o.add_option("-1","--init", action="store_true", default=False, - help="..keep running as init-process (default if PID 1)") + _o.add_option("-c", "--config", metavar="NAME=VAL", action="append", default=[], + help="..override internal variables (InitLoopSleep,SysInitTarget) {%default}") + _o.add_option("-e", "--extra-vars", "--environment", metavar="NAME=VAL", action="append", default=[], + help="..override settings in the syntax of 'Environment='") + _o.add_option("-v", "--verbose", action="count", default=0, + help="..increase debugging information level") + _o.add_option("-4", "--ipv4", action="store_true", default=False, + help="..only keep ipv4 localhost in /etc/hosts") + _o.add_option("-6", "--ipv6", action="store_true", default=False, + help="..only keep ipv6 localhost in /etc/hosts") + _o.add_option("-1", "--init", action="store_true", default=False, + help="..keep running as init-process (default if PID 1)") opt, args = _o.parse_args() logging.basicConfig(level = max(0, logging.FATAL - 10 * opt.verbose)) logg.setLevel(max(0, logging.ERROR - 10 * opt.verbose)) @@ -6361,15 +6766,15 @@ def logg_debug(*msg): pass _quiet = opt.quiet _root = opt.root _show_all = opt.show_all - _unit_state = opt.state - _unit_type = opt.unit_type - _unit_property = opt.unit_property - _what_kind = opt.what_kind + _only_state = opt.only_state + _only_type = opt.only_type + _only_property = opt.only_property + _only_what = opt.only_what # being PID 1 (or 0) in a container will imply --init _pid = os.getpid() - _init = opt.init or _pid in [ 1, 0 ] + _init = opt.init or _pid in [1, 0] _user_mode = opt.user - if os.geteuid() and _pid in [ 1, 0 ]: + if os.geteuid() and _pid in [1, 0]: _user_mode = True if opt.system: _user_mode = False # override --user @@ -6400,6 +6805,10 @@ def logg_debug(*msg): pass logg.debug("str %s=%s", nam, val) globals()[nam] = val.strip() logg.debug("... SysInitTarget=%s", SysInitTarget) + elif isinstance(old, list): + logg.debug("str %s+=[%s]", nam, val) + globals()[nam] += val.strip().split(",") + logg.debug("... _extra_vars=%s", _extra_vars) else: logg.warning("(ignored) unknown target type -c '%s' : %s", nam, type(old)) else: @@ -6417,21 +6826,18 @@ def logg_debug(*msg): pass loggfile.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) logg.addHandler(loggfile) logg.setLevel(logging.DEBUG) - logg.info("EXEC BEGIN %s %s%s%s", os.path.realpath(sys.argv[0]), " ".join(args), - _user_mode and " --user" or " --system", _init and " --init" or "", ) - if _root and not is_good_root(_root): - logg.warning("the --root=path should have alteast three levels /tmp/test_123/root") # + print_begin(sys.argv, args) # systemctl = Systemctl() if opt.version: - args = [ "version" ] + args = ["version"] if not args: if _init: - args = [ "default" ] + args = ["default"] else: - args = [ "list-units" ] - logg.debug("======= systemctl.py " + " ".join(args)) + args = ["list-units"] + print_begin2(args) command = args[0] modules = args[1:] try: @@ -6442,38 +6848,4 @@ def logg_debug(*msg): pass systemctl.force_ipv4() elif opt.ipv6: systemctl.force_ipv6() - found = False - # command NAME - if command.startswith("__"): - command_name = command[2:] - command_func = getattr(systemctl, command_name, None) - if callable(command_func) and not found: - found = True - result = command_func(*modules) - command_name = command.replace("-","_").replace(".","_")+"_modules" - command_func = getattr(systemctl, command_name, None) - if callable(command_func) and not found: - found = True - result = command_func(*modules) - command_name = "show_"+command.replace("-","_").replace(".","_") - command_func = getattr(systemctl, command_name, None) - if callable(command_func) and not found: - found = True - result = command_func(*modules) - command_name = "system_"+command.replace("-","_").replace(".","_") - command_func = getattr(systemctl, command_name, None) - if callable(command_func) and not found: - found = True - result = command_func() - command_name = "systems_"+command.replace("-","_").replace(".","_") - command_func = getattr(systemctl, command_name, None) - if callable(command_func) and not found: - found = True - result = command_func() - if not found: - logg.error("Unknown operation %s.", command) - sys.exit(1) - # - exitcode = print_result(result) - exitcode |= systemctl.error - sys.exit(exitcode) + sys.exit(run(command, *modules))