+/*
+ * insserv(.c)
+ *
+ * Copyright 2000-2008 Werner Fink, 2000 SuSE GmbH Nuernberg, Germany,
+ * 2003 SuSE Linux AG, Germany.
+ * 2004 SuSE LINUX AG, Germany.
+ * 2005-2008 SUSE LINUX Products GmbH, Germany.
+ * Copyright 2005,2008 Petter Reinholdtsen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#define MINIMAL_MAKE 1 /* Remove disabled scripts from .depend.boot,
+ * .depend.start, .depend.halt, and .depend.stop */
+#define MINIMAL_RULES 1 /* ditto */
+
+#include <pwd.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/syscall.h>
+#include <dirent.h>
+#include <regex.h>
+#include <errno.h>
+#include <limits.h>
+#include <getopt.h>
+#if defined(USE_RPMLIB) && (USE_RPMLIB > 0)
+# include <rpm/rpmlib.h>
+# include <rpm/rpmmacro.h>
+#endif /* USE_RPMLIB */
+#include "listing.h"
+
+#ifdef SUSE
+# define DEFAULT_START_LVL "3 5"
+# define DEFAULT_STOP_LVL "3 5"
+# define USE_KILL_IN_BOOT 1
+# define USE_COMPAT_EMPTY 1
+static inline void oneway(char *restrict stop) attribute((always_inline,nonnull(1)));
+static inline void oneway(char *restrict stop)
+{
+ char * ptr = stop;
+ while ((ptr = strpbrk(ptr, "016sS")))
+ *ptr++ = ' ';
+}
+#else /* not SUSE, but Debian */
+# define DEFAULT_START_LVL "2 3 4 5"
+# define DEFAULT_STOP_LVL "0 1 6"
+# define DEFAULT_DEPENDENCY "$remote_fs $syslog"
+# undef USE_KILL_IN_BOOT
+# undef USE_COMPAT_EMPTY
+#endif /* not SUSE, but Debian */
+
+#ifndef INITDIR
+# define INITDIR "/etc/init.d"
+#endif
+#ifndef OVERRIDEDIR
+# define OVERRIDEDIR "/etc/insserv/overrides"
+#endif
+#ifndef INSCONF
+# define INSCONF "/etc/insserv.conf"
+#endif
+
+/*
+ * For a description of regular expressions see regex(7).
+ */
+#define COMM "^#[[:blank:]]*"
+#define VALUE ":[[:blank:]]*([[:print:]]*)"
+/* The second substring contains our value (the first is all) */
+#define SUBNUM 2
+#define SUBNUM_SHD 3
+#define START "[-_]+start"
+#define STOP "[-_]+stop"
+
+/* The main regular search expressions */
+#define PROVIDES COMM "provides" VALUE
+#define REQUIRED COMM "required"
+#define SHOULD COMM "(x[-_]+[a-z0-9_-]*)?should"
+#define BEFORE COMM "(x[-_]+[a-z0-9_-]*)?start[-_]+before"
+#define AFTER COMM "(x[-_]+[a-z0-9_-]*)?stop[-_]+after"
+#define DEFAULT COMM "default"
+#define REQUIRED_START REQUIRED START VALUE
+#define REQUIRED_STOP REQUIRED STOP VALUE
+#define SHOULD_START SHOULD START VALUE
+#define SHOULD_STOP SHOULD STOP VALUE
+#define START_BEFORE BEFORE VALUE
+#define STOP_AFTER AFTER VALUE
+#define DEFAULT_START DEFAULT START VALUE
+#define DEFAULT_STOP DEFAULT STOP VALUE
+#define DESCRIPTION COMM "description" VALUE
+
+/* System facility search within /etc/insserv.conf */
+#define EQSIGN "([[:blank:]]*[=:][[:blank:]]*|[[:blank:]]+)"
+#define CONFLINE "^(\\$[a-z0-9_-]+)" EQSIGN "([[:print:]]*)"
+#define CONFLINE2 "^(<[a-z0-9_-]+>)" EQSIGN "([[:print:]]*)"
+#define SUBCONF 2
+#define SUBCONFNUM 4
+
+/* The root file system */
+static char *root;
+
+/* The main line buffer if unique */
+static char buf[LINE_MAX];
+
+/* When to be verbose */
+static boolean verbose = false;
+
+/* When to be verbose */
+static boolean dryrun = false;
+
+/* When paths set do not add root if any */
+static boolean set_override = false;
+static boolean set_insconf = false;
+
+/* Search results points here */
+typedef struct lsb_struct {
+ char *provides;
+ char *required_start;
+ char *required_stop;
+ char *should_start;
+ char *should_stop;
+ char *start_before;
+ char *stop_after;
+ char *default_start;
+ char *default_stop;
+ char *description;
+} attribute((aligned(sizeof(char*)))) lsb_t;
+
+/* Search results points here */
+typedef struct reg_struct {
+ regex_t prov;
+ regex_t req_start;
+ regex_t req_stop;
+ regex_t shl_start;
+ regex_t shl_stop;
+ regex_t start_bf;
+ regex_t stop_af;
+ regex_t def_start;
+ regex_t def_stop;
+ regex_t desc;
+} attribute((aligned(sizeof(regex_t)))) reg_t;
+
+typedef struct creg_struct {
+ regex_t isysfaci;
+ regex_t isactive;
+} attribute((aligned(sizeof(regex_t)))) creg_t;
+
+static lsb_t script_inf;
+static reg_t reg;
+static creg_t creg;
+static char empty[1] = "";
+
+/* Delimeters used for spliting results with strsep(3) */
+const char *const delimeter = " ,;\t";
+
+/*
+ * push and pop directory changes: pushd() and popd()
+ */
+typedef struct pwd_struct {
+ list_t deep;
+ char *pwd;
+} __align pwd_t;
+#define getpwd(list) list_entry((list), struct pwd_struct, deep)
+
+static list_t pwd = { &pwd, &pwd }, * topd = &pwd;
+
+static void pushd(const char *restrict const path) attribute((nonnull(1)));
+static void pushd(const char *restrict const path)
+{
+ pwd_t *restrict dir;
+
+ if (posix_memalign((void*)&dir, sizeof(void*), alignof(pwd_t)) == 0) {
+ if (!(dir->pwd = getcwd((char*)0, 0)))
+ goto err;
+ insert(&dir->deep, topd->prev);
+ if (chdir(path) < 0)
+ goto err;
+ return;
+ }
+err:
+ error ("pushd() can not change to directory %s: %s\n", path, strerror(errno));
+}
+
+static void popd(void)
+{
+ pwd_t * dir;
+
+ if (list_empty(topd))
+ goto out;
+ dir = getpwd(topd->prev);
+ if (chdir(dir->pwd) < 0)
+ error ("popd() can not change directory %s: %s\n", dir->pwd, strerror(errno));
+ delete(topd->prev);
+ free(dir->pwd);
+ free(dir);
+out:
+ return;
+}
+
+/*
+ * Linked list of system facilities services and their replacment
+ */
+typedef struct string {
+ int *restrict ref;
+ char *name;
+} __align string_t;
+
+typedef struct repl {
+ list_t r_list;
+ string_t r[1];
+} __align repl_t;
+#define getrepl(arg) list_entry((arg), struct repl, r_list)
+
+typedef struct faci {
+ list_t list;
+ list_t replace;
+ char *name;
+} __align faci_t;
+#define getfaci(arg) list_entry((arg), struct faci, list)
+
+static list_t sysfaci = { &sysfaci, &sysfaci }, *sysfaci_start = &sysfaci;
+
+/*
+ * Remember requests for required or should services and expand `$' token
+ */
+static void rememberreq(service_t *restrict serv, uint bit,
+ const char *restrict required) attribute((noinline,nonnull(1,3)));
+static void rememberreq(service_t * restrict serv, uint bit, const char * restrict required)
+{
+ const char type = (bit & REQ_KILL) ? 'K' : 'S';
+ const char * token;
+ char * tmp = strdupa(required);
+ list_t * ptr, * list;
+ ushort old = bit;
+
+ if (!tmp)
+ error("%s", strerror(errno));
+
+ while ((token = strsep(&tmp, delimeter)) && *token) {
+ service_t * req, * here, * need;
+ boolean found = false;
+
+ bit = old;
+
+ switch(*token) {
+ case '+':
+ /* This is an optional token */
+ token++;
+ bit &= ~REQ_MUST;
+ bit |= REQ_SHLD;
+ default:
+ req = addservice(token);
+ if (bit & REQ_KILL) {
+ req = getorig(req);
+ list = &req->sort.rev;
+ here = req;
+ need = serv;
+ } else {
+ serv = getorig(serv);
+ list = &serv->sort.req;
+ here = serv;
+ need = req;
+ }
+ np_list_for_each(ptr, list) {
+ if (!strcmp(getreq(ptr)->serv->name, need->name)) {
+ getreq(ptr)->flags |= bit;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ req_t *restrict this;
+ if (posix_memalign((void*)&this, sizeof(void*), alignof(req_t)) != 0)
+ error("%s", strerror(errno));
+ memset(this, 0, alignof(req_t));
+ insert(&this->list, list->prev);
+ this->flags = bit;
+ this->serv = need;
+ }
+ /* Expand requested services for sorting */
+ requires(here, need, type);
+ break;
+ case '$':
+ if (strcasecmp(token, "$all") == 0) {
+ serv->attr.flags |= SERV_ALL;
+ break;
+ }
+ /* Expand the `$' token recursively down */
+ list_for_each(ptr, sysfaci_start) {
+ if (!strcmp(token, getfaci(ptr)->name)) {
+ list_t * lst;
+ np_list_for_each(lst, &getfaci(ptr)->replace)
+ rememberreq(serv, bit, getrepl(lst)->r[0].name);
+ break;
+ }
+ }
+ break;
+ }
+ }
+}
+
+static void reversereq(service_t *restrict serv, uint bit,
+ const char *restrict list) attribute((noinline,nonnull(1,3)));
+static void reversereq(service_t *restrict serv, uint bit, const char *restrict list)
+{
+ const char * token;
+ char * tmp = strdupa(list);
+ ushort old = bit;
+
+ if (!tmp)
+ error("%s", strerror(errno));
+
+ while ((token = strsep(&tmp, delimeter)) && *token) {
+ service_t * rev;
+ list_t * ptr;
+
+ bit = old;
+
+ switch (*token) {
+ case '+':
+ token++;
+ bit &= ~REQ_MUST;
+ bit |= REQ_SHLD;
+ default:
+ rev = addservice(token);
+ rememberreq(rev, bit, serv->name);
+ break;
+ case '$':
+ list_for_each(ptr, sysfaci_start) {
+ if (!strcmp(token, getfaci(ptr)->name)) {
+ list_t * lst;
+ np_list_for_each(lst, &getfaci(ptr)->replace)
+ reversereq(serv, bit, getrepl(lst)->r[0].name);
+ break;
+ }
+ }
+ break;
+ }
+ }
+}
+
+/*
+ * Check required services for name
+ */
+static boolean chkrequired(service_t *restrict serv) attribute((nonnull(1)));
+static boolean chkrequired(service_t *restrict serv)
+{
+ boolean ret = true;
+ list_t * pos;
+
+ if (!serv)
+ goto out;
+ serv = getorig(serv);
+
+ np_list_for_each(pos, &serv->sort.req) {
+ req_t *req = getreq(pos);
+ service_t * must;
+
+ if ((req->flags & REQ_MUST) == 0)
+ continue;
+ must = req->serv;
+ must = getorig(must);
+
+ if ((must->attr.flags & (SERV_CMDLINE|SERV_ENABLED)) == 0) {
+ warn("Service %s has to be enabled to start service %s\n",
+ req->serv->name, serv->name);
+ ret = false;
+ }
+ }
+#if 0
+ if (serv->attr.flags & (SERV_CMDLINE|SERV_ENABLED))
+ goto out;
+ np_list_for_each(pos, &serv->sort.rev) {
+ req_t *rev = getreq(pos);
+ service_t * must;
+
+ if ((rev->flags & REQ_MUST) == 0)
+ continue;
+ must = rev->serv;
+ must = getorig(must);
+
+ if (must->attr.flags & (SERV_CMDLINE|SERV_ENABLED)) {
+ warn("Service %s has to be enabled to stop service %s\n",
+ serv->name, rev->serv->name);
+ ret = false;
+ }
+ }
+#endif
+out:
+ return ret;
+}
+
+/*
+ * Check dependencies for name as a service
+ */
+static boolean chkdependencies(service_t *restrict serv) attribute((nonnull(1)));
+static boolean chkdependencies(service_t *restrict serv)
+{
+ const char * const name = serv->name;
+ boolean ret = true;
+ list_t * ptr;
+
+ list_for_each(ptr, s_start) {
+ service_t * cur = getservice(ptr);
+ list_t * pos;
+
+ if (!cur)
+ continue;
+
+ if ((cur->attr.flags & SERV_ENABLED) == 0)
+ continue;
+
+ if (cur->attr.flags & SERV_DUPLET)
+ continue;
+
+ if (list_empty(&cur->sort.req))
+ continue;
+
+ np_list_for_each(pos, &cur->sort.req) {
+ req_t *req = getreq(pos);
+ const ushort flags = req->serv->attr.flags;
+
+ if (!(req->flags & REQ_MUST))
+ continue;
+
+ if (strcmp(req->serv->name, name) != 0)
+ continue;
+
+ if ((cur->attr.flags & SERV_CMDLINE) && (flags & SERV_CMDLINE))
+ continue;
+
+ warn("Service %s has to be enabled to start service %s\n",
+ name, cur->name);
+ ret = false;
+ }
+ }
+ return ret;
+}
+
+/*
+ * This helps us to work out the current symbolic link structure
+ */
+static inline service_t * current_structure(const char *const restrict this, const char order,
+ const int runlvl, const char type) attribute((always_inline,nonnull(1)));
+static inline service_t * current_structure(const char *const this, const char order,
+ const int runlvl, const char type)
+{
+ service_t * serv = addservice(this);
+ level_t * run;
+ ushort here = map_runlevel_to_lvl(runlvl);
+
+ if (type == 'K') {
+ run = serv->stopp;
+ if (!serv->attr.korder)
+ serv->attr.korder = 99;
+ if (serv->attr.korder > order)
+ serv->attr.korder = order;
+#ifdef SUSE
+ /* This because SuSE boot script concept uses a differential link scheme. */
+ here &= ~LVL_ONEWAY;
+#endif /* SUSE */
+ } else {
+ run = serv->start;
+ if (serv->attr.sorder < order)
+ serv->attr.sorder = order;
+ }
+ run->lvl |= here;
+
+ return serv;
+}
+
+static void setlsb(const char *restrict const name) attribute((unused));
+static void setlsb(const char *restrict const name)
+{
+ service_t * serv = findservice(name);
+ if (serv)
+ serv->attr.flags &= ~SERV_NOTLSB;
+}
+
+/*
+ * This helps us to set none LSB conform scripts to required
+ * max order, therefore we set a dependency to the first
+ * lsb conform service found in current link scheme.
+ */
+static inline void nonlsb_script(void) attribute((always_inline));
+static inline void nonlsb_script(void)
+{
+ list_t * pos;
+
+ list_for_each(pos, s_start) {
+ if (getservice(pos)->attr.flags & SERV_NOTLSB) {
+ service_t * req, * srv = getservice(pos);
+ list_t * tmp;
+ uchar max;
+
+ max = 0;
+ req = (service_t*)0;
+ list_for_each(tmp, s_start) {
+ service_t * cur = getservice(tmp);
+ if (cur->attr.flags & SERV_NOTLSB)
+ continue;
+ if ((cur->attr.flags & SERV_ENABLED) == 0)
+ continue;
+ if (!cur->attr.sorder)
+ continue;
+ if ((srv->start->lvl & cur->start->lvl) == 0)
+ continue;
+ if (cur->attr.sorder >= srv->attr.sorder)
+ continue;
+ if (max < cur->attr.sorder) {
+ max = cur->attr.sorder;
+ req = cur;
+ }
+ }
+
+ if (req)
+ requires(srv, req, 'S');
+
+ max = 99;
+ req = (service_t*)0;
+ list_for_each(tmp, s_start) {
+ service_t * cur = getservice(tmp);
+ if (cur->attr.flags & SERV_NOTLSB)
+ continue;
+ if ((cur->attr.flags & SERV_ENABLED) == 0)
+ continue;
+ if (!cur->attr.korder)
+ continue;
+ if ((srv->stopp->lvl & cur->stopp->lvl) == 0)
+ continue;
+ if (cur->attr.korder <= srv->attr.korder)
+ continue;
+ if (max > cur->attr.korder) {
+ max = cur->attr.korder;
+ req = cur;
+ }
+ }
+
+ if (req)
+ requires(req, srv, 'K');
+ }
+ }
+}
+
+/*
+ * This helps us to get interactive scripts to be the only service
+ * within on start or stop service group. Remaining problem is that
+ * if required scripts are missed the order can be wrong.
+ */
+static inline void active_script(void) attribute((always_inline));
+static inline void active_script(void)
+{
+ list_t * pos;
+ int deep = 1;
+
+ for (deep = 0; deep < 100; deep++) {
+ list_for_each(pos, s_start) {
+ service_t * serv = getservice(pos);
+ list_t * tmp;
+
+ if (serv->attr.script == (char*)0)
+ continue;
+
+ if ((serv->attr.flags & SERV_INTRACT) == 0)
+ continue;
+
+ serv->attr.sorder = getorder(serv->attr.script, 'S');
+
+ if (serv->attr.sorder != deep)
+ continue;
+
+ if (serv->attr.flags & SERV_DUPLET)
+ continue; /* Duplet */
+
+ list_for_each(tmp, s_start) {
+ service_t * cur = getservice(tmp);
+ const char * script;
+
+ if (getorig(cur) == serv)
+ continue;
+
+ if ((serv->start->lvl & cur->start->lvl) == 0)
+ continue;
+
+ /*
+ * Use real script name for getorder()/setorder()
+ */
+ if (cur->attr.script == (char*)0)
+ continue;
+ script = cur->attr.script;
+
+ cur->attr.sorder = getorder(script, 'S');
+
+ if (cur->attr.sorder != deep)
+ continue;
+ /*
+ * Increase order of members of the same start
+ * group and recalculate dependency order (`true')
+ */
+ setorder(script, 'S', ++cur->attr.sorder, true);
+ }
+ }
+ }
+}
+
+/*
+ * Last but not least the `$all' scripts will be set to the
+ * end of the current start order.
+ */
+static inline void all_script(void) attribute((always_inline));
+static inline void all_script(void)
+{
+ list_t * pos;
+
+ list_for_each(pos, s_start) {
+ service_t * serv = getservice(pos);
+ list_t * tmp;
+ int neworder;
+
+ if (serv->attr.flags & SERV_DUPLET)
+ continue; /* Duplet */
+
+ if (!(serv->attr.flags & SERV_ALL))
+ continue;
+
+ if (serv->attr.script == (char*)0)
+ continue;
+
+ neworder = 0;
+ list_for_each(tmp, s_start) {
+ service_t * cur = getservice(tmp);
+
+ if (cur->attr.flags & SERV_DUPLET)
+ continue; /* Duplet */
+
+ if ((serv->start->lvl & cur->start->lvl) == 0)
+ continue;
+
+ if (cur->attr.script == (char*)0)
+ continue;
+
+ if (cur == serv)
+ continue;
+
+ if (cur->attr.flags & SERV_ALL)
+ continue;
+
+ cur->attr.sorder = getorder(cur->attr.script, 'S');
+
+ if (cur->attr.sorder > neworder)
+ neworder = cur->attr.sorder;
+ }
+ neworder++;
+
+ if (neworder > MAX_DEEP)
+ neworder = maxstart;
+ else if (neworder > maxstart)
+ maxstart = neworder;
+
+ setorder(serv->attr.script, 'S', neworder, false);
+ }
+}
+
+/*
+ * Make the dependency files
+ */
+static inline void makedep(void) attribute((always_inline));
+static inline void makedep(void)
+{
+ FILE *boot, *start, *stop, *out;
+#ifdef USE_KILL_IN_BOOT
+ FILE *halt;
+#endif /* USE_KILL_IN_BOOT */
+ const char *target;
+ service_t *serv;
+
+ if (dryrun) {
+#ifdef USE_KILL_IN_BOOT
+ info("dryrun, not creating .depend.boot, .depend.start, .depend.halt, and .depend.stop\n");
+#else /* not USE_KILL_IN_BOOT */
+ info("dryrun, not creating .depend.boot, .depend.start, and .depend.stop\n");
+#endif /* not USE_KILL_IN_BOOT */
+ return;
+ }
+ if (!(boot = fopen(".depend.boot", "w"))) {
+ warn("fopen(.depend.stop): %s\n", strerror(errno));
+ return;
+ }
+
+ if (!(start = fopen(".depend.start", "w"))) {
+ warn("fopen(.depend.start): %s\n", strerror(errno));
+ fclose(boot);
+ return;
+ }
+
+ info("creating .depend.boot\n");
+ info("creating .depend.start\n");
+
+ lsort('S'); /* Sort into start order, set new sorder */
+
+ target = (char*)0;
+ fprintf(boot, "TARGETS =");
+ while ((serv = listscripts(&target, 'S', LVL_BOOT))) {
+ if (!serv)
+ continue;
+#if defined(MINIMAL_MAKE) && (MINIMAL_MAKE != 0)
+ if (serv->attr.ref <= 0)
+ continue;
+#endif /* MINIMAL_MAKE */
+ fprintf(boot, " %s", target);
+ }
+ fputc('\n', boot);
+
+ target = (char*)0;
+ fprintf(start, "TARGETS =");
+ while ((serv = listscripts(&target, 'S', LVL_ALL))) { /* LVL_ALL: nearly all but not BOOT */
+ if (!serv)
+ continue;
+#if defined(MINIMAL_MAKE) && (MINIMAL_MAKE != 0)
+ if (serv->attr.ref <= 0)
+ continue;
+#endif /* MINIMAL_MAKE */
+ fprintf(start, " %s", target);
+ }
+ fputc('\n', start);
+
+ fprintf(boot, "INTERACTIVE =");
+ fprintf(start, "INTERACTIVE =");
+
+ target = (char*)0;
+ while ((serv = listscripts(&target, 'S', LVL_BOOT|LVL_ALL))) {
+ if (!serv)
+ continue;
+#if defined(MINIMAL_MAKE) && (MINIMAL_MAKE != 0)
+ if (serv->attr.ref <= 0)
+ continue;
+#endif /* not MINIMAL_MAKE */
+
+ if (list_empty(&serv->sort.req))
+ continue;
+
+ if (serv->start->lvl & LVL_BOOT)
+ out = boot;
+ else
+ out = start;
+
+ if (serv->attr.flags & SERV_INTRACT)
+ fprintf(out, " %s", target);
+ }
+ fputc('\n', boot);
+ fputc('\n', start);
+
+ target = (char*)0;
+ while ((serv = listscripts(&target, 'S', LVL_BOOT|LVL_ALL))) {
+ boolean mark;
+ list_t * pos;
+
+ if (!serv)
+ continue;
+#if defined(MINIMAL_RULES) && (MINIMAL_RULES != 0)
+ if (serv->attr.ref <= 0)
+ continue;
+#endif /* not MINIMAL_RULES */
+
+ if (list_empty(&serv->sort.req))
+ continue;
+
+ if (serv->start->lvl & LVL_BOOT)
+ out = boot;
+ else
+ out = start;
+
+ mark = false;
+ if (serv->attr.flags & SERV_ALL) {
+ list_for_each(pos, s_start) {
+ service_t * dep = getservice(pos);
+ const char * name;
+
+ if (!dep)
+ continue;
+
+ if (dep->attr.flags & SERV_DUPLET)
+ continue; /* Duplet */
+
+#if defined(MINIMAL_RULES) && (MINIMAL_RULES != 0)
+ if (dep->attr.ref <= 0)
+ continue;
+#endif /* not MINIMAL_RULES */
+
+ /*
+ * No self dependcies or from the last
+ */
+ if (dep == serv || (dep->attr.flags & SERV_ALL))
+ continue;
+
+ if ((serv->start->lvl & dep->start->lvl) == 0)
+ continue;
+
+ if ((name = dep->attr.script) == (char*)0)
+ continue;
+
+ if (!mark) {
+ fprintf(out, "%s:", target);
+ mark = true;
+ }
+ fprintf(out, " %s", name);
+ }
+ } else {
+ np_list_for_each(pos, &serv->sort.req) {
+ req_t * req = getreq(pos);
+ service_t * dep = req->serv;
+ const char * name;
+
+ if (!dep)
+ continue;
+
+ if (dep->attr.flags & SERV_DUPLET)
+ continue;
+
+#if defined(MINIMAL_RULES) && (MINIMAL_RULES != 0)
+ if (dep->attr.ref <= 0)
+ continue;
+#endif /* not MINIMAL_RULES */
+
+ /*
+ * No self dependcies or from the last
+ */
+ if (dep == serv || (dep->attr.flags & SERV_ALL))
+ continue;
+
+ if ((serv->start->lvl & dep->start->lvl) == 0)
+ continue;
+
+ if ((name = dep->attr.script) == (char*)0)
+ continue;
+
+ if (!mark) {
+ fprintf(out, "%s:", target);
+ mark = true;
+ }
+ fprintf(out, " %s", name);
+ }
+ }
+ if (mark) fputc('\n', out);
+ }
+
+ fclose(boot);
+ fclose(start);
+
+ if (!(stop = fopen(".depend.stop", "w"))) {
+ warn("fopen(.depend.stop): %s\n", strerror(errno));
+ return;
+ }
+
+#ifdef USE_KILL_IN_BOOT
+ if (!(halt = fopen(".depend.halt", "w"))) {
+ warn("fopen(.depend.start): %s\n", strerror(errno));
+ fclose(stop);
+ return;
+ }
+
+ info("creating .depend.halt\n");
+#endif /* USE_KILL_IN_BOOT */
+ info("creating .depend.stop\n");
+
+ lsort('K'); /* Sort into stop order, set new korder */
+
+ target = (char*)0;
+ fprintf(stop, "TARGETS =");
+ while ((serv = listscripts(&target, 'K', LVL_NORM))) { /* LVL_NORM: nearly all but not BOOT and not SINGLE */
+ if (!serv)
+ continue;
+#if defined(MINIMAL_MAKE) && (MINIMAL_MAKE != 0)
+ if (serv->attr.ref <= 0)
+ continue;
+#endif /* MINIMAL_MAKE */
+ fprintf(stop, " %s", target);
+ }
+ fputc('\n', stop);
+
+#ifdef USE_KILL_IN_BOOT
+ target = (char*)0;
+ fprintf(halt, "TARGETS =");
+ while ((serv = listscripts(&target, 'K', LVL_BOOT))) {
+ if (!serv)
+ continue;
+# if defined(MINIMAL_MAKE) && (MINIMAL_MAKE != 0)
+ if (serv->attr.ref <= 0)
+ continue;
+# endif /* MINIMAL_MAKE */
+ fprintf(halt, " %s", target);
+ }
+ fputc('\n', halt);
+#endif /* USE_KILL_IN_BOOT */
+
+ target = (char*)0;
+ while ((serv = listscripts(&target, 'K', (LVL_NORM|LVL_BOOT)))) {
+ boolean mark;
+ list_t * pos;
+
+ if (!serv)
+ continue;
+#if defined(MINIMAL_RULES) && (MINIMAL_RULES != 0)
+ if (serv->attr.ref <= 0)
+ continue;
+#endif /* not MINIMAL_RULES */
+
+ if (list_empty(&serv->sort.rev))
+ continue;
+
+ if (serv->stopp->lvl & LVL_BOOT)
+#ifdef USE_KILL_IN_BOOT
+ out = halt;
+ else
+#else /* not USE_KILL_IN_BOOT */
+ continue;
+#endif /* not USE_KILL_IN_BOOT */
+ out = stop;
+
+ mark = false;
+ np_list_for_each(pos, &serv->sort.rev) {
+ req_t * rev = getreq(pos);
+ service_t * dep = rev->serv;
+ const char * name;
+
+ if (!dep)
+ continue;
+
+ if (dep->attr.flags & (SERV_DUPLET|SERV_NOSTOP))
+ continue; /* Duplet or no stop link */
+
+#if defined(MINIMAL_RULES) && (MINIMAL_RULES != 0)
+ if (dep->attr.ref <= 0)
+ continue;
+#endif /* not MINIMAL_RULES */
+
+ if ((serv->stopp->lvl & dep->stopp->lvl) == 0)
+ continue;
+
+ if ((name = dep->attr.script) == (char*)0)
+ continue;
+
+ if (!mark) {
+ fprintf(out, "%s:", target);
+ mark = true;
+ }
+ fprintf(out, " %s", name);
+ }
+ if (mark) fputc('\n', out);
+ }
+
+#ifdef USE_KILL_IN_BOOT
+ fclose(halt);
+#endif /* USE_KILL_IN_BOOT */
+ fclose(stop);
+}
+
+/*
+ * Internal logger
+ */
+char *myname = (char*)0;
+static void _logger (const char *restrict const fmt, va_list ap);
+static void _logger (const char *restrict const fmt, va_list ap)
+{
+ extension char buf[strlen(myname)+2+strlen(fmt)+1];
+ strcat(strcat(strcpy(buf, myname), ": "), fmt);
+ vfprintf(stderr, buf, ap);
+ return;
+}
+
+/*
+ * Cry and exit.
+ */
+void error (const char *restrict const fmt, ...)
+{
+ static char called;
+ va_list ap;
+ if (called++)
+ exit (1);
+ va_start(ap, fmt);
+ _logger(fmt, ap);
+ va_end(ap);
+ popd();
+ exit (1);
+}
+
+/*
+ * Warn the user.
+ */
+void warn (const char *restrict const fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ _logger(fmt, ap);
+ va_end(ap);
+ return;
+}
+
+/*
+ * Print message when verbose is enabled
+ */
+void info(const char *fmt, ...) {
+ va_list ap;
+ if (!verbose)
+ goto out;
+ va_start(ap, fmt);
+ _logger(fmt, ap);
+ va_end(ap);
+out:
+ return;
+}
+
+/*
+ * Check for script in list.
+ */
+static int curr_argc = -1;
+static inline boolean chkfor(const char *restrict const script,
+ char **restrict const list, const int cnt) attribute((nonnull(1,2)));
+static inline boolean chkfor(const char *restrict const script, char **restrict const list, const int cnt)
+{
+ boolean isinc = false;
+ register int c = cnt;
+
+ curr_argc = -1;
+ while (c--) {
+ if (*script != *list[c])
+ continue;
+ if (!strcmp(script, list[c])) {
+ isinc = true;
+ curr_argc = c;
+ break;
+ }
+ }
+ return isinc;
+}
+
+/*
+ * Open a runlevel directory, if it not
+ * exists than create one.
+ */
+static DIR * openrcdir(const char *restrict const rcpath) attribute((nonnull(1)));
+static DIR * openrcdir(const char *restrict const rcpath)
+{
+ DIR * rcdir;
+ struct stat st;
+ int dfd;
+
+ if (stat(rcpath, &st) < 0) {
+ if (errno == ENOENT) {
+ info("creating directory '%s'\n", rcpath);
+ if (!dryrun)
+ mkdir(rcpath, (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH));
+ } else
+ error("can not stat(%s): %s\n", rcpath, strerror(errno));
+ }
+
+ if ((rcdir = opendir(rcpath)) == (DIR*)0) {
+ if (dryrun)
+ warn ("can not opendir(%s): %s\n", rcpath, strerror(errno));
+ else
+ error("can not opendir(%s): %s\n", rcpath, strerror(errno));
+ }
+#if defined _XOPEN_SOURCE && (_XOPEN_SOURCE - 0) >= 600
+ else if ((dfd = dirfd(rcdir)) != 0) {
+ (void)posix_fadvise(dfd, 0, 0, POSIX_FADV_WILLNEED);
+ (void)posix_fadvise(dfd, 0, 0, POSIX_FADV_SEQUENTIAL);
+ }
+#endif
+ return rcdir;
+}
+
+/*
+ * Wrapper for regcomp(3)
+ */
+static inline void regcompiler(regex_t *restrict preg,
+ const char *restrict regex,
+ int cflags) attribute((always_inline,nonnull(1,2)));
+static inline void regcompiler(regex_t *restrict preg, const char *restrict regex, int cflags)
+{
+ register int ret = regcomp(preg, regex, cflags);
+ if (ret) {
+ regerror(ret, preg, buf, sizeof (buf));
+ regfree (preg);
+ error("%s\n", buf);
+ }
+ return;
+}
+
+/*
+ * Wrapper for regexec(3)
+ */
+static inline boolean regexecutor(regex_t *restrict preg,
+ const char *restrict string,
+ size_t nmatch, regmatch_t pmatch[], int eflags) attribute((nonnull(1,2)));
+static inline boolean regexecutor(regex_t *preg, const char *string,
+ size_t nmatch, regmatch_t pmatch[], int eflags)
+{
+ register int ret = regexec(preg, string, nmatch, pmatch, eflags);
+ if (ret > REG_NOMATCH) {
+ regerror(ret, preg, buf, sizeof (buf));
+ regfree (preg);
+ warn("%s\n", buf);
+ }
+ return (ret ? false : true);
+}
+
+/*
+ * The script scanning engine.
+ * We have to alloc the regular expressions first before
+ * calling scan_script_defaults(). After the last call
+ * of scan_script_defaults() we may free the expressions.
+ */
+static inline void scan_script_regalloc(void) attribute((always_inline));
+static inline void scan_script_regalloc(void)
+{
+ regcompiler(®.prov, PROVIDES, REG_EXTENDED|REG_ICASE);
+ regcompiler(®.req_start, REQUIRED_START, REG_EXTENDED|REG_ICASE|REG_NEWLINE);
+ regcompiler(®.req_stop, REQUIRED_STOP, REG_EXTENDED|REG_ICASE|REG_NEWLINE);
+ regcompiler(®.shl_start, SHOULD_START, REG_EXTENDED|REG_ICASE|REG_NEWLINE);
+ regcompiler(®.shl_stop, SHOULD_STOP, REG_EXTENDED|REG_ICASE|REG_NEWLINE);
+ regcompiler(®.start_bf, START_BEFORE, REG_EXTENDED|REG_ICASE|REG_NEWLINE);
+ regcompiler(®.stop_af, STOP_AFTER, REG_EXTENDED|REG_ICASE|REG_NEWLINE);
+ regcompiler(®.def_start, DEFAULT_START, REG_EXTENDED|REG_ICASE|REG_NEWLINE);
+ regcompiler(®.def_stop, DEFAULT_STOP, REG_EXTENDED|REG_ICASE|REG_NEWLINE);
+ regcompiler(®.desc, DESCRIPTION, REG_EXTENDED|REG_ICASE|REG_NEWLINE);
+}
+
+static inline void scan_script_reset(void) attribute((always_inline));
+static inline void scan_script_reset(void)
+{
+ xreset(script_inf.provides);
+ xreset(script_inf.required_start);
+ xreset(script_inf.required_stop);
+ xreset(script_inf.should_start);
+ xreset(script_inf.should_stop);
+ xreset(script_inf.start_before);
+ xreset(script_inf.stop_after);
+ xreset(script_inf.default_start);
+ xreset(script_inf.default_stop);
+ xreset(script_inf.description);
+}
+
+#define FOUND_LSB_HEADER 0x01
+#define FOUND_LSB_DEFAULT 0x02
+#define FOUND_LSB_OVERRIDE 0x04
+
+static int o_flags = O_RDONLY;
+
+static uchar scan_lsb_headers(const int dfd, const char *restrict const path,
+ const boolean cache, const boolean ignore) attribute((nonnull(2)));
+static uchar scan_lsb_headers(const int dfd, const char *restrict const path,
+ const boolean cache, const boolean ignore)
+{
+ regmatch_t subloc[SUBNUM_SHD+1], *val = &subloc[SUBNUM-1], *shl = &subloc[SUBNUM_SHD-1];
+ char *begin = (char*)0, *end = (char*)0;
+ char *pbuf = buf;
+ FILE *script;
+ uchar ret = 0;
+ int fd;
+
+#define provides script_inf.provides
+#define required_start script_inf.required_start
+#define required_stop script_inf.required_stop
+#define should_start script_inf.should_start
+#define should_stop script_inf.should_stop
+#define start_before script_inf.start_before
+#define stop_after script_inf.stop_after
+#define default_start script_inf.default_start
+#define default_stop script_inf.default_stop
+#define description script_inf.description
+
+ info("Loading %s\n", path);
+
+ if ((fd = xopen(dfd, path, o_flags)) < 0 || (script = fdopen(fd, "r")) == (FILE*)0)
+ error("fopen(%s): %s\n", path, strerror(errno));
+
+#if defined _XOPEN_SOURCE && (_XOPEN_SOURCE - 0) >= 600
+ (void)posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
+#endif
+
+#define COMMON_ARGS buf, SUBNUM, subloc, 0
+#define COMMON_SHD_ARGS buf, SUBNUM_SHD, subloc, 0
+ while (fgets(buf, sizeof(buf), script)) {
+
+ /* Skip scanning above from LSB magic start */
+ if (!begin) {
+ if ( (begin = strstr(buf, "### BEGIN INIT INFO")) ) {
+ /* Let the latest LSB header override the one found earlier */
+ scan_script_reset();
+ }
+ continue;
+ }
+
+ if (!provides && regexecutor(®.prov, COMMON_ARGS) == true) {
+ if (val->rm_so < val->rm_eo) {
+ *(pbuf+val->rm_eo) = '\0';
+ provides = xstrdup(pbuf+val->rm_so);
+ } else
+ provides = empty;
+ }
+ if (!required_start && regexecutor(®.req_start, COMMON_ARGS) == true) {
+ if (val->rm_so < val->rm_eo) {
+ *(pbuf+val->rm_eo) = '\0';
+ required_start = xstrdup(pbuf+val->rm_so);
+ } else
+ required_start = empty;
+ }
+ if (!required_stop && regexecutor(®.req_stop, COMMON_ARGS) == true) {
+ if (val->rm_so < val->rm_eo) {
+ *(pbuf+val->rm_eo) = '\0';
+ required_stop = xstrdup(pbuf+val->rm_so);
+ } else
+ required_stop = empty;
+ }
+ if (!should_start && regexecutor(®.shl_start, COMMON_SHD_ARGS) == true) {
+ if (shl->rm_so < shl->rm_eo) {
+ *(pbuf+shl->rm_eo) = '\0';
+ should_start = xstrdup(pbuf+shl->rm_so);
+ } else
+ should_start = empty;
+ }
+ if (!should_stop && regexecutor(®.shl_stop, COMMON_SHD_ARGS) == true) {
+ if (shl->rm_so < shl->rm_eo) {
+ *(pbuf+shl->rm_eo) = '\0';
+ should_stop = xstrdup(pbuf+shl->rm_so);
+ } else
+ should_stop = empty;
+ }
+ if (!start_before && regexecutor(®.start_bf, COMMON_SHD_ARGS) == true) {
+ if (shl->rm_so < shl->rm_eo) {
+ *(pbuf+shl->rm_eo) = '\0';
+ start_before = xstrdup(pbuf+shl->rm_so);
+ } else
+ start_before = empty;
+ }
+ if (!stop_after && regexecutor(®.stop_af, COMMON_SHD_ARGS) == true) {
+ if (shl->rm_so < shl->rm_eo) {
+ *(pbuf+shl->rm_eo) = '\0';
+ stop_after = xstrdup(pbuf+shl->rm_so);
+ } else
+ stop_after = empty;
+ }
+ if (!default_start && regexecutor(®.def_start, COMMON_ARGS) == true) {
+ if (val->rm_so < val->rm_eo) {
+ *(pbuf+val->rm_eo) = '\0';
+ default_start = xstrdup(pbuf+val->rm_so);
+ } else
+ default_start = empty;
+ }
+#ifndef SUSE
+ if (!default_stop && regexecutor(®.def_stop, COMMON_ARGS) == true) {
+ if (val->rm_so < val->rm_eo) {
+ *(pbuf+val->rm_eo) = '\0';
+ default_stop = xstrdup(pbuf+val->rm_so);
+ } else
+ default_stop = empty;
+ }
+#endif
+ if (!description && regexecutor(®.desc, COMMON_ARGS) == true) {
+ if (val->rm_so < val->rm_eo) {
+ *(pbuf+val->rm_eo) = '\0';
+ description = xstrdup(pbuf+val->rm_so);
+ } else
+ description = empty;
+ }
+
+ /* Skip scanning below from LSB magic end */
+ if ((end = strstr(buf, "### END INIT INFO")))
+ break;
+ }
+#undef COMMON_ARGS
+#undef COMMON_SHD_ARGS
+
+#if defined _XOPEN_SOURCE && (_XOPEN_SOURCE - 0) >= 600
+ if (cache) {
+ off_t deep = ftello(script);
+ (void)posix_fadvise(fd, 0, deep, POSIX_FADV_WILLNEED);
+ (void)posix_fadvise(fd, deep, 0, POSIX_FADV_DONTNEED);
+ } else
+ (void)posix_fadvise(fd, 0, 0, POSIX_FADV_NOREUSE);
+#endif
+
+ fclose(script);
+
+ if (begin && end)
+ ret |= FOUND_LSB_HEADER;
+
+ if (begin && !end) {
+ char *name = basename(path);
+ if (*name == 'S' || *name == 'K')
+ name += 3;
+ warn("Script %s is broken: missing end of LSB comment.\n", name);
+ if (!ignore)
+ error("exiting now!\n");
+ }
+
+ if (begin && end && (!provides || (provides == empty) ||
+#ifdef SUSE
+ !required_start || !required_stop || !default_start
+#else /* not SUSE */
+ !required_start || !required_stop || !default_start || !default_stop
+#endif /* not SUSE */
+ ))
+ {
+ char *name = basename(path);
+ if (*name == 'S' || *name == 'K')
+ name += 3;
+ warn("Script %s is broken: incomplete LSB comment.\n", name);
+ if (!provides)
+ warn("missing `Provides:' entry: please add.\n");
+ if (provides == empty)
+ warn("missing valid name for `Provides:' please add.\n");
+ if (!required_start)
+ warn("missing `Required-Start:' entry: please add even if empty.\n");
+ if (!required_stop)
+ warn("missing `Required-Stop:' entry: please add even if empty.\n");
+ if (!default_start)
+ warn("missing `Default-Start:' entry: please add even if empty.\n");
+#ifndef SUSE
+ if (!default_stop)
+ warn("missing `Default-Stop:' entry: please add even if empty.\n");
+#endif
+ }
+
+#undef provides
+#undef required_start
+#undef required_stop
+#undef should_start
+#undef should_stop
+#undef start_before
+#undef stop_after
+#undef default_start
+#undef default_stop
+#undef description
+ return ret;
+}
+
+/*
+ * Follow symlinks, return the basename of the file pointed to by
+ * symlinks or the basename of the current path if no symlink.
+ */
+static char * scriptname(int dfd, const char *restrict const path, char **restrict first) attribute((malloc,nonnull(2)));
+static char * scriptname(int dfd, const char *restrict const path, char **restrict first)
+{
+ uint deep = 0;
+ char linkbuf[PATH_MAX+1];
+ char *script = xstrdup(path);
+
+ strncpy(linkbuf, script, sizeof(linkbuf)-1);
+ linkbuf[PATH_MAX] = '\0';
+
+ do {
+ struct stat st;
+ int linklen;
+
+ if (deep++ > MAXSYMLINKS) {
+ errno = ELOOP;
+ warn("Can not determine script name for %s: %s\n", path, strerror(errno));
+ break;
+ }
+
+ if (xlstat(dfd, script, &st) < 0) {
+ warn("Can not stat %s: %s\n", script, strerror(errno));
+ break;
+ }
+
+ if (!S_ISLNK(st.st_mode))
+ break;
+
+ if ((linklen = xreadlink(dfd, script, linkbuf, sizeof(linkbuf)-1)) < 0)
+ break;
+ linkbuf[linklen] = '\0';
+
+ if (linkbuf[0] != '/') { /* restore relative links */
+ const char *lastslash;
+
+ if ((lastslash = strrchr(script, '/'))) {
+ size_t dirname_len = lastslash - script + 1;
+
+ if (dirname_len + linklen > PATH_MAX)
+ linklen = PATH_MAX - dirname_len;
+
+ memmove(&linkbuf[dirname_len], &linkbuf[0], linklen + 1);
+ memcpy(&linkbuf[0], script, dirname_len);
+ }
+ }
+
+ free(script);
+ script = xstrdup(linkbuf);
+
+ if (deep == 1 && first)
+ *first = xstrdup(basename(linkbuf));
+
+ } while (1);
+
+ free(script);
+ script = xstrdup(basename(linkbuf));
+
+ return script;
+}
+
+static uchar load_overrides(const char *restrict const dir,
+ const char *restrict const name,
+ const boolean cache, const boolean ignore) attribute((nonnull(1,2)));
+static uchar load_overrides(const char *restrict const dir,
+ const char *restrict const name,
+ const boolean cache, const boolean ignore)
+{
+ uchar ret = 0;
+ char fullpath[PATH_MAX+1];
+ struct stat statbuf;
+ int n;
+
+ n = snprintf(&fullpath[0], sizeof(fullpath), "%s%s/%s", (root && !set_override) ? root : "", dir, name);
+ if (n >= (int)sizeof(fullpath) || n < 0)
+ error("snprintf(): %s\n", strerror(errno));
+
+ if (stat(fullpath, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
+ ret = scan_lsb_headers(-1, fullpath, cache, ignore);
+ if (ret & FOUND_LSB_HEADER)
+ ret |= FOUND_LSB_OVERRIDE;
+ return ret;
+}
+
+static uchar scan_script_defaults(int dfd, const char *const restrict path,
+ const char *const restrict override_path,
+ char **restrict first,
+ const boolean cache, const boolean ignore) attribute((nonnull(2,3)));
+static uchar scan_script_defaults(int dfd, const char *restrict const path,
+ const char *restrict const override_path,
+ char **restrict first,
+ const boolean cache, const boolean ignore)
+{
+ char * name = scriptname(dfd, path, first);
+ uchar ret = 0;
+
+ if (!name)
+ return ret;
+
+ /* Reset old results */
+ scan_script_reset();
+
+#ifdef SUSE
+ /* Common script ... */
+ if (!strcmp(name, "halt")) {
+ ret |= (FOUND_LSB_HEADER|FOUND_LSB_DEFAULT);
+ goto out;
+ }
+
+ /* ... and its link */
+ if (!strcmp(name, "reboot")) {
+ ret |= (FOUND_LSB_HEADER|FOUND_LSB_DEFAULT);
+ goto out;
+ }
+
+ /* Common script for single mode */
+ if (!strcmp(name, "single")) {
+ ret |= (FOUND_LSB_HEADER|FOUND_LSB_DEFAULT);
+ goto out;
+ }
+#endif /* SUSE */
+
+ /* Replace with headers from the script itself */
+ ret |= scan_lsb_headers(dfd, path, cache, ignore);
+
+ /* Load values if the override file exist */
+ if ((ret & FOUND_LSB_HEADER) == 0)
+ ret |= load_overrides("/usr/share/insserv/overrides", name, cache, ignore);
+ else
+ ret |= FOUND_LSB_DEFAULT;
+
+ /*
+ * Allow host-specific overrides to replace the content in the
+ * init.d scripts
+ */
+ ret |= load_overrides(override_path, name, cache, ignore);
+#ifdef SUSE
+out:
+#endif /* SUSE */
+ free(name);
+ return ret;
+}
+
+static inline void scan_script_regfree() attribute((always_inline));
+static inline void scan_script_regfree()
+{
+ regfree(®.prov);
+ regfree(®.req_start);
+ regfree(®.req_stop);
+ regfree(®.shl_start);
+ regfree(®.shl_stop);
+ regfree(®.start_bf);
+ regfree(®.stop_af);
+ regfree(®.def_start);
+ regfree(®.def_stop);
+ regfree(®.desc);
+}
+
+static struct {
+ char *location;
+ const ushort lvl;
+ const ushort seek;
+ const char key;
+} attribute((aligned(sizeof(char*)))) runlevel_locations[] = {
+#ifdef SUSE /* SuSE's SystemV link scheme */
+ {"rc0.d/", LVL_HALT, LVL_NORM, '0'},
+ {"rc1.d/", LVL_ONE, LVL_NORM, '1'}, /* runlevel 1 switch over to single user mode */
+ {"rc2.d/", LVL_TWO, LVL_NORM, '2'},
+ {"rc3.d/", LVL_THREE, LVL_NORM, '3'},
+ {"rc4.d/", LVL_FOUR, LVL_NORM, '4'},
+ {"rc5.d/", LVL_FIVE, LVL_NORM, '5'},
+ {"rc6.d/", LVL_REBOOT, LVL_NORM, '6'},
+ {"rcS.d/", LVL_SINGLE, LVL_NORM, 'S'}, /* runlevel S is for single user mode */
+ {"boot.d/", LVL_BOOT, LVL_BOOT, 'B'}, /* runlevel B is for system initialization */
+#else /* not SUSE (actually, Debian) */
+ {"../rc0.d/", LVL_HALT, LVL_NORM, '0'},
+ {"../rc1.d/", LVL_ONE, LVL_NORM, '1'}, /* runlevel 1 switch over to single user mode */
+ {"../rc2.d/", LVL_TWO, LVL_NORM, '2'},
+ {"../rc3.d/", LVL_THREE, LVL_NORM, '3'},
+ {"../rc4.d/", LVL_FOUR, LVL_NORM, '4'},
+ {"../rc5.d/", LVL_FIVE, LVL_NORM, '5'},
+ {"../rc6.d/", LVL_REBOOT, LVL_NORM, '6'},
+ {"../rcS.d/", LVL_BOOT, LVL_BOOT, 'S'}, /* runlevel S is for system initialization */
+ /* On e.g. Debian there exist no boot.d */
+#endif /* not SUSE */
+};
+
+#define RUNLEVLES (int)(sizeof(runlevel_locations)/sizeof(runlevel_locations[0]))
+
+int map_has_runlevels(void)
+{
+ return RUNLEVLES;
+}
+
+char map_runlevel_to_key(const int runlevel)
+{
+ if (runlevel >= RUNLEVLES) {
+ warn("Wrong runlevel %d\n", runlevel);
+ }
+ return runlevel_locations[runlevel].key;
+}
+
+ushort map_key_to_lvl(const char key)
+{
+ int runlevel;
+ const char uckey = toupper(key);
+ for (runlevel = 0; runlevel < RUNLEVLES; runlevel++) {
+ if (uckey == runlevel_locations[runlevel].key)
+ return runlevel_locations[runlevel].lvl;
+ }
+ warn("Wrong runlevel key '%c'\n", uckey);
+ return 0;
+}
+
+const char *map_runlevel_to_location(const int runlevel)
+{
+ if (runlevel >= RUNLEVLES) {
+ warn("Wrong runlevel %d\n", runlevel);
+ }
+ return runlevel_locations[runlevel].location;
+}
+
+ushort map_runlevel_to_lvl(const int runlevel)
+{
+ if (runlevel >= RUNLEVLES) {
+ warn("Wrong runlevel %d\n", runlevel);
+ }
+ return runlevel_locations[runlevel].lvl;
+}
+
+ushort map_runlevel_to_seek(const int runlevel)
+{
+ return runlevel_locations[runlevel].seek;
+}
+
+/*
+ * Two helpers for runlevel bits and strings.
+ */
+ushort str2lvl(const char *restrict lvl)
+{
+ char * token, *tmp = strdupa(lvl);
+ ushort ret = 0;
+
+ if (!tmp)
+ error("%s", strerror(errno));
+
+ while ((token = strsep(&tmp, delimeter))) {
+ if (!*token || strlen(token) != 1)
+ continue;
+ if (!strpbrk(token, "0123456sSbB"))
+ continue;
+
+ ret |= map_key_to_lvl(*token);
+ }
+
+ return ret;
+}
+
+char * lvl2str(const ushort lvl)
+{
+ char * ptr, * last;
+ char str[20];
+ int num;
+ uint bit = 0x001;
+
+ last = ptr = &str[0];
+ memset(ptr, '\0', sizeof(str));
+ for (num = 0; num < RUNLEVLES; num++) {
+ if (bit & lvl) {
+ if (ptr > last)
+ *ptr++ = ' ';
+ last = ptr;
+ if (LVL_NORM & bit)
+ *ptr++ = num + 48;
+#ifdef SUSE
+ else if (LVL_SINGLE & bit)
+ *ptr++ = 'S';
+ else if (LVL_BOOT & bit)
+ *ptr++ = 'B';
+#else /* not SUSE */
+ else if (LVL_BOOT & bit)
+ *ptr++ = 'S';
+#endif /* not SUSE */
+ else
+ error("Wrong runlevel %d\n", num);
+ }
+ bit <<= 1;
+ }
+ if (strlen(str) == 0)
+ return (char*)0;
+ return xstrdup(str);
+}
+
+/*
+ * Scan current service structure
+ */
+static void scan_script_locations(const char *const restrict path,
+ const char *const restrict override_path,
+ const boolean ignore) attribute((nonnull(1,2)));
+static void scan_script_locations(const char *const path, const char *const override_path,
+ const boolean ignore)
+{
+ int runlevel;
+
+ pushd(path);
+ for (runlevel = 0; runlevel < RUNLEVLES; runlevel++) {
+ const char * rcd = (char*)0;
+ struct stat st_script;
+ struct dirent *d;
+ DIR * rcdir;
+ char * token;
+ int dfd;
+
+ rcd = map_runlevel_to_location(runlevel);
+
+ rcdir = openrcdir(rcd); /* Creates runlevel directory if necessary */
+ if (rcdir == (DIR*)0)
+ break;
+ if ((dfd = dirfd(rcdir)) < 0) {
+ closedir(rcdir);
+ break;
+ }
+ pushd(rcd);
+
+ while ((d = readdir(rcdir)) != (struct dirent*)0) {
+ char * name = (char *)0;
+ char * ptr = d->d_name;
+ service_t * first;
+ char * begin; /* Remember address of ptr handled by strsep() */
+ char order;
+ uchar lsb;
+ char type;
+
+ if (*ptr != 'S' && *ptr != 'K')
+ continue;
+ type = *ptr;
+ ptr++;
+
+ if (strspn(ptr, "0123456789") < 2)
+ continue;
+ order = atoi(ptr);
+ ptr += 2;
+
+ if (xstat(dfd, d->d_name, &st_script) < 0) {
+ xremove(dfd, d->d_name); /* dangling sym link */
+ continue;
+ }
+
+ lsb = scan_script_defaults(dfd, d->d_name, override_path, &name, true, ignore);
+ if (!script_inf.provides || script_inf.provides == empty)
+ script_inf.provides = xstrdup(ptr);
+
+#ifndef SUSE
+ if (!lsb) {
+ script_inf.required_start = xstrdup(DEFAULT_DEPENDENCY);
+ script_inf.required_stop = xstrdup(DEFAULT_DEPENDENCY);
+ }
+#endif /* not SUSE */
+
+ first = (service_t*)0;
+ begin = script_inf.provides;
+ while ((token = strsep(&begin, delimeter)) && *token) {
+ service_t * service;
+
+ if (*token == '$') {
+ warn("script %s provides system facility %s, skipped!\n", d->d_name, token);
+ continue;
+ }
+ if (*token == '#') {
+ warn("script %s provides facility %s with comment sign, skipped!\n", d->d_name, token);
+ continue;
+ }
+
+ service = current_structure(token, order, runlevel, type);
+
+ if (first)
+ nickservice(first, service);
+ else
+ first = service;
+
+ if (!makeprov(service, name))
+ continue;
+
+ ++service->attr.ref; /* May enabled in several levels */
+
+ if (service->attr.flags & SERV_KNOWN)
+ continue;
+ service->attr.flags |= (SERV_KNOWN|SERV_ENABLED);
+
+ if (!lsb)
+ service->attr.flags |= SERV_NOTLSB;
+
+ if ((lsb & FOUND_LSB_HEADER) == 0) {
+ if ((lsb & (FOUND_LSB_DEFAULT|FOUND_LSB_OVERRIDE)) == 0)
+ warn("warning: script '%s' missing LSB tags and overrides\n", d->d_name);
+ else
+ warn("warning: script '%s' missing LSB tags\n", d->d_name);
+ }
+
+ if (script_inf.required_start && script_inf.required_start != empty) {
+ rememberreq(service, REQ_MUST, script_inf.required_start);
+#ifdef USE_COMPAT_EMPTY
+ if (!script_inf.required_stop || script_inf.required_stop == empty)
+ script_inf.required_stop = xstrdup(script_inf.required_start);
+#endif /* USE_COMPAT_EMPTY */
+ }
+ if (script_inf.should_start && script_inf.should_start != empty) {
+ rememberreq(service, REQ_SHLD, script_inf.should_start);
+#ifdef USE_COMPAT_EMPTY
+ if (!script_inf.should_stop || script_inf.should_stop == empty)
+ script_inf.should_stop = xstrdup(script_inf.should_start);
+#endif /* USE_COMPAT_EMPTY */
+ }
+ if (script_inf.start_before && script_inf.start_before != empty) {
+ reversereq(service, REQ_SHLD, script_inf.start_before);
+#ifdef USE_COMPAT_EMPTY
+ if (!script_inf.stop_after || script_inf.stop_after == empty)
+ script_inf.stop_after = xstrdup(script_inf.start_before);
+#endif /* USE_COMPAT_EMPTY */
+ }
+ if (script_inf.required_stop && script_inf.required_stop != empty) {
+ rememberreq(service, REQ_MUST|REQ_KILL, script_inf.required_stop);
+ }
+ if (script_inf.should_stop && script_inf.should_stop != empty) {
+ rememberreq(service, REQ_SHLD|REQ_KILL, script_inf.should_stop);
+ }
+ if (script_inf.stop_after && script_inf.stop_after != empty) {
+ reversereq(service, REQ_SHLD|REQ_KILL, script_inf.stop_after);
+ }
+ }
+
+ if (name)
+ xreset(name);
+
+ scan_script_reset();
+
+ } /* while ((token = strsep(&begin, delimeter)) && *token) */
+
+ popd();
+ closedir(rcdir);
+ }
+ popd();
+ return;
+}
+
+/*
+ * The /etc/insserv.conf scanning engine.
+ */
+static void scan_conf_file(const char *restrict file) attribute((nonnull(1)));
+static void scan_conf_file(const char *restrict file)
+{
+ regmatch_t subloc[SUBCONFNUM], *val = (regmatch_t*)0;
+ FILE *conf;
+
+ info("Loading %s\n", file);
+
+ do {
+ const char * fptr = file;
+ if (*fptr == '/')
+ fptr++;
+ /* Try relativ location first */
+ if ((conf = fopen(fptr, "r")))
+ break;
+ /* Try absolute location */
+ if ((conf = fopen(file, "r")))
+ break;
+ goto err;
+ } while (1);
+
+ while (fgets(buf, sizeof(buf), conf)) {
+ char *pbuf = &buf[0];
+ if (*pbuf == '#')
+ continue;
+ if (*pbuf == '\n')
+ continue;
+ if (regexecutor(&creg.isysfaci, buf, SUBCONFNUM, subloc, 0) == true) {
+ char * virt = (char*)0, * real = (char*)0;
+ val = &subloc[SUBCONF - 1];
+ if (val->rm_so < val->rm_eo) {
+ *(pbuf+val->rm_eo) = '\0';
+ virt = pbuf+val->rm_so;
+ }
+ val = &subloc[SUBCONFNUM - 1];
+ if (val->rm_so < val->rm_eo) {
+ *(pbuf+val->rm_eo) = '\0';
+ real = pbuf+val->rm_so;
+ }
+ if (virt) {
+ list_t * ptr;
+ boolean found = false;
+ list_for_each(ptr, sysfaci_start) {
+ if (!strcmp(getfaci(ptr)->name, virt)) {
+ found = true;
+ if(real) {
+ list_t * r_list = &getfaci(ptr)->replace;
+ char * token;
+ while ((token = strsep(&real, delimeter))) {
+ repl_t *restrict subst;
+ string_t * r;
+ if (posix_memalign((void*)&subst, sizeof(void*), alignof(repl_t)) != 0)
+ error("%s", strerror(errno));
+ insert(&subst->r_list, r_list->prev);
+ r = &subst->r[0];
+ if (posix_memalign((void*)&r->ref, sizeof(void*), alignof(typeof(r->ref))+strsize(token)) != 0)
+ error("%s", strerror(errno));
+ *r->ref = 1;
+ r->name = ((char*)(r->ref))+alignof(typeof(r->ref));
+ strcpy(r->name, token);
+ }
+ }
+ break;
+ }
+ }
+ if (!found) {
+ faci_t *restrict this;
+ if (posix_memalign((void*)&this, sizeof(void*), alignof(faci_t)) != 0)
+ error("%s", strerror(errno));
+ else {
+ list_t * r_list = &this->replace;
+ char * token;
+ r_list->next = r_list;
+ r_list->prev = r_list;
+ insert(&this->list, sysfaci_start->prev);
+ this->name = xstrdup(virt);
+ while ((token = strsep(&real, delimeter))) {
+ repl_t *restrict subst;
+ string_t * r;
+ if (posix_memalign((void*)&subst, sizeof(void*), alignof(repl_t)) != 0)
+ error("%s", strerror(errno));
+ insert(&subst->r_list, r_list->prev);
+ r = &subst->r[0];
+ if (posix_memalign((void*)&r->ref, sizeof(void*), alignof(typeof(r->ref))+strsize(token)) != 0)
+ error("%s", strerror(errno));
+ *r->ref = 1;
+ r->name = ((char*)(r->ref))+alignof(typeof(r->ref));
+ strcpy(r->name, token);
+ }
+ }
+ }
+ }
+ }
+ if (regexecutor(&creg.isactive, buf, SUBCONFNUM, subloc, 0) == true) {
+ char * key = (char*)0, * servs = (char*)0;
+ val = &subloc[SUBCONF - 1];
+ if (val->rm_so < val->rm_eo) {
+ *(pbuf+val->rm_eo) = '\0';
+ key = pbuf+val->rm_so;
+ }
+ val = &subloc[SUBCONFNUM - 1];
+ if (val->rm_so < val->rm_eo) {
+ *(pbuf+val->rm_eo) = '\0';
+ servs = pbuf+val->rm_so;
+ }
+ if (key && *key == '<' && servs && *servs) {
+ if (!strncmp("<interactive>", key, strlen(key))) {
+ char * token;
+ while ((token = strsep(&servs, delimeter))) {
+ service_t *service = addservice(token);
+ service = getorig(service);
+ service->attr.flags |= SERV_INTRACT;
+ }
+ }
+ }
+ }
+ }
+
+ fclose(conf);
+ return;
+err:
+ warn("fopen(%s): %s\n", file, strerror(errno));
+}
+
+static int cfgfile_filter(const struct dirent *restrict d) attribute((nonnull(1)));
+static int cfgfile_filter(const struct dirent *restrict d)
+{
+ boolean ret = false;
+ const char * name = d->d_name;
+ const char * end;
+
+ if (*name == '.')
+ goto out;
+ if (!name || (*name == '\0'))
+ goto out;
+ if ((end = strrchr(name, '.'))) {
+ end++;
+ if (!strncmp(end, "rpm", 3) || /* .rpmorig, .rpmnew, .rmpsave, ... */
+ !strncmp(end, "ba", 2) || /* .bak, .backup, ... */
+#ifdef SUSE
+ !strcmp(end, "local") || /* .local are sourced by the basename */
+#endif /* not SUSE */
+ !strcmp(end, "old") ||
+ !strcmp(end, "new") ||
+ !strcmp(end, "org") ||
+ !strcmp(end, "orig") ||
+ !strncmp(end, "dpkg", 3) || /* .dpkg-old, .dpkg-new ... */
+ !strcmp(end, "save") ||
+ !strcmp(end, "swp") || /* Used by vi like editors */
+ !strcmp(end, "core")) /* modern core dump */
+ {
+ goto out;
+ }
+ }
+ if ((end = strrchr(name, ','))) {
+ end++;
+ if (!strcmp(end, "v")) /* rcs-files */
+ goto out;
+ }
+ ret = true;
+out:
+ return (int)ret;
+}
+
+static void scan_conf(const char *restrict file) attribute((nonnull(1)));
+static void scan_conf(const char *restrict file)
+{
+ struct dirent** namelist = (struct dirent**)0;
+ char path[PATH_MAX+1];
+ int n;
+
+ regcompiler(&creg.isysfaci, CONFLINE, REG_EXTENDED|REG_ICASE);
+ regcompiler(&creg.isactive, CONFLINE2, REG_EXTENDED|REG_ICASE);
+
+ n = snprintf(&path[0], sizeof(path), "%s%s", (root && !set_insconf) ? root : "", file);
+ if (n >= (int)sizeof(path) || n < 0)
+ error("snprintf(): %s\n", strerror(errno));
+
+ scan_conf_file(path);
+
+ n = snprintf(&path[0], sizeof(path), "%s%s.d", (root && !set_insconf) ? root : "", file);
+ if (n >= (int)sizeof(path) || n < 0)
+ error("snprintf(): %s\n", strerror(errno));
+
+ n = scandir(path, &namelist, cfgfile_filter, alphasort);
+ if(n > 0) {
+ while(n--) {
+ struct stat st;
+ char buf[PATH_MAX+1];
+ int r;
+
+ r = snprintf(&buf[0], sizeof(buf), "%s/%s", path, namelist[n]->d_name);
+ if (r >= (int)sizeof(buf) || r < 0)
+ error("snprintf(): %s\n", strerror(errno));
+
+ if ((stat(buf, &st) < 0) || !S_ISREG(st.st_mode))
+ continue;
+
+ scan_conf_file(buf);
+
+ free(namelist[n]);
+ }
+ }
+
+ if (namelist)
+ free(namelist);
+
+ regfree(&creg.isysfaci);
+ regfree(&creg.isactive);
+}
+
+static void expand_faci(list_t *restrict rlist, list_t *restrict head,
+ int *restrict deep) attribute((noinline,nonnull(1,2,3)));
+static void expand_faci(list_t *restrict rlist, list_t *restrict head, int *restrict deep)
+{
+ repl_t * rent = getrepl(rlist);
+ list_t * tmp, * safe, * ptr = (list_t*)0;
+
+ list_for_each(tmp, sysfaci_start) {
+ if (!strcmp(getfaci(tmp)->name, rent->r[0].name)) {
+ ptr = &getfaci(tmp)->replace;
+ break;
+ }
+ }
+
+ if (!ptr || list_empty(ptr)) {
+ delete(rlist);
+ if (--(*rent->r[0].ref) <= 0)
+ free(rent->r[0].ref);
+ free(rent);
+ goto out;
+ }
+
+ if ((*deep)++ > 10) {
+ warn("The nested level of the system facilities in the insserv.conf file(s) is to large\n");
+ goto out;
+ }
+
+ list_for_each_safe(tmp, safe, ptr) {
+ repl_t * rnxt = getrepl(tmp);
+ if (*rnxt->r[0].name == '$') {
+ expand_faci(tmp, head, deep);
+ } else {
+ if (*deep == 1) {
+ if (--(*rent->r[0].ref) <= 0)
+ free(rent->r[0].ref);
+ rent->r[0] = rnxt->r[0];
+ ++(*rent->r[0].ref);
+ } else {
+ repl_t *restrict subst;
+ if (posix_memalign((void*)&subst, sizeof(void*), alignof(repl_t)) != 0)
+ error("%s", strerror(errno));
+ insert(&subst->r_list, head);
+ subst->r[0] = rnxt->r[0];
+ ++(*subst->r[0].ref);
+ }
+ }
+ }
+out:
+ (*deep)--;
+ return;
+}
+
+static inline void expand_conf(void)
+{
+ list_t *ptr;
+ list_for_each(ptr, sysfaci_start) {
+ list_t * rlist, * safe, * head = &getfaci(ptr)->replace;
+ list_for_each_safe(rlist, safe, head) {
+ if (*getrepl(rlist)->r[0].name == '$') {
+ int deep = 0;
+ expand_faci(rlist, rlist, &deep);
+ }
+ }
+ }
+}
+
+/*
+ * Scan for a Start or Kill script within a runlevel directory.
+ * We start were we leave the directory, the upper level
+ * has to call rewinddir(3) if necessary.
+ */
+static inline char * scan_for(DIR *const restrict rcdir,
+ const char *const restrict script,
+ const char type) attribute((always_inline,nonnull(1,2)));
+static inline char * scan_for(DIR *const rcdir,
+ const char *const script, const char type)
+{
+ struct dirent *d;
+ char * ret = (char*)0;
+
+ while ((d = readdir(rcdir)) != (struct dirent*)0) {
+ char * ptr = d->d_name;
+
+ if (*ptr != type)
+ continue;
+ ptr++;
+
+ if (strspn(ptr, "0123456789") < 2)
+ continue;
+ ptr += 2;
+
+ if (!strcmp(ptr, script)) {
+ ret = d->d_name;
+ break;
+ }
+ }
+ return ret;
+}
+
+#ifdef SUSE
+/*
+ * A simple command line checker of the parent process to determine if this is
+ * a sub process "/bin/sh" forked off for executing a temporary file for %preun,
+ * %postun, %pre, or %post scriptlet.
+ */
+static inline boolean underrpm(void)
+{
+ boolean ret = false;
+ const pid_t pp = getppid();
+ char buf[PATH_MAX], *argv[3], *ptr;
+# if defined(USE_RPMLIB) && (USE_RPMLIB > 0)
+ char *tmppath, *shell;
+# endif /* USE_RPMLIB */
+ int argc, fd;
+ ssize_t len;
+
+ snprintf(buf, sizeof(buf)-1, "/proc/%lu/cmdline", (unsigned long)pp);
+ if ((fd = open(buf, O_NOCTTY|O_RDONLY)) < 0)
+ goto out;
+
+ memset(buf, '\0', sizeof(buf));
+ if ((len = read(fd , buf, sizeof(buf)-1)) < 0)
+ goto out;
+
+ ptr = &buf[0];
+ argc = 0;
+ do {
+ argv[argc++] = ptr;
+ if (argc > 2)
+ break;
+ if ((len = len - (ssize_t)(ptr - &buf[0])) < 0)
+ break;
+ } while ((ptr = memchr(ptr, '\0', len)) && *(++ptr));
+
+ if (argc != 3)
+ goto out;
+
+# if defined(USE_RPMLIB) && (USE_RPMLIB > 0)
+ rpmReadConfigFiles(NULL, NULL);
+ rpmFreeRpmrc();
+
+ if ((shell = rpmExpand("%_buildshell", NULL)) == NULL)
+ shell = xstrdup("/bin/sh");
+
+ if (strncmp(argv[0], shell, strlen(shell)) != 0) {
+ free(shell);
+ goto out;
+ }
+ free(shell);
+
+ if ((tmppath = rpmExpand("%_tmppath", NULL)) == NULL)
+ tmppath = xstrdup("/var/tmp");
+
+ if (strncmp(argv[1], tmppath, strlen(tmppath)) != 0) {
+ free(tmppath);
+ goto out;
+ }
+
+ len = strlen(tmppath);
+ free(tmppath);
+
+ ptr = argv[1];
+ if (strncmp(ptr + len, "/rpm-tmp.", 9) != 0)
+ goto out;
+# else /* not USE_RPMLIB */
+ if ((strcmp(argv[0], "/bin/sh") != 0) &&
+ (strcmp(argv[0], "/bin/bash") != 0))
+ goto out;
+
+ if ((strncmp(argv[1], "/var/tmp/rpm-tmp.", 17) != 0) &&
+ (strncmp(argv[1], "/usr/tmp/rpm-tmp.", 17) != 0) &&
+ (strncmp(argv[1], "/tmp/rpm-tmp.", 13) != 0))
+ goto out;
+# endif /* not USE_RPMLIB */
+ if ((argc = atoi(argv[2])) >= 0 && argc <= 2)
+ ret = true;
+out:
+ if (fd >= 0)
+ close(fd);
+
+ return ret;
+}
+#endif /* SUSE */
+
+static struct option long_options[] =
+{
+ {"verbose", 0, (int*)0, 'v'},
+ {"config", 1, (int*)0, 'c'},
+ {"dryrun", 0, (int*)0, 'n'},
+ {"default", 0, (int*)0, 'd'},
+ {"remove", 0, (int*)0, 'r'},
+ {"force", 0, (int*)0, 'f'},
+ {"path", 1, (int*)0, 'p'},
+ {"override",1, (int*)0, 'o'},
+ {"help", 0, (int*)0, 'h'},
+ { 0, 0, (int*)0, 0 },
+};
+
+static void help(const char *restrict const name) attribute((nonnull(1)));
+static void help(const char *restrict const name)
+{
+ printf("Usage: %s [<options>] [init_script|init_directory]\n", name);
+ printf("Available options:\n");
+ printf(" -h, --help This help.\n");
+ printf(" -r, --remove Remove the listed scripts from all runlevels.\n");
+ printf(" -f, --force Ignore if a required service is missed.\n");
+ printf(" -v, --verbose Provide information on what is being done.\n");
+ printf(" -p <path>, --path <path> Path to replace " INITDIR ".\n");
+ printf(" -o <path>, --override <path> Path to replace " OVERRIDEDIR ".\n");
+ printf(" -c <config>, --config <config> Path to config file.\n");
+ printf(" -n, --dryrun Do not change the system, only talk about it.\n");
+ printf(" -d, --default Use default runlevels a defined in the scripts\n");
+}
+
+
+/*
+ * Do the job.
+ */
+int main (int argc, char *argv[])
+{
+ DIR * initdir;
+ struct dirent *d;
+ struct stat st_script;
+ extension char * argr[argc];
+ char * path = INITDIR;
+ char * override_path = OVERRIDEDIR;
+ char * insconf = INSCONF;
+ const char *const ipath = path;
+ int runlevel, c, dfd;
+ boolean del = false;
+ boolean defaults = false;
+ boolean ignore = false;
+
+ myname = basename(*argv);
+
+#ifdef SUSE
+ if (underrpm())
+ ignore = true;
+#endif /* SUSE */
+
+ if (getuid() == (uid_t)0)
+ o_flags |= O_NOATIME;
+
+ for (c = 0; c < argc; c++)
+ argr[c] = (char*)0;
+
+ while ((c = getopt_long(argc, argv, "c:dfrhvno:p:", long_options, (int *)0)) != -1) {
+ size_t l;
+ switch (c) {
+ case 'c':
+ if (optarg == (char*)0 || *optarg == '\0')
+ goto err;
+ insconf = optarg;
+ set_insconf = true;
+ break;
+ case 'd':
+ defaults = true;
+ break;
+ case 'r':
+ del = true;
+ break;
+ case 'f':
+ ignore = true;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ case 'n':
+ verbose = true;
+ dryrun = true;
+ break;
+ case 'p':
+ if (optarg == (char*)0 || *optarg == '\0')
+ goto err;
+ if (path != ipath) free(path);
+ l = strlen(optarg) - 1;
+ path = xstrdup(optarg);
+ if (*(path+l) == '/')
+ *(path+l) = '\0';
+ break;
+ case 'o':
+ if (optarg == (char*)0 || *optarg == '\0')
+ goto err;
+ override_path = optarg;
+ set_override = true;
+ break;
+ case '?':
+ err:
+ error("For help use: %s -h\n", myname);
+ case 'h':
+ help(myname);
+ exit(0);
+ default:
+ break;
+ }
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (!argc && del)
+ error("usage: %s [[-r] init_script|init_directory]\n", myname);
+
+ if (*argv) {
+ char * token = strpbrk(*argv, delimeter);
+
+ /*
+ * Let us separate the script/service name from the additional arguments.
+ */
+ if (token && *token) {
+ *token = '\0';
+ *argr = ++token;
+ }
+
+ /* Catch `/path/script', `./script', and `path/script' */
+ if (strchr(*argv, '/')) {
+ if (stat(*argv, &st_script) < 0)
+ error("%s: %s\n", *argv, strerror(errno));
+ } else {
+ pushd(path);
+ if (stat(*argv, &st_script) < 0)
+ error("%s: %s\n", *argv, strerror(errno));
+ popd();
+ }
+
+ if (S_ISDIR(st_script.st_mode)) {
+ const size_t l = strlen(*argv) - 1;
+
+ if (path != ipath) free(path);
+ path = xstrdup(*argv);
+ if (*(path+l) == '/')
+ *(path+l) = '\0';
+
+ argv++;
+ argc--;
+ if (argc || del)
+ error("usage: %s [[-r] init_script|init_directory]\n", myname);
+
+ } else {
+ char * base, * ptr = xstrdup(*argv);
+
+ if ((base = strrchr(ptr, '/'))) {
+ if (path != ipath) free(path);
+ *base = '\0';
+ path = ptr;
+ } else
+ free(ptr);
+ }
+ }
+
+ if (strcmp(path, INITDIR) != 0) {
+ char * tmp;
+ root = xstrdup(path);
+ if ((tmp = strstr(root, INITDIR))) {
+ *tmp = '\0';
+ } else {
+ free(root);
+ root = (char*)0;
+ }
+ }
+
+ c = argc;
+ while (c--) {
+ char * base;
+ char * token = strpbrk(argv[c], delimeter);
+
+ /*
+ * Let us separate the script/service name from the additional arguments.
+ */
+ if (token && *token) {
+ *token = '\0';
+ argr[c] = ++token;
+ }
+
+ if (stat(argv[c], &st_script) < 0) {
+ if (errno != ENOENT)
+ error("%s: %s\n", argv[c], strerror(errno));
+ pushd(path);
+ if (stat(argv[c], &st_script) < 0)
+ error("%s: %s\n", argv[c], strerror(errno));
+ popd();
+ }
+ if ((base = strrchr(argv[c], '/'))) {
+ base++;
+ argv[c] = base;
+ }
+ }
+
+#if defined(DEBUG) && (DEBUG > 0)
+ for (c = 0; c < argc; c++)
+ if (argr[c])
+ printf("Overwrite argument for %s is %s\n", argv[c], argr[c]);
+#endif /* DEBUG */
+
+ /*
+ * Scan and set our configuration for virtual services.
+ */
+ scan_conf(insconf);
+
+ /*
+ * Expand system facilities to real serivces
+ */
+ expand_conf();
+
+ /*
+ * Initialize the regular scanner for the scripts.
+ */
+ scan_script_regalloc();
+
+ /*
+ * Scan always for the runlevel links to see the current
+ * link scheme of the services.
+ */
+ scan_script_locations(path, override_path, ignore);
+
+ /*
+ * Clear out aliases found for scripts found up to this point.
+ */
+ clear_all();
+
+ /*
+ * Open the script directory
+ */
+ if ((initdir = opendir(path)) == (DIR*)0 || (dfd = dirfd(initdir)) < 0)
+ error("can not opendir(%s): %s\n", path, strerror(errno));
+
+#if defined _XOPEN_SOURCE && (_XOPEN_SOURCE - 0) >= 600
+ (void)posix_fadvise(dfd, 0, 0, POSIX_FADV_WILLNEED);
+ (void)posix_fadvise(dfd, 0, 0, POSIX_FADV_SEQUENTIAL);
+#endif
+
+ /*
+ * Now scan for the service scripts and their LSB comments.
+ */
+ pushd(path);
+
+ /*
+ * Scan scripts found in the command line to be able to resolve
+ * all dependcies given within those scripts.
+ */
+ if (argc > 1) for (c = 0; c < argc; c++) {
+ const char *const name = argv[c];
+ service_t * first = (service_t*)0;
+ char * provides, * begin, * token;
+ const uchar lsb = scan_script_defaults(dfd, name, override_path, (char**)0, true, ignore);
+
+ if ((lsb & FOUND_LSB_HEADER) == 0) {
+ if ((lsb & (FOUND_LSB_DEFAULT|FOUND_LSB_OVERRIDE)) == 0)
+ warn("warning: script '%s' missing LSB tags and overrides\n", name);
+ else
+ warn("warning: script '%s' missing LSB tags\n", name);
+ }
+
+ if (!script_inf.provides || script_inf.provides == empty)
+ script_inf.provides = xstrdup(name);
+
+ provides = xstrdup(script_inf.provides);
+ begin = provides;
+ while ((token = strsep(&begin, delimeter)) && *token) {
+ service_t * service;
+
+ if (*token == '$') {
+ warn("script %s provides system facility %s, skipped!\n", name, token);
+ continue;
+ }
+ if (*token == '#') {
+ warn("script %s provides facility %s with comment sign, skipped!\n", name, token);
+ continue;
+ }
+
+ service = addservice(token);
+
+ if (first)
+ nickservice(first, service);
+ else
+ first = service;
+
+ service->attr.flags |= SERV_CMDLINE;
+ }
+ free(provides);
+ }
+
+ /*
+ * Scan now all scripts found in the init.d/ directory
+ */
+ while ((d = readdir(initdir)) != (struct dirent*)0) {
+ const boolean isarg = chkfor(d->d_name, argv, argc);
+ service_t * service = (service_t*)0;
+ char * token;
+ char * begin = (char*)0; /* hold start pointer of strings handled by strsep() */
+ boolean hard = false;
+ uchar lsb = 0;
+#if defined(DEBUG) && (DEBUG > 0)
+ int nobug = 0;
+#endif
+
+ if (*d->d_name == '.')
+ continue;
+ errno = 0;
+
+ /* d_type seems not to work, therefore use (l)stat(2) */
+ if (xstat(dfd, d->d_name, &st_script) < 0) {
+ warn("can not stat(%s)\n", d->d_name);
+ continue;
+ }
+ if (!S_ISREG(st_script.st_mode) || !(S_IXUSR & st_script.st_mode))
+ {
+ if (S_ISDIR(st_script.st_mode))
+ continue;
+ if (isarg)
+ warn("script %s is not an executable regular file, skipped!\n", d->d_name);
+ continue;
+ }
+
+ if (!strncmp(d->d_name, "README", strlen("README"))) {
+ if (isarg)
+ warn("script name %s is not valid, skipped!\n", d->d_name);
+ continue;
+ }
+
+ if (!strncmp(d->d_name, "Makefile", strlen("Makefile"))) {
+ if (isarg)
+ warn("script name %s is not valid, skipped!\n", d->d_name);
+ continue;
+ }
+
+ if (!strncmp(d->d_name, "core", strlen("core"))) {
+ if (isarg)
+ warn("script name %s is not valid, skipped!\n", d->d_name);
+ continue;
+ }
+
+ /* Common scripts not used within runlevels */
+ if (!strcmp(d->d_name, "rx") ||
+ !strncmp(d->d_name, "skeleton", 8) ||
+ !strncmp(d->d_name, "powerfail", 9))
+ {
+ if (isarg)
+ warn("script name %s is not valid, skipped!\n", d->d_name);
+ continue;
+ }
+
+#ifdef SUSE
+ if (!strcmp(d->d_name, "boot") || !strcmp(d->d_name, "rc"))
+#else /* not SUSE */
+ if (!strcmp(d->d_name, "rcS") || !strcmp(d->d_name, "rc"))
+#endif /* not SUSE */
+ {
+ if (isarg)
+ warn("script name %s is not valid, skipped!\n", d->d_name);
+ continue;
+ }
+
+ if (cfgfile_filter(d) == 0) {
+ if (isarg)
+ warn("script name %s is not valid, skipped!\n", d->d_name);
+ continue;
+ }
+
+ /* left by emacs like editors */
+ if (d->d_name[strlen(d->d_name)-1] == '~') {
+ if (isarg)
+ warn("script name %s is not valid, skipped!\n", d->d_name);
+ continue;
+ }
+
+ if (strspn(d->d_name, "$.#%_+-\\*[]^:()~")) {
+ if (isarg)
+ warn("script name %s is not valid, skipped!\n", d->d_name);
+ continue;
+ }
+
+ /* main scanner for LSB comment in current script */
+ lsb = scan_script_defaults(dfd, d->d_name, override_path, (char**)0, false, ignore);
+
+ if ((lsb & FOUND_LSB_HEADER) == 0) {
+ if ((lsb & (FOUND_LSB_DEFAULT|FOUND_LSB_OVERRIDE)) == 0)
+ warn("warning: script '%s' missing LSB tags and overrides\n", d->d_name);
+ else
+ warn("warning: script '%s' missing LSB tags\n", d->d_name);
+ }
+
+#ifdef SUSE
+ /* Common script ... */
+ if (!strcmp(d->d_name, "halt")) {
+ service_t *serv = addservice("halt");
+ serv = getorig(serv);
+ makeprov(serv, d->d_name);
+ runlevels(serv, 'S', "0");
+ serv->attr.flags |= (SERV_ALL|SERV_NOSTOP|SERV_INTRACT);
+ continue;
+ }
+
+ /* ... and its link */
+ if (!strcmp(d->d_name, "reboot")) {
+ service_t *serv = addservice("reboot");
+ serv = getorig(serv);
+ makeprov(serv, d->d_name);
+ runlevels(serv, 'S', "6");
+ serv->attr.flags |= (SERV_ALL|SERV_NOSTOP|SERV_INTRACT);
+ continue;
+ }
+
+ /* Common script for single mode */
+ if (!strcmp(d->d_name, "single")) {
+ service_t *serv = addservice("single");
+ serv = getorig(serv);
+ makeprov(serv, d->d_name);
+ runlevels(serv, 'S', "1 S");
+ serv->attr.flags |= (SERV_ALL|SERV_NOSTOP|SERV_INTRACT);
+ rememberreq(serv, REQ_SHLD, "kbd");
+ continue;
+ }
+#endif /* SUSE */
+
+#ifndef SUSE
+ if (!lsb) {
+ script_inf.required_start = xstrdup(DEFAULT_DEPENDENCY);
+ script_inf.required_stop = xstrdup(DEFAULT_DEPENDENCY);
+ script_inf.default_start = xstrdup(DEFAULT_START_LVL);
+ script_inf.default_stop = xstrdup(DEFAULT_STOP_LVL);
+ }
+#endif /* not SUSE */
+
+ /*
+ * Oops, no comment found, guess one
+ */
+ if (!script_inf.provides || script_inf.provides == empty) {
+ service_t * guess;
+ script_inf.provides = xstrdup(d->d_name);
+
+ /*
+ * Use guessed service to find it within the the runlevels
+ * (by using the list from the first scan for script locations).
+ */
+ if ((guess = findservice(script_inf.provides))) {
+ /*
+ * Try to guess required services out from current scheme.
+ * Note, this means that all services are required.
+ */
+ if (!script_inf.required_start || script_inf.required_start == empty) {
+ list_t * ptr;
+ list_for_each_prev(ptr, s_start) {
+ service_t * tmp = getservice(ptr);
+ tmp = getorig(tmp);
+ if (!tmp->attr.sorder)
+ continue;
+ if (tmp->attr.sorder >= guess->attr.sorder)
+ continue;
+ if (tmp->start->lvl & guess->start->lvl) {
+ script_inf.required_start = xstrdup(tmp->name);
+ break;
+ }
+ }
+ }
+ if (!script_inf.required_stop || script_inf.required_stop == empty) {
+ list_t * ptr;
+ list_for_each_prev(ptr, s_start) {
+ service_t * tmp = getservice(ptr);
+ tmp = getorig(tmp);
+ if (!tmp->attr.korder)
+ continue;
+ if (tmp->attr.korder <= guess->attr.korder)
+ continue;
+ if (tmp->stopp->lvl & guess->stopp->lvl) {
+ script_inf.required_stop = xstrdup(tmp->name);
+ break;
+ }
+ }
+ }
+ if (!script_inf.default_start || script_inf.default_start == empty) {
+ if (guess->start->lvl)
+ script_inf.default_start = lvl2str(guess->start->lvl);
+ }
+ if (!script_inf.default_stop || script_inf.default_stop == empty) {
+ if (guess->stopp->lvl)
+ script_inf.default_stop = lvl2str(guess->stopp->lvl);
+ }
+
+ } else { /* !findservice(&guess, script_inf.provides) */
+
+ list_t * ptr;
+ /*
+ * Find out which levels this service may have out from current scheme.
+ * Note, this means that the first requiring service wins.
+ */
+ list_for_each(ptr, s_start) {
+ service_t * cur;
+ list_t * req;
+
+ if (script_inf.default_start && script_inf.default_start != empty)
+ break;
+ cur = getservice(ptr);
+ cur = getorig(cur);
+
+ if (list_empty(&cur->sort.req) || !(cur->attr.flags & SERV_ENABLED))
+ continue;
+
+ np_list_for_each(req, &cur->sort.req) {
+ if (!strcmp(getreq(req)->serv->name, script_inf.provides)) {
+ script_inf.default_start = lvl2str(getservice(ptr)->start->lvl);
+ break;
+ }
+ }
+ }
+ list_for_each(ptr, s_start) {
+ service_t * cur;
+ list_t * rev;
+
+ if (script_inf.default_stop && script_inf.default_stop != empty)
+ break;
+ cur = getservice(ptr);
+ cur = getorig(cur);
+
+ if (list_empty(&cur->sort.rev) || !(cur->attr.flags & SERV_ENABLED))
+ continue;
+
+ np_list_for_each(rev, &cur->sort.rev) {
+ if (!strcmp(getreq(rev)->serv->name, script_inf.provides)) {
+ script_inf.default_stop = lvl2str(getservice(ptr)->stopp->lvl);
+ break;
+ }
+ }
+ }
+ } /* !findservice(&guess, script_inf.provides) */
+ }
+
+ /*
+ * Use guessed service to find it within the the runlevels
+ * (by using the list from the first scan for script locations).
+ */
+ if (!service) {
+ char * provides = xstrdup(script_inf.provides);
+ service_t * first = (service_t*)0;
+
+ begin = provides;
+ while ((token = strsep(&begin, delimeter)) && *token) {
+
+ if (*token == '$') {
+ warn("script %s provides system facility %s, skipped!\n", d->d_name, token);
+ continue;
+ }
+ if (*token == '#') {
+ warn("script %s provides facility %s with comment sign, skipped!\n", d->d_name, token);
+ continue;
+ }
+
+ service = addservice(token);
+
+ if (first)
+ nickservice(first, service);
+ else
+ first = service;
+
+#if defined(DEBUG) && (DEBUG > 0)
+ nobug++;
+#endif
+ if (!makeprov(service, d->d_name)) {
+
+ if (!del || (del && !isarg))
+ warn("script %s: service %s already provided!\n", d->d_name, token);
+
+ if (!del && !ignore && isarg)
+ error("exiting now!\n");
+
+ if (!del || (del && !ignore && !isarg))
+ continue;
+
+ /* Provide this service with an other name to be able to delete it */
+ service = addservice(d->d_name);
+ service = getorig(service);
+ service->attr.flags |= SERV_ALREADY;
+ (void)makeprov(service, d->d_name);
+
+ continue;
+ }
+
+ if (service) {
+ boolean known = (service->attr.flags & SERV_KNOWN);
+ service->attr.flags |= SERV_KNOWN;
+
+ if (!known) {
+ if (script_inf.required_start && script_inf.required_start != empty) {
+ rememberreq(service, REQ_MUST, script_inf.required_start);
+#ifdef USE_COMPAT_EMPTY
+ if (!script_inf.required_stop || script_inf.required_stop == empty)
+ script_inf.required_stop = xstrdup(script_inf.required_start);
+#endif /* USE_COMPAT_EMPTY */
+ }
+ if (script_inf.should_start && script_inf.should_start != empty) {
+ rememberreq(service, REQ_SHLD, script_inf.should_start);
+#ifdef USE_COMPAT_EMPTY
+ if (!script_inf.should_stop || script_inf.should_stop == empty)
+ script_inf.should_stop = xstrdup(script_inf.should_start);
+#endif /* USE_COMPAT_EMPTY */
+ }
+ if (script_inf.required_stop && script_inf.required_stop != empty) {
+ rememberreq(service, REQ_MUST|REQ_KILL, script_inf.required_stop);
+ }
+ if (script_inf.should_stop && script_inf.should_stop != empty) {
+ rememberreq(service, REQ_SHLD|REQ_KILL, script_inf.should_stop);
+ }
+ }
+
+ if (script_inf.start_before && script_inf.start_before != empty) {
+ reversereq(service, REQ_SHLD, script_inf.start_before);
+#ifdef USE_COMPAT_EMPTY
+ if (!script_inf.stop_after || script_inf.stop_after == empty)
+ script_inf.stop_after = xstrdup(script_inf.start_before);
+#endif /* USE_COMPAT_EMPTY */
+ }
+ if (script_inf.stop_after && script_inf.stop_after != empty) {
+ reversereq(service, REQ_SHLD|REQ_KILL, script_inf.stop_after);
+ }
+ /*
+ * Use information from symbolic link structure to
+ * check if all services are around for this script.
+ */
+ if (isarg && !ignore) {
+ boolean ok = true;
+ if (del)
+ ok = chkdependencies(service);
+ else
+ ok = chkrequired(service);
+ if (!ok && !ignore)
+ error("exiting now!\n");
+ }
+
+ if (script_inf.default_start && script_inf.default_start != empty) {
+ ushort deflvls = str2lvl(script_inf.default_start);
+
+ if (service->attr.flags & SERV_ENABLED) {
+ /*
+ * Currently linked into service runlevel scheme, check
+ * if the defaults are overwriten. Compare all bits,
+ * which means `==' and not `&' and overwrite the defaults
+ * of the current script.
+ */
+ if (!defaults && (deflvls != service->start->lvl)) {
+ if (!del && isarg && !(argr[curr_argc]))
+ warn("warning: current start runlevel(s) (%s) of script `%s' overwrites defaults (%s).\n",
+ service->start->lvl ? lvl2str(service->start->lvl) : "empty", d->d_name, lvl2str(deflvls));
+ }
+ } else
+ /*
+ * Currently not linked into service runlevel scheme, info
+ * needed for enabling interactive services at first time.
+ */
+ service->start->lvl = deflvls;
+
+ } else if (script_inf.default_start == empty) {
+ if (service->attr.flags & SERV_ENABLED) {
+ /*
+ * Currently linked into service runlevel scheme, check
+ * if the defaults are overwriten. Compare all bits,
+ * which means `==' and not `&' and overwrite the defaults
+ * of the current script.
+ */
+ if (!defaults && service->start->lvl != 0) {
+ warn("warning: current start runlevel(s) (%s) of script `%s' overwrites defaults (empty).\n",
+ lvl2str(service->start->lvl), d->d_name);
+ script_inf.default_start = lvl2str(service->start->lvl);
+ }
+ }
+ } else if (!script_inf.default_start && (service->attr.flags & SERV_NOTLSB)) {
+#ifdef SUSE
+ /*
+ * Could be a none LSB script, use info from current link scheme.
+ * If not found use default.
+ */
+ if (service->attr.flags & SERV_ENABLED)
+ script_inf.default_start = lvl2str(service->start->lvl);
+ else
+ script_inf.default_start = xstrdup(DEFAULT_START_LVL);
+#endif /* SUSE */
+ }
+#ifdef SUSE
+ /*
+ * This because SuSE boot script concept uses a differential link scheme.
+ * Therefore default_stop is ignored and overwriten by default_start.
+ */
+ xreset(script_inf.default_stop);
+ if (script_inf.default_start && script_inf.default_start != empty)
+ script_inf.default_stop = xstrdup(script_inf.default_start);
+ else
+ script_inf.default_stop = empty;
+ oneway(script_inf.default_stop);
+#endif /* SUSE */
+ if (script_inf.default_stop && script_inf.default_stop != empty) {
+ ushort deflvlk = str2lvl(script_inf.default_stop);
+
+ /*
+ * Compare all bits, which means `==' and not `&' and overwrite
+ * the defaults of the current script.
+ */
+ if (service->attr.flags & SERV_ENABLED) {
+ /*
+ * Currently linked into service runlevel scheme, check
+ * if the defaults are overwriten.
+ */
+ if (!defaults && (deflvlk != service->stopp->lvl)) {
+ if (!del && isarg && !(argr[curr_argc]))
+ warn("warning: current stop runlevel(s) (%s) of script `%s' overwrites defaults (%s).\n",
+ service->stopp->lvl ? lvl2str(service->stopp->lvl) : "empty", d->d_name, lvl2str(deflvlk));
+ }
+ } else
+ /*
+ * Currently not linked into service runlevel scheme, info
+ * needed for enabling interactive services at first time.
+ */
+ service->stopp->lvl = deflvlk;
+
+ } else if (script_inf.default_stop == empty) {
+ if (service->attr.flags & SERV_ENABLED) {
+ /*
+ * Currently linked into service runlevel scheme, check
+ * if the defaults are overwriten. Compare all bits,
+ * which means `==' and not `&' and overwrite the defaults
+ * of the current script.
+ */
+ if (!defaults && service->stopp->lvl != 0) {
+ warn("warning: current stop runlevel(s) (%s) of script `%s' overwrites defaults (empty).\n",
+ lvl2str(service->stopp->lvl), d->d_name);
+ script_inf.default_stop = lvl2str(service->stopp->lvl);
+ }
+ }
+ } else if (!script_inf.default_stop && (service->attr.flags & SERV_NOTLSB)) {
+#ifdef SUSE
+ /*
+ * Could be a none LSB script, use info from current link scheme.
+ * If not found use default.
+ */
+ if (service->attr.flags & SERV_ENABLED)
+ script_inf.default_stop = lvl2str(service->stopp->lvl);
+ else
+ script_inf.default_stop = xstrdup(DEFAULT_STOP_LVL);
+#endif /* SUSE */
+ }
+ }
+ }
+ free(provides);
+ }
+
+#ifdef SUSE
+ /* Ahh ... set default multiuser with network */
+ if (!script_inf.default_start || script_inf.default_start == empty) {
+ if (!script_inf.default_start)
+ warn("Default-Start undefined, assuming default start runlevel(s) for script `%s'\n", d->d_name);
+ script_inf.default_start = xstrdup(DEFAULT_START_LVL);
+ xreset(script_inf.default_stop);
+ script_inf.default_stop = xstrdup(script_inf.default_start);
+ oneway(script_inf.default_stop);
+ }
+#else /* not SUSE */
+ if (!script_inf.default_start) {
+ warn("Default-Start undefined, assuming empty start runlevel(s) for script `%s'\n", d->d_name);
+ script_inf.default_start = empty;
+ }
+#endif /* not SUSE */
+
+#ifdef SUSE
+ if (!script_inf.default_stop || script_inf.default_stop == empty) {
+ if (script_inf.default_start && script_inf.default_start != empty)
+ script_inf.default_stop = xstrdup(script_inf.default_start);
+ else
+ script_inf.default_stop = xstrdup(DEFAULT_STOP_LVL);
+ oneway(script_inf.default_stop);
+ }
+#else /* not SUSE */
+ if (!script_inf.default_stop) {
+ warn("Default-Stop undefined, assuming empty stop runlevel(s) for script `%s'\n", d->d_name);
+ script_inf.default_stop = empty;
+ }
+#endif /* not SUSE */
+
+ if (isarg && !defaults && !del) {
+ if (argr[curr_argc]) {
+ char * ptr = argr[curr_argc];
+ struct _mark {
+ const char * wrd;
+ char * order;
+ char ** str;
+ } mark[] = {
+ {"start=", (char*)0, &script_inf.default_start},
+ {"stop=", (char*)0, &script_inf.default_stop },
+#if 0
+ {"reqstart=", (char*)0, &script_inf.required_start},
+ {"reqstop=", (char*)0, &script_inf.required_stop },
+#endif
+ {(char*)0, (char*)0, (char**)0}
+ };
+
+ for (c = 0; mark[c].wrd; c++) {
+ char * order = strstr(ptr, mark[c].wrd);
+ if (order)
+ mark[c].order = order;
+ }
+
+ for (c = 0; mark[c].wrd; c++)
+ if (mark[c].order) {
+ *(mark[c].order) = '\0';
+ mark[c].order += strlen(mark[c].wrd);
+ }
+
+ for (c = 0; mark[c].wrd; c++)
+ if (mark[c].order) {
+ size_t len = strlen(mark[c].order);
+ if (len > 0) {
+ char * ptr = mark[c].order + len - 1;
+ if (*ptr == ',') *ptr = '\0';
+ }
+ xreset(*(mark[c].str));
+ *(mark[c].str) = xstrdup(mark[c].order);
+ }
+ hard = true;
+#ifdef SUSE
+ /*
+ * This because SuSE boot script concept uses a differential link scheme.
+ * Therefore default_stop is ignored and overwriten by default_start.
+ */
+ if (strcmp(script_inf.default_stop, script_inf.default_start) != 0) {
+ xreset(script_inf.default_stop);
+ script_inf.default_stop = xstrdup(script_inf.default_start);
+ oneway(script_inf.default_stop);
+ }
+#endif /* SUSE */
+ }
+ }
+
+#if defined(DEBUG) && (DEBUG > 0)
+ if (!nobug) {
+ fprintf(stderr, "internal BUG at line %d with script %s\n", __LINE__, d->d_name);
+ exit(1);
+ }
+#endif
+
+ begin = script_inf.provides;
+ while ((token = strsep(&script_inf.provides, delimeter)) && *token) {
+ if (*token == '$')
+ continue;
+ if (*token == '#')
+ continue;
+ if (!service)
+ service = addservice(token);
+ service = getorig(service);
+
+ if ((service->attr.flags & SERV_ENABLED) && !hard) {
+ if (del)
+ continue;
+ if (!defaults)
+ continue;
+ }
+
+ if (script_inf.default_start && script_inf.default_start != empty)
+ runlevels(service, 'S', script_inf.default_start);
+ if (script_inf.default_stop && script_inf.default_stop != empty)
+ runlevels(service, 'K', script_inf.default_stop);
+ }
+ script_inf.provides = begin;
+
+ /* Remember if not LSB conform script */
+ if (!lsb && service) {
+ service = getorig(service);
+ service->attr.flags |= SERV_NOTLSB;
+ }
+ }
+ /* Reset remaining pointers */
+ scan_script_reset();
+
+ /*
+ * Free the regular scanner for the scripts.
+ */
+ scan_script_regfree();
+
+ /* back */
+ popd();
+ closedir(initdir);
+
+ /*
+ * Clear out aliases found for all scripts.
+ */
+ clear_all();
+
+ /*
+ * Set virtual dependencies for already enabled none LSB scripts.
+ */
+ nonlsb_script();
+
+ /*
+ * Now generate for all scripts the dependencies
+ */
+ follow_all();
+ if (is_loop_detected() && !ignore)
+ error("exiting without changing boot order!\n");
+
+ /*
+ * Be sure that interactive scripts are the only member of
+ * a start group (for parallel start only).
+ */
+ active_script();
+
+ /*
+ * Move the `$all' scripts to the end of all
+ */
+ all_script();
+
+ /*
+ * Sorry but we support only [KS][0-9][0-9]<name>
+ */
+ if (maxstart > MAX_DEEP || maxstop > MAX_DEEP)
+ error("Maximum of %u in ordering reached\n", MAX_DEEP);
+
+#if defined(DEBUG) && (DEBUG > 0)
+ printf("Maxorder %d/%d\n", maxstart, maxstop);
+ show_all();
+#else
+# ifdef SUSE /* SuSE's SystemV link scheme */
+ pushd(path);
+ for (runlevel = 0; runlevel < RUNLEVLES; runlevel++) {
+ const ushort lvl = map_runlevel_to_lvl(runlevel);
+ char nlink[PATH_MAX+1], olink[PATH_MAX+1];
+ const char * rcd = (char*)0;
+ const char * script;
+ service_t *serv;
+ DIR * rcdir;
+
+ if ((rcd = map_runlevel_to_location(runlevel)) == (char*)0)
+ continue;
+
+ rcdir = openrcdir(rcd); /* Creates runlevel directory if necessary */
+ if (rcdir == (DIR*)0)
+ break;
+ if ((dfd = dirfd(rcdir)) < 0) {
+ closedir(rcdir);
+ break;
+ }
+ pushd(rcd);
+
+ /*
+ * See if we found scripts which should not be
+ * included within this runlevel directory.
+ */
+ while ((d = readdir(rcdir)) != (struct dirent*)0) {
+ const char * ptr = d->d_name;
+ char type;
+
+ if (*ptr != 'S' && *ptr != 'K')
+ continue;
+ type = *ptr;
+ ptr++;
+
+ if (strspn(ptr, "0123456789") != 2)
+ continue;
+ ptr += 2;
+
+ if (xstat(dfd, d->d_name, &st_script) < 0)
+ xremove(dfd, d->d_name); /* dangling sym link */
+
+ if (notincluded(ptr, type, runlevel)) {
+ serv = findservice(getprovides(ptr));
+ if (defaults) {
+ xremove(dfd, d->d_name);
+ if (serv && --serv->attr.ref <= 0)
+ serv->attr.flags &= ~SERV_ENABLED;
+ } else if (lvl & LVL_ONEWAY) {
+ xremove(dfd, d->d_name);
+ if (serv && --serv->attr.ref <= 0)
+ serv->attr.flags &= ~SERV_ENABLED;
+ } else if (del && ignore) {
+ if (serv && (serv->attr.flags & SERV_ALREADY)) {
+ xremove(dfd, d->d_name);
+ if (--serv->attr.ref <= 0)
+ serv->attr.flags &= ~SERV_ENABLED;
+ }
+ }
+ }
+ }
+
+ /*
+ * Seek for scripts which are included, link or
+ * correct order number if necessary.
+ */
+
+ script = (char*)0;
+ while ((serv = listscripts(&script, 'X', lvl))) {
+ const boolean this = chkfor(script, argv, argc);
+ boolean found, slink;
+ char * clink;
+
+ if (*script == '$') /* Do not link in virtual dependencies */
+ continue;
+
+ slink = false;
+ if ((serv->start->lvl & lvl) == 0)
+ goto stop;
+
+ sprintf(olink, "../%s", script);
+ sprintf(nlink, "S%.2d%s", serv->attr.sorder, script);
+
+ found = false;
+ rewinddir(rcdir);
+ while ((clink = scan_for(rcdir, script, 'S'))) {
+ found = true;
+ if (strcmp(clink, nlink)) {
+ xremove(dfd, clink); /* Wrong order, remove link */
+ if (--serv->attr.ref <= 0)
+ serv->attr.flags &= ~SERV_ENABLED;
+ if (!this) {
+ xsymlink(dfd, olink, nlink); /* Not ours, but correct order */
+ if (++serv->attr.ref)
+ serv->attr.flags |= SERV_ENABLED;
+ }
+ if (this && !del) {
+ xsymlink(dfd, olink, nlink); /* Restore, with correct order */
+ if (++serv->attr.ref)
+ serv->attr.flags |= SERV_ENABLED;
+ }
+ } else {
+ if (del && this) {
+ xremove(dfd, clink); /* Found it, remove link */
+ if (--serv->attr.ref <= 0)
+ serv->attr.flags &= ~SERV_ENABLED;
+ }
+ }
+ }
+
+ if (this) {
+ /*
+ * If we haven't found it and we shouldn't delete it
+ * we try to add it.
+ */
+ if (!del && !found) {
+ xsymlink(dfd, olink, nlink);
+ if (++serv->attr.ref)
+ serv->attr.flags |= SERV_ENABLED;
+ found = true;
+ }
+ }
+
+ /* Start links done, now do Kill links */
+
+ slink = found;
+ stop:
+ if ((serv->stopp->lvl & lvl) == 0)
+ continue;
+
+ sprintf(olink, "../%s", script);
+ sprintf(nlink, "K%.2d%s", serv->attr.korder, script);
+
+ found = false;
+ rewinddir(rcdir);
+ while ((clink = scan_for(rcdir, script, 'K'))) {
+ found = true;
+ if (strcmp(clink, nlink)) {
+ xremove(dfd, clink); /* Wrong order, remove link */
+ if (--serv->attr.ref <= 0)
+ serv->attr.flags &= ~SERV_ENABLED;
+ if (!this) {
+ xsymlink(dfd, olink, nlink); /* Not ours, but correct order */
+ if (++serv->attr.ref)
+ serv->attr.flags |= SERV_ENABLED;
+ }
+ if (this && !del) {
+ xsymlink(dfd, olink, nlink); /* Restore, with correct order */
+ if (++serv->attr.ref)
+ serv->attr.flags |= SERV_ENABLED;
+ }
+ } else {
+ if (del && this) {
+ xremove(dfd, clink); /* Found it, remove link */
+ if (--serv->attr.ref <= 0)
+ serv->attr.flags &= ~SERV_ENABLED;
+ }
+ }
+ }
+
+ if (this && slink) {
+ /*
+ * If we haven't found it and we shouldn't delete it
+ * we try to add it.
+ */
+ if (!del && !found) {
+ xsymlink(dfd, olink, nlink);
+ if (++serv->attr.ref)
+ serv->attr.flags |= SERV_ENABLED;
+ }
+ }
+ }
+ popd();
+ closedir(rcdir);
+ }
+# else /* not SUSE but Debian SystemV link scheme */
+ /*
+ * Remark: At SuSE we use boot scripts for system initialization which
+ * will be executed by /etc/init.d/boot (which is equal to rc.sysinit).
+ * At system reboot or system halt the stop links of those boot scripts
+ * will be executed by /etc/init.d/halt. Don't know how todo this for
+ * a traditional standard SystemV link scheme. Maybe for such an
+ * approach a new directory halt.d/ whould be an idea.
+ */
+ pushd(path);
+ for (runlevel = 0; runlevel < RUNLEVLES; runlevel++) {
+ char nlink[PATH_MAX+1], olink[PATH_MAX+1];
+ const char * rcd = (char*)0;
+ const char * script;
+ service_t * serv;
+ ushort lvl, seek;
+ DIR * rcdir;
+
+ if ((rcd = map_runlevel_to_location(runlevel)) == (char*)0)
+ continue;
+ lvl = map_runlevel_to_lvl(runlevel);
+ seek = map_runlevel_to_seek(runlevel);
+
+ rcdir = openrcdir(rcd); /* Creates runlevel directory if necessary */
+ if (rcdir == (DIR*)0)
+ break;
+ if ((dfd = dirfd(rcdir)) < 0) {
+ closedir(rcdir);
+ break;
+ }
+ pushd(rcd);
+
+ /*
+ * See if we found scripts which should not be
+ * included within this runlevel directory.
+ */
+ while ((d = readdir(rcdir)) != (struct dirent*)0) {
+ const char * ptr = d->d_name;
+ char type;
+
+ if (*ptr != 'S' && *ptr != 'K')
+ continue;
+ type = *ptr;
+ ptr++;
+
+ if (strspn(ptr, "0123456789") != 2)
+ continue;
+ ptr += 2;
+
+ if (xstat(dfd, d->d_name, &st_script) < 0)
+ xremove(dfd, d->d_name); /* dangling sym link */
+
+ if (notincluded(ptr, type, runlevel)) {
+ serv = findservice(getprovides(ptr));
+ if (defaults) {
+ xremove(dfd, d->d_name);
+ if (serv && --serv->attr.ref <= 0)
+ serv->attr.flags &= ~SERV_ENABLED;
+# ifndef USE_KILL_IN_BOOT
+ } else if (lvl & LVL_BOOT) {
+ xremove(dfd, d->d_name);
+ if (serv && --serv->attr.ref <= 0)
+ serv->attr.flags &= ~SERV_ENABLED;
+# endif /* USE_KILL_IN_BOOT */
+ } else if (del && ignore) {
+ if (serv && (serv->attr.flags & SERV_ALREADY))
+ xremove(dfd, d->d_name);
+ if (--serv->attr.ref <= 0)
+ serv->attr.flags &= ~SERV_ENABLED;
+ }
+ }
+ }
+
+ script = (char*)0;
+ while ((serv = listscripts(&script, 'X', seek))) {
+ const boolean this = chkfor(script, argv, argc);
+ boolean found;
+ char * clink;
+ char mode;
+
+ if (*script == '$') /* Do not link in virtual dependencies */
+ continue;
+
+ sprintf(olink, "../init.d/%s", script);
+ if (serv->stopp->lvl & lvl) {
+# ifndef USE_KILL_IN_BOOT
+ if (lvl & LVL_BOOT) /* No kill links in rcS.d */
+ continue;
+# endif /* USE_KILL_IN_BOOT */
+ sprintf(nlink, "K%.2d%s", serv->attr.korder, script);
+ mode = 'K';
+ } else if (serv->start->lvl & lvl) {
+ sprintf(nlink, "S%.2d%s", serv->attr.sorder, script);
+ mode = 'S';
+ } else
+ continue; /* We aren't suppose to be on this runlevel */
+
+ found = false;
+
+ rewinddir(rcdir);
+ while ((clink = scan_for(rcdir, script, mode))) {
+ found = true;
+ if (strcmp(clink, nlink)) {
+ xremove(dfd, clink); /* Wrong order, remove link */
+ if (--serv->attr.ref <= 0)
+ serv->attr.flags &= ~SERV_ENABLED;
+ if (!this) {
+ xsymlink(dfd, olink, nlink); /* Not ours, but correct order */
+ if (++serv->attr.ref)
+ serv->attr.flags |= SERV_ENABLED;
+ }
+ if (this && !del) {
+ xsymlink(dfd, olink, nlink); /* Restore, with correct order */
+ if (++serv->attr.ref)
+ serv->attr.flags |= SERV_ENABLED;
+ }
+ } else {
+ if (del && this) {
+ xremove(dfd, clink); /* Found it, remove link */
+ if (--serv->attr.ref <= 0)
+ serv->attr.flags &= ~SERV_ENABLED;
+ }
+ }
+ }
+
+ if (this) {
+ /*
+ * If we haven't found it and we shouldn't delete it
+ * we try to add it.
+ */
+ if (!del && !found) {
+ xsymlink(dfd, olink, nlink);
+ if (++serv->attr.ref)
+ serv->attr.flags |= SERV_ENABLED;
+ found = true;
+ }
+ }
+ }
+
+ popd();
+ closedir(rcdir);
+ }
+# endif /* !SUSE, standard SystemV link scheme */
+#endif /* !DEBUG */
+
+ /*
+ * Do the makedep
+ */
+ makedep();
+
+ /*
+ * Back to the root(s)
+ */
+ popd();
+
+ /*
+ * Make valgrind happy
+ */
+ if (path != ipath) free(path);
+ if (root) free(root);
+
+ return 0;
+}