1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 Copyright © 2014 Thomas H.P. Andersen
4 Copyright © 2011 Michal Schmidt
11 #include "alloc-util.h"
12 #include "dirent-util.h"
13 #include "exit-status.h"
16 #include "generator.h"
18 #include "hexdecoct.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"
37 /* Standard SysV runlevels for start-up */
38 { "rc1.d", SPECIAL_RESCUE_TARGET
},
39 { "rc2.d", SPECIAL_MULTI_USER_TARGET
},
40 { "rc3.d", SPECIAL_MULTI_USER_TARGET
},
41 { "rc4.d", SPECIAL_MULTI_USER_TARGET
},
42 { "rc5.d", SPECIAL_GRAPHICAL_TARGET
},
44 /* We ignore the SysV runlevels for shutdown here, as SysV services get default dependencies anyway, and that
45 * means they are shut down anyway at system power off if running. */
48 static const char *arg_dest
= "/tmp";
50 typedef struct SysvStub
{
54 int sysv_start_priority
;
65 static void free_sysvstub(SysvStub
*s
) {
76 strv_free(s
->wanted_by
);
80 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
82 static void free_sysvstub_hashmapp(Hashmap
**h
) {
83 hashmap_free_with_destructor(*h
, free_sysvstub
);
86 static int add_alias(const char *service
, const char *alias
) {
93 link
= strjoina(arg_dest
, "/", alias
);
95 r
= symlink(service
, link
);
106 static int generate_unit_file(SysvStub
*s
) {
107 _cleanup_free_
char *path_escaped
= NULL
;
108 _cleanup_fclose_
FILE *f
= NULL
;
118 path_escaped
= specifier_escape(s
->path
);
122 unit
= strjoina(arg_dest
, "/", s
->name
);
124 /* We might already have a symlink with the same name from a Provides:,
125 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
126 * so remove an existing link */
127 if (is_symlink(unit
) > 0) {
128 log_warning("Overwriting existing symlink %s with real service.", unit
);
132 f
= fopen(unit
, "wxe");
134 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
137 "# Automatically generated by systemd-sysv-generator\n\n"
139 "Documentation=man:systemd-sysv-generator(8)\n"
143 if (s
->description
) {
144 _cleanup_free_
char *t
;
146 t
= specifier_escape(s
->description
);
150 fprintf(f
, "Description=%s\n", t
);
153 STRV_FOREACH(p
, s
->before
)
154 fprintf(f
, "Before=%s\n", *p
);
155 STRV_FOREACH(p
, s
->after
)
156 fprintf(f
, "After=%s\n", *p
);
157 STRV_FOREACH(p
, s
->wants
)
158 fprintf(f
, "Wants=%s\n", *p
);
168 "RemainAfterExit=%s\n",
169 yes_no(!s
->pid_file
));
172 _cleanup_free_
char *t
;
174 t
= specifier_escape(s
->pid_file
);
178 fprintf(f
, "PIDFile=%s\n", t
);
181 /* Consider two special LSB exit codes a clean exit */
184 "SuccessExitStatus=%i %i\n",
189 "ExecStart=%s start\n"
190 "ExecStop=%s stop\n",
191 path_escaped
, path_escaped
);
194 fprintf(f
, "ExecReload=%s reload\n", path_escaped
);
196 r
= fflush_and_check(f
);
198 return log_error_errno(r
, "Failed to write unit %s: %m", unit
);
200 STRV_FOREACH(p
, s
->wanted_by
)
201 (void) generator_add_symlink(arg_dest
, *p
, "wants", s
->name
);
206 static bool usage_contains_reload(const char *line
) {
207 return (strcasestr(line
, "{reload|") ||
208 strcasestr(line
, "{reload}") ||
209 strcasestr(line
, "{reload\"") ||
210 strcasestr(line
, "|reload|") ||
211 strcasestr(line
, "|reload}") ||
212 strcasestr(line
, "|reload\""));
215 static char *sysv_translate_name(const char *name
) {
216 _cleanup_free_
char *c
= NULL
;
223 res
= endswith(c
, ".sh");
227 if (unit_name_mangle(c
, 0, &res
) < 0)
233 static int sysv_translate_facility(SysvStub
*s
, unsigned line
, const char *name
, char **ret
) {
235 /* We silently ignore the $ prefix here. According to the LSB
236 * spec it simply indicates whether something is a
237 * standardized name or a distribution-specific one. Since we
238 * just follow what already exists and do not introduce new
239 * uses or names we don't care who introduced a new name. */
241 static const char * const table
[] = {
242 /* LSB defined facilities */
244 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
245 "named", SPECIAL_NSS_LOOKUP_TARGET
,
246 "portmap", SPECIAL_RPCBIND_TARGET
,
247 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
249 "time", SPECIAL_TIME_SYNC_TARGET
,
252 const char *filename
;
253 char *filename_no_sh
, *e
, *m
;
262 filename
= basename(s
->path
);
264 n
= *name
== '$' ? name
+ 1 : name
;
266 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
267 if (!streq(table
[i
], n
))
275 m
= strdup(table
[i
+1]);
283 /* If we don't know this name, fallback heuristics to figure
284 * out whether something is a target or a service alias. */
286 /* Facilities starting with $ are most likely targets */
288 r
= unit_name_build(n
, NULL
, ".target", ret
);
290 return log_error_errno(r
, "[%s:%u] Could not build name for facility %s: %m", s
->path
, line
, name
);
295 /* Strip ".sh" suffix from file name for comparison */
296 filename_no_sh
= strdupa(filename
);
297 e
= endswith(filename_no_sh
, ".sh");
300 filename
= filename_no_sh
;
303 /* Names equaling the file name of the services are redundant */
304 if (streq_ptr(n
, filename
)) {
309 /* Everything else we assume to be normal service names */
310 m
= sysv_translate_name(n
);
318 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
326 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
328 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
330 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
334 r
= sysv_translate_facility(s
, line
, word
, &m
);
335 if (r
<= 0) /* continue on error */
338 switch (unit_name_to_type(m
)) {
341 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
342 r
= add_alias(s
->name
, m
);
344 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
349 /* NB: SysV targets which are provided by a
350 * service are pulled in by the services, as
351 * an indication that the generic service is
352 * now available. This is strictly one-way.
353 * The targets do NOT pull in SysV services! */
355 r
= strv_extend(&s
->before
, m
);
359 r
= strv_extend(&s
->wants
, m
);
363 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
364 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
367 r
= strv_extend(&s
->wants
, SPECIAL_NETWORK_TARGET
);
374 case _UNIT_TYPE_INVALID
:
375 log_warning("Unit name '%s' is invalid", m
);
379 log_warning("Unknown unit type for unit '%s'", m
);
386 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
394 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
397 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
399 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
403 r
= sysv_translate_facility(s
, line
, word
, &m
);
404 if (r
<= 0) /* continue on error */
407 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
409 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
410 /* the network-online target is special, as it needs to be actively pulled in */
411 r
= strv_extend(&s
->after
, m
);
415 r
= strv_extend(&s
->wants
, m
);
417 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
425 static int load_sysv(SysvStub
*s
) {
426 _cleanup_fclose_
FILE *f
;
436 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
438 bool supports_reload
= false;
443 f
= fopen(s
->path
, "re");
448 return log_error_errno(errno
, "Failed to open %s: %m", s
->path
);
451 log_debug("Loading SysV script %s", s
->path
);
453 FOREACH_LINE(l
, f
, goto fail
) {
460 /* Try to figure out whether this init script supports
461 * the reload operation. This heuristic looks for
462 * "Usage" lines which include the reload option. */
463 if ( state
== USAGE_CONTINUATION
||
464 (state
== NORMAL
&& strcasestr(t
, "usage"))) {
465 if (usage_contains_reload(t
)) {
466 supports_reload
= true;
468 } else if (t
[strlen(t
)-1] == '\\')
469 state
= USAGE_CONTINUATION
;
477 if (state
== NORMAL
&& streq(t
, "### BEGIN INIT INFO")) {
483 if (IN_SET(state
, LSB_DESCRIPTION
, LSB
) && streq(t
, "### END INIT INFO")) {
489 t
+= strspn(t
, WHITESPACE
);
491 if (state
== NORMAL
) {
493 /* Try to parse Red Hat style description */
495 if (startswith_no_case(t
, "description:")) {
501 if (k
> 0 && t
[k
-1] == '\\') {
506 j
= empty_to_null(strstrip(t
+12));
508 r
= free_and_strdup(&chkconfig_description
, j
);
512 } else if (startswith_no_case(t
, "pidfile:")) {
518 if (!path_is_absolute(fn
)) {
519 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
523 r
= free_and_strdup(&s
->pid_file
, fn
);
528 } else if (state
== DESCRIPTION
) {
530 /* Try to parse Red Hat style description
537 if (k
> 0 && t
[k
-1] == '\\')
546 if (chkconfig_description
)
547 d
= strjoin(chkconfig_description
, " ", j
);
553 free(chkconfig_description
);
554 chkconfig_description
= d
;
557 } else if (IN_SET(state
, LSB
, LSB_DESCRIPTION
)) {
559 if (startswith_no_case(t
, "Provides:")) {
562 r
= handle_provides(s
, line
, t
, t
+ 9);
566 } else if (startswith_no_case(t
, "Required-Start:") ||
567 startswith_no_case(t
, "Should-Start:") ||
568 startswith_no_case(t
, "X-Start-Before:") ||
569 startswith_no_case(t
, "X-Start-After:")) {
573 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
577 } else if (startswith_no_case(t
, "Description:")) {
580 state
= LSB_DESCRIPTION
;
582 j
= empty_to_null(strstrip(t
+12));
584 r
= free_and_strdup(&long_description
, j
);
588 } else if (startswith_no_case(t
, "Short-Description:")) {
593 j
= empty_to_null(strstrip(t
+18));
595 r
= free_and_strdup(&short_description
, j
);
599 } else if (state
== LSB_DESCRIPTION
) {
601 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
608 if (long_description
)
609 d
= strjoin(long_description
, " ", t
);
615 free(long_description
);
616 long_description
= d
;
625 s
->reload
= supports_reload
;
627 /* We use the long description only if
628 * no short description is set. */
630 if (short_description
)
631 description
= short_description
;
632 else if (chkconfig_description
)
633 description
= chkconfig_description
;
634 else if (long_description
)
635 description
= long_description
;
642 d
= strappend(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
653 return log_error_errno(errno
, "Failed to read configuration file '%s': %m", s
->path
);
656 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
666 if (s
->sysv_start_priority
< 0)
669 HASHMAP_FOREACH(other
, all_services
, j
) {
676 if (other
->sysv_start_priority
< 0)
679 /* If both units have modern headers we don't care
680 * about the priorities */
681 if (s
->has_lsb
&& other
->has_lsb
)
684 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
685 r
= strv_extend(&s
->after
, other
->name
);
689 } else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
690 r
= strv_extend(&s
->before
, other
->name
);
696 /* FIXME: Maybe we should compare the name here lexicographically? */
702 static int acquire_search_path(const char *def
, const char *envvar
, char ***ret
) {
703 _cleanup_strv_free_
char **l
= NULL
;
712 r
= path_split_and_make_absolute(e
, &l
);
714 return log_error_errno(r
, "Failed to make $%s search path absolute: %m", envvar
);
717 if (strv_isempty(l
)) {
720 l
= strv_new(def
, NULL
);
725 if (!path_strv_resolve_uniq(l
, NULL
))
733 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
734 _cleanup_strv_free_
char **sysvinit_path
= NULL
;
740 r
= acquire_search_path(SYSTEM_SYSVINIT_PATH
, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path
);
744 STRV_FOREACH(path
, sysvinit_path
) {
745 _cleanup_closedir_
DIR *d
= NULL
;
751 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", *path
);
755 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", *path
)) {
756 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
757 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
760 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
761 log_warning_errno(errno
, "stat() failed on %s/%s, ignoring: %m", *path
, de
->d_name
);
765 if (!(st
.st_mode
& S_IXUSR
))
768 if (!S_ISREG(st
.st_mode
))
771 name
= sysv_translate_name(de
->d_name
);
775 if (hashmap_contains(all_services
, name
))
778 r
= unit_file_exists(UNIT_FILE_SYSTEM
, lp
, name
);
779 if (r
< 0 && !IN_SET(r
, -ELOOP
, -ERFKILL
, -EADDRNOTAVAIL
)) {
780 log_debug_errno(r
, "Failed to detect whether %s exists, skipping: %m", name
);
783 log_debug("Native unit for %s already exists, skipping.", name
);
787 fpath
= strjoin(*path
, "/", de
->d_name
);
791 service
= new0(SysvStub
, 1);
795 service
->sysv_start_priority
= -1;
796 service
->name
= TAKE_PTR(name
);
797 service
->path
= TAKE_PTR(fpath
);
799 r
= hashmap_put(all_services
, service
->name
, service
);
810 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
811 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
812 _cleanup_strv_free_
char **sysvrcnd_path
= NULL
;
821 r
= acquire_search_path(SYSTEM_SYSVRCND_PATH
, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path
);
825 STRV_FOREACH(p
, sysvrcnd_path
) {
826 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
828 _cleanup_closedir_
DIR *d
= NULL
;
829 _cleanup_free_
char *path
= NULL
;
832 path
= strjoin(*p
, "/", rcnd_table
[i
].path
);
841 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", path
);
846 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", path
)) {
847 _cleanup_free_
char *name
= NULL
, *fpath
= NULL
;
850 if (de
->d_name
[0] != 'S')
853 if (strlen(de
->d_name
) < 4)
856 a
= undecchar(de
->d_name
[1]);
857 b
= undecchar(de
->d_name
[2]);
862 fpath
= strjoin(*p
, "/", de
->d_name
);
868 name
= sysv_translate_name(de
->d_name
+ 3);
874 service
= hashmap_get(all_services
, name
);
876 log_debug("Ignoring %s symlink in %s, not generating %s.", de
->d_name
, rcnd_table
[i
].path
, name
);
880 service
->sysv_start_priority
= MAX(a
*10 + b
, service
->sysv_start_priority
);
882 r
= set_ensure_allocated(&runlevel_services
[i
], NULL
);
888 r
= set_put(runlevel_services
[i
], service
);
897 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
898 SET_FOREACH(service
, runlevel_services
[i
], j
) {
899 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
904 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
914 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
915 set_free(runlevel_services
[i
]);
920 int main(int argc
, char *argv
[]) {
921 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
922 _cleanup_(lookup_paths_free
) LookupPaths lp
= {};
927 if (argc
> 1 && argc
!= 4) {
928 log_error("This program takes three or no arguments.");
935 log_set_prohibit_ipc(true);
936 log_set_target(LOG_TARGET_AUTO
);
937 log_parse_environment();
942 r
= lookup_paths_init(&lp
, UNIT_FILE_SYSTEM
, LOOKUP_PATHS_EXCLUDE_GENERATED
, NULL
);
944 log_error_errno(r
, "Failed to find lookup paths: %m");
948 all_services
= hashmap_new(&string_hash_ops
);
954 r
= enumerate_sysv(&lp
, all_services
);
958 r
= set_dependencies_from_rcnd(&lp
, all_services
);
962 HASHMAP_FOREACH(service
, all_services
, j
)
963 (void) load_sysv(service
);
965 HASHMAP_FOREACH(service
, all_services
, j
) {
966 (void) fix_order(service
, all_services
);
967 (void) generate_unit_file(service
);
973 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;