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"
31 #include "generator.h"
33 #include "hexdecoct.h"
37 #include "path-lookup.h"
38 #include "path-util.h"
41 #include "stat-util.h"
42 #include "string-util.h"
44 #include "unit-name.h"
51 /* Standard SysV runlevels for start-up */
52 { "rc1.d", SPECIAL_RESCUE_TARGET
},
53 { "rc2.d", SPECIAL_MULTI_USER_TARGET
},
54 { "rc3.d", SPECIAL_MULTI_USER_TARGET
},
55 { "rc4.d", SPECIAL_MULTI_USER_TARGET
},
56 { "rc5.d", SPECIAL_GRAPHICAL_TARGET
},
58 /* We ignore the SysV runlevels for shutdown here, as SysV services get default dependencies anyway, and that
59 * means they are shut down anyway at system power off if running. */
62 static const char *arg_dest
= "/tmp";
64 typedef struct SysvStub
{
68 int sysv_start_priority
;
79 static void free_sysvstub(SysvStub
*s
) {
90 strv_free(s
->wanted_by
);
94 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
96 static void free_sysvstub_hashmapp(Hashmap
**h
) {
99 while ((stub
= hashmap_steal_first(*h
)))
105 static int add_alias(const char *service
, const char *alias
) {
112 link
= strjoina(arg_dest
, "/", alias
);
114 r
= symlink(service
, link
);
125 static int generate_unit_file(SysvStub
*s
) {
126 _cleanup_fclose_
FILE *f
= NULL
;
136 unit
= strjoina(arg_dest
, "/", s
->name
);
138 /* We might already have a symlink with the same name from a Provides:,
139 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
140 * so remove an existing link */
141 if (is_symlink(unit
) > 0) {
142 log_warning("Overwriting existing symlink %s with real service.", unit
);
146 f
= fopen(unit
, "wxe");
148 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
151 "# Automatically generated by systemd-sysv-generator\n\n"
153 "Documentation=man:systemd-sysv-generator(8)\n"
158 fprintf(f
, "Description=%s\n", s
->description
);
160 STRV_FOREACH(p
, s
->before
)
161 fprintf(f
, "Before=%s\n", *p
);
162 STRV_FOREACH(p
, s
->after
)
163 fprintf(f
, "After=%s\n", *p
);
164 STRV_FOREACH(p
, s
->wants
)
165 fprintf(f
, "Wants=%s\n", *p
);
175 "RemainAfterExit=%s\n",
176 yes_no(!s
->pid_file
));
179 fprintf(f
, "PIDFile=%s\n", s
->pid_file
);
181 /* Consider two special LSB exit codes a clean exit */
184 "SuccessExitStatus=%i %i\n",
189 "ExecStart=%s start\n"
190 "ExecStop=%s stop\n",
194 fprintf(f
, "ExecReload=%s reload\n", s
->path
);
196 r
= fflush_and_check(f
);
198 return log_error_errno(r
, "Failed to write unit %s: %m", unit
);
200 STRV_FOREACH(p
, s
->wanted_by
)
201 (void) generator_add_symlink(arg_dest
, *p
, "wants", s
->name
);
206 static bool usage_contains_reload(const char *line
) {
207 return (strcasestr(line
, "{reload|") ||
208 strcasestr(line
, "{reload}") ||
209 strcasestr(line
, "{reload\"") ||
210 strcasestr(line
, "|reload|") ||
211 strcasestr(line
, "|reload}") ||
212 strcasestr(line
, "|reload\""));
215 static char *sysv_translate_name(const char *name
) {
216 _cleanup_free_
char *c
= NULL
;
223 res
= endswith(c
, ".sh");
227 if (unit_name_mangle(c
, UNIT_NAME_NOGLOB
, &res
) < 0)
233 static int sysv_translate_facility(SysvStub
*s
, unsigned line
, const char *name
, char **ret
) {
235 /* We silently ignore the $ prefix here. According to the LSB
236 * spec it simply indicates whether something is a
237 * standardized name or a distribution-specific one. Since we
238 * just follow what already exists and do not introduce new
239 * uses or names we don't care who introduced a new name. */
241 static const char * const table
[] = {
242 /* LSB defined facilities */
244 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
245 "named", SPECIAL_NSS_LOOKUP_TARGET
,
246 "portmap", SPECIAL_RPCBIND_TARGET
,
247 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
249 "time", SPECIAL_TIME_SYNC_TARGET
,
252 const char *filename
;
253 char *filename_no_sh
, *e
, *m
;
262 filename
= basename(s
->path
);
264 n
= *name
== '$' ? name
+ 1 : name
;
266 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
267 if (!streq(table
[i
], n
))
275 m
= strdup(table
[i
+1]);
283 /* If we don't know this name, fallback heuristics to figure
284 * out whether something is a target or a service alias. */
286 /* Facilities starting with $ are most likely targets */
288 r
= unit_name_build(n
, NULL
, ".target", ret
);
290 return log_error_errno(r
, "[%s:%u] Could not build name for facility %s: %m", s
->path
, line
, name
);
295 /* Strip ".sh" suffix from file name for comparison */
296 filename_no_sh
= strdupa(filename
);
297 e
= endswith(filename_no_sh
, ".sh");
300 filename
= filename_no_sh
;
303 /* Names equaling the file name of the services are redundant */
304 if (streq_ptr(n
, filename
)) {
309 /* Everything else we assume to be normal service names */
310 m
= sysv_translate_name(n
);
318 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
326 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
328 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
330 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
334 r
= sysv_translate_facility(s
, line
, word
, &m
);
335 if (r
<= 0) /* continue on error */
338 switch (unit_name_to_type(m
)) {
341 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
342 r
= add_alias(s
->name
, m
);
344 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
349 /* NB: SysV targets which are provided by a
350 * service are pulled in by the services, as
351 * an indication that the generic service is
352 * now available. This is strictly one-way.
353 * The targets do NOT pull in SysV services! */
355 r
= strv_extend(&s
->before
, m
);
359 r
= strv_extend(&s
->wants
, m
);
363 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
364 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
367 r
= strv_extend(&s
->wants
, SPECIAL_NETWORK_TARGET
);
374 case _UNIT_TYPE_INVALID
:
375 log_warning("Unit name '%s' is invalid", m
);
379 log_warning("Unknown unit type for unit '%s'", m
);
386 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
394 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
397 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
399 return log_error_errno(r
, "[%s:%u] Failed to parse word from provides string: %m", s
->path
, line
);
403 r
= sysv_translate_facility(s
, line
, word
, &m
);
404 if (r
<= 0) /* continue on error */
407 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
409 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
410 /* the network-online target is special, as it needs to be actively pulled in */
411 r
= strv_extend(&s
->after
, m
);
415 r
= strv_extend(&s
->wants
, m
);
417 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
425 static int load_sysv(SysvStub
*s
) {
426 _cleanup_fclose_
FILE *f
;
436 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
438 bool supports_reload
= false;
443 f
= fopen(s
->path
, "re");
448 return log_error_errno(errno
, "Failed to open %s: %m", s
->path
);
451 log_debug("Loading SysV script %s", s
->path
);
453 FOREACH_LINE(l
, f
, goto fail
) {
460 /* Try to figure out whether this init script supports
461 * the reload operation. This heuristic looks for
462 * "Usage" lines which include the reload option. */
463 if ( state
== USAGE_CONTINUATION
||
464 (state
== NORMAL
&& strcasestr(t
, "usage"))) {
465 if (usage_contains_reload(t
)) {
466 supports_reload
= true;
468 } else if (t
[strlen(t
)-1] == '\\')
469 state
= USAGE_CONTINUATION
;
477 if (state
== NORMAL
&& streq(t
, "### BEGIN INIT INFO")) {
483 if (IN_SET(state
, LSB_DESCRIPTION
, LSB
) && streq(t
, "### END INIT INFO")) {
489 t
+= strspn(t
, WHITESPACE
);
491 if (state
== NORMAL
) {
493 /* Try to parse Red Hat style description */
495 if (startswith_no_case(t
, "description:")) {
501 if (k
> 0 && t
[k
-1] == '\\') {
506 j
= empty_to_null(strstrip(t
+12));
508 r
= free_and_strdup(&chkconfig_description
, j
);
512 } else if (startswith_no_case(t
, "pidfile:")) {
518 if (!path_is_absolute(fn
)) {
519 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
523 r
= free_and_strdup(&s
->pid_file
, fn
);
528 } else if (state
== DESCRIPTION
) {
530 /* Try to parse Red Hat style description
537 if (k
> 0 && t
[k
-1] == '\\')
546 if (chkconfig_description
)
547 d
= strjoin(chkconfig_description
, " ", j
);
553 free(chkconfig_description
);
554 chkconfig_description
= d
;
557 } else if (IN_SET(state
, LSB
, LSB_DESCRIPTION
)) {
559 if (startswith_no_case(t
, "Provides:")) {
562 r
= handle_provides(s
, line
, t
, t
+ 9);
566 } else if (startswith_no_case(t
, "Required-Start:") ||
567 startswith_no_case(t
, "Should-Start:") ||
568 startswith_no_case(t
, "X-Start-Before:") ||
569 startswith_no_case(t
, "X-Start-After:")) {
573 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
577 } else if (startswith_no_case(t
, "Description:")) {
580 state
= LSB_DESCRIPTION
;
582 j
= empty_to_null(strstrip(t
+12));
584 r
= free_and_strdup(&long_description
, j
);
588 } else if (startswith_no_case(t
, "Short-Description:")) {
593 j
= empty_to_null(strstrip(t
+18));
595 r
= free_and_strdup(&short_description
, j
);
599 } else if (state
== LSB_DESCRIPTION
) {
601 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
608 if (long_description
)
609 d
= strjoin(long_description
, " ", t
);
615 free(long_description
);
616 long_description
= d
;
625 s
->reload
= supports_reload
;
627 /* We use the long description only if
628 * no short description is set. */
630 if (short_description
)
631 description
= short_description
;
632 else if (chkconfig_description
)
633 description
= chkconfig_description
;
634 else if (long_description
)
635 description
= long_description
;
642 d
= strappend(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
653 return log_error_errno(errno
, "Failed to read configuration file '%s': %m", s
->path
);
656 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
666 if (s
->sysv_start_priority
< 0)
669 HASHMAP_FOREACH(other
, all_services
, j
) {
676 if (other
->sysv_start_priority
< 0)
679 /* If both units have modern headers we don't care
680 * about the priorities */
681 if (s
->has_lsb
&& other
->has_lsb
)
684 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
685 r
= strv_extend(&s
->after
, other
->name
);
689 } else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
690 r
= strv_extend(&s
->before
, other
->name
);
696 /* FIXME: Maybe we should compare the name here lexicographically? */
702 static int acquire_search_path(const char *def
, const char *envvar
, char ***ret
) {
703 _cleanup_strv_free_
char **l
= NULL
;
712 r
= path_split_and_make_absolute(e
, &l
);
714 return log_error_errno(r
, "Failed to make $%s search path absolute: %m", envvar
);
717 if (strv_isempty(l
)) {
720 l
= strv_new(def
, NULL
);
725 if (!path_strv_resolve_uniq(l
, NULL
))
734 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
735 _cleanup_strv_free_
char **sysvinit_path
= NULL
;
741 r
= acquire_search_path(SYSTEM_SYSVINIT_PATH
, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path
);
745 STRV_FOREACH(path
, sysvinit_path
) {
746 _cleanup_closedir_
DIR *d
= NULL
;
752 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", *path
);
756 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", *path
)) {
757 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
758 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
761 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
762 log_warning_errno(errno
, "stat() failed on %s/%s, ignoring: %m", *path
, de
->d_name
);
766 if (!(st
.st_mode
& S_IXUSR
))
769 if (!S_ISREG(st
.st_mode
))
772 name
= sysv_translate_name(de
->d_name
);
776 if (hashmap_contains(all_services
, name
))
779 r
= unit_file_exists(UNIT_FILE_SYSTEM
, lp
, name
);
780 if (r
< 0 && !IN_SET(r
, -ELOOP
, -ERFKILL
, -EADDRNOTAVAIL
)) {
781 log_debug_errno(r
, "Failed to detect whether %s exists, skipping: %m", name
);
784 log_debug("Native unit for %s already exists, skipping.", name
);
788 fpath
= strjoin(*path
, "/", de
->d_name
);
792 service
= new0(SysvStub
, 1);
796 service
->sysv_start_priority
= -1;
797 service
->name
= name
;
798 service
->path
= fpath
;
801 r
= hashmap_put(all_services
, service
->name
, service
);
812 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
813 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
814 _cleanup_strv_free_
char **sysvrcnd_path
= NULL
;
823 r
= acquire_search_path(SYSTEM_SYSVRCND_PATH
, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path
);
827 STRV_FOREACH(p
, sysvrcnd_path
) {
828 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
830 _cleanup_closedir_
DIR *d
= NULL
;
831 _cleanup_free_
char *path
= NULL
;
834 path
= strjoin(*p
, "/", rcnd_table
[i
].path
);
843 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", path
);
848 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", path
)) {
849 _cleanup_free_
char *name
= NULL
, *fpath
= NULL
;
852 if (de
->d_name
[0] != 'S')
855 if (strlen(de
->d_name
) < 4)
858 a
= undecchar(de
->d_name
[1]);
859 b
= undecchar(de
->d_name
[2]);
864 fpath
= strjoin(*p
, "/", de
->d_name
);
870 name
= sysv_translate_name(de
->d_name
+ 3);
876 service
= hashmap_get(all_services
, name
);
878 log_debug("Ignoring %s symlink in %s, not generating %s.", de
->d_name
, rcnd_table
[i
].path
, name
);
882 service
->sysv_start_priority
= MAX(a
*10 + b
, service
->sysv_start_priority
);
884 r
= set_ensure_allocated(&runlevel_services
[i
], NULL
);
890 r
= set_put(runlevel_services
[i
], service
);
899 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
900 SET_FOREACH(service
, runlevel_services
[i
], j
) {
901 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
906 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
916 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
917 set_free(runlevel_services
[i
]);
922 int main(int argc
, char *argv
[]) {
923 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
924 _cleanup_lookup_paths_free_ LookupPaths lp
= {};
929 if (argc
> 1 && argc
!= 4) {
930 log_error("This program takes three or no arguments.");
937 log_set_target(LOG_TARGET_SAFE
);
938 log_parse_environment();
943 r
= lookup_paths_init(&lp
, UNIT_FILE_SYSTEM
, LOOKUP_PATHS_EXCLUDE_GENERATED
, NULL
);
945 log_error_errno(r
, "Failed to find lookup paths: %m");
949 all_services
= hashmap_new(&string_hash_ops
);
955 r
= enumerate_sysv(&lp
, all_services
);
959 r
= set_dependencies_from_rcnd(&lp
, all_services
);
963 HASHMAP_FOREACH(service
, all_services
, j
)
964 (void) load_sysv(service
);
966 HASHMAP_FOREACH(service
, all_services
, j
) {
967 (void) fix_order(service
, all_services
);
968 (void) generate_unit_file(service
);
974 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;