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"
45 typedef enum RunlevelType
{
53 const RunlevelType type
;
55 /* Standard SysV runlevels for start-up */
56 { "rc1.d", SPECIAL_RESCUE_TARGET
, RUNLEVEL_UP
},
57 { "rc2.d", SPECIAL_MULTI_USER_TARGET
, RUNLEVEL_UP
},
58 { "rc3.d", SPECIAL_MULTI_USER_TARGET
, RUNLEVEL_UP
},
59 { "rc4.d", SPECIAL_MULTI_USER_TARGET
, RUNLEVEL_UP
},
60 { "rc5.d", SPECIAL_GRAPHICAL_TARGET
, RUNLEVEL_UP
},
62 /* Standard SysV runlevels for shutdown */
63 { "rc0.d", SPECIAL_POWEROFF_TARGET
, RUNLEVEL_DOWN
},
64 { "rc6.d", SPECIAL_REBOOT_TARGET
, RUNLEVEL_DOWN
}
66 /* Note that the order here matters, as we read the
67 directories in this order, and we want to make sure that
68 sysv_start_priority is known when we first load the
69 unit. And that value we only know from S links. Hence
70 UP must be read before DOWN */
73 static const char *arg_dest
= "/tmp";
75 typedef struct SysvStub
{
79 int sysv_start_priority
;
91 static void free_sysvstub(SysvStub
*s
) {
102 strv_free(s
->wanted_by
);
103 strv_free(s
->conflicts
);
107 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
109 static void free_sysvstub_hashmapp(Hashmap
**h
) {
112 while ((stub
= hashmap_steal_first(*h
)))
118 static int add_symlink(const char *service
, const char *where
) {
119 const char *from
, *to
;
125 from
= strjoina(arg_dest
, "/", service
);
126 to
= strjoina(arg_dest
, "/", where
, ".wants/", service
);
128 mkdir_parents_label(to
, 0755);
130 r
= symlink(from
, to
);
141 static int add_alias(const char *service
, const char *alias
) {
148 link
= strjoina(arg_dest
, "/", alias
);
150 r
= symlink(service
, link
);
161 static int generate_unit_file(SysvStub
*s
) {
162 _cleanup_fclose_
FILE *f
= NULL
;
172 unit
= strjoina(arg_dest
, "/", s
->name
);
174 /* We might already have a symlink with the same name from a Provides:,
175 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
176 * so remove an existing link */
177 if (is_symlink(unit
) > 0) {
178 log_warning("Overwriting existing symlink %s with real service.", unit
);
182 f
= fopen(unit
, "wxe");
184 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
187 "# Automatically generated by systemd-sysv-generator\n\n"
189 "Documentation=man:systemd-sysv-generator(8)\n"
194 fprintf(f
, "Description=%s\n", s
->description
);
196 STRV_FOREACH(p
, s
->before
)
197 fprintf(f
, "Before=%s\n", *p
);
198 STRV_FOREACH(p
, s
->after
)
199 fprintf(f
, "After=%s\n", *p
);
200 STRV_FOREACH(p
, s
->wants
)
201 fprintf(f
, "Wants=%s\n", *p
);
202 STRV_FOREACH(p
, s
->conflicts
)
203 fprintf(f
, "Conflicts=%s\n", *p
);
213 "RemainAfterExit=%s\n",
214 yes_no(!s
->pid_file
));
217 fprintf(f
, "PIDFile=%s\n", s
->pid_file
);
220 "ExecStart=%s start\n"
221 "ExecStop=%s stop\n",
225 fprintf(f
, "ExecReload=%s reload\n", s
->path
);
227 r
= fflush_and_check(f
);
229 return log_error_errno(r
, "Failed to write unit %s: %m", unit
);
231 STRV_FOREACH(p
, s
->wanted_by
) {
232 r
= add_symlink(s
->name
, *p
);
234 log_warning_errno(r
, "Failed to create 'Wants' symlink to %s, ignoring: %m", *p
);
240 static bool usage_contains_reload(const char *line
) {
241 return (strcasestr(line
, "{reload|") ||
242 strcasestr(line
, "{reload}") ||
243 strcasestr(line
, "{reload\"") ||
244 strcasestr(line
, "|reload|") ||
245 strcasestr(line
, "|reload}") ||
246 strcasestr(line
, "|reload\""));
249 static char *sysv_translate_name(const char *name
) {
250 _cleanup_free_
char *c
= NULL
;
257 res
= endswith(c
, ".sh");
261 if (unit_name_mangle(c
, UNIT_NAME_NOGLOB
, &res
) < 0)
267 static int sysv_translate_facility(const char *name
, const char *filename
, char **ret
) {
269 /* We silently ignore the $ prefix here. According to the LSB
270 * spec it simply indicates whether something is a
271 * standardized name or a distribution-specific one. Since we
272 * just follow what already exists and do not introduce new
273 * uses or names we don't care who introduced a new name. */
275 static const char * const table
[] = {
276 /* LSB defined facilities */
278 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
279 "named", SPECIAL_NSS_LOOKUP_TARGET
,
280 "portmap", SPECIAL_RPCBIND_TARGET
,
281 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
283 "time", SPECIAL_TIME_SYNC_TARGET
,
286 char *filename_no_sh
, *e
, *m
;
295 n
= *name
== '$' ? name
+ 1 : name
;
297 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
298 if (!streq(table
[i
], n
))
304 m
= strdup(table
[i
+1]);
312 /* If we don't know this name, fallback heuristics to figure
313 * out whether something is a target or a service alias. */
315 /* Facilities starting with $ are most likely targets */
317 r
= unit_name_build(n
, NULL
, ".target", ret
);
319 return log_error_errno(r
, "Failed to build name: %m");
324 /* Strip ".sh" suffix from file name for comparison */
325 filename_no_sh
= strdupa(filename
);
326 e
= endswith(filename_no_sh
, ".sh");
329 filename
= filename_no_sh
;
332 /* Names equaling the file name of the services are redundant */
333 if (streq_ptr(n
, filename
))
336 /* Everything else we assume to be normal service names */
337 m
= sysv_translate_name(n
);
345 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
353 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
355 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
357 return log_error_errno(r
, "Failed to parse word from provides string: %m");
361 r
= sysv_translate_facility(word
, basename(s
->path
), &m
);
362 if (r
<= 0) /* continue on error */
365 switch (unit_name_to_type(m
)) {
368 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
369 r
= add_alias(s
->name
, m
);
371 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
376 /* NB: SysV targets which are provided by a
377 * service are pulled in by the services, as
378 * an indication that the generic service is
379 * now available. This is strictly one-way.
380 * The targets do NOT pull in SysV services! */
382 r
= strv_extend(&s
->before
, m
);
386 r
= strv_extend(&s
->wants
, m
);
390 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
391 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
398 case _UNIT_TYPE_INVALID
:
399 log_warning("Unit name '%s' is invalid", m
);
403 log_warning("Unknown unit type for unit '%s'", m
);
410 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
418 _cleanup_free_
char *word
= NULL
, *m
= NULL
;
421 r
= extract_first_word(&text
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_RELAX
);
423 return log_error_errno(r
, "Failed to parse word from provides string: %m");
427 r
= sysv_translate_facility(word
, basename(s
->path
), &m
);
428 if (r
<= 0) /* continue on error */
431 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
433 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
434 /* the network-online target is special, as it needs to be actively pulled in */
435 r
= strv_extend(&s
->after
, m
);
439 r
= strv_extend(&s
->wants
, m
);
441 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
449 static int load_sysv(SysvStub
*s
) {
450 _cleanup_fclose_
FILE *f
;
460 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
462 bool supports_reload
= false;
467 f
= fopen(s
->path
, "re");
472 return log_error_errno(errno
, "Failed to open %s: %m", s
->path
);
475 log_debug("Loading SysV script %s", s
->path
);
477 FOREACH_LINE(l
, f
, goto fail
) {
484 /* Try to figure out whether this init script supports
485 * the reload operation. This heuristic looks for
486 * "Usage" lines which include the reload option. */
487 if ( state
== USAGE_CONTINUATION
||
488 (state
== NORMAL
&& strcasestr(t
, "usage"))) {
489 if (usage_contains_reload(t
)) {
490 supports_reload
= true;
492 } else if (t
[strlen(t
)-1] == '\\')
493 state
= USAGE_CONTINUATION
;
501 if (state
== NORMAL
&& streq(t
, "### BEGIN INIT INFO")) {
507 if ((state
== LSB_DESCRIPTION
|| state
== LSB
) && streq(t
, "### END INIT INFO")) {
513 t
+= strspn(t
, WHITESPACE
);
515 if (state
== NORMAL
) {
517 /* Try to parse Red Hat style description */
519 if (startswith_no_case(t
, "description:")) {
525 if (k
> 0 && t
[k
-1] == '\\') {
534 r
= free_and_strdup(&chkconfig_description
, j
);
538 } else if (startswith_no_case(t
, "pidfile:")) {
544 if (!path_is_absolute(fn
)) {
545 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
549 r
= free_and_strdup(&s
->pid_file
, fn
);
554 } else if (state
== DESCRIPTION
) {
556 /* Try to parse Red Hat style description
563 if (k
> 0 && t
[k
-1] == '\\')
572 if (chkconfig_description
)
573 d
= strjoin(chkconfig_description
, " ", j
, NULL
);
579 free(chkconfig_description
);
580 chkconfig_description
= d
;
583 } else if (state
== LSB
|| state
== LSB_DESCRIPTION
) {
585 if (startswith_no_case(t
, "Provides:")) {
588 r
= handle_provides(s
, line
, t
, t
+ 9);
592 } else if (startswith_no_case(t
, "Required-Start:") ||
593 startswith_no_case(t
, "Should-Start:") ||
594 startswith_no_case(t
, "X-Start-Before:") ||
595 startswith_no_case(t
, "X-Start-After:")) {
599 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
603 } else if (startswith_no_case(t
, "Description:")) {
606 state
= LSB_DESCRIPTION
;
612 r
= free_and_strdup(&long_description
, j
);
616 } else if (startswith_no_case(t
, "Short-Description:")) {
625 r
= free_and_strdup(&short_description
, j
);
629 } else if (state
== LSB_DESCRIPTION
) {
631 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
638 if (long_description
)
639 d
= strjoin(long_description
, " ", t
, NULL
);
645 free(long_description
);
646 long_description
= d
;
655 s
->reload
= supports_reload
;
657 /* We use the long description only if
658 * no short description is set. */
660 if (short_description
)
661 description
= short_description
;
662 else if (chkconfig_description
)
663 description
= chkconfig_description
;
664 else if (long_description
)
665 description
= long_description
;
672 d
= strappend(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
683 return log_error_errno(errno
, "Failed to read configuration file '%s': %m", s
->path
);
686 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
696 if (s
->sysv_start_priority
< 0)
699 HASHMAP_FOREACH(other
, all_services
, j
) {
706 if (other
->sysv_start_priority
< 0)
709 /* If both units have modern headers we don't care
710 * about the priorities */
711 if (s
->has_lsb
&& other
->has_lsb
)
714 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
715 r
= strv_extend(&s
->after
, other
->name
);
719 } else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
720 r
= strv_extend(&s
->before
, other
->name
);
726 /* FIXME: Maybe we should compare the name here lexicographically? */
732 static int acquire_search_path(const char *def
, const char *envvar
, char ***ret
) {
733 _cleanup_strv_free_
char **l
= NULL
;
742 r
= path_split_and_make_absolute(e
, &l
);
744 return log_error_errno(r
, "Failed to make $%s search path absolute: %m", envvar
);
747 if (strv_isempty(l
)) {
750 l
= strv_new(def
, NULL
);
755 if (!path_strv_resolve_uniq(l
, NULL
))
764 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
765 _cleanup_strv_free_
char **sysvinit_path
= NULL
;
771 r
= acquire_search_path(SYSTEM_SYSVINIT_PATH
, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path
);
775 STRV_FOREACH(path
, sysvinit_path
) {
776 _cleanup_closedir_
DIR *d
= NULL
;
782 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", *path
);
786 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", *path
)) {
787 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
788 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
791 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
792 log_warning_errno(errno
, "stat() failed on %s/%s, ignoring: %m", *path
, de
->d_name
);
796 if (!(st
.st_mode
& S_IXUSR
))
799 if (!S_ISREG(st
.st_mode
))
802 name
= sysv_translate_name(de
->d_name
);
806 if (hashmap_contains(all_services
, name
))
809 r
= unit_file_lookup_state(UNIT_FILE_SYSTEM
, NULL
, lp
, name
, NULL
);
810 if (r
< 0 && r
!= -ENOENT
) {
811 log_debug_errno(r
, "Failed to detect whether %s exists, skipping: %m", name
);
814 log_debug("Native unit for %s already exists, skipping.", name
);
818 fpath
= strjoin(*path
, "/", de
->d_name
, NULL
);
822 service
= new0(SysvStub
, 1);
826 service
->sysv_start_priority
= -1;
827 service
->name
= name
;
828 service
->path
= fpath
;
831 r
= hashmap_put(all_services
, service
->name
, service
);
842 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
843 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
844 _cleanup_set_free_ Set
*shutdown_services
= NULL
;
845 _cleanup_strv_free_
char **sysvrcnd_path
= NULL
;
854 r
= acquire_search_path(SYSTEM_SYSVRCND_PATH
, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path
);
858 STRV_FOREACH(p
, sysvrcnd_path
) {
859 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
861 _cleanup_closedir_
DIR *d
= NULL
;
862 _cleanup_free_
char *path
= NULL
;
865 path
= strjoin(*p
, "/", rcnd_table
[i
].path
, NULL
);
874 log_warning_errno(errno
, "Opening %s failed, ignoring: %m", path
);
879 FOREACH_DIRENT(de
, d
, log_error_errno(errno
, "Failed to enumerate directory %s, ignoring: %m", path
)) {
880 _cleanup_free_
char *name
= NULL
, *fpath
= NULL
;
883 if (de
->d_name
[0] != 'S' && de
->d_name
[0] != 'K')
886 if (strlen(de
->d_name
) < 4)
889 a
= undecchar(de
->d_name
[1]);
890 b
= undecchar(de
->d_name
[2]);
895 fpath
= strjoin(*p
, "/", de
->d_name
, NULL
);
901 name
= sysv_translate_name(de
->d_name
+ 3);
907 service
= hashmap_get(all_services
, name
);
909 log_debug("Ignoring %s symlink in %s, not generating %s.", de
->d_name
, rcnd_table
[i
].path
, name
);
913 if (de
->d_name
[0] == 'S') {
915 if (rcnd_table
[i
].type
== RUNLEVEL_UP
)
916 service
->sysv_start_priority
= MAX(a
*10 + b
, service
->sysv_start_priority
);
918 r
= set_ensure_allocated(&runlevel_services
[i
], NULL
);
924 r
= set_put(runlevel_services
[i
], service
);
930 } else if (de
->d_name
[0] == 'K' &&
931 (rcnd_table
[i
].type
== RUNLEVEL_DOWN
)) {
933 r
= set_ensure_allocated(&shutdown_services
, NULL
);
939 r
= set_put(shutdown_services
, service
);
950 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
951 SET_FOREACH(service
, runlevel_services
[i
], j
) {
952 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
957 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
964 SET_FOREACH(service
, shutdown_services
, j
) {
965 r
= strv_extend(&service
->before
, SPECIAL_SHUTDOWN_TARGET
);
970 r
= strv_extend(&service
->conflicts
, SPECIAL_SHUTDOWN_TARGET
);
980 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
981 set_free(runlevel_services
[i
]);
986 int main(int argc
, char *argv
[]) {
987 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
988 _cleanup_lookup_paths_free_ LookupPaths lp
= {};
993 if (argc
> 1 && argc
!= 4) {
994 log_error("This program takes three or no arguments.");
1001 log_set_target(LOG_TARGET_SAFE
);
1002 log_parse_environment();
1007 r
= lookup_paths_init(&lp
, UNIT_FILE_SYSTEM
, NULL
);
1009 log_error_errno(r
, "Failed to find lookup paths: %m");
1013 all_services
= hashmap_new(&string_hash_ops
);
1014 if (!all_services
) {
1019 r
= enumerate_sysv(&lp
, all_services
);
1023 r
= set_dependencies_from_rcnd(&lp
, all_services
);
1027 HASHMAP_FOREACH(service
, all_services
, j
)
1028 (void) load_sysv(service
);
1030 HASHMAP_FOREACH(service
, all_services
, j
) {
1031 (void) fix_order(service
, all_services
);
1032 (void) generate_unit_file(service
);
1038 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;