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
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 #include "alloc-util.h"
28 #include "dirent-util.h"
29 #include "exit-status.h"
32 #include "generator.h"
34 #include "hexdecoct.h"
38 #include "path-lookup.h"
39 #include "path-util.h"
42 #include "stat-util.h"
43 #include "string-util.h"
45 #include "unit-name.h"
52 /* Standard SysV runlevels for start-up */
53 { "rc1.d", SPECIAL_RESCUE_TARGET
},
54 { "rc2.d", SPECIAL_MULTI_USER_TARGET
},
55 { "rc3.d", SPECIAL_MULTI_USER_TARGET
},
56 { "rc4.d", SPECIAL_MULTI_USER_TARGET
},
57 { "rc5.d", SPECIAL_GRAPHICAL_TARGET
},
59 /* We ignore the SysV runlevels for shutdown here, as SysV services get default dependencies anyway, and that
60 * means they are shut down anyway at system power off if running. */
63 static const char *arg_dest
= "/tmp";
65 typedef struct SysvStub
{
69 int sysv_start_priority
;
80 static void free_sysvstub(SysvStub
*s
) {
91 strv_free(s
->wanted_by
);
95 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
97 static void free_sysvstub_hashmapp(Hashmap
**h
) {
100 while ((stub
= hashmap_steal_first(*h
)))
106 static int add_alias(const char *service
, const char *alias
) {
113 link
= strjoina(arg_dest
, "/", alias
);
115 r
= symlink(service
, link
);
126 static int generate_unit_file(SysvStub
*s
) {
127 _cleanup_fclose_
FILE *f
= NULL
;
137 unit
= strjoina(arg_dest
, "/", s
->name
);
139 /* We might already have a symlink with the same name from a Provides:,
140 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
141 * so remove an existing link */
142 if (is_symlink(unit
) > 0) {
143 log_warning("Overwriting existing symlink %s with real service.", unit
);
147 f
= fopen(unit
, "wxe");
149 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
152 "# Automatically generated by systemd-sysv-generator\n\n"
154 "Documentation=man:systemd-sysv-generator(8)\n"
159 fprintf(f
, "Description=%s\n", s
->description
);
161 STRV_FOREACH(p
, s
->before
)
162 fprintf(f
, "Before=%s\n", *p
);
163 STRV_FOREACH(p
, s
->after
)
164 fprintf(f
, "After=%s\n", *p
);
165 STRV_FOREACH(p
, s
->wants
)
166 fprintf(f
, "Wants=%s\n", *p
);
176 "RemainAfterExit=%s\n",
177 yes_no(!s
->pid_file
));
180 fprintf(f
, "PIDFile=%s\n", s
->pid_file
);
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",
195 fprintf(f
, "ExecReload=%s reload\n", s
->path
);
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
, UNIT_NAME_NOGLOB
, &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 const char *filename
;
254 char *filename_no_sh
, *e
, *m
;
263 filename
= basename(s
->path
);
265 n
= *name
== '$' ? name
+ 1 : name
;
267 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
268 if (!streq(table
[i
], n
))
276 m
= strdup(table
[i
+1]);
284 /* If we don't know this name, fallback heuristics to figure
285 * out whether something is a target or a service alias. */
287 /* Facilities starting with $ are most likely targets */
289 r
= unit_name_build(n
, NULL
, ".target", ret
);
291 return log_error_errno(r
, "[%s:%u] Could not build name for facility %s: %m", s
->path
, line
, name
);
296 /* Strip ".sh" suffix from file name for comparison */
297 filename_no_sh
= strdupa(filename
);
298 e
= endswith(filename_no_sh
, ".sh");
301 filename
= filename_no_sh
;
304 /* Names equaling the file name of the services are redundant */
305 if (streq_ptr(n
, filename
)) {
310 /* Everything else we assume to be normal service names */
311 m
= sysv_translate_name(n
);
319 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
327 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
329 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
331 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
335 r
= sysv_translate_facility(s
, line
, word
, &m
);
336 if (r
<= 0) /* continue on error */
339 switch (unit_name_to_type(m
)) {
342 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
343 r
= add_alias(s
->name
, m
);
345 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
350 /* NB: SysV targets which are provided by a
351 * service are pulled in by the services, as
352 * an indication that the generic service is
353 * now available. This is strictly one-way.
354 * The targets do NOT pull in SysV services! */
356 r
= strv_extend(&s
->before
, m
);
360 r
= strv_extend(&s
->wants
, m
);
364 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
365 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
368 r
= strv_extend(&s
->wants
, SPECIAL_NETWORK_TARGET
);
375 case _UNIT_TYPE_INVALID
:
376 log_warning("Unit name '%s' is invalid", m
);
380 log_warning("Unknown unit type for unit '%s'", m
);
387 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
395 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
398 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
400 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
404 r
= sysv_translate_facility(s
, line
, word
, &m
);
405 if (r
<= 0) /* continue on error */
408 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
410 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
411 /* the network-online target is special, as it needs to be actively pulled in */
412 r
= strv_extend(&s
->after
, m
);
416 r
= strv_extend(&s
->wants
, m
);
418 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
426 static int load_sysv(SysvStub
*s
) {
427 _cleanup_fclose_
FILE *f
;
437 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
439 bool supports_reload
= false;
444 f
= fopen(s
->path
, "re");
449 return log_error_errno(errno
, "Failed to open %s: %m", s
->path
);
452 log_debug("Loading SysV script %s", s
->path
);
454 FOREACH_LINE(l
, f
, goto fail
) {
461 /* Try to figure out whether this init script supports
462 * the reload operation. This heuristic looks for
463 * "Usage" lines which include the reload option. */
464 if ( state
== USAGE_CONTINUATION
||
465 (state
== NORMAL
&& strcasestr(t
, "usage"))) {
466 if (usage_contains_reload(t
)) {
467 supports_reload
= true;
469 } else if (t
[strlen(t
)-1] == '\\')
470 state
= USAGE_CONTINUATION
;
478 if (state
== NORMAL
&& streq(t
, "### BEGIN INIT INFO")) {
484 if (IN_SET(state
, LSB_DESCRIPTION
, LSB
) && streq(t
, "### END INIT INFO")) {
490 t
+= strspn(t
, WHITESPACE
);
492 if (state
== NORMAL
) {
494 /* Try to parse Red Hat style description */
496 if (startswith_no_case(t
, "description:")) {
502 if (k
> 0 && t
[k
-1] == '\\') {
507 j
= empty_to_null(strstrip(t
+12));
509 r
= free_and_strdup(&chkconfig_description
, j
);
513 } else if (startswith_no_case(t
, "pidfile:")) {
519 if (!path_is_absolute(fn
)) {
520 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
524 r
= free_and_strdup(&s
->pid_file
, fn
);
529 } else if (state
== DESCRIPTION
) {
531 /* Try to parse Red Hat style description
538 if (k
> 0 && t
[k
-1] == '\\')
547 if (chkconfig_description
)
548 d
= strjoin(chkconfig_description
, " ", j
);
554 free(chkconfig_description
);
555 chkconfig_description
= d
;
558 } else if (IN_SET(state
, LSB
, LSB_DESCRIPTION
)) {
560 if (startswith_no_case(t
, "Provides:")) {
563 r
= handle_provides(s
, line
, t
, t
+ 9);
567 } else if (startswith_no_case(t
, "Required-Start:") ||
568 startswith_no_case(t
, "Should-Start:") ||
569 startswith_no_case(t
, "X-Start-Before:") ||
570 startswith_no_case(t
, "X-Start-After:")) {
574 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
578 } else if (startswith_no_case(t
, "Description:")) {
581 state
= LSB_DESCRIPTION
;
583 j
= empty_to_null(strstrip(t
+12));
585 r
= free_and_strdup(&long_description
, j
);
589 } else if (startswith_no_case(t
, "Short-Description:")) {
594 j
= empty_to_null(strstrip(t
+18));
596 r
= free_and_strdup(&short_description
, j
);
600 } else if (state
== LSB_DESCRIPTION
) {
602 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
609 if (long_description
)
610 d
= strjoin(long_description
, " ", t
);
616 free(long_description
);
617 long_description
= d
;
626 s
->reload
= supports_reload
;
628 /* We use the long description only if
629 * no short description is set. */
631 if (short_description
)
632 description
= short_description
;
633 else if (chkconfig_description
)
634 description
= chkconfig_description
;
635 else if (long_description
)
636 description
= long_description
;
643 d
= strappend(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
654 return log_error_errno(errno
, "Failed to read configuration file '%s': %m", s
->path
);
657 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
667 if (s
->sysv_start_priority
< 0)
670 HASHMAP_FOREACH(other
, all_services
, j
) {
677 if (other
->sysv_start_priority
< 0)
680 /* If both units have modern headers we don't care
681 * about the priorities */
682 if (s
->has_lsb
&& other
->has_lsb
)
685 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
686 r
= strv_extend(&s
->after
, other
->name
);
690 } else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
691 r
= strv_extend(&s
->before
, other
->name
);
697 /* FIXME: Maybe we should compare the name here lexicographically? */
703 static int acquire_search_path(const char *def
, const char *envvar
, char ***ret
) {
704 _cleanup_strv_free_
char **l
= NULL
;
713 r
= path_split_and_make_absolute(e
, &l
);
715 return log_error_errno(r
, "Failed to make $%s search path absolute: %m", envvar
);
718 if (strv_isempty(l
)) {
721 l
= strv_new(def
, NULL
);
726 if (!path_strv_resolve_uniq(l
, NULL
))
735 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
736 _cleanup_strv_free_
char **sysvinit_path
= NULL
;
742 r
= acquire_search_path(SYSTEM_SYSVINIT_PATH
, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path
);
746 STRV_FOREACH(path
, sysvinit_path
) {
747 _cleanup_closedir_
DIR *d
= NULL
;
753 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", *path
);
757 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", *path
)) {
758 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
759 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
762 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
763 log_warning_errno(errno
, "stat() failed on %s/%s, ignoring: %m", *path
, de
->d_name
);
767 if (!(st
.st_mode
& S_IXUSR
))
770 if (!S_ISREG(st
.st_mode
))
773 name
= sysv_translate_name(de
->d_name
);
777 if (hashmap_contains(all_services
, name
))
780 r
= unit_file_exists(UNIT_FILE_SYSTEM
, lp
, name
);
781 if (r
< 0 && !IN_SET(r
, -ELOOP
, -ERFKILL
, -EADDRNOTAVAIL
)) {
782 log_debug_errno(r
, "Failed to detect whether %s exists, skipping: %m", name
);
785 log_debug("Native unit for %s already exists, skipping.", name
);
789 fpath
= strjoin(*path
, "/", de
->d_name
);
793 service
= new0(SysvStub
, 1);
797 service
->sysv_start_priority
= -1;
798 service
->name
= name
;
799 service
->path
= 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_target(LOG_TARGET_SAFE
);
939 log_parse_environment();
944 r
= lookup_paths_init(&lp
, UNIT_FILE_SYSTEM
, LOOKUP_PATHS_EXCLUDE_GENERATED
, NULL
);
946 log_error_errno(r
, "Failed to find lookup paths: %m");
950 all_services
= hashmap_new(&string_hash_ops
);
956 r
= enumerate_sysv(&lp
, all_services
);
960 r
= set_dependencies_from_rcnd(&lp
, all_services
);
964 HASHMAP_FOREACH(service
, all_services
, j
)
965 (void) load_sysv(service
);
967 HASHMAP_FOREACH(service
, all_services
, j
) {
968 (void) fix_order(service
, all_services
);
969 (void) generate_unit_file(service
);
975 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;