diff mbox series

[meta-oe] Add nativesdk-systemd-systemctl as dependency of dnf-plugin-tui

Message ID 1665547429-23508-1-git-send-email-wangmy@fujitsu.com
State Under Review
Headers show
Series [meta-oe] Add nativesdk-systemd-systemctl as dependency of dnf-plugin-tui | expand

Commit Message

Mingyu Wang (Fujitsu) Oct. 12, 2022, 4:03 a.m. UTC
Signed-off-by: Wang Mingyu <wangmy@fujitsu.com>
---
 .../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

Comments

Jose Quaresma Oct. 12, 2022, 5:20 p.m. UTC | #1
Hi wangmy,

wangmy <wangmy@fujitsu.com> escreveu no dia quarta, 12/10/2022 à(s) 05:04:

> Signed-off-by: Wang Mingyu <wangmy@fujitsu.com>
> ---
>  .../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 ?

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
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#99142):
> https://lists.openembedded.org/g/openembedded-devel/message/99142
> Mute This Topic: https://lists.openembedded.org/mt/94275098/5052612
> Group Owner: openembedded-devel+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [
> quaresma.jose@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
>
Khem Raj Oct. 12, 2022, 10:27 p.m. UTC | #2
On Wed, Oct 12, 2022 at 10:20 AM Jose Quaresma <quaresma.jose@gmail.com> wrote:
>
> Hi wangmy,
>
> wangmy <wangmy@fujitsu.com> escreveu no dia quarta, 12/10/2022 à(s) 05:04:
>>
>> Signed-off-by: Wang Mingyu <wangmy@fujitsu.com>
>> ---
>>  .../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 (#99146): https://lists.openembedded.org/g/openembedded-devel/message/99146
> Mute This Topic: https://lists.openembedded.org/mt/94275098/1997914
> Group Owner: openembedded-devel+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [raj.khem@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
Ross Burton Oct. 13, 2022, 10:09 a.m. UTC | #3
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 <raj.khem=gmail.com@lists.openembedded.org> wrote:
> 
> On Wed, Oct 12, 2022 at 10:20 AM Jose Quaresma <quaresma.jose@gmail.com> wrote:
>> 
>> Hi wangmy,
>> 
>> wangmy <wangmy@fujitsu.com> escreveu no dia quarta, 12/10/2022 à(s) 05:04:
>>> 
>>> Signed-off-by: Wang Mingyu <wangmy@fujitsu.com>
>>> ---
>>> .../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 (#99151): https://lists.openembedded.org/g/openembedded-devel/message/99151
> Mute This Topic: https://lists.openembedded.org/mt/94275098/6875888
> Group Owner: openembedded-devel+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [ross.burton@arm.com]
> -=-=-=-=-=-=-=-=-=-=-=-
Khem Raj Oct. 13, 2022, 3:35 p.m. UTC | #4
On Thu, Oct 13, 2022 at 3:10 AM Ross Burton <Ross.Burton@arm.com> wrote:
>
> 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.

agreed. Although its perhaps better to soak it a bit, I thought here.

>
> 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 <raj.khem=gmail.com@lists.openembedded.org> wrote:
> >
> > On Wed, Oct 12, 2022 at 10:20 AM Jose Quaresma <quaresma.jose@gmail.com> wrote:
> >>
> >> Hi wangmy,
> >>
> >> wangmy <wangmy@fujitsu.com> escreveu no dia quarta, 12/10/2022 à(s) 05:04:
> >>>
> >>> Signed-off-by: Wang Mingyu <wangmy@fujitsu.com>
> >>> ---
> >>> .../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 (#99151): https://lists.openembedded.org/g/openembedded-devel/message/99151
> > Mute This Topic: https://lists.openembedded.org/mt/94275098/6875888
> > Group Owner: openembedded-devel+owner@lists.openembedded.org
> > Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [ross.burton@arm.com]
> > -=-=-=-=-=-=-=-=-=-=-=-
>
Mingyu Wang (Fujitsu) Oct. 14, 2022, 12:23 a.m. UTC | #5
> Also, why would a nativesdk DNF need to manage systemd units?
When executing postinstall, the system is needed.

  --
Best Regards
---------------------------------------------------
Wang Mingyu
Development Dept.I
Nanjing Fujitsu Nanda Software Tech. Co., Ltd.(FNST) No. 6 Wenzhu Road, Nanjing, 210012, China
TEL: +86+25-86630566-8568
COINS: 79988548
FAX: +86+25-83317685
MAIL: wangmy@fujitsu.com
http://www.fujitsu.com/cn/fnst/

> -----Original Message-----
> From: Ross Burton <Ross.Burton@arm.com>
> Sent: Thursday, October 13, 2022 6:10 PM
> To: raj.khem@gmail.com
> Cc: Jose Quaresma <quaresma.jose@gmail.com>; Wang, Mingyu/王 鸣瑜
> <wangmy@fujitsu.com>; openembedded-devel@lists.openembedded.org
> Subject: Re: [oe] [meta-oe] Add nativesdk-systemd-systemctl as dependency of
> dnf-plugin-tui
> 
> 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
> <raj.khem=gmail.com@lists.openembedded.org> wrote:
> >
> > On Wed, Oct 12, 2022 at 10:20 AM Jose Quaresma
> <quaresma.jose@gmail.com> wrote:
> >>
> >> Hi wangmy,
> >>
> >> wangmy <wangmy@fujitsu.com> escreveu no dia quarta, 12/10/2022 à(s)
> 05:04:
> >>>
> >>> Signed-off-by: Wang Mingyu <wangmy@fujitsu.com>
> >>> ---
> >>> .../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.b
> >>> +++ b
> >>> @@ -0,0 +1,17 @@
> >>> +SUMMARY = "Wrapper for enabling systemd services"
> >>> +
> >>> +LICENSE = "MIT"
> >>> +LIC_FILES_CHKSUM =
> "file://${COREBASE}/meta/COPYING.MIT;md5=3da9cfbcb788c80a0384361b4de
> 20420"
> >>> +
> >>> +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 (#99151):
> > https://lists.openembedded.org/g/openembedded-devel/message/99151
> > Mute This Topic: https://lists.openembedded.org/mt/94275098/6875888
> > Group Owner: openembedded-devel+owner@lists.openembedded.org
> > Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub
> > [ross.burton@arm.com]
> > -=-=-=-=-=-=-=-=-=-=-=-
Mingyu Wang (Fujitsu) Oct. 14, 2022, 12:26 a.m. UTC | #6
> agreed. Although its perhaps better to soak it a bit, I thought here.

We will review again to see if we can add this script to the dnf-plugin-ui directory.
Do you think it would be better?

  --
Best Regards
---------------------------------------------------
Wang Mingyu
Development Dept.I
Nanjing Fujitsu Nanda Software Tech. Co., Ltd.(FNST) No. 6 Wenzhu Road, Nanjing, 210012, China
TEL: +86+25-86630566-8568
COINS: 79988548
FAX: +86+25-83317685
MAIL: wangmy@fujitsu.com
http://www.fujitsu.com/cn/fnst/

> -----Original Message-----
> From: Khem Raj <raj.khem@gmail.com>
> Sent: Thursday, October 13, 2022 11:36 PM
> To: Ross Burton <Ross.Burton@arm.com>
> Cc: Jose Quaresma <quaresma.jose@gmail.com>; Wang, Mingyu/王 鸣瑜
> <wangmy@fujitsu.com>; openembedded-devel@lists.openembedded.org
> Subject: Re: [oe] [meta-oe] Add nativesdk-systemd-systemctl as dependency of
> dnf-plugin-tui
> 
> On Thu, Oct 13, 2022 at 3:10 AM Ross Burton <Ross.Burton@arm.com> wrote:
> >
> > 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.
> 
> agreed. Although its perhaps better to soak it a bit, I thought here.
> 
> >
> > 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
> <raj.khem=gmail.com@lists.openembedded.org> wrote:
> > >
> > > On Wed, Oct 12, 2022 at 10:20 AM Jose Quaresma
> <quaresma.jose@gmail.com> wrote:
> > >>
> > >> Hi wangmy,
> > >>
> > >> wangmy <wangmy@fujitsu.com> escreveu no dia quarta, 12/10/2022 à(s)
> 05:04:
> > >>>
> > >>> Signed-off-by: Wang Mingyu <wangmy@fujitsu.com>
> > >>> ---
> > >>> .../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=3da9cfbcb788c80a0384361b4de
> 20420"
> > >>> +
> > >>> +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 (#99151):
> > > https://lists.openembedded.org/g/openembedded-devel/message/99151
> > > Mute This Topic: https://lists.openembedded.org/mt/94275098/6875888
> > > Group Owner: openembedded-devel+owner@lists.openembedded.org
> > > Unsubscribe:
> > > https://lists.openembedded.org/g/openembedded-devel/unsub
> > > [ross.burton@arm.com]
> > > -=-=-=-=-=-=-=-=-=-=-=-
> >
Ross Burton Oct. 14, 2022, 10:31 a.m. UTC | #7
On 14 Oct 2022, at 01:23, wangmy via lists.openembedded.org <wangmy=fujitsu.com@lists.openembedded.org> wrote:
> 
>> Also, why would a nativesdk DNF need to manage systemd units?
> When executing postinstall, the system is needed.

So this is a direct clone of the systemctl in systemd-systemctl-native, which solves exactly the same problem.

We shouldn’t be duplicating code.  If this is a better script then it should replace the one in oe-core.  If the one in oe-core is sufficient, then it can be simply be extended to build for nativesdk.

Ross
Khem Raj Oct. 14, 2022, 3:08 p.m. UTC | #8
On Fri, Oct 14, 2022 at 3:31 AM Ross Burton <Ross.Burton@arm.com> wrote:
>
> On 14 Oct 2022, at 01:23, wangmy via lists.openembedded.org <wangmy=fujitsu.com@lists.openembedded.org> wrote:
> >
> >> Also, why would a nativesdk DNF need to manage systemd units?
> > When executing postinstall, the system is needed.
>
> So this is a direct clone of the systemctl in systemd-systemctl-native, which solves exactly the same problem.
>

looks like that. Yes, I agree, I guess replacing oe-core script with
python could be an improvement.

> We shouldn’t be duplicating code.  If this is a better script then it should replace the one in oe-core.  If the one in oe-core is sufficient, then it can be simply be extended to build for nativesdk.
>
> Ross
diff mbox series

Patch

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"
+
+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()