644ee05aab
Added ability to use "@" symbol in command named in the inittab file. This treats commands as literal and does not launch a shell to interpret them. Updated inittab manual page to include overview of symbols which trigger a shell interpretor and how to disable them using the @ symbol.
3192 lines
70 KiB
C
3192 lines
70 KiB
C
/*
|
|
* Init A System-V Init Clone.
|
|
*
|
|
* Usage: /sbin/init
|
|
* init [0123456SsQqAaBbCc]
|
|
* telinit [0123456SsQqAaBbCc]
|
|
*
|
|
* Version: @(#)init.c 2.86 30-Jul-2004 miquels@cistron.nl
|
|
* Version: init.c 2.90 18-Jun-2018 jsmith@resonatingmedia.com
|
|
*/
|
|
|
|
/*
|
|
Version information is not placed in the top-level Makefile by default
|
|
*/
|
|
#ifndef VERSION
|
|
#define VERSION "3.01"
|
|
#endif
|
|
/*
|
|
* This file is part of the sysvinit suite,
|
|
* Copyright (C) 1991-2004 Miquel van Smoorenburg.
|
|
* Copyright (C) 2017-2019 Jesse Smith
|
|
*
|
|
* 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
|
|
*
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/wait.h>
|
|
#ifdef __linux__
|
|
#include <sys/kd.h>
|
|
#endif
|
|
#include <sys/resource.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <termios.h>
|
|
#ifdef __FreeBSD__
|
|
#include <utmpx.h>
|
|
#else
|
|
#include <utmp.h>
|
|
#endif
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <sys/ttydefaults.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/time.h>
|
|
/*
|
|
* inittab.d
|
|
*/
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
|
|
#ifdef WITH_SELINUX
|
|
# include <selinux/selinux.h>
|
|
#endif
|
|
#ifdef __FreeBSD__
|
|
extern char **environ;
|
|
#endif
|
|
|
|
#ifdef __i386__
|
|
# ifdef __GLIBC__
|
|
/* GNU libc 2.x */
|
|
# define STACK_DEBUG 1
|
|
# if (__GLIBC__ == 2 && __GLIBC_MINOR__ == 0)
|
|
/* Only glibc 2.0 needs this */
|
|
# include <sigcontext.h>
|
|
# elif ( __GLIBC__ > 2) && ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1))
|
|
# include <bits/sigcontext.h>
|
|
# endif
|
|
# endif
|
|
#endif
|
|
|
|
#include "init.h"
|
|
#include "initreq.h"
|
|
#include "paths.h"
|
|
#include "reboot.h"
|
|
#include "runlevellog.h"
|
|
#include "set.h"
|
|
|
|
#ifndef SIGPWR
|
|
# define SIGPWR SIGUSR2
|
|
#endif
|
|
|
|
#ifndef CBAUD
|
|
# define CBAUD 0
|
|
#endif
|
|
#ifndef CBAUDEX
|
|
# define CBAUDEX 0
|
|
#endif
|
|
|
|
/* Set a signal handler. */
|
|
#define SETSIG(sa, sig, fun, flags) \
|
|
do { \
|
|
memset(&sa, 0, sizeof(sa)); \
|
|
sa.sa_handler = fun; \
|
|
sa.sa_flags = flags; \
|
|
sigemptyset(&sa.sa_mask); \
|
|
sigaction(sig, &sa, NULL); \
|
|
} while(0)
|
|
|
|
/* Version information */
|
|
char *Version = "@(#) init " VERSION " miquels@cistron.nl";
|
|
char *bootmsg = "version " VERSION " %s";
|
|
#define E_VERSION "INIT_VERSION=sysvinit-" VERSION
|
|
|
|
CHILD *family = NULL; /* The linked list of all entries */
|
|
CHILD *newFamily = NULL; /* The list after inittab re-read */
|
|
|
|
CHILD ch_emerg = { /* Emergency shell */
|
|
WAITING, 0, 0, 0, 0,
|
|
"~~",
|
|
"S",
|
|
3,
|
|
"/sbin/sulogin",
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
char runlevel = 'S'; /* The current run level */
|
|
char thislevel = 'S'; /* The current runlevel */
|
|
char prevlevel = 'N'; /* Previous runlevel */
|
|
int dfl_level = 0; /* Default runlevel */
|
|
sig_atomic_t got_cont = 0; /* Set if we received the SIGCONT signal */
|
|
sig_atomic_t got_signals; /* Set if we received a signal. */
|
|
int emerg_shell = 0; /* Start emergency shell? */
|
|
int wrote_wtmp_reboot = 1; /* Set when we wrote the reboot record */
|
|
int wrote_utmp_reboot = 1; /* Set when we wrote the reboot record */
|
|
int wrote_wtmp_rlevel = 1; /* Set when we wrote the runlevel record */
|
|
int wrote_utmp_rlevel = 1; /* Set when we wrote the runlevel record */
|
|
int sleep_time = WAIT_BETWEEN_SIGNALS; /* Sleep time between TERM and KILL */
|
|
char *argv0; /* First arguments; show up in ps listing */
|
|
int maxproclen; /* Maximal length of argv[0] with \0 */
|
|
struct utmp utproto; /* Only used for sizeof(utproto.ut_id) */
|
|
char *console_dev; /* Console device. */
|
|
int pipe_fd = -1; /* /run/initctl */
|
|
int did_boot = 0; /* Did we already do BOOT* stuff? */
|
|
int main(int, char **);
|
|
|
|
/* Used by re-exec part */
|
|
int reload = 0; /* Should we do initialization stuff? */
|
|
char *myname=INIT; /* What should we exec */
|
|
int oops_error; /* Used by some of the re-exec code. */
|
|
const char *Signature = "12567362"; /* Signature for re-exec fd */
|
|
|
|
/* Macro to see if this is a special action */
|
|
#define ISPOWER(i) ((i) == POWERWAIT || (i) == POWERFAIL || \
|
|
(i) == POWEROKWAIT || (i) == POWERFAILNOW || \
|
|
(i) == CTRLALTDEL)
|
|
|
|
/* ascii values for the `action' field. */
|
|
struct actions {
|
|
char *name;
|
|
int act;
|
|
} actions[] = {
|
|
{ "respawn", RESPAWN },
|
|
{ "wait", WAIT },
|
|
{ "once", ONCE },
|
|
{ "boot", BOOT },
|
|
{ "bootwait", BOOTWAIT },
|
|
{ "powerfail", POWERFAIL },
|
|
{ "powerfailnow",POWERFAILNOW },
|
|
{ "powerwait", POWERWAIT },
|
|
{ "powerokwait", POWEROKWAIT },
|
|
{ "ctrlaltdel", CTRLALTDEL },
|
|
{ "off", OFF },
|
|
{ "ondemand", ONDEMAND },
|
|
{ "initdefault", INITDEFAULT },
|
|
{ "sysinit", SYSINIT },
|
|
{ "kbrequest", KBREQUEST },
|
|
{ NULL, 0 },
|
|
};
|
|
|
|
/*
|
|
* State parser token table (see receive_state)
|
|
*/
|
|
struct {
|
|
char name[4];
|
|
int cmd;
|
|
} cmds[] = {
|
|
{ "VER", C_VER },
|
|
{ "END", C_END },
|
|
{ "REC", C_REC },
|
|
{ "EOR", C_EOR },
|
|
{ "LEV", C_LEV },
|
|
{ "FL ", C_FLAG },
|
|
{ "AC ", C_ACTION },
|
|
{ "CMD", C_PROCESS },
|
|
{ "PID", C_PID },
|
|
{ "EXS", C_EXS },
|
|
{ "-RL", D_RUNLEVEL },
|
|
{ "-TL", D_THISLEVEL },
|
|
{ "-PL", D_PREVLEVEL },
|
|
{ "-SI", D_GOTSIGN },
|
|
{ "-WR", D_WROTE_WTMP_REBOOT},
|
|
{ "-WU", D_WROTE_UTMP_REBOOT},
|
|
{ "-ST", D_SLTIME },
|
|
{ "-DB", D_DIDBOOT },
|
|
{ "-LW", D_WROTE_WTMP_RLEVEL},
|
|
{ "-LU", D_WROTE_UTMP_RLEVEL},
|
|
{ "", 0 }
|
|
};
|
|
struct {
|
|
char *name;
|
|
int mask;
|
|
} flags[]={
|
|
{"RU",RUNNING},
|
|
{"DE",DEMAND},
|
|
{"XD",XECUTED},
|
|
{"WT",WAITING},
|
|
{NULL,0}
|
|
};
|
|
|
|
#define NR_EXTRA_ENV 16
|
|
char *extra_env[NR_EXTRA_ENV];
|
|
|
|
#define MINI_SLEEP 10 /* ten milliseconds */
|
|
#define SHORT_SLEEP 5000 /* five seconds */
|
|
#define LONG_SLEEP 30000 /* 30 seconds sleep to deal with memory issues*/
|
|
|
|
/*
|
|
* Sleep a number of milliseconds.
|
|
*
|
|
* This only works correctly because Linux select updates
|
|
* the elapsed time in the struct timeval passed to select!
|
|
*/
|
|
static
|
|
void do_msleep(int msec)
|
|
{
|
|
struct timeval tv;
|
|
|
|
tv.tv_sec = msec / 1000;
|
|
tv.tv_usec = (msec % 1000) * 1000;
|
|
|
|
while(select(0, NULL, NULL, NULL, &tv) < 0 && errno == EINTR)
|
|
;
|
|
}
|
|
|
|
|
|
/*
|
|
* Non-failing allocation routines (init cannot fail).
|
|
*/
|
|
static
|
|
void *imalloc(size_t size)
|
|
{
|
|
void *m;
|
|
|
|
while ((m = malloc(size)) == NULL) {
|
|
initlog(L_VB, "out of memory");
|
|
do_msleep(SHORT_SLEEP);
|
|
}
|
|
memset(m, 0, size);
|
|
return m;
|
|
}
|
|
|
|
static
|
|
char *istrdup(const char *s)
|
|
{
|
|
char *m;
|
|
int l;
|
|
|
|
l = strlen(s) + 1;
|
|
m = imalloc(l);
|
|
memcpy(m, s, l);
|
|
return m;
|
|
}
|
|
|
|
|
|
/*
|
|
* Send the state info of the previous running init to
|
|
* the new one, in a version-independant way.
|
|
*/
|
|
static
|
|
void send_state(int fd)
|
|
{
|
|
FILE *fp;
|
|
CHILD *p;
|
|
int i,val;
|
|
|
|
fp = fdopen(fd,"w");
|
|
|
|
fprintf(fp, "VER%s\n", Version);
|
|
fprintf(fp, "-RL%c\n", runlevel);
|
|
fprintf(fp, "-TL%c\n", thislevel);
|
|
fprintf(fp, "-PL%c\n", prevlevel);
|
|
fprintf(fp, "-SI%u\n", got_signals);
|
|
fprintf(fp, "-WR%d\n", wrote_wtmp_reboot);
|
|
fprintf(fp, "-WU%d\n", wrote_utmp_reboot);
|
|
fprintf(fp, "-ST%d\n", sleep_time);
|
|
fprintf(fp, "-DB%d\n", did_boot);
|
|
|
|
for (p = family; p; p = p->next) {
|
|
fprintf(fp, "REC%s\n", p->id);
|
|
fprintf(fp, "LEV%s\n", p->rlevel);
|
|
for (i = 0, val = p->flags; flags[i].mask; i++)
|
|
if (val & flags[i].mask) {
|
|
val &= ~flags[i].mask;
|
|
fprintf(fp, "FL %s\n",flags[i].name);
|
|
}
|
|
fprintf(fp, "PID%d\n",p->pid);
|
|
fprintf(fp, "EXS%u\n",p->exstat);
|
|
for(i = 0; actions[i].act; i++)
|
|
if (actions[i].act == p->action) {
|
|
fprintf(fp, "AC %s\n", actions[i].name);
|
|
break;
|
|
}
|
|
fprintf(fp, "CMD%s\n", p->process);
|
|
fprintf(fp, "EOR\n");
|
|
}
|
|
fprintf(fp, "END\n");
|
|
fclose(fp);
|
|
}
|
|
|
|
/*
|
|
* Read a string from a file descriptor.
|
|
* Q: why not use fgets() ?
|
|
* A: Answer: looked into this. Turns out after all the checks
|
|
* required in the fgets() approach (removing newline, read errors, etc)
|
|
* the function is longer and takes approximately the same amount of
|
|
* time to do one big fgets and checks as it does to do a pile of getcs.
|
|
* We don't benefit from switching.
|
|
* - Jesse
|
|
*/
|
|
static int get_string(char *p, int size, FILE *f)
|
|
{
|
|
int c;
|
|
|
|
while ((c = getc(f)) != EOF && c != '\n') {
|
|
if (--size > 0)
|
|
*p++ = c;
|
|
}
|
|
*p = '\0';
|
|
return (c != EOF) && (size > 0);
|
|
}
|
|
|
|
/*
|
|
* Read trailing data from the state pipe until we see a newline.
|
|
*/
|
|
static int get_void(FILE *f)
|
|
{
|
|
int c;
|
|
|
|
while ((c = getc(f)) != EOF && c != '\n')
|
|
;
|
|
|
|
return (c != EOF);
|
|
}
|
|
|
|
/*
|
|
* Read the next "command" from the state pipe.
|
|
*/
|
|
static int get_cmd(FILE *f)
|
|
{
|
|
char cmd[4] = " ";
|
|
int i;
|
|
|
|
if (fread(cmd, 1, sizeof(cmd) - 1, f) != sizeof(cmd) - 1)
|
|
return C_EOF;
|
|
|
|
for(i = 0; cmds[i].cmd && strcmp(cmds[i].name, cmd) != 0; i++)
|
|
;
|
|
return cmds[i].cmd;
|
|
}
|
|
|
|
/*
|
|
* Read a CHILD * from the state pipe.
|
|
*/
|
|
static CHILD *get_record(FILE *f)
|
|
{
|
|
int cmd;
|
|
char s[32];
|
|
int i;
|
|
CHILD *p;
|
|
|
|
do {
|
|
errno = 0;
|
|
switch (cmd = get_cmd(f)) {
|
|
case C_END:
|
|
get_void(f);
|
|
return NULL;
|
|
case 0:
|
|
get_void(f);
|
|
break;
|
|
case C_REC:
|
|
break;
|
|
case D_RUNLEVEL:
|
|
if (fscanf(f, "%c\n", &runlevel) == EOF && errno != 0) {
|
|
fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno));
|
|
}
|
|
break;
|
|
case D_THISLEVEL:
|
|
if (fscanf(f, "%c\n", &thislevel) == EOF && errno != 0) {
|
|
fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno));
|
|
}
|
|
break;
|
|
case D_PREVLEVEL:
|
|
if (fscanf(f, "%c\n", &prevlevel) == EOF && errno != 0) {
|
|
fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno));
|
|
}
|
|
break;
|
|
case D_GOTSIGN:
|
|
if (fscanf(f, "%u\n", &got_signals) == EOF && errno != 0) {
|
|
fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno));
|
|
}
|
|
break;
|
|
case D_WROTE_WTMP_REBOOT:
|
|
if (fscanf(f, "%d\n", &wrote_wtmp_reboot) == EOF && errno != 0) {
|
|
fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno));
|
|
}
|
|
break;
|
|
case D_WROTE_UTMP_REBOOT:
|
|
if (fscanf(f, "%d\n", &wrote_utmp_reboot) == EOF && errno != 0) {
|
|
fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno));
|
|
}
|
|
break;
|
|
case D_SLTIME:
|
|
if (fscanf(f, "%d\n", &sleep_time) == EOF && errno != 0) {
|
|
fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno));
|
|
}
|
|
break;
|
|
case D_DIDBOOT:
|
|
if (fscanf(f, "%d\n", &did_boot) == EOF && errno != 0) {
|
|
fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno));
|
|
}
|
|
break;
|
|
case D_WROTE_WTMP_RLEVEL:
|
|
if (fscanf(f, "%d\n", &wrote_wtmp_rlevel) == EOF && errno != 0) {
|
|
fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno));
|
|
}
|
|
break;
|
|
case D_WROTE_UTMP_RLEVEL:
|
|
if (fscanf(f, "%d\n", &wrote_utmp_rlevel) == EOF && errno != 0) {
|
|
fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno));
|
|
}
|
|
break;
|
|
default:
|
|
if (cmd > 0 || cmd == C_EOF) {
|
|
oops_error = -1;
|
|
return NULL;
|
|
}
|
|
}
|
|
} while (cmd != C_REC);
|
|
|
|
p = imalloc(sizeof(CHILD));
|
|
get_string(p->id, sizeof(p->id), f);
|
|
|
|
do switch(cmd = get_cmd(f)) {
|
|
case 0:
|
|
case C_EOR:
|
|
get_void(f);
|
|
break;
|
|
case C_PID:
|
|
if (fscanf(f, "%d\n", &(p->pid)) == EOF && errno != 0) {
|
|
fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno));
|
|
}
|
|
break;
|
|
case C_EXS:
|
|
if (fscanf(f, "%u\n", &(p->exstat)) == EOF && errno != 0) {
|
|
fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno));
|
|
}
|
|
break;
|
|
case C_LEV:
|
|
get_string(p->rlevel, sizeof(p->rlevel), f);
|
|
break;
|
|
case C_PROCESS:
|
|
get_string(p->process, sizeof(p->process), f);
|
|
break;
|
|
case C_FLAG:
|
|
get_string(s, sizeof(s), f);
|
|
for(i = 0; flags[i].name; i++) {
|
|
if (strcmp(flags[i].name,s) == 0)
|
|
break;
|
|
}
|
|
p->flags |= flags[i].mask;
|
|
break;
|
|
case C_ACTION:
|
|
get_string(s, sizeof(s), f);
|
|
for(i = 0; actions[i].name; i++) {
|
|
if (strcmp(actions[i].name, s) == 0)
|
|
break;
|
|
}
|
|
p->action = actions[i].act ? actions[i].act : OFF;
|
|
break;
|
|
default:
|
|
free(p);
|
|
oops_error = -1;
|
|
return NULL;
|
|
} while( cmd != C_EOR);
|
|
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Read the complete state info from the state pipe.
|
|
* Returns 0 on success
|
|
*/
|
|
static
|
|
int receive_state(int fd)
|
|
{
|
|
FILE *f;
|
|
char old_version[256];
|
|
CHILD **pp;
|
|
|
|
f = fdopen(fd, "r");
|
|
|
|
if (get_cmd(f) != C_VER) {
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
get_string(old_version, sizeof(old_version), f);
|
|
oops_error = 0;
|
|
for (pp = &family; (*pp = get_record(f)) != NULL; pp = &((*pp)->next))
|
|
;
|
|
fclose(f);
|
|
return oops_error;
|
|
}
|
|
|
|
/*
|
|
* Set the process title.
|
|
*/
|
|
#ifdef __GNUC__
|
|
#ifndef __FreeBSD__
|
|
__attribute__ ((format (printf, 1, 2)))
|
|
#endif
|
|
#endif
|
|
/* This function already exists on FreeBSD. No need to declare it. */
|
|
#ifndef __FreeBSD__
|
|
static int setproctitle(char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int len;
|
|
char buf[256];
|
|
|
|
buf[0] = 0;
|
|
|
|
va_start(ap, fmt);
|
|
len = vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (maxproclen > 1) {
|
|
memset(argv0, 0, maxproclen);
|
|
strncpy(argv0, buf, maxproclen - 1);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Set console_dev to a working console.
|
|
*/
|
|
static
|
|
void console_init(void)
|
|
{
|
|
int fd;
|
|
int tried_devcons = 0;
|
|
int tried_vtmaster = 0;
|
|
char *s;
|
|
|
|
if ((s = getenv("CONSOLE")) != NULL)
|
|
console_dev = s;
|
|
else {
|
|
console_dev = CONSOLE;
|
|
tried_devcons++;
|
|
}
|
|
|
|
while ((fd = open(console_dev, O_RDONLY|O_NONBLOCK)) < 0) {
|
|
if (!tried_devcons) {
|
|
tried_devcons++;
|
|
console_dev = CONSOLE;
|
|
continue;
|
|
}
|
|
if (!tried_vtmaster) {
|
|
tried_vtmaster++;
|
|
console_dev = VT_MASTER;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (fd < 0)
|
|
console_dev = "/dev/null";
|
|
else
|
|
close(fd);
|
|
}
|
|
|
|
|
|
/*
|
|
* Open the console with retries.
|
|
*/
|
|
static
|
|
int console_open(int mode)
|
|
{
|
|
int f, fd = -1;
|
|
int m;
|
|
|
|
/*
|
|
* Open device in nonblocking mode.
|
|
*/
|
|
m = mode | O_NONBLOCK;
|
|
|
|
/*
|
|
* Retry the open five times.
|
|
*/
|
|
for(f = 0; f < 5; f++) {
|
|
if ((fd = open(console_dev, m)) >= 0) break;
|
|
usleep(10000);
|
|
}
|
|
|
|
if (fd < 0) return fd;
|
|
|
|
/*
|
|
* Set original flags.
|
|
*/
|
|
if (m != mode)
|
|
fcntl(fd, F_SETFL, mode);
|
|
return fd;
|
|
}
|
|
|
|
/*
|
|
* We got a signal (HUP PWR WINCH ALRM INT)
|
|
*/
|
|
static
|
|
void signal_handler(int sig)
|
|
{
|
|
ADDSET(got_signals, sig);
|
|
}
|
|
|
|
/*
|
|
* SIGCHLD: one of our children has died.
|
|
*/
|
|
static
|
|
# ifdef __GNUC__
|
|
void chld_handler(int sig __attribute__((unused)))
|
|
# else
|
|
void chld_handler(int sig)
|
|
# endif
|
|
{
|
|
CHILD *ch;
|
|
int pid, st;
|
|
int saved_errno = errno;
|
|
|
|
/*
|
|
* Find out which process(es) this was (were)
|
|
*/
|
|
while((pid = waitpid(-1, &st, WNOHANG)) != 0) {
|
|
if (errno == ECHILD) break;
|
|
for( ch = family; ch; ch = ch->next )
|
|
if ( ch->pid == pid && (ch->flags & RUNNING) ) {
|
|
INITDBG(L_VB,
|
|
"chld_handler: marked %d as zombie",
|
|
ch->pid);
|
|
ADDSET(got_signals, SIGCHLD);
|
|
ch->exstat = st;
|
|
ch->flags |= ZOMBIE;
|
|
if (ch->new) {
|
|
ch->new->exstat = st;
|
|
ch->new->flags |= ZOMBIE;
|
|
}
|
|
break;
|
|
}
|
|
if (ch == NULL) {
|
|
INITDBG(L_VB, "chld_handler: unknown child %d exited.",
|
|
pid);
|
|
}
|
|
}
|
|
errno = saved_errno;
|
|
}
|
|
|
|
/*
|
|
* Linux ignores all signals sent to init when the
|
|
* SIG_DFL handler is installed. Therefore we must catch SIGTSTP
|
|
* and SIGCONT, or else they won't work....
|
|
*
|
|
* The SIGCONT handler
|
|
*/
|
|
static
|
|
# ifdef __GNUC__
|
|
void cont_handler(int sig __attribute__((unused)))
|
|
# else
|
|
void cont_handler(int sig)
|
|
# endif
|
|
{
|
|
got_cont = 1;
|
|
}
|
|
|
|
/*
|
|
* Fork and dump core in /.
|
|
*/
|
|
static
|
|
void coredump(void)
|
|
{
|
|
static int dumped = 0;
|
|
struct rlimit rlim;
|
|
sigset_t mask;
|
|
|
|
if (dumped) return;
|
|
dumped = 1;
|
|
|
|
if (fork() != 0) return;
|
|
|
|
sigfillset(&mask);
|
|
sigprocmask(SIG_SETMASK, &mask, NULL);
|
|
|
|
rlim.rlim_cur = RLIM_INFINITY;
|
|
rlim.rlim_max = RLIM_INFINITY;
|
|
setrlimit(RLIMIT_CORE, &rlim);
|
|
if (0 != chdir("/"))
|
|
initlog(L_VB, "unable to chdir to /: %s",
|
|
strerror(errno));
|
|
|
|
signal(SIGSEGV, SIG_DFL);
|
|
raise(SIGSEGV);
|
|
sigdelset(&mask, SIGSEGV);
|
|
sigprocmask(SIG_SETMASK, &mask, NULL);
|
|
|
|
do_msleep(SHORT_SLEEP);
|
|
exit(0);
|
|
}
|
|
|
|
/*
|
|
* OOPS: segmentation violation!
|
|
* If we have the info, print where it occurred.
|
|
* Then sleep 30 seconds and try to continue.
|
|
*/
|
|
static
|
|
#if defined(STACK_DEBUG) && defined(__linux__)
|
|
# ifdef __GNUC__
|
|
void segv_handler(int sig __attribute__((unused)), struct sigcontext ctx)
|
|
# else
|
|
void segv_handler(int sig, struct sigcontext ctx)
|
|
# endif
|
|
{
|
|
char *p = "";
|
|
int saved_errno = errno;
|
|
|
|
if ((void *)ctx.eip >= (void *)do_msleep &&
|
|
(void *)ctx.eip < (void *)main)
|
|
p = " (code)";
|
|
initlog(L_VB, "PANIC: segmentation violation at %p%s! "
|
|
"sleeping for 30 seconds.", (void *)ctx.eip, p);
|
|
coredump();
|
|
do_msleep(LONG_SLEEP);
|
|
errno = saved_errno;
|
|
}
|
|
#else
|
|
# ifdef __GNUC__
|
|
void segv_handler(int sig __attribute__((unused)))
|
|
# else
|
|
void segv_handler(int sig)
|
|
# endif
|
|
{
|
|
int saved_errno = errno;
|
|
|
|
initlog(L_VB,
|
|
"PANIC: segmentation violation! sleeping for 30 seconds.");
|
|
coredump();
|
|
do_msleep(LONG_SLEEP);
|
|
errno = saved_errno;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* The SIGSTOP & SIGTSTP handler
|
|
*/
|
|
static
|
|
# ifdef __GNUC__
|
|
void stop_handler(int sig __attribute__((unused)))
|
|
# else
|
|
void stop_handler(int sig)
|
|
# endif
|
|
{
|
|
int saved_errno = errno;
|
|
|
|
got_cont = 0;
|
|
while(!got_cont) pause();
|
|
got_cont = 0;
|
|
errno = saved_errno;
|
|
}
|
|
|
|
/*
|
|
* Set terminal settings to reasonable defaults
|
|
*/
|
|
static
|
|
void console_stty(void)
|
|
{
|
|
struct termios tty;
|
|
int fd;
|
|
|
|
if ((fd = console_open(O_RDWR|O_NOCTTY)) < 0) {
|
|
initlog(L_VB, "can't open %s", console_dev);
|
|
return;
|
|
}
|
|
|
|
#ifdef __FreeBSD_kernel__
|
|
/*
|
|
* The kernel of FreeBSD expects userland to set TERM. Usually, we want
|
|
* "xterm". Later, gettys might disagree on this (i.e. we're not using
|
|
* syscons) but some boot scripts, like /etc/init.d/xserver-xorg, still
|
|
* need a non-dumb terminal.
|
|
*/
|
|
putenv ("TERM=xterm");
|
|
#endif
|
|
|
|
(void) tcgetattr(fd, &tty);
|
|
|
|
tty.c_cflag &= CBAUD|CBAUDEX|CSIZE|CSTOPB|PARENB|PARODD;
|
|
tty.c_cflag |= HUPCL|CLOCAL|CREAD;
|
|
|
|
tty.c_cc[VINTR] = CINTR;
|
|
tty.c_cc[VQUIT] = CQUIT;
|
|
tty.c_cc[VERASE] = CERASE; /* ASCII DEL (0177) */
|
|
tty.c_cc[VKILL] = CKILL;
|
|
tty.c_cc[VEOF] = CEOF;
|
|
tty.c_cc[VTIME] = 0;
|
|
tty.c_cc[VMIN] = 1;
|
|
#ifdef VSWTC /* not defined on FreeBSD */
|
|
tty.c_cc[VSWTC] = _POSIX_VDISABLE;
|
|
#endif /* VSWTC */
|
|
tty.c_cc[VSTART] = CSTART;
|
|
tty.c_cc[VSTOP] = CSTOP;
|
|
tty.c_cc[VSUSP] = CSUSP;
|
|
tty.c_cc[VEOL] = _POSIX_VDISABLE;
|
|
tty.c_cc[VREPRINT] = CREPRINT;
|
|
tty.c_cc[VDISCARD] = CDISCARD;
|
|
tty.c_cc[VWERASE] = CWERASE;
|
|
tty.c_cc[VLNEXT] = CLNEXT;
|
|
tty.c_cc[VEOL2] = _POSIX_VDISABLE;
|
|
|
|
/*
|
|
* Set pre and post processing
|
|
*/
|
|
tty.c_iflag = IGNPAR|ICRNL|IXON|IXANY
|
|
#ifdef IUTF8 /* Not defined on FreeBSD */
|
|
| (tty.c_iflag & IUTF8)
|
|
#endif /* IUTF8 */
|
|
;
|
|
tty.c_oflag = OPOST|ONLCR;
|
|
tty.c_lflag = ISIG|ICANON|ECHO|ECHOCTL|ECHOE|ECHOKE;
|
|
|
|
#if defined(SANE_TIO) && (SANE_TIO == 1)
|
|
/*
|
|
* Disable flow control (-ixon), ignore break (ignbrk),
|
|
* and make nl/cr more usable (sane).
|
|
*/
|
|
tty.c_iflag |= IGNBRK;
|
|
tty.c_iflag &= ~(BRKINT|INLCR|IGNCR|IXON);
|
|
tty.c_oflag &= ~(OCRNL|ONLRET);
|
|
#endif
|
|
/*
|
|
* Now set the terminal line.
|
|
* We don't care about non-transmitted output data
|
|
* and non-read input data.
|
|
*/
|
|
(void) tcsetattr(fd, TCSANOW, &tty);
|
|
(void) tcflush(fd, TCIOFLUSH);
|
|
(void) close(fd);
|
|
}
|
|
|
|
static ssize_t
|
|
safe_write(int fd, const char *buffer, size_t count)
|
|
{
|
|
ssize_t offset = 0;
|
|
|
|
while (count > 0) {
|
|
ssize_t block = write(fd, &buffer[offset], count);
|
|
|
|
if (block < 0 && errno == EINTR)
|
|
continue;
|
|
if (block <= 0)
|
|
return offset ? offset : block;
|
|
offset += block;
|
|
count -= block;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
/*
|
|
* Print to the system console
|
|
*/
|
|
void print(char *s)
|
|
{
|
|
int fd;
|
|
|
|
if ((fd = console_open(O_WRONLY|O_NOCTTY|O_NDELAY)) >= 0) {
|
|
safe_write(fd, s, strlen(s));
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Log something to a logfile and the console.
|
|
*/
|
|
#ifdef __GNUC__
|
|
__attribute__ ((format (printf, 2, 3)))
|
|
#endif
|
|
void initlog(int loglevel, char *s, ...)
|
|
{
|
|
va_list va_alist;
|
|
char buf[256];
|
|
sigset_t nmask, omask;
|
|
|
|
va_start(va_alist, s);
|
|
vsnprintf(buf, sizeof(buf), s, va_alist);
|
|
va_end(va_alist);
|
|
|
|
if (loglevel & L_SY) {
|
|
/*
|
|
* Re-establish connection with syslogd every time.
|
|
* Block signals while talking to syslog.
|
|
*/
|
|
sigfillset(&nmask);
|
|
sigprocmask(SIG_BLOCK, &nmask, &omask);
|
|
openlog("init", 0, LOG_DAEMON);
|
|
syslog(LOG_INFO, "%s", buf);
|
|
closelog();
|
|
sigprocmask(SIG_SETMASK, &omask, NULL);
|
|
}
|
|
|
|
/*
|
|
* And log to the console.
|
|
*/
|
|
if (loglevel & L_CO) {
|
|
print("\rINIT: ");
|
|
print(buf);
|
|
print("\r\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Add or replace specific environment value
|
|
*/
|
|
int addnewenv(const char *new, char **curr, int n)
|
|
{
|
|
size_t nlen = strcspn(new, "=");
|
|
int i;
|
|
for (i = 0; i < n; i++) {
|
|
if (nlen != strcspn(curr[i], "="))
|
|
continue;
|
|
if (strncmp (new, curr[i], nlen) == 0)
|
|
break;
|
|
}
|
|
if (i >= n)
|
|
curr[n++] = istrdup(new);
|
|
else {
|
|
free(curr[i]);
|
|
curr[i] = istrdup(new);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* Build a new environment for execve().
|
|
*/
|
|
char **init_buildenv(int child)
|
|
{
|
|
char i_lvl[] = "RUNLEVEL=x";
|
|
char i_prev[] = "PREVLEVEL=x";
|
|
char i_cons[128];
|
|
char i_shell[] = "SHELL=" SHELL;
|
|
char **e;
|
|
int n, i;
|
|
|
|
for (n = 0; environ[n]; n++)
|
|
;
|
|
n += NR_EXTRA_ENV + 1; /* Also room for last NULL */
|
|
if (child)
|
|
n += 8;
|
|
|
|
while ((e = (char**)calloc(n, sizeof(char *))) == NULL) {
|
|
initlog(L_VB, "out of memory");
|
|
do_msleep(SHORT_SLEEP);
|
|
}
|
|
|
|
for (n = 0; environ[n]; n++)
|
|
e[n] = istrdup(environ[n]);
|
|
|
|
for (i = 0; i < NR_EXTRA_ENV; i++) {
|
|
if (extra_env[i] == NULL || *extra_env[i] == '\0')
|
|
continue;
|
|
n = addnewenv(extra_env[i], e, n);
|
|
}
|
|
|
|
if (child) {
|
|
snprintf(i_cons, sizeof(i_cons), "CONSOLE=%s", console_dev);
|
|
i_lvl[9] = thislevel;
|
|
i_prev[10] = prevlevel;
|
|
n = addnewenv(i_shell, e, n);
|
|
n = addnewenv(i_lvl, e, n);
|
|
n = addnewenv(i_prev, e, n);
|
|
n = addnewenv(i_cons, e, n);
|
|
n = addnewenv(E_VERSION, e, n);
|
|
}
|
|
|
|
e[n++] = NULL;
|
|
|
|
return e;
|
|
}
|
|
|
|
|
|
void init_freeenv(char **e)
|
|
{
|
|
int n;
|
|
|
|
for (n = 0; e[n]; n++)
|
|
free(e[n]);
|
|
free(e);
|
|
}
|
|
|
|
|
|
/*
|
|
* Fork and execute.
|
|
*
|
|
* This function is too long and indents too deep.
|
|
*
|
|
*/
|
|
static
|
|
pid_t spawn(CHILD *ch, int *res)
|
|
{
|
|
char *args[16]; /* Argv array */
|
|
char buf[136]; /* Line buffer */
|
|
int f, st; /* Scratch variables */
|
|
char *ptr; /* Ditto */
|
|
time_t t; /* System time */
|
|
int oldAlarm; /* Previous alarm value */
|
|
char *proc = ch->process; /* Command line */
|
|
pid_t pid, pgrp; /* child, console process group. */
|
|
sigset_t nmask, omask; /* For blocking SIGCHLD */
|
|
struct sigaction sa;
|
|
|
|
*res = -1;
|
|
buf[sizeof(buf) - 1] = 0;
|
|
|
|
/* Skip '+' if it's there */
|
|
if (proc[0] == '+') proc++;
|
|
|
|
ch->flags |= XECUTED;
|
|
|
|
if (ch->action == RESPAWN || ch->action == ONDEMAND) {
|
|
/* Is the date stamp from less than 2 minutes ago? */
|
|
time(&t);
|
|
if (ch->tm + TESTTIME > t) {
|
|
ch->count++;
|
|
} else {
|
|
ch->count = 0;
|
|
ch->tm = t;
|
|
}
|
|
|
|
/* Do we try to respawn too fast? */
|
|
if (ch->count >= MAXSPAWN) {
|
|
|
|
initlog(L_VB,
|
|
"Id \"%s\" respawning too fast: disabled for %d minutes",
|
|
ch->id, SLEEPTIME / 60);
|
|
ch->flags &= ~RUNNING;
|
|
ch->flags |= FAILING;
|
|
|
|
/* Remember the time we stopped */
|
|
ch->tm = t;
|
|
|
|
/* Try again in 5 minutes */
|
|
oldAlarm = alarm(0);
|
|
if (oldAlarm > SLEEPTIME || oldAlarm <= 0) oldAlarm = SLEEPTIME;
|
|
alarm(oldAlarm);
|
|
return(-1);
|
|
}
|
|
}
|
|
|
|
/* See if there is an "initscript" (except in single user mode). */
|
|
if (access(INITSCRIPT, R_OK) == 0 && runlevel != 'S') {
|
|
/* Build command line using "initscript" */
|
|
args[1] = SHELL;
|
|
args[2] = INITSCRIPT;
|
|
args[3] = ch->id;
|
|
args[4] = ch->rlevel;
|
|
args[5] = "unknown";
|
|
for(f = 0; actions[f].name; f++) {
|
|
if (ch->action == actions[f].act) {
|
|
args[5] = actions[f].name;
|
|
break;
|
|
}
|
|
}
|
|
if (proc[0] == '@') proc++; /*skip leading backslash */
|
|
args[6] = proc;
|
|
args[7] = NULL;
|
|
} else if ( (strpbrk(proc, "~`!$^&*()=|\\{}[];\"'<>?")) && (proc[0] != '@') ){
|
|
/* See if we need to fire off a shell for this command */
|
|
/* Do not launch shell if first character in proc string is an at symbol */
|
|
/* Give command line to shell */
|
|
args[1] = SHELL;
|
|
args[2] = "-c";
|
|
strcpy(buf, "exec ");
|
|
strncat(buf, proc, sizeof(buf) - strlen(buf) - 1);
|
|
args[3] = buf;
|
|
args[4] = NULL;
|
|
} else {
|
|
/* Split up command line arguments */
|
|
buf[0] = 0;
|
|
if (proc[0] == '@') proc++;
|
|
strncat(buf, proc, sizeof(buf) - 1);
|
|
ptr = buf;
|
|
for(f = 1; f < 15; f++) {
|
|
/* Skip white space */
|
|
while(*ptr == ' ' || *ptr == '\t') ptr++;
|
|
args[f] = ptr;
|
|
|
|
/* May be trailing space.. */
|
|
if (*ptr == 0) break;
|
|
|
|
/* Skip this `word' */
|
|
while(*ptr && *ptr != ' ' && *ptr != '\t' && *ptr != '#')
|
|
ptr++;
|
|
|
|
/* If end-of-line, break */
|
|
if (*ptr == '#' || *ptr == 0) {
|
|
f++;
|
|
*ptr = 0;
|
|
break;
|
|
}
|
|
/* End word with \0 and continue */
|
|
*ptr++ = 0;
|
|
}
|
|
args[f] = NULL;
|
|
}
|
|
args[0] = args[1];
|
|
while(1) {
|
|
/*
|
|
* Block sigchild while forking.
|
|
*/
|
|
sigemptyset(&nmask);
|
|
sigaddset(&nmask, SIGCHLD);
|
|
sigprocmask(SIG_BLOCK, &nmask, &omask);
|
|
|
|
if ((pid = fork()) == 0) {
|
|
|
|
close(0);
|
|
close(1);
|
|
close(2);
|
|
if (pipe_fd >= 0)
|
|
{
|
|
close(pipe_fd);
|
|
pipe_fd = -1;
|
|
}
|
|
|
|
sigprocmask(SIG_SETMASK, &omask, NULL);
|
|
|
|
/*
|
|
* In sysinit, boot, bootwait or single user mode:
|
|
* for any wait-type subprocess we _force_ the console
|
|
* to be its controlling tty.
|
|
*/
|
|
if (strchr("*#sS", runlevel) && ch->flags & WAITING) {
|
|
int ftty; /* Handler for tty controlling */
|
|
/*
|
|
* We fork once extra. This is so that we can
|
|
* wait and change the process group and session
|
|
* of the console after exit of the leader.
|
|
*/
|
|
setsid();
|
|
if ((ftty = console_open(O_RDWR|O_NOCTTY)) >= 0) {
|
|
/* Take over controlling tty by force */
|
|
(void)ioctl(ftty, TIOCSCTTY, 1);
|
|
|
|
if(dup(ftty) < 0){
|
|
initlog(L_VB, "cannot duplicate console fd");
|
|
}
|
|
|
|
if(dup(ftty) < 0){
|
|
initlog(L_VB, "cannot duplicate console fd");
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* 4 Sep 2001, Andrea Arcangeli:
|
|
* Fix a race in spawn() that is used to deadlock init in a
|
|
* waitpid() loop: must set the childhandler as default before forking
|
|
* off the child or the chld_handler could run before the waitpid loop
|
|
* has a chance to find its zombie-child.
|
|
*/
|
|
SETSIG(sa, SIGCHLD, SIG_DFL, SA_RESTART);
|
|
if ((pid = fork()) < 0) {
|
|
initlog(L_VB, "cannot fork: %s",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (pid > 0) {
|
|
pid_t rc;
|
|
/*
|
|
* Ignore keyboard signals etc.
|
|
* Then wait for child to exit.
|
|
*/
|
|
SETSIG(sa, SIGINT, SIG_IGN, SA_RESTART);
|
|
SETSIG(sa, SIGTSTP, SIG_IGN, SA_RESTART);
|
|
SETSIG(sa, SIGQUIT, SIG_IGN, SA_RESTART);
|
|
|
|
while ((rc = waitpid(pid, &st, 0)) != pid)
|
|
if (rc < 0 && errno == ECHILD)
|
|
break;
|
|
|
|
/*
|
|
* Small optimization. See if stealing
|
|
* controlling tty back is needed.
|
|
*/
|
|
pgrp = tcgetpgrp(ftty);
|
|
if (pgrp != getpid())
|
|
exit(0);
|
|
|
|
/*
|
|
* Steal controlling tty away. We do
|
|
* this with a temporary process.
|
|
*/
|
|
if ((pid = fork()) < 0) {
|
|
initlog(L_VB, "cannot fork: %s",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (pid == 0) {
|
|
setsid();
|
|
(void)ioctl(ftty, TIOCSCTTY, 1);
|
|
exit(0);
|
|
}
|
|
while((rc = waitpid(pid, &st, 0)) != pid)
|
|
if (rc < 0 && errno == ECHILD)
|
|
break;
|
|
exit(0);
|
|
}
|
|
|
|
/* Set ioctl settings to default ones */
|
|
console_stty();
|
|
|
|
} else { /* parent */
|
|
int fd;
|
|
setsid();
|
|
if ((fd = console_open(O_RDWR|O_NOCTTY)) < 0) {
|
|
initlog(L_VB, "open(%s): %s", console_dev,
|
|
strerror(errno));
|
|
fd = open("/dev/null", O_RDWR);
|
|
}
|
|
|
|
if(dup(fd) < 0) {
|
|
initlog(L_VB, "cannot duplicate /dev/null fd");
|
|
}
|
|
|
|
if(dup(fd) < 0) {
|
|
initlog(L_VB, "cannot duplicate /dev/null fd");
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Update utmp/wtmp file prior to starting
|
|
* any child. This MUST be done right here in
|
|
* the child process in order to prevent a race
|
|
* condition that occurs when the child
|
|
* process' time slice executes before the
|
|
* parent (can and does happen in a uniprocessor
|
|
* environment). If the child is a getty and
|
|
* the race condition happens, then init's utmp
|
|
* update will happen AFTER the getty runs
|
|
* and expects utmp to be updated already!
|
|
*
|
|
* Do NOT log if process field starts with '+'
|
|
* This is for compatibility with *very*
|
|
* old getties - probably it can be taken out.
|
|
*/
|
|
if (ch->process[0] != '+')
|
|
write_utmp_wtmp("", ch->id, getpid(), INIT_PROCESS, "");
|
|
|
|
/* Reset all the signals, set up environment */
|
|
for(f = 1; f < NSIG; f++) SETSIG(sa, f, SIG_DFL, SA_RESTART);
|
|
environ = init_buildenv(1);
|
|
|
|
/*
|
|
* Execute prog. In case of ENOEXEC try again
|
|
* as a shell script.
|
|
*/
|
|
execvp(args[1], args + 1);
|
|
if (errno == ENOEXEC) {
|
|
args[1] = SHELL;
|
|
args[2] = "-c";
|
|
strcpy(buf, "exec ");
|
|
strncat(buf, proc, sizeof(buf) - strlen(buf) - 1);
|
|
args[3] = buf;
|
|
args[4] = NULL;
|
|
execvp(args[1], args + 1);
|
|
}
|
|
initlog(L_VB, "cannot execute \"%s\"", args[1]);
|
|
|
|
if (ch->process[0] != '+')
|
|
write_utmp_wtmp("", ch->id, getpid(), DEAD_PROCESS, NULL);
|
|
exit(1);
|
|
}
|
|
*res = pid;
|
|
sigprocmask(SIG_SETMASK, &omask, NULL);
|
|
|
|
INITDBG(L_VB, "Started id %s (pid %d)", ch->id, pid);
|
|
|
|
if (pid == -1) {
|
|
initlog(L_VB, "cannot fork, retry..");
|
|
do_msleep(SHORT_SLEEP);
|
|
continue;
|
|
}
|
|
return(pid);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Start a child running!
|
|
*/
|
|
static
|
|
void startup(CHILD *ch)
|
|
{
|
|
/*
|
|
* See if it's disabled
|
|
*/
|
|
if (ch->flags & FAILING) return;
|
|
|
|
switch(ch->action) {
|
|
|
|
case SYSINIT:
|
|
case BOOTWAIT:
|
|
case WAIT:
|
|
case POWERWAIT:
|
|
case POWERFAILNOW:
|
|
case POWEROKWAIT:
|
|
case CTRLALTDEL:
|
|
if (!(ch->flags & XECUTED)) ch->flags |= WAITING;
|
|
/* Fall through */
|
|
case KBREQUEST:
|
|
case BOOT:
|
|
case POWERFAIL:
|
|
case ONCE:
|
|
if (ch->flags & XECUTED) break;
|
|
/* Fall through */
|
|
case ONDEMAND:
|
|
case RESPAWN:
|
|
ch->flags |= RUNNING;
|
|
(void)spawn(ch, &(ch->pid));
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef __linux__
|
|
static
|
|
void check_kernel_console()
|
|
{
|
|
FILE* fp;
|
|
char buf[4096];
|
|
if ((fp = fopen("/proc/cmdline", "r")) == 0) {
|
|
return;
|
|
}
|
|
if (fgets(buf, sizeof(buf), fp)) {
|
|
char* p = buf;
|
|
if ( strstr(p, "init.autocon=1") )
|
|
{
|
|
while ((p = strstr(p, "console="))) {
|
|
char* e;
|
|
p += strlen("console=");
|
|
for (e = p; *e; ++e) {
|
|
switch (*e) {
|
|
case '-' ... '9':
|
|
case 'A' ... 'Z':
|
|
case '_':
|
|
case 'a' ... 'z':
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (p != e) {
|
|
CHILD* old;
|
|
int dup = 0;
|
|
char id[8] = {0};
|
|
char dev[32] = {0};
|
|
strncpy(dev, p, MIN(sizeof(dev), (unsigned)(e-p)));
|
|
if (!strncmp(dev, "tty", 3))
|
|
strncpy(id, dev+3, sizeof(id));
|
|
else
|
|
strncpy(id, dev, sizeof(id));
|
|
|
|
for(old = newFamily; old; old = old->next) {
|
|
if (!strcmp(old->id, id)) {
|
|
dup = 1;
|
|
}
|
|
}
|
|
if (!dup) {
|
|
CHILD* ch = imalloc(sizeof(CHILD));
|
|
ch->action = RESPAWN;
|
|
strcpy(ch->id, id);
|
|
strcpy(ch->rlevel, "2345");
|
|
sprintf(ch->process, "/sbin/agetty -L -s 115200,38400,9600 %s vt102", dev);
|
|
ch->next = NULL;
|
|
for(old = family; old; old = old->next) {
|
|
if (strcmp(old->id, ch->id) == 0) {
|
|
old->new = ch;
|
|
break;
|
|
}
|
|
}
|
|
/* add to end */
|
|
for(old = newFamily; old; old = old->next) {
|
|
if (!old->next) {
|
|
old->next = ch;
|
|
break;
|
|
}
|
|
}
|
|
|
|
initlog(L_VB, "added agetty on %s with id %s", dev, id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Read the inittab file.
|
|
*/
|
|
static
|
|
void read_inittab(void)
|
|
{
|
|
FILE *fp; /* The INITTAB file */
|
|
FILE *fp_tab; /* The INITTABD files */
|
|
CHILD *ch, *old, *i; /* Pointers to CHILD structure */
|
|
CHILD *head = NULL; /* Head of linked list */
|
|
#ifdef INITLVL
|
|
struct stat st; /* To stat INITLVL */
|
|
#endif
|
|
sigset_t nmask, omask; /* For blocking SIGCHLD. */
|
|
char buf[256]; /* Line buffer */
|
|
char err[64]; /* Error message. */
|
|
char *id, *rlevel,
|
|
*action, *process; /* Fields of a line */
|
|
char *p;
|
|
int lineNo = 0; /* Line number in INITTAB file */
|
|
int actionNo; /* Decoded action field */
|
|
int f; /* Counter */
|
|
int round; /* round 0 for SIGTERM, 1 for SIGKILL */
|
|
int foundOne = 0; /* No killing no sleep */
|
|
int talk; /* Talk to the user */
|
|
int done = -1; /* Ready yet? , 2 level : -1 nothing done, 0 inittab done, 1 inittab and inittab.d done */
|
|
DIR *tabdir=NULL; /* the INITTAB.D dir */
|
|
struct dirent *file_entry; /* inittab.d entry */
|
|
char f_name[272]; /* size d_name + strlen /etc/inittad.d/ */
|
|
|
|
#if DEBUG
|
|
if (newFamily != NULL) {
|
|
INITDBG(L_VB, "PANIC newFamily != NULL");
|
|
exit(1);
|
|
}
|
|
INITDBG(L_VB, "Reading inittab");
|
|
#endif
|
|
|
|
/*
|
|
* Open INITTAB and read line by line.
|
|
*/
|
|
if ((fp = fopen(INITTAB, "r")) == NULL)
|
|
initlog(L_VB, "No inittab file found");
|
|
|
|
/*
|
|
* Open INITTAB.D directory
|
|
*/
|
|
if( (tabdir = opendir(INITTABD))==NULL)
|
|
initlog(L_VB, "No inittab.d directory found");
|
|
|
|
while(done!=1) {
|
|
/*
|
|
* Add single user shell entry at the end.
|
|
*/
|
|
if(done == -1) {
|
|
if (fp == NULL || fgets(buf, sizeof(buf), fp) == NULL) {
|
|
done = 0;
|
|
/*
|
|
* See if we have a single user entry.
|
|
*/
|
|
for(old = newFamily; old; old = old->next)
|
|
if (strpbrk(old->rlevel, "S")) break;
|
|
if (old == NULL)
|
|
snprintf(buf, sizeof(buf), "~~:S:wait:%s\n", SULOGIN);
|
|
else
|
|
continue;
|
|
}
|
|
} /* end if( done==-1) */
|
|
else if ( done == 0 ){
|
|
/* parse /etc/inittab.d and read all .tab files */
|
|
if(tabdir!=NULL){
|
|
if( (file_entry = readdir(tabdir))!=NULL){
|
|
/* ignore files not like *.tab */
|
|
if (!strcmp(file_entry->d_name, ".") || !strcmp(file_entry->d_name, ".."))
|
|
continue;
|
|
if (strlen(file_entry->d_name) < 5 || strcmp(file_entry->d_name + strlen(file_entry->d_name) - 4, ".tab"))
|
|
continue;
|
|
/*
|
|
* initialize filename
|
|
*/
|
|
memset(f_name,0,sizeof(char)*272);
|
|
snprintf(f_name,272,"/etc/inittab.d/%s",file_entry->d_name);
|
|
initlog(L_VB, "Reading: %s",f_name);
|
|
/*
|
|
* read file in inittab.d only one entry per file
|
|
*/
|
|
if ((fp_tab = fopen(f_name, "r")) == NULL)
|
|
continue;
|
|
/* read the file while the line contain comment */
|
|
while( fgets(buf, sizeof(buf), fp_tab) != NULL) {
|
|
for(p = buf; *p == ' ' || *p == '\t'; p++);
|
|
if (*p != '#' && *p != '\n')
|
|
break;
|
|
}
|
|
fclose(fp_tab);
|
|
/* do some checks */
|
|
if( strlen( p ) == 0 )
|
|
continue;
|
|
} /* end of readdir, all is done */
|
|
else {
|
|
done = 1;
|
|
continue;
|
|
}
|
|
} /* end of if(tabdir!=NULL) */
|
|
else {
|
|
done = 1;
|
|
continue;
|
|
}
|
|
} /* end of if ( done == 0 ) */
|
|
lineNo++;
|
|
/*
|
|
* Skip comments and empty lines
|
|
*/
|
|
for(p = buf; *p == ' ' || *p == '\t'; p++)
|
|
;
|
|
if (*p == '#' || *p == '\n') continue;
|
|
|
|
/*
|
|
* Decode the fields
|
|
*/
|
|
id = strsep(&p, ":");
|
|
rlevel = strsep(&p, ":");
|
|
action = strsep(&p, ":");
|
|
process = strsep(&p, "\n");
|
|
|
|
/*
|
|
* Check if syntax is OK. Be very verbose here, to
|
|
* avoid newbie postings on comp.os.linux.setup :)
|
|
*/
|
|
err[0] = 0;
|
|
if (!id || !*id) strcpy(err, "missing id field");
|
|
if (!rlevel) strcpy(err, "missing runlevel field");
|
|
if (!process) strcpy(err, "missing process field");
|
|
if (!action || !*action)
|
|
strcpy(err, "missing action field");
|
|
if (id && strlen(id) > sizeof(utproto.ut_id))
|
|
sprintf(err, "id field too long (max %d characters)",
|
|
(int)sizeof(utproto.ut_id));
|
|
if (rlevel && strlen(rlevel) > 11)
|
|
strcpy(err, "rlevel field too long (max 11 characters)");
|
|
if (process && strlen(process) > 127)
|
|
strcpy(err, "process field too long (max 127 characters)");
|
|
if (action && strlen(action) > 32)
|
|
strcpy(err, "action field too long");
|
|
if (err[0] != 0) {
|
|
initlog(L_VB, "%s[%d]: %s", INITTAB, lineNo, err);
|
|
INITDBG(L_VB, "%s:%s:%s:%s", id, rlevel, action, process);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Decode the "action" field
|
|
*/
|
|
actionNo = -1;
|
|
for(f = 0; actions[f].name; f++)
|
|
if (strcasecmp(action, actions[f].name) == 0) {
|
|
actionNo = actions[f].act;
|
|
break;
|
|
}
|
|
if (actionNo == -1) {
|
|
initlog(L_VB, "%s[%d]: %s: unknown action field",
|
|
INITTAB, lineNo, action);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* See if the id field is unique
|
|
*/
|
|
for(old = newFamily; old; old = old->next) {
|
|
if(strcmp(old->id, id) == 0 && strcmp(id, "~~")) {
|
|
initlog(L_VB, "%s[%d]: duplicate ID field \"%s\"",
|
|
INITTAB, lineNo, id);
|
|
break;
|
|
}
|
|
}
|
|
if (old) continue;
|
|
|
|
/*
|
|
* Allocate a CHILD structure
|
|
*/
|
|
ch = imalloc(sizeof(CHILD));
|
|
|
|
/*
|
|
* And fill it in.
|
|
*/
|
|
ch->action = actionNo;
|
|
strncpy(ch->id, id, sizeof(utproto.ut_id) + 1); /* Hack for different libs. */
|
|
strncpy(ch->process, process, sizeof(ch->process) - 1);
|
|
if (rlevel[0]) {
|
|
for(f = 0; f < (int)sizeof(rlevel) - 1 && rlevel[f]; f++) {
|
|
ch->rlevel[f] = rlevel[f];
|
|
if (ch->rlevel[f] == 's') ch->rlevel[f] = 'S';
|
|
}
|
|
strncpy(ch->rlevel, rlevel, sizeof(ch->rlevel) - 1);
|
|
} else {
|
|
strcpy(ch->rlevel, "0123456789");
|
|
if (ISPOWER(ch->action))
|
|
strcpy(ch->rlevel, "S0123456789");
|
|
}
|
|
/*
|
|
* We have the fake runlevel '#' for SYSINIT and
|
|
* '*' for BOOT and BOOTWAIT.
|
|
*/
|
|
if (ch->action == SYSINIT) strcpy(ch->rlevel, "#");
|
|
if (ch->action == BOOT || ch->action == BOOTWAIT)
|
|
strcpy(ch->rlevel, "*");
|
|
|
|
/*
|
|
* Now add it to the linked list. Special for powerfail.
|
|
*/
|
|
if (ISPOWER(ch->action)) {
|
|
|
|
/*
|
|
* Disable by default
|
|
*/
|
|
ch->flags |= XECUTED;
|
|
|
|
/*
|
|
* Tricky: insert at the front of the list..
|
|
*/
|
|
old = NULL;
|
|
for(i = newFamily; i; i = i->next) {
|
|
if (!ISPOWER(i->action)) break;
|
|
old = i;
|
|
}
|
|
/*
|
|
* Now add after entry "old"
|
|
*/
|
|
if (old) {
|
|
ch->next = i;
|
|
old->next = ch;
|
|
if (i == NULL) head = ch;
|
|
} else {
|
|
ch->next = newFamily;
|
|
newFamily = ch;
|
|
if (ch->next == NULL) head = ch;
|
|
}
|
|
} else {
|
|
/*
|
|
* Just add at end of the list
|
|
*/
|
|
if (ch->action == KBREQUEST) ch->flags |= XECUTED;
|
|
ch->next = NULL;
|
|
if (head)
|
|
head->next = ch;
|
|
else
|
|
newFamily = ch;
|
|
head = ch;
|
|
}
|
|
|
|
/*
|
|
* Walk through the old list comparing id fields
|
|
*/
|
|
for(old = family; old; old = old->next)
|
|
if (strcmp(old->id, ch->id) == 0) {
|
|
old->new = ch;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We're done.
|
|
*/
|
|
if (fp) fclose(fp);
|
|
if(tabdir) closedir(tabdir);
|
|
|
|
#ifdef __linux__
|
|
check_kernel_console();
|
|
#endif
|
|
|
|
/*
|
|
* Loop through the list of children, and see if they need to
|
|
* be killed.
|
|
*/
|
|
|
|
INITDBG(L_VB, "Checking for children to kill");
|
|
for(round = 0; round < 2; round++) {
|
|
talk = 1;
|
|
for(ch = family; ch; ch = ch->next) {
|
|
ch->flags &= ~KILLME;
|
|
|
|
/*
|
|
* Is this line deleted?
|
|
*/
|
|
if (ch->new == NULL) ch->flags |= KILLME;
|
|
|
|
/*
|
|
* If the entry has changed, kill it anyway. Note that
|
|
* we do not check ch->process, only the "action" field.
|
|
* This way, you can turn an entry "off" immediately, but
|
|
* changes in the command line will only become effective
|
|
* after the running version has exited.
|
|
*/
|
|
if (ch->new && ch->action != ch->new->action) ch->flags |= KILLME;
|
|
|
|
/*
|
|
* Only BOOT processes may live in all levels
|
|
*/
|
|
if (ch->action != BOOT &&
|
|
strchr(ch->rlevel, runlevel) == NULL) {
|
|
/*
|
|
* Ondemand procedures live always,
|
|
* except in single user
|
|
*/
|
|
if (runlevel == 'S' || !(ch->flags & DEMAND))
|
|
ch->flags |= KILLME;
|
|
}
|
|
|
|
/*
|
|
* Now, if this process may live note so in the new list
|
|
*/
|
|
if ((ch->flags & KILLME) == 0) {
|
|
ch->new->flags = ch->flags;
|
|
ch->new->pid = ch->pid;
|
|
ch->new->exstat = ch->exstat;
|
|
continue;
|
|
}
|
|
|
|
|
|
/*
|
|
* Is this process still around?
|
|
*/
|
|
if ((ch->flags & RUNNING) == 0) {
|
|
ch->flags &= ~KILLME;
|
|
continue;
|
|
}
|
|
INITDBG(L_VB, "Killing \"%s\"", ch->process);
|
|
switch(round) {
|
|
case 0: /* Send TERM signal */
|
|
if (talk)
|
|
initlog(L_CO,
|
|
"Sending processes configured via /etc/inittab the TERM signal");
|
|
kill(-(ch->pid), SIGTERM);
|
|
foundOne = 1;
|
|
break;
|
|
case 1: /* Send KILL signal and collect status */
|
|
if (talk)
|
|
initlog(L_CO,
|
|
"Sending processes configured via /etc/inittab the KILL signal");
|
|
kill(-(ch->pid), SIGKILL);
|
|
break;
|
|
}
|
|
talk = 0;
|
|
|
|
}
|
|
/*
|
|
* See if we have to wait sleep_time seconds
|
|
*/
|
|
if (foundOne && round == 0) {
|
|
/*
|
|
* Yup, but check every 10 milliseconds if we still have children.
|
|
* The f < 100 * sleep_time refers to sleep time in 10 millisecond chunks.
|
|
*/
|
|
for(f = 0; f < 100 * sleep_time; f++) {
|
|
for(ch = family; ch; ch = ch->next) {
|
|
if (!(ch->flags & KILLME)) continue;
|
|
if ((ch->flags & RUNNING) && !(ch->flags & ZOMBIE))
|
|
break;
|
|
}
|
|
if (ch == NULL) {
|
|
/*
|
|
* No running children, skip SIGKILL
|
|
*/
|
|
round = 1;
|
|
foundOne = 0; /* Skip the sleep below. */
|
|
break;
|
|
}
|
|
do_msleep(MINI_SLEEP);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now give all processes the chance to die and collect exit statuses.
|
|
*/
|
|
if (foundOne) do_msleep(MINI_SLEEP);
|
|
for(ch = family; ch; ch = ch->next)
|
|
if (ch->flags & KILLME) {
|
|
if (!(ch->flags & ZOMBIE))
|
|
initlog(L_CO, "Pid %d [id %s] seems to hang", ch->pid,
|
|
ch->id);
|
|
else {
|
|
INITDBG(L_VB, "Updating utmp for pid %d [id %s]",
|
|
ch->pid, ch->id);
|
|
ch->flags &= ~RUNNING;
|
|
if (ch->process[0] != '+')
|
|
write_utmp_wtmp("", ch->id, ch->pid, DEAD_PROCESS, NULL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Both rounds done; clean up the list.
|
|
*/
|
|
sigemptyset(&nmask);
|
|
sigaddset(&nmask, SIGCHLD);
|
|
sigprocmask(SIG_BLOCK, &nmask, &omask);
|
|
for(ch = family; ch; ch = old) {
|
|
old = ch->next;
|
|
free(ch);
|
|
}
|
|
family = newFamily;
|
|
for(ch = family; ch; ch = ch->next) ch->new = NULL;
|
|
newFamily = NULL;
|
|
sigprocmask(SIG_SETMASK, &omask, NULL);
|
|
|
|
#ifdef INITLVL
|
|
/*
|
|
* Dispose of INITLVL file.
|
|
*/
|
|
if (lstat(INITLVL, &st) >= 0 && S_ISLNK(st.st_mode)) {
|
|
/*
|
|
* INITLVL is a symbolic link, so just truncate the file.
|
|
*/
|
|
close(open(INITLVL, O_WRONLY|O_TRUNC));
|
|
} else {
|
|
/*
|
|
* Delete INITLVL file.
|
|
*/
|
|
unlink(INITLVL);
|
|
}
|
|
#endif
|
|
#ifdef INITLVL2
|
|
/*
|
|
* Dispose of INITLVL2 file.
|
|
*/
|
|
if (lstat(INITLVL2, &st) >= 0 && S_ISLNK(st.st_mode)) {
|
|
/*
|
|
* INITLVL2 is a symbolic link, so just truncate the file.
|
|
*/
|
|
close(open(INITLVL2, O_WRONLY|O_TRUNC));
|
|
} else {
|
|
/*
|
|
* Delete INITLVL2 file.
|
|
*/
|
|
unlink(INITLVL2);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Walk through the family list and start up children.
|
|
* The entries that do not belong here at all are removed
|
|
* from the list.
|
|
*/
|
|
static
|
|
void start_if_needed(void)
|
|
{
|
|
CHILD *ch; /* Pointer to child */
|
|
int delete; /* Delete this entry from list? */
|
|
|
|
INITDBG(L_VB, "Checking for children to start");
|
|
|
|
for(ch = family; ch; ch = ch->next) {
|
|
|
|
#if DEBUG
|
|
if (ch->rlevel[0] == 'C') {
|
|
INITDBG(L_VB, "%s: flags %d", ch->process, ch->flags);
|
|
}
|
|
#endif
|
|
|
|
/* Are we waiting for this process? Then quit here. */
|
|
if (ch->flags & WAITING) break;
|
|
|
|
/* Already running? OK, don't touch it */
|
|
if (ch->flags & RUNNING) continue;
|
|
|
|
/* See if we have to start it up */
|
|
delete = 1;
|
|
if (strchr(ch->rlevel, runlevel) ||
|
|
((ch->flags & DEMAND) && !strchr("#*Ss", runlevel))) {
|
|
startup(ch);
|
|
delete = 0;
|
|
}
|
|
|
|
if (delete) {
|
|
/* is this OK? */
|
|
ch->flags &= ~(RUNNING|WAITING);
|
|
if (!ISPOWER(ch->action) && ch->action != KBREQUEST)
|
|
ch->flags &= ~XECUTED;
|
|
ch->pid = 0;
|
|
} else
|
|
/* Do we have to wait for this process? */
|
|
if (ch->flags & WAITING) break;
|
|
}
|
|
/* Done. */
|
|
}
|
|
|
|
/*
|
|
* Ask the user on the console for a runlevel
|
|
*/
|
|
static
|
|
int ask_runlevel(void)
|
|
{
|
|
const char prompt[] = "\nEnter runlevel: ";
|
|
char buf[8];
|
|
int lvl = -1;
|
|
int fd;
|
|
|
|
console_stty();
|
|
fd = console_open(O_RDWR|O_NOCTTY);
|
|
|
|
if (fd < 0) return('S');
|
|
|
|
while(!strchr("0123456789S", lvl)) {
|
|
safe_write(fd, prompt, sizeof(prompt) - 1);
|
|
if (read(fd, buf, sizeof(buf)) <= 0)
|
|
buf[0] = 0;
|
|
if (buf[0] != 0 && (buf[1] == '\r' || buf[1] == '\n'))
|
|
lvl = buf[0];
|
|
if (islower(lvl)) lvl = toupper(lvl);
|
|
}
|
|
close(fd);
|
|
return lvl;
|
|
}
|
|
|
|
/*
|
|
* Search the INITTAB file for the 'initdefault' field, with the default
|
|
* runlevel. If this fails, ask the user to supply a runlevel.
|
|
*/
|
|
static
|
|
int get_init_default(void)
|
|
{
|
|
CHILD *ch;
|
|
int lvl = -1;
|
|
char *p;
|
|
|
|
/*
|
|
* Look for initdefault.
|
|
*/
|
|
for(ch = family; ch; ch = ch->next)
|
|
if (ch->action == INITDEFAULT) {
|
|
p = ch->rlevel;
|
|
while(*p) {
|
|
if (*p > lvl) lvl = *p;
|
|
p++;
|
|
}
|
|
break;
|
|
}
|
|
/*
|
|
* See if level is valid
|
|
*/
|
|
if (lvl > 0) {
|
|
if (islower(lvl)) lvl = toupper(lvl);
|
|
if (strchr("0123456789S", lvl) == NULL) {
|
|
initlog(L_VB,
|
|
"Initdefault level '%c' is invalid", lvl);
|
|
lvl = 0;
|
|
}
|
|
}
|
|
/*
|
|
* Ask for runlevel on console if needed.
|
|
*/
|
|
if (lvl <= 0) lvl = ask_runlevel();
|
|
|
|
/*
|
|
* Log the fact that we have a runlevel now.
|
|
*/
|
|
Write_Runlevel_Log(lvl);
|
|
return lvl;
|
|
}
|
|
|
|
|
|
/*
|
|
* We got signaled.
|
|
*
|
|
* Do actions for the new level. If we are compatible with
|
|
* the "old" INITLVL and arg == 0, try to read the new
|
|
* runlevel from that file first.
|
|
*/
|
|
static
|
|
int read_level(int arg)
|
|
{
|
|
CHILD *ch; /* Walk through list */
|
|
unsigned char foo = 'X'; /* Contents of INITLVL */
|
|
int ok = 1;
|
|
#ifdef INITLVL
|
|
FILE *fp;
|
|
struct stat stt;
|
|
int st;
|
|
#endif
|
|
|
|
if (arg) foo = arg;
|
|
|
|
#ifdef INITLVL
|
|
ok = 0;
|
|
|
|
if (arg == 0) {
|
|
fp = NULL;
|
|
if (stat(INITLVL, &stt) != 0 || stt.st_size != 0L)
|
|
fp = fopen(INITLVL, "r");
|
|
#ifdef INITLVL2
|
|
if (fp == NULL &&
|
|
(stat(INITLVL2, &stt) != 0 || stt.st_size != 0L))
|
|
fp = fopen(INITLVL2, "r");
|
|
#endif
|
|
if (fp == NULL) {
|
|
/* INITLVL file empty or not there - act as 'init q' */
|
|
initlog(L_SY, "Re-reading inittab");
|
|
return(runlevel);
|
|
}
|
|
ok = fscanf(fp, "%c %d", &foo, &st);
|
|
fclose(fp);
|
|
} else {
|
|
/* We go to the new runlevel passed as an argument. */
|
|
foo = arg;
|
|
ok = 1;
|
|
}
|
|
if (ok == 2) sleep_time = st;
|
|
|
|
#endif /* INITLVL */
|
|
|
|
if (islower(foo)) foo = toupper(foo);
|
|
if (ok < 1 || ok > 2 || strchr("QS0123456789ABCU", foo) == NULL) {
|
|
initlog(L_VB, "bad runlevel: %c", foo);
|
|
return runlevel;
|
|
}
|
|
|
|
/* Log this action */
|
|
switch(foo) {
|
|
case 'S':
|
|
initlog(L_VB, "Going single user");
|
|
break;
|
|
case 'Q':
|
|
initlog(L_SY, "Re-reading inittab");
|
|
break;
|
|
case 'A':
|
|
case 'B':
|
|
case 'C':
|
|
initlog(L_SY,
|
|
"Activating demand-procedures for '%c'", foo);
|
|
break;
|
|
case 'U':
|
|
initlog(L_SY, "Trying to re-exec init");
|
|
return 'U';
|
|
default:
|
|
initlog(L_VB, "Switching to runlevel: %c", foo);
|
|
}
|
|
|
|
if (foo == 'Q') {
|
|
#if defined(SIGINT_ONLYONCE) && (SIGINT_ONLYONCE == 1)
|
|
/* Re-enable signal from keyboard */
|
|
struct sigaction sa;
|
|
SETSIG(sa, SIGINT, signal_handler, 0);
|
|
#endif
|
|
return runlevel;
|
|
}
|
|
|
|
/* Check if this is a runlevel a, b or c */
|
|
if (strchr("ABC", foo)) {
|
|
if (runlevel == 'S') return(runlevel);
|
|
|
|
/* Read inittab again first! */
|
|
read_inittab();
|
|
|
|
/* Mark those special tasks */
|
|
for(ch = family; ch; ch = ch->next)
|
|
if (strchr(ch->rlevel, foo) != NULL ||
|
|
strchr(ch->rlevel, tolower(foo)) != NULL) {
|
|
ch->flags |= DEMAND;
|
|
ch->flags &= ~XECUTED;
|
|
INITDBG(L_VB,
|
|
"Marking (%s) as ondemand, flags %d",
|
|
ch->id, ch->flags);
|
|
}
|
|
return runlevel;
|
|
}
|
|
|
|
/* Store both the old and the new runlevel. */
|
|
wrote_utmp_rlevel = 0;
|
|
wrote_wtmp_rlevel = 0;
|
|
write_utmp_wtmp("runlevel", "~~", foo + 256*runlevel, RUN_LVL, "~");
|
|
thislevel = foo;
|
|
prevlevel = runlevel;
|
|
Write_Runlevel_Log(runlevel);
|
|
return foo;
|
|
}
|
|
|
|
|
|
/*
|
|
* This procedure is called after every signal (SIGHUP, SIGALRM..)
|
|
*
|
|
* Only clear the 'failing' flag if the process is sleeping
|
|
* longer than 5 minutes, or inittab was read again due
|
|
* to user interaction.
|
|
*/
|
|
static
|
|
void fail_check(void)
|
|
{
|
|
CHILD *ch; /* Pointer to child structure */
|
|
time_t t; /* System time */
|
|
time_t next_alarm = 0; /* When to set next alarm */
|
|
|
|
time(&t);
|
|
|
|
for(ch = family; ch; ch = ch->next) {
|
|
|
|
if (ch->flags & FAILING) {
|
|
/* Can we free this sucker? */
|
|
if (ch->tm + SLEEPTIME < t) {
|
|
ch->flags &= ~FAILING;
|
|
ch->count = 0;
|
|
ch->tm = 0;
|
|
} else {
|
|
/* No, we'll look again later */
|
|
if (next_alarm == 0 ||
|
|
ch->tm + SLEEPTIME > next_alarm)
|
|
next_alarm = ch->tm + SLEEPTIME;
|
|
}
|
|
}
|
|
}
|
|
if (next_alarm) {
|
|
next_alarm -= t;
|
|
if (next_alarm < 1) next_alarm = 1;
|
|
alarm(next_alarm);
|
|
}
|
|
}
|
|
|
|
/* Set all 'Fail' timers to 0 */
|
|
static
|
|
void fail_cancel(void)
|
|
{
|
|
CHILD *ch;
|
|
|
|
for(ch = family; ch; ch = ch->next) {
|
|
ch->count = 0;
|
|
ch->tm = 0;
|
|
ch->flags &= ~FAILING;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Start up powerfail entries.
|
|
*/
|
|
static
|
|
void do_power_fail(int pwrstat)
|
|
{
|
|
CHILD *ch;
|
|
|
|
/*
|
|
* Tell powerwait & powerfail entries to start up
|
|
*/
|
|
for (ch = family; ch; ch = ch->next) {
|
|
if (pwrstat == 'O') {
|
|
/*
|
|
* The power is OK again.
|
|
*/
|
|
if (ch->action == POWEROKWAIT)
|
|
ch->flags &= ~XECUTED;
|
|
} else if (pwrstat == 'L') {
|
|
/*
|
|
* Low battery, shut down now.
|
|
*/
|
|
if (ch->action == POWERFAILNOW)
|
|
ch->flags &= ~XECUTED;
|
|
} else {
|
|
/*
|
|
* Power is failing, shutdown imminent
|
|
*/
|
|
if (ch->action == POWERFAIL || ch->action == POWERWAIT)
|
|
ch->flags &= ~XECUTED;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check for state-pipe presence
|
|
*/
|
|
static
|
|
int check_pipe(int fd)
|
|
{
|
|
struct timeval t;
|
|
fd_set s;
|
|
char signature[8];
|
|
|
|
FD_ZERO(&s);
|
|
FD_SET(fd, &s);
|
|
t.tv_sec = t.tv_usec = 0;
|
|
|
|
if (select(fd+1, &s, NULL, NULL, &t) != 1)
|
|
return 0;
|
|
if (read(fd, signature, 8) != 8)
|
|
return 0;
|
|
return strncmp(Signature, signature, 8) == 0;
|
|
}
|
|
|
|
/*
|
|
* Make a state-pipe.
|
|
*/
|
|
static
|
|
int make_pipe(int fd)
|
|
{
|
|
int fds[2];
|
|
|
|
if (pipe(fds)) {
|
|
initlog(L_VB, "pipe: %m");
|
|
return -1;
|
|
}
|
|
dup2(fds[0], fd);
|
|
close(fds[0]);
|
|
fcntl(fds[1], F_SETFD, 1);
|
|
fcntl(fd, F_SETFD, 0);
|
|
safe_write(fds[1], Signature, 8);
|
|
|
|
return fds[1];
|
|
}
|
|
|
|
/*
|
|
* Attempt to re-exec.
|
|
* Renaming to my_re_exec since re_exec is now a common function name
|
|
* which conflicts.
|
|
*/
|
|
static
|
|
void my_re_exec(void)
|
|
{
|
|
CHILD *ch;
|
|
sigset_t mask, oldset;
|
|
pid_t pid;
|
|
char **env;
|
|
int fd;
|
|
|
|
if (strchr("S0123456",runlevel) == NULL)
|
|
return;
|
|
|
|
/*
|
|
* Reset the alarm, and block all signals.
|
|
*/
|
|
alarm(0);
|
|
sigfillset(&mask);
|
|
sigprocmask(SIG_BLOCK, &mask, &oldset);
|
|
|
|
/*
|
|
* construct a pipe fd --> STATE_PIPE and write a signature
|
|
*/
|
|
if ((fd = make_pipe(STATE_PIPE)) < 0) {
|
|
sigprocmask(SIG_SETMASK, &oldset, NULL);
|
|
initlog(L_CO, "Attempt to re-exec failed");
|
|
}
|
|
|
|
fail_cancel();
|
|
if (pipe_fd >= 0)
|
|
close(pipe_fd);
|
|
pipe_fd = -1;
|
|
DELSET(got_signals, SIGCHLD);
|
|
DELSET(got_signals, SIGHUP);
|
|
DELSET(got_signals, SIGUSR1);
|
|
DELSET(got_signals, SIGUSR2);
|
|
|
|
/*
|
|
* That should be cleaned.
|
|
*/
|
|
for(ch = family; ch; ch = ch->next)
|
|
if (ch->flags & ZOMBIE) {
|
|
INITDBG(L_VB, "Child died, PID= %d", ch->pid);
|
|
ch->flags &= ~(RUNNING|ZOMBIE|WAITING);
|
|
if (ch->process[0] != '+')
|
|
write_utmp_wtmp("", ch->id, ch->pid, DEAD_PROCESS, NULL);
|
|
}
|
|
|
|
if ((pid = fork()) == 0) {
|
|
/*
|
|
* Child sends state information to the parent.
|
|
*/
|
|
send_state(fd);
|
|
exit(0);
|
|
}
|
|
|
|
/*
|
|
* The existing init process execs a new init binary.
|
|
*/
|
|
env = init_buildenv(0);
|
|
execle(myname, myname, "--init", NULL, env);
|
|
|
|
/*
|
|
* We shouldn't be here, something failed.
|
|
* Close the state pipe, unblock signals and return.
|
|
*/
|
|
init_freeenv(env);
|
|
close(fd);
|
|
close(STATE_PIPE);
|
|
sigprocmask(SIG_SETMASK, &oldset, NULL);
|
|
initlog(L_CO, "Attempt to re-exec failed");
|
|
}
|
|
|
|
/*
|
|
* Redo utmp/wtmp entries if required or requested
|
|
* Check for written records and size of utmp
|
|
*/
|
|
static
|
|
void redo_utmp_wtmp(void)
|
|
{
|
|
struct stat ustat;
|
|
const int ret = stat(UTMP_FILE, &ustat);
|
|
|
|
if ((ret < 0) || (ustat.st_size == 0))
|
|
wrote_utmp_rlevel = wrote_utmp_reboot = 0;
|
|
|
|
if ((wrote_wtmp_reboot == 0) || (wrote_utmp_reboot == 0))
|
|
write_utmp_wtmp("reboot", "~~", 0, BOOT_TIME, "~");
|
|
|
|
if ((wrote_wtmp_rlevel == 0) || (wrote_utmp_rlevel == 0))
|
|
write_utmp_wtmp("runlevel", "~~", thislevel + 256 * prevlevel, RUN_LVL, "~");
|
|
}
|
|
|
|
/*
|
|
* We got a change runlevel request through the
|
|
* init.fifo. Process it.
|
|
*/
|
|
static
|
|
void fifo_new_level(int level)
|
|
{
|
|
#if CHANGE_WAIT
|
|
CHILD *ch;
|
|
#endif
|
|
int oldlevel;
|
|
|
|
if (level == runlevel) return;
|
|
|
|
#if CHANGE_WAIT
|
|
/* Are we waiting for a child? */
|
|
for(ch = family; ch; ch = ch->next)
|
|
if (ch->flags & WAITING) break;
|
|
if (ch == NULL)
|
|
#endif
|
|
{
|
|
/* We need to go into a new runlevel */
|
|
oldlevel = runlevel;
|
|
runlevel = read_level(level);
|
|
if (runlevel == 'U') {
|
|
runlevel = oldlevel;
|
|
my_re_exec();
|
|
} else {
|
|
if (oldlevel != 'S' && runlevel == 'S') console_stty();
|
|
if (runlevel == '6' || runlevel == '0' ||
|
|
runlevel == '1') console_stty();
|
|
if (runlevel > '1' && runlevel < '6') redo_utmp_wtmp();
|
|
read_inittab();
|
|
fail_cancel();
|
|
setproctitle("init [%c]", (int)runlevel);
|
|
}
|
|
}
|
|
Write_Runlevel_Log(runlevel);
|
|
}
|
|
|
|
|
|
/*
|
|
* Set/unset environment variables. The variables are
|
|
* encoded as KEY=VAL\0KEY=VAL\0\0. With "=VAL" it means
|
|
* setenv, without it means unsetenv.
|
|
*/
|
|
static
|
|
void initcmd_setenv(char *data, int size)
|
|
{
|
|
char *env, *p, *e;
|
|
size_t sz;
|
|
int i, eq;
|
|
|
|
e = data + size;
|
|
|
|
while (*data && data < e) {
|
|
for (p = data; *p && p < e; p++)
|
|
;
|
|
if (*p) break;
|
|
env = data;
|
|
data = ++p;
|
|
|
|
/*
|
|
* We only allow INIT_* to be set.
|
|
*/
|
|
if (strncmp(env, "INIT_", 5) != 0)
|
|
continue;
|
|
|
|
sz = strcspn(env, "=");
|
|
eq = (env[sz] == '=');
|
|
|
|
/*initlog(L_SY, "init_setenv: %s, %d, %d", env, eq, sz);*/
|
|
|
|
/* Free existing vars. */
|
|
for (i = 0; i < NR_EXTRA_ENV; i++) {
|
|
if (extra_env[i] == NULL)
|
|
continue;
|
|
if (sz != strcspn(extra_env[i], "="))
|
|
continue;
|
|
if (strncmp(extra_env[i], env, sz) == 0) {
|
|
free(extra_env[i]);
|
|
extra_env[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if (eq == 0)
|
|
continue;
|
|
|
|
/* Set new vars if needed. */
|
|
for (i = 0; i < NR_EXTRA_ENV; i++) {
|
|
if (extra_env[i] == NULL) {
|
|
extra_env[i] = istrdup(env);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Read from the init FIFO. Processes like telnetd and rlogind can
|
|
* ask us to create login processes on their behalf.
|
|
*/
|
|
static
|
|
void check_init_fifo(void)
|
|
{
|
|
struct init_request request;
|
|
struct timeval tv;
|
|
struct stat st, st2;
|
|
fd_set fds;
|
|
int n;
|
|
int quit = 0;
|
|
|
|
/*
|
|
* First, try to create /run/initctl if not present.
|
|
*/
|
|
if (stat(INIT_FIFO, &st2) < 0 && errno == ENOENT)
|
|
(void)mkfifo(INIT_FIFO, 0600);
|
|
|
|
/*
|
|
* If /run/initctl is open, stat the file to see if it
|
|
* is still the _same_ inode.
|
|
*/
|
|
if (pipe_fd >= 0) {
|
|
fstat(pipe_fd, &st);
|
|
if (stat(INIT_FIFO, &st2) < 0 ||
|
|
st.st_dev != st2.st_dev ||
|
|
st.st_ino != st2.st_ino) {
|
|
close(pipe_fd);
|
|
pipe_fd = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now finally try to open /run/initctl if pipe_fd is -1
|
|
* if it is -2, then we leave it closed
|
|
*/
|
|
if (pipe_fd == -1) {
|
|
if ((pipe_fd = open(INIT_FIFO, O_RDWR|O_NONBLOCK)) >= 0) {
|
|
fstat(pipe_fd, &st);
|
|
if (!S_ISFIFO(st.st_mode)) {
|
|
initlog(L_VB, "%s is not a fifo", INIT_FIFO);
|
|
close(pipe_fd);
|
|
pipe_fd = -1;
|
|
}
|
|
}
|
|
if (pipe_fd >= 0) {
|
|
/*
|
|
* Don't use fd's 0, 1 or 2.
|
|
*/
|
|
(void) dup2(pipe_fd, PIPE_FD);
|
|
close(pipe_fd);
|
|
pipe_fd = PIPE_FD;
|
|
|
|
/*
|
|
* Return to caller - we'll be back later.
|
|
*/
|
|
}
|
|
}
|
|
|
|
/* Wait for data to appear, _if_ the pipe was opened. */
|
|
if (pipe_fd >= 0) {
|
|
while(!quit) {
|
|
|
|
/* Do select, return on EINTR. */
|
|
FD_ZERO(&fds);
|
|
FD_SET(pipe_fd, &fds);
|
|
tv.tv_sec = 5;
|
|
tv.tv_usec = 0;
|
|
n = select(pipe_fd + 1, &fds, NULL, NULL, &tv);
|
|
if (n <= 0) {
|
|
if (n == 0 || errno == EINTR) return;
|
|
continue;
|
|
}
|
|
|
|
/* Read the data, return on EINTR. */
|
|
n = read(pipe_fd, &request, sizeof(request));
|
|
if (n == 0) {
|
|
/*
|
|
* End of file. This can't happen under Linux (because
|
|
* the pipe is opened O_RDWR - see select() in the
|
|
* kernel) but you never know...
|
|
*/
|
|
close(pipe_fd);
|
|
pipe_fd = -1;
|
|
return;
|
|
}
|
|
if (n <= 0) {
|
|
if (errno == EINTR) return;
|
|
initlog(L_VB, "error reading initrequest");
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* This is a convenient point to also try to
|
|
* find the console device or check if it changed.
|
|
*/
|
|
console_init();
|
|
|
|
/*
|
|
* Process request.
|
|
*/
|
|
if (request.magic != INIT_MAGIC || n != sizeof(request)) {
|
|
initlog(L_VB, "got bogus initrequest");
|
|
continue;
|
|
}
|
|
switch(request.cmd) {
|
|
case INIT_CMD_RUNLVL:
|
|
sleep_time = request.sleeptime;
|
|
fifo_new_level(request.runlevel);
|
|
quit = 1;
|
|
break;
|
|
case INIT_CMD_POWERFAIL:
|
|
sleep_time = request.sleeptime;
|
|
do_power_fail('F');
|
|
quit = 1;
|
|
break;
|
|
case INIT_CMD_POWERFAILNOW:
|
|
sleep_time = request.sleeptime;
|
|
do_power_fail('L');
|
|
quit = 1;
|
|
break;
|
|
case INIT_CMD_POWEROK:
|
|
sleep_time = request.sleeptime;
|
|
do_power_fail('O');
|
|
quit = 1;
|
|
break;
|
|
case INIT_CMD_SETENV:
|
|
initcmd_setenv(request.i.data, sizeof(request.i.data));
|
|
break;
|
|
default:
|
|
initlog(L_VB, "got unimplemented initrequest.");
|
|
break;
|
|
} /* end of switch */
|
|
} /* end of while loop not quitting */
|
|
} /* end of if the pipe is open */
|
|
/*
|
|
* We come here if the pipe couldn't be opened.
|
|
*/
|
|
if (pipe_fd == -1) pause();
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* This function is used in the transition
|
|
* sysinit (-> single user) boot -> multi-user.
|
|
*/
|
|
static
|
|
void boot_transitions()
|
|
{
|
|
CHILD *ch;
|
|
static int newlevel = 0;
|
|
static int warn = 1;
|
|
int loglevel;
|
|
int oldlevel;
|
|
|
|
/* Check if there is something to wait for! */
|
|
for( ch = family; ch; ch = ch->next )
|
|
if ((ch->flags & RUNNING) && ch->action != BOOT) break;
|
|
|
|
if (ch == NULL) {
|
|
/* No processes left in this level, proceed to next level. */
|
|
loglevel = -1;
|
|
oldlevel = 'N';
|
|
switch(runlevel) {
|
|
case '#': /* SYSINIT -> BOOT */
|
|
INITDBG(L_VB, "SYSINIT -> BOOT");
|
|
|
|
/* Write a boot record. */
|
|
wrote_utmp_reboot = 0;
|
|
wrote_wtmp_reboot = 0;
|
|
write_utmp_wtmp("reboot", "~~", 0, BOOT_TIME, "~");
|
|
|
|
/* Get our run level */
|
|
newlevel = dfl_level ? dfl_level : get_init_default();
|
|
if (newlevel == 'S') {
|
|
runlevel = newlevel;
|
|
/* Not really 'S' but show anyway. */
|
|
setproctitle("init [S]");
|
|
} else
|
|
runlevel = '*';
|
|
break;
|
|
case '*': /* BOOT -> NORMAL */
|
|
INITDBG(L_VB, "BOOT -> NORMAL");
|
|
if (runlevel != newlevel)
|
|
loglevel = newlevel;
|
|
runlevel = newlevel;
|
|
did_boot = 1;
|
|
warn = 1;
|
|
break;
|
|
case 'S': /* Ended SU mode */
|
|
case 's':
|
|
INITDBG(L_VB, "END SU MODE");
|
|
newlevel = get_init_default();
|
|
if (!did_boot && newlevel != 'S')
|
|
runlevel = '*';
|
|
else {
|
|
if (runlevel != newlevel)
|
|
loglevel = newlevel;
|
|
runlevel = newlevel;
|
|
oldlevel = 'S';
|
|
}
|
|
warn = 1;
|
|
for(ch = family; ch; ch = ch->next)
|
|
if (strcmp(ch->rlevel, "S") == 0)
|
|
ch->flags &= ~(FAILING|WAITING|XECUTED);
|
|
break;
|
|
default:
|
|
if (warn)
|
|
initlog(L_VB,
|
|
"no more processes left in this runlevel");
|
|
warn = 0;
|
|
loglevel = -1;
|
|
if (got_signals == 0)
|
|
check_init_fifo();
|
|
break;
|
|
}
|
|
if (loglevel > 0) {
|
|
initlog(L_VB, "Entering runlevel: %c", runlevel);
|
|
wrote_utmp_rlevel = 0;
|
|
wrote_wtmp_rlevel = 0;
|
|
write_utmp_wtmp("runlevel", "~~", runlevel + 256 * oldlevel, RUN_LVL, "~");
|
|
thislevel = runlevel;
|
|
prevlevel = oldlevel;
|
|
setproctitle("init [%c]", (int)runlevel);
|
|
}
|
|
Write_Runlevel_Log(runlevel);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Init got hit by a signal. See which signal it is,
|
|
* and act accordingly.
|
|
*/
|
|
static
|
|
void process_signals()
|
|
{
|
|
CHILD *ch;
|
|
int pwrstat;
|
|
int oldlevel;
|
|
int fd;
|
|
char c;
|
|
|
|
if (ISMEMBER(got_signals, SIGPWR)) {
|
|
INITDBG(L_VB, "got SIGPWR");
|
|
/* See _what_ kind of SIGPWR this is. */
|
|
pwrstat = 0;
|
|
if ((fd = open(PWRSTAT, O_RDONLY)) >= 0) {
|
|
if (read(fd, &c, 1) != 1)
|
|
c = 0;
|
|
pwrstat = c;
|
|
close(fd);
|
|
unlink(PWRSTAT);
|
|
} else if ((fd = open(PWRSTAT_OLD, O_RDONLY)) >= 0) {
|
|
/* Path changed 2010-03-20. Look for the old path for a while. */
|
|
initlog(L_VB, "warning: found obsolete path %s, use %s instead",
|
|
PWRSTAT_OLD, PWRSTAT);
|
|
if (read(fd, &c, 1) != 1)
|
|
c = 0;
|
|
pwrstat = c;
|
|
close(fd);
|
|
unlink(PWRSTAT_OLD);
|
|
}
|
|
do_power_fail(pwrstat);
|
|
DELSET(got_signals, SIGPWR);
|
|
}
|
|
|
|
if (ISMEMBER(got_signals, SIGINT)) {
|
|
#if defined(SIGINT_ONLYONCE) && (SIGINT_ONLYONCE == 1)
|
|
/* Ignore any further signal from keyboard */
|
|
struct sigaction sa;
|
|
SETSIG(sa, SIGINT, SIG_IGN, SA_RESTART);
|
|
#endif
|
|
INITDBG(L_VB, "got SIGINT");
|
|
/* Tell ctrlaltdel entry to start up */
|
|
for(ch = family; ch; ch = ch->next)
|
|
if (ch->action == CTRLALTDEL)
|
|
ch->flags &= ~XECUTED;
|
|
DELSET(got_signals, SIGINT);
|
|
}
|
|
|
|
if (ISMEMBER(got_signals, SIGWINCH)) {
|
|
INITDBG(L_VB, "got SIGWINCH");
|
|
/* Tell kbrequest entry to start up */
|
|
for(ch = family; ch; ch = ch->next)
|
|
if (ch->action == KBREQUEST)
|
|
ch->flags &= ~XECUTED;
|
|
DELSET(got_signals, SIGWINCH);
|
|
}
|
|
|
|
if (ISMEMBER(got_signals, SIGALRM)) {
|
|
INITDBG(L_VB, "got SIGALRM");
|
|
/* The timer went off: check it out */
|
|
DELSET(got_signals, SIGALRM);
|
|
}
|
|
|
|
if (ISMEMBER(got_signals, SIGCHLD)) {
|
|
INITDBG(L_VB, "got SIGCHLD");
|
|
/* First set flag to 0 */
|
|
DELSET(got_signals, SIGCHLD);
|
|
|
|
/* See which child this was */
|
|
for(ch = family; ch; ch = ch->next)
|
|
if (ch->flags & ZOMBIE) {
|
|
INITDBG(L_VB, "Child died, PID= %d", ch->pid);
|
|
ch->flags &= ~(RUNNING|ZOMBIE|WAITING);
|
|
if (ch->process[0] != '+')
|
|
write_utmp_wtmp("", ch->id, ch->pid, DEAD_PROCESS, NULL);
|
|
}
|
|
|
|
}
|
|
|
|
if (ISMEMBER(got_signals, SIGHUP)) {
|
|
INITDBG(L_VB, "got SIGHUP");
|
|
#if CHANGE_WAIT
|
|
/* Are we waiting for a child? */
|
|
for(ch = family; ch; ch = ch->next)
|
|
if (ch->flags & WAITING) break;
|
|
if (ch == NULL)
|
|
#endif
|
|
{
|
|
/* We need to go into a new runlevel */
|
|
oldlevel = runlevel;
|
|
#ifdef INITLVL
|
|
runlevel = read_level(0);
|
|
#endif
|
|
if (runlevel == 'U') {
|
|
runlevel = oldlevel;
|
|
my_re_exec();
|
|
} else {
|
|
if (oldlevel != 'S' && runlevel == 'S') console_stty();
|
|
if (runlevel == '6' || runlevel == '0' ||
|
|
runlevel == '1') console_stty();
|
|
read_inittab();
|
|
fail_cancel();
|
|
setproctitle("init [%c]", (int)runlevel);
|
|
DELSET(got_signals, SIGHUP);
|
|
}
|
|
Write_Runlevel_Log(runlevel);
|
|
}
|
|
}
|
|
if (ISMEMBER(got_signals, SIGUSR1)) {
|
|
/*
|
|
* SIGUSR1 means close and reopen /run/initctl
|
|
*/
|
|
INITDBG(L_VB, "got SIGUSR1");
|
|
if (pipe_fd)
|
|
close(pipe_fd);
|
|
pipe_fd = -1;
|
|
DELSET(got_signals, SIGUSR1);
|
|
}
|
|
else if (ISMEMBER(got_signals, SIGUSR2)) {
|
|
/* SIGUSR1 mean close the pipe and leave it closed */
|
|
INITDBG(L_VB, "got SIGUSR2");
|
|
if (pipe_fd)
|
|
close(pipe_fd);
|
|
pipe_fd = -2;
|
|
DELSET(got_signals, SIGUSR2);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The main loop
|
|
*/
|
|
static
|
|
void init_main(void)
|
|
{
|
|
CHILD *ch;
|
|
struct sigaction sa;
|
|
sigset_t sgt;
|
|
int f, st;
|
|
|
|
if (!reload) {
|
|
|
|
#if INITDEBUG
|
|
/*
|
|
* Fork so we can debug the init process.
|
|
*/
|
|
if ((f = fork()) > 0) {
|
|
static const char killmsg[] = "PRNT: init killed.\r\n";
|
|
pid_t rc;
|
|
|
|
while((rc = wait(&st)) != f)
|
|
if (rc < 0 && errno == ECHILD)
|
|
break;
|
|
safe_write(1, killmsg, sizeof(killmsg) - 1);
|
|
while(1) pause();
|
|
}
|
|
#endif
|
|
|
|
#ifdef __linux__
|
|
/*
|
|
* Tell the kernel to send us SIGINT when CTRL-ALT-DEL
|
|
* is pressed, and that we want to handle keyboard signals.
|
|
*/
|
|
init_reboot(BMAGIC_SOFT);
|
|
if ((f = open(VT_MASTER, O_RDWR | O_NOCTTY)) >= 0) {
|
|
(void) ioctl(f, KDSIGACCEPT, SIGWINCH);
|
|
close(f);
|
|
} else
|
|
(void) ioctl(0, KDSIGACCEPT, SIGWINCH);
|
|
#endif
|
|
|
|
/*
|
|
* Ignore all signals.
|
|
*/
|
|
for(f = 1; f <= NSIG; f++)
|
|
SETSIG(sa, f, SIG_IGN, SA_RESTART);
|
|
}
|
|
|
|
SETSIG(sa, SIGALRM, signal_handler, 0);
|
|
SETSIG(sa, SIGHUP, signal_handler, 0);
|
|
SETSIG(sa, SIGINT, signal_handler, 0);
|
|
SETSIG(sa, SIGCHLD, chld_handler, SA_RESTART);
|
|
SETSIG(sa, SIGPWR, signal_handler, 0);
|
|
SETSIG(sa, SIGWINCH, signal_handler, 0);
|
|
SETSIG(sa, SIGUSR1, signal_handler, 0);
|
|
SETSIG(sa, SIGUSR2, signal_handler, 0);
|
|
SETSIG(sa, SIGSTOP, stop_handler, SA_RESTART);
|
|
SETSIG(sa, SIGTSTP, stop_handler, SA_RESTART);
|
|
SETSIG(sa, SIGCONT, cont_handler, SA_RESTART);
|
|
SETSIG(sa, SIGSEGV, (void (*)(int))segv_handler, SA_RESTART);
|
|
|
|
console_init();
|
|
|
|
if (!reload) {
|
|
int fd;
|
|
|
|
/* Close whatever files are open, and reset the console. */
|
|
close(0);
|
|
close(1);
|
|
close(2);
|
|
console_stty();
|
|
setsid();
|
|
|
|
/*
|
|
* Set default PATH variable.
|
|
*/
|
|
setenv("PATH", PATH_DEFAULT, 1 /* Overwrite */);
|
|
|
|
/*
|
|
* Initialize /var/run/utmp (only works if /var is on
|
|
* root and mounted rw)
|
|
*/
|
|
if ((fd = open(UTMP_FILE, O_WRONLY|O_CREAT|O_TRUNC, 0644)) >= 0)
|
|
close(fd);
|
|
|
|
/*
|
|
* Say hello to the world
|
|
*/
|
|
initlog(L_CO, bootmsg, "booting");
|
|
|
|
/*
|
|
* See if we have to start an emergency shell.
|
|
*/
|
|
if (emerg_shell) {
|
|
pid_t rc;
|
|
SETSIG(sa, SIGCHLD, SIG_DFL, SA_RESTART);
|
|
if (spawn(&ch_emerg, &f) > 0) {
|
|
while((rc = wait(&st)) != f)
|
|
if (rc < 0 && errno == ECHILD)
|
|
break;
|
|
}
|
|
SETSIG(sa, SIGCHLD, chld_handler, SA_RESTART);
|
|
}
|
|
|
|
/*
|
|
* Start normal boot procedure.
|
|
*/
|
|
runlevel = '#';
|
|
read_inittab();
|
|
|
|
} else {
|
|
/*
|
|
* Restart: unblock signals and let the show go on
|
|
*/
|
|
initlog(L_CO, bootmsg, "reloading");
|
|
sigfillset(&sgt);
|
|
sigprocmask(SIG_UNBLOCK, &sgt, NULL);
|
|
|
|
/*
|
|
* Set default PATH variable.
|
|
*/
|
|
setenv("PATH", PATH_DEFAULT, 0 /* Don't overwrite */);
|
|
}
|
|
start_if_needed();
|
|
|
|
while(1) {
|
|
|
|
/* See if we need to make the boot transitions. */
|
|
boot_transitions();
|
|
INITDBG(L_VB, "init_main: waiting..");
|
|
|
|
/* Check if there are processes to be waited on. */
|
|
for(ch = family; ch; ch = ch->next)
|
|
if ((ch->flags & RUNNING) && ch->action != BOOT) break;
|
|
|
|
#if CHANGE_WAIT
|
|
/* Wait until we get hit by some signal. */
|
|
while (ch != NULL && got_signals == 0) {
|
|
if (ISMEMBER(got_signals, SIGHUP)) {
|
|
/* See if there are processes to be waited on. */
|
|
for(ch = family; ch; ch = ch->next)
|
|
if (ch->flags & WAITING) break;
|
|
}
|
|
if (ch != NULL) check_init_fifo();
|
|
}
|
|
#else /* CHANGE_WAIT */
|
|
if (ch != NULL && got_signals == 0) check_init_fifo();
|
|
#endif /* CHANGE_WAIT */
|
|
|
|
/* Check the 'failing' flags */
|
|
fail_check();
|
|
|
|
/* Process any signals. */
|
|
process_signals();
|
|
|
|
/* See what we need to start up (again) */
|
|
start_if_needed();
|
|
}
|
|
/*NOTREACHED*/
|
|
}
|
|
|
|
/*
|
|
* Tell the user about the syntax we expect.
|
|
*/
|
|
static
|
|
void usage(char *s)
|
|
{
|
|
fprintf(stderr, "Usage: %s {-e VAR[=VAL] | [-t SECONDS] {0|1|2|3|4|5|6|S|s|Q|q|A|a|B|b|C|c|U|u}}\n", s);
|
|
exit(1);
|
|
}
|
|
|
|
static
|
|
int telinit(char *progname, int argc, char **argv)
|
|
{
|
|
#ifdef TELINIT_USES_INITLVL
|
|
FILE *fp;
|
|
#endif
|
|
struct init_request request;
|
|
struct sigaction sa;
|
|
int f, fd, l;
|
|
char *env = NULL;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
request.magic = INIT_MAGIC;
|
|
|
|
while ((f = getopt(argc, argv, "t:e:")) != EOF) switch(f) {
|
|
case 't':
|
|
sleep_time = atoi(optarg);
|
|
break;
|
|
case 'e':
|
|
if (env == NULL)
|
|
env = request.i.data;
|
|
l = strlen(optarg);
|
|
if (env + l + 2 > request.i.data + sizeof(request.i.data)) {
|
|
fprintf(stderr, "%s: -e option data "
|
|
"too large\n", progname);
|
|
exit(1);
|
|
}
|
|
memcpy(env, optarg, l);
|
|
env += l;
|
|
*env++ = 0;
|
|
break;
|
|
default:
|
|
usage(progname);
|
|
break;
|
|
}
|
|
|
|
if (env) *env++ = 0;
|
|
|
|
if (env) {
|
|
if (argc != optind)
|
|
usage(progname);
|
|
request.cmd = INIT_CMD_SETENV;
|
|
} else {
|
|
if (argc - optind != 1 || strlen(argv[optind]) != 1)
|
|
usage(progname);
|
|
if (!strchr("0123456789SsQqAaBbCcUu", argv[optind][0]))
|
|
usage(progname);
|
|
request.cmd = INIT_CMD_RUNLVL;
|
|
request.runlevel = argv[optind][0];
|
|
request.sleeptime = sleep_time;
|
|
}
|
|
|
|
/* Change to the root directory. */
|
|
if (0 != chdir("/"))
|
|
initlog(L_VB, "unable to chdir to /: %s",
|
|
strerror(errno));
|
|
|
|
/* Open the fifo and write a command. */
|
|
/* Make sure we don't hang on opening /run/initctl */
|
|
SETSIG(sa, SIGALRM, signal_handler, 0);
|
|
alarm(3);
|
|
if ((fd = open(INIT_FIFO, O_WRONLY)) >= 0) {
|
|
ssize_t p = 0;
|
|
size_t s = sizeof(request);
|
|
void *ptr = &request;
|
|
|
|
while (s > 0) {
|
|
p = write(fd, ptr, s);
|
|
if (p < 0) {
|
|
if (errno == EINTR || errno == EAGAIN)
|
|
continue;
|
|
break;
|
|
}
|
|
ptr += p;
|
|
s -= p;
|
|
}
|
|
close(fd);
|
|
alarm(0);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef TELINIT_USES_INITLVL
|
|
if (request.cmd == INIT_CMD_RUNLVL) {
|
|
/* Fallthrough to the old method. */
|
|
|
|
/* Now write the new runlevel. */
|
|
if ((fp = fopen(INITLVL, "w")) == NULL) {
|
|
fprintf(stderr, "%s: cannot create %s\n",
|
|
progname, INITLVL);
|
|
exit(1);
|
|
}
|
|
fprintf(fp, "%s %d", argv[optind], sleep_time);
|
|
fclose(fp);
|
|
|
|
/* And tell init about the pending runlevel change. */
|
|
if (kill(INITPID, SIGHUP) < 0) perror(progname);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
fprintf(stderr, "%s: ", progname);
|
|
if (ISMEMBER(got_signals, SIGALRM)) {
|
|
fprintf(stderr, "timeout opening/writing control channel %s\n",
|
|
INIT_FIFO);
|
|
} else {
|
|
perror(INIT_FIFO);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Main entry for init and telinit.
|
|
*/
|
|
int main(int argc, char **argv)
|
|
{
|
|
char *p;
|
|
int f;
|
|
int isinit;
|
|
#ifdef WITH_SELINUX
|
|
int enforce = 0;
|
|
#endif
|
|
|
|
/* Get my own name */
|
|
if ((p = strrchr(argv[0], '/')) != NULL)
|
|
p++;
|
|
else
|
|
p = argv[0];
|
|
|
|
if ( (argc == 2) && (! strcmp(argv[1], "--version") ) )
|
|
{
|
|
printf("SysV init version: %s\n\n", VERSION);
|
|
exit(0);
|
|
}
|
|
|
|
/* Common umask */
|
|
umask(umask(077) | 022);
|
|
|
|
/* Quick check */
|
|
if (geteuid() != 0) {
|
|
fprintf(stderr, "%s: must be superuser.\n", p);
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* Is this telinit or init ?
|
|
*/
|
|
isinit = (getpid() == 1);
|
|
for (f = 1; f < argc; f++) {
|
|
if (!strcmp(argv[f], "-i") || !strcmp(argv[f], "--init")) {
|
|
isinit = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!isinit) exit(telinit(p, argc, argv));
|
|
|
|
/*
|
|
* Check for re-exec
|
|
*/
|
|
if (check_pipe(STATE_PIPE)) {
|
|
|
|
receive_state(STATE_PIPE);
|
|
|
|
myname = istrdup(argv[0]);
|
|
argv0 = argv[0];
|
|
maxproclen = 0;
|
|
for (f = 0; f < argc; f++)
|
|
maxproclen += strlen(argv[f]) + 1;
|
|
reload = 1;
|
|
setproctitle("init [%c]", (int)runlevel);
|
|
|
|
init_main();
|
|
}
|
|
|
|
/* Check command line arguments */
|
|
maxproclen = strlen(argv[0]) + 1;
|
|
for(f = 1; f < argc; f++) {
|
|
if (!strcmp(argv[f], "single") || !strcmp(argv[f], "-s"))
|
|
dfl_level = 'S';
|
|
else if (!strcmp(argv[f], "-a") || !strcmp(argv[f], "auto"))
|
|
putenv("AUTOBOOT=YES");
|
|
else if (!strcmp(argv[f], "-b") || !strcmp(argv[f],"emergency"))
|
|
emerg_shell = 1;
|
|
else if (!strcmp(argv[f], "-z")) {
|
|
/* Ignore -z xxx */
|
|
if (argv[f + 1]) f++;
|
|
} else if (strchr("0123456789sS", argv[f][0])
|
|
&& strlen(argv[f]) == 1)
|
|
dfl_level = argv[f][0];
|
|
/* "init u" in the very beginning makes no sense */
|
|
if (dfl_level == 's') dfl_level = 'S';
|
|
maxproclen += strlen(argv[f]) + 1;
|
|
}
|
|
|
|
#ifdef WITH_SELINUX
|
|
if (getenv("SELINUX_INIT") == NULL) {
|
|
if (is_selinux_enabled() != 1) {
|
|
if (selinux_init_load_policy(&enforce) == 0) {
|
|
putenv("SELINUX_INIT=YES");
|
|
execv(myname, argv);
|
|
} else {
|
|
if (enforce > 0) {
|
|
/* SELinux in enforcing mode but load_policy failed */
|
|
/* At this point, we probably can't open /dev/console, so log() won't work */
|
|
fprintf(stderr,"Unable to load SELinux Policy. Machine is in enforcing mode. Halting now.\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
/* Start booting. */
|
|
argv0 = argv[0];
|
|
argv[1] = NULL;
|
|
setproctitle("init boot");
|
|
init_main();
|
|
|
|
/*NOTREACHED*/
|
|
return 0;
|
|
}
|