From 986fee6217051ce1ffdb53de45fdf9d7bae611ce Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 17 Nov 2025 14:44:18 +0000 Subject: [PATCH] Drop support for sysvinit scripts As announced by a few releases now, finally drop support for sysvinit scripts. Keep rc-local generator for now, as it's really a distinct feature even though from the same era. --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 - .github/ISSUE_TEMPLATE/feature_request.yml | 1 - LICENSES/README.md | 1 - docs/DISTRO_PORTING.md | 2 - docs/ENVIRONMENT.md | 8 - docs/sysvinit/README.in | 27 - docs/sysvinit/meson.build | 8 - man/rules/meson.build | 1 - man/systemd-sysv-generator.xml | 68 -- man/systemd.generator.xml | 1 - man/systemd.service.xml | 6 - meson.build | 10 - meson_options.txt | 8 +- .../mkosi.conf.d/arch/mkosi.build.chroot | 2 + .../mkosi.conf.d/opensuse/mkosi.build.chroot | 1 + src/basic/build.c | 6 - src/systemctl/meson.build | 1 - src/systemctl/systemctl-enable.c | 5 - src/systemctl/systemctl-is-enabled.c | 10 +- src/systemctl/systemctl-sysv-compat.c | 167 ---- src/systemctl/systemctl-sysv-compat.h | 10 - src/systemctl/systemd-sysv-install.SKELETON | 51 - src/sysv-generator/meson.build | 9 - src/sysv-generator/sysv-generator.c | 937 ------------------ test/meson.build | 10 - test/sysv-generator-test.py | 413 -------- test/units/TEST-26-SYSTEMCTL.sh | 112 --- tmpfiles.d/legacy.conf.in | 6 - 28 files changed, 8 insertions(+), 1874 deletions(-) delete mode 100644 docs/sysvinit/README.in delete mode 100644 docs/sysvinit/meson.build delete mode 100644 man/systemd-sysv-generator.xml delete mode 100644 src/systemctl/systemctl-sysv-compat.c delete mode 100644 src/systemctl/systemctl-sysv-compat.h delete mode 100755 src/systemctl/systemd-sysv-install.SKELETON delete mode 100644 src/sysv-generator/meson.build delete mode 100644 src/sysv-generator/sysv-generator.c delete mode 100755 test/sysv-generator-test.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 416cb76f401..1997e3bc7f1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -133,7 +133,6 @@ body: - 'systemd-sysctl' - 'systemd-sysext' - 'systemd-sysusers' - - 'systemd-sysv-generator' - 'systemd-timedate' - 'systemd-timesync' - 'systemd-tmpfiles' diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index fc66c1ab43f..f6f8ac0f752 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -74,7 +74,6 @@ body: - 'systemd-sysctl' - 'systemd-sysext' - 'systemd-sysusers' - - 'systemd-sysv-generator' - 'systemd-timedate' - 'systemd-timesync' - 'systemd-tmpfiles' diff --git a/LICENSES/README.md b/LICENSES/README.md index f25e82119f1..5522a08e109 100644 --- a/LICENSES/README.md +++ b/LICENSES/README.md @@ -56,7 +56,6 @@ The following exceptions apply: - src/basic/siphash24.h * the following sources are licensed under the **MIT-0** license: - all examples under man/ - - src/systemctl/systemd-sysv-install.SKELETON - config files and examples under /network * the following sources are under **Public Domain** (LicenseRef-murmurhash2-public-domain): - src/basic/MurmurHash2.c diff --git a/docs/DISTRO_PORTING.md b/docs/DISTRO_PORTING.md index aeccf0df7c7..fc25d91d70c 100644 --- a/docs/DISTRO_PORTING.md +++ b/docs/DISTRO_PORTING.md @@ -13,8 +13,6 @@ You need to make the follow changes to adapt systemd to your distribution: 1. Find the right configure parameters for: - * `-Dsysvinit-path=` - * `-Dsysvrcnd-path=` * `-Drc-local=` * `-Dloadkeys-path=` * `-Dsetfont-path=` diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index b64d8b0f20b..5897ede77c9 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -449,14 +449,6 @@ All tools: as a child process by another tool, such as package managers running it in a postinstall script. -`systemd-sysv-generator`: - -* `$SYSTEMD_SYSVINIT_PATH` — Controls where `systemd-sysv-generator` looks for - SysV init scripts. - -* `$SYSTEMD_SYSVRCND_PATH` — Controls where `systemd-sysv-generator` looks for - SysV init script runlevel link farms. - systemd tests: * `$SYSTEMD_TEST_DATA` — override the location of test data. This is useful if diff --git a/docs/sysvinit/README.in b/docs/sysvinit/README.in deleted file mode 100644 index ace1abaecfb..00000000000 --- a/docs/sysvinit/README.in +++ /dev/null @@ -1,27 +0,0 @@ -You are looking for the traditional init scripts in {{ SYSTEM_SYSVINIT_PATH }}, -and they are gone? - -Here's an explanation on what's going on: - -You are running a systemd-based OS where traditional init scripts have -been replaced by native systemd services files. Service files provide -very similar functionality to init scripts. To make use of service -files simply invoke "systemctl", which will output a list of all -currently running services (and other units). Use "systemctl -list-unit-files" to get a listing of all known unit files, including -stopped, disabled and masked ones. Use "systemctl start -foobar.service" and "systemctl stop foobar.service" to start or stop a -service, respectively. For further details, please refer to -systemctl(1). - -Note that traditional init scripts continue to function on a systemd -system. An init script {{ SYSTEM_SYSVINIT_PATH }}/foobar is implicitly mapped -into a service unit foobar.service during system initialization. - -Thank you! - -Further reading: - man:systemctl(1) - man:systemd(1) - https://0pointer.de/blog/projects/systemd-for-admins-3.html - https://systemd.io/INCOMPATIBILITIES diff --git a/docs/sysvinit/meson.build b/docs/sysvinit/meson.build deleted file mode 100644 index 9dd9ac8ef3c..00000000000 --- a/docs/sysvinit/meson.build +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -custom_target( - input : 'README.in', - output : 'README', - command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], - install : conf.get('HAVE_SYSV_COMPAT') == 1, - install_dir : sysvinit_path) diff --git a/man/rules/meson.build b/man/rules/meson.build index 6bc89398c83..4868a227090 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1190,7 +1190,6 @@ manpages = [ ['systemd-sysupdated'], 'ENABLE_SYSUPDATED'], ['systemd-sysusers', '8', ['systemd-sysusers.service'], ''], - ['systemd-sysv-generator', '8', [], 'HAVE_SYSV_COMPAT'], ['systemd-time-wait-sync.service', '8', ['systemd-time-wait-sync'], diff --git a/man/systemd-sysv-generator.xml b/man/systemd-sysv-generator.xml deleted file mode 100644 index f5a9889e587..00000000000 --- a/man/systemd-sysv-generator.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - systemd-sysv-generator - systemd - - - - systemd-sysv-generator - 8 - - - - systemd-sysv-generator - Unit generator for SysV init scripts - - - - /usr/lib/systemd/system-generators/systemd-sysv-generator - - - - Description - - Note: this component is deprecated and scheduled for removal. Please replace remaining - SysV init scripts with native unit files. - - systemd-sysv-generator is a generator that creates wrapper .service units for - System V init scripts in - /etc/init.d/* at boot and when configuration of the system manager is reloaded. This - allows systemd1 to - support them similarly to native units. - - LSB - headers in SysV init scripts are interpreted, and the ordering specified in the header is turned - into dependencies between the generated unit and other units. The LSB facilities - $remote_fs, $network, $named, - $portmap, $time are supported and will be turned into dependencies - on specific native systemd targets. See - systemd.special7 for - more details. - - Note that compatibility is quite comprehensive but not 100%, for more details see Compatibility with SysV. - - systemd does not support SysV scripts as part of early boot, so all wrapper - units are ordered after basic.target. - - systemd-sysv-generator implements - systemd.generator7. - - - - See Also - - systemd1 - systemd.service5 - systemd.target5 - - - - diff --git a/man/systemd.generator.xml b/man/systemd.generator.xml index 589c057dd94..47bb970a550 100644 --- a/man/systemd.generator.xml +++ b/man/systemd.generator.xml @@ -407,7 +407,6 @@ find $dir systemd-hibernate-resume-generator8 systemd-rc-local-generator8 systemd-system-update-generator8 - systemd-sysv-generator8 systemd-xdg-autostart-generator8 systemd.unit5 systemctl1 diff --git a/man/systemd.service.xml b/man/systemd.service.xml index 2ddeeafcec8..603dc8f68d4 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -50,12 +50,6 @@ which configure resource control settings for the processes of the service. - If SysV init compat is enabled, systemd automatically creates service units that wrap SysV init - scripts (the service name is the same as the name of the script, with a .service - suffix added); see - systemd-sysv-generator8. - - The systemd-run1 command allows creating .service and .scope units dynamically and transiently from the command line. diff --git a/meson.build b/meson.build index de685fe9ce0..89f9f0fd695 100644 --- a/meson.build +++ b/meson.build @@ -90,10 +90,6 @@ conf.set10('HAVE_SPLIT_BIN', split_bin, have_standalone_binaries = get_option('standalone-binaries') -sysvinit_path = get_option('sysvinit-path') -sysvrcnd_path = get_option('sysvrcnd-path') -conf.set10('HAVE_SYSV_COMPAT', sysvinit_path != '' and sysvrcnd_path != '', - description : 'SysV init scripts and rcN.d links are supported') sysvrclocal_path = get_option('rc-local') conf.set10('HAVE_SYSV_RC_LOCAL', sysvrclocal_path != '') conf.set10('CREATE_LOG_DIRS', get_option('create-log-dirs')) @@ -303,8 +299,6 @@ conf.set_quoted('SYSTEM_GENERATOR_DIR', systemgeneratordir conf.set_quoted('SYSTEM_PRESET_DIR', systempresetdir) conf.set_quoted('SYSTEM_SHUTDOWN_PATH', systemshutdowndir) conf.set_quoted('SYSTEM_SLEEP_PATH', systemsleepdir) -conf.set_quoted('SYSTEM_SYSVINIT_PATH', sysvinit_path) -conf.set_quoted('SYSTEM_SYSVRCND_PATH', sysvrcnd_path) conf.set_quoted('SYSTEM_SYSVRCLOCAL_PATH', sysvrclocal_path) conf.set_quoted('SYSUSERS_DIR', sysusersdir) conf.set_quoted('TMPFILES_DIR', tmpfilesdir) @@ -2431,7 +2425,6 @@ subdir('src/system-update-generator') subdir('src/systemctl') subdir('src/sysupdate') subdir('src/sysusers') -subdir('src/sysv-generator') subdir('src/timedate') subdir('src/timesync') subdir('src/tmpfiles') @@ -2792,7 +2785,6 @@ subdir('test') ##################################################################### -subdir('docs/sysvinit') subdir('docs/var-log') subdir('hwdb.d') subdir('man') @@ -3023,8 +3015,6 @@ summary({ 'sysconf directory' : sysconfdir, 'include directory' : includedir, 'lib directory' : libdir, - 'SysV init scripts' : sysvinit_path, - 'SysV rc?.d directories' : sysvrcnd_path, 'SysV rc.local script' : sysvrclocal_path, 'PAM modules directory' : pamlibdir, 'PAM configuration directory' : pamconfdir, diff --git a/meson_options.txt b/meson_options.txt index e43c0a21c4e..c555b9ebdc4 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -41,10 +41,10 @@ option('static-libudev', type : 'combo', option('standalone-binaries', type : 'boolean', value : false, description : 'also build standalone versions of supported binaries') -option('sysvinit-path', type : 'string', value : '/etc/init.d', - description : 'the directory where the SysV init scripts are located') -option('sysvrcnd-path', type : 'string', value : '/etc/rc.d', - description : 'the base directory for SysV rcN.d directories') +option('sysvinit-path', type : 'string', value : '/etc/init.d', deprecated : true, + description : 'This option is deprecated and will be removed in a future release') +option('sysvrcnd-path', type : 'string', value : '/etc/rc.d', deprecated : true, + description : 'This option is deprecated and will be removed in a future release') option('rc-local', type : 'string', value : '/etc/rc.local', description : 'path to SysV rc.local script') option('initrd', type : 'boolean', diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot b/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot index 910a7a0c479..76fdece83ba 100755 --- a/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot +++ b/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot @@ -59,6 +59,8 @@ EOF TS="${SOURCE_DATE_EPOCH:-$(date +%s)}" sed "pkg/$PKG_SUBDIR/PKGBUILD" \ + --expression "/-Dsysvinit-path=/d" \ + --expression "/-Dsysvrcnd-path=/d" \ --expression "s/^pkgver=.*/pkgver=$(cat meson.version)/" \ --expression "s/^pkgrel=.*/pkgrel=$(date "+%Y%m%d%H%M%S" --date "@$TS")/" >/tmp/PKGBUILD mount --bind /tmp/PKGBUILD "pkg/$PKG_SUBDIR/PKGBUILD" diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot b/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot index 2d34be043d3..efbc4670dcf 100755 --- a/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot +++ b/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot @@ -26,6 +26,7 @@ while read -r filelist; do -e 's/\.gz$//; /systemd-cgroups-agent/d; s/import-pubring.gpg/import-pubring.pgp/' \ -e '/(initctl|runlevel|telinit)/ d' \ -e 's/systemd-quotacheck.service.8/systemd-quotacheck@.service.8/' \ + -e '/systemd-sysv-generator/d' \ "$filelist" >"/tmp/$(basename "$filelist")" mount --bind "/tmp/$(basename "$filelist")" "$filelist" done < <(find "pkg/$PKG_SUBDIR${GIT_SUBDIR:+/$GIT_SUBDIR}" -name "files.*") diff --git a/src/basic/build.c b/src/basic/build.c index f5695eeb932..213ccca5f4e 100644 --- a/src/basic/build.c +++ b/src/basic/build.c @@ -232,12 +232,6 @@ const char* const systemd_features = " -UTMP" #endif -#if HAVE_SYSV_COMPAT - " +SYSVINIT" -#else - " -SYSVINIT" -#endif - #if HAVE_LIBARCHIVE " +LIBARCHIVE" #else diff --git a/src/systemctl/meson.build b/src/systemctl/meson.build index 820214919f0..2ce11c8f48d 100644 --- a/src/systemctl/meson.build +++ b/src/systemctl/meson.build @@ -36,7 +36,6 @@ systemctl_extract_sources = files( 'systemctl-daemon-reload.c', 'systemctl-logind.c', 'systemctl-start-unit.c', - 'systemctl-sysv-compat.c', 'systemctl-util.c', 'systemctl.c', ) diff --git a/src/systemctl/systemctl-enable.c b/src/systemctl/systemctl-enable.c index 2903d76929e..904a3a8234d 100644 --- a/src/systemctl/systemctl-enable.c +++ b/src/systemctl/systemctl-enable.c @@ -19,7 +19,6 @@ #include "systemctl-daemon-reload.h" #include "systemctl-enable.h" #include "systemctl-start-unit.h" -#include "systemctl-sysv-compat.h" #include "systemctl-util.h" #include "unit-name.h" #include "verbs.h" @@ -97,10 +96,6 @@ int verb_enable(int argc, char *argv[], void *userdata) { if (r < 0) return r; - r = enable_sysv_units(verb, names); - if (r < 0) - return r; - /* If the operation was fully executed by the SysV compat, let's finish early */ if (strv_isempty(names)) { if (arg_no_reload || install_client_side() != INSTALL_CLIENT_SIDE_NO) diff --git a/src/systemctl/systemctl-is-enabled.c b/src/systemctl/systemctl-is-enabled.c index da028b1385c..77b8cac5f01 100644 --- a/src/systemctl/systemctl-is-enabled.c +++ b/src/systemctl/systemctl-is-enabled.c @@ -12,7 +12,6 @@ #include "strv.h" #include "systemctl.h" #include "systemctl-is-enabled.h" -#include "systemctl-sysv-compat.h" #include "systemctl-util.h" static int show_installation_targets_client_side(const char *name) { @@ -68,20 +67,13 @@ static int show_installation_targets(sd_bus *bus, const char *name) { int verb_is_enabled(int argc, char *argv[], void *userdata) { _cleanup_strv_free_ char **names = NULL; - bool not_found, enabled; + bool not_found = true, enabled = false; int r; r = mangle_names("to check", strv_skip(argv, 1), &names); if (r < 0) return r; - r = enable_sysv_units(argv[0], names); - if (r < 0) - return r; - - not_found = r == 0; /* Doesn't have SysV support or SYSV_UNIT_NOT_FOUND */ - enabled = r == SYSV_UNIT_ENABLED; - if (install_client_side() != INSTALL_CLIENT_SIDE_NO) STRV_FOREACH(name, names) { UnitFileState state; diff --git a/src/systemctl/systemctl-sysv-compat.c b/src/systemctl/systemctl-sysv-compat.c deleted file mode 100644 index adc73c310ec..00000000000 --- a/src/systemctl/systemctl-sysv-compat.c +++ /dev/null @@ -1,167 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include - -#include "env-util.h" -#include "install.h" -#include "log.h" -#include "path-lookup.h" -#include "path-util.h" -#include "pidref.h" -#include "process-util.h" -#include "string-util.h" -#include "strv.h" -#include "systemctl.h" -#include "systemctl-sysv-compat.h" - -int enable_sysv_units(const char *verb, char **args) { - int r = 0; - -#if HAVE_SYSV_COMPAT - _cleanup_(lookup_paths_done) LookupPaths paths = {}; - unsigned f = 0; - SysVUnitEnableState enable_state = SYSV_UNIT_NOT_FOUND; - - /* Processes all SysV units, and reshuffles the array so that afterwards only the native units remain */ - - if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) - return 0; - - if (getenv_bool("SYSTEMCTL_SKIP_SYSV") > 0) - return 0; - - if (!STR_IN_SET(verb, - "enable", - "disable", - "is-enabled")) - return 0; - - r = lookup_paths_init_or_warn(&paths, arg_runtime_scope, LOOKUP_PATHS_EXCLUDE_GENERATED, arg_root); - if (r < 0) - return r; - - r = 0; - while (args[f]) { - - const char *argv[] = { - LIBEXECDIR "/systemd-sysv-install", - NULL, /* --root= */ - NULL, /* verb */ - NULL, /* service */ - NULL, - }; - - _cleanup_free_ char *p = NULL, *q = NULL, *l = NULL, *v = NULL, *b = NULL; - bool found_native = false, found_sysv; - const char *name; - unsigned c = 1; - int j; - - name = args[f++]; - - if (!endswith(name, ".service")) - continue; - - if (path_is_absolute(name)) - continue; - - j = unit_file_exists(arg_runtime_scope, &paths, name); - if (j < 0 && !IN_SET(j, -ELOOP, -ERFKILL, -EADDRNOTAVAIL)) - return log_error_errno(j, "Failed to look up unit file state: %m"); - found_native = j != 0; - - /* If we have both a native unit and a SysV script, enable/disable them both (below); for - * is-enabled, prefer the native unit */ - if (found_native && streq(verb, "is-enabled")) - continue; - - p = path_join(arg_root, SYSTEM_SYSVINIT_PATH, name); - if (!p) - return log_oom(); - - p[strlen(p) - STRLEN(".service")] = 0; - found_sysv = access(p, F_OK) >= 0; - if (!found_sysv) - continue; - - if (!arg_quiet) { - if (found_native) - log_info("Synchronizing state of %s with SysV service script with %s.", name, argv[0]); - else - log_info("%s is not a native service, redirecting to systemd-sysv-install.", name); - } - - if (!isempty(arg_root)) { - q = strjoin("--root=", arg_root); - if (!q) - return log_oom(); - - argv[c++] = q; - } - - /* Let's copy the verb, since it's still pointing directly into the original argv[] array we - * got passed, but safe_fork() is likely going to rewrite that for the new child */ - v = strdup(verb); - if (!v) - return log_oom(); - - j = path_extract_filename(p, &b); - if (j < 0) - return log_error_errno(j, "Failed to extract file name from '%s': %m", p); - - argv[c++] = v; - argv[c++] = b; - argv[c] = NULL; - - l = strv_join((char**)argv, " "); - if (!l) - return log_oom(); - - if (!arg_quiet) - log_info("Executing: %s", l); - - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - j = pidref_safe_fork("(sysv-install)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pidref); - if (j < 0) - return j; - if (j == 0) { - /* Child */ - execv(argv[0], (char**) argv); - log_error_errno(errno, "Failed to execute %s: %m", argv[0]); - _exit(EXIT_FAILURE); - } - - j = pidref_wait_for_terminate_and_check("sysv-install", &pidref, WAIT_LOG_ABNORMAL); - if (j < 0) - return j; - if (streq(verb, "is-enabled")) { - if (j == EXIT_SUCCESS) { - if (!arg_quiet) - puts("enabled"); - enable_state = SYSV_UNIT_ENABLED; - } else { - if (!arg_quiet) - puts("disabled"); - if (enable_state != SYSV_UNIT_ENABLED) - enable_state = SYSV_UNIT_DISABLED; - } - - } else if (j != EXIT_SUCCESS) - return -EBADE; /* We don't warn here, under the assumption the script already showed an explanation */ - - if (found_native) - continue; - - /* Remove this entry, so that we don't try enabling it as native unit */ - assert(f > 0); - f--; - assert(args[f] == name); - strv_remove(args + f, name); - } - - if (streq(verb, "is-enabled")) - return enable_state; -#endif - return r; -} diff --git a/src/systemctl/systemctl-sysv-compat.h b/src/systemctl/systemctl-sysv-compat.h deleted file mode 100644 index 72e823ffe2a..00000000000 --- a/src/systemctl/systemctl-sysv-compat.h +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -typedef enum SysVUnitEnableState { - SYSV_UNIT_NOT_FOUND = 0, - SYSV_UNIT_DISABLED, - SYSV_UNIT_ENABLED, -} SysVUnitEnableState; - -int enable_sysv_units(const char *verb, char **args); diff --git a/src/systemctl/systemd-sysv-install.SKELETON b/src/systemctl/systemd-sysv-install.SKELETON deleted file mode 100755 index cb58d8243b9..00000000000 --- a/src/systemctl/systemd-sysv-install.SKELETON +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: MIT-0 -# -# This script is called by "systemctl enable/disable" when the given unit is a -# SysV init.d script. It needs to call the distribution's mechanism for -# enabling/disabling those, such as chkconfig, update-rc.d, or similar. This -# can optionally take a --root argument for enabling a SysV init script -# in a chroot or similar. -set -e - -usage() { - echo "Usage: $0 [--root=path] enable|disable|is-enabled " >&2 - exit 1 -} - -unset ROOT - -# parse options -eval set -- "$(getopt -o r: --long root: -- "$@")" -while true; do - case "$1" in - -r|--root) - ROOT="$2" - shift 2 ;; - --) shift ; break ;; - *) usage ;; - esac -done - -NAME="$2" -[ -n "$NAME" ] || usage - -case "$1" in - enable) - # call the command to enable SysV init script $NAME here - # (consider optional $ROOT) - echo "IMPLEMENT ME: enabling SysV init.d script $NAME" - ;; - disable) - # call the command to disable SysV init script $NAME here - # (consider optional $ROOT) - echo "IMPLEMENT ME: disabling SysV init.d script $NAME" - ;; - is-enabled) - # exit with 0 if $NAME is enabled, non-zero if it is disabled - # (consider optional $ROOT) - echo "IMPLEMENT ME: checking SysV init.d script $NAME" - ;; - *) - usage ;; -esac diff --git a/src/sysv-generator/meson.build b/src/sysv-generator/meson.build deleted file mode 100644 index 4e89439df6f..00000000000 --- a/src/sysv-generator/meson.build +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -executables += [ - generator_template + { - 'name' : 'systemd-sysv-generator', - 'conditions' : ['HAVE_SYSV_COMPAT'], - 'sources' : files('sysv-generator.c'), - }, -] diff --git a/src/sysv-generator/sysv-generator.c b/src/sysv-generator/sysv-generator.c deleted file mode 100644 index e11edef15ed..00000000000 --- a/src/sysv-generator/sysv-generator.c +++ /dev/null @@ -1,937 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include - -#include "sd-messages.h" - -#include "alloc-util.h" -#include "dirent-util.h" -#include "exit-status.h" -#include "extract-word.h" -#include "fd-util.h" -#include "fileio.h" -#include "generator.h" -#include "glyph-util.h" -#include "hashmap.h" -#include "hexdecoct.h" -#include "initrd-util.h" -#include "install.h" -#include "log.h" -#include "path-lookup.h" -#include "path-util.h" -#include "set.h" -#include "special.h" -#include "specifier.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" - -/* 🚨 Note: this generator is deprecated! Please do not add new features! Instead, please port remaining SysV - * scripts over to native unit files! Thank you! 🚨 */ - -static const struct { - const char *path; - const char *target; -} rcnd_table[] = { - /* Standard SysV runlevels for start-up */ - { "rc1.d", SPECIAL_RESCUE_TARGET }, - { "rc2.d", SPECIAL_MULTI_USER_TARGET }, - { "rc3.d", SPECIAL_MULTI_USER_TARGET }, - { "rc4.d", SPECIAL_MULTI_USER_TARGET }, - { "rc5.d", SPECIAL_GRAPHICAL_TARGET }, - - /* We ignore the SysV runlevels for shutdown here, as SysV services get default dependencies anyway, and that - * means they are shut down anyway at system power off if running. */ -}; - -static const char *arg_dest = NULL; - -typedef struct SysvStub { - char *name; - char *path; - char *description; - int sysv_start_priority; - char *pid_file; - char **before; - char **after; - char **wants; - char **wanted_by; - bool has_lsb; - bool reload; - bool loaded; -} SysvStub; - -static SysvStub* sysvstub_free(SysvStub *s) { - if (!s) - return NULL; - - free(s->name); - free(s->path); - free(s->description); - free(s->pid_file); - strv_free(s->before); - strv_free(s->after); - strv_free(s->wants); - strv_free(s->wanted_by); - return mfree(s); -} -DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub*, sysvstub_free); - -DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( - sysvstub_hash_ops, - char, string_hash_func, string_compare_func, - SysvStub, sysvstub_free); - -static int add_alias(const char *service, const char *alias) { - _cleanup_free_ char *link = NULL; - - assert(service); - assert(alias); - - link = path_join(arg_dest, alias); - if (!link) - return -ENOMEM; - - if (symlink(service, link) < 0) { - if (errno == EEXIST) - return 0; - - return -errno; - } - - return 1; -} - -static int generate_unit_file(SysvStub *s) { - _cleanup_free_ char *path_escaped = NULL, *unit = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(s); - - if (!s->loaded) - return 0; - - path_escaped = specifier_escape(s->path); - if (!path_escaped) - return log_oom(); - - unit = path_join(arg_dest, s->name); - if (!unit) - return log_oom(); - - /* We might already have a symlink with the same name from a Provides:, - * or from backup files like /etc/init.d/foo.bak. Real scripts always win, - * so remove an existing link */ - if (is_symlink(unit) > 0) { - log_warning("Overwriting existing symlink %s with real service.", unit); - (void) unlink(unit); - } - - f = fopen(unit, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", unit); - - fprintf(f, - "# Automatically generated by systemd-sysv-generator\n\n" - "[Unit]\n" - "Documentation=man:systemd-sysv-generator(8)\n" - "SourcePath=%s\n", - path_escaped); - - if (s->description) { - _cleanup_free_ char *t = NULL; - - t = specifier_escape(s->description); - if (!t) - return log_oom(); - - fprintf(f, "Description=%s\n", t); - } - - STRV_FOREACH(p, s->before) - fprintf(f, "Before=%s\n", *p); - STRV_FOREACH(p, s->after) - fprintf(f, "After=%s\n", *p); - STRV_FOREACH(p, s->wants) - fprintf(f, "Wants=%s\n", *p); - - fprintf(f, - "\n[Service]\n" - "Type=forking\n" - "Restart=no\n" - "TimeoutSec=5min\n" - "IgnoreSIGPIPE=no\n" - "KillMode=process\n" - "GuessMainPID=no\n" - "RemainAfterExit=%s\n", - yes_no(!s->pid_file)); - - if (s->pid_file) { - _cleanup_free_ char *t = NULL; - - t = specifier_escape(s->pid_file); - if (!t) - return log_oom(); - - fprintf(f, "PIDFile=%s\n", t); - } - - /* Consider two special LSB exit codes a clean exit */ - if (s->has_lsb) - fprintf(f, - "SuccessExitStatus=%i %i\n", - EXIT_NOTINSTALLED, - EXIT_NOTCONFIGURED); - - fprintf(f, - "ExecStart=%s start\n" - "ExecStop=%s stop\n", - path_escaped, path_escaped); - - if (s->reload) - fprintf(f, "ExecReload=%s reload\n", path_escaped); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit %s: %m", unit); - - STRV_FOREACH(p, s->wanted_by) - (void) generator_add_symlink(arg_dest, *p, "wants", s->name); - - return 1; -} - -static bool usage_contains_reload(const char *line) { - return (strcasestr(line, "{reload|") || - strcasestr(line, "{reload}") || - strcasestr(line, "{reload\"") || - strcasestr(line, "|reload|") || - strcasestr(line, "|reload}") || - strcasestr(line, "|reload\"")); -} - -static char *sysv_translate_name(const char *name) { - _cleanup_free_ char *c = NULL; - char *res; - - c = strdup(name); - if (!c) - return NULL; - - res = endswith(c, ".sh"); - if (res) - *res = 0; - - if (unit_name_mangle(c, 0, &res) < 0) - return NULL; - - return res; -} - -static int sysv_translate_facility(SysvStub *s, unsigned line, const char *name, char **ret) { - - /* We silently ignore the $ prefix here. According to the LSB - * spec it simply indicates whether something is a - * standardized name or a distribution-specific one. Since we - * just follow what already exists and do not introduce new - * uses or names we don't care who introduced a new name. */ - - static const char * const table[] = { - /* LSB defined facilities */ - "local_fs", NULL, - "network", SPECIAL_NETWORK_ONLINE_TARGET, - "named", SPECIAL_NSS_LOOKUP_TARGET, - "portmap", SPECIAL_RPCBIND_TARGET, - "remote_fs", SPECIAL_REMOTE_FS_TARGET, - "syslog", NULL, - "time", SPECIAL_TIME_SYNC_TARGET, - }; - - _cleanup_free_ char *filename = NULL; - const char *n; - char *e, *m; - int r; - - assert(name); - assert(s); - assert(ret); - - r = path_extract_filename(s->path, &filename); - if (r < 0) - return log_error_errno(r, "Failed to extract file name from path '%s': %m", s->path); - - n = *name == '$' ? name + 1 : name; - - for (size_t i = 0; i < ELEMENTSOF(table); i += 2) { - if (!streq(table[i], n)) - continue; - - if (!table[i+1]) { - *ret = NULL; - return 0; - } - - m = strdup(table[i+1]); - if (!m) - return log_oom(); - - *ret = m; - return 1; - } - - /* If we don't know this name, fallback heuristics to figure - * out whether something is a target or a service alias. */ - - /* Facilities starting with $ are most likely targets */ - if (*name == '$') { - r = unit_name_build(n, NULL, ".target", ret); - if (r < 0) - return log_error_errno(r, "[%s:%u] Could not build name for facility %s: %m", s->path, line, name); - - return 1; - } - - /* Strip ".sh" suffix from file name for comparison */ - e = endswith(filename, ".sh"); - if (e) - *e = '\0'; - - /* Names equaling the file name of the services are redundant */ - if (streq_ptr(n, filename)) { - *ret = NULL; - return 0; - } - - /* Everything else we assume to be normal service names */ - m = sysv_translate_name(n); - if (!m) - return log_oom(); - - *ret = m; - return 1; -} - -static int handle_provides(SysvStub *s, unsigned line, const char *full_text, const char *text) { - int r; - - assert(s); - assert(full_text); - assert(text); - - for (;;) { - _cleanup_free_ char *word = NULL, *m = NULL; - - r = extract_first_word(&text, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX); - if (r < 0) - return log_error_errno(r, "[%s:%u] Failed to parse word from provides string: %m", s->path, line); - if (r == 0) - break; - - r = sysv_translate_facility(s, line, word, &m); - if (r <= 0) /* continue on error */ - continue; - - switch (unit_name_to_type(m)) { - - case UNIT_SERVICE: - log_debug("Adding Provides: alias '%s' for '%s'", m, s->name); - r = add_alias(s->name, m); - if (r < 0) - log_warning_errno(r, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s->path, line, m); - break; - - case UNIT_TARGET: - - /* NB: SysV targets which are provided by a - * service are pulled in by the services, as - * an indication that the generic service is - * now available. This is strictly one-way. - * The targets do NOT pull in SysV services! */ - - r = strv_extend(&s->before, m); - if (r < 0) - return log_oom(); - - r = strv_extend(&s->wants, m); - if (r < 0) - return log_oom(); - - if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) { - r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET); - if (r < 0) - return log_oom(); - r = strv_extend(&s->wants, SPECIAL_NETWORK_TARGET); - if (r < 0) - return log_oom(); - } - - break; - - case _UNIT_TYPE_INVALID: - log_warning("Unit name '%s' is invalid", m); - break; - - default: - log_warning("Unknown unit type for unit '%s'", m); - } - } - - return 0; -} - -static int handle_dependencies(SysvStub *s, unsigned line, const char *full_text, const char *text) { - int r; - - assert(s); - assert(full_text); - assert(text); - - for (;;) { - _cleanup_free_ char *word = NULL, *m = NULL; - bool is_before; - - r = extract_first_word(&text, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX); - if (r < 0) - return log_error_errno(r, "[%s:%u] Failed to parse word from provides string: %m", s->path, line); - if (r == 0) - break; - - r = sysv_translate_facility(s, line, word, &m); - if (r <= 0) /* continue on error */ - continue; - - is_before = startswith_no_case(full_text, "X-Start-Before:"); - - if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) { - /* the network-online target is special, as it needs to be actively pulled in */ - r = strv_extend(&s->after, m); - if (r < 0) - return log_oom(); - - r = strv_extend(&s->wants, m); - } else - r = strv_extend(is_before ? &s->before : &s->after, m); - if (r < 0) - return log_oom(); - } - - return 0; -} - -static int load_sysv(SysvStub *s) { - _cleanup_fclose_ FILE *f = NULL; - unsigned line = 0; - int r; - enum { - NORMAL, - DESCRIPTION, - LSB, - LSB_DESCRIPTION, - USAGE_CONTINUATION - } state = NORMAL; - _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL; - char *description; - bool supports_reload = false; - - assert(s); - - f = fopen(s->path, "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open %s: %m", s->path); - } - - log_debug("Loading SysV script %s", s->path); - - for (;;) { - _cleanup_free_ char *l = NULL; - - r = read_stripped_line(f, LONG_LINE_MAX, &l); - if (r < 0) - return log_error_errno(r, "Failed to read configuration file '%s': %m", s->path); - if (r == 0) - break; - - line++; - - if (l[0] != '#') { - /* Try to figure out whether this init script supports - * the reload operation. This heuristic looks for - * "Usage" lines which include the reload option. */ - if (state == USAGE_CONTINUATION || - (state == NORMAL && strcasestr(l, "usage"))) { - if (usage_contains_reload(l)) { - supports_reload = true; - state = NORMAL; - } else if (endswith(l, "\\")) - state = USAGE_CONTINUATION; - else - state = NORMAL; - } - - continue; - } - - if (state == NORMAL && streq(l, "### BEGIN INIT INFO")) { - state = LSB; - s->has_lsb = true; - continue; - } - - if (IN_SET(state, LSB_DESCRIPTION, LSB) && streq(l, "### END INIT INFO")) { - state = NORMAL; - continue; - } - - char *t = l + 1; - t += strspn(t, WHITESPACE); - - if (state == NORMAL) { - - /* Try to parse Red Hat style description */ - - if (startswith_no_case(t, "description:")) { - - size_t k; - const char *j; - - k = strlen(t); - if (k > 0 && t[k-1] == '\\') { - state = DESCRIPTION; - t[k-1] = 0; - } - - j = empty_to_null(strstrip(t+12)); - - r = free_and_strdup(&chkconfig_description, j); - if (r < 0) - return log_oom(); - - } else if (startswith_no_case(t, "pidfile:")) { - const char *fn; - - state = NORMAL; - - fn = strstrip(t+8); - if (!path_is_absolute(fn)) { - log_error("[%s:%u] PID file not absolute. Ignoring.", s->path, line); - continue; - } - - r = free_and_strdup(&s->pid_file, fn); - if (r < 0) - return log_oom(); - } - - } else if (state == DESCRIPTION) { - - /* Try to parse Red Hat style description - * continuation */ - - size_t k; - const char *j; - - k = strlen(t); - if (k > 0 && t[k-1] == '\\') - t[k-1] = 0; - else - state = NORMAL; - - j = strstrip(t); - if (!isempty(j) && !strextend_with_separator(&chkconfig_description, " ", j)) - return log_oom(); - - } else if (IN_SET(state, LSB, LSB_DESCRIPTION)) { - - if (startswith_no_case(t, "Provides:")) { - state = LSB; - - r = handle_provides(s, line, t, t + 9); - if (r < 0) - return r; - - } else if (startswith_no_case(t, "Required-Start:") || - startswith_no_case(t, "Should-Start:") || - startswith_no_case(t, "X-Start-Before:") || - startswith_no_case(t, "X-Start-After:")) { - - state = LSB; - - r = handle_dependencies(s, line, t, strchr(t, ':') + 1); - if (r < 0) - return r; - - } else if (startswith_no_case(t, "Description:")) { - const char *j; - - state = LSB_DESCRIPTION; - - j = empty_to_null(strstrip(t+12)); - - r = free_and_strdup(&long_description, j); - if (r < 0) - return log_oom(); - - } else if (startswith_no_case(t, "Short-Description:")) { - const char *j; - - state = LSB; - - j = empty_to_null(strstrip(t+18)); - - r = free_and_strdup(&short_description, j); - if (r < 0) - return log_oom(); - - } else if (state == LSB_DESCRIPTION) { - - if (startswith(l, "#\t") || startswith(l, "# ")) { - const char *j; - - j = strstrip(t); - if (!isempty(j) && !strextend_with_separator(&long_description, " ", j)) - return log_oom(); - } else - state = LSB; - } - } - } - - s->reload = supports_reload; - - /* We use the long description only if - * no short description is set. */ - - if (short_description) - description = short_description; - else if (chkconfig_description) - description = chkconfig_description; - else if (long_description) - description = long_description; - else - description = NULL; - - if (description) { - char *d; - - d = strjoin(s->has_lsb ? "LSB: " : "SYSV: ", description); - if (!d) - return log_oom(); - - s->description = d; - } - - s->loaded = true; - return 0; -} - -static int fix_order(SysvStub *s, Hashmap *all_services) { - SysvStub *other; - int r; - - assert(s); - - if (!s->loaded) - return 0; - - if (s->sysv_start_priority < 0) - return 0; - - HASHMAP_FOREACH(other, all_services) { - if (s == other) - continue; - - if (!other->loaded) - continue; - - if (other->sysv_start_priority < 0) - continue; - - /* If both units have modern headers we don't care - * about the priorities */ - if (s->has_lsb && other->has_lsb) - continue; - - if (other->sysv_start_priority < s->sysv_start_priority) { - r = strv_extend(&s->after, other->name); - if (r < 0) - return log_oom(); - - } else if (other->sysv_start_priority > s->sysv_start_priority) { - r = strv_extend(&s->before, other->name); - if (r < 0) - return log_oom(); - } else - continue; - - /* FIXME: Maybe we should compare the name here lexicographically? */ - } - - return 0; -} - -static int acquire_search_path(const char *def, const char *envvar, char ***ret) { - _cleanup_strv_free_ char **l = NULL; - const char *e; - int r; - - assert(def); - assert(envvar); - - e = getenv(envvar); - if (e) { - r = path_split_and_make_absolute(e, &l); - if (r < 0) - return log_error_errno(r, "Failed to make $%s search path absolute: %m", envvar); - } - - if (strv_isempty(l)) { - strv_free(l); - - l = strv_new(def); - if (!l) - return log_oom(); - } - - if (!path_strv_resolve_uniq(l, NULL)) - return log_oom(); - - *ret = TAKE_PTR(l); - - return 0; -} - -static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) { - _cleanup_strv_free_ char **sysvinit_path = NULL; - int r; - - assert(lp); - - r = acquire_search_path(SYSTEM_SYSVINIT_PATH, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path); - if (r < 0) - return r; - - STRV_FOREACH(path, sysvinit_path) { - _cleanup_closedir_ DIR *d = NULL; - - d = opendir(*path); - if (!d) { - if (errno != ENOENT) - log_warning_errno(errno, "Opening %s failed, ignoring: %m", *path); - continue; - } - - FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", *path)) { - _cleanup_free_ char *fpath = NULL, *name = NULL; - _cleanup_(sysvstub_freep) SysvStub *service = NULL; - struct stat st; - - if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) { - log_warning_errno(errno, "stat() failed on %s/%s, ignoring: %m", *path, de->d_name); - continue; - } - - if (!(st.st_mode & S_IXUSR)) - continue; - - if (!S_ISREG(st.st_mode)) - continue; - - name = sysv_translate_name(de->d_name); - if (!name) - return log_oom(); - - if (hashmap_contains(all_services, name)) - continue; - - r = unit_file_exists(RUNTIME_SCOPE_SYSTEM, lp, name); - if (r < 0 && !IN_SET(r, -ELOOP, -ERFKILL, -EADDRNOTAVAIL)) { - log_debug_errno(r, "Failed to detect whether %s exists, skipping: %m", name); - continue; - } else if (r != 0) { - log_debug("Native unit for %s already exists, skipping.", name); - continue; - } - - fpath = path_join(*path, de->d_name); - if (!fpath) - return log_oom(); - - log_struct(LOG_WARNING, - LOG_MESSAGE("SysV service '%s' lacks a native systemd unit file, " - "automatically generating a unit file for compatibility.\n" - "Please update package to include a native systemd unit file.\n" - "%s This compatibility logic is deprecated, expect removal soon. %s", - fpath, - glyph(GLYPH_WARNING_SIGN), - glyph(GLYPH_WARNING_SIGN)), - LOG_MESSAGE_ID(SD_MESSAGE_SYSV_GENERATOR_DEPRECATED_STR), - LOG_ITEM("SYSVSCRIPT=%s", fpath), - LOG_ITEM("UNIT=%s", name)); - - service = new(SysvStub, 1); - if (!service) - return log_oom(); - - *service = (SysvStub) { - .sysv_start_priority = -1, - .name = TAKE_PTR(name), - .path = TAKE_PTR(fpath), - }; - - r = hashmap_put(all_services, service->name, service); - if (r < 0) - return log_oom(); - - TAKE_PTR(service); - } - } - - return 0; -} - -static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_services) { - Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {}; - _cleanup_strv_free_ char **sysvrcnd_path = NULL; - SysvStub *service; - int r; - - assert(lp); - - r = acquire_search_path(SYSTEM_SYSVRCND_PATH, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path); - if (r < 0) - return r; - - STRV_FOREACH(p, sysvrcnd_path) - for (unsigned i = 0; i < ELEMENTSOF(rcnd_table); i++) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_free_ char *path = NULL; - - path = path_join(*p, rcnd_table[i].path); - if (!path) { - r = log_oom(); - goto finish; - } - - d = opendir(path); - if (!d) { - if (errno != ENOENT) - log_warning_errno(errno, "Opening %s failed, ignoring: %m", path); - - continue; - } - - FOREACH_DIRENT(de, d, log_warning_errno(errno, "Failed to enumerate directory %s, ignoring: %m", path)) { - _cleanup_free_ char *name = NULL, *fpath = NULL; - int a, b; - - if (de->d_name[0] != 'S') - continue; - - if (strlen(de->d_name) < 4) - continue; - - a = undecchar(de->d_name[1]); - b = undecchar(de->d_name[2]); - - if (a < 0 || b < 0) - continue; - - fpath = path_join(*p, de->d_name); - if (!fpath) { - r = log_oom(); - goto finish; - } - - name = sysv_translate_name(de->d_name + 3); - if (!name) { - r = log_oom(); - goto finish; - } - - service = hashmap_get(all_services, name); - if (!service) { - log_debug("Ignoring %s symlink in %s, not generating %s.", de->d_name, rcnd_table[i].path, name); - continue; - } - - service->sysv_start_priority = MAX(a*10 + b, service->sysv_start_priority); - - r = set_ensure_put(&runlevel_services[i], NULL, service); - if (r < 0) { - log_oom(); - goto finish; - } - } - } - - for (unsigned i = 0; i < ELEMENTSOF(rcnd_table); i++) - SET_FOREACH(service, runlevel_services[i]) { - r = strv_extend(&service->before, rcnd_table[i].target); - if (r < 0) { - log_oom(); - goto finish; - } - r = strv_extend(&service->wanted_by, rcnd_table[i].target); - if (r < 0) { - log_oom(); - goto finish; - } - } - - r = 0; - -finish: - for (unsigned i = 0; i < ELEMENTSOF(rcnd_table); i++) - set_free(runlevel_services[i]); - - return r; -} - -static int run(const char *dest, const char *dest_early, const char *dest_late) { - _cleanup_hashmap_free_ Hashmap *all_services = NULL; - _cleanup_(lookup_paths_done) LookupPaths lp = {}; - SysvStub *service; - int r; - - if (in_initrd()) { - log_debug("Skipping generator, running in the initrd."); - return EXIT_SUCCESS; - } - - assert_se(arg_dest = dest_late); - - r = lookup_paths_init_or_warn(&lp, RUNTIME_SCOPE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, NULL); - if (r < 0) - return r; - - all_services = hashmap_new(&sysvstub_hash_ops); - if (!all_services) - return log_oom(); - - r = enumerate_sysv(&lp, all_services); - if (r < 0) - return r; - - r = set_dependencies_from_rcnd(&lp, all_services); - if (r < 0) - return r; - - HASHMAP_FOREACH(service, all_services) - (void) load_sysv(service); - - HASHMAP_FOREACH(service, all_services) { - (void) fix_order(service, all_services); - (void) generate_unit_file(service); - } - - return 0; -} - -DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/test/meson.build b/test/meson.build index 7e8c3296ab5..b1d5421f671 100644 --- a/test/meson.build +++ b/test/meson.build @@ -65,16 +65,6 @@ endif ############################################################ -if want_tests != 'false' and conf.get('HAVE_SYSV_COMPAT') == 1 - exe = executables_by_name.get('systemd-sysv-generator') - test('sysv-generator-test', - files('sysv-generator-test.py'), - depends : exe, - suite : 'sysv') -endif - -############################################################ - if want_tests != 'false' and conf.get('HAVE_BLKID') == 1 exe = executables_by_name.get('bootctl') test('test-bootctl-json', diff --git a/test/sysv-generator-test.py b/test/sysv-generator-test.py deleted file mode 100755 index 24fafbaaa49..00000000000 --- a/test/sysv-generator-test.py +++ /dev/null @@ -1,413 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: LGPL-2.1-or-later -# -# systemd-sysv-generator integration test -# -# © 2015 Canonical Ltd. -# Author: Martin Pitt - -import collections -import os -import shutil -import subprocess -import sys -import tempfile -import unittest - -from configparser import RawConfigParser -from glob import glob - -sysv_generator = './systemd-sysv-generator' - -class MultiDict(collections.OrderedDict): - def __setitem__(self, key, value): - if isinstance(value, list) and key in self: - self[key].extend(value) - else: - super(MultiDict, self).__setitem__(key, value) - -class SysvGeneratorTest(unittest.TestCase): - def setUp(self): - self.workdir = tempfile.mkdtemp(prefix='sysv-gen-test.') - self.init_d_dir = os.path.join(self.workdir, 'init.d') - os.mkdir(self.init_d_dir) - self.rcnd_dir = self.workdir - self.unit_dir = os.path.join(self.workdir, 'systemd') - os.mkdir(self.unit_dir) - self.out_dir = os.path.join(self.workdir, 'output') - os.mkdir(self.out_dir) - - def tearDown(self): - shutil.rmtree(self.workdir) - - # - # Helper methods - # - - def run_generator(self, expect_error=False): - '''Run sysv-generator. - - Fail if stderr contains any "Fail", unless expect_error is True. - Return (stderr, filename -> ConfigParser) pair with output to stderr and - parsed generated units. - ''' - env = os.environ.copy() - # We might debug log about errors that aren't actually fatal so let's bump the log level to info to - # prevent those logs from interfering with the test. - env['SYSTEMD_LOG_LEVEL'] = 'info' - env['SYSTEMD_LOG_TARGET'] = 'console' - env['SYSTEMD_SYSVINIT_PATH'] = self.init_d_dir - env['SYSTEMD_SYSVRCND_PATH'] = self.rcnd_dir - env['SYSTEMD_UNIT_PATH'] = self.unit_dir - gen = subprocess.Popen( - [sysv_generator, 'ignored', 'ignored', self.out_dir], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True, env=env) - (out, err) = gen.communicate() - if not expect_error: - self.assertFalse('Fail' in err, err) - self.assertEqual(gen.returncode, 0, err) - - results = {} - for service in glob(self.out_dir + '/*.service'): - if os.path.islink(service): - continue - try: - # for python3 we need here strict=False to parse multiple - # lines with the same key - cp = RawConfigParser(dict_type=MultiDict, strict=False) - except TypeError: - # RawConfigParser in python2 does not have the strict option - # but it allows multiple lines with the same key by default - cp = RawConfigParser(dict_type=MultiDict) - cp.optionxform = lambda o: o # don't lower-case option names - with open(service) as f: - cp.read_file(f) - results[os.path.basename(service)] = cp - - return (err, results) - - def add_sysv(self, fname, keys, enable=False, prio=1): - '''Create a SysV init script with the given keys in the LSB header - - There are sensible default values for all fields. - If enable is True, links will be created in the rcN.d dirs. In that - case, the priority can be given with "prio" (default to 1). - - Return path of generated script. - ''' - name_without_sh = fname.endswith('.sh') and fname[:-3] or fname - keys.setdefault('Provides', name_without_sh) - keys.setdefault('Required-Start', '$local_fs') - keys.setdefault('Required-Stop', keys['Required-Start']) - keys.setdefault('Default-Start', '2 3 4 5') - keys.setdefault('Default-Stop', '0 1 6') - keys.setdefault('Short-Description', 'test {} service'.format(name_without_sh)) - keys.setdefault('Description', 'long description for test {} service'.format(name_without_sh)) - script = os.path.join(self.init_d_dir, fname) - with open(script, 'w') as f: - f.write('#!/bin/init-d-interpreter\n### BEGIN INIT INFO\n') - for k, v in keys.items(): - if v is not None: - f.write('#{:>20} {}\n'.format(k + ':', v)) - f.write('### END INIT INFO\ncode --goes here\n') - os.chmod(script, 0o755) - - if enable: - def make_link(prefix, runlevel): - d = os.path.join(self.rcnd_dir, 'rc{}.d'.format(runlevel)) - if not os.path.isdir(d): - os.mkdir(d) - os.symlink('../init.d/' + fname, os.path.join(d, prefix + fname)) - - for rl in keys['Default-Start'].split(): - make_link('S%02i' % prio, rl) - for rl in keys['Default-Stop'].split(): - make_link('K%02i' % (99 - prio), rl) - - return script - - def assert_enabled(self, unit, targets): - '''assert that a unit is enabled in precisely the given targets''' - - all_targets = ['multi-user', 'graphical'] - - # should be enabled - for target in all_targets: - link = os.path.join(self.out_dir, '{}.target.wants'.format(target), unit) - if target in targets: - unit_file = os.readlink(link) - # os.path.exists() will fail on a dangling symlink - self.assertTrue(os.path.exists(link)) - self.assertEqual(os.path.basename(unit_file), unit) - else: - self.assertFalse(os.path.exists(link), - '{} unexpectedly exists'.format(link)) - - # - # test cases - # - - def test_nothing(self): - '''no input files''' - - results = self.run_generator()[1] - self.assertEqual(results, {}) - self.assertEqual(os.listdir(self.out_dir), []) - - def test_simple_disabled(self): - '''simple service without dependencies, disabled''' - - self.add_sysv('foo', {}, enable=False) - err, results = self.run_generator() - self.assertEqual(len(results), 1) - - # no enablement links or other stuff - self.assertEqual(os.listdir(self.out_dir), ['foo.service']) - - s = results['foo.service'] - self.assertEqual(s.sections(), ['Unit', 'Service']) - self.assertEqual(s.get('Unit', 'Description'), 'LSB: test foo service') - # $local_fs does not need translation, don't expect any dependency - # fields here - self.assertEqual(set(s.options('Unit')), - set(['Documentation', 'SourcePath', 'Description'])) - - self.assertEqual(s.get('Service', 'Type'), 'forking') - init_script = os.path.join(self.init_d_dir, 'foo') - self.assertEqual(s.get('Service', 'ExecStart'), - '{} start'.format(init_script)) - self.assertEqual(s.get('Service', 'ExecStop'), - '{} stop'.format(init_script)) - - self.assertNotIn('Overwriting', err) - - def test_simple_enabled_all(self): - '''simple service without dependencies, enabled in all runlevels''' - - self.add_sysv('foo', {}, enable=True) - err, results = self.run_generator() - self.assertEqual(list(results), ['foo.service']) - self.assert_enabled('foo.service', ['multi-user', 'graphical']) - self.assertNotIn('Overwriting', err) - - def test_simple_escaped(self): - '''simple service without dependencies, that requires escaping the name''' - - self.add_sysv('foo+', {}) - self.add_sysv('foo-admin', {}) - err, results = self.run_generator() - self.assertEqual(set(results), {'foo-admin.service', 'foo\\x2b.service'}) - self.assertNotIn('Overwriting', err) - - def test_simple_enabled_some(self): - '''simple service without dependencies, enabled in some runlevels''' - - self.add_sysv('foo', {'Default-Start': '2 4'}, enable=True) - err, results = self.run_generator() - self.assertEqual(list(results), ['foo.service']) - self.assert_enabled('foo.service', ['multi-user']) - - def test_lsb_macro_dep_single(self): - '''single LSB macro dependency: $network''' - - self.add_sysv('foo', {'Required-Start': '$network'}) - s = self.run_generator()[1]['foo.service'] - self.assertEqual(set(s.options('Unit')), - set(['Documentation', 'SourcePath', 'Description', 'After', 'Wants'])) - self.assertEqual(s.get('Unit', 'After'), 'network-online.target') - self.assertEqual(s.get('Unit', 'Wants'), 'network-online.target') - - def test_lsb_macro_dep_multi(self): - '''multiple LSB macro dependencies''' - - self.add_sysv('foo', {'Required-Start': '$named $portmap'}) - s = self.run_generator()[1]['foo.service'] - self.assertEqual(set(s.options('Unit')), - set(['Documentation', 'SourcePath', 'Description', 'After'])) - self.assertEqual(s.get('Unit', 'After').split(), ['nss-lookup.target', 'rpcbind.target']) - - def test_lsb_deps(self): - '''LSB header dependencies to other services''' - - # also give symlink priorities here; they should be ignored - self.add_sysv('foo', {'Required-Start': 'must1 must2', - 'Should-Start': 'may1 ne_may2'}, - enable=True, prio=40) - self.add_sysv('must1', {}, enable=True, prio=10) - self.add_sysv('must2', {}, enable=True, prio=15) - self.add_sysv('may1', {}, enable=True, prio=20) - # do not create ne_may2 - err, results = self.run_generator() - self.assertEqual(sorted(results), - ['foo.service', 'may1.service', 'must1.service', 'must2.service']) - - # foo should depend on all of them - self.assertEqual(sorted(results['foo.service'].get('Unit', 'After').split()), - ['may1.service', 'must1.service', 'must2.service', 'ne_may2.service']) - - # other services should not depend on each other - self.assertFalse(results['must1.service'].has_option('Unit', 'After')) - self.assertFalse(results['must2.service'].has_option('Unit', 'After')) - self.assertFalse(results['may1.service'].has_option('Unit', 'After')) - - def test_symlink_prio_deps(self): - '''script without LSB headers use rcN.d priority''' - - # create two init.d scripts without LSB header and enable them with - # startup priorities - for prio, name in [(10, 'provider'), (15, 'consumer')]: - with open(os.path.join(self.init_d_dir, name), 'w') as f: - f.write('#!/bin/init-d-interpreter\ncode --goes here\n') - os.fchmod(f.fileno(), 0o755) - - d = os.path.join(self.rcnd_dir, 'rc2.d') - if not os.path.isdir(d): - os.mkdir(d) - os.symlink('../init.d/' + name, os.path.join(d, 'S{:>2}{}'.format(prio, name))) - - err, results = self.run_generator() - self.assertEqual(sorted(results), ['consumer.service', 'provider.service']) - self.assertFalse(results['provider.service'].has_option('Unit', 'After')) - self.assertEqual(results['consumer.service'].get('Unit', 'After'), - 'provider.service') - - def test_multiple_provides(self): - '''multiple Provides: names''' - - self.add_sysv('foo', {'Provides': 'foo bar baz'}) - err, results = self.run_generator() - self.assertEqual(list(results), ['foo.service']) - self.assertEqual(set(results['foo.service'].options('Unit')), - set(['Documentation', 'SourcePath', 'Description'])) - # should create symlinks for the alternative names - for f in ['bar.service', 'baz.service']: - self.assertEqual(os.readlink(os.path.join(self.out_dir, f)), - 'foo.service') - self.assertNotIn('Overwriting', err) - - def test_provides_escaped(self): - '''a script that Provides: a name that requires escaping''' - - self.add_sysv('foo', {'Provides': 'foo foo+'}) - err, results = self.run_generator() - self.assertEqual(list(results), ['foo.service']) - self.assertEqual(os.readlink(os.path.join(self.out_dir, 'foo\\x2b.service')), - 'foo.service') - self.assertNotIn('Overwriting', err) - - def test_same_provides_in_multiple_scripts(self): - '''multiple init.d scripts provide the same name''' - - self.add_sysv('foo', {'Provides': 'foo common'}, enable=True, prio=1) - self.add_sysv('bar', {'Provides': 'bar common'}, enable=True, prio=2) - err, results = self.run_generator() - self.assertEqual(sorted(results), ['bar.service', 'foo.service']) - # should create symlink for the alternative name for either unit - self.assertIn(os.readlink(os.path.join(self.out_dir, 'common.service')), - ['foo.service', 'bar.service']) - - def test_provide_other_script(self): - '''init.d scripts provides the name of another init.d script''' - - self.add_sysv('foo', {'Provides': 'foo bar'}, enable=True) - self.add_sysv('bar', {'Provides': 'bar'}, enable=True) - err, results = self.run_generator() - self.assertEqual(sorted(results), ['bar.service', 'foo.service']) - # we do expect an overwrite here, bar.service should overwrite the - # alias link from foo.service - self.assertIn('Overwriting', err) - - def test_nonexecutable_script(self): - '''ignores non-executable init.d script''' - - os.chmod(self.add_sysv('foo', {}), 0o644) - err, results = self.run_generator() - self.assertEqual(results, {}) - - def test_sh_suffix(self): - '''init.d script with .sh suffix''' - - self.add_sysv('foo.sh', {}, enable=True) - err, results = self.run_generator() - s = results['foo.service'] - - self.assertEqual(s.sections(), ['Unit', 'Service']) - # should not have a .sh - self.assertEqual(s.get('Unit', 'Description'), 'LSB: test foo service') - - # calls correct script with .sh - init_script = os.path.join(self.init_d_dir, 'foo.sh') - self.assertEqual(s.get('Service', 'ExecStart'), - '{} start'.format(init_script)) - self.assertEqual(s.get('Service', 'ExecStop'), - '{} stop'.format(init_script)) - - self.assert_enabled('foo.service', ['multi-user', 'graphical']) - - def test_sh_suffix_with_provides(self): - '''init.d script with .sh suffix and Provides:''' - - self.add_sysv('foo.sh', {'Provides': 'foo bar'}) - err, results = self.run_generator() - # ensure we don't try to create a symlink to itself - self.assertNotIn('itself', err) - self.assertEqual(list(results), ['foo.service']) - self.assertEqual(results['foo.service'].get('Unit', 'Description'), - 'LSB: test foo service') - - # should create symlink for the alternative name - self.assertEqual(os.readlink(os.path.join(self.out_dir, 'bar.service')), - 'foo.service') - - def test_hidden_files(self): - '''init.d script with hidden file suffix''' - - script = self.add_sysv('foo', {}, enable=True) - # backup files (not enabled in rcN.d/) - shutil.copy(script, script + '.dpkg-new') - shutil.copy(script, script + '.dpkg-dist') - shutil.copy(script, script + '.swp') - shutil.copy(script, script + '.rpmsave') - - err, results = self.run_generator() - self.assertEqual(list(results), ['foo.service']) - - self.assert_enabled('foo.service', ['multi-user', 'graphical']) - - def test_backup_file(self): - '''init.d script with backup file''' - - script = self.add_sysv('foo', {}, enable=True) - # backup files (not enabled in rcN.d/) - shutil.copy(script, script + '.bak') - shutil.copy(script, script + '.old') - shutil.copy(script, script + '.tmp') - shutil.copy(script, script + '.new') - - err, results = self.run_generator() - print(err) - self.assertEqual(sorted(results), ['foo.service', 'foo.tmp.service']) - - # ensure we don't try to create a symlink to itself - self.assertNotIn('itself', err) - - self.assert_enabled('foo.service', ['multi-user', 'graphical']) - self.assert_enabled('foo.bak.service', []) - self.assert_enabled('foo.old.service', []) - - def test_existing_native_unit(self): - '''existing native unit''' - - with open(os.path.join(self.unit_dir, 'foo.service'), 'w') as f: - f.write('[Unit]\n') - - self.add_sysv('foo.sh', {'Provides': 'foo bar'}, enable=True) - err, results = self.run_generator() - self.assertEqual(list(results), []) - # no enablement or alias links, as native unit is disabled - self.assertEqual(os.listdir(self.out_dir), []) - - -if __name__ == '__main__': - unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh index 88e37fd4eb0..f18357075a5 100755 --- a/test/units/TEST-26-SYSTEMCTL.sh +++ b/test/units/TEST-26-SYSTEMCTL.sh @@ -449,118 +449,6 @@ systemctl unset-environment IMPORT_THIS IMPORT_THIS_TOO (! systemctl show-environment | grep "^IMPORT_THIS=") (! systemctl show-environment | grep "^IMPORT_THIS_TOO=") -# test for sysv-generator (issue #24990) -if [[ -x /usr/lib/systemd/system-generators/systemd-sysv-generator ]]; then - # This is configurable via -Dsysvinit-path=, but we can't get the value - # at runtime, so let's just support the two most common paths for now. - [[ -d /etc/rc.d/init.d ]] && SYSVINIT_PATH="/etc/rc.d/init.d" || SYSVINIT_PATH="/etc/init.d" - - # OpenSUSE leaves sysvinit-path enabled, which means systemd-sysv-generator is built - # but may not create the directory if there's no services that use it. - mkdir -p "$SYSVINIT_PATH" - - # invalid dependency - cat >"${SYSVINIT_PATH:?}/issue-24990" <<\EOF -#!/usr/bin/env bash - -### BEGIN INIT INFO -# Provides:test1 test2 -# Required-Start:test1 $remote_fs $network -# Required-Stop:test1 $remote_fs $network -# Description:Test -# Short-Description: Test -### END INIT INFO - -case "$1" in - start) - echo "Starting issue-24990.service" - sleep 1000 & - ;; - stop) - echo "Stopping issue-24990.service" - sleep 10 & - ;; - *) - echo "Usage: service test {start|stop|restart|status}" - ;; -esac -EOF - - chmod +x "$SYSVINIT_PATH/issue-24990" - systemctl daemon-reload - [[ -L /run/systemd/generator.late/test1.service ]] - [[ -L /run/systemd/generator.late/test2.service ]] - assert_eq "$(readlink -f /run/systemd/generator.late/test1.service)" "/run/systemd/generator.late/issue-24990.service" - assert_eq "$(readlink -f /run/systemd/generator.late/test2.service)" "/run/systemd/generator.late/issue-24990.service" - output=$(systemctl cat issue-24990) - assert_in "SourcePath=$SYSVINIT_PATH/issue-24990" "$output" - assert_in "Description=LSB: Test" "$output" - assert_in "After=test1.service" "$output" - assert_in "After=remote-fs.target" "$output" - assert_in "After=network-online.target" "$output" - assert_in "Wants=network-online.target" "$output" - assert_in "ExecStart=$SYSVINIT_PATH/issue-24990 start" "$output" - assert_in "ExecStop=$SYSVINIT_PATH/issue-24990 stop" "$output" - systemctl status issue-24990 || : - systemctl show issue-24990 - assert_not_in "issue-24990.service" "$(systemctl show --property=After --value)" - assert_not_in "issue-24990.service" "$(systemctl show --property=Before --value)" - - if ! systemctl is-active network-online.target; then - systemctl start network-online.target - fi - - systemctl restart issue-24990 - systemctl stop issue-24990 - - # valid dependency - cat >"$SYSVINIT_PATH/issue-24990" <<\EOF -#!/usr/bin/env bash - -### BEGIN INIT INFO -# Provides:test1 test2 -# Required-Start:$remote_fs -# Required-Stop:$remote_fs -# Description:Test -# Short-Description: Test -### END INIT INFO - -case "$1" in - start) - echo "Starting issue-24990.service" - sleep 1000 & - ;; - stop) - echo "Stopping issue-24990.service" - sleep 10 & - ;; - *) - echo "Usage: service test {start|stop|restart|status}" - ;; -esac -EOF - - chmod +x "$SYSVINIT_PATH/issue-24990" - systemctl daemon-reload - [[ -L /run/systemd/generator.late/test1.service ]] - [[ -L /run/systemd/generator.late/test2.service ]] - assert_eq "$(readlink -f /run/systemd/generator.late/test1.service)" "/run/systemd/generator.late/issue-24990.service" - assert_eq "$(readlink -f /run/systemd/generator.late/test2.service)" "/run/systemd/generator.late/issue-24990.service" - output=$(systemctl cat issue-24990) - assert_in "SourcePath=$SYSVINIT_PATH/issue-24990" "$output" - assert_in "Description=LSB: Test" "$output" - assert_in "After=remote-fs.target" "$output" - assert_in "ExecStart=$SYSVINIT_PATH/issue-24990 start" "$output" - assert_in "ExecStop=$SYSVINIT_PATH/issue-24990 stop" "$output" - systemctl status issue-24990 || : - systemctl show issue-24990 - assert_not_in "issue-24990.service" "$(systemctl show --property=After --value)" - assert_not_in "issue-24990.service" "$(systemctl show --property=Before --value)" - - systemctl restart issue-24990 - systemctl stop issue-24990 -fi - # %J in WantedBy= causes ABRT (#26467) cat >/run/systemd/system/test-WantedBy.service <