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.
This commit is contained in:
Luca Boccassi
2026-01-06 10:34:02 +01:00
parent 5b3a86f6c3
commit 986fee6217
28 changed files with 8 additions and 1874 deletions
-1
View File
@@ -133,7 +133,6 @@ body:
- 'systemd-sysctl'
- 'systemd-sysext'
- 'systemd-sysusers'
- 'systemd-sysv-generator'
- 'systemd-timedate'
- 'systemd-timesync'
- 'systemd-tmpfiles'
@@ -74,7 +74,6 @@ body:
- 'systemd-sysctl'
- 'systemd-sysext'
- 'systemd-sysusers'
- 'systemd-sysv-generator'
- 'systemd-timedate'
- 'systemd-timesync'
- 'systemd-tmpfiles'
-1
View File
@@ -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
-2
View File
@@ -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=`
-8
View File
@@ -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
-27
View File
@@ -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
-8
View File
@@ -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)
-1
View File
@@ -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'],
-68
View File
@@ -1,68 +0,0 @@
<?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">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-sysv-generator" conditional="HAVE_SYSV_COMPAT">
<refentryinfo>
<title>systemd-sysv-generator</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-sysv-generator</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-sysv-generator</refname>
<refpurpose>Unit generator for SysV init scripts</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>/usr/lib/systemd/system-generators/systemd-sysv-generator</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><emphasis>Note: this component is deprecated and scheduled for removal. Please replace remaining
SysV init scripts with native unit files.</emphasis></para>
<para><filename>systemd-sysv-generator</filename> is a generator that creates wrapper .service units for
<ulink url="https://savannah.nongnu.org/projects/sysvinit">System V init</ulink> scripts in
<filename>/etc/init.d/*</filename> at boot and when configuration of the system manager is reloaded. This
allows <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> to
support them similarly to native units.</para>
<para><ulink
url="http://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/iniscrptact.html">LSB
headers</ulink> 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
<literal>$remote_fs</literal>, <literal>$network</literal>, <literal>$named</literal>,
<literal>$portmap</literal>, <literal>$time</literal> are supported and will be turned into dependencies
on specific native systemd targets. See
<citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
more details.</para>
<para>Note that compatibility is quite comprehensive but not 100%, for more details see <ulink
url="https://systemd.io/INCOMPATIBILITIES">Compatibility with SysV</ulink>.</para>
<para><command>systemd</command> does not support SysV scripts as part of early boot, so all wrapper
units are ordered after <filename>basic.target</filename>.</para>
<para><filename>systemd-sysv-generator</filename> implements
<citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.target</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
-1
View File
@@ -407,7 +407,6 @@ find $dir</programlisting>
<member><citerefentry><refentrytitle>systemd-hibernate-resume-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-rc-local-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-system-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-sysv-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-xdg-autostart-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
-6
View File
@@ -50,12 +50,6 @@
which configure resource control settings for the processes of the
service.</para>
<para>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 <literal>.service</literal>
suffix added); see
<citerefentry><refentrytitle>systemd-sysv-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
</para>
<para>The <citerefentry><refentrytitle>systemd-run</refentrytitle><manvolnum>1</manvolnum></citerefentry>
command allows creating <filename>.service</filename> and <filename>.scope</filename> units dynamically
and transiently from the command line.</para>
-10
View File
@@ -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,
+4 -4
View File
@@ -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',
@@ -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"
@@ -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.*")
-6
View File
@@ -232,12 +232,6 @@ const char* const systemd_features =
" -UTMP"
#endif
#if HAVE_SYSV_COMPAT
" +SYSVINIT"
#else
" -SYSVINIT"
#endif
#if HAVE_LIBARCHIVE
" +LIBARCHIVE"
#else
-1
View File
@@ -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',
)
-5
View File
@@ -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)
+1 -9
View File
@@ -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;
-167
View File
@@ -1,167 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdlib.h>
#include <unistd.h>
#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;
}
-10
View File
@@ -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);
@@ -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 <sysv script name>" >&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
-9
View File
@@ -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'),
},
]
-937
View File
@@ -1,937 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#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);
-10
View File
@@ -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',
-413
View File
@@ -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 <martin.pitt@ubuntu.com>
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))
-112
View File
@@ -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 <<EOF
[Service]
-6
View File
@@ -16,9 +16,3 @@ L /var/lock - - - - ../run/lock
{% if CREATE_LOG_DIRS %}
L$ /var/log/README - - - - ../..{{DOC_DIR}}/README.logs
{% endif %}
{% if HAVE_SYSV_COMPAT %}
# /run/lock/subsys is used for serializing SysV service execution, and
# hence without use on SysV-less systems.
d /run/lock/subsys 0755 root root -
{% endif %}