systemd-systemctl: enable templated services

The wrapper script previously did not allow to instantiate templated
services without DefaultInstance defined. This change allows
foo@.service be installed and therefor be in enabled state.

In real systemd land, templated units without a specified instance are
valid, but we restrict it here a bit in order to not have a foo@.service
with:

  [Install]
  WantedBy=bar.service    # bar.service starts -> start foo@???.service

In this case, system can never resolve the specific instance of
foo@.service when bar.service is started. It is fixed by adding the
template_only variable, which restricts the dependents of generic
template units to template-only ones. This means the following is still
legal for the above foo@.service:

  [Install]
  WantedBy=bar@.service   # bar@1.service starts -> start foo@1.service
                          # bar@2.service starts -> start foo@2.service
                          # etc...

  WantedBy=bas@1.service  # bas@1.service starts -> start foo@1.service
                          # bas@2.service starts -> no dependency here
                          # etc...

Signed-off-by: Fabian Pflug <f.pflug@pengutronix.de>
Signed-off-by: Khem Raj <khem.raj@oss.qualcomm.com>
This commit is contained in:
Fabian Pflug authored and Khem Raj committed 2026-09-08 08:23:16 -07:00
1 parent abb086bef6
commit 1c4c37d21a
1 file changed
+20 -10
@@ -179,12 +179,18 @@ class SystemdUnit():
raise SystemdUnitNotFoundError(self.root, unit)
def _process_deps(self, config, service, location, prop, dirstem):
def _process_deps(self, config, service, location, prop, dirstem,
template_only):
systemdir = self.root / SYSCONFDIR / "systemd" / "system"
target = ROOT / location.relative_to(self.root)
try:
for dependent in config.get('Install', prop):
# template-only dependencies means either foo@.unit or foo@bar.unit,
# but not foo.unit, as then systemd cannot resolve the instance
if template_only and not re.match(r"[^@]+@[^\.]*\.", dependent):
continue
wants = systemdir / "{}.{}".format(dependent, dirstem) / service
add_link(wants, target)
@@ -209,20 +215,24 @@ class SystemdUnit():
return
config = SystemdFile(self.root, path)
service = self.unit
template_only = False
# handle enabling a template unit with unspecified instance
if instance == "":
try:
default_instance = config.get('Install', 'DefaultInstance')[0]
except KeyError:
# no default instance, so nothing to enable
return
# no default instance, allow template only dependencies
template_only = True
else:
# otherwise rewrite the service name with the default instance
service = self.unit.replace("@.", "@{}.".format(default_instance))
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')
self._process_deps(config, service, path, 'WantedBy', 'wants',
template_only)
self._process_deps(config, service, path, 'RequiredBy', 'requires',
template_only)
try:
for also in config.get('Install', 'Also'):