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]]
-=-=-=-=-=-=-=-=-=-=-=-

Reply via email to