1 /* SPDX-License-Identifier: LGPL-2.1+ */
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 void free_sysvstub(SysvStub
*s
) {
73 strv_free(s
->wanted_by
);
77 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
79 static void free_sysvstub_hashmapp(Hashmap
**h
) {
80 hashmap_free_with_destructor(*h
, free_sysvstub
);
83 static int add_alias(const char *service
, const char *alias
) {
90 link
= prefix_roota(arg_dest
, alias
);
92 r
= symlink(service
, link
);
103 static int generate_unit_file(SysvStub
*s
) {
104 _cleanup_free_
char *path_escaped
= NULL
;
105 _cleanup_fclose_
FILE *f
= NULL
;
115 path_escaped
= specifier_escape(s
->path
);
119 unit
= prefix_roota(arg_dest
, s
->name
);
121 /* We might already have a symlink with the same name from a Provides:,
122 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
123 * so remove an existing link */
124 if (is_symlink(unit
) > 0) {
125 log_warning("Overwriting existing symlink %s with real service.", unit
);
129 f
= fopen(unit
, "wxe");
131 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
134 "# Automatically generated by systemd-sysv-generator\n\n"
136 "Documentation=man:systemd-sysv-generator(8)\n"
140 if (s
->description
) {
141 _cleanup_free_
char *t
;
143 t
= specifier_escape(s
->description
);
147 fprintf(f
, "Description=%s\n", t
);
150 STRV_FOREACH(p
, s
->before
)
151 fprintf(f
, "Before=%s\n", *p
);
152 STRV_FOREACH(p
, s
->after
)
153 fprintf(f
, "After=%s\n", *p
);
154 STRV_FOREACH(p
, s
->wants
)
155 fprintf(f
, "Wants=%s\n", *p
);
165 "RemainAfterExit=%s\n",
166 yes_no(!s
->pid_file
));
169 _cleanup_free_
char *t
;
171 t
= specifier_escape(s
->pid_file
);
175 fprintf(f
, "PIDFile=%s\n", t
);
178 /* Consider two special LSB exit codes a clean exit */
181 "SuccessExitStatus=%i %i\n",
186 "ExecStart=%s start\n"
187 "ExecStop=%s stop\n",
188 path_escaped
, path_escaped
);
191 fprintf(f
, "ExecReload=%s reload\n", path_escaped
);
193 r
= fflush_and_check(f
);
195 return log_error_errno(r
, "Failed to write unit %s: %m", unit
);
197 STRV_FOREACH(p
, s
->wanted_by
)
198 (void) generator_add_symlink(arg_dest
, *p
, "wants", s
->name
);
203 static bool usage_contains_reload(const char *line
) {
204 return (strcasestr(line
, "{reload|") ||
205 strcasestr(line
, "{reload}") ||
206 strcasestr(line
, "{reload\"") ||
207 strcasestr(line
, "|reload|") ||
208 strcasestr(line
, "|reload}") ||
209 strcasestr(line
, "|reload\""));
212 static char *sysv_translate_name(const char *name
) {
213 _cleanup_free_
char *c
= NULL
;
220 res
= endswith(c
, ".sh");
224 if (unit_name_mangle(c
, 0, &res
) < 0)
230 static int sysv_translate_facility(SysvStub
*s
, unsigned line
, const char *name
, char **ret
) {
232 /* We silently ignore the $ prefix here. According to the LSB
233 * spec it simply indicates whether something is a
234 * standardized name or a distribution-specific one. Since we
235 * just follow what already exists and do not introduce new
236 * uses or names we don't care who introduced a new name. */
238 static const char * const table
[] = {
239 /* LSB defined facilities */
241 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
242 "named", SPECIAL_NSS_LOOKUP_TARGET
,
243 "portmap", SPECIAL_RPCBIND_TARGET
,
244 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
246 "time", SPECIAL_TIME_SYNC_TARGET
,
249 const char *filename
;
250 char *filename_no_sh
, *e
, *m
;
259 filename
= basename(s
->path
);
261 n
= *name
== '$' ? name
+ 1 : name
;
263 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
264 if (!streq(table
[i
], n
))
272 m
= strdup(table
[i
+1]);
280 /* If we don't know this name, fallback heuristics to figure
281 * out whether something is a target or a service alias. */
283 /* Facilities starting with $ are most likely targets */
285 r
= unit_name_build(n
, NULL
, ".target", ret
);
287 return log_error_errno(r
, "[%s:%u] Could not build name for facility %s: %m", s
->path
, line
, name
);
292 /* Strip ".sh" suffix from file name for comparison */
293 filename_no_sh
= strdupa(filename
);
294 e
= endswith(filename_no_sh
, ".sh");
297 filename
= filename_no_sh
;
300 /* Names equaling the file name of the services are redundant */
301 if (streq_ptr(n
, filename
)) {
306 /* Everything else we assume to be normal service names */
307 m
= sysv_translate_name(n
);
315 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
323 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
325 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_UNQUOTE
|EXTRACT_RELAX
);
327 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
331 r
= sysv_translate_facility(s
, line
, word
, &m
);
332 if (r
<= 0) /* continue on error */
335 switch (unit_name_to_type(m
)) {
338 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
339 r
= add_alias(s
->name
, m
);
341 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
346 /* NB: SysV targets which are provided by a
347 * service are pulled in by the services, as
348 * an indication that the generic service is
349 * now available. This is strictly one-way.
350 * The targets do NOT pull in SysV services! */
352 r
= strv_extend(&s
->before
, m
);
356 r
= strv_extend(&s
->wants
, m
);
360 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
361 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
364 r
= strv_extend(&s
->wants
, SPECIAL_NETWORK_TARGET
);
371 case _UNIT_TYPE_INVALID
:
372 log_warning("Unit name '%s' is invalid", m
);
376 log_warning("Unknown unit type for unit '%s'", m
);
383 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
391 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
394 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_UNQUOTE
|EXTRACT_RELAX
);
396 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
400 r
= sysv_translate_facility(s
, line
, word
, &m
);
401 if (r
<= 0) /* continue on error */
404 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
406 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
407 /* the network-online target is special, as it needs to be actively pulled in */
408 r
= strv_extend(&s
->after
, m
);
412 r
= strv_extend(&s
->wants
, m
);
414 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
422 static int load_sysv(SysvStub
*s
) {
423 _cleanup_fclose_
FILE *f
;
433 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
435 bool supports_reload
= false;
439 f
= fopen(s
->path
, "re");
444 return log_error_errno(errno
, "Failed to open %s: %m", s
->path
);
447 log_debug("Loading SysV script %s", s
->path
);
450 _cleanup_free_
char *l
= NULL
;
453 r
= read_line(f
, LONG_LINE_MAX
, &l
);
455 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(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 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
)) {
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
= path_join(*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
= path_join(*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
= path_join(*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 static int run(const char *dest
, const char *dest_early
, const char *dest_late
) {
921 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
922 _cleanup_(lookup_paths_free
) LookupPaths lp
= {};
927 assert_se(arg_dest
= dest_late
);
929 r
= lookup_paths_init(&lp
, UNIT_FILE_SYSTEM
, LOOKUP_PATHS_EXCLUDE_GENERATED
, NULL
);
931 return log_error_errno(r
, "Failed to find lookup paths: %m");
933 all_services
= hashmap_new(&string_hash_ops
);
937 r
= enumerate_sysv(&lp
, all_services
);
941 r
= set_dependencies_from_rcnd(&lp
, all_services
);
945 HASHMAP_FOREACH(service
, all_services
, j
)
946 (void) load_sysv(service
);
948 HASHMAP_FOREACH(service
, all_services
, j
) {
949 (void) fix_order(service
, all_services
);
950 (void) generate_unit_file(service
);
956 DEFINE_MAIN_GENERATOR_FUNCTION(run
);