What’s the difference between this python implementation of systemctl, and the shell implementation that’s already in oe-core in systemd-systemctl-native?
Looks like pointless duplication to me. If the Python implementation here is better, then it should be in core. Also, why would a nativesdk DNF need to manage systemd units? Ross > On 12 Oct 2022, at 23:27, Khem Raj via lists.openembedded.org > <[email protected]> wrote: > > On Wed, Oct 12, 2022 at 10:20 AM Jose Quaresma <[email protected]> > wrote: >> >> Hi wangmy, >> >> wangmy <[email protected]> escreveu no dia quarta, 12/10/2022 à(s) 05:04: >>> >>> Signed-off-by: Wang Mingyu <[email protected]> >>> --- >>> .../systemd/nativesdk-systemd-systemctl.bb | 17 + >>> .../systemd/systemd-systemctl/systemctl | 340 ++++++++++++++++++ >>> 2 files changed, 357 insertions(+) >>> create mode 100644 >>> meta-oe/recipes-devtools/systemd/nativesdk-systemd-systemctl.bb >>> create mode 100755 >>> meta-oe/recipes-devtools/systemd/systemd-systemctl/systemctl >>> >>> diff --git >>> a/meta-oe/recipes-devtools/systemd/nativesdk-systemd-systemctl.bb >>> b/meta-oe/recipes-devtools/systemd/nativesdk-systemd-systemctl.bb >>> new file mode 100644 >>> index 0000000000..7ac21aa260 >>> --- /dev/null >>> +++ b/meta-oe/recipes-devtools/systemd/nativesdk-systemd-systemctl.bb >>> @@ -0,0 +1,17 @@ >>> +SUMMARY = "Wrapper for enabling systemd services" >>> + >>> +LICENSE = "MIT" >>> +LIC_FILES_CHKSUM = >>> "file://${COREBASE}/meta/COPYING.MIT;md5=3da9cfbcb788c80a0384361b4de20420" >>> + >>> +PR = "r6" >> >> >> Why is this recipe starting with the package release 6 ? >> > > Right, I have fixed it before accepting. > >> Jose >> >>> >>> + >>> +inherit nativesdk >>> + >>> +SRC_URI = "file://systemctl" >>> + >>> +S = "${WORKDIR}" >>> + >>> +do_install() { >>> + install -d ${D}${bindir} >>> + install -m 0755 ${WORKDIR}/systemctl ${D}${bindir} >>> +} >>> diff --git a/meta-oe/recipes-devtools/systemd/systemd-systemctl/systemctl >>> b/meta-oe/recipes-devtools/systemd/systemd-systemctl/systemctl >>> new file mode 100755 >>> index 0000000000..6324319a45 >>> --- /dev/null >>> +++ b/meta-oe/recipes-devtools/systemd/systemd-systemctl/systemctl >>> @@ -0,0 +1,340 @@ >>> +#!/usr/bin/env python3 >>> +"""systemctl: subset of systemctl used for image construction >>> + >>> +Mask/preset systemd units >>> +""" >>> + >>> +import argparse >>> +import fnmatch >>> +import os >>> +import re >>> +import sys >>> + >>> +from collections import namedtuple >>> +from pathlib import Path >>> + >>> +version = 1.0 >>> + >>> +ROOT = Path("/") >>> +SYSCONFDIR = Path("etc") >>> +BASE_LIBDIR = Path("lib") >>> +LIBDIR = Path("usr", "lib") >>> + >>> +locations = list() >>> + >>> + >>> +class SystemdFile(): >>> + """Class representing a single systemd configuration file""" >>> + def __init__(self, root, path): >>> + self.sections = dict() >>> + self._parse(root, path) >>> + dirname = os.path.basename(path.name) + ".d" >>> + for location in locations: >>> + for path2 in sorted((root / location / "system" / >>> dirname).glob("*.conf")): >>> + self._parse(root, path2) >>> + >>> + def _parse(self, root, path): >>> + """Parse a systemd syntax configuration file >>> + >>> + Args: >>> + path: A pathlib.Path object pointing to the file >>> + >>> + """ >>> + skip_re = re.compile(r"^\s*([#;]|$)") >>> + section_re = re.compile(r"^\s*\[(?P<section>.*)\]") >>> + kv_re = re.compile(r"^\s*(?P<key>[^\s]+)\s*=\s*(?P<value>.*)") >>> + section = None >>> + >>> + if path.is_symlink(): >>> + try: >>> + path.resolve() >>> + except FileNotFoundError: >>> + # broken symlink, try relative to root >>> + path = root / >>> Path(os.readlink(str(path))).relative_to(ROOT) >>> + >>> + with path.open() as f: >>> + for line in f: >>> + if skip_re.match(line): >>> + continue >>> + >>> + line = line.strip() >>> + m = section_re.match(line) >>> + if m: >>> + if m.group('section') not in self.sections: >>> + section = dict() >>> + self.sections[m.group('section')] = section >>> + else: >>> + section = self.sections[m.group('section')] >>> + continue >>> + >>> + while line.endswith("\\"): >>> + line += f.readline().rstrip("\n") >>> + >>> + m = kv_re.match(line) >>> + k = m.group('key') >>> + v = m.group('value') >>> + if k not in section: >>> + section[k] = list() >>> + section[k].extend(v.split()) >>> + >>> + def get(self, section, prop): >>> + """Get a property from section >>> + >>> + Args: >>> + section: Section to retrieve property from >>> + prop: Property to retrieve >>> + >>> + Returns: >>> + List representing all properties of type prop in section. >>> + >>> + Raises: >>> + KeyError: if ``section`` or ``prop`` not found >>> + """ >>> + return self.sections[section][prop] >>> + >>> + >>> +class Presets(): >>> + """Class representing all systemd presets""" >>> + def __init__(self, scope, root): >>> + self.directives = list() >>> + self._collect_presets(scope, root) >>> + >>> + def _parse_presets(self, presets): >>> + """Parse presets out of a set of preset files""" >>> + skip_re = re.compile(r"^\s*([#;]|$)") >>> + directive_re = >>> re.compile(r"^\s*(?P<action>enable|disable)\s+(?P<unit_name>(.+))") >>> + >>> + Directive = namedtuple("Directive", "action unit_name") >>> + for preset in presets: >>> + with preset.open() as f: >>> + for line in f: >>> + m = directive_re.match(line) >>> + if m: >>> + directive = Directive(action=m.group('action'), >>> + >>> unit_name=m.group('unit_name')) >>> + self.directives.append(directive) >>> + elif skip_re.match(line): >>> + pass >>> + else: >>> + sys.exit("Unparsed preset line in >>> {}".format(preset)) >>> + >>> + def _collect_presets(self, scope, root): >>> + """Collect list of preset files""" >>> + presets = dict() >>> + for location in locations: >>> + paths = (root / location / scope).glob("*.preset") >>> + for path in paths: >>> + # earlier names override later ones >>> + if path.name not in presets: >>> + presets[path.name] = path >>> + >>> + self._parse_presets([v for k, v in sorted(presets.items())]) >>> + >>> + def state(self, unit_name): >>> + """Return state of preset for unit_name >>> + >>> + Args: >>> + presets: set of presets >>> + unit_name: name of the unit >>> + >>> + Returns: >>> + None: no matching preset >>> + `enable`: unit_name is enabled >>> + `disable`: unit_name is disabled >>> + """ >>> + for directive in self.directives: >>> + if fnmatch.fnmatch(unit_name, directive.unit_name): >>> + return directive.action >>> + >>> + return None >>> + >>> + >>> +def add_link(path, target): >>> + try: >>> + path.parent.mkdir(parents=True) >>> + except FileExistsError: >>> + pass >>> + if not path.is_symlink(): >>> + print("ln -s {} {}".format(target, path)) >>> + path.symlink_to(target) >>> + >>> + >>> +class SystemdUnitNotFoundError(Exception): >>> + def __init__(self, path, unit): >>> + self.path = path >>> + self.unit = unit >>> + >>> + >>> +class SystemdUnit(): >>> + def __init__(self, root, unit): >>> + self.root = root >>> + self.unit = unit >>> + self.config = None >>> + >>> + def _path_for_unit(self, unit): >>> + for location in locations: >>> + path = self.root / location / "system" / unit >>> + if path.exists() or path.is_symlink(): >>> + return path >>> + >>> + raise SystemdUnitNotFoundError(self.root, unit) >>> + >>> + def _process_deps(self, config, service, location, prop, dirstem): >>> + systemdir = self.root / SYSCONFDIR / "systemd" / "system" >>> + >>> + target = ROOT / location.relative_to(self.root) >>> + try: >>> + for dependent in config.get('Install', prop): >>> + wants = systemdir / "{}.{}".format(dependent, dirstem) / >>> service >>> + add_link(wants, target) >>> + >>> + except KeyError: >>> + pass >>> + >>> + def enable(self, caller_unit=None): >>> + # if we're enabling an instance, first extract the actual instance >>> + # then figure out what the template unit is >>> + template = re.match(r"[^@]+@(?P<instance>[^\.]*)\.", self.unit) >>> + if template: >>> + instance = template.group('instance') >>> + unit = re.sub(r"@[^\.]*\.", "@.", self.unit, 1) >>> + else: >>> + instance = None >>> + unit = self.unit >>> + >>> + path = self._path_for_unit(unit) >>> + >>> + if path.is_symlink(): >>> + # ignore aliases >>> + return >>> + >>> + config = SystemdFile(self.root, path) >>> + if instance == "": >>> + try: >>> + default_instance = config.get('Install', >>> 'DefaultInstance')[0] >>> + except KeyError: >>> + # no default instance, so nothing to enable >>> + return >>> + >>> + service = self.unit.replace("@.", >>> + "@{}.".format(default_instance)) >>> + else: >>> + service = self.unit >>> + >>> + self._process_deps(config, service, path, 'WantedBy', 'wants') >>> + self._process_deps(config, service, path, 'RequiredBy', 'requires') >>> + >>> + try: >>> + for also in config.get('Install', 'Also'): >>> + try: >>> + if caller_unit != also: >>> + SystemdUnit(self.root, also).enable(unit) >>> + except SystemdUnitNotFoundError as e: >>> + sys.exit("Error: Systemctl also enable issue with %s >>> (%s)" % (service, e.unit)) >>> + >>> + except KeyError: >>> + pass >>> + >>> + systemdir = self.root / SYSCONFDIR / "systemd" / "system" >>> + target = ROOT / path.relative_to(self.root) >>> + try: >>> + for dest in config.get('Install', 'Alias'): >>> + alias = systemdir / dest >>> + add_link(alias, target) >>> + >>> + except KeyError: >>> + pass >>> + >>> + def mask(self): >>> + systemdir = self.root / SYSCONFDIR / "systemd" / "system" >>> + add_link(systemdir / self.unit, "/dev/null") >>> + >>> + >>> +def collect_services(root): >>> + """Collect list of service files""" >>> + services = set() >>> + for location in locations: >>> + paths = (root / location / "system").glob("*") >>> + for path in paths: >>> + if path.is_dir(): >>> + continue >>> + services.add(path.name) >>> + >>> + return services >>> + >>> + >>> +def preset_all(root): >>> + presets = Presets('system-preset', root) >>> + services = collect_services(root) >>> + >>> + for service in services: >>> + state = presets.state(service) >>> + >>> + if state == "enable" or state is None: >>> + try: >>> + SystemdUnit(root, service).enable() >>> + except SystemdUnitNotFoundError: >>> + sys.exit("Error: Systemctl preset_all issue in %s" % >>> service) >>> + >>> + # If we populate the systemd links we also create /etc/machine-id, >>> which >>> + # allows systemd to boot with the filesystem read-only before >>> generating >>> + # a real value and then committing it back. >>> + # >>> + # For the stateless configuration, where /etc is generated at runtime >>> + # (for example on a tmpfs), this script shouldn't run at all and we >>> + # allow systemd to completely populate /etc. >>> + (root / SYSCONFDIR / "machine-id").touch() >>> + >>> + >>> +def main(): >>> + if sys.version_info < (3, 4, 0): >>> + sys.exit("Python 3.4 or greater is required") >>> + >>> + parser = argparse.ArgumentParser() >>> + parser.add_argument('command', nargs='?', choices=['enable', 'mask', >>> + 'preset-all']) >>> + parser.add_argument('service', nargs=argparse.REMAINDER) >>> + parser.add_argument('--root') >>> + parser.add_argument('--preset-mode', >>> + choices=['full', 'enable-only', 'disable-only'], >>> + default='full') >>> + >>> + args = parser.parse_args() >>> + >>> + root = Path(args.root) if args.root else ROOT >>> + >>> + locations.append(SYSCONFDIR / "systemd") >>> + # Handle the usrmerge case by ignoring /lib when it's a symlink >>> + if not (root / BASE_LIBDIR).is_symlink(): >>> + locations.append(BASE_LIBDIR / "systemd") >>> + locations.append(LIBDIR / "systemd") >>> + >>> + command = args.command >>> + if not command: >>> + parser.print_help() >>> + return 0 >>> + >>> + if command == "mask": >>> + for service in args.service: >>> + try: >>> + SystemdUnit(root, service).mask() >>> + except SystemdUnitNotFoundError as e: >>> + sys.exit("Error: Systemctl main mask issue in %s (%s)" % >>> (service, e.unit)) >>> + elif command == "enable": >>> + for service in args.service: >>> + try: >>> + SystemdUnit(root, service).enable() >>> + except SystemdUnitNotFoundError as e: >>> + sys.exit("Error: Systemctl main enable issue in %s (%s)" % >>> (service, e.unit)) >>> + elif command == "preset-all": >>> + if len(args.service) != 0: >>> + sys.exit("Too many arguments.") >>> + if args.preset_mode != "enable-only": >>> + sys.exit("Only enable-only is supported as preset-mode.") >>> + preset_all(root) >>> + else: >>> + raise RuntimeError() >>> + >>> + >>> +if __name__ == '__main__': >>> + main() >>> -- >>> 2.25.1 >>> >>> >>> >>> >> >> >> -- >> Best regards, >> >> José Quaresma >> >> >> > >
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#99157): https://lists.openembedded.org/g/openembedded-devel/message/99157 Mute This Topic: https://lists.openembedded.org/mt/94275098/21656 Group Owner: [email protected] Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
