1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Thomas H.P. Andersen
7 Copyright 2010 Lennart Poettering
8 Copyright 2011 Michal Schmidt
10 systemd is free software; you can redistribute it and/or modify it
11 under the terms of the GNU Lesser General Public License as published by
12 the Free Software Foundation; either version 2.1 of the License, or
13 (at your option) any later version.
15 systemd is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 Lesser General Public License for more details.
20 You should have received a copy of the GNU Lesser General Public License
21 along with systemd; If not, see <http://www.gnu.org/licenses/>.
31 #include "path-util.h"
32 #include "path-lookup.h"
34 #include "unit-name.h"
40 typedef enum RunlevelType
{
48 const RunlevelType type
;
50 /* Standard SysV runlevels for start-up */
51 { "rc1.d", SPECIAL_RESCUE_TARGET
, RUNLEVEL_UP
},
52 { "rc2.d", SPECIAL_MULTI_USER_TARGET
, RUNLEVEL_UP
},
53 { "rc3.d", SPECIAL_MULTI_USER_TARGET
, RUNLEVEL_UP
},
54 { "rc4.d", SPECIAL_MULTI_USER_TARGET
, RUNLEVEL_UP
},
55 { "rc5.d", SPECIAL_GRAPHICAL_TARGET
, RUNLEVEL_UP
},
57 /* Standard SysV runlevels for shutdown */
58 { "rc0.d", SPECIAL_POWEROFF_TARGET
, RUNLEVEL_DOWN
},
59 { "rc6.d", SPECIAL_REBOOT_TARGET
, RUNLEVEL_DOWN
}
61 /* Note that the order here matters, as we read the
62 directories in this order, and we want to make sure that
63 sysv_start_priority is known when we first load the
64 unit. And that value we only know from S links. Hence
65 UP must be read before DOWN */
68 const char *arg_dest
= "/tmp";
70 typedef struct SysvStub
{
74 int sysv_start_priority
;
85 static void free_sysvstub(SysvStub
*s
) {
93 strv_free(s
->wanted_by
);
94 strv_free(s
->conflicts
);
98 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
100 static void free_sysvstub_hashmapp(Hashmap
**h
) {
103 while ((stub
= hashmap_steal_first(*h
)))
109 static int add_symlink(const char *service
, const char *where
) {
110 _cleanup_free_
char *from
= NULL
, *to
= NULL
;
116 from
= strjoin(arg_dest
, "/", service
, NULL
);
120 to
= strjoin(arg_dest
, "/", where
, ".wants/", service
, NULL
);
124 mkdir_parents_label(to
, 0755);
126 r
= symlink(from
, to
);
136 static int add_alias(const char *service
, const char *alias
) {
137 _cleanup_free_
char *link
= NULL
;
143 link
= strjoin(arg_dest
, "/", alias
, NULL
);
147 r
= symlink(service
, link
);
157 static int generate_unit_file(SysvStub
*s
) {
159 _cleanup_fclose_
FILE *f
= NULL
;
160 _cleanup_free_
char *unit
= NULL
,
161 *before
= NULL
, *after
= NULL
,
162 *wants
= NULL
, *conflicts
= NULL
;
165 before
= strv_join(s
->before
, " ");
166 after
= strv_join(s
->after
, " ");
167 wants
= strv_join(s
->wants
, " ");
168 conflicts
= strv_join(s
->conflicts
, " ");
169 unit
= strjoin(arg_dest
, "/", s
->name
, NULL
);
170 if (!before
|| !after
|| !wants
|| !conflicts
|| !unit
)
173 /* We might already have a symlink with the same name from a Provides:,
174 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
175 * so remove an existing link */
176 if (is_symlink(unit
) > 0) {
177 log_warning("Overwriting existing symlink %s with real service", unit
);
181 f
= fopen(unit
, "wxe");
183 return log_error_errno(errno
, "Failed to create unit file %s: %m", unit
);
186 "# Automatically generated by systemd-sysv-generator\n\n"
188 "Documentation=man:systemd-sysv-generator(8)\n"
191 s
->path
, s
->description
);
193 if (!isempty(before
))
194 fprintf(f
, "Before=%s\n", before
);
196 fprintf(f
, "After=%s\n", after
);
198 fprintf(f
, "Wants=%s\n", wants
);
199 if (!isempty(conflicts
))
200 fprintf(f
, "Conflicts=%s\n", conflicts
);
210 "RemainAfterExit=%s\n",
211 yes_no(!s
->pid_file
));
214 fprintf(f
, "PIDFile=%s\n", s
->pid_file
);
217 "ExecStart=%s start\n"
218 "ExecStop=%s stop\n",
222 fprintf(f
, "ExecReload=%s reload\n", s
->path
);
224 STRV_FOREACH(p
, s
->wanted_by
) {
225 r
= add_symlink(s
->name
, *p
);
227 log_error_errno(r
, "Failed to create 'Wants' symlink to %s: %m", *p
);
233 static bool usage_contains_reload(const char *line
) {
234 return (strcasestr(line
, "{reload|") ||
235 strcasestr(line
, "{reload}") ||
236 strcasestr(line
, "{reload\"") ||
237 strcasestr(line
, "|reload|") ||
238 strcasestr(line
, "|reload}") ||
239 strcasestr(line
, "|reload\""));
242 static char *sysv_translate_name(const char *name
) {
243 _cleanup_free_
char *c
= NULL
;
250 res
= endswith(c
, ".sh");
254 if (unit_name_mangle(c
, UNIT_NAME_NOGLOB
, &res
) < 0)
260 static int sysv_translate_facility(const char *name
, const char *filename
, char **_r
) {
262 /* We silently ignore the $ prefix here. According to the LSB
263 * spec it simply indicates whether something is a
264 * standardized name or a distribution-specific one. Since we
265 * just follow what already exists and do not introduce new
266 * uses or names we don't care who introduced a new name. */
268 static const char * const table
[] = {
269 /* LSB defined facilities */
271 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
272 "named", SPECIAL_NSS_LOOKUP_TARGET
,
273 "portmap", SPECIAL_RPCBIND_TARGET
,
274 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
276 "time", SPECIAL_TIME_SYNC_TARGET
,
279 char *filename_no_sh
, *e
, *r
;
286 n
= *name
== '$' ? name
+ 1 : name
;
288 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
290 if (!streq(table
[i
], n
))
296 r
= strdup(table
[i
+1]);
303 /* strip ".sh" suffix from file name for comparison */
304 filename_no_sh
= strdupa(filename
);
305 e
= endswith(filename_no_sh
, ".sh");
308 filename
= filename_no_sh
;
311 /* If we don't know this name, fallback heuristics to figure
312 * out whether something is a target or a service alias. */
317 /* Facilities starting with $ are most likely targets */
318 k
= unit_name_build(n
, NULL
, ".target", &r
);
322 } else if (streq_ptr(n
, filename
))
323 /* Names equaling the file name of the services are redundant */
326 /* Everything else we assume to be normal service names */
327 r
= sysv_translate_name(n
);
337 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
338 const char *word
, *state_
;
342 FOREACH_WORD_QUOTED(word
, z
, text
, state_
) {
343 _cleanup_free_
char *n
= NULL
, *m
= NULL
;
346 n
= strndup(word
, z
);
350 r
= sysv_translate_facility(n
, basename(s
->path
), &m
);
356 t
= unit_name_to_type(m
);
357 if (t
== UNIT_SERVICE
) {
358 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
359 r
= add_alias(s
->name
, m
);
361 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
362 } else if (t
== UNIT_TARGET
) {
363 /* NB: SysV targets which are provided by a
364 * service are pulled in by the services, as
365 * an indication that the generic service is
366 * now available. This is strictly one-way.
367 * The targets do NOT pull in SysV services! */
368 r
= strv_extend(&s
->before
, m
);
371 r
= strv_extend(&s
->wants
, m
);
374 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
375 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
379 } else if (t
== _UNIT_TYPE_INVALID
)
380 log_warning("Unit name '%s' is invalid", m
);
382 log_warning("Unknown unit type for unit '%s'", m
);
384 if (!isempty(state_
))
385 log_error("[%s:%u] Trailing garbage in Provides, ignoring.", s
->path
, line
);
389 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
390 const char *word
, *state_
;
394 FOREACH_WORD_QUOTED(word
, z
, text
, state_
) {
395 _cleanup_free_
char *n
= NULL
, *m
= NULL
;
398 n
= strndup(word
, z
);
402 r
= sysv_translate_facility(n
, basename(s
->path
), &m
);
404 log_warning_errno(r
, "[%s:%u] Failed to translate LSB dependency %s, ignoring: %m", s
->path
, line
, n
);
410 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
412 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
413 /* the network-online target is special, as it needs to be actively pulled in */
414 r
= strv_extend(&s
->after
, m
);
417 r
= strv_extend(&s
->wants
, m
);
419 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
424 if (!isempty(state_
))
425 log_warning("[%s:%u] Trailing garbage in %*s, ignoring.", s
->path
, line
, (int)(strchr(full_text
, ':') - full_text
), full_text
);
429 static int load_sysv(SysvStub
*s
) {
430 _cleanup_fclose_
FILE *f
;
440 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
442 bool supports_reload
= false;
446 f
= fopen(s
->path
, "re");
448 return errno
== ENOENT
? 0 : -errno
;
450 log_debug("Loading SysV script %s", s
->path
);
453 char l
[LINE_MAX
], *t
;
455 if (!fgets(l
, sizeof(l
), f
)) {
459 return log_error_errno(errno
, "Failed to read configuration file '%s': %m", s
->path
);
466 /* Try to figure out whether this init script supports
467 * the reload operation. This heuristic looks for
468 * "Usage" lines which include the reload option. */
469 if ( state
== USAGE_CONTINUATION
||
470 (state
== NORMAL
&& strcasestr(t
, "usage"))) {
471 if (usage_contains_reload(t
)) {
472 supports_reload
= true;
474 } else if (t
[strlen(t
)-1] == '\\')
475 state
= USAGE_CONTINUATION
;
483 if (state
== NORMAL
&& streq(t
, "### BEGIN INIT INFO")) {
489 if ((state
== LSB_DESCRIPTION
|| state
== LSB
) && streq(t
, "### END INIT INFO")) {
495 t
+= strspn(t
, WHITESPACE
);
497 if (state
== NORMAL
) {
499 /* Try to parse Red Hat style description */
501 if (startswith_no_case(t
, "description:")) {
503 size_t k
= strlen(t
);
507 if (t
[k
-1] == '\\') {
520 free(chkconfig_description
);
521 chkconfig_description
= d
;
523 } else if (startswith_no_case(t
, "pidfile:")) {
530 if (!path_is_absolute(fn
)) {
531 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
543 } else if (state
== DESCRIPTION
) {
545 /* Try to parse Red Hat style description
548 size_t k
= strlen(t
);
560 if (chkconfig_description
)
561 d
= strjoin(chkconfig_description
, " ", j
, NULL
);
568 free(chkconfig_description
);
569 chkconfig_description
= d
;
572 } else if (state
== LSB
|| state
== LSB_DESCRIPTION
) {
574 if (startswith_no_case(t
, "Provides:")) {
577 r
= handle_provides(s
, line
, t
, t
+ 9);
580 } else if (startswith_no_case(t
, "Required-Start:") ||
581 startswith_no_case(t
, "Should-Start:") ||
582 startswith_no_case(t
, "X-Start-Before:") ||
583 startswith_no_case(t
, "X-Start-After:")) {
587 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
592 } else if (startswith_no_case(t
, "Description:")) {
595 state
= LSB_DESCRIPTION
;
605 free(long_description
);
606 long_description
= d
;
608 } else if (startswith_no_case(t
, "Short-Description:")) {
621 free(short_description
);
622 short_description
= d
;
624 } else if (state
== LSB_DESCRIPTION
) {
626 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
633 if (long_description
)
634 d
= strjoin(long_description
, " ", t
, NULL
);
641 free(long_description
);
642 long_description
= d
;
651 s
->reload
= supports_reload
;
653 /* We use the long description only if
654 * no short description is set. */
656 if (short_description
)
657 description
= short_description
;
658 else if (chkconfig_description
)
659 description
= chkconfig_description
;
660 else if (long_description
)
661 description
= long_description
;
668 d
= strappend(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
678 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
685 if (s
->sysv_start_priority
< 0)
688 HASHMAP_FOREACH(other
, all_services
, j
) {
692 if (other
->sysv_start_priority
< 0)
695 /* If both units have modern headers we don't care
696 * about the priorities */
697 if (s
->has_lsb
&& other
->has_lsb
)
700 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
701 r
= strv_extend(&s
->after
, other
->name
);
705 else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
706 r
= strv_extend(&s
->before
, other
->name
);
713 /* FIXME: Maybe we should compare the name here lexicographically? */
719 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
722 STRV_FOREACH(path
, lp
->sysvinit_path
) {
723 _cleanup_closedir_
DIR *d
= NULL
;
729 log_warning_errno(errno
, "opendir(%s) failed: %m", *path
);
733 while ((de
= readdir(d
))) {
734 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
735 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
739 if (hidden_file(de
->d_name
))
742 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
743 log_warning_errno(errno
, "stat() failed on %s/%s: %m", *path
, de
->d_name
);
747 if (!(st
.st_mode
& S_IXUSR
))
750 if (!S_ISREG(st
.st_mode
))
753 name
= sysv_translate_name(de
->d_name
);
757 if (hashmap_contains(all_services
, name
))
760 fpath
= strjoin(*path
, "/", de
->d_name
, NULL
);
764 if (unit_file_lookup_state(UNIT_FILE_SYSTEM
, NULL
, lp
, name
) >= 0) {
765 log_debug("Native unit for %s already exists, skipping", name
);
769 service
= new0(SysvStub
, 1);
773 service
->sysv_start_priority
= -1;
774 service
->name
= name
;
775 service
->path
= fpath
;
777 r
= hashmap_put(all_services
, service
->name
, service
);
789 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
792 _cleanup_closedir_
DIR *d
= NULL
;
793 _cleanup_free_
char *path
= NULL
, *fpath
= NULL
;
796 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
797 _cleanup_set_free_ Set
*shutdown_services
= NULL
;
800 STRV_FOREACH(p
, lp
->sysvrcnd_path
)
801 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
805 path
= strjoin(*p
, "/", rcnd_table
[i
].path
, NULL
);
814 log_warning_errno(errno
, "opendir(%s) failed: %m", path
);
819 while ((de
= readdir(d
))) {
820 _cleanup_free_
char *name
= NULL
;
824 if (hidden_file(de
->d_name
))
827 if (de
->d_name
[0] != 'S' && de
->d_name
[0] != 'K')
830 if (strlen(de
->d_name
) < 4)
833 a
= undecchar(de
->d_name
[1]);
834 b
= undecchar(de
->d_name
[2]);
840 fpath
= strjoin(*p
, "/", de
->d_name
, NULL
);
846 name
= sysv_translate_name(de
->d_name
+ 3);
852 service
= hashmap_get(all_services
, name
);
854 log_debug("Ignoring %s symlink in %s, not generating %s.",
855 de
->d_name
, rcnd_table
[i
].path
, name
);
859 if (de
->d_name
[0] == 'S') {
861 if (rcnd_table
[i
].type
== RUNLEVEL_UP
) {
862 service
->sysv_start_priority
=
863 MAX(a
*10 + b
, service
->sysv_start_priority
);
866 r
= set_ensure_allocated(&runlevel_services
[i
], NULL
);
870 r
= set_put(runlevel_services
[i
], service
);
874 } else if (de
->d_name
[0] == 'K' &&
875 (rcnd_table
[i
].type
== RUNLEVEL_DOWN
)) {
877 r
= set_ensure_allocated(&shutdown_services
, NULL
);
881 r
= set_put(shutdown_services
, service
);
889 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
890 SET_FOREACH(service
, runlevel_services
[i
], j
) {
891 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
894 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
899 SET_FOREACH(service
, shutdown_services
, j
) {
900 r
= strv_extend(&service
->before
, SPECIAL_SHUTDOWN_TARGET
);
903 r
= strv_extend(&service
->conflicts
, SPECIAL_SHUTDOWN_TARGET
);
912 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
913 set_free(runlevel_services
[i
]);
918 int main(int argc
, char *argv
[]) {
920 _cleanup_lookup_paths_free_ LookupPaths lp
= {};
921 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
925 if (argc
> 1 && argc
!= 4) {
926 log_error("This program takes three or no arguments.");
933 log_set_target(LOG_TARGET_SAFE
);
934 log_parse_environment();
939 r
= lookup_paths_init(&lp
, MANAGER_SYSTEM
, true, NULL
, NULL
, NULL
, NULL
);
941 log_error("Failed to find lookup paths.");
945 all_services
= hashmap_new(&string_hash_ops
);
951 r
= enumerate_sysv(&lp
, all_services
);
953 log_error("Failed to generate units for all init scripts.");
957 r
= set_dependencies_from_rcnd(&lp
, all_services
);
959 log_error("Failed to read runlevels from rcnd links.");
963 HASHMAP_FOREACH(service
, all_services
, j
) {
964 q
= load_sysv(service
);
969 HASHMAP_FOREACH(service
, all_services
, j
) {
970 q
= fix_order(service
, all_services
);
974 q
= generate_unit_file(service
);