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/>.
32 #include "path-lookup.h"
33 #include "path-util.h"
36 #include "string-util.h"
38 #include "unit-name.h"
41 typedef enum RunlevelType
{
49 const RunlevelType type
;
51 /* Standard SysV runlevels for start-up */
52 { "rc1.d", SPECIAL_RESCUE_TARGET
, RUNLEVEL_UP
},
53 { "rc2.d", SPECIAL_MULTI_USER_TARGET
, RUNLEVEL_UP
},
54 { "rc3.d", SPECIAL_MULTI_USER_TARGET
, RUNLEVEL_UP
},
55 { "rc4.d", SPECIAL_MULTI_USER_TARGET
, RUNLEVEL_UP
},
56 { "rc5.d", SPECIAL_GRAPHICAL_TARGET
, RUNLEVEL_UP
},
58 /* Standard SysV runlevels for shutdown */
59 { "rc0.d", SPECIAL_POWEROFF_TARGET
, RUNLEVEL_DOWN
},
60 { "rc6.d", SPECIAL_REBOOT_TARGET
, RUNLEVEL_DOWN
}
62 /* Note that the order here matters, as we read the
63 directories in this order, and we want to make sure that
64 sysv_start_priority is known when we first load the
65 unit. And that value we only know from S links. Hence
66 UP must be read before DOWN */
69 const char *arg_dest
= "/tmp";
71 typedef struct SysvStub
{
75 int sysv_start_priority
;
86 static void free_sysvstub(SysvStub
*s
) {
94 strv_free(s
->wanted_by
);
95 strv_free(s
->conflicts
);
99 DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub
*, free_sysvstub
);
101 static void free_sysvstub_hashmapp(Hashmap
**h
) {
104 while ((stub
= hashmap_steal_first(*h
)))
110 static int add_symlink(const char *service
, const char *where
) {
111 _cleanup_free_
char *from
= NULL
, *to
= NULL
;
117 from
= strjoin(arg_dest
, "/", service
, NULL
);
121 to
= strjoin(arg_dest
, "/", where
, ".wants/", service
, NULL
);
125 mkdir_parents_label(to
, 0755);
127 r
= symlink(from
, to
);
137 static int add_alias(const char *service
, const char *alias
) {
138 _cleanup_free_
char *link
= NULL
;
144 link
= strjoin(arg_dest
, "/", alias
, NULL
);
148 r
= symlink(service
, link
);
158 static int generate_unit_file(SysvStub
*s
) {
160 _cleanup_fclose_
FILE *f
= NULL
;
161 _cleanup_free_
char *unit
= NULL
,
162 *before
= NULL
, *after
= NULL
,
163 *wants
= NULL
, *conflicts
= NULL
;
166 before
= strv_join(s
->before
, " ");
167 after
= strv_join(s
->after
, " ");
168 wants
= strv_join(s
->wants
, " ");
169 conflicts
= strv_join(s
->conflicts
, " ");
170 unit
= strjoin(arg_dest
, "/", s
->name
, NULL
);
171 if (!before
|| !after
|| !wants
|| !conflicts
|| !unit
)
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"
192 s
->path
, s
->description
);
194 if (!isempty(before
))
195 fprintf(f
, "Before=%s\n", before
);
197 fprintf(f
, "After=%s\n", after
);
199 fprintf(f
, "Wants=%s\n", wants
);
200 if (!isempty(conflicts
))
201 fprintf(f
, "Conflicts=%s\n", conflicts
);
211 "RemainAfterExit=%s\n",
212 yes_no(!s
->pid_file
));
215 fprintf(f
, "PIDFile=%s\n", s
->pid_file
);
218 "ExecStart=%s start\n"
219 "ExecStop=%s stop\n",
223 fprintf(f
, "ExecReload=%s reload\n", s
->path
);
225 STRV_FOREACH(p
, s
->wanted_by
) {
226 r
= add_symlink(s
->name
, *p
);
228 log_error_errno(r
, "Failed to create 'Wants' symlink to %s: %m", *p
);
234 static bool usage_contains_reload(const char *line
) {
235 return (strcasestr(line
, "{reload|") ||
236 strcasestr(line
, "{reload}") ||
237 strcasestr(line
, "{reload\"") ||
238 strcasestr(line
, "|reload|") ||
239 strcasestr(line
, "|reload}") ||
240 strcasestr(line
, "|reload\""));
243 static char *sysv_translate_name(const char *name
) {
244 _cleanup_free_
char *c
= NULL
;
251 res
= endswith(c
, ".sh");
255 if (unit_name_mangle(c
, UNIT_NAME_NOGLOB
, &res
) < 0)
261 static int sysv_translate_facility(const char *name
, const char *filename
, char **_r
) {
263 /* We silently ignore the $ prefix here. According to the LSB
264 * spec it simply indicates whether something is a
265 * standardized name or a distribution-specific one. Since we
266 * just follow what already exists and do not introduce new
267 * uses or names we don't care who introduced a new name. */
269 static const char * const table
[] = {
270 /* LSB defined facilities */
272 "network", SPECIAL_NETWORK_ONLINE_TARGET
,
273 "named", SPECIAL_NSS_LOOKUP_TARGET
,
274 "portmap", SPECIAL_RPCBIND_TARGET
,
275 "remote_fs", SPECIAL_REMOTE_FS_TARGET
,
277 "time", SPECIAL_TIME_SYNC_TARGET
,
280 char *filename_no_sh
, *e
, *r
;
287 n
= *name
== '$' ? name
+ 1 : name
;
289 for (i
= 0; i
< ELEMENTSOF(table
); i
+= 2) {
291 if (!streq(table
[i
], n
))
297 r
= strdup(table
[i
+1]);
304 /* strip ".sh" suffix from file name for comparison */
305 filename_no_sh
= strdupa(filename
);
306 e
= endswith(filename_no_sh
, ".sh");
309 filename
= filename_no_sh
;
312 /* If we don't know this name, fallback heuristics to figure
313 * out whether something is a target or a service alias. */
318 /* Facilities starting with $ are most likely targets */
319 k
= unit_name_build(n
, NULL
, ".target", &r
);
323 } else if (streq_ptr(n
, filename
))
324 /* Names equaling the file name of the services are redundant */
327 /* Everything else we assume to be normal service names */
328 r
= sysv_translate_name(n
);
338 static int handle_provides(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
339 const char *word
, *state_
;
343 FOREACH_WORD_QUOTED(word
, z
, text
, state_
) {
344 _cleanup_free_
char *n
= NULL
, *m
= NULL
;
347 n
= strndup(word
, z
);
351 r
= sysv_translate_facility(n
, basename(s
->path
), &m
);
357 t
= unit_name_to_type(m
);
358 if (t
== UNIT_SERVICE
) {
359 log_debug("Adding Provides: alias '%s' for '%s'", m
, s
->name
);
360 r
= add_alias(s
->name
, m
);
362 log_warning_errno(r
, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s
->path
, line
, m
);
363 } else if (t
== UNIT_TARGET
) {
364 /* NB: SysV targets which are provided by a
365 * service are pulled in by the services, as
366 * an indication that the generic service is
367 * now available. This is strictly one-way.
368 * The targets do NOT pull in SysV services! */
369 r
= strv_extend(&s
->before
, m
);
372 r
= strv_extend(&s
->wants
, m
);
375 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
)) {
376 r
= strv_extend(&s
->before
, SPECIAL_NETWORK_TARGET
);
380 } else if (t
== _UNIT_TYPE_INVALID
)
381 log_warning("Unit name '%s' is invalid", m
);
383 log_warning("Unknown unit type for unit '%s'", m
);
385 if (!isempty(state_
))
386 log_error("[%s:%u] Trailing garbage in Provides, ignoring.", s
->path
, line
);
390 static int handle_dependencies(SysvStub
*s
, unsigned line
, const char *full_text
, const char *text
) {
391 const char *word
, *state_
;
395 FOREACH_WORD_QUOTED(word
, z
, text
, state_
) {
396 _cleanup_free_
char *n
= NULL
, *m
= NULL
;
399 n
= strndup(word
, z
);
403 r
= sysv_translate_facility(n
, basename(s
->path
), &m
);
405 log_warning_errno(r
, "[%s:%u] Failed to translate LSB dependency %s, ignoring: %m", s
->path
, line
, n
);
411 is_before
= startswith_no_case(full_text
, "X-Start-Before:");
413 if (streq(m
, SPECIAL_NETWORK_ONLINE_TARGET
) && !is_before
) {
414 /* the network-online target is special, as it needs to be actively pulled in */
415 r
= strv_extend(&s
->after
, m
);
418 r
= strv_extend(&s
->wants
, m
);
420 r
= strv_extend(is_before
? &s
->before
: &s
->after
, m
);
425 if (!isempty(state_
))
426 log_warning("[%s:%u] Trailing garbage in %*s, ignoring.", s
->path
, line
, (int)(strchr(full_text
, ':') - full_text
), full_text
);
430 static int load_sysv(SysvStub
*s
) {
431 _cleanup_fclose_
FILE *f
;
441 _cleanup_free_
char *short_description
= NULL
, *long_description
= NULL
, *chkconfig_description
= NULL
;
443 bool supports_reload
= false;
447 f
= fopen(s
->path
, "re");
449 return errno
== ENOENT
? 0 : -errno
;
451 log_debug("Loading SysV script %s", s
->path
);
454 char l
[LINE_MAX
], *t
;
456 if (!fgets(l
, sizeof(l
), f
)) {
460 return log_error_errno(errno
, "Failed to read configuration file '%s': %m", s
->path
);
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:")) {
504 size_t k
= strlen(t
);
508 if (t
[k
-1] == '\\') {
521 free(chkconfig_description
);
522 chkconfig_description
= d
;
524 } else if (startswith_no_case(t
, "pidfile:")) {
531 if (!path_is_absolute(fn
)) {
532 log_error("[%s:%u] PID file not absolute. Ignoring.", s
->path
, line
);
544 } else if (state
== DESCRIPTION
) {
546 /* Try to parse Red Hat style description
549 size_t k
= strlen(t
);
561 if (chkconfig_description
)
562 d
= strjoin(chkconfig_description
, " ", j
, NULL
);
569 free(chkconfig_description
);
570 chkconfig_description
= d
;
573 } else if (state
== LSB
|| state
== LSB_DESCRIPTION
) {
575 if (startswith_no_case(t
, "Provides:")) {
578 r
= handle_provides(s
, line
, t
, t
+ 9);
581 } else if (startswith_no_case(t
, "Required-Start:") ||
582 startswith_no_case(t
, "Should-Start:") ||
583 startswith_no_case(t
, "X-Start-Before:") ||
584 startswith_no_case(t
, "X-Start-After:")) {
588 r
= handle_dependencies(s
, line
, t
, strchr(t
, ':') + 1);
593 } else if (startswith_no_case(t
, "Description:")) {
596 state
= LSB_DESCRIPTION
;
606 free(long_description
);
607 long_description
= d
;
609 } else if (startswith_no_case(t
, "Short-Description:")) {
622 free(short_description
);
623 short_description
= d
;
625 } else if (state
== LSB_DESCRIPTION
) {
627 if (startswith(l
, "#\t") || startswith(l
, "# ")) {
634 if (long_description
)
635 d
= strjoin(long_description
, " ", t
, NULL
);
642 free(long_description
);
643 long_description
= d
;
652 s
->reload
= supports_reload
;
654 /* We use the long description only if
655 * no short description is set. */
657 if (short_description
)
658 description
= short_description
;
659 else if (chkconfig_description
)
660 description
= chkconfig_description
;
661 else if (long_description
)
662 description
= long_description
;
669 d
= strappend(s
->has_lsb
? "LSB: " : "SYSV: ", description
);
679 static int fix_order(SysvStub
*s
, Hashmap
*all_services
) {
686 if (s
->sysv_start_priority
< 0)
689 HASHMAP_FOREACH(other
, all_services
, j
) {
693 if (other
->sysv_start_priority
< 0)
696 /* If both units have modern headers we don't care
697 * about the priorities */
698 if (s
->has_lsb
&& other
->has_lsb
)
701 if (other
->sysv_start_priority
< s
->sysv_start_priority
) {
702 r
= strv_extend(&s
->after
, other
->name
);
706 else if (other
->sysv_start_priority
> s
->sysv_start_priority
) {
707 r
= strv_extend(&s
->before
, other
->name
);
714 /* FIXME: Maybe we should compare the name here lexicographically? */
720 static int enumerate_sysv(const LookupPaths
*lp
, Hashmap
*all_services
) {
723 STRV_FOREACH(path
, lp
->sysvinit_path
) {
724 _cleanup_closedir_
DIR *d
= NULL
;
730 log_warning_errno(errno
, "opendir(%s) failed: %m", *path
);
734 while ((de
= readdir(d
))) {
735 _cleanup_free_
char *fpath
= NULL
, *name
= NULL
;
736 _cleanup_(free_sysvstubp
) SysvStub
*service
= NULL
;
740 if (hidden_file(de
->d_name
))
743 if (fstatat(dirfd(d
), de
->d_name
, &st
, 0) < 0) {
744 log_warning_errno(errno
, "stat() failed on %s/%s: %m", *path
, de
->d_name
);
748 if (!(st
.st_mode
& S_IXUSR
))
751 if (!S_ISREG(st
.st_mode
))
754 name
= sysv_translate_name(de
->d_name
);
758 if (hashmap_contains(all_services
, name
))
761 fpath
= strjoin(*path
, "/", de
->d_name
, NULL
);
765 if (unit_file_lookup_state(UNIT_FILE_SYSTEM
, NULL
, lp
, name
) >= 0) {
766 log_debug("Native unit for %s already exists, skipping", name
);
770 service
= new0(SysvStub
, 1);
774 service
->sysv_start_priority
= -1;
775 service
->name
= name
;
776 service
->path
= fpath
;
778 r
= hashmap_put(all_services
, service
->name
, service
);
790 static int set_dependencies_from_rcnd(const LookupPaths
*lp
, Hashmap
*all_services
) {
793 _cleanup_closedir_
DIR *d
= NULL
;
794 _cleanup_free_
char *path
= NULL
, *fpath
= NULL
;
797 Set
*runlevel_services
[ELEMENTSOF(rcnd_table
)] = {};
798 _cleanup_set_free_ Set
*shutdown_services
= NULL
;
801 STRV_FOREACH(p
, lp
->sysvrcnd_path
)
802 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++) {
806 path
= strjoin(*p
, "/", rcnd_table
[i
].path
, NULL
);
815 log_warning_errno(errno
, "opendir(%s) failed: %m", path
);
820 while ((de
= readdir(d
))) {
821 _cleanup_free_
char *name
= NULL
;
825 if (hidden_file(de
->d_name
))
828 if (de
->d_name
[0] != 'S' && de
->d_name
[0] != 'K')
831 if (strlen(de
->d_name
) < 4)
834 a
= undecchar(de
->d_name
[1]);
835 b
= undecchar(de
->d_name
[2]);
841 fpath
= strjoin(*p
, "/", de
->d_name
, NULL
);
847 name
= sysv_translate_name(de
->d_name
+ 3);
853 service
= hashmap_get(all_services
, name
);
855 log_debug("Ignoring %s symlink in %s, not generating %s.",
856 de
->d_name
, rcnd_table
[i
].path
, name
);
860 if (de
->d_name
[0] == 'S') {
862 if (rcnd_table
[i
].type
== RUNLEVEL_UP
) {
863 service
->sysv_start_priority
=
864 MAX(a
*10 + b
, service
->sysv_start_priority
);
867 r
= set_ensure_allocated(&runlevel_services
[i
], NULL
);
871 r
= set_put(runlevel_services
[i
], service
);
875 } else if (de
->d_name
[0] == 'K' &&
876 (rcnd_table
[i
].type
== RUNLEVEL_DOWN
)) {
878 r
= set_ensure_allocated(&shutdown_services
, NULL
);
882 r
= set_put(shutdown_services
, service
);
890 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
891 SET_FOREACH(service
, runlevel_services
[i
], j
) {
892 r
= strv_extend(&service
->before
, rcnd_table
[i
].target
);
895 r
= strv_extend(&service
->wanted_by
, rcnd_table
[i
].target
);
900 SET_FOREACH(service
, shutdown_services
, j
) {
901 r
= strv_extend(&service
->before
, SPECIAL_SHUTDOWN_TARGET
);
904 r
= strv_extend(&service
->conflicts
, SPECIAL_SHUTDOWN_TARGET
);
913 for (i
= 0; i
< ELEMENTSOF(rcnd_table
); i
++)
914 set_free(runlevel_services
[i
]);
919 int main(int argc
, char *argv
[]) {
921 _cleanup_lookup_paths_free_ LookupPaths lp
= {};
922 _cleanup_(free_sysvstub_hashmapp
) Hashmap
*all_services
= NULL
;
926 if (argc
> 1 && argc
!= 4) {
927 log_error("This program takes three or no arguments.");
934 log_set_target(LOG_TARGET_SAFE
);
935 log_parse_environment();
940 r
= lookup_paths_init(&lp
, MANAGER_SYSTEM
, true, NULL
, NULL
, NULL
, NULL
);
942 log_error("Failed to find lookup paths.");
946 all_services
= hashmap_new(&string_hash_ops
);
952 r
= enumerate_sysv(&lp
, all_services
);
954 log_error("Failed to generate units for all init scripts.");
958 r
= set_dependencies_from_rcnd(&lp
, all_services
);
960 log_error("Failed to read runlevels from rcnd links.");
964 HASHMAP_FOREACH(service
, all_services
, j
) {
965 q
= load_sysv(service
);
970 HASHMAP_FOREACH(service
, all_services
, j
) {
971 q
= fix_order(service
, all_services
);
975 q
= generate_unit_file(service
);