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 "specifier.h"
43 #include "stat-util.h"
44 #include "string-util.h"
46 #include "unit-name.h"
53 /* Standard SysV runlevels for start-up */
54 { "rc1.d", SPECIAL_RESCUE_TARGET
},
55 { "rc2.d", SPECIAL_MULTI_USER_TARGET
},
56 { "rc3.d", SPECIAL_MULTI_USER_TARGET
},
57 { "rc4.d", SPECIAL_MULTI_USER_TARGET
},
58 { "rc5.d", SPECIAL_GRAPHICAL_TARGET
},
60 /* We ignore the SysV runlevels for shutdown here, as SysV services get default dependencies anyway, and that
61 * means they are shut down anyway at system power off if running. */
64 static const char *arg_dest
= "/tmp";
66 typedef struct SysvStub
{
70 int sysv_start_priority
;
81 static void free_sysvstub(SysvStub
*s
) {
92 strv_free(s
->wanted_by
);
96 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
98 static void free_sysvstub_hashmapp(Hashmap
**h
) {
99 hashmap_free_with_destructor(*h
, free_sysvstub
);
102 static int add_alias(const char *service
, const char *alias
) {
109 link
= strjoina(arg_dest
, "/", alias
);
111 r
= symlink(service
, link
);
122 static int generate_unit_file(SysvStub
*s
) {
123 _cleanup_free_
char *path_escaped
= NULL
;
124 _cleanup_fclose_
FILE *f
= NULL
;
134 path_escaped
= specifier_escape(s
->path
);
138 unit
= strjoina(arg_dest
, "/", s
->name
);
140 /* We might already have a symlink with the same name from a Provides:,
141 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
142 * so remove an existing link */
143 if (is_symlink(unit
) > 0) {
144 log_warning("Overwriting existing symlink %s with real service.", unit
);
148 f
= fopen(unit
, "wxe");
150 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
153 "# Automatically generated by systemd-sysv-generator\n\n"
155 "Documentation=man:systemd-sysv-generator(8)\n"
159 if (s
->description
) {
160 _cleanup_free_
char *t
;
162 t
= specifier_escape(s
->description
);
166 fprintf(f
, "Description=%s\n", t
);
169 STRV_FOREACH(p
, s
->before
)
170 fprintf(f
, "Before=%s\n", *p
);
171 STRV_FOREACH(p
, s
->after
)
172 fprintf(f
, "After=%s\n", *p
);
173 STRV_FOREACH(p
, s
->wants
)
174 fprintf(f
, "Wants=%s\n", *p
);
184 "RemainAfterExit=%s\n",
185 yes_no(!s
->pid_file
));
188 _cleanup_free_
char *t
;
190 t
= specifier_escape(s
->pid_file
);
194 fprintf(f
, "PIDFile=%s\n", t
);
197 /* Consider two special LSB exit codes a clean exit */
200 "SuccessExitStatus=%i %i\n",
205 "ExecStart=%s start\n"
206 "ExecStop=%s stop\n",
207 path_escaped
, path_escaped
);
210 fprintf(f
, "ExecReload=%s reload\n", path_escaped
);
212 r
= fflush_and_check(f
);
214 return log_error_errno(r
, "Failed to write unit %s: %m", unit
);
216 STRV_FOREACH(p
, s
->wanted_by
)
217 (void) generator_add_symlink(arg_dest
, *p
, "wants", s
->name
);
222 static bool usage_contains_reload(const char *line
) {
223 return (strcasestr(line
, "{reload|") ||
224 strcasestr(line
, "{reload}") ||
225 strcasestr(line
, "{reload\"") ||
226 strcasestr(line
, "|reload|") ||
227 strcasestr(line
, "|reload}") ||
228 strcasestr(line
, "|reload\""));
231 static char *sysv_translate_name(const char *name
) {
232 _cleanup_free_
char *c
= NULL
;
239 res
= endswith(c
, ".sh");
243 if (unit_name_mangle(c
, 0, &res
) < 0)
249 static int sysv_translate_facility(SysvStub
*s
, unsigned line
, const char *name
, char **ret
) {
251 /* We silently ignore the $ prefix here. According to the LSB
252 * spec it simply indicates whether something is a
253 * standardized name or a distribution-specific one. Since we
254 * just follow what already exists and do not introduce new
255 * uses or names we don't care who introduced a new name. */
257 static const char * const table
[] = {
258 /* LSB defined facilities */
260 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
261 "named", SPECIAL_NSS_LOOKUP_TARGET
,
262 "portmap", SPECIAL_RPCBIND_TARGET
,
263 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
265 "time", SPECIAL_TIME_SYNC_TARGET
,
268 const char *filename
;
269 char *filename_no_sh
, *e
, *m
;
278 filename
= basename(s
->path
);
280 n
= *name
== '$' ? name
+ 1 : name
;
282 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
283 if (!streq(table
[i
], n
))
291 m
= strdup(table
[i
+1]);
299 /* If we don't know this name, fallback heuristics to figure
300 * out whether something is a target or a service alias. */
302 /* Facilities starting with $ are most likely targets */
304 r
= unit_name_build(n
, NULL
, ".target", ret
);
306 return log_error_errno(r
, "[%s:%u] Could not build name for facility %s: %m", s
->path
, line
, name
);
311 /* Strip ".sh" suffix from file name for comparison */
312 filename_no_sh
= strdupa(filename
);
313 e
= endswith(filename_no_sh
, ".sh");
316 filename
= filename_no_sh
;
319 /* Names equaling the file name of the services are redundant */
320 if (streq_ptr(n
, filename
)) {
325 /* Everything else we assume to be normal service names */
326 m
= sysv_translate_name(n
);
334 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
342 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
344 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
346 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
350 r
= sysv_translate_facility(s
, line
, word
, &m
);
351 if (r
<= 0) /* continue on error */
354 switch (unit_name_to_type(m
)) {
357 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
358 r
= add_alias(s
->name
, m
);
360 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
365 /* NB: SysV targets which are provided by a
366 * service are pulled in by the services, as
367 * an indication that the generic service is
368 * now available. This is strictly one-way.
369 * The targets do NOT pull in SysV services! */
371 r
= strv_extend(&s
->before
, m
);
375 r
= strv_extend(&s
->wants
, m
);
379 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
380 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
383 r
= strv_extend(&s
->wants
, SPECIAL_NETWORK_TARGET
);
390 case _UNIT_TYPE_INVALID
:
391 log_warning("Unit name '%s' is invalid", m
);
395 log_warning("Unknown unit type for unit '%s'", m
);
402 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
410 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
413 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
415 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
419 r
= sysv_translate_facility(s
, line
, word
, &m
);
420 if (r
<= 0) /* continue on error */
423 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
425 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
426 /* the network-online target is special, as it needs to be actively pulled in */
427 r
= strv_extend(&s
->after
, m
);
431 r
= strv_extend(&s
->wants
, m
);
433 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
441 static int load_sysv(SysvStub
*s
) {
442 _cleanup_fclose_
FILE *f
;
452 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
454 bool supports_reload
= false;
459 f
= fopen(s
->path
, "re");
464 return log_error_errno(errno
, "Failed to open %s: %m", s
->path
);
467 log_debug("Loading SysV script %s", s
->path
);
469 FOREACH_LINE(l
, f
, goto fail
) {
476 /* Try to figure out whether this init script supports
477 * the reload operation. This heuristic looks for
478 * "Usage" lines which include the reload option. */
479 if ( state
== USAGE_CONTINUATION
||
480 (state
== NORMAL
&& strcasestr(t
, "usage"))) {
481 if (usage_contains_reload(t
)) {
482 supports_reload
= true;
484 } else if (t
[strlen(t
)-1] == '\\')
485 state
= USAGE_CONTINUATION
;
493 if (state
== NORMAL
&& streq(t
, "### BEGIN INIT INFO")) {
499 if (IN_SET(state
, LSB_DESCRIPTION
, LSB
) && streq(t
, "### END INIT INFO")) {
505 t
+= strspn(t
, WHITESPACE
);
507 if (state
== NORMAL
) {
509 /* Try to parse Red Hat style description */
511 if (startswith_no_case(t
, "description:")) {
517 if (k
> 0 && t
[k
-1] == '\\') {
522 j
= empty_to_null(strstrip(t
+12));
524 r
= free_and_strdup(&chkconfig_description
, j
);
528 } else if (startswith_no_case(t
, "pidfile:")) {
534 if (!path_is_absolute(fn
)) {
535 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
539 r
= free_and_strdup(&s
->pid_file
, fn
);
544 } else if (state
== DESCRIPTION
) {
546 /* Try to parse Red Hat style description
553 if (k
> 0 && t
[k
-1] == '\\')
562 if (chkconfig_description
)
563 d
= strjoin(chkconfig_description
, " ", j
);
569 free(chkconfig_description
);
570 chkconfig_description
= d
;
573 } else if (IN_SET(state
, LSB
, LSB_DESCRIPTION
)) {
575 if (startswith_no_case(t
, "Provides:")) {
578 r
= handle_provides(s
, line
, t
, t
+ 9);
582 } else if (startswith_no_case(t
, "Required-Start:") ||
583 startswith_no_case(t
, "Should-Start:") ||
584 startswith_no_case(t
, "X-Start-Before:") ||
585 startswith_no_case(t
, "X-Start-After:")) {
589 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
593 } else if (startswith_no_case(t
, "Description:")) {
596 state
= LSB_DESCRIPTION
;
598 j
= empty_to_null(strstrip(t
+12));
600 r
= free_and_strdup(&long_description
, j
);
604 } else if (startswith_no_case(t
, "Short-Description:")) {
609 j
= empty_to_null(strstrip(t
+18));
611 r
= free_and_strdup(&short_description
, j
);
615 } else if (state
== LSB_DESCRIPTION
) {
617 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
624 if (long_description
)
625 d
= strjoin(long_description
, " ", t
);
631 free(long_description
);
632 long_description
= d
;
641 s
->reload
= supports_reload
;
643 /* We use the long description only if
644 * no short description is set. */
646 if (short_description
)
647 description
= short_description
;
648 else if (chkconfig_description
)
649 description
= chkconfig_description
;
650 else if (long_description
)
651 description
= long_description
;
658 d
= strappend(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
669 return log_error_errno(errno
, "Failed to read configuration file '%s': %m", s
->path
);
672 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
682 if (s
->sysv_start_priority
< 0)
685 HASHMAP_FOREACH(other
, all_services
, j
) {
692 if (other
->sysv_start_priority
< 0)
695 /* If both units have modern headers we don't care
696 * about the priorities */
697 if (s
->has_lsb
&& other
->has_lsb
)
700 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
701 r
= strv_extend(&s
->after
, other
->name
);
705 } else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
706 r
= strv_extend(&s
->before
, other
->name
);
712 /* FIXME: Maybe we should compare the name here lexicographically? */
718 static int acquire_search_path(const char *def
, const char *envvar
, char ***ret
) {
719 _cleanup_strv_free_
char **l
= NULL
;
728 r
= path_split_and_make_absolute(e
, &l
);
730 return log_error_errno(r
, "Failed to make $%s search path absolute: %m", envvar
);
733 if (strv_isempty(l
)) {
736 l
= strv_new(def
, NULL
);
741 if (!path_strv_resolve_uniq(l
, NULL
))
749 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
750 _cleanup_strv_free_
char **sysvinit_path
= NULL
;
756 r
= acquire_search_path(SYSTEM_SYSVINIT_PATH
, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path
);
760 STRV_FOREACH(path
, sysvinit_path
) {
761 _cleanup_closedir_
DIR *d
= NULL
;
767 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", *path
);
771 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", *path
)) {
772 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
773 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
776 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
777 log_warning_errno(errno
, "stat() failed on %s/%s, ignoring: %m", *path
, de
->d_name
);
781 if (!(st
.st_mode
& S_IXUSR
))
784 if (!S_ISREG(st
.st_mode
))
787 name
= sysv_translate_name(de
->d_name
);
791 if (hashmap_contains(all_services
, name
))
794 r
= unit_file_exists(UNIT_FILE_SYSTEM
, lp
, name
);
795 if (r
< 0 && !IN_SET(r
, -ELOOP
, -ERFKILL
, -EADDRNOTAVAIL
)) {
796 log_debug_errno(r
, "Failed to detect whether %s exists, skipping: %m", name
);
799 log_debug("Native unit for %s already exists, skipping.", name
);
803 fpath
= strjoin(*path
, "/", de
->d_name
);
807 service
= new0(SysvStub
, 1);
811 service
->sysv_start_priority
= -1;
812 service
->name
= name
;
813 service
->path
= fpath
;
816 r
= hashmap_put(all_services
, service
->name
, service
);
827 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
828 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
829 _cleanup_strv_free_
char **sysvrcnd_path
= NULL
;
838 r
= acquire_search_path(SYSTEM_SYSVRCND_PATH
, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path
);
842 STRV_FOREACH(p
, sysvrcnd_path
) {
843 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
845 _cleanup_closedir_
DIR *d
= NULL
;
846 _cleanup_free_
char *path
= NULL
;
849 path
= strjoin(*p
, "/", rcnd_table
[i
].path
);
858 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", path
);
863 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", path
)) {
864 _cleanup_free_
char *name
= NULL
, *fpath
= NULL
;
867 if (de
->d_name
[0] != 'S')
870 if (strlen(de
->d_name
) < 4)
873 a
= undecchar(de
->d_name
[1]);
874 b
= undecchar(de
->d_name
[2]);
879 fpath
= strjoin(*p
, "/", de
->d_name
);
885 name
= sysv_translate_name(de
->d_name
+ 3);
891 service
= hashmap_get(all_services
, name
);
893 log_debug("Ignoring %s symlink in %s, not generating %s.", de
->d_name
, rcnd_table
[i
].path
, name
);
897 service
->sysv_start_priority
= MAX(a
*10 + b
, service
->sysv_start_priority
);
899 r
= set_ensure_allocated(&runlevel_services
[i
], NULL
);
905 r
= set_put(runlevel_services
[i
], service
);
914 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
915 SET_FOREACH(service
, runlevel_services
[i
], j
) {
916 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
921 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
931 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
932 set_free(runlevel_services
[i
]);
937 int main(int argc
, char *argv
[]) {
938 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
939 _cleanup_lookup_paths_free_ LookupPaths lp
= {};
944 if (argc
> 1 && argc
!= 4) {
945 log_error("This program takes three or no arguments.");
952 log_set_prohibit_ipc(true);
953 log_set_target(LOG_TARGET_AUTO
);
954 log_parse_environment();
959 r
= lookup_paths_init(&lp
, UNIT_FILE_SYSTEM
, LOOKUP_PATHS_EXCLUDE_GENERATED
, NULL
);
961 log_error_errno(r
, "Failed to find lookup paths: %m");
965 all_services
= hashmap_new(&string_hash_ops
);
971 r
= enumerate_sysv(&lp
, all_services
);
975 r
= set_dependencies_from_rcnd(&lp
, all_services
);
979 HASHMAP_FOREACH(service
, all_services
, j
)
980 (void) load_sysv(service
);
982 HASHMAP_FOREACH(service
, all_services
, j
) {
983 (void) fix_order(service
, all_services
);
984 (void) generate_unit_file(service
);
990 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;