1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "alloc-util.h"
8 #include "dirent-util.h"
9 #include "exit-status.h"
12 #include "generator.h"
14 #include "hexdecoct.h"
17 #include "main-func.h"
19 #include "path-lookup.h"
20 #include "path-util.h"
23 #include "specifier.h"
24 #include "stat-util.h"
25 #include "string-util.h"
27 #include "unit-name.h"
34 /* Standard SysV runlevels for start-up */
35 { "rc1.d", SPECIAL_RESCUE_TARGET
},
36 { "rc2.d", SPECIAL_MULTI_USER_TARGET
},
37 { "rc3.d", SPECIAL_MULTI_USER_TARGET
},
38 { "rc4.d", SPECIAL_MULTI_USER_TARGET
},
39 { "rc5.d", SPECIAL_GRAPHICAL_TARGET
},
41 /* We ignore the SysV runlevels for shutdown here, as SysV services get default dependencies anyway, and that
42 * means they are shut down anyway at system power off if running. */
45 static const char *arg_dest
= NULL
;
47 typedef struct SysvStub
{
51 int sysv_start_priority
;
62 static SysvStub
* free_sysvstub(SysvStub
*s
) {
73 strv_free(s
->wanted_by
);
76 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
78 static void free_sysvstub_hashmapp(Hashmap
**h
) {
79 hashmap_free_with_destructor(*h
, free_sysvstub
);
82 static int add_alias(const char *service
, const char *alias
) {
89 link
= prefix_roota(arg_dest
, alias
);
91 r
= symlink(service
, link
);
102 static int generate_unit_file(SysvStub
*s
) {
103 _cleanup_free_
char *path_escaped
= NULL
;
104 _cleanup_fclose_
FILE *f
= NULL
;
114 path_escaped
= specifier_escape(s
->path
);
118 unit
= prefix_roota(arg_dest
, s
->name
);
120 /* We might already have a symlink with the same name from a Provides:,
121 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
122 * so remove an existing link */
123 if (is_symlink(unit
) > 0) {
124 log_warning("Overwriting existing symlink %s with real service.", unit
);
128 f
= fopen(unit
, "wxe");
130 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
133 "# Automatically generated by systemd-sysv-generator\n\n"
135 "Documentation=man:systemd-sysv-generator(8)\n"
139 if (s
->description
) {
140 _cleanup_free_
char *t
= NULL
;
142 t
= specifier_escape(s
->description
);
146 fprintf(f
, "Description=%s\n", t
);
149 STRV_FOREACH(p
, s
->before
)
150 fprintf(f
, "Before=%s\n", *p
);
151 STRV_FOREACH(p
, s
->after
)
152 fprintf(f
, "After=%s\n", *p
);
153 STRV_FOREACH(p
, s
->wants
)
154 fprintf(f
, "Wants=%s\n", *p
);
164 "RemainAfterExit=%s\n",
165 yes_no(!s
->pid_file
));
168 _cleanup_free_
char *t
= NULL
;
170 t
= specifier_escape(s
->pid_file
);
174 fprintf(f
, "PIDFile=%s\n", t
);
177 /* Consider two special LSB exit codes a clean exit */
180 "SuccessExitStatus=%i %i\n",
185 "ExecStart=%s start\n"
186 "ExecStop=%s stop\n",
187 path_escaped
, path_escaped
);
190 fprintf(f
, "ExecReload=%s reload\n", path_escaped
);
192 r
= fflush_and_check(f
);
194 return log_error_errno(r
, "Failed to write unit %s: %m", unit
);
196 STRV_FOREACH(p
, s
->wanted_by
)
197 (void) generator_add_symlink(arg_dest
, *p
, "wants", s
->name
);
202 static bool usage_contains_reload(const char *line
) {
203 return (strcasestr(line
, "{reload|") ||
204 strcasestr(line
, "{reload}") ||
205 strcasestr(line
, "{reload\"") ||
206 strcasestr(line
, "|reload|") ||
207 strcasestr(line
, "|reload}") ||
208 strcasestr(line
, "|reload\""));
211 static char *sysv_translate_name(const char *name
) {
212 _cleanup_free_
char *c
= NULL
;
219 res
= endswith(c
, ".sh");
223 if (unit_name_mangle(c
, 0, &res
) < 0)
229 static int sysv_translate_facility(SysvStub
*s
, unsigned line
, const char *name
, char **ret
) {
231 /* We silently ignore the $ prefix here. According to the LSB
232 * spec it simply indicates whether something is a
233 * standardized name or a distribution-specific one. Since we
234 * just follow what already exists and do not introduce new
235 * uses or names we don't care who introduced a new name. */
237 static const char * const table
[] = {
238 /* LSB defined facilities */
240 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
241 "named", SPECIAL_NSS_LOOKUP_TARGET
,
242 "portmap", SPECIAL_RPCBIND_TARGET
,
243 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
245 "time", SPECIAL_TIME_SYNC_TARGET
,
248 const char *filename
;
249 char *filename_no_sh
, *e
, *m
;
258 filename
= basename(s
->path
);
260 n
= *name
== '$' ? name
+ 1 : name
;
262 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
263 if (!streq(table
[i
], n
))
271 m
= strdup(table
[i
+1]);
279 /* If we don't know this name, fallback heuristics to figure
280 * out whether something is a target or a service alias. */
282 /* Facilities starting with $ are most likely targets */
284 r
= unit_name_build(n
, NULL
, ".target", ret
);
286 return log_error_errno(r
, "[%s:%u] Could not build name for facility %s: %m", s
->path
, line
, name
);
291 /* Strip ".sh" suffix from file name for comparison */
292 filename_no_sh
= strdupa_safe(filename
);
293 e
= endswith(filename_no_sh
, ".sh");
296 filename
= filename_no_sh
;
299 /* Names equaling the file name of the services are redundant */
300 if (streq_ptr(n
, filename
)) {
305 /* Everything else we assume to be normal service names */
306 m
= sysv_translate_name(n
);
314 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
322 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
324 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_UNQUOTE
|EXTRACT_RELAX
);
326 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
330 r
= sysv_translate_facility(s
, line
, word
, &m
);
331 if (r
<= 0) /* continue on error */
334 switch (unit_name_to_type(m
)) {
337 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
338 r
= add_alias(s
->name
, m
);
340 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
345 /* NB: SysV targets which are provided by a
346 * service are pulled in by the services, as
347 * an indication that the generic service is
348 * now available. This is strictly one-way.
349 * The targets do NOT pull in SysV services! */
351 r
= strv_extend(&s
->before
, m
);
355 r
= strv_extend(&s
->wants
, m
);
359 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
360 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
363 r
= strv_extend(&s
->wants
, SPECIAL_NETWORK_TARGET
);
370 case _UNIT_TYPE_INVALID
:
371 log_warning("Unit name '%s' is invalid", m
);
375 log_warning("Unknown unit type for unit '%s'", m
);
382 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
390 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
393 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_UNQUOTE
|EXTRACT_RELAX
);
395 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
399 r
= sysv_translate_facility(s
, line
, word
, &m
);
400 if (r
<= 0) /* continue on error */
403 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
405 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
406 /* the network-online target is special, as it needs to be actively pulled in */
407 r
= strv_extend(&s
->after
, m
);
411 r
= strv_extend(&s
->wants
, m
);
413 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
421 static int load_sysv(SysvStub
*s
) {
422 _cleanup_fclose_
FILE *f
= NULL
;
432 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
434 bool supports_reload
= false;
438 f
= fopen(s
->path
, "re");
443 return log_error_errno(errno
, "Failed to open %s: %m", s
->path
);
446 log_debug("Loading SysV script %s", s
->path
);
449 _cleanup_free_
char *l
= NULL
;
452 r
= read_line(f
, LONG_LINE_MAX
, &l
);
454 return log_error_errno(r
, "Failed to read configuration file '%s': %m", s
->path
);
462 /* Try to figure out whether this init script supports
463 * the reload operation. This heuristic looks for
464 * "Usage" lines which include the reload option. */
465 if (state
== USAGE_CONTINUATION
||
466 (state
== NORMAL
&& strcasestr(t
, "usage"))) {
467 if (usage_contains_reload(t
)) {
468 supports_reload
= true;
470 } else if (t
[strlen(t
)-1] == '\\')
471 state
= USAGE_CONTINUATION
;
479 if (state
== NORMAL
&& streq(t
, "### BEGIN INIT INFO")) {
485 if (IN_SET(state
, LSB_DESCRIPTION
, LSB
) && streq(t
, "### END INIT INFO")) {
491 t
+= strspn(t
, WHITESPACE
);
493 if (state
== NORMAL
) {
495 /* Try to parse Red Hat style description */
497 if (startswith_no_case(t
, "description:")) {
503 if (k
> 0 && t
[k
-1] == '\\') {
508 j
= empty_to_null(strstrip(t
+12));
510 r
= free_and_strdup(&chkconfig_description
, j
);
514 } else if (startswith_no_case(t
, "pidfile:")) {
520 if (!path_is_absolute(fn
)) {
521 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
525 r
= free_and_strdup(&s
->pid_file
, fn
);
530 } else if (state
== DESCRIPTION
) {
532 /* Try to parse Red Hat style description
539 if (k
> 0 && t
[k
-1] == '\\')
545 if (!isempty(j
) && !strextend_with_separator(&chkconfig_description
, " ", j
))
548 } else if (IN_SET(state
, LSB
, LSB_DESCRIPTION
)) {
550 if (startswith_no_case(t
, "Provides:")) {
553 r
= handle_provides(s
, line
, t
, t
+ 9);
557 } else if (startswith_no_case(t
, "Required-Start:") ||
558 startswith_no_case(t
, "Should-Start:") ||
559 startswith_no_case(t
, "X-Start-Before:") ||
560 startswith_no_case(t
, "X-Start-After:")) {
564 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
568 } else if (startswith_no_case(t
, "Description:")) {
571 state
= LSB_DESCRIPTION
;
573 j
= empty_to_null(strstrip(t
+12));
575 r
= free_and_strdup(&long_description
, j
);
579 } else if (startswith_no_case(t
, "Short-Description:")) {
584 j
= empty_to_null(strstrip(t
+18));
586 r
= free_and_strdup(&short_description
, j
);
590 } else if (state
== LSB_DESCRIPTION
) {
592 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
596 if (!isempty(j
) && !strextend_with_separator(&long_description
, " ", j
))
604 s
->reload
= supports_reload
;
606 /* We use the long description only if
607 * no short description is set. */
609 if (short_description
)
610 description
= short_description
;
611 else if (chkconfig_description
)
612 description
= chkconfig_description
;
613 else if (long_description
)
614 description
= long_description
;
621 d
= strjoin(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
632 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
641 if (s
->sysv_start_priority
< 0)
644 HASHMAP_FOREACH(other
, all_services
) {
651 if (other
->sysv_start_priority
< 0)
654 /* If both units have modern headers we don't care
655 * about the priorities */
656 if (s
->has_lsb
&& other
->has_lsb
)
659 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
660 r
= strv_extend(&s
->after
, other
->name
);
664 } else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
665 r
= strv_extend(&s
->before
, other
->name
);
671 /* FIXME: Maybe we should compare the name here lexicographically? */
677 static int acquire_search_path(const char *def
, const char *envvar
, char ***ret
) {
678 _cleanup_strv_free_
char **l
= NULL
;
687 r
= path_split_and_make_absolute(e
, &l
);
689 return log_error_errno(r
, "Failed to make $%s search path absolute: %m", envvar
);
692 if (strv_isempty(l
)) {
700 if (!path_strv_resolve_uniq(l
, NULL
))
708 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
709 _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
;
726 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", *path
);
730 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", *path
)) {
731 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
732 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
735 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
736 log_warning_errno(errno
, "stat() failed on %s/%s, ignoring: %m", *path
, de
->d_name
);
740 if (!(st
.st_mode
& S_IXUSR
))
743 if (!S_ISREG(st
.st_mode
))
746 name
= sysv_translate_name(de
->d_name
);
750 if (hashmap_contains(all_services
, name
))
753 r
= unit_file_exists(UNIT_FILE_SYSTEM
, lp
, name
);
754 if (r
< 0 && !IN_SET(r
, -ELOOP
, -ERFKILL
, -EADDRNOTAVAIL
)) {
755 log_debug_errno(r
, "Failed to detect whether %s exists, skipping: %m", name
);
758 log_debug("Native unit for %s already exists, skipping.", name
);
762 fpath
= path_join(*path
, de
->d_name
);
766 log_warning("SysV service '%s' lacks a native systemd unit file. "
767 "Automatically generating a unit file for compatibility. "
768 "Please update package to include a native systemd unit file, in order to make it more safe and robust.", fpath
);
770 service
= new(SysvStub
, 1);
774 *service
= (SysvStub
) {
775 .sysv_start_priority
= -1,
776 .name
= TAKE_PTR(name
),
777 .path
= TAKE_PTR(fpath
),
780 r
= hashmap_put(all_services
, service
->name
, service
);
791 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
792 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
793 _cleanup_strv_free_
char **sysvrcnd_path
= NULL
;
800 r
= acquire_search_path(SYSTEM_SYSVRCND_PATH
, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path
);
804 STRV_FOREACH(p
, sysvrcnd_path
)
805 for (unsigned i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
806 _cleanup_closedir_
DIR *d
= NULL
;
807 _cleanup_free_
char *path
= NULL
;
810 path
= path_join(*p
, rcnd_table
[i
].path
);
819 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", path
);
824 FOREACH_DIRENT(de
, d
, log_warning_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", path
)) {
825 _cleanup_free_
char *name
= NULL
, *fpath
= NULL
;
828 if (de
->d_name
[0] != 'S')
831 if (strlen(de
->d_name
) < 4)
834 a
= undecchar(de
->d_name
[1]);
835 b
= undecchar(de
->d_name
[2]);
840 fpath
= path_join(*p
, de
->d_name
);
846 name
= sysv_translate_name(de
->d_name
+ 3);
852 service
= hashmap_get(all_services
, name
);
854 log_debug("Ignoring %s symlink in %s, not generating %s.", de
->d_name
, rcnd_table
[i
].path
, name
);
858 service
->sysv_start_priority
= MAX(a
*10 + b
, service
->sysv_start_priority
);
860 r
= set_ensure_put(&runlevel_services
[i
], NULL
, service
);
868 for (unsigned i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
869 SET_FOREACH(service
, runlevel_services
[i
]) {
870 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
875 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
885 for (unsigned i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
886 set_free(runlevel_services
[i
]);
891 static int run(const char *dest
, const char *dest_early
, const char *dest_late
) {
892 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
893 _cleanup_(lookup_paths_free
) LookupPaths lp
= {};
897 assert_se(arg_dest
= dest_late
);
899 r
= lookup_paths_init(&lp
, UNIT_FILE_SYSTEM
, LOOKUP_PATHS_EXCLUDE_GENERATED
, NULL
);
901 return log_error_errno(r
, "Failed to find lookup paths: %m");
903 all_services
= hashmap_new(&string_hash_ops
);
907 r
= enumerate_sysv(&lp
, all_services
);
911 r
= set_dependencies_from_rcnd(&lp
, all_services
);
915 HASHMAP_FOREACH(service
, all_services
)
916 (void) load_sysv(service
);
918 HASHMAP_FOREACH(service
, all_services
) {
919 (void) fix_order(service
, all_services
);
920 (void) generate_unit_file(service
);
926 DEFINE_MAIN_GENERATOR_FUNCTION(run
);