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
) {
98 hashmap_free_with_destructor(*h
, free_sysvstub
);
101 static int add_alias(const char *service
, const char *alias
) {
108 link
= strjoina(arg_dest
, "/", alias
);
110 r
= symlink(service
, link
);
121 static int generate_unit_file(SysvStub
*s
) {
122 _cleanup_fclose_
FILE *f
= NULL
;
132 unit
= strjoina(arg_dest
, "/", s
->name
);
134 /* We might already have a symlink with the same name from a Provides:,
135 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
136 * so remove an existing link */
137 if (is_symlink(unit
) > 0) {
138 log_warning("Overwriting existing symlink %s with real service.", unit
);
142 f
= fopen(unit
, "wxe");
144 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
147 "# Automatically generated by systemd-sysv-generator\n\n"
149 "Documentation=man:systemd-sysv-generator(8)\n"
154 fprintf(f
, "Description=%s\n", s
->description
);
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 fprintf(f
, "PIDFile=%s\n", s
->pid_file
);
177 /* Consider two special LSB exit codes a clean exit */
180 "SuccessExitStatus=%i %i\n",
185 "ExecStart=%s start\n"
186 "ExecStop=%s stop\n",
190 fprintf(f
, "ExecReload=%s reload\n", s
->path
);
192 r
= fflush_and_check(f
);
194 return log_error_errno(r
, "Failed to write unit %s: %m", unit
);
196 STRV_FOREACH(p
, s
->wanted_by
)
197 (void) generator_add_symlink(arg_dest
, *p
, "wants", s
->name
);
202 static bool usage_contains_reload(const char *line
) {
203 return (strcasestr(line
, "{reload|") ||
204 strcasestr(line
, "{reload}") ||
205 strcasestr(line
, "{reload\"") ||
206 strcasestr(line
, "|reload|") ||
207 strcasestr(line
, "|reload}") ||
208 strcasestr(line
, "|reload\""));
211 static char *sysv_translate_name(const char *name
) {
212 _cleanup_free_
char *c
= NULL
;
219 res
= endswith(c
, ".sh");
223 if (unit_name_mangle(c
, UNIT_NAME_NOGLOB
, &res
) < 0)
229 static int sysv_translate_facility(SysvStub
*s
, unsigned line
, const char *name
, char **ret
) {
231 /* We silently ignore the $ prefix here. According to the LSB
232 * spec it simply indicates whether something is a
233 * standardized name or a distribution-specific one. Since we
234 * just follow what already exists and do not introduce new
235 * uses or names we don't care who introduced a new name. */
237 static const char * const table
[] = {
238 /* LSB defined facilities */
240 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
241 "named", SPECIAL_NSS_LOOKUP_TARGET
,
242 "portmap", SPECIAL_RPCBIND_TARGET
,
243 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
245 "time", SPECIAL_TIME_SYNC_TARGET
,
248 const char *filename
;
249 char *filename_no_sh
, *e
, *m
;
258 filename
= basename(s
->path
);
260 n
= *name
== '$' ? name
+ 1 : name
;
262 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
263 if (!streq(table
[i
], n
))
271 m
= strdup(table
[i
+1]);
279 /* If we don't know this name, fallback heuristics to figure
280 * out whether something is a target or a service alias. */
282 /* Facilities starting with $ are most likely targets */
284 r
= unit_name_build(n
, NULL
, ".target", ret
);
286 return log_error_errno(r
, "[%s:%u] Could not build name for facility %s: %m", s
->path
, line
, name
);
291 /* Strip ".sh" suffix from file name for comparison */
292 filename_no_sh
= strdupa(filename
);
293 e
= endswith(filename_no_sh
, ".sh");
296 filename
= filename_no_sh
;
299 /* Names equaling the file name of the services are redundant */
300 if (streq_ptr(n
, filename
)) {
305 /* Everything else we assume to be normal service names */
306 m
= sysv_translate_name(n
);
314 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
322 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
324 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
326 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
330 r
= sysv_translate_facility(s
, line
, word
, &m
);
331 if (r
<= 0) /* continue on error */
334 switch (unit_name_to_type(m
)) {
337 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
338 r
= add_alias(s
->name
, m
);
340 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
345 /* NB: SysV targets which are provided by a
346 * service are pulled in by the services, as
347 * an indication that the generic service is
348 * now available. This is strictly one-way.
349 * The targets do NOT pull in SysV services! */
351 r
= strv_extend(&s
->before
, m
);
355 r
= strv_extend(&s
->wants
, m
);
359 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
360 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
363 r
= strv_extend(&s
->wants
, SPECIAL_NETWORK_TARGET
);
370 case _UNIT_TYPE_INVALID
:
371 log_warning("Unit name '%s' is invalid", m
);
375 log_warning("Unknown unit type for unit '%s'", m
);
382 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
390 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
393 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
395 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
399 r
= sysv_translate_facility(s
, line
, word
, &m
);
400 if (r
<= 0) /* continue on error */
403 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
405 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
406 /* the network-online target is special, as it needs to be actively pulled in */
407 r
= strv_extend(&s
->after
, m
);
411 r
= strv_extend(&s
->wants
, m
);
413 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
421 static int load_sysv(SysvStub
*s
) {
422 _cleanup_fclose_
FILE *f
;
432 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
434 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
);
449 FOREACH_LINE(l
, f
, goto fail
) {
456 /* Try to figure out whether this init script supports
457 * the reload operation. This heuristic looks for
458 * "Usage" lines which include the reload option. */
459 if ( state
== USAGE_CONTINUATION
||
460 (state
== NORMAL
&& strcasestr(t
, "usage"))) {
461 if (usage_contains_reload(t
)) {
462 supports_reload
= true;
464 } else if (t
[strlen(t
)-1] == '\\')
465 state
= USAGE_CONTINUATION
;
473 if (state
== NORMAL
&& streq(t
, "### BEGIN INIT INFO")) {
479 if (IN_SET(state
, LSB_DESCRIPTION
, LSB
) && streq(t
, "### END INIT INFO")) {
485 t
+= strspn(t
, WHITESPACE
);
487 if (state
== NORMAL
) {
489 /* Try to parse Red Hat style description */
491 if (startswith_no_case(t
, "description:")) {
497 if (k
> 0 && t
[k
-1] == '\\') {
502 j
= empty_to_null(strstrip(t
+12));
504 r
= free_and_strdup(&chkconfig_description
, j
);
508 } else if (startswith_no_case(t
, "pidfile:")) {
514 if (!path_is_absolute(fn
)) {
515 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
519 r
= free_and_strdup(&s
->pid_file
, fn
);
524 } else if (state
== DESCRIPTION
) {
526 /* Try to parse Red Hat style description
533 if (k
> 0 && t
[k
-1] == '\\')
542 if (chkconfig_description
)
543 d
= strjoin(chkconfig_description
, " ", j
);
549 free(chkconfig_description
);
550 chkconfig_description
= d
;
553 } else if (IN_SET(state
, LSB
, LSB_DESCRIPTION
)) {
555 if (startswith_no_case(t
, "Provides:")) {
558 r
= handle_provides(s
, line
, t
, t
+ 9);
562 } else if (startswith_no_case(t
, "Required-Start:") ||
563 startswith_no_case(t
, "Should-Start:") ||
564 startswith_no_case(t
, "X-Start-Before:") ||
565 startswith_no_case(t
, "X-Start-After:")) {
569 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
573 } else if (startswith_no_case(t
, "Description:")) {
576 state
= LSB_DESCRIPTION
;
578 j
= empty_to_null(strstrip(t
+12));
580 r
= free_and_strdup(&long_description
, j
);
584 } else if (startswith_no_case(t
, "Short-Description:")) {
589 j
= empty_to_null(strstrip(t
+18));
591 r
= free_and_strdup(&short_description
, j
);
595 } else if (state
== LSB_DESCRIPTION
) {
597 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
604 if (long_description
)
605 d
= strjoin(long_description
, " ", t
);
611 free(long_description
);
612 long_description
= d
;
621 s
->reload
= supports_reload
;
623 /* We use the long description only if
624 * no short description is set. */
626 if (short_description
)
627 description
= short_description
;
628 else if (chkconfig_description
)
629 description
= chkconfig_description
;
630 else if (long_description
)
631 description
= long_description
;
638 d
= strappend(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
649 return log_error_errno(errno
, "Failed to read configuration file '%s': %m", s
->path
);
652 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
662 if (s
->sysv_start_priority
< 0)
665 HASHMAP_FOREACH(other
, all_services
, j
) {
672 if (other
->sysv_start_priority
< 0)
675 /* If both units have modern headers we don't care
676 * about the priorities */
677 if (s
->has_lsb
&& other
->has_lsb
)
680 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
681 r
= strv_extend(&s
->after
, other
->name
);
685 } else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
686 r
= strv_extend(&s
->before
, other
->name
);
692 /* FIXME: Maybe we should compare the name here lexicographically? */
698 static int acquire_search_path(const char *def
, const char *envvar
, char ***ret
) {
699 _cleanup_strv_free_
char **l
= NULL
;
708 r
= path_split_and_make_absolute(e
, &l
);
710 return log_error_errno(r
, "Failed to make $%s search path absolute: %m", envvar
);
713 if (strv_isempty(l
)) {
716 l
= strv_new(def
, NULL
);
721 if (!path_strv_resolve_uniq(l
, NULL
))
730 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
731 _cleanup_strv_free_
char **sysvinit_path
= NULL
;
737 r
= acquire_search_path(SYSTEM_SYSVINIT_PATH
, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path
);
741 STRV_FOREACH(path
, sysvinit_path
) {
742 _cleanup_closedir_
DIR *d
= NULL
;
748 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", *path
);
752 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", *path
)) {
753 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
754 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
757 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
758 log_warning_errno(errno
, "stat() failed on %s/%s, ignoring: %m", *path
, de
->d_name
);
762 if (!(st
.st_mode
& S_IXUSR
))
765 if (!S_ISREG(st
.st_mode
))
768 name
= sysv_translate_name(de
->d_name
);
772 if (hashmap_contains(all_services
, name
))
775 r
= unit_file_exists(UNIT_FILE_SYSTEM
, lp
, name
);
776 if (r
< 0 && !IN_SET(r
, -ELOOP
, -ERFKILL
, -EADDRNOTAVAIL
)) {
777 log_debug_errno(r
, "Failed to detect whether %s exists, skipping: %m", name
);
780 log_debug("Native unit for %s already exists, skipping.", name
);
784 fpath
= strjoin(*path
, "/", de
->d_name
);
788 service
= new0(SysvStub
, 1);
792 service
->sysv_start_priority
= -1;
793 service
->name
= name
;
794 service
->path
= fpath
;
797 r
= hashmap_put(all_services
, service
->name
, service
);
808 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
809 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
810 _cleanup_strv_free_
char **sysvrcnd_path
= NULL
;
819 r
= acquire_search_path(SYSTEM_SYSVRCND_PATH
, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path
);
823 STRV_FOREACH(p
, sysvrcnd_path
) {
824 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
826 _cleanup_closedir_
DIR *d
= NULL
;
827 _cleanup_free_
char *path
= NULL
;
830 path
= strjoin(*p
, "/", rcnd_table
[i
].path
);
839 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", path
);
844 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", path
)) {
845 _cleanup_free_
char *name
= NULL
, *fpath
= NULL
;
848 if (de
->d_name
[0] != 'S')
851 if (strlen(de
->d_name
) < 4)
854 a
= undecchar(de
->d_name
[1]);
855 b
= undecchar(de
->d_name
[2]);
860 fpath
= strjoin(*p
, "/", de
->d_name
);
866 name
= sysv_translate_name(de
->d_name
+ 3);
872 service
= hashmap_get(all_services
, name
);
874 log_debug("Ignoring %s symlink in %s, not generating %s.", de
->d_name
, rcnd_table
[i
].path
, name
);
878 service
->sysv_start_priority
= MAX(a
*10 + b
, service
->sysv_start_priority
);
880 r
= set_ensure_allocated(&runlevel_services
[i
], NULL
);
886 r
= set_put(runlevel_services
[i
], service
);
895 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
896 SET_FOREACH(service
, runlevel_services
[i
], j
) {
897 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
902 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
912 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
913 set_free(runlevel_services
[i
]);
918 int main(int argc
, char *argv
[]) {
919 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
920 _cleanup_lookup_paths_free_ LookupPaths lp
= {};
925 if (argc
> 1 && argc
!= 4) {
926 log_error("This program takes three or no arguments.");
933 log_set_target(LOG_TARGET_SAFE
);
934 log_parse_environment();
939 r
= lookup_paths_init(&lp
, UNIT_FILE_SYSTEM
, LOOKUP_PATHS_EXCLUDE_GENERATED
, NULL
);
941 log_error_errno(r
, "Failed to find lookup paths: %m");
945 all_services
= hashmap_new(&string_hash_ops
);
951 r
= enumerate_sysv(&lp
, all_services
);
955 r
= set_dependencies_from_rcnd(&lp
, all_services
);
959 HASHMAP_FOREACH(service
, all_services
, j
)
960 (void) load_sysv(service
);
962 HASHMAP_FOREACH(service
, all_services
, j
) {
963 (void) fix_order(service
, all_services
);
964 (void) generate_unit_file(service
);
970 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;