2 This file is part of systemd.
4 Copyright 2014 Thomas H.P. Andersen
5 Copyright 2010 Lennart Poettering
6 Copyright 2011 Michal Schmidt
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include "alloc-util.h"
27 #include "dirent-util.h"
28 #include "exit-status.h"
32 #include "hexdecoct.h"
36 #include "path-lookup.h"
37 #include "path-util.h"
40 #include "stat-util.h"
41 #include "string-util.h"
43 #include "unit-name.h"
50 /* Standard SysV runlevels for start-up */
51 { "rc1.d", SPECIAL_RESCUE_TARGET
},
52 { "rc2.d", SPECIAL_MULTI_USER_TARGET
},
53 { "rc3.d", SPECIAL_MULTI_USER_TARGET
},
54 { "rc4.d", SPECIAL_MULTI_USER_TARGET
},
55 { "rc5.d", SPECIAL_GRAPHICAL_TARGET
},
57 /* We ignore the SysV runlevels for shutdown here, as SysV services get default dependencies anyway, and that
58 * means they are shut down anyway at system power off if running. */
61 static const char *arg_dest
= "/tmp";
63 typedef struct SysvStub
{
67 int sysv_start_priority
;
78 static void free_sysvstub(SysvStub
*s
) {
89 strv_free(s
->wanted_by
);
93 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
95 static void free_sysvstub_hashmapp(Hashmap
**h
) {
98 while ((stub
= hashmap_steal_first(*h
)))
104 static int add_symlink(const char *service
, const char *where
) {
105 const char *from
, *to
;
111 from
= strjoina(arg_dest
, "/", service
);
112 to
= strjoina(arg_dest
, "/", where
, ".wants/", service
);
114 mkdir_parents_label(to
, 0755);
116 r
= symlink(from
, to
);
127 static int add_alias(const char *service
, const char *alias
) {
134 link
= strjoina(arg_dest
, "/", alias
);
136 r
= symlink(service
, link
);
147 static int generate_unit_file(SysvStub
*s
) {
148 _cleanup_fclose_
FILE *f
= NULL
;
158 unit
= strjoina(arg_dest
, "/", s
->name
);
160 /* We might already have a symlink with the same name from a Provides:,
161 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
162 * so remove an existing link */
163 if (is_symlink(unit
) > 0) {
164 log_warning("Overwriting existing symlink %s with real service.", unit
);
168 f
= fopen(unit
, "wxe");
170 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
173 "# Automatically generated by systemd-sysv-generator\n\n"
175 "Documentation=man:systemd-sysv-generator(8)\n"
180 fprintf(f
, "Description=%s\n", s
->description
);
182 STRV_FOREACH(p
, s
->before
)
183 fprintf(f
, "Before=%s\n", *p
);
184 STRV_FOREACH(p
, s
->after
)
185 fprintf(f
, "After=%s\n", *p
);
186 STRV_FOREACH(p
, s
->wants
)
187 fprintf(f
, "Wants=%s\n", *p
);
197 "RemainAfterExit=%s\n",
198 yes_no(!s
->pid_file
));
201 fprintf(f
, "PIDFile=%s\n", s
->pid_file
);
203 /* Consider two special LSB exit codes a clean exit */
206 "SuccessExitStatus=%i %i\n",
211 "ExecStart=%s start\n"
212 "ExecStop=%s stop\n",
216 fprintf(f
, "ExecReload=%s reload\n", s
->path
);
218 r
= fflush_and_check(f
);
220 return log_error_errno(r
, "Failed to write unit %s: %m", unit
);
222 STRV_FOREACH(p
, s
->wanted_by
) {
223 r
= add_symlink(s
->name
, *p
);
225 log_warning_errno(r
, "Failed to create 'Wants' symlink to %s, ignoring: %m", *p
);
231 static bool usage_contains_reload(const char *line
) {
232 return (strcasestr(line
, "{reload|") ||
233 strcasestr(line
, "{reload}") ||
234 strcasestr(line
, "{reload\"") ||
235 strcasestr(line
, "|reload|") ||
236 strcasestr(line
, "|reload}") ||
237 strcasestr(line
, "|reload\""));
240 static char *sysv_translate_name(const char *name
) {
241 _cleanup_free_
char *c
= NULL
;
248 res
= endswith(c
, ".sh");
252 if (unit_name_mangle(c
, UNIT_NAME_NOGLOB
, &res
) < 0)
258 static int sysv_translate_facility(SysvStub
*s
, unsigned line
, const char *name
, char **ret
) {
260 /* We silently ignore the $ prefix here. According to the LSB
261 * spec it simply indicates whether something is a
262 * standardized name or a distribution-specific one. Since we
263 * just follow what already exists and do not introduce new
264 * uses or names we don't care who introduced a new name. */
266 static const char * const table
[] = {
267 /* LSB defined facilities */
269 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
270 "named", SPECIAL_NSS_LOOKUP_TARGET
,
271 "portmap", SPECIAL_RPCBIND_TARGET
,
272 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
274 "time", SPECIAL_TIME_SYNC_TARGET
,
277 const char *filename
;
278 char *filename_no_sh
, *e
, *m
;
287 filename
= basename(s
->path
);
289 n
= *name
== '$' ? name
+ 1 : name
;
291 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
292 if (!streq(table
[i
], n
))
300 m
= strdup(table
[i
+1]);
308 /* If we don't know this name, fallback heuristics to figure
309 * out whether something is a target or a service alias. */
311 /* Facilities starting with $ are most likely targets */
313 r
= unit_name_build(n
, NULL
, ".target", ret
);
315 return log_error_errno(r
, "[%s:%u] Could not build name for facility %s: %m", s
->path
, line
, name
);
320 /* Strip ".sh" suffix from file name for comparison */
321 filename_no_sh
= strdupa(filename
);
322 e
= endswith(filename_no_sh
, ".sh");
325 filename
= filename_no_sh
;
328 /* Names equaling the file name of the services are redundant */
329 if (streq_ptr(n
, filename
)) {
334 /* Everything else we assume to be normal service names */
335 m
= sysv_translate_name(n
);
343 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
351 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
353 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
355 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
359 r
= sysv_translate_facility(s
, line
, word
, &m
);
360 if (r
<= 0) /* continue on error */
363 switch (unit_name_to_type(m
)) {
366 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
367 r
= add_alias(s
->name
, m
);
369 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
374 /* NB: SysV targets which are provided by a
375 * service are pulled in by the services, as
376 * an indication that the generic service is
377 * now available. This is strictly one-way.
378 * The targets do NOT pull in SysV services! */
380 r
= strv_extend(&s
->before
, m
);
384 r
= strv_extend(&s
->wants
, m
);
388 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
389 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
392 r
= strv_extend(&s
->wants
, SPECIAL_NETWORK_TARGET
);
399 case _UNIT_TYPE_INVALID
:
400 log_warning("Unit name '%s' is invalid", m
);
404 log_warning("Unknown unit type for unit '%s'", m
);
411 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
419 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
422 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
424 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
428 r
= sysv_translate_facility(s
, line
, word
, &m
);
429 if (r
<= 0) /* continue on error */
432 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
434 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
435 /* the network-online target is special, as it needs to be actively pulled in */
436 r
= strv_extend(&s
->after
, m
);
440 r
= strv_extend(&s
->wants
, m
);
442 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
450 static int load_sysv(SysvStub
*s
) {
451 _cleanup_fclose_
FILE *f
;
461 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
463 bool supports_reload
= false;
468 f
= fopen(s
->path
, "re");
473 return log_error_errno(errno
, "Failed to open %s: %m", s
->path
);
476 log_debug("Loading SysV script %s", s
->path
);
478 FOREACH_LINE(l
, f
, goto fail
) {
485 /* Try to figure out whether this init script supports
486 * the reload operation. This heuristic looks for
487 * "Usage" lines which include the reload option. */
488 if ( state
== USAGE_CONTINUATION
||
489 (state
== NORMAL
&& strcasestr(t
, "usage"))) {
490 if (usage_contains_reload(t
)) {
491 supports_reload
= true;
493 } else if (t
[strlen(t
)-1] == '\\')
494 state
= USAGE_CONTINUATION
;
502 if (state
== NORMAL
&& streq(t
, "### BEGIN INIT INFO")) {
508 if ((state
== LSB_DESCRIPTION
|| state
== LSB
) && streq(t
, "### END INIT INFO")) {
514 t
+= strspn(t
, WHITESPACE
);
516 if (state
== NORMAL
) {
518 /* Try to parse Red Hat style description */
520 if (startswith_no_case(t
, "description:")) {
526 if (k
> 0 && t
[k
-1] == '\\') {
531 j
= empty_to_null(strstrip(t
+12));
533 r
= free_and_strdup(&chkconfig_description
, j
);
537 } else if (startswith_no_case(t
, "pidfile:")) {
543 if (!path_is_absolute(fn
)) {
544 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
548 r
= free_and_strdup(&s
->pid_file
, fn
);
553 } else if (state
== DESCRIPTION
) {
555 /* Try to parse Red Hat style description
562 if (k
> 0 && t
[k
-1] == '\\')
571 if (chkconfig_description
)
572 d
= strjoin(chkconfig_description
, " ", j
);
578 free(chkconfig_description
);
579 chkconfig_description
= d
;
582 } else if (state
== LSB
|| state
== LSB_DESCRIPTION
) {
584 if (startswith_no_case(t
, "Provides:")) {
587 r
= handle_provides(s
, line
, t
, t
+ 9);
591 } else if (startswith_no_case(t
, "Required-Start:") ||
592 startswith_no_case(t
, "Should-Start:") ||
593 startswith_no_case(t
, "X-Start-Before:") ||
594 startswith_no_case(t
, "X-Start-After:")) {
598 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
602 } else if (startswith_no_case(t
, "Description:")) {
605 state
= LSB_DESCRIPTION
;
607 j
= empty_to_null(strstrip(t
+12));
609 r
= free_and_strdup(&long_description
, j
);
613 } else if (startswith_no_case(t
, "Short-Description:")) {
618 j
= empty_to_null(strstrip(t
+18));
620 r
= free_and_strdup(&short_description
, j
);
624 } else if (state
== LSB_DESCRIPTION
) {
626 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
633 if (long_description
)
634 d
= strjoin(long_description
, " ", t
);
640 free(long_description
);
641 long_description
= d
;
650 s
->reload
= supports_reload
;
652 /* We use the long description only if
653 * no short description is set. */
655 if (short_description
)
656 description
= short_description
;
657 else if (chkconfig_description
)
658 description
= chkconfig_description
;
659 else if (long_description
)
660 description
= long_description
;
667 d
= strappend(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
678 return log_error_errno(errno
, "Failed to read configuration file '%s': %m", s
->path
);
681 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
691 if (s
->sysv_start_priority
< 0)
694 HASHMAP_FOREACH(other
, all_services
, j
) {
701 if (other
->sysv_start_priority
< 0)
704 /* If both units have modern headers we don't care
705 * about the priorities */
706 if (s
->has_lsb
&& other
->has_lsb
)
709 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
710 r
= strv_extend(&s
->after
, other
->name
);
714 } else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
715 r
= strv_extend(&s
->before
, other
->name
);
721 /* FIXME: Maybe we should compare the name here lexicographically? */
727 static int acquire_search_path(const char *def
, const char *envvar
, char ***ret
) {
728 _cleanup_strv_free_
char **l
= NULL
;
737 r
= path_split_and_make_absolute(e
, &l
);
739 return log_error_errno(r
, "Failed to make $%s search path absolute: %m", envvar
);
742 if (strv_isempty(l
)) {
745 l
= strv_new(def
, NULL
);
750 if (!path_strv_resolve_uniq(l
, NULL
))
759 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
760 _cleanup_strv_free_
char **sysvinit_path
= NULL
;
766 r
= acquire_search_path(SYSTEM_SYSVINIT_PATH
, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path
);
770 STRV_FOREACH(path
, sysvinit_path
) {
771 _cleanup_closedir_
DIR *d
= NULL
;
777 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", *path
);
781 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", *path
)) {
782 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
783 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
786 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
787 log_warning_errno(errno
, "stat() failed on %s/%s, ignoring: %m", *path
, de
->d_name
);
791 if (!(st
.st_mode
& S_IXUSR
))
794 if (!S_ISREG(st
.st_mode
))
797 name
= sysv_translate_name(de
->d_name
);
801 if (hashmap_contains(all_services
, name
))
804 r
= unit_file_exists(UNIT_FILE_SYSTEM
, lp
, name
);
805 if (r
< 0 && !IN_SET(r
, -ELOOP
, -ERFKILL
, -EADDRNOTAVAIL
)) {
806 log_debug_errno(r
, "Failed to detect whether %s exists, skipping: %m", name
);
809 log_debug("Native unit for %s already exists, skipping.", name
);
813 fpath
= strjoin(*path
, "/", de
->d_name
);
817 service
= new0(SysvStub
, 1);
821 service
->sysv_start_priority
= -1;
822 service
->name
= name
;
823 service
->path
= fpath
;
826 r
= hashmap_put(all_services
, service
->name
, service
);
837 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
838 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
839 _cleanup_strv_free_
char **sysvrcnd_path
= NULL
;
848 r
= acquire_search_path(SYSTEM_SYSVRCND_PATH
, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path
);
852 STRV_FOREACH(p
, sysvrcnd_path
) {
853 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
855 _cleanup_closedir_
DIR *d
= NULL
;
856 _cleanup_free_
char *path
= NULL
;
859 path
= strjoin(*p
, "/", rcnd_table
[i
].path
);
868 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", path
);
873 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", path
)) {
874 _cleanup_free_
char *name
= NULL
, *fpath
= NULL
;
877 if (de
->d_name
[0] != 'S')
880 if (strlen(de
->d_name
) < 4)
883 a
= undecchar(de
->d_name
[1]);
884 b
= undecchar(de
->d_name
[2]);
889 fpath
= strjoin(*p
, "/", de
->d_name
);
895 name
= sysv_translate_name(de
->d_name
+ 3);
901 service
= hashmap_get(all_services
, name
);
903 log_debug("Ignoring %s symlink in %s, not generating %s.", de
->d_name
, rcnd_table
[i
].path
, name
);
907 service
->sysv_start_priority
= MAX(a
*10 + b
, service
->sysv_start_priority
);
909 r
= set_ensure_allocated(&runlevel_services
[i
], NULL
);
915 r
= set_put(runlevel_services
[i
], service
);
924 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
925 SET_FOREACH(service
, runlevel_services
[i
], j
) {
926 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
931 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
941 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
942 set_free(runlevel_services
[i
]);
947 int main(int argc
, char *argv
[]) {
948 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
949 _cleanup_lookup_paths_free_ LookupPaths lp
= {};
954 if (argc
> 1 && argc
!= 4) {
955 log_error("This program takes three or no arguments.");
962 log_set_target(LOG_TARGET_SAFE
);
963 log_parse_environment();
968 r
= lookup_paths_init(&lp
, UNIT_FILE_SYSTEM
, LOOKUP_PATHS_EXCLUDE_GENERATED
, NULL
);
970 log_error_errno(r
, "Failed to find lookup paths: %m");
974 all_services
= hashmap_new(&string_hash_ops
);
980 r
= enumerate_sysv(&lp
, all_services
);
984 r
= set_dependencies_from_rcnd(&lp
, all_services
);
988 HASHMAP_FOREACH(service
, all_services
, j
)
989 (void) load_sysv(service
);
991 HASHMAP_FOREACH(service
, all_services
, j
) {
992 (void) fix_order(service
, all_services
);
993 (void) generate_unit_file(service
);
999 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;