]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add fallback-default-target build option
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Tue, 23 Jun 2026 08:43:50 +0000 (10:43 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 2 Jul 2026 15:19:18 +0000 (17:19 +0200)
Add a 'fallback-default-target' meson option that configures which unit
to activate when default.target is not installed. This makes things more
resilient in general and is also useful for minimal/statically-linked
installations that may not ship a default.target symlink.

'graphical.target' is used as the default value of the setting, because
that's what we symlink as default.target in units/meson.build.

In the initrd, we had a fallback to start default.target if initrd.target
cannot be started. This fallback is changed to only do that if it is
not found, not on other errors. This seems more correct (and makes
the two fallbacks symmetrical.)

man/custom-entities.ent.in
man/systemd.special.xml
meson.build
meson_options.txt
src/core/main.c
src/test/test-unit-name.c

index 4494a1c3ab1af501ec649ad0d52555a4e4de7ef2..beff1529a785c1013516b57a41f0ac2d487cca09 100644 (file)
@@ -15,6 +15,7 @@
 <!ENTITY HIGH_RLIMIT_NOFILE "{{HIGH_RLIMIT_NOFILE}}">
 <!ENTITY DEFAULT_DNSSEC_MODE "{{DEFAULT_DNSSEC_MODE_STR}}">
 <!ENTITY DEFAULT_DNS_OVER_TLS_MODE "{{DEFAULT_DNS_OVER_TLS_MODE_STR}}">
+<!ENTITY FALLBACK_DEFAULT_TARGET "{{FALLBACK_DEFAULT_TARGET}}">
 <!ENTITY DEFAULT_TIMEOUT_SEC "{{DEFAULT_TIMEOUT_SEC}} s">
 <!ENTITY DEFAULT_USER_TIMEOUT_SEC "{{DEFAULT_USER_TIMEOUT_SEC}} s">
 <!ENTITY SYSTEMD_DEFAULT_KEYMAP "{{SYSTEMD_DEFAULT_KEYMAP}}">
index f6f35b861d01a7d30e3e3a7b2e4ab0289fc53475..deba4f8e4f59cfd96a17942abba8c2e252af61aa 100644 (file)
@@ -1,6 +1,9 @@
 <?xml version='1.0'?> <!--*-nxml-*-->
 <!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
-  "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
+  "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+<!ENTITY % entities SYSTEM "custom-entities.ent" >
+%entities;
+]>
 <!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
 
 <refentry id="systemd.special" xmlns:xi="http://www.w3.org/2001/XInclude">
             <varname>3</varname>, <varname>5</varname>, …; see
             <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
 
+            <para>If <filename>default.target</filename> is not present on disk and no unit was requested on
+            the kernel command line, the default set at compile time will be used
+            (<filename>&FALLBACK_DEFAULT_TARGET;</filename>).</para>
+
+            <para>If the default unit or one of the fallbacks cannot be started, systemd starts
+            <filename>rescue.target</filename>.</para>
+
             <para>For typical unit files please set <literal>WantedBy=</literal> to a regular target (like
             <filename>multi-user.target</filename> or <filename>graphical.target</filename>),
             instead of <filename>default.target</filename>, since such a service will also be run on special
index abdde0040b3c0b5957ad054c71cc626f3aa4c737..423f92183442eb072204a9554d1f1ffcc93118a2 100644 (file)
@@ -726,6 +726,9 @@ if fallback_hostname == '' or fallback_hostname[0] == '.' or fallback_hostname[0
 endif
 conf.set_quoted('FALLBACK_HOSTNAME', fallback_hostname)
 
+fallback_default_target = get_option('fallback-default-target')
+conf.set_quoted('FALLBACK_DEFAULT_TARGET', fallback_default_target)
+
 extra_net_naming_schemes = []
 extra_net_naming_map = []
 foreach scheme: get_option('extra-net-naming-schemes').split(',')
@@ -2751,6 +2754,7 @@ summary({
         'nobody user name'                          : nobody_user,
         'nobody group name'                         : nobody_group,
         'fallback hostname'                         : get_option('fallback-hostname'),
+        'fallback default target'                   : fallback_default_target,
         'default compression method'                : compression,
         'default DNSSEC mode'                       : default_dnssec,
         'default DNS-over-TLS mode'                 : default_dns_over_tls,
index 8df682742d1b2c547dcc3a7d2833cfe535dbee9d..9c0cda4c38e2a41ed0ea1bbe93c8fdb5ba0c5588 100644 (file)
@@ -267,6 +267,8 @@ option('clock-valid-range-usec-max', type : 'integer', value : 473364000000000,
        description : 'maximum value in microseconds for the difference between RTC and epoch, exceeding which is considered an RTC error ["0" disables]')
 option('default-user-shell', type : 'string', value : '/bin/bash',
        description : 'default interactive shell')
+option('fallback-default-target', type : 'string', value : 'graphical.target',
+       description : 'unit to activate when default.target is not found on disk')
 
 option('system-alloc-uid-min', type : 'integer', value : 0,
        description : 'minimum system UID used when allocating')
index c476400a93eb85dc427cb1c8f09d2e82dfaa0bfc..3dd227f24439e2db10258749d6d2e617e3eba818 100644 (file)
@@ -2768,15 +2768,22 @@ static int do_queue_default_job(
         log_debug("Activating default unit: %s", unit);
 
         r = manager_load_startable_unit_or_warn(m, unit, NULL, &target);
-        if (r < 0 && in_initrd() && !arg_default_unit) {
-                /* Fall back to default.target, which we used to always use by default. Only do this if no
-                 * explicit configuration was given. */
-
-                log_info("Falling back to %s.", SPECIAL_DEFAULT_TARGET);
+        if (r == -ENOENT && !arg_default_unit) {
+                if (in_initrd())
+                        /* Fall back to default.target, which we used to always use by default.
+                         * Only do this if no explicit configuration was given. */
+                        unit = SPECIAL_DEFAULT_TARGET;
+                else
+                        /* The default.target symlink was not found on disk and the target was not
+                         * explicitly specified. Fall back to the target configured at build time
+                         * via -Ddefault-target=. */
+                        unit = FALLBACK_DEFAULT_TARGET;
 
-                r = manager_load_startable_unit_or_warn(m, SPECIAL_DEFAULT_TARGET, NULL, &target);
+                log_info("Falling back to %s.", unit);
+                r = manager_load_startable_unit_or_warn(m, unit, NULL, &target);
         }
         if (r < 0) {
+                /* We failed. Activate rescue mode. */
                 log_info("Falling back to %s.", SPECIAL_RESCUE_TARGET);
 
                 r = manager_load_startable_unit_or_warn(m, SPECIAL_RESCUE_TARGET, NULL, &target);
index 7f71f5fdc74e1d4f4afc5ad8727fe6e003056f1c..c218e015faea43997730258fb363a47f16778da6 100644 (file)
@@ -88,6 +88,9 @@ TEST(unit_name_is_valid) {
         test_unit_name_is_valid_one("foo.target.wants/plain.service", UNIT_NAME_ANY, false);
         test_unit_name_is_valid_one("foo.target.conf/foo.conf", UNIT_NAME_ANY, false);
         test_unit_name_is_valid_one("foo.target.requires/plain.socket", UNIT_NAME_ANY, false);
+
+        /* The build-time configured fallback for default.target must be a valid plain unit name. */
+        test_unit_name_is_valid_one(FALLBACK_DEFAULT_TARGET, UNIT_NAME_PLAIN, true);
 }
 
 static void test_unit_name_replace_instance_one(const char *pattern, const char *repl, const char *expected, int ret) {