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(const char *name
, const char *filename
, 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 char *filename_no_sh
, *e
, *m
;
278 n
= *name
== '$' ? name
+ 1 : name
;
280 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
281 if (!streq(table
[i
], n
))
287 m
= strdup(table
[i
+1]);
295 /* If we don't know this name, fallback heuristics to figure
296 * out whether something is a target or a service alias. */
298 /* Facilities starting with $ are most likely targets */
300 r
= unit_name_build(n
, NULL
, ".target", ret
);
302 return log_error_errno(r
, "Failed to build name: %m");
307 /* Strip ".sh" suffix from file name for comparison */
308 filename_no_sh
= strdupa(filename
);
309 e
= endswith(filename_no_sh
, ".sh");
312 filename
= filename_no_sh
;
315 /* Names equaling the file name of the services are redundant */
316 if (streq_ptr(n
, filename
))
319 /* Everything else we assume to be normal service names */
320 m
= sysv_translate_name(n
);
328 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
336 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
338 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
340 return log_error_errno(r
, "Failed to parse word from provides string: %m");
344 r
= sysv_translate_facility(word
, basename(s
->path
), &m
);
345 if (r
<= 0) /* continue on error */
348 switch (unit_name_to_type(m
)) {
351 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
352 r
= add_alias(s
->name
, m
);
354 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
359 /* NB: SysV targets which are provided by a
360 * service are pulled in by the services, as
361 * an indication that the generic service is
362 * now available. This is strictly one-way.
363 * The targets do NOT pull in SysV services! */
365 r
= strv_extend(&s
->before
, m
);
369 r
= strv_extend(&s
->wants
, m
);
373 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
374 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
381 case _UNIT_TYPE_INVALID
:
382 log_warning("Unit name '%s' is invalid", m
);
386 log_warning("Unknown unit type for unit '%s'", m
);
393 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
401 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
404 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
406 return log_error_errno(r
, "Failed to parse word from provides string: %m");
410 r
= sysv_translate_facility(word
, basename(s
->path
), &m
);
411 if (r
<= 0) /* continue on error */
414 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
416 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
417 /* the network-online target is special, as it needs to be actively pulled in */
418 r
= strv_extend(&s
->after
, m
);
422 r
= strv_extend(&s
->wants
, m
);
424 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
432 static int load_sysv(SysvStub
*s
) {
433 _cleanup_fclose_
FILE *f
;
443 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
445 bool supports_reload
= false;
450 f
= fopen(s
->path
, "re");
455 return log_error_errno(errno
, "Failed to open %s: %m", s
->path
);
458 log_debug("Loading SysV script %s", s
->path
);
460 FOREACH_LINE(l
, f
, goto fail
) {
467 /* Try to figure out whether this init script supports
468 * the reload operation. This heuristic looks for
469 * "Usage" lines which include the reload option. */
470 if ( state
== USAGE_CONTINUATION
||
471 (state
== NORMAL
&& strcasestr(t
, "usage"))) {
472 if (usage_contains_reload(t
)) {
473 supports_reload
= true;
475 } else if (t
[strlen(t
)-1] == '\\')
476 state
= USAGE_CONTINUATION
;
484 if (state
== NORMAL
&& streq(t
, "### BEGIN INIT INFO")) {
490 if ((state
== LSB_DESCRIPTION
|| state
== LSB
) && streq(t
, "### END INIT INFO")) {
496 t
+= strspn(t
, WHITESPACE
);
498 if (state
== NORMAL
) {
500 /* Try to parse Red Hat style description */
502 if (startswith_no_case(t
, "description:")) {
508 if (k
> 0 && t
[k
-1] == '\\') {
513 j
= empty_to_null(strstrip(t
+12));
515 r
= free_and_strdup(&chkconfig_description
, j
);
519 } else if (startswith_no_case(t
, "pidfile:")) {
525 if (!path_is_absolute(fn
)) {
526 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
530 r
= free_and_strdup(&s
->pid_file
, fn
);
535 } else if (state
== DESCRIPTION
) {
537 /* Try to parse Red Hat style description
544 if (k
> 0 && t
[k
-1] == '\\')
553 if (chkconfig_description
)
554 d
= strjoin(chkconfig_description
, " ", j
, NULL
);
560 free(chkconfig_description
);
561 chkconfig_description
= d
;
564 } else if (state
== LSB
|| state
== LSB_DESCRIPTION
) {
566 if (startswith_no_case(t
, "Provides:")) {
569 r
= handle_provides(s
, line
, t
, t
+ 9);
573 } else if (startswith_no_case(t
, "Required-Start:") ||
574 startswith_no_case(t
, "Should-Start:") ||
575 startswith_no_case(t
, "X-Start-Before:") ||
576 startswith_no_case(t
, "X-Start-After:")) {
580 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
584 } else if (startswith_no_case(t
, "Description:")) {
587 state
= LSB_DESCRIPTION
;
589 j
= empty_to_null(strstrip(t
+12));
591 r
= free_and_strdup(&long_description
, j
);
595 } else if (startswith_no_case(t
, "Short-Description:")) {
600 j
= empty_to_null(strstrip(t
+18));
602 r
= free_and_strdup(&short_description
, j
);
606 } else if (state
== LSB_DESCRIPTION
) {
608 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
615 if (long_description
)
616 d
= strjoin(long_description
, " ", t
, NULL
);
622 free(long_description
);
623 long_description
= d
;
632 s
->reload
= supports_reload
;
634 /* We use the long description only if
635 * no short description is set. */
637 if (short_description
)
638 description
= short_description
;
639 else if (chkconfig_description
)
640 description
= chkconfig_description
;
641 else if (long_description
)
642 description
= long_description
;
649 d
= strappend(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
660 return log_error_errno(errno
, "Failed to read configuration file '%s': %m", s
->path
);
663 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
673 if (s
->sysv_start_priority
< 0)
676 HASHMAP_FOREACH(other
, all_services
, j
) {
683 if (other
->sysv_start_priority
< 0)
686 /* If both units have modern headers we don't care
687 * about the priorities */
688 if (s
->has_lsb
&& other
->has_lsb
)
691 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
692 r
= strv_extend(&s
->after
, other
->name
);
696 } else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
697 r
= strv_extend(&s
->before
, other
->name
);
703 /* FIXME: Maybe we should compare the name here lexicographically? */
709 static int acquire_search_path(const char *def
, const char *envvar
, char ***ret
) {
710 _cleanup_strv_free_
char **l
= NULL
;
719 r
= path_split_and_make_absolute(e
, &l
);
721 return log_error_errno(r
, "Failed to make $%s search path absolute: %m", envvar
);
724 if (strv_isempty(l
)) {
727 l
= strv_new(def
, NULL
);
732 if (!path_strv_resolve_uniq(l
, NULL
))
741 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
742 _cleanup_strv_free_
char **sysvinit_path
= NULL
;
748 r
= acquire_search_path(SYSTEM_SYSVINIT_PATH
, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path
);
752 STRV_FOREACH(path
, sysvinit_path
) {
753 _cleanup_closedir_
DIR *d
= NULL
;
759 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", *path
);
763 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", *path
)) {
764 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
765 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
768 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
769 log_warning_errno(errno
, "stat() failed on %s/%s, ignoring: %m", *path
, de
->d_name
);
773 if (!(st
.st_mode
& S_IXUSR
))
776 if (!S_ISREG(st
.st_mode
))
779 name
= sysv_translate_name(de
->d_name
);
783 if (hashmap_contains(all_services
, name
))
786 r
= unit_file_exists(UNIT_FILE_SYSTEM
, lp
, name
);
787 if (r
< 0 && !IN_SET(r
, -ELOOP
, -ERFKILL
, -EADDRNOTAVAIL
)) {
788 log_debug_errno(r
, "Failed to detect whether %s exists, skipping: %m", name
);
791 log_debug("Native unit for %s already exists, skipping.", name
);
795 fpath
= strjoin(*path
, "/", de
->d_name
, NULL
);
799 service
= new0(SysvStub
, 1);
803 service
->sysv_start_priority
= -1;
804 service
->name
= name
;
805 service
->path
= fpath
;
808 r
= hashmap_put(all_services
, service
->name
, service
);
819 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
820 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
821 _cleanup_strv_free_
char **sysvrcnd_path
= NULL
;
830 r
= acquire_search_path(SYSTEM_SYSVRCND_PATH
, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path
);
834 STRV_FOREACH(p
, sysvrcnd_path
) {
835 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
837 _cleanup_closedir_
DIR *d
= NULL
;
838 _cleanup_free_
char *path
= NULL
;
841 path
= strjoin(*p
, "/", rcnd_table
[i
].path
, NULL
);
850 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", path
);
855 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", path
)) {
856 _cleanup_free_
char *name
= NULL
, *fpath
= NULL
;
859 if (de
->d_name
[0] != 'S')
862 if (strlen(de
->d_name
) < 4)
865 a
= undecchar(de
->d_name
[1]);
866 b
= undecchar(de
->d_name
[2]);
871 fpath
= strjoin(*p
, "/", de
->d_name
, NULL
);
877 name
= sysv_translate_name(de
->d_name
+ 3);
883 service
= hashmap_get(all_services
, name
);
885 log_debug("Ignoring %s symlink in %s, not generating %s.", de
->d_name
, rcnd_table
[i
].path
, name
);
889 service
->sysv_start_priority
= MAX(a
*10 + b
, service
->sysv_start_priority
);
891 r
= set_ensure_allocated(&runlevel_services
[i
], NULL
);
897 r
= set_put(runlevel_services
[i
], service
);
906 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
907 SET_FOREACH(service
, runlevel_services
[i
], j
) {
908 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
913 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
923 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
924 set_free(runlevel_services
[i
]);
929 int main(int argc
, char *argv
[]) {
930 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
931 _cleanup_lookup_paths_free_ LookupPaths lp
= {};
936 if (argc
> 1 && argc
!= 4) {
937 log_error("This program takes three or no arguments.");
944 log_set_target(LOG_TARGET_SAFE
);
945 log_parse_environment();
950 r
= lookup_paths_init(&lp
, UNIT_FILE_SYSTEM
, LOOKUP_PATHS_EXCLUDE_GENERATED
, NULL
);
952 log_error_errno(r
, "Failed to find lookup paths: %m");
956 all_services
= hashmap_new(&string_hash_ops
);
962 r
= enumerate_sysv(&lp
, all_services
);
966 r
= set_dependencies_from_rcnd(&lp
, all_services
);
970 HASHMAP_FOREACH(service
, all_services
, j
)
971 (void) load_sysv(service
);
973 HASHMAP_FOREACH(service
, all_services
, j
) {
974 (void) fix_order(service
, all_services
);
975 (void) generate_unit_file(service
);
981 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;