mirror of https://github.com/slicer69/insserv.git
4436 lines
116 KiB
C
4436 lines
116 KiB
C
/*
|
|
* insserv(.c)
|
|
*
|
|
* Copyright 2000-2009 Werner Fink, 2000 SuSE GmbH Nuernberg, Germany,
|
|
* 2003 SuSE Linux AG, Germany.
|
|
* 2004 SuSE LINUX AG, Germany.
|
|
* 2005-2009 SUSE LINUX Products GmbH, Germany.
|
|
* Copyright 2005,2008,2009 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef WANT_SYSTEMD
|
|
/*
|
|
* Systemd integration
|
|
*/
|
|
#ifndef SYSTEMD_SERVICE_PATH
|
|
#define SYSTEMD_SERVICE_PATH "/lib/systemd/system"
|
|
#endif
|
|
#ifndef SYSTEMD_BINARY_PATH
|
|
#define SYSTEMD_BINARY_PATH "/bin/systemd"
|
|
#endif
|
|
#endif /* WANT_SYSTEMD */
|
|
|
|
#define MINIMAL_MAKE 1 /* Remove disabled scripts from .depend.boot,
|
|
* .depend.start, .depend.halt, and .depend.stop */
|
|
#define MINIMAL_RULES 1 /* ditto */
|
|
#define MINIMAL_DEPEND 1 /* Remove redundant dependencies */
|
|
|
|
#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/statfs.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(__linux__)
|
|
# include <linux/magic.h>
|
|
#endif
|
|
#if !defined(CGROUP_SUPER_MAGIC)
|
|
# define CGROUP_SUPER_MAGIC 0x27e0eb
|
|
#endif
|
|
#if defined(USE_RPMLIB) && (USE_RPMLIB > 0)
|
|
# include <rpm/rpmlib.h>
|
|
# include <rpm/rpmmacro.h>
|
|
#endif /* USE_RPMLIB */
|
|
#ifdef SUSE
|
|
# include <sys/mount.h>
|
|
#endif /* SUSE */
|
|
#ifndef NEED_RUNLEVELS_DEF
|
|
#define NEED_RUNLEVELS_DEF
|
|
#endif
|
|
#include "listing.h"
|
|
#include "systemd.h"
|
|
|
|
#ifdef __m68k__ /* Fix #493637 */
|
|
# define aligned(a)
|
|
#endif
|
|
|
|
#if defined _XOPEN_SOURCE && (_XOPEN_SOURCE - 0) >= 600
|
|
# ifndef POSIX_FADV_SEQUENTIAL
|
|
# define posix_fadvise(fd, off, len, adv) (-1)
|
|
# endif
|
|
#endif
|
|
|
|
#ifndef PATH_MAX
|
|
# ifdef MAXPATHLEN
|
|
# define PATH_MAX MAXPATHLEN
|
|
# else
|
|
# define PATH_MAX 2048
|
|
# endif
|
|
#endif
|
|
|
|
#ifndef MAXSYMLINKS
|
|
#define MAXSYMLINKS 20
|
|
#endif
|
|
|
|
#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
|
|
|
|
/* Upstart suport */
|
|
static const char *upstartjob_path = "/lib/init/upstart-job";
|
|
|
|
#ifdef WANT_SYSTEMD
|
|
/* Systemd support */
|
|
static DBusConnection *sbus;
|
|
|
|
#endif /* WANT_SYSTEMD */
|
|
|
|
/*
|
|
* 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
|
|
#define INTERACTIVE COMM "(x[-_]+[a-z0-9_-]*)?interactive" 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, and what level of verbosity */
|
|
static int verbose = 0;
|
|
static boolean silent_mode = false;
|
|
static boolean dryrun = false;
|
|
|
|
/* When paths set do not add root if any */
|
|
static boolean set_override = false;
|
|
static boolean set_insconf = false;
|
|
|
|
/* Legacy and current location for dependency files */
|
|
/* #define DEPENDENCY_PATH "/lib/insserv/" */
|
|
#define LEGACY_DEPENDENCY_PATH "/etc/init.d/."
|
|
char *dependency_path = LEGACY_DEPENDENCY_PATH;
|
|
|
|
/* List of custom file extensions we should ignore.
|
|
Loaded from /etc/insserv/file-filters.
|
|
*/
|
|
#define FILE_FILTER_PATH "/etc/insserv/file-filters"
|
|
char **file_filters = NULL;
|
|
|
|
/* Wether systemd is active or not */
|
|
#if WANT_SYSTEMD
|
|
static boolean systemd = false;
|
|
static boolean is_overridden_by_systemd(const char *);
|
|
#endif /* WANT_SYSTEMD */
|
|
|
|
/* 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;
|
|
char *interactive;
|
|
} 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;
|
|
regex_t interact;
|
|
} 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;
|
|
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)))
|
|
fprintf(stderr, "pushd() cannot save current working directory.\n");
|
|
else
|
|
insert(&dir->deep, topd->prev);
|
|
if (chdir(path) < 0)
|
|
error("pushd() cannot change to directory %s: %s\n", path, strerror(errno) );
|
|
}
|
|
}
|
|
|
|
static void popd(void)
|
|
{
|
|
pwd_t * dir;
|
|
|
|
if (list_empty(topd))
|
|
return;
|
|
if (topd->prev)
|
|
{
|
|
dir = getpwd(topd->prev);
|
|
if (! dir->pwd)
|
|
{
|
|
fprintf(stderr, "popd() previous directory does not exist in memory.\n");
|
|
return;
|
|
}
|
|
if (chdir(dir->pwd) < 0)
|
|
{
|
|
fprintf(stderr, "popd() can not change directory %s: %s\n", dir->pwd, strerror(errno) );
|
|
return;
|
|
}
|
|
delete(topd->prev);
|
|
free(dir->pwd);
|
|
free(dir);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
This function loads a list of newline-separated extensions from
|
|
the FILE_FILTER_PATH text file.
|
|
The function returns an array of strings (extensions) on success
|
|
and NULL on failure.
|
|
*/
|
|
char **Load_File_Filters()
|
|
{
|
|
FILE *my_file;
|
|
char **temp_strings;
|
|
char line[32];
|
|
char *status;
|
|
int read_lines = 0;
|
|
int buffer_size = 10;
|
|
|
|
my_file = fopen(FILE_FILTER_PATH, "r");
|
|
if (! my_file)
|
|
return NULL;
|
|
|
|
temp_strings = (char **) calloc(buffer_size, sizeof(char *));
|
|
if (! temp_strings)
|
|
{
|
|
fclose(my_file);
|
|
return NULL;
|
|
}
|
|
status = fgets(line, 32, my_file);
|
|
while (status)
|
|
{
|
|
/* check to see if line has any contents */
|
|
if ( (line[0]) && (line[0] != '\n') && (line[0] != '\r') )
|
|
{
|
|
int string_length;
|
|
/* trim newline */
|
|
string_length = strlen(line);
|
|
if ( line[string_length - 1] == '\n' )
|
|
line[string_length - 1] = '\0';
|
|
|
|
temp_strings[read_lines] = strdup(line);
|
|
read_lines++;
|
|
/* buffer may be full, extend it */
|
|
if (read_lines >= 1000) /* too big, bail out */
|
|
{
|
|
warn("warning: too many file extensions listed in %s\n", FILE_FILTER_PATH);
|
|
break;
|
|
}
|
|
if (read_lines >= buffer_size)
|
|
{
|
|
int index;
|
|
buffer_size += 10; /* extend the buffer */
|
|
temp_strings = (char **) realloc(temp_strings, buffer_size * sizeof(char *));
|
|
for (index = read_lines; index < buffer_size; index++)
|
|
temp_strings[index] = NULL; /* init memory since realloc does not */
|
|
}
|
|
}
|
|
status = fgets(line, 32, my_file);
|
|
}
|
|
fclose(my_file);
|
|
return temp_strings;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Linked list of system facilities services and their replacment
|
|
*/
|
|
typedef struct string {
|
|
list_t s_list;
|
|
int ref;
|
|
char *name;
|
|
} __align string_t;
|
|
#define getfstr(arg) list_entry((arg), struct string, s_list)
|
|
|
|
typedef struct repl {
|
|
list_t r_list;
|
|
struct {
|
|
string_t *addr;
|
|
const char *name;
|
|
};
|
|
ushort flags;
|
|
} __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 facistr = { &facistr, &facistr }, *facistr_start = &facistr;
|
|
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;
|
|
boolean can_expand_name = false;
|
|
|
|
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;
|
|
/* fall through */
|
|
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, "$null") == 0)
|
|
break;
|
|
if (strcasecmp(token, "$all") == 0) {
|
|
if (bit & REQ_KILL)
|
|
serv->attr.flags |= SERV_FIRST;
|
|
else
|
|
serv->attr.flags |= SERV_ALL;
|
|
break;
|
|
}
|
|
/* Expand the `$' token recursively down */
|
|
can_expand_name = false;
|
|
list_for_each(ptr, sysfaci_start) {
|
|
if (!strcmp(token, getfaci(ptr)->name)) {
|
|
list_t * lst;
|
|
can_expand_name = true;
|
|
np_list_for_each(lst, &getfaci(ptr)->replace)
|
|
rememberreq(serv, bit, getrepl(lst)->name);
|
|
break;
|
|
}
|
|
}
|
|
if (! can_expand_name)
|
|
warn("warning: could not find all dependencies for %s\n", token);
|
|
|
|
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;
|
|
/* fall through */
|
|
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)->name);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check required services for name
|
|
*/
|
|
static boolean chkrequired(service_t *restrict serv, const boolean recursive) attribute((nonnull(1)));
|
|
static boolean chkrequired(service_t *restrict serv, const boolean recursive)
|
|
{
|
|
boolean ret = true;
|
|
list_t * pos;
|
|
|
|
/* Technically, it is not possible for this function to be called if serv is
|
|
NULL, which makes the compiler complain about this check. Commenting it
|
|
out since neither of these conditions is likely to change given program's
|
|
mature/legacy nature. -- Jesse
|
|
if (!serv)
|
|
return ret;
|
|
*/
|
|
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) {
|
|
if (recursive) {
|
|
must->attr.flags |= SERV_ENFORCE;
|
|
continue; /* Enabled this later even if not on command line */
|
|
}
|
|
if ((must->attr.flags & SERV_WARNED) == 0) {
|
|
warn("FATAL: service %s has to be enabled to use service %s\n",
|
|
req->serv->name, serv->name);
|
|
must->attr.flags |= SERV_WARNED;
|
|
}
|
|
ret = false;
|
|
}
|
|
}
|
|
#if 0
|
|
/*
|
|
* Once we may use REQ_MUST for X-Start-Before and/or
|
|
* X-Stop-After we may enable this, see reversereq()
|
|
*/
|
|
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("FATAL: service %s has to be enabled to use service %s\n",
|
|
serv->name, rev->serv->name);
|
|
ret = false;
|
|
}
|
|
}
|
|
#endif
|
|
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("FATAL: service %s has to be enabled to use 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
|
|
* beginning of the future stop order respectivly to the
|
|
* end of the future 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;
|
|
|
|
if (serv->attr.flags & SERV_DUPLET)
|
|
continue; /* Duplet */
|
|
|
|
if (!(serv->attr.flags & SERV_FIRST))
|
|
continue;
|
|
|
|
if (serv->attr.script == (char*)0)
|
|
continue;
|
|
|
|
list_for_each(tmp, s_start) {
|
|
service_t * cur = getservice(tmp);
|
|
|
|
if (cur->attr.flags & SERV_DUPLET)
|
|
continue; /* Duplet */
|
|
|
|
if ((serv->stopp->lvl & cur->stopp->lvl) == 0)
|
|
continue;
|
|
|
|
if (cur == serv)
|
|
continue;
|
|
|
|
if (cur->attr.flags & SERV_FIRST)
|
|
continue;
|
|
|
|
rememberreq(serv, REQ_SHLD|REQ_KILL, cur->name);
|
|
}
|
|
|
|
setorder(serv->attr.script, 'K', 1, false);
|
|
}
|
|
|
|
list_for_each(pos, s_start) {
|
|
service_t * serv = getservice(pos);
|
|
list_t * tmp;
|
|
|
|
if (serv->attr.flags & SERV_DUPLET)
|
|
continue; /* Duplet */
|
|
|
|
if (!(serv->attr.flags & SERV_ALL))
|
|
continue;
|
|
|
|
if (serv->attr.script == (char*)0)
|
|
continue;
|
|
|
|
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 == serv)
|
|
continue;
|
|
|
|
if (cur->attr.flags & SERV_ALL)
|
|
continue;
|
|
|
|
rememberreq(serv, REQ_SHLD, cur->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
const service_t *serv;
|
|
char current_path[PATH_MAX];
|
|
|
|
if (dryrun) {
|
|
#ifdef USE_KILL_IN_BOOT
|
|
info(1, "dryrun, not creating depend.boot, depend.start, depend.halt, and depend.stop in %s\n", dependency_path);
|
|
#else /* not USE_KILL_IN_BOOT */
|
|
info(1, "dryrun, not creating depend.boot, depend.start, and depend.stop in %s\n", dependency_path);
|
|
#endif /* not USE_KILL_IN_BOOT */
|
|
return;
|
|
}
|
|
snprintf(current_path, PATH_MAX, "%sdepend.boot", dependency_path);
|
|
if (!(boot = fopen(current_path, "w"))) {
|
|
warn("fopen(%s): %s\n", current_path, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
snprintf(current_path, PATH_MAX, "%sdepend.start", dependency_path);
|
|
if (!(start = fopen(current_path, "w"))) {
|
|
warn("fopen(%s): %s\n", current_path, strerror(errno));
|
|
fclose(boot);
|
|
return;
|
|
}
|
|
|
|
info(1, "creating %sdepend.boot\n", dependency_path);
|
|
info(1, "creating %sdepend.start\n", dependency_path);
|
|
|
|
lsort('S'); /* Sort into start order, set new sorder */
|
|
|
|
target = (char*)0;
|
|
fprintf(boot, "TARGETS =");
|
|
while ((serv = listscripts(&target, 'S', LVL_BOOT))) {
|
|
#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 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 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))) {
|
|
#if defined(MINIMAL_DEPEND) && (MINIMAL_DEPEND != 0)
|
|
const service_t * lserv[100] = {0};
|
|
unsigned long lcnt = 0;
|
|
#endif /* not MINIMAL_DEPEND */
|
|
boolean mark;
|
|
list_t * pos;
|
|
|
|
#if defined(MINIMAL_RULES) && (MINIMAL_RULES != 0)
|
|
if (serv->attr.ref <= 0)
|
|
continue;
|
|
#endif /* not MINIMAL_RULES */
|
|
|
|
if (serv->start->lvl & LVL_BOOT)
|
|
out = boot;
|
|
else
|
|
out = start;
|
|
|
|
if (list_empty(&serv->sort.req))
|
|
continue;
|
|
|
|
mark = false;
|
|
|
|
np_list_for_each(pos, &serv->sort.req) {
|
|
req_t * req = getreq(pos);
|
|
service_t * dep = req->serv;
|
|
#if defined(MINIMAL_DEPEND) && (MINIMAL_DEPEND != 0)
|
|
boolean shadow = false;
|
|
unsigned long n;
|
|
#endif /* not MINIMAL_DEPEND */
|
|
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;
|
|
}
|
|
#if defined(MINIMAL_DEPEND) && (MINIMAL_DEPEND != 0)
|
|
for (n = 0; n < lcnt && lserv[n] ; n++) {
|
|
list_t * red;
|
|
if (lserv[n]->attr.sorder <= dep->attr.sorder)
|
|
break;
|
|
np_list_for_each(red, &(lserv[n])->sort.req) {
|
|
req_t * other = getreq(red);
|
|
if (other->serv->attr.flags & SERV_DUPLET)
|
|
continue;
|
|
if (other->serv->attr.ref <= 0)
|
|
continue;
|
|
if ((serv->start->lvl & other->serv->start->lvl) == 0)
|
|
continue;
|
|
if (!other->serv->attr.script)
|
|
continue;
|
|
if (other->serv != dep)
|
|
continue;
|
|
shadow = true;
|
|
}
|
|
}
|
|
if (shadow)
|
|
continue;
|
|
#endif /* not MINIMAL_DEPEND */
|
|
fprintf(out, " %s", name);
|
|
|
|
#if defined(MINIMAL_DEPEND) && (MINIMAL_DEPEND != 0)
|
|
if (lcnt >= sizeof(lserv)/sizeof(lserv[0]))
|
|
continue;
|
|
lserv[lcnt++] = dep;
|
|
#endif /* not MINIMAL_DEPEND */
|
|
}
|
|
|
|
if (mark) fputc('\n', out);
|
|
}
|
|
|
|
fclose(boot);
|
|
fclose(start);
|
|
|
|
snprintf(current_path, PATH_MAX, "%sdepend.stop", dependency_path);
|
|
if (!(stop = fopen(current_path, "w"))) {
|
|
warn("fopen(%s): %s\n", current_path, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_KILL_IN_BOOT
|
|
snprintf(current_path, PATH_MAX, "%sdepend.halt", dependency_path);
|
|
if (!(halt = fopen(current_path, "w"))) {
|
|
warn("fopen(%s): %s\n", current_path, strerror(errno));
|
|
fclose(stop);
|
|
return;
|
|
}
|
|
|
|
info(1, "creating %sdepend.halt\n", dependency_path);
|
|
#endif /* USE_KILL_IN_BOOT */
|
|
info(1, "creating %sdepend.stop\n", dependency_path);
|
|
|
|
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 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 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)))) {
|
|
#if defined(MINIMAL_DEPEND) && (MINIMAL_DEPEND != 0)
|
|
const service_t * lserv[100] = {0};
|
|
unsigned long lcnt = 0;
|
|
#endif /* not MINIMAL_DEPEND */
|
|
boolean mark;
|
|
list_t * pos;
|
|
|
|
#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;
|
|
#if defined(MINIMAL_DEPEND) && (MINIMAL_DEPEND != 0)
|
|
boolean shadow = false;
|
|
unsigned long n;
|
|
#endif /* not MINIMAL_DEPEND */
|
|
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;
|
|
}
|
|
#if defined(MINIMAL_DEPEND) && (MINIMAL_DEPEND != 0)
|
|
for (n = 0; n < lcnt && lserv[n]; n++) {
|
|
list_t * red;
|
|
if (lserv[n]->attr.korder <= dep->attr.korder)
|
|
break;
|
|
np_list_for_each(red, &(lserv[n])->sort.rev) {
|
|
req_t * other = getreq(red);
|
|
if (other->serv->attr.flags & SERV_DUPLET)
|
|
continue;
|
|
if (other->serv->attr.ref <= 0)
|
|
continue;
|
|
if ((serv->start->lvl & other->serv->start->lvl) == 0)
|
|
continue;
|
|
if (!other->serv->attr.script)
|
|
continue;
|
|
if (other->serv != dep)
|
|
continue;
|
|
shadow = true;
|
|
}
|
|
}
|
|
if (shadow)
|
|
continue;
|
|
#endif /* not MINIMAL_DEPEND */
|
|
fprintf(out, " %s", name);
|
|
|
|
#if defined(MINIMAL_DEPEND) && (MINIMAL_DEPEND != 0)
|
|
if (lcnt >= sizeof(lserv)/sizeof(lserv[0]))
|
|
continue;
|
|
lserv[lcnt++] = dep;
|
|
#endif /* not MINIMAL_DEPEND */
|
|
}
|
|
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)
|
|
{
|
|
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, ...)
|
|
{
|
|
if (silent_mode)
|
|
return; /* do not print warnings in silent mode */
|
|
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
_logger(fmt, ap);
|
|
va_end(ap);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Print message when verbose is enabled
|
|
*/
|
|
void info(int level, const char *fmt, ...) {
|
|
va_list ap;
|
|
if (level > 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(1, "creating directory '%s'\n", rcpath);
|
|
if (!dryrun)
|
|
if (0 > mkdir(rcpath, (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)))
|
|
error("mkdir(%s, ...) failed: %s", rcpath, strerror(errno));
|
|
} 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);
|
|
regcompiler(®.interact, INTERACTIVE, 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);
|
|
xreset(script_inf.interactive);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if the script is an OpenRC script.
|
|
*/
|
|
|
|
int is_openrc_job(const char *path)
|
|
{
|
|
char buf[64];
|
|
FILE *script = NULL;
|
|
char *p = NULL;
|
|
|
|
script = fopen(path, "r");
|
|
if (script == NULL) {
|
|
warn("Can not open script %s: %s\n", path, strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
if (fgets(buf, 64, script) == NULL) {
|
|
warn("Could not read script %s: %s\n", path, strerror(errno));
|
|
fclose(script);
|
|
return 0;
|
|
}
|
|
fclose(script);
|
|
|
|
p = buf;
|
|
while (*p) {
|
|
if (isspace(*p)) {
|
|
memmove(p , p + 1, strlen(p + 1) + 1);
|
|
}
|
|
p++;
|
|
}
|
|
|
|
if (! strncmp(buf, "#!/sbin/openrc-run", 18))
|
|
return 1;
|
|
else if (! strncmp(buf, "#!/usr/sbin/openrc-run", 22) )
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* return name of upstart job if the script is a symlink to
|
|
* /lib/init/upstart-job, or NULL if path do not point to an
|
|
* upstart job.
|
|
*/
|
|
static char *is_upstart_job(const char *path)
|
|
{
|
|
uint deep = 0;
|
|
char buf[PATH_MAX+1];
|
|
char *script = xstrdup(path);
|
|
char *retval = basename(path); /* GNU basename */
|
|
|
|
buf[PATH_MAX] = '\0';
|
|
|
|
do {
|
|
struct stat statbuf;
|
|
int len;
|
|
|
|
if (deep++ > MAXSYMLINKS) {
|
|
errno = ELOOP;
|
|
warn("Can not determine upstart job name for %s: %s\n", path, strerror(errno));
|
|
break;
|
|
}
|
|
|
|
if (lstat(script, &statbuf) < 0) {
|
|
warn("Can not stat %s: %s\n", path, strerror(errno));
|
|
break;
|
|
}
|
|
|
|
if (!S_ISLNK(statbuf.st_mode))
|
|
break;
|
|
|
|
if ((len = readlink(script, buf, sizeof(buf)-1)) < 0)
|
|
break;
|
|
buf[len] = '\0';
|
|
|
|
if (buf[0] != '/') { /* restore relative links */
|
|
const char *lastslash;
|
|
|
|
if ((lastslash = strrchr(script, '/'))) {
|
|
size_t dirlen = lastslash - script + 1;
|
|
|
|
if (dirlen + len > PATH_MAX)
|
|
len = PATH_MAX - dirlen;
|
|
|
|
memmove(&buf[dirlen], &buf[0], len + 1);
|
|
memcpy(&buf[0], script, dirlen);
|
|
}
|
|
}
|
|
|
|
free(script);
|
|
|
|
if (strcmp(buf, upstartjob_path) == 0) {
|
|
info(2, "script '%s' is upstart job\n", retval);
|
|
return strdup(retval);
|
|
}
|
|
|
|
script = xstrdup(buf);
|
|
|
|
} while (1);
|
|
|
|
free(script);
|
|
|
|
return (char*)0;
|
|
}
|
|
|
|
#define FOUND_LSB_HEADER 0x01
|
|
#define FOUND_LSB_DEFAULT 0x02
|
|
#define FOUND_LSB_OVERRIDE 0x04
|
|
#define FOUND_LSB_UPSTART 0x08
|
|
#define FOUND_LSB_SYSTEMD 0x10
|
|
#define FOUND_LSB_OPENRC 0x20
|
|
|
|
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 *upstart_job = (char*)0;
|
|
char *begin = (char*)0, *end = (char*)0;
|
|
char *pbuf = buf;
|
|
FILE *script;
|
|
uchar ret = 0;
|
|
int fd = -1;
|
|
|
|
#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
|
|
#define interactive script_inf.interactive
|
|
|
|
info(2, "Loading %s\n", path);
|
|
|
|
if (NULL != (upstart_job = is_upstart_job(path))) {
|
|
char cmd[PATH_MAX];
|
|
int len;
|
|
len = snprintf(cmd, sizeof(cmd), "%s %s lsb-header", upstartjob_path, upstart_job);
|
|
if (len < 0 || sizeof(cmd) == len)
|
|
error("snprintf: insufficient buffer for %s\n", path);
|
|
if ((script = popen(cmd, "r")) == (FILE*)0)
|
|
error("popen(%s): %s\n", path, strerror(errno));
|
|
ret |= FOUND_LSB_UPSTART;
|
|
} else {
|
|
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;
|
|
}
|
|
|
|
if (!interactive && regexecutor(®.interact, COMMON_SHD_ARGS) == true) {
|
|
if (shl->rm_so < shl->rm_eo) {
|
|
*(pbuf+shl->rm_eo) = '\0';
|
|
interactive = xstrdup(pbuf+shl->rm_so);
|
|
} else
|
|
interactive = empty;
|
|
}
|
|
|
|
/* Skip scanning below from LSB magic end */
|
|
if ((end = strstr(buf, "### END INIT INFO")))
|
|
break;
|
|
}
|
|
#undef COMMON_ARGS
|
|
#undef COMMON_SHD_ARGS
|
|
|
|
if (upstart_job) {
|
|
pclose(script);
|
|
xreset(upstart_job);
|
|
} else {
|
|
#if defined _XOPEN_SOURCE && (_XOPEN_SOURCE - 0) >= 600
|
|
if (cache) {
|
|
off_t deep = ftello(script);
|
|
if (-1 != deep) {
|
|
(void)posix_fadvise(fd, 0, deep, POSIX_FADV_WILLNEED);
|
|
(void)posix_fadvise(fd, deep, 0, POSIX_FADV_DONTNEED);
|
|
} else
|
|
warn("ftello(%s) failed: %s\n", path, strerror(errno));
|
|
} 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("%sscript %s is broken: missing end of LSB comment.\n", ignore ? "" : "FATAL: ", 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
|
|
#undef interactive
|
|
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;
|
|
char *upstart_job = (char*)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 */
|
|
|
|
#ifdef WANT_SYSTEMD
|
|
if (systemd) {
|
|
const char *serv;
|
|
serv = path;
|
|
if (strncmp("boot.", serv, 5) == 0)
|
|
serv += 5;
|
|
if (is_overridden_by_systemd(serv)) {
|
|
ret |= FOUND_LSB_SYSTEMD;
|
|
}
|
|
}
|
|
#endif /* WANT_SYSTEMD */
|
|
|
|
if (is_openrc_job(path)) {
|
|
ret |= FOUND_LSB_OPENRC;
|
|
goto out;
|
|
}
|
|
|
|
if (NULL != (upstart_job = is_upstart_job(path))) {
|
|
xreset(upstart_job);
|
|
/*
|
|
* Do not override the upstarts defaults, if we allow this
|
|
* we have to change name to the link name otherwise the
|
|
* name is always "upstart-job"
|
|
*/
|
|
ret |= scan_lsb_headers(dfd, path, cache, ignore);
|
|
if (ret & FOUND_LSB_UPSTART)
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Allow host-specific overrides to replace the content in the
|
|
* init.d scripts
|
|
*/
|
|
ret |= load_overrides(override_path, name, cache, ignore);
|
|
if (ret & FOUND_LSB_OVERRIDE)
|
|
goto out;
|
|
|
|
/*
|
|
* Load third-party-specific values if the override file exist
|
|
*/
|
|
ret |= load_overrides("/usr/share/insserv/overrides", name, cache, ignore);
|
|
if (ret & FOUND_LSB_OVERRIDE)
|
|
goto out;
|
|
|
|
/*
|
|
* Replace with headers from the script itself
|
|
*/
|
|
ret |= scan_lsb_headers(dfd, path, cache, ignore);
|
|
out:
|
|
ret |= FOUND_LSB_DEFAULT;
|
|
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);
|
|
regfree(®.interact);
|
|
}
|
|
|
|
/*
|
|
* 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 < RUNLEVELS; 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 < RUNLEVELS; 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, false, ignore);
|
|
if (!name) {
|
|
warn("warning: script is corrupt or invalid: %s/%s%s\n", path, rcd, d->d_name);
|
|
continue;
|
|
}
|
|
|
|
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|FOUND_LSB_OPENRC)) == 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 (script_inf.interactive && 0 == strcmp(script_inf.interactive, "true")) {
|
|
service->attr.flags |= SERV_INTRACT;
|
|
}
|
|
}
|
|
|
|
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(2, "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;
|
|
list_t *r_list = (list_t*)0;
|
|
list_for_each(ptr, sysfaci_start) {
|
|
if (!strcmp(getfaci(ptr)->name, virt)) {
|
|
r_list = &getfaci(ptr)->replace;
|
|
break;
|
|
}
|
|
}
|
|
if (!r_list) {
|
|
faci_t *restrict this;
|
|
if (posix_memalign((void*)&this, sizeof(void*), alignof(faci_t)) != 0)
|
|
error("%s", strerror(errno));
|
|
else {
|
|
r_list = &this->replace;
|
|
initial(r_list);
|
|
insert(&this->list, sysfaci_start->prev);
|
|
this->name = xstrdup(virt);
|
|
}
|
|
}
|
|
if(real) {
|
|
char *token;
|
|
while ((token = strsep(&real, delimeter))) {
|
|
repl_t *restrict subst;
|
|
string_t *r = (string_t*)0;
|
|
if (posix_memalign((void*)&subst, sizeof(void*), alignof(repl_t)) != 0)
|
|
error("%s", strerror(errno));
|
|
insert(&subst->r_list, r_list->prev);
|
|
subst->flags = 0;
|
|
list_for_each(ptr, facistr_start) {
|
|
if (strcmp(getfstr(ptr)->name, token) == 0) {
|
|
r = getfstr(ptr);
|
|
break;
|
|
}
|
|
}
|
|
if (!r) {
|
|
if (posix_memalign((void*)&r, sizeof(void*), alignof(string_t)+strsize(token)) != 0)
|
|
error("%s", strerror(errno));
|
|
r->ref = 1;
|
|
insert(&r->s_list, facistr_start);
|
|
r->name = ((char*)r)+alignof(string_t);
|
|
strcpy(r->name, token);
|
|
} else
|
|
r->ref++;
|
|
subst->addr = r;
|
|
subst->name = r->name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 || (*name == '\0'))
|
|
goto out;
|
|
if (*name == '.')
|
|
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", 4) || /* .dpkg-old, .dpkg-new ... */
|
|
!strncmp(end, "ucf", 3) || /* .ucf-old, .ucf-dist ... */
|
|
!strcmp(end, "save") ||
|
|
!strcmp(end, "swp") || /* Used by vi like editors */
|
|
!strcmp(end, "core")) /* modern core dump */
|
|
{
|
|
goto out;
|
|
}
|
|
/* check loaded filters */
|
|
else if (file_filters)
|
|
{
|
|
boolean found = false;
|
|
int index = 0;
|
|
while ( (file_filters[index]) && (! found) )
|
|
{
|
|
if (! strcmp(end, file_filters[index]) )
|
|
found = true;
|
|
else
|
|
index++;
|
|
}
|
|
if (found)
|
|
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);
|
|
}
|
|
|
|
#ifdef WANT_SYSTEMD
|
|
/*
|
|
* Maps between systemd and SystemV
|
|
*/
|
|
static const char* sdmap[] = {
|
|
"$local-fs", "$local_fs",
|
|
"$remote-fs", "$remote_fs",
|
|
"cryptsetup", "boot.crypto-early",
|
|
"udev", "boot.udev",
|
|
"multipathd", "boot.multipath",
|
|
"loadmodules", "boot.loadmodules",
|
|
"device-mapper", "boot.device-mapper",
|
|
"sysctl", "boot.sysctl",
|
|
"fsck-root", "boot.rootfsck",
|
|
"localfs", "boot.localfs"
|
|
};
|
|
|
|
#endif /* WANT_SYSTEMD */
|
|
|
|
#ifdef WANT_SYSTEMD
|
|
/*
|
|
* Here the systemd targets are imported as system facilities
|
|
*/
|
|
static void import_systemd_facilities(void)
|
|
{
|
|
list_t *ptr;
|
|
list_for_each(ptr, &sdservs) {
|
|
sdserv_t *sdserv = list_entry(ptr, sdserv_t, s_list);
|
|
const char *facilitiy;
|
|
list_t *r_list;
|
|
list_t *iptr;
|
|
int n;
|
|
|
|
if (*sdserv->name != '$')
|
|
continue;
|
|
|
|
facilitiy = sdserv->name;
|
|
for (n = 0; n < (int)(sizeof(sdmap)/sizeof(sdmap[0])); n += 2) {
|
|
if (strcmp(sdmap[n], facilitiy) == 0) {
|
|
facilitiy = sdmap[n+1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
r_list = (list_t*)0;
|
|
np_list_for_each(iptr, sysfaci_start) {
|
|
if (strcmp(getfaci(iptr)->name, facilitiy) == 0) {
|
|
r_list = &getfaci(iptr)->replace;
|
|
break;
|
|
}
|
|
}
|
|
if (!r_list) {
|
|
faci_t *restrict this;
|
|
if (posix_memalign((void*)&this, sizeof(void*), alignof(faci_t)) != 0)
|
|
error("%s", strerror(errno));
|
|
else {
|
|
r_list = &this->replace;
|
|
initial(r_list);
|
|
insert(&this->list, sysfaci_start->prev);
|
|
this->name = xstrdup(facilitiy);
|
|
}
|
|
}
|
|
|
|
np_list_for_each(iptr, &sdserv->a_list) {
|
|
ally_t *ally = list_entry(iptr, ally_t, a_list);
|
|
repl_t *restrict subst;
|
|
string_t *r = (string_t*)0;
|
|
const char *token;
|
|
list_t *fptr;
|
|
|
|
if (ally->flags & SDREL_CONFLICTS)
|
|
continue;
|
|
if (ally->flags & SDREL_BEFORE)
|
|
continue;
|
|
|
|
token = ally->serv->name;
|
|
for (n = 0; n < (int)(sizeof(sdmap)/sizeof(sdmap[0])); n += 2) {
|
|
if (strcmp(sdmap[n], token) == 0) {
|
|
token = sdmap[n+1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (posix_memalign((void*)&subst, sizeof(void*), alignof(repl_t)) != 0)
|
|
error("%s", strerror(errno));
|
|
insert(&subst->r_list, r_list->prev);
|
|
subst->flags = 0;
|
|
np_list_for_each(fptr, facistr_start) {
|
|
if (strcmp(getfstr(fptr)->name, token) == 0) {
|
|
r = getfstr(fptr);
|
|
break;
|
|
}
|
|
}
|
|
if (!r) {
|
|
if (posix_memalign((void*)&r, sizeof(void*), alignof(string_t)+strsize(token)) != 0)
|
|
error("%s", strerror(errno));
|
|
r->ref = 1;
|
|
insert(&r->s_list, facistr_start);
|
|
r->name = ((char*)r)+alignof(string_t);
|
|
strcpy(r->name, token);
|
|
} else
|
|
r->ref++;
|
|
subst->addr = r;
|
|
subst->name = r->name;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Here the systemd servies are imported as services
|
|
*/
|
|
static void import_systemd_services(void)
|
|
{
|
|
list_t *ptr;
|
|
list_for_each(ptr, &sdservs) {
|
|
sdserv_t *sdserv = list_entry(ptr, sdserv_t, s_list);
|
|
const char *this;
|
|
list_t *aptr;
|
|
int n;
|
|
|
|
if (*sdserv->name == '$')
|
|
continue;
|
|
|
|
this = sdserv->name;
|
|
for (n = 0; n < (int)(sizeof(sdmap)/sizeof(sdmap[0])); n += 2) {
|
|
if (strcmp(sdmap[n], this) == 0) {
|
|
this = sdmap[n+1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
np_list_for_each(aptr, &sdserv->a_list) {
|
|
ally_t *ally = list_entry(aptr, ally_t, a_list);
|
|
service_t * service;
|
|
const char *token;
|
|
|
|
if (ally->flags & SDREL_CONFLICTS)
|
|
continue;
|
|
if (ally->flags & SDREL_BEFORE)
|
|
continue;
|
|
|
|
token = ally->serv->name;
|
|
for (n = 0; n < (int)(sizeof(sdmap)/sizeof(sdmap[0])); n += 2) {
|
|
if (strcmp(sdmap[n], token) == 0) {
|
|
token = sdmap[n+1];
|
|
break;
|
|
}
|
|
}
|
|
service = addservice(this);
|
|
rememberreq(service, (ally->flags & SDREL_WANTS) ? REQ_SHLD : REQ_MUST, token);
|
|
service->attr.flags |= SERV_SYSTEMD;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif /* WANT_SYSTEMD */
|
|
|
|
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->name)) {
|
|
ptr = &getfaci(tmp)->replace;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ptr || list_empty(ptr))
|
|
goto out;
|
|
|
|
list_for_each_safe(tmp, safe, ptr) {
|
|
repl_t *rnxt = getrepl(tmp);
|
|
if (rnxt->flags & 0x0001) {
|
|
error("Loop detected during expanding system facilities in the insserv.conf file(s): %s %s\n",
|
|
rnxt->name, getrepl(head->prev)->name);
|
|
}
|
|
if (*rnxt->name == '$') {
|
|
if (*deep > 10) {
|
|
warn("The nested level of the system facilities in the insserv.conf file(s) is to large\n");
|
|
goto out;
|
|
}
|
|
(*deep)++;
|
|
rnxt->flags |= 0x0001;
|
|
expand_faci(tmp, head, deep);
|
|
rnxt->flags &= ~0x0001;
|
|
(*deep)--;
|
|
} else if (*deep >= 0) {
|
|
repl_t *restrict subst;
|
|
if (posix_memalign((void*)&subst, sizeof(void*), alignof(repl_t)) != 0)
|
|
error("%s", strerror(errno));
|
|
insert(&subst->r_list, head->prev);
|
|
subst->addr = rnxt->addr;
|
|
subst->name = rnxt->name;
|
|
subst->addr->ref++;
|
|
}
|
|
}
|
|
out:
|
|
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) {
|
|
repl_t *tmp = getrepl(rlist);
|
|
if (*tmp->name == '$') {
|
|
int deep = 0;
|
|
expand_faci(rlist, head, &deep);
|
|
delete(rlist);
|
|
if (--(tmp->addr->ref) <= 0)
|
|
free(tmp->addr);
|
|
free(tmp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Scan for a Start or Kill script within a runlevel directory.
|
|
* We start where 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;
|
|
boolean mnt = true;
|
|
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);
|
|
do {
|
|
if ((fd = open(buf, O_NOCTTY|O_RDONLY)) >= 0)
|
|
break;
|
|
if (!mnt || (errno != ENOENT))
|
|
goto out;
|
|
if (mount("proc", "/proc", "proc", 0, NULL) < 0)
|
|
error ("underrpm() can not mount /proc: %s\n", strerror(errno));
|
|
mnt = false;
|
|
} while (1);
|
|
|
|
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);
|
|
if (!mnt)
|
|
umount("/proc");
|
|
|
|
return ret;
|
|
}
|
|
#endif /* SUSE */
|
|
|
|
#ifdef WANT_SYSTEMD
|
|
|
|
/*
|
|
* Systemd integration
|
|
*/
|
|
static boolean is_overridden_by_systemd(const char *service) {
|
|
char *p;
|
|
boolean ret = false;
|
|
|
|
if (asprintf(&p, SYSTEMD_SERVICE_PATH "/%s.service", service) < 0)
|
|
error("asprintf(): %s\n", strerror(errno));
|
|
|
|
if (access(p, F_OK) >= 0)
|
|
ret = true;
|
|
free(p);
|
|
return ret;
|
|
}
|
|
|
|
static void forward_to_systemd (const char *initscript, const char *verb, boolean alternative_root) {
|
|
const char *name;
|
|
|
|
if (initscript == NULL)
|
|
return;
|
|
|
|
if (strncmp("boot.",initscript,5) == 0)
|
|
name = initscript+5;
|
|
else
|
|
name = initscript;
|
|
|
|
if (is_overridden_by_systemd (name)) {
|
|
char *p;
|
|
int err = 0;
|
|
|
|
if (alternative_root && root)
|
|
err = asprintf (&p, "/bin/systemctl --quiet --no-reload --root %s %s %s.service", root, verb, name);
|
|
else {
|
|
struct statfs stfs;
|
|
if (statfs("/sys/fs/cgroup/systemd", &stfs) < 0 && errno != ENOENT)
|
|
error("statfs(): %s\n", strerror(errno));
|
|
if (errno == 0 && stfs.f_type == CGROUP_SUPER_MAGIC)
|
|
err = asprintf (&p, "/bin/systemctl --quiet %s %s.service", verb, name);
|
|
else
|
|
err = asprintf (&p, "/bin/systemctl --quiet --no-reload %s %s.service", verb, name);
|
|
}
|
|
if (err < 0)
|
|
error("asprintf(): %s\n", strerror(errno));
|
|
|
|
warn("Note: sysvinit service %s is shadowed by systemd %s.service,\nForwarding request to '%s'.\n", initscript, name, p);
|
|
if (!dryrun)
|
|
err = system(p);
|
|
if (err < 0)
|
|
warn("Failed to forward service request to systemctl: %m\n");
|
|
else if (err > 0)
|
|
warn("Forward service request to systemctl returned error status : %d\n",err);
|
|
free (p);
|
|
}
|
|
}
|
|
|
|
#endif /* WANT_SYSTEMD */
|
|
|
|
/* See if the start and stop runlevels of a script use the same
|
|
stop or stop levels.
|
|
Returns true if overlap is found and false is none is found.
|
|
*/
|
|
boolean Start_Stop_Overlap(char *start_levels, char *stop_levels)
|
|
{
|
|
boolean found_overlap = false;
|
|
int string_index = 0;
|
|
char *found;
|
|
|
|
if (!start_levels || !stop_levels) return false;
|
|
|
|
while (start_levels[string_index]) /* go to end of string */
|
|
{
|
|
if (start_levels[string_index] != ' ') /* skip spaces */
|
|
{
|
|
found = strchr(stop_levels, start_levels[string_index]);
|
|
if (found)
|
|
found_overlap = true;
|
|
}
|
|
string_index++;
|
|
}
|
|
return found_overlap;
|
|
}
|
|
|
|
|
|
static struct option long_options[] =
|
|
{
|
|
{"verbose", 0, (int*)0, 'v'},
|
|
{"config", 1, (int*)0, 'c'},
|
|
{"dryrun", 0, (int*)0, 'n'},
|
|
{"dry-run", 0, (int*)0, 'n'},
|
|
{"default", 0, (int*)0, 'd'},
|
|
{"remove", 0, (int*)0, 'r'},
|
|
{"force", 0, (int*)0, 'f'},
|
|
{"insserv-dir", 1, (int*)0, 'i'},
|
|
/* {"legacy-path", 0, (int*)0, 'l'}, */
|
|
{"path", 1, (int*)0, 'p'},
|
|
{"override", 1, (int*)0, 'o'},
|
|
{"upstart-job", 1, (int*)0, 'u'},
|
|
{"silent", 0, (int*)0, 'q'},
|
|
{"recursive", 0, (int*)0, 'e'},
|
|
{"showall", 0, (int*)0, 's'},
|
|
{"show-all", 0, (int*)0, 's'},
|
|
{"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(" -q, --silent Do not print warnings, only fatal errors.\n");
|
|
/* printf(" -l, --legacy-path Place dependency files in /etc/init.d instead of /lib/insserv.\n"); */
|
|
printf(" -i, --insserv-dir Place dependency files in a location other than /lib/insserv\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, --dry-run Do not change the system, only talk about it.\n");
|
|
printf(" -s, --show-all Output runlevel and sequence information.\n");
|
|
printf(" -u <path>, --upstart-job <path> Path to replace existing upstart job path.\n");
|
|
printf(" -e, --recursive Expand and enable all required services.\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 * 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;
|
|
boolean loadarg = false;
|
|
boolean recursive = false;
|
|
boolean showall = false;
|
|
boolean waserr = false;
|
|
/* boolean legacy_path = false; */
|
|
boolean free_dependency_path = false;
|
|
boolean overlap;
|
|
|
|
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:dfrhqvni:o:p:u:es", 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 'q':
|
|
silent_mode = true;
|
|
break;
|
|
case 'v':
|
|
verbose ++;
|
|
break;
|
|
/*
|
|
case 'l':
|
|
dependency_path = LEGACY_DEPENDENCY_PATH;
|
|
legacy_path = true;
|
|
break;
|
|
*/
|
|
case 'i':
|
|
if ( (! optarg) || (! optarg[0]) )
|
|
{
|
|
fprintf(stderr, "Please provide a valid path\n");
|
|
goto err;
|
|
}
|
|
if (optarg[0] == '/') // absolute path
|
|
asprintf(&dependency_path, "%s/.", optarg);
|
|
else // relative
|
|
{
|
|
char current_dir[PATH_MAX];
|
|
getcwd(current_dir, PATH_MAX);
|
|
asprintf(&dependency_path, "%s/%s/.", current_dir, optarg);
|
|
}
|
|
free_dependency_path = true;
|
|
break;
|
|
case 'n':
|
|
verbose ++;
|
|
dryrun = true;
|
|
break;
|
|
case 's':
|
|
showall = 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 'u':
|
|
if (optarg == (char*)0 || *optarg == '\0')
|
|
goto err;
|
|
upstartjob_path = optarg;
|
|
break;
|
|
case 'e':
|
|
recursive = 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)
|
|
loadarg = true;
|
|
else if (del)
|
|
error("usage: %s [[-r] init_script|init_directory]\n", myname);
|
|
|
|
/* Make sure the target directory exists */
|
|
/*
|
|
if ( (! dryrun) && (! legacy_path) )
|
|
mkdir(DEPENDENCY_PATH, 0755);
|
|
*/
|
|
|
|
/* load options from /etc/inssserv/ directory */
|
|
file_filters = Load_File_Filters();
|
|
|
|
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;
|
|
if (*path != '/') {
|
|
char * pwd = getcwd((char*)0, 0);
|
|
size_t len;
|
|
if (NULL == pwd)
|
|
error("unable to find current working directory: %s",
|
|
strerror(errno));
|
|
len = strlen(pwd)+2+strlen(path);
|
|
root = (char*)malloc(len);
|
|
if (!root)
|
|
error("%s", strerror(errno));
|
|
strcpy(root, pwd);
|
|
if (pwd[1])
|
|
strcat(root, "/");
|
|
strcat(root, path);
|
|
free(pwd);
|
|
} else
|
|
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 */
|
|
|
|
#ifdef SUSE
|
|
if (!underrpm())
|
|
#endif
|
|
/*
|
|
* Systemd support
|
|
*/
|
|
#ifdef WANT_SYSTEMD
|
|
if (access(SYSTEMD_BINARY_PATH, F_OK) == 0 && (sbus = systemd_open_conn())) {
|
|
|
|
for (c = 0; c < argc; c++)
|
|
forward_to_systemd (argv[c], del ? "disable": "enable", path != ipath);
|
|
|
|
(void)systemd_get_tree(sbus);
|
|
systemd_close_conn(sbus);
|
|
systemd = true;
|
|
}
|
|
#endif /* WANT_SYSTEMD */
|
|
|
|
/*
|
|
* Scan and set our configuration for virtual services.
|
|
*/
|
|
scan_conf(insconf);
|
|
|
|
#ifdef WANT_SYSTEMD
|
|
/*
|
|
* Handle Systemd target as system facilities (<name>.target -> $<name>)
|
|
*/
|
|
if (systemd)
|
|
import_systemd_facilities();
|
|
#endif
|
|
|
|
/*
|
|
* Expand system facilities to real services
|
|
*/
|
|
expand_conf();
|
|
|
|
#ifdef WANT_SYSTEMD
|
|
/*
|
|
* Handle Systemd services (<name>.service -> <name>)
|
|
*/
|
|
if (systemd) {
|
|
import_systemd_services();
|
|
systemd_free(); /* Not used anymore */
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
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, false, ignore);
|
|
|
|
if ((lsb & (FOUND_LSB_HEADER|FOUND_LSB_OPENRC)) == 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
|
|
*/
|
|
for (;;) {
|
|
service_t * service = (service_t*)0;
|
|
char * token;
|
|
char * begin = (char*)0; /* hold start pointer of strings handled by strsep() */
|
|
boolean hard = false;
|
|
boolean isarg = false;
|
|
uchar lsb = 0;
|
|
#if defined(DEBUG) && (DEBUG > 0)
|
|
int nobug = 0;
|
|
#endif
|
|
|
|
if ((d = readdir(initdir)) == (struct dirent*)0) {
|
|
/*
|
|
* If first script in argument list was loaded in advance, then
|
|
* rewind the init.d/ directory stream and attempt to load all
|
|
* other scripts.
|
|
*/
|
|
if (loadarg) {
|
|
loadarg = false;
|
|
rewinddir(initdir);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
isarg = chkfor(d->d_name, argv, argc);
|
|
|
|
/*
|
|
* Load first script in argument list before all other scripts. This
|
|
* avoids problems with loading scripts in underterministic sequence
|
|
* returned by readdir(3).
|
|
*/
|
|
if (loadarg && !isarg)
|
|
continue;
|
|
if (loadarg && isarg && (curr_argc != 0))
|
|
continue;
|
|
if (!loadarg && isarg && (curr_argc == 0))
|
|
continue;
|
|
|
|
if (*d->d_name == '.')
|
|
continue;
|
|
errno = 0;
|
|
|
|
/* d_type seems not to work, therefore use (l)stat(2) */
|
|
if (xlstat(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_ISLNK(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 file, will be skipped in boot sequence!\n", d->d_name);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Do extra sanity checking of symlinks in init.d/ dir, except if it
|
|
* is named reboot, as that is a special case on SUSE
|
|
*/
|
|
if (S_ISLNK(st_script.st_mode) && ((strcmp(d->d_name, "reboot") != 0)))
|
|
{
|
|
char * base;
|
|
char linkbuf[PATH_MAX+1];
|
|
int linklen;
|
|
|
|
linklen = xreadlink(dfd, d->d_name, linkbuf, sizeof(linkbuf)-1);
|
|
if (linklen < 0)
|
|
continue;
|
|
linkbuf[linklen] = '\0';
|
|
|
|
/* skip symbolic links to other scripts in this relative path */
|
|
if (!(base = strrchr(linkbuf, '/'))) {
|
|
if (isarg)
|
|
warn("script %s is a symlink to another script, skipped!\n",
|
|
d->d_name);
|
|
continue;
|
|
}
|
|
|
|
/* stat the symlink target and make sure it is a valid script */
|
|
if (xstat(dfd, d->d_name, &st_script) < 0)
|
|
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, will be skipped in boot sequence!\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 (!strcmp(d->d_name, "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|FOUND_LSB_OPENRC)) == 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 || (lsb & FOUND_LSB_OPENRC)) {
|
|
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) {
|
|
waserr = true;
|
|
continue;
|
|
}
|
|
|
|
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.interactive && 0 == strcmp(script_inf.interactive, "true")) {
|
|
service->attr.flags |= SERV_INTRACT;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
overlap = Start_Stop_Overlap(script_inf.default_start, script_inf.default_stop);
|
|
if (overlap)
|
|
{
|
|
warn("Script `%s' has overlapping Default-Start and Default-Stop runlevels (%s) and (%s). This should be fixed.\n",
|
|
d->d_name, script_inf.default_start, script_inf.default_stop);
|
|
}
|
|
|
|
/*
|
|
* 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, recursive);
|
|
if (!ok && !ignore)
|
|
waserr = true;
|
|
}
|
|
|
|
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' overrides LSB 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) {
|
|
if (!del && isarg && !(argr[curr_argc]))
|
|
warn("warning: current start runlevel(s) (%s) of script `%s' overrides LSB 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' overrides LSB 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) {
|
|
if (!del && isarg && !(argr[curr_argc]))
|
|
warn("warning: current stop runlevel(s) (%s) of script `%s' overrides LSB 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;
|
|
const boolean sk;
|
|
char * order;
|
|
char ** str;
|
|
} mark[] = {
|
|
{"start=", true, (char*)0, &script_inf.default_start},
|
|
{"stop=", false, (char*)0, &script_inf.default_stop },
|
|
#if 0
|
|
{"reqstart=", false, (char*)0, &script_inf.required_start},
|
|
{"reqstop=", false, (char*)0, &script_inf.required_stop },
|
|
#endif
|
|
{(char*)0, false, (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';
|
|
}
|
|
if (ignore) {
|
|
service_t *arg = findservice(getprovides(d->d_name));
|
|
arg = getorig(arg);
|
|
if (mark[c].sk)
|
|
arg->start->lvl = 0;
|
|
else
|
|
arg->stopp->lvl = 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();
|
|
|
|
/*
|
|
* Handle the `$all' scripts
|
|
*/
|
|
all_script();
|
|
|
|
/*
|
|
* Now generate for all scripts the dependencies
|
|
*/
|
|
follow_all();
|
|
if (is_loop_detected() && !ignore)
|
|
error("exiting now without changing boot order!\n");
|
|
|
|
/*
|
|
* Be sure that interactive scripts are the only member of
|
|
* a start group (for parallel start only).
|
|
*/
|
|
active_script();
|
|
|
|
/*
|
|
* Check if runlevels of required scripts are a real subset
|
|
* of the services handled here.
|
|
*/
|
|
if (!del && !ignore) {
|
|
list_t * ptr;
|
|
list_for_each(ptr, s_start) {
|
|
service_t * cur = getservice(ptr);
|
|
ushort clvl = cur->start->lvl & ~LVL_SINGLE;
|
|
list_t * pos;
|
|
|
|
cur = getorig(cur);
|
|
if (list_empty(&cur->sort.req))
|
|
continue;
|
|
|
|
if (cur->attr.flags & SERV_SYSTEMD)
|
|
continue;
|
|
|
|
np_list_for_each(pos, &cur->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_SYSTEMD)
|
|
continue;
|
|
|
|
/*
|
|
* Check for recursive mode the existence of the required services
|
|
*/
|
|
if (cur->attr.flags & SERV_CMDLINE) {
|
|
|
|
if (must->attr.flags & SERV_ENABLED) {
|
|
ushort mlvl = must->start->lvl & ~LVL_SINGLE;
|
|
|
|
if ((mlvl & LVL_BOOT) && (clvl & LVL_BOOT) == 0)
|
|
continue;
|
|
|
|
if ((mlvl & clvl) == clvl)
|
|
continue;
|
|
if (recursive) {
|
|
must->start->lvl |= clvl;
|
|
must->stopp->lvl |= clvl;
|
|
continue;
|
|
}
|
|
clvl &= ~mlvl;
|
|
if ((must->attr.flags & SERV_WARNED) == 0)
|
|
#ifdef OSCBUILD
|
|
warn("Service %s is missed in the runlevels %s to use service %s\n",
|
|
must->name, lvl2str(clvl), cur->name);
|
|
#else
|
|
warn("FATAL: service %s is missed in the runlevels %s to use service %s\n",
|
|
must->name, lvl2str(clvl), cur->name);
|
|
waserr = true;
|
|
#endif
|
|
must->attr.flags |= SERV_WARNED;
|
|
continue;
|
|
}
|
|
if ((must->attr.flags & (SERV_ENFORCE|SERV_KNOWN)) == SERV_ENFORCE) {
|
|
if ((must->attr.flags & SERV_WARNED) == 0)
|
|
#ifdef OSCBUILD
|
|
warn("Service %s has to exist for service %s\n",
|
|
must->name, cur->name);
|
|
#else
|
|
warn("FATAL: service %s has to exist for service %s\n",
|
|
must->name, cur->name);
|
|
waserr = true;
|
|
#endif
|
|
must->attr.flags |= SERV_WARNED;
|
|
continue;
|
|
}
|
|
if (recursive) {
|
|
must->start->lvl |= clvl;
|
|
must->stopp->lvl |= clvl;
|
|
continue;
|
|
}
|
|
continue;
|
|
}
|
|
if ((cur->attr.flags & SERV_ENABLED) == 0)
|
|
continue;
|
|
if ((must->attr.flags & (SERV_CMDLINE|SERV_KNOWN)) == 0) {
|
|
if ((must->attr.flags & SERV_WARNED) == 0)
|
|
#ifdef OSCBUILD
|
|
warn("Service %s has to exist for service %s\n",
|
|
must->name, cur->name);
|
|
#else
|
|
warn("FATAL: service %s has to exist for service %s\n",
|
|
must->name, cur->name);
|
|
waserr = true;
|
|
#endif
|
|
must->attr.flags |= SERV_WARNED;
|
|
continue;
|
|
}
|
|
if (must->attr.flags & SERV_ENABLED) {
|
|
ushort mlvl = must->start->lvl & ~LVL_SINGLE;
|
|
|
|
if ((mlvl & LVL_BOOT) && (clvl & LVL_BOOT) == 0)
|
|
continue;
|
|
|
|
if ((mlvl & clvl) == clvl)
|
|
continue;
|
|
clvl &= ~mlvl;
|
|
if ((must->attr.flags & SERV_WARNED) == 0)
|
|
#ifdef OSCBUILD
|
|
warn("Service %s is missed in the runlevels %s to use service %s\n",
|
|
must->name, lvl2str(clvl), cur->name);
|
|
#else
|
|
warn("FATAL: service %s is missed in the runlevels %s to use service %s\n",
|
|
must->name, lvl2str(clvl), cur->name);
|
|
waserr = true;
|
|
#endif
|
|
must->attr.flags |= SERV_WARNED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (waserr)
|
|
error("exiting now!\n");
|
|
|
|
/*
|
|
* 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 (showall)
|
|
show_all();
|
|
|
|
#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 < RUNLEVELS; 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))) {
|
|
boolean this = chkfor(script, argv, argc);
|
|
boolean found, slink;
|
|
char * clink;
|
|
|
|
if (*script == '$') /* Do not link in virtual dependencies */
|
|
continue;
|
|
|
|
if ((serv->attr.flags & (SERV_ENFORCE|SERV_ENABLED)) == SERV_ENFORCE)
|
|
this = true;
|
|
|
|
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 < RUNLEVELS; 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))) {
|
|
boolean this = chkfor(script, argv, argc);
|
|
boolean found;
|
|
char * clink;
|
|
char mode;
|
|
|
|
if (*script == '$') /* Do not link in virtual dependencies */
|
|
continue;
|
|
|
|
if ((serv->attr.flags & (SERV_ENFORCE|SERV_ENABLED)) == SERV_ENFORCE)
|
|
this = true;
|
|
|
|
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);
|
|
if ( (free_dependency_path) && (dependency_path) )
|
|
free(dependency_path);
|
|
if (file_filters)
|
|
{
|
|
int count = 0;
|
|
while (file_filters[count])
|
|
{
|
|
free(file_filters[count]);
|
|
count++;
|
|
}
|
|
free(file_filters);
|
|
}
|
|
return 0;
|
|
}
|