Repository: osbuild/osbuild
Pull request #1505 (2023-12-14 12:49:39)

org.osbuild.systemd: Support masking generators
This adds a new key masked_generators, similar to masked_services, which masks systemd generators from running at boot, by creating symlinks to /dev/null in /etc/systemd/systemd-generators, as described in:
 https://www.freedesktop.org/software/systemd/man/latest/systemd.generator.html#Description

This will be useful for the automotive project, as it allows disabling of unsupported things like sysv or rc.local legacy support, while improving boot performance.

mvo5: > The PR extended the current test to also test this feature.

Sorry again, I should have been more clear. I was thinking about a small unit test, I attached what I had in mind. It maybe slightly over the top as the extended stage test checks that the expected mask file is created. The (small in this case) values this new unit test style tests are adding is a) run faster b) more precise, i.e. we can check the link target is correct. 

Anyway, sorry for my nitpicks, this looks fine.

[0001-stages-systemd-add-unit-test-for-the-new-masked_gene.patch.txt](https://github.com/osbuild/osbuild/files/13683238/0001-stages-systemd-add-unit-test-for-the-new-masked_gene.patch.txt)

alexlarsson: Added the comment and your unit test.

Patch:
diff --git a/osbuild/mounts.py b/osbuild/mounts.py
index 54fc5b17..76a143cc 100644
--- a/osbuild/mounts.py
+++ b/osbuild/mounts.py
@@ -58,7 +58,18 @@ class MountManager:
 
     def mount(self, mount: Mount) -> Dict:
 
+        # Get the absolute path to the source device inside the
+        # temporary filesystem (i.e. /run/osbuild/osbuild-dev-xyz/loop0)
+        # and also the relative path to the source device inside
+        # that filesystem (i.e. loop0). If the device also exists on the
+        # host in `/dev` (like /dev/loop0), we'll use that path for the
+        # mount because some tools (like grub2-install) consult mountinfo
+        # to try to canonicalize paths for mounts and inside the bwrap env
+        # the device will be under `/dev`. https://github.com/osbuild/osbuild/issues/1492
         source = self.devices.device_abspath(mount.device)
+        relpath = self.devices.device_relpath(mount.device)
+        if relpath and os.path.exists(os.path.join('/dev', relpath)):
+            source = os.path.join('/dev', relpath)
 
         root = os.fspath(self.root)
 
diff --git a/stages/org.osbuild.systemd b/stages/org.osbuild.systemd
index ffe875f0..994e3a71 100755
--- a/stages/org.osbuild.systemd
+++ b/stages/org.osbuild.systemd
@@ -24,6 +24,7 @@ the following subset of options:
     - 'Environment' option
 """
 
+import os
 import subprocess
 import sys
 
@@ -47,6 +48,11 @@ SCHEMA = r"""
     "items": { "type": "string" },
     "description": "Array of systemd unit names to be masked"
   },
+  "masked_generators": {
+    "type": "array",
+    "items": { "type": "string" },
+    "description": "Array of systemd generators to be masked"
+  },
   "default_target": {
     "type": "string",
     "description": "The default target to boot into"
@@ -59,6 +65,7 @@ def main(tree, options):
     enabled_services = options.get("enabled_services", [])
     disabled_services = options.get("disabled_services", [])
     masked_services = options.get("masked_services", [])
+    masked_generators = options.get("masked_generators", [])
     default_target = options.get("default_target")
 
     for service in enabled_services:
@@ -70,6 +77,14 @@ def main(tree, options):
     for service in masked_services:
         subprocess.run(["systemctl", "--root", tree, "mask", service], check=True)
 
+    if masked_generators:
+        # systemctl can't mask generators, so manually create the mask
+        generator_dir = f"{tree}/etc/systemd/system-generators"
+        os.makedirs(generator_dir, exist_ok=True)
+        for generator in masked_generators:
+            target = os.path.join(generator_dir, generator)
+            os.symlink("/dev/null", target)
+
     if default_target:
         subprocess.run(["systemctl", "--root", tree, "set-default", default_target], check=True)
 


Test patch:
diff --git a/stages/test/test_systemd.py b/stages/test/test_systemd.py
new file mode 100644
index 00000000..92eef2e5
--- /dev/null
+++ b/stages/test/test_systemd.py
@@ -0,0 +1,19 @@
+#!/usr/bin/python3
+
+import os
+import os.path
+
+from osbuild.testutil.imports import import_module_from_path
+
+
+def test_systemd_masked_generators(tmp_path):
+    stage_path = os.path.join(os.path.dirname(__file__), "../org.osbuild.systemd")
+    stage = import_module_from_path("stage", stage_path)
+
+    options = {"masked_generators": ["fake-generator"]}
+
+    stage.main(tmp_path, options)
+
+    masked_generator_path = os.path.join(tmp_path, "etc/systemd/system-generators/fake-generator")
+    assert os.path.lexists(masked_generator_path)
+    assert os.readlink(masked_generator_path) == "/dev/null"
diff --git a/test/data/stages/systemd/b.json b/test/data/stages/systemd/b.json
index 36ecccfe..81ba3d17 100644
--- a/test/data/stages/systemd/b.json
+++ b/test/data/stages/systemd/b.json
@@ -649,6 +649,9 @@
             "masked_services": [
               "ldconfig"
             ],
+            "masked_generators": [
+              "systemd-sysv-generator"
+            ],
             "disabled_services": [
               "sshd"
             ]
diff --git a/test/data/stages/systemd/b.mpp.yaml b/test/data/stages/systemd/b.mpp.yaml
index 46824df2..d8b00250 100644
--- a/test/data/stages/systemd/b.mpp.yaml
+++ b/test/data/stages/systemd/b.mpp.yaml
@@ -35,5 +35,7 @@ pipelines:
             - nftables
           masked_services:
             - ldconfig
+          masked_generators:
+            - systemd-sysv-generator
           disabled_services:
             - sshd
diff --git a/test/data/stages/systemd/diff.json b/test/data/stages/systemd/diff.json
index 7ceb776c..78328693 100644
--- a/test/data/stages/systemd/diff.json
+++ b/test/data/stages/systemd/diff.json
@@ -1,7 +1,9 @@
 {
   "added_files": [
     "/etc/systemd/system/ldconfig.service",
-    "/etc/systemd/system/multi-user.target.wants/nftables.service"
+    "/etc/systemd/system/multi-user.target.wants/nftables.service",
+    "/etc/systemd/system-generators",
+    "/etc/systemd/system-generators/systemd-sysv-generator"
   ],
   "deleted_files": [
     "/etc/systemd/system/multi-user.target.wants/sshd.service"