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"
31 #include "hexdecoct.h"
35 #include "path-lookup.h"
36 #include "path-util.h"
39 #include "stat-util.h"
40 #include "string-util.h"
42 #include "unit-name.h"
49 /* Standard SysV runlevels for start-up */
50 { "rc1.d", SPECIAL_RESCUE_TARGET
},
51 { "rc2.d", SPECIAL_MULTI_USER_TARGET
},
52 { "rc3.d", SPECIAL_MULTI_USER_TARGET
},
53 { "rc4.d", SPECIAL_MULTI_USER_TARGET
},
54 { "rc5.d", SPECIAL_GRAPHICAL_TARGET
},
56 /* We ignore the SysV runlevels for shutdown here, as SysV services get default dependencies anyway, and that
57 * means they are shut down anyway at system power off if running. */
60 static const char *arg_dest
= "/tmp";
62 typedef struct SysvStub
{
66 int sysv_start_priority
;
77 static void free_sysvstub(SysvStub
*s
) {
88 strv_free(s
->wanted_by
);
92 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
94 static void free_sysvstub_hashmapp(Hashmap
**h
) {
97 while ((stub
= hashmap_steal_first(*h
)))
103 static int add_symlink(const char *service
, const char *where
) {
104 const char *from
, *to
;
110 from
= strjoina(arg_dest
, "/", service
);
111 to
= strjoina(arg_dest
, "/", where
, ".wants/", service
);
113 mkdir_parents_label(to
, 0755);
115 r
= symlink(from
, to
);
126 static int add_alias(const char *service
, const char *alias
) {
133 link
= strjoina(arg_dest
, "/", alias
);
135 r
= symlink(service
, link
);
146 static int generate_unit_file(SysvStub
*s
) {
147 _cleanup_fclose_
FILE *f
= NULL
;
157 unit
= strjoina(arg_dest
, "/", s
->name
);
159 /* We might already have a symlink with the same name from a Provides:,
160 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
161 * so remove an existing link */
162 if (is_symlink(unit
) > 0) {
163 log_warning("Overwriting existing symlink %s with real service.", unit
);
167 f
= fopen(unit
, "wxe");
169 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
172 "# Automatically generated by systemd-sysv-generator\n\n"
174 "Documentation=man:systemd-sysv-generator(8)\n"
179 fprintf(f
, "Description=%s\n", s
->description
);
181 STRV_FOREACH(p
, s
->before
)
182 fprintf(f
, "Before=%s\n", *p
);
183 STRV_FOREACH(p
, s
->after
)
184 fprintf(f
, "After=%s\n", *p
);
185 STRV_FOREACH(p
, s
->wants
)
186 fprintf(f
, "Wants=%s\n", *p
);
196 "RemainAfterExit=%s\n",
197 yes_no(!s
->pid_file
));
200 fprintf(f
, "PIDFile=%s\n", s
->pid_file
);
203 "ExecStart=%s start\n"
204 "ExecStop=%s stop\n",
208 fprintf(f
, "ExecReload=%s reload\n", s
->path
);
210 r
= fflush_and_check(f
);
212 return log_error_errno(r
, "Failed to write unit %s: %m", unit
);
214 STRV_FOREACH(p
, s
->wanted_by
) {
215 r
= add_symlink(s
->name
, *p
);
217 log_warning_errno(r
, "Failed to create 'Wants' symlink to %s, ignoring: %m", *p
);
223 static bool usage_contains_reload(const char *line
) {
224 return (strcasestr(line
, "{reload|") ||
225 strcasestr(line
, "{reload}") ||
226 strcasestr(line
, "{reload\"") ||
227 strcasestr(line
, "|reload|") ||
228 strcasestr(line
, "|reload}") ||
229 strcasestr(line
, "|reload\""));
232 static char *sysv_translate_name(const char *name
) {
233 _cleanup_free_
char *c
= NULL
;
240 res
= endswith(c
, ".sh");
244 if (unit_name_mangle(c
, UNIT_NAME_NOGLOB
, &res
) < 0)
250 static int sysv_translate_facility(SysvStub
*s
, unsigned line
, const char *name
, char **ret
) {
252 /* We silently ignore the $ prefix here. According to the LSB
253 * spec it simply indicates whether something is a
254 * standardized name or a distribution-specific one. Since we
255 * just follow what already exists and do not introduce new
256 * uses or names we don't care who introduced a new name. */
258 static const char * const table
[] = {
259 /* LSB defined facilities */
261 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
262 "named", SPECIAL_NSS_LOOKUP_TARGET
,
263 "portmap", SPECIAL_RPCBIND_TARGET
,
264 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
266 "time", SPECIAL_TIME_SYNC_TARGET
,
269 const char *filename
;
270 char *filename_no_sh
, *e
, *m
;
279 filename
= basename(s
->path
);
281 n
= *name
== '$' ? name
+ 1 : name
;
283 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
284 if (!streq(table
[i
], n
))
290 m
= strdup(table
[i
+1]);
298 /* If we don't know this name, fallback heuristics to figure
299 * out whether something is a target or a service alias. */
301 /* Facilities starting with $ are most likely targets */
303 r
= unit_name_build(n
, NULL
, ".target", ret
);
305 return log_error_errno(r
, "[%s:%u] Could not build name for facility %s: %m", s
->path
, line
, name
);
310 /* Strip ".sh" suffix from file name for comparison */
311 filename_no_sh
= strdupa(filename
);
312 e
= endswith(filename_no_sh
, ".sh");
315 filename
= filename_no_sh
;
318 /* Names equaling the file name of the services are redundant */
319 if (streq_ptr(n
, filename
))
322 /* Everything else we assume to be normal service names */
323 m
= sysv_translate_name(n
);
331 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
339 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
341 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
343 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
347 r
= sysv_translate_facility(s
, line
, word
, &m
);
348 if (r
<= 0) /* continue on error */
351 switch (unit_name_to_type(m
)) {
354 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
355 r
= add_alias(s
->name
, m
);
357 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
362 /* NB: SysV targets which are provided by a
363 * service are pulled in by the services, as
364 * an indication that the generic service is
365 * now available. This is strictly one-way.
366 * The targets do NOT pull in SysV services! */
368 r
= strv_extend(&s
->before
, m
);
372 r
= strv_extend(&s
->wants
, m
);
376 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
377 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
384 case _UNIT_TYPE_INVALID
:
385 log_warning("Unit name '%s' is invalid", m
);
389 log_warning("Unknown unit type for unit '%s'", m
);
396 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
404 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
407 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
409 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
413 r
= sysv_translate_facility(s
, line
, word
, &m
);
414 if (r
<= 0) /* continue on error */
417 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
419 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
420 /* the network-online target is special, as it needs to be actively pulled in */
421 r
= strv_extend(&s
->after
, m
);
425 r
= strv_extend(&s
->wants
, m
);
427 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
435 static int load_sysv(SysvStub
*s
) {
436 _cleanup_fclose_
FILE *f
;
446 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
448 bool supports_reload
= false;
453 f
= fopen(s
->path
, "re");
458 return log_error_errno(errno
, "Failed to open %s: %m", s
->path
);
461 log_debug("Loading SysV script %s", s
->path
);
463 FOREACH_LINE(l
, f
, goto fail
) {
470 /* Try to figure out whether this init script supports
471 * the reload operation. This heuristic looks for
472 * "Usage" lines which include the reload option. */
473 if ( state
== USAGE_CONTINUATION
||
474 (state
== NORMAL
&& strcasestr(t
, "usage"))) {
475 if (usage_contains_reload(t
)) {
476 supports_reload
= true;
478 } else if (t
[strlen(t
)-1] == '\\')
479 state
= USAGE_CONTINUATION
;
487 if (state
== NORMAL
&& streq(t
, "### BEGIN INIT INFO")) {
493 if ((state
== LSB_DESCRIPTION
|| state
== LSB
) && streq(t
, "### END INIT INFO")) {
499 t
+= strspn(t
, WHITESPACE
);
501 if (state
== NORMAL
) {
503 /* Try to parse Red Hat style description */
505 if (startswith_no_case(t
, "description:")) {
511 if (k
> 0 && t
[k
-1] == '\\') {
516 j
= empty_to_null(strstrip(t
+12));
518 r
= free_and_strdup(&chkconfig_description
, j
);
522 } else if (startswith_no_case(t
, "pidfile:")) {
528 if (!path_is_absolute(fn
)) {
529 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
533 r
= free_and_strdup(&s
->pid_file
, fn
);
538 } else if (state
== DESCRIPTION
) {
540 /* Try to parse Red Hat style description
547 if (k
> 0 && t
[k
-1] == '\\')
556 if (chkconfig_description
)
557 d
= strjoin(chkconfig_description
, " ", j
, NULL
);
563 free(chkconfig_description
);
564 chkconfig_description
= d
;
567 } else if (state
== LSB
|| state
== LSB_DESCRIPTION
) {
569 if (startswith_no_case(t
, "Provides:")) {
572 r
= handle_provides(s
, line
, t
, t
+ 9);
576 } else if (startswith_no_case(t
, "Required-Start:") ||
577 startswith_no_case(t
, "Should-Start:") ||
578 startswith_no_case(t
, "X-Start-Before:") ||
579 startswith_no_case(t
, "X-Start-After:")) {
583 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
587 } else if (startswith_no_case(t
, "Description:")) {
590 state
= LSB_DESCRIPTION
;
592 j
= empty_to_null(strstrip(t
+12));
594 r
= free_and_strdup(&long_description
, j
);
598 } else if (startswith_no_case(t
, "Short-Description:")) {
603 j
= empty_to_null(strstrip(t
+18));
605 r
= free_and_strdup(&short_description
, j
);
609 } else if (state
== LSB_DESCRIPTION
) {
611 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
618 if (long_description
)
619 d
= strjoin(long_description
, " ", t
, NULL
);
625 free(long_description
);
626 long_description
= d
;
635 s
->reload
= supports_reload
;
637 /* We use the long description only if
638 * no short description is set. */
640 if (short_description
)
641 description
= short_description
;
642 else if (chkconfig_description
)
643 description
= chkconfig_description
;
644 else if (long_description
)
645 description
= long_description
;
652 d
= strappend(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
663 return log_error_errno(errno
, "Failed to read configuration file '%s': %m", s
->path
);
666 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
676 if (s
->sysv_start_priority
< 0)
679 HASHMAP_FOREACH(other
, all_services
, j
) {
686 if (other
->sysv_start_priority
< 0)
689 /* If both units have modern headers we don't care
690 * about the priorities */
691 if (s
->has_lsb
&& other
->has_lsb
)
694 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
695 r
= strv_extend(&s
->after
, other
->name
);
699 } else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
700 r
= strv_extend(&s
->before
, other
->name
);
706 /* FIXME: Maybe we should compare the name here lexicographically? */
712 static int acquire_search_path(const char *def
, const char *envvar
, char ***ret
) {
713 _cleanup_strv_free_
char **l
= NULL
;
722 r
= path_split_and_make_absolute(e
, &l
);
724 return log_error_errno(r
, "Failed to make $%s search path absolute: %m", envvar
);
727 if (strv_isempty(l
)) {
730 l
= strv_new(def
, NULL
);
735 if (!path_strv_resolve_uniq(l
, NULL
))
744 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
745 _cleanup_strv_free_
char **sysvinit_path
= NULL
;
751 r
= acquire_search_path(SYSTEM_SYSVINIT_PATH
, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path
);
755 STRV_FOREACH(path
, sysvinit_path
) {
756 _cleanup_closedir_
DIR *d
= NULL
;
762 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", *path
);
766 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", *path
)) {
767 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
768 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
771 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
772 log_warning_errno(errno
, "stat() failed on %s/%s, ignoring: %m", *path
, de
->d_name
);
776 if (!(st
.st_mode
& S_IXUSR
))
779 if (!S_ISREG(st
.st_mode
))
782 name
= sysv_translate_name(de
->d_name
);
786 if (hashmap_contains(all_services
, name
))
789 r
= unit_file_exists(UNIT_FILE_SYSTEM
, lp
, name
);
790 if (r
< 0 && !IN_SET(r
, -ELOOP
, -ERFKILL
, -EADDRNOTAVAIL
)) {
791 log_debug_errno(r
, "Failed to detect whether %s exists, skipping: %m", name
);
794 log_debug("Native unit for %s already exists, skipping.", name
);
798 fpath
= strjoin(*path
, "/", de
->d_name
, NULL
);
802 service
= new0(SysvStub
, 1);
806 service
->sysv_start_priority
= -1;
807 service
->name
= name
;
808 service
->path
= fpath
;
811 r
= hashmap_put(all_services
, service
->name
, service
);
822 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
823 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
824 _cleanup_strv_free_
char **sysvrcnd_path
= NULL
;
833 r
= acquire_search_path(SYSTEM_SYSVRCND_PATH
, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path
);
837 STRV_FOREACH(p
, sysvrcnd_path
) {
838 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
840 _cleanup_closedir_
DIR *d
= NULL
;
841 _cleanup_free_
char *path
= NULL
;
844 path
= strjoin(*p
, "/", rcnd_table
[i
].path
, NULL
);
853 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", path
);
858 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", path
)) {
859 _cleanup_free_
char *name
= NULL
, *fpath
= NULL
;
862 if (de
->d_name
[0] != 'S')
865 if (strlen(de
->d_name
) < 4)
868 a
= undecchar(de
->d_name
[1]);
869 b
= undecchar(de
->d_name
[2]);
874 fpath
= strjoin(*p
, "/", de
->d_name
, NULL
);
880 name
= sysv_translate_name(de
->d_name
+ 3);
886 service
= hashmap_get(all_services
, name
);
888 log_debug("Ignoring %s symlink in %s, not generating %s.", de
->d_name
, rcnd_table
[i
].path
, name
);
892 service
->sysv_start_priority
= MAX(a
*10 + b
, service
->sysv_start_priority
);
894 r
= set_ensure_allocated(&runlevel_services
[i
], NULL
);
900 r
= set_put(runlevel_services
[i
], service
);
909 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
910 SET_FOREACH(service
, runlevel_services
[i
], j
) {
911 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
916 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
926 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
927 set_free(runlevel_services
[i
]);
932 int main(int argc
, char *argv
[]) {
933 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
934 _cleanup_lookup_paths_free_ LookupPaths lp
= {};
939 if (argc
> 1 && argc
!= 4) {
940 log_error("This program takes three or no arguments.");
947 log_set_target(LOG_TARGET_SAFE
);
948 log_parse_environment();
953 r
= lookup_paths_init(&lp
, UNIT_FILE_SYSTEM
, LOOKUP_PATHS_EXCLUDE_GENERATED
, NULL
);
955 log_error_errno(r
, "Failed to find lookup paths: %m");
959 all_services
= hashmap_new(&string_hash_ops
);
965 r
= enumerate_sysv(&lp
, all_services
);
969 r
= set_dependencies_from_rcnd(&lp
, all_services
);
973 HASHMAP_FOREACH(service
, all_services
, j
)
974 (void) load_sysv(service
);
976 HASHMAP_FOREACH(service
, all_services
, j
) {
977 (void) fix_order(service
, all_services
);
978 (void) generate_unit_file(service
);
984 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;