1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2014 Thomas H.P. Andersen
6 Copyright 2010 Lennart Poettering
7 Copyright 2011 Michal Schmidt
14 #include "alloc-util.h"
15 #include "dirent-util.h"
16 #include "exit-status.h"
19 #include "generator.h"
21 #include "hexdecoct.h"
25 #include "path-lookup.h"
26 #include "path-util.h"
29 #include "specifier.h"
30 #include "stat-util.h"
31 #include "string-util.h"
33 #include "unit-name.h"
40 /* Standard SysV runlevels for start-up */
41 { "rc1.d", SPECIAL_RESCUE_TARGET
},
42 { "rc2.d", SPECIAL_MULTI_USER_TARGET
},
43 { "rc3.d", SPECIAL_MULTI_USER_TARGET
},
44 { "rc4.d", SPECIAL_MULTI_USER_TARGET
},
45 { "rc5.d", SPECIAL_GRAPHICAL_TARGET
},
47 /* We ignore the SysV runlevels for shutdown here, as SysV services get default dependencies anyway, and that
48 * means they are shut down anyway at system power off if running. */
51 static const char *arg_dest
= "/tmp";
53 typedef struct SysvStub
{
57 int sysv_start_priority
;
68 static void free_sysvstub(SysvStub
*s
) {
79 strv_free(s
->wanted_by
);
83 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
85 static void free_sysvstub_hashmapp(Hashmap
**h
) {
86 hashmap_free_with_destructor(*h
, free_sysvstub
);
89 static int add_alias(const char *service
, const char *alias
) {
96 link
= strjoina(arg_dest
, "/", alias
);
98 r
= symlink(service
, link
);
109 static int generate_unit_file(SysvStub
*s
) {
110 _cleanup_free_
char *path_escaped
= NULL
;
111 _cleanup_fclose_
FILE *f
= NULL
;
121 path_escaped
= specifier_escape(s
->path
);
125 unit
= strjoina(arg_dest
, "/", s
->name
);
127 /* We might already have a symlink with the same name from a Provides:,
128 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
129 * so remove an existing link */
130 if (is_symlink(unit
) > 0) {
131 log_warning("Overwriting existing symlink %s with real service.", unit
);
135 f
= fopen(unit
, "wxe");
137 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
140 "# Automatically generated by systemd-sysv-generator\n\n"
142 "Documentation=man:systemd-sysv-generator(8)\n"
146 if (s
->description
) {
147 _cleanup_free_
char *t
;
149 t
= specifier_escape(s
->description
);
153 fprintf(f
, "Description=%s\n", t
);
156 STRV_FOREACH(p
, s
->before
)
157 fprintf(f
, "Before=%s\n", *p
);
158 STRV_FOREACH(p
, s
->after
)
159 fprintf(f
, "After=%s\n", *p
);
160 STRV_FOREACH(p
, s
->wants
)
161 fprintf(f
, "Wants=%s\n", *p
);
171 "RemainAfterExit=%s\n",
172 yes_no(!s
->pid_file
));
175 _cleanup_free_
char *t
;
177 t
= specifier_escape(s
->pid_file
);
181 fprintf(f
, "PIDFile=%s\n", t
);
184 /* Consider two special LSB exit codes a clean exit */
187 "SuccessExitStatus=%i %i\n",
192 "ExecStart=%s start\n"
193 "ExecStop=%s stop\n",
194 path_escaped
, path_escaped
);
197 fprintf(f
, "ExecReload=%s reload\n", path_escaped
);
199 r
= fflush_and_check(f
);
201 return log_error_errno(r
, "Failed to write unit %s: %m", unit
);
203 STRV_FOREACH(p
, s
->wanted_by
)
204 (void) generator_add_symlink(arg_dest
, *p
, "wants", s
->name
);
209 static bool usage_contains_reload(const char *line
) {
210 return (strcasestr(line
, "{reload|") ||
211 strcasestr(line
, "{reload}") ||
212 strcasestr(line
, "{reload\"") ||
213 strcasestr(line
, "|reload|") ||
214 strcasestr(line
, "|reload}") ||
215 strcasestr(line
, "|reload\""));
218 static char *sysv_translate_name(const char *name
) {
219 _cleanup_free_
char *c
= NULL
;
226 res
= endswith(c
, ".sh");
230 if (unit_name_mangle(c
, 0, &res
) < 0)
236 static int sysv_translate_facility(SysvStub
*s
, unsigned line
, const char *name
, char **ret
) {
238 /* We silently ignore the $ prefix here. According to the LSB
239 * spec it simply indicates whether something is a
240 * standardized name or a distribution-specific one. Since we
241 * just follow what already exists and do not introduce new
242 * uses or names we don't care who introduced a new name. */
244 static const char * const table
[] = {
245 /* LSB defined facilities */
247 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
248 "named", SPECIAL_NSS_LOOKUP_TARGET
,
249 "portmap", SPECIAL_RPCBIND_TARGET
,
250 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
252 "time", SPECIAL_TIME_SYNC_TARGET
,
255 const char *filename
;
256 char *filename_no_sh
, *e
, *m
;
265 filename
= basename(s
->path
);
267 n
= *name
== '$' ? name
+ 1 : name
;
269 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
270 if (!streq(table
[i
], n
))
278 m
= strdup(table
[i
+1]);
286 /* If we don't know this name, fallback heuristics to figure
287 * out whether something is a target or a service alias. */
289 /* Facilities starting with $ are most likely targets */
291 r
= unit_name_build(n
, NULL
, ".target", ret
);
293 return log_error_errno(r
, "[%s:%u] Could not build name for facility %s: %m", s
->path
, line
, name
);
298 /* Strip ".sh" suffix from file name for comparison */
299 filename_no_sh
= strdupa(filename
);
300 e
= endswith(filename_no_sh
, ".sh");
303 filename
= filename_no_sh
;
306 /* Names equaling the file name of the services are redundant */
307 if (streq_ptr(n
, filename
)) {
312 /* Everything else we assume to be normal service names */
313 m
= sysv_translate_name(n
);
321 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
329 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
331 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
333 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
337 r
= sysv_translate_facility(s
, line
, word
, &m
);
338 if (r
<= 0) /* continue on error */
341 switch (unit_name_to_type(m
)) {
344 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
345 r
= add_alias(s
->name
, m
);
347 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
352 /* NB: SysV targets which are provided by a
353 * service are pulled in by the services, as
354 * an indication that the generic service is
355 * now available. This is strictly one-way.
356 * The targets do NOT pull in SysV services! */
358 r
= strv_extend(&s
->before
, m
);
362 r
= strv_extend(&s
->wants
, m
);
366 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
367 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
370 r
= strv_extend(&s
->wants
, SPECIAL_NETWORK_TARGET
);
377 case _UNIT_TYPE_INVALID
:
378 log_warning("Unit name '%s' is invalid", m
);
382 log_warning("Unknown unit type for unit '%s'", m
);
389 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
397 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
400 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
402 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
406 r
= sysv_translate_facility(s
, line
, word
, &m
);
407 if (r
<= 0) /* continue on error */
410 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
412 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
413 /* the network-online target is special, as it needs to be actively pulled in */
414 r
= strv_extend(&s
->after
, m
);
418 r
= strv_extend(&s
->wants
, m
);
420 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
428 static int load_sysv(SysvStub
*s
) {
429 _cleanup_fclose_
FILE *f
;
439 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
441 bool supports_reload
= false;
446 f
= fopen(s
->path
, "re");
451 return log_error_errno(errno
, "Failed to open %s: %m", s
->path
);
454 log_debug("Loading SysV script %s", s
->path
);
456 FOREACH_LINE(l
, f
, goto fail
) {
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(t
, "usage"))) {
468 if (usage_contains_reload(t
)) {
469 supports_reload
= true;
471 } else if (t
[strlen(t
)-1] == '\\')
472 state
= USAGE_CONTINUATION
;
480 if (state
== NORMAL
&& streq(t
, "### BEGIN INIT INFO")) {
486 if (IN_SET(state
, LSB_DESCRIPTION
, LSB
) && streq(t
, "### 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] == '\\')
549 if (chkconfig_description
)
550 d
= strjoin(chkconfig_description
, " ", j
);
556 free(chkconfig_description
);
557 chkconfig_description
= d
;
560 } else if (IN_SET(state
, LSB
, LSB_DESCRIPTION
)) {
562 if (startswith_no_case(t
, "Provides:")) {
565 r
= handle_provides(s
, line
, t
, t
+ 9);
569 } else if (startswith_no_case(t
, "Required-Start:") ||
570 startswith_no_case(t
, "Should-Start:") ||
571 startswith_no_case(t
, "X-Start-Before:") ||
572 startswith_no_case(t
, "X-Start-After:")) {
576 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
580 } else if (startswith_no_case(t
, "Description:")) {
583 state
= LSB_DESCRIPTION
;
585 j
= empty_to_null(strstrip(t
+12));
587 r
= free_and_strdup(&long_description
, j
);
591 } else if (startswith_no_case(t
, "Short-Description:")) {
596 j
= empty_to_null(strstrip(t
+18));
598 r
= free_and_strdup(&short_description
, j
);
602 } else if (state
== LSB_DESCRIPTION
) {
604 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
611 if (long_description
)
612 d
= strjoin(long_description
, " ", t
);
618 free(long_description
);
619 long_description
= d
;
628 s
->reload
= supports_reload
;
630 /* We use the long description only if
631 * no short description is set. */
633 if (short_description
)
634 description
= short_description
;
635 else if (chkconfig_description
)
636 description
= chkconfig_description
;
637 else if (long_description
)
638 description
= long_description
;
645 d
= strappend(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
656 return log_error_errno(errno
, "Failed to read configuration file '%s': %m", s
->path
);
659 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
669 if (s
->sysv_start_priority
< 0)
672 HASHMAP_FOREACH(other
, all_services
, j
) {
679 if (other
->sysv_start_priority
< 0)
682 /* If both units have modern headers we don't care
683 * about the priorities */
684 if (s
->has_lsb
&& other
->has_lsb
)
687 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
688 r
= strv_extend(&s
->after
, other
->name
);
692 } else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
693 r
= strv_extend(&s
->before
, other
->name
);
699 /* FIXME: Maybe we should compare the name here lexicographically? */
705 static int acquire_search_path(const char *def
, const char *envvar
, char ***ret
) {
706 _cleanup_strv_free_
char **l
= NULL
;
715 r
= path_split_and_make_absolute(e
, &l
);
717 return log_error_errno(r
, "Failed to make $%s search path absolute: %m", envvar
);
720 if (strv_isempty(l
)) {
723 l
= strv_new(def
, NULL
);
728 if (!path_strv_resolve_uniq(l
, NULL
))
736 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
737 _cleanup_strv_free_
char **sysvinit_path
= NULL
;
743 r
= acquire_search_path(SYSTEM_SYSVINIT_PATH
, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path
);
747 STRV_FOREACH(path
, sysvinit_path
) {
748 _cleanup_closedir_
DIR *d
= NULL
;
754 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", *path
);
758 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", *path
)) {
759 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
760 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
763 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
764 log_warning_errno(errno
, "stat() failed on %s/%s, ignoring: %m", *path
, de
->d_name
);
768 if (!(st
.st_mode
& S_IXUSR
))
771 if (!S_ISREG(st
.st_mode
))
774 name
= sysv_translate_name(de
->d_name
);
778 if (hashmap_contains(all_services
, name
))
781 r
= unit_file_exists(UNIT_FILE_SYSTEM
, lp
, name
);
782 if (r
< 0 && !IN_SET(r
, -ELOOP
, -ERFKILL
, -EADDRNOTAVAIL
)) {
783 log_debug_errno(r
, "Failed to detect whether %s exists, skipping: %m", name
);
786 log_debug("Native unit for %s already exists, skipping.", name
);
790 fpath
= strjoin(*path
, "/", de
->d_name
);
794 service
= new0(SysvStub
, 1);
798 service
->sysv_start_priority
= -1;
799 service
->name
= TAKE_PTR(name
);
800 service
->path
= TAKE_PTR(fpath
);
802 r
= hashmap_put(all_services
, service
->name
, service
);
813 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
814 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
815 _cleanup_strv_free_
char **sysvrcnd_path
= NULL
;
824 r
= acquire_search_path(SYSTEM_SYSVRCND_PATH
, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path
);
828 STRV_FOREACH(p
, sysvrcnd_path
) {
829 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
831 _cleanup_closedir_
DIR *d
= NULL
;
832 _cleanup_free_
char *path
= NULL
;
835 path
= strjoin(*p
, "/", rcnd_table
[i
].path
);
844 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", path
);
849 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", path
)) {
850 _cleanup_free_
char *name
= NULL
, *fpath
= NULL
;
853 if (de
->d_name
[0] != 'S')
856 if (strlen(de
->d_name
) < 4)
859 a
= undecchar(de
->d_name
[1]);
860 b
= undecchar(de
->d_name
[2]);
865 fpath
= strjoin(*p
, "/", de
->d_name
);
871 name
= sysv_translate_name(de
->d_name
+ 3);
877 service
= hashmap_get(all_services
, name
);
879 log_debug("Ignoring %s symlink in %s, not generating %s.", de
->d_name
, rcnd_table
[i
].path
, name
);
883 service
->sysv_start_priority
= MAX(a
*10 + b
, service
->sysv_start_priority
);
885 r
= set_ensure_allocated(&runlevel_services
[i
], NULL
);
891 r
= set_put(runlevel_services
[i
], service
);
900 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
901 SET_FOREACH(service
, runlevel_services
[i
], j
) {
902 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
907 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
917 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
918 set_free(runlevel_services
[i
]);
923 int main(int argc
, char *argv
[]) {
924 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
925 _cleanup_lookup_paths_free_ LookupPaths lp
= {};
930 if (argc
> 1 && argc
!= 4) {
931 log_error("This program takes three or no arguments.");
938 log_set_prohibit_ipc(true);
939 log_set_target(LOG_TARGET_AUTO
);
940 log_parse_environment();
945 r
= lookup_paths_init(&lp
, UNIT_FILE_SYSTEM
, LOOKUP_PATHS_EXCLUDE_GENERATED
, NULL
);
947 log_error_errno(r
, "Failed to find lookup paths: %m");
951 all_services
= hashmap_new(&string_hash_ops
);
957 r
= enumerate_sysv(&lp
, all_services
);
961 r
= set_dependencies_from_rcnd(&lp
, all_services
);
965 HASHMAP_FOREACH(service
, all_services
, j
)
966 (void) load_sysv(service
);
968 HASHMAP_FOREACH(service
, all_services
, j
) {
969 (void) fix_order(service
, all_services
);
970 (void) generate_unit_file(service
);
976 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;