1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "sd-messages.h"
9 #include "alloc-util.h"
10 #include "dirent-util.h"
11 #include "exit-status.h"
14 #include "generator.h"
16 #include "hexdecoct.h"
17 #include "initrd-util.h"
20 #include "main-func.h"
22 #include "path-lookup.h"
23 #include "path-util.h"
26 #include "specifier.h"
27 #include "stat-util.h"
28 #include "string-util.h"
30 #include "unit-name.h"
32 /* 🚨 Note: this generator is deprecated! Please do not add new features! Instead, please port remaining SysV
33 * scripts over to native unit files! Thank you! 🚨 */
39 /* Standard SysV runlevels for start-up */
40 { "rc1.d", SPECIAL_RESCUE_TARGET
},
41 { "rc2.d", SPECIAL_MULTI_USER_TARGET
},
42 { "rc3.d", SPECIAL_MULTI_USER_TARGET
},
43 { "rc4.d", SPECIAL_MULTI_USER_TARGET
},
44 { "rc5.d", SPECIAL_GRAPHICAL_TARGET
},
46 /* We ignore the SysV runlevels for shutdown here, as SysV services get default dependencies anyway, and that
47 * means they are shut down anyway at system power off if running. */
50 static const char *arg_dest
= NULL
;
52 typedef struct SysvStub
{
56 int sysv_start_priority
;
67 static SysvStub
* free_sysvstub(SysvStub
*s
) {
78 strv_free(s
->wanted_by
);
81 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
83 static void free_sysvstub_hashmapp(Hashmap
**h
) {
84 hashmap_free_with_destructor(*h
, free_sysvstub
);
87 static int add_alias(const char *service
, const char *alias
) {
88 _cleanup_free_
char *link
= NULL
;
93 link
= path_join(arg_dest
, alias
);
97 if (symlink(service
, link
) < 0) {
107 static int generate_unit_file(SysvStub
*s
) {
108 _cleanup_free_
char *path_escaped
= NULL
, *unit
= NULL
;
109 _cleanup_fclose_
FILE *f
= NULL
;
117 path_escaped
= specifier_escape(s
->path
);
121 unit
= path_join(arg_dest
, s
->name
);
125 /* We might already have a symlink with the same name from a Provides:,
126 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
127 * so remove an existing link */
128 if (is_symlink(unit
) > 0) {
129 log_warning("Overwriting existing symlink %s with real service.", unit
);
133 f
= fopen(unit
, "wxe");
135 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
138 "# Automatically generated by systemd-sysv-generator\n\n"
140 "Documentation=man:systemd-sysv-generator(8)\n"
144 if (s
->description
) {
145 _cleanup_free_
char *t
= NULL
;
147 t
= specifier_escape(s
->description
);
151 fprintf(f
, "Description=%s\n", t
);
154 STRV_FOREACH(p
, s
->before
)
155 fprintf(f
, "Before=%s\n", *p
);
156 STRV_FOREACH(p
, s
->after
)
157 fprintf(f
, "After=%s\n", *p
);
158 STRV_FOREACH(p
, s
->wants
)
159 fprintf(f
, "Wants=%s\n", *p
);
169 "RemainAfterExit=%s\n",
170 yes_no(!s
->pid_file
));
173 _cleanup_free_
char *t
= NULL
;
175 t
= specifier_escape(s
->pid_file
);
179 fprintf(f
, "PIDFile=%s\n", t
);
182 /* Consider two special LSB exit codes a clean exit */
185 "SuccessExitStatus=%i %i\n",
190 "ExecStart=%s start\n"
191 "ExecStop=%s stop\n",
192 path_escaped
, path_escaped
);
195 fprintf(f
, "ExecReload=%s reload\n", path_escaped
);
197 r
= fflush_and_check(f
);
199 return log_error_errno(r
, "Failed to write unit %s: %m", unit
);
201 STRV_FOREACH(p
, s
->wanted_by
)
202 (void) generator_add_symlink(arg_dest
, *p
, "wants", s
->name
);
207 static bool usage_contains_reload(const char *line
) {
208 return (strcasestr(line
, "{reload|") ||
209 strcasestr(line
, "{reload}") ||
210 strcasestr(line
, "{reload\"") ||
211 strcasestr(line
, "|reload|") ||
212 strcasestr(line
, "|reload}") ||
213 strcasestr(line
, "|reload\""));
216 static char *sysv_translate_name(const char *name
) {
217 _cleanup_free_
char *c
= NULL
;
224 res
= endswith(c
, ".sh");
228 if (unit_name_mangle(c
, 0, &res
) < 0)
234 static int sysv_translate_facility(SysvStub
*s
, unsigned line
, const char *name
, char **ret
) {
236 /* We silently ignore the $ prefix here. According to the LSB
237 * spec it simply indicates whether something is a
238 * standardized name or a distribution-specific one. Since we
239 * just follow what already exists and do not introduce new
240 * uses or names we don't care who introduced a new name. */
242 static const char * const table
[] = {
243 /* LSB defined facilities */
245 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
246 "named", SPECIAL_NSS_LOOKUP_TARGET
,
247 "portmap", SPECIAL_RPCBIND_TARGET
,
248 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
250 "time", SPECIAL_TIME_SYNC_TARGET
,
253 _cleanup_free_
char *filename
= NULL
;
262 r
= path_extract_filename(s
->path
, &filename
);
264 return log_error_errno(r
, "Failed to extract file name from path '%s': %m", s
->path
);
266 n
= *name
== '$' ? name
+ 1 : name
;
268 for (size_t i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
269 if (!streq(table
[i
], n
))
277 m
= strdup(table
[i
+1]);
285 /* If we don't know this name, fallback heuristics to figure
286 * out whether something is a target or a service alias. */
288 /* Facilities starting with $ are most likely targets */
290 r
= unit_name_build(n
, NULL
, ".target", ret
);
292 return log_error_errno(r
, "[%s:%u] Could not build name for facility %s: %m", s
->path
, line
, name
);
297 /* Strip ".sh" suffix from file name for comparison */
298 e
= endswith(filename
, ".sh");
302 /* Names equaling the file name of the services are redundant */
303 if (streq_ptr(n
, filename
)) {
308 /* Everything else we assume to be normal service names */
309 m
= sysv_translate_name(n
);
317 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
325 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
327 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_UNQUOTE
|EXTRACT_RELAX
);
329 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
333 r
= sysv_translate_facility(s
, line
, word
, &m
);
334 if (r
<= 0) /* continue on error */
337 switch (unit_name_to_type(m
)) {
340 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
341 r
= add_alias(s
->name
, m
);
343 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
348 /* NB: SysV targets which are provided by a
349 * service are pulled in by the services, as
350 * an indication that the generic service is
351 * now available. This is strictly one-way.
352 * The targets do NOT pull in SysV services! */
354 r
= strv_extend(&s
->before
, m
);
358 r
= strv_extend(&s
->wants
, m
);
362 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
363 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
366 r
= strv_extend(&s
->wants
, SPECIAL_NETWORK_TARGET
);
373 case _UNIT_TYPE_INVALID
:
374 log_warning("Unit name '%s' is invalid", m
);
378 log_warning("Unknown unit type for unit '%s'", m
);
385 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
393 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
396 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_UNQUOTE
|EXTRACT_RELAX
);
398 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
402 r
= sysv_translate_facility(s
, line
, word
, &m
);
403 if (r
<= 0) /* continue on error */
406 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
408 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
409 /* the network-online target is special, as it needs to be actively pulled in */
410 r
= strv_extend(&s
->after
, m
);
414 r
= strv_extend(&s
->wants
, m
);
416 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
424 static int load_sysv(SysvStub
*s
) {
425 _cleanup_fclose_
FILE *f
= NULL
;
435 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
437 bool supports_reload
= false;
441 f
= fopen(s
->path
, "re");
446 return log_error_errno(errno
, "Failed to open %s: %m", s
->path
);
449 log_debug("Loading SysV script %s", s
->path
);
452 _cleanup_free_
char *l
= NULL
;
454 r
= read_stripped_line(f
, LONG_LINE_MAX
, &l
);
456 return log_error_errno(r
, "Failed to read configuration file '%s': %m", s
->path
);
463 /* Try to figure out whether this init script supports
464 * the reload operation. This heuristic looks for
465 * "Usage" lines which include the reload option. */
466 if (state
== USAGE_CONTINUATION
||
467 (state
== NORMAL
&& strcasestr(l
, "usage"))) {
468 if (usage_contains_reload(l
)) {
469 supports_reload
= true;
471 } else if (endswith(l
, "\\"))
472 state
= USAGE_CONTINUATION
;
480 if (state
== NORMAL
&& streq(l
, "### BEGIN INIT INFO")) {
486 if (IN_SET(state
, LSB_DESCRIPTION
, LSB
) && streq(l
, "### END INIT INFO")) {
492 t
+= strspn(t
, WHITESPACE
);
494 if (state
== NORMAL
) {
496 /* Try to parse Red Hat style description */
498 if (startswith_no_case(t
, "description:")) {
504 if (k
> 0 && t
[k
-1] == '\\') {
509 j
= empty_to_null(strstrip(t
+12));
511 r
= free_and_strdup(&chkconfig_description
, j
);
515 } else if (startswith_no_case(t
, "pidfile:")) {
521 if (!path_is_absolute(fn
)) {
522 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
526 r
= free_and_strdup(&s
->pid_file
, fn
);
531 } else if (state
== DESCRIPTION
) {
533 /* Try to parse Red Hat style description
540 if (k
> 0 && t
[k
-1] == '\\')
546 if (!isempty(j
) && !strextend_with_separator(&chkconfig_description
, " ", j
))
549 } else if (IN_SET(state
, LSB
, LSB_DESCRIPTION
)) {
551 if (startswith_no_case(t
, "Provides:")) {
554 r
= handle_provides(s
, line
, t
, t
+ 9);
558 } else if (startswith_no_case(t
, "Required-Start:") ||
559 startswith_no_case(t
, "Should-Start:") ||
560 startswith_no_case(t
, "X-Start-Before:") ||
561 startswith_no_case(t
, "X-Start-After:")) {
565 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
569 } else if (startswith_no_case(t
, "Description:")) {
572 state
= LSB_DESCRIPTION
;
574 j
= empty_to_null(strstrip(t
+12));
576 r
= free_and_strdup(&long_description
, j
);
580 } else if (startswith_no_case(t
, "Short-Description:")) {
585 j
= empty_to_null(strstrip(t
+18));
587 r
= free_and_strdup(&short_description
, j
);
591 } else if (state
== LSB_DESCRIPTION
) {
593 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
597 if (!isempty(j
) && !strextend_with_separator(&long_description
, " ", j
))
605 s
->reload
= supports_reload
;
607 /* We use the long description only if
608 * no short description is set. */
610 if (short_description
)
611 description
= short_description
;
612 else if (chkconfig_description
)
613 description
= chkconfig_description
;
614 else if (long_description
)
615 description
= long_description
;
622 d
= strjoin(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
633 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
642 if (s
->sysv_start_priority
< 0)
645 HASHMAP_FOREACH(other
, all_services
) {
652 if (other
->sysv_start_priority
< 0)
655 /* If both units have modern headers we don't care
656 * about the priorities */
657 if (s
->has_lsb
&& other
->has_lsb
)
660 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
661 r
= strv_extend(&s
->after
, other
->name
);
665 } else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
666 r
= strv_extend(&s
->before
, other
->name
);
672 /* FIXME: Maybe we should compare the name here lexicographically? */
678 static int acquire_search_path(const char *def
, const char *envvar
, char ***ret
) {
679 _cleanup_strv_free_
char **l
= NULL
;
688 r
= path_split_and_make_absolute(e
, &l
);
690 return log_error_errno(r
, "Failed to make $%s search path absolute: %m", envvar
);
693 if (strv_isempty(l
)) {
701 if (!path_strv_resolve_uniq(l
, NULL
))
709 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
710 _cleanup_strv_free_
char **sysvinit_path
= NULL
;
715 r
= acquire_search_path(SYSTEM_SYSVINIT_PATH
, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path
);
719 STRV_FOREACH(path
, sysvinit_path
) {
720 _cleanup_closedir_
DIR *d
= NULL
;
725 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", *path
);
729 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", *path
)) {
730 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
731 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
734 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
735 log_warning_errno(errno
, "stat() failed on %s/%s, ignoring: %m", *path
, de
->d_name
);
739 if (!(st
.st_mode
& S_IXUSR
))
742 if (!S_ISREG(st
.st_mode
))
745 name
= sysv_translate_name(de
->d_name
);
749 if (hashmap_contains(all_services
, name
))
752 r
= unit_file_exists(RUNTIME_SCOPE_SYSTEM
, lp
, name
);
753 if (r
< 0 && !IN_SET(r
, -ELOOP
, -ERFKILL
, -EADDRNOTAVAIL
)) {
754 log_debug_errno(r
, "Failed to detect whether %s exists, skipping: %m", name
);
757 log_debug("Native unit for %s already exists, skipping.", name
);
761 fpath
= path_join(*path
, de
->d_name
);
765 log_struct(LOG_WARNING
,
766 LOG_MESSAGE("SysV service '%s' lacks a native systemd unit file. "
767 "%s Automatically generating a unit file for compatibility. Please update package to include a native systemd unit file, in order to make it safe, robust and future-proof. "
768 "%s This compatibility logic is deprecated, expect removal soon. %s",
770 special_glyph(SPECIAL_GLYPH_RECYCLING
),
771 special_glyph(SPECIAL_GLYPH_WARNING_SIGN
), special_glyph(SPECIAL_GLYPH_WARNING_SIGN
)),
772 "MESSAGE_ID=" SD_MESSAGE_SYSV_GENERATOR_DEPRECATED_STR
,
773 "SYSVSCRIPT=%s", fpath
,
776 service
= new(SysvStub
, 1);
780 *service
= (SysvStub
) {
781 .sysv_start_priority
= -1,
782 .name
= TAKE_PTR(name
),
783 .path
= TAKE_PTR(fpath
),
786 r
= hashmap_put(all_services
, service
->name
, service
);
797 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
798 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
799 _cleanup_strv_free_
char **sysvrcnd_path
= NULL
;
805 r
= acquire_search_path(SYSTEM_SYSVRCND_PATH
, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path
);
809 STRV_FOREACH(p
, sysvrcnd_path
)
810 for (unsigned i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
811 _cleanup_closedir_
DIR *d
= NULL
;
812 _cleanup_free_
char *path
= NULL
;
814 path
= path_join(*p
, rcnd_table
[i
].path
);
823 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", path
);
828 FOREACH_DIRENT(de
, d
, log_warning_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", path
)) {
829 _cleanup_free_
char *name
= NULL
, *fpath
= NULL
;
832 if (de
->d_name
[0] != 'S')
835 if (strlen(de
->d_name
) < 4)
838 a
= undecchar(de
->d_name
[1]);
839 b
= undecchar(de
->d_name
[2]);
844 fpath
= path_join(*p
, de
->d_name
);
850 name
= sysv_translate_name(de
->d_name
+ 3);
856 service
= hashmap_get(all_services
, name
);
858 log_debug("Ignoring %s symlink in %s, not generating %s.", de
->d_name
, rcnd_table
[i
].path
, name
);
862 service
->sysv_start_priority
= MAX(a
*10 + b
, service
->sysv_start_priority
);
864 r
= set_ensure_put(&runlevel_services
[i
], NULL
, service
);
872 for (unsigned i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
873 SET_FOREACH(service
, runlevel_services
[i
]) {
874 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
879 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
889 for (unsigned i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
890 set_free(runlevel_services
[i
]);
895 static int run(const char *dest
, const char *dest_early
, const char *dest_late
) {
896 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
897 _cleanup_(lookup_paths_free
) LookupPaths lp
= {};
902 log_debug("Skipping generator, running in the initrd.");
906 assert_se(arg_dest
= dest_late
);
908 r
= lookup_paths_init_or_warn(&lp
, RUNTIME_SCOPE_SYSTEM
, LOOKUP_PATHS_EXCLUDE_GENERATED
, NULL
);
912 all_services
= hashmap_new(&string_hash_ops
);
916 r
= enumerate_sysv(&lp
, all_services
);
920 r
= set_dependencies_from_rcnd(&lp
, all_services
);
924 HASHMAP_FOREACH(service
, all_services
)
925 (void) load_sysv(service
);
927 HASHMAP_FOREACH(service
, all_services
) {
928 (void) fix_order(service
, all_services
);
929 (void) generate_unit_file(service
);
935 DEFINE_MAIN_GENERATOR_FUNCTION(run
);