| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| |
| /* |
| * Copyright (c) 2013 Gary Mills |
| * |
| * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved. |
| */ |
| |
| /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ |
| /* All Rights Reserved */ |
| |
| /* |
| * University Copyright- Copyright (c) 1982, 1986, 1988 |
| * The Regents of the University of California |
| * All Rights Reserved |
| * |
| * University Acknowledgment- Portions of this document are derived from |
| * software developed by the University of California, Berkeley, and its |
| * contributors. |
| */ |
| |
| /* |
| * init(1M) is the general process spawning program. Its primary job is to |
| * start and restart svc.startd for smf(5). For backwards-compatibility it also |
| * spawns and respawns processes according to /etc/inittab and the current |
| * run-level. It reads /etc/default/inittab for general configuration. |
| * |
| * To change run-levels the system administrator runs init from the command |
| * line with a level name. init signals svc.startd via libscf and directs the |
| * zone's init (pid 1 in the global zone) what to do by sending it a signal; |
| * these signal numbers are commonly refered to in the code as 'states'. Valid |
| * run-levels are [sS0123456]. Additionally, init can be given directives |
| * [qQabc], which indicate actions to be taken pertaining to /etc/inittab. |
| * |
| * When init processes inittab entries, it finds processes that are to be |
| * spawned at various run-levels. inittab contains the set of the levels for |
| * which each inittab entry is valid. |
| * |
| * State File and Restartability |
| * Premature exit by init(1M) is handled as a special case by the kernel: |
| * init(1M) will be immediately re-executed, retaining its original PID. (PID |
| * 1 in the global zone.) To track the processes it has previously spawned, |
| * as well as other mutable state, init(1M) regularly updates a state file |
| * such that its subsequent invocations have knowledge of its various |
| * dependent processes and duties. |
| * |
| * Process Contracts |
| * We start svc.startd(1M) in a contract and transfer inherited contracts when |
| * restarting it. Everything else is started using the legacy contract |
| * template, and the created contracts are abandoned when they become empty. |
| * |
| * utmpx Entry Handling |
| * Because init(1M) no longer governs the startup process, its knowledge of |
| * when utmpx becomes writable is indirect. However, spawned processes |
| * expect to be constructed with valid utmpx entries. As a result, attempts |
| * to write normal entries will be retried until successful. |
| * |
| * Maintenance Mode |
| * In certain failure scenarios, init(1M) will enter a maintenance mode, in |
| * which it invokes sulogin(1M) to allow the operator an opportunity to |
| * repair the system. Normally, this operation is performed as a |
| * fork(2)-exec(2)-waitpid(3C) sequence with the parent waiting for repair or |
| * diagnosis to be completed. In the cases that fork(2) requests themselves |
| * fail, init(1M) will directly execute sulogin(1M), and allow the kernel to |
| * restart init(1M) on exit from the operator session. |
| * |
| * One scenario where init(1M) enters its maintenance mode is when |
| * svc.startd(1M) begins to fail rapidly, defined as when the average time |
| * between recent failures drops below a given threshold. |
| */ |
| |
| #include <sys/contract/process.h> |
| #include <sys/ctfs.h> |
| #include <sys/stat.h> |
| #include <sys/statvfs.h> |
| #include <sys/stropts.h> |
| #include <sys/systeminfo.h> |
| #include <sys/time.h> |
| #include <sys/termios.h> |
| #include <sys/tty.h> |
| #include <sys/types.h> |
| #include <sys/utsname.h> |
| |
| #include <bsm/adt_event.h> |
| #include <bsm/libbsm.h> |
| #include <security/pam_appl.h> |
| |
| #include <assert.h> |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <libcontract.h> |
| #include <libcontract_priv.h> |
| #include <libintl.h> |
| #include <libscf.h> |
| #include <libscf_priv.h> |
| #include <poll.h> |
| #include <procfs.h> |
| #include <signal.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdio_ext.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <syslog.h> |
| #include <time.h> |
| #include <ulimit.h> |
| #include <unistd.h> |
| #include <utmpx.h> |
| #include <wait.h> |
| #include <zone.h> |
| #include <ucontext.h> |
| |
| #undef sleep |
| |
| #define fioctl(p, sptr, cmd) ioctl(fileno(p), sptr, cmd) |
| #define min(a, b) (((a) < (b)) ? (a) : (b)) |
| |
| #define TRUE 1 |
| #define FALSE 0 |
| #define FAILURE -1 |
| |
| #define UT_USER_SZ 32 /* Size of a utmpx ut_user field */ |
| #define UT_LINE_SZ 32 /* Size of a utmpx ut_line field */ |
| |
| /* |
| * SLEEPTIME The number of seconds "init" sleeps between wakeups if |
| * nothing else requires this "init" wakeup. |
| */ |
| #define SLEEPTIME (5 * 60) |
| |
| /* |
| * MAXCMDL The maximum length of a command string in inittab. |
| */ |
| #define MAXCMDL 512 |
| |
| /* |
| * EXEC The length of the prefix string added to all comamnds |
| * found in inittab. |
| */ |
| #define EXEC (sizeof ("exec ") - 1) |
| |
| /* |
| * TWARN The amount of time between warning signal, SIGTERM, |
| * and the fatal kill signal, SIGKILL. |
| */ |
| #define TWARN 5 |
| |
| #define id_eq(x, y) ((x[0] == y[0] && x[1] == y[1] && x[2] == y[2] &&\ |
| x[3] == y[3]) ? TRUE : FALSE) |
| |
| /* |
| * The kernel's default umask is 022 these days; since some processes inherit |
| * their umask from init, init will set it from CMASK in /etc/default/init. |
| * init gets the default umask from the kernel, it sets it to 022 whenever |
| * it wants to create a file and reverts to CMASK afterwards. |
| */ |
| |
| static int cmask; |
| |
| /* |
| * The following definitions, concluding with the 'lvls' array, provide a |
| * common mapping between level-name (like 'S'), signal number (state), |
| * run-level mask, and specific properties associated with a run-level. |
| * This array should be accessed using the routines lvlname_to_state(), |
| * lvlname_to_mask(), state_to_mask(), and state_to_flags(). |
| */ |
| |
| /* |
| * Correspondence of signals to init actions. |
| */ |
| #define LVLQ SIGHUP |
| #define LVL0 SIGINT |
| #define LVL1 SIGQUIT |
| #define LVL2 SIGILL |
| #define LVL3 SIGTRAP |
| #define LVL4 SIGIOT |
| #define LVL5 SIGEMT |
| #define LVL6 SIGFPE |
| #define SINGLE_USER SIGBUS |
| #define LVLa SIGSEGV |
| #define LVLb SIGSYS |
| #define LVLc SIGPIPE |
| |
| /* |
| * Bit Mask for each level. Used to determine legal levels. |
| */ |
| #define MASK0 0x0001 |
| #define MASK1 0x0002 |
| #define MASK2 0x0004 |
| #define MASK3 0x0008 |
| #define MASK4 0x0010 |
| #define MASK5 0x0020 |
| #define MASK6 0x0040 |
| #define MASKSU 0x0080 |
| #define MASKa 0x0100 |
| #define MASKb 0x0200 |
| #define MASKc 0x0400 |
| |
| #define MASK_NUMERIC (MASK0 | MASK1 | MASK2 | MASK3 | MASK4 | MASK5 | MASK6) |
| #define MASK_abc (MASKa | MASKb | MASKc) |
| |
| /* |
| * Flags to indicate properties of various states. |
| */ |
| #define LSEL_RUNLEVEL 0x0001 /* runlevels you can transition to */ |
| |
| typedef struct lvl { |
| int lvl_state; |
| int lvl_mask; |
| char lvl_name; |
| int lvl_flags; |
| } lvl_t; |
| |
| static lvl_t lvls[] = { |
| { LVLQ, 0, 'Q', 0 }, |
| { LVLQ, 0, 'q', 0 }, |
| { LVL0, MASK0, '0', LSEL_RUNLEVEL }, |
| { LVL1, MASK1, '1', LSEL_RUNLEVEL }, |
| { LVL2, MASK2, '2', LSEL_RUNLEVEL }, |
| { LVL3, MASK3, '3', LSEL_RUNLEVEL }, |
| { LVL4, MASK4, '4', LSEL_RUNLEVEL }, |
| { LVL5, MASK5, '5', LSEL_RUNLEVEL }, |
| { LVL6, MASK6, '6', LSEL_RUNLEVEL }, |
| { SINGLE_USER, MASKSU, 'S', LSEL_RUNLEVEL }, |
| { SINGLE_USER, MASKSU, 's', LSEL_RUNLEVEL }, |
| { LVLa, MASKa, 'a', 0 }, |
| { LVLb, MASKb, 'b', 0 }, |
| { LVLc, MASKc, 'c', 0 } |
| }; |
| |
| #define LVL_NELEMS (sizeof (lvls) / sizeof (lvl_t)) |
| |
| /* |
| * Legal action field values. |
| */ |
| #define OFF 0 /* Kill process if on, else ignore */ |
| #define RESPAWN 1 /* Continuously restart process when it dies */ |
| #define ONDEMAND RESPAWN /* Respawn for a, b, c type processes */ |
| #define ONCE 2 /* Start process, do not respawn when dead */ |
| #define WAIT 3 /* Perform once and wait to complete */ |
| #define BOOT 4 /* Start at boot time only */ |
| #define BOOTWAIT 5 /* Start at boot time and wait to complete */ |
| #define POWERFAIL 6 /* Start on powerfail */ |
| #define POWERWAIT 7 /* Start and wait for complete on powerfail */ |
| #define INITDEFAULT 8 /* Default level "init" should start at */ |
| #define SYSINIT 9 /* Actions performed before init speaks */ |
| |
| #define M_OFF 0001 |
| #define M_RESPAWN 0002 |
| #define M_ONDEMAND M_RESPAWN |
| #define M_ONCE 0004 |
| #define M_WAIT 0010 |
| #define M_BOOT 0020 |
| #define M_BOOTWAIT 0040 |
| #define M_PF 0100 |
| #define M_PWAIT 0200 |
| #define M_INITDEFAULT 0400 |
| #define M_SYSINIT 01000 |
| |
| /* States for the inittab parser in getcmd(). */ |
| #define ID 1 |
| #define LEVELS 2 |
| #define ACTION 3 |
| #define COMMAND 4 |
| #define COMMENT 5 |
| |
| /* |
| * inittab entry id constants |
| */ |
| #define INITTAB_ENTRY_ID_SIZE 4 |
| #define INITTAB_ENTRY_ID_STR_FORMAT "%.4s" /* if INITTAB_ENTRY_ID_SIZE */ |
| /* changes, this should */ |
| /* change accordingly */ |
| |
| /* |
| * Init can be in any of three main states, "normal" mode where it is |
| * processing entries for the lines file in a normal fashion, "boot" mode, |
| * where it is only interested in the boot actions, and "powerfail" mode, |
| * where it is only interested in powerfail related actions. The following |
| * masks declare the legal actions for each mode. |
| */ |
| #define NORMAL_MODES (M_OFF | M_RESPAWN | M_ONCE | M_WAIT) |
| #define BOOT_MODES (M_BOOT | M_BOOTWAIT) |
| #define PF_MODES (M_PF | M_PWAIT) |
| |
| struct PROC_TABLE { |
| char p_id[INITTAB_ENTRY_ID_SIZE]; /* Four letter unique id of */ |
| /* process */ |
| pid_t p_pid; /* Process id */ |
| short p_count; /* How many respawns of this command in */ |
| /* the current series */ |
| long p_time; /* Start time for a series of respawns */ |
| short p_flags; |
| short p_exit; /* Exit status of a process which died */ |
| }; |
| |
| /* |
| * Flags for the "p_flags" word of a PROC_TABLE entry: |
| * |
| * OCCUPIED This slot in init's proc table is in use. |
| * |
| * LIVING Process is alive. |
| * |
| * NOCLEANUP efork() is not allowed to cleanup this entry even |
| * if process is dead. |
| * |
| * NAMED This process has a name, i.e. came from inittab. |
| * |
| * DEMANDREQUEST Process started by a "telinit [abc]" command. Processes |
| * formed this way are respawnable and immune to level |
| * changes as long as their entry exists in inittab. |
| * |
| * TOUCHED Flag used by remv() to determine whether it has looked |
| * at an entry while checking for processes to be killed. |
| * |
| * WARNED Flag used by remv() to mark processes that have been |
| * sent the SIGTERM signal. If they don't die in 5 |
| * seconds, they are sent the SIGKILL signal. |
| * |
| * KILLED Flag used by remv() to mark procs that have been sent |
| * the SIGTERM and SIGKILL signals. |
| * |
| * PF_MASK Bitwise or of legal flags, for sanity checking. |
| */ |
| #define OCCUPIED 01 |
| #define LIVING 02 |
| #define NOCLEANUP 04 |
| #define NAMED 010 |
| #define DEMANDREQUEST 020 |
| #define TOUCHED 040 |
| #define WARNED 0100 |
| #define KILLED 0200 |
| #define PF_MASK 0377 |
| |
| /* |
| * Respawn limits for processes that are to be respawned: |
| * |
| * SPAWN_INTERVAL The number of seconds over which "init" will try to |
| * respawn a process SPAWN_LIMIT times before it gets mad. |
| * |
| * SPAWN_LIMIT The number of respawns "init" will attempt in |
| * SPAWN_INTERVAL seconds before it generates an |
| * error message and inhibits further tries for |
| * INHIBIT seconds. |
| * |
| * INHIBIT The number of seconds "init" ignores an entry it had |
| * trouble spawning unless a "telinit Q" is received. |
| */ |
| |
| #define SPAWN_INTERVAL (2*60) |
| #define SPAWN_LIMIT 10 |
| #define INHIBIT (5*60) |
| |
| /* |
| * The maximum number of decimal digits for an id_t. (ceil(log10 (max_id))) |
| */ |
| #define ID_MAX_STR_LEN 10 |
| |
| #define NULLPROC ((struct PROC_TABLE *)(0)) |
| #define NO_ROOM ((struct PROC_TABLE *)(FAILURE)) |
| |
| struct CMD_LINE { |
| char c_id[INITTAB_ENTRY_ID_SIZE]; /* Four letter unique id of */ |
| /* process to be affected by */ |
| /* action */ |
| short c_levels; /* Mask of legal levels for process */ |
| short c_action; /* Mask for type of action required */ |
| char *c_command; /* Pointer to init command */ |
| }; |
| |
| struct pidrec { |
| int pd_type; /* Command type */ |
| pid_t pd_pid; /* pid to add or remove */ |
| }; |
| |
| /* |
| * pd_type's |
| */ |
| #define ADDPID 1 |
| #define REMPID 2 |
| |
| static struct pidlist { |
| pid_t pl_pid; /* pid to watch for */ |
| int pl_dflag; /* Flag indicating SIGCLD from this pid */ |
| short pl_exit; /* Exit status of proc */ |
| struct pidlist *pl_next; /* Next in list */ |
| } *Plhead, *Plfree; |
| |
| /* |
| * The following structure contains a set of modes for /dev/syscon |
| * and should match the default contents of /etc/ioctl.syscon. |
| */ |
| static struct termios dflt_termios = { |
| .c_iflag = BRKINT|ICRNL|IXON|IMAXBEL, |
| .c_oflag = OPOST|ONLCR|TAB3, |
| .c_cflag = CS8|CREAD|B9600, |
| .c_lflag = ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE|IEXTEN, |
| .c_cc = { CINTR, CQUIT, CERASE, CKILL, CEOF, 0, 0, 0, |
| CSTART, CSTOP, CSWTCH, CDSUSP, CRPRNT, CFLUSH, CWERASE, CLNEXT, |
| CSTATUS, CERASE2, 0 |
| } |
| }; |
| |
| static struct termios stored_syscon_termios; |
| static int write_ioctl = 0; /* Rewrite /etc/ioctl.syscon */ |
| |
| static union WAKEUP { |
| struct WAKEFLAGS { |
| unsigned w_usersignal : 1; /* User sent signal to "init" */ |
| unsigned w_childdeath : 1; /* An "init" child died */ |
| unsigned w_powerhit : 1; /* OS experienced powerfail */ |
| } w_flags; |
| int w_mask; |
| } wakeup; |
| |
| |
| struct init_state { |
| int ist_runlevel; |
| int ist_num_proc; |
| int ist_utmpx_ok; |
| struct PROC_TABLE ist_proc_table[1]; |
| }; |
| |
| #define cur_state (g_state->ist_runlevel) |
| #define num_proc (g_state->ist_num_proc) |
| #define proc_table (g_state->ist_proc_table) |
| #define utmpx_ok (g_state->ist_utmpx_ok) |
| |
| /* Contract cookies. */ |
| #define ORDINARY_COOKIE 0 |
| #define STARTD_COOKIE 1 |
| |
| |
| #ifndef NDEBUG |
| #define bad_error(func, err) { \ |
| (void) fprintf(stderr, "%s:%d: %s() failed with unexpected " \ |
| "error %d. Aborting.\n", __FILE__, __LINE__, (func), (err)); \ |
| abort(); \ |
| } |
| #else |
| #define bad_error(func, err) abort() |
| #endif |
| |
| |
| /* |
| * Useful file and device names. |
| */ |
| static char *CONSOLE = "/dev/console"; /* Real system console */ |
| static char *INITPIPE_DIR = "/var/run"; |
| static char *INITPIPE = "/var/run/initpipe"; |
| |
| #define INIT_STATE_DIR "/etc/svc/volatile" |
| static const char * const init_state_file = INIT_STATE_DIR "/init.state"; |
| static const char * const init_next_state_file = |
| INIT_STATE_DIR "/init-next.state"; |
| |
| static const int init_num_proc = 20; /* Initial size of process table. */ |
| |
| static char *UTMPX = UTMPX_FILE; /* Snapshot record file */ |
| static char *WTMPX = WTMPX_FILE; /* Long term record file */ |
| static char *INITTAB = "/etc/inittab"; /* Script file for "init" */ |
| static char *SYSTTY = "/dev/systty"; /* System Console */ |
| static char *SYSCON = "/dev/syscon"; /* Virtual System console */ |
| static char *IOCTLSYSCON = "/etc/ioctl.syscon"; /* Last syscon modes */ |
| static char *ENVFILE = "/etc/default/init"; /* Default env. */ |
| static char *SU = "/etc/sulogin"; /* Super-user program for single user */ |
| static char *SH = "/sbin/sh"; /* Standard shell */ |
| |
| /* |
| * Default Path. /sbin is included in path only during sysinit phase |
| */ |
| #define DEF_PATH "PATH=/usr/sbin:/usr/bin" |
| #define INIT_PATH "PATH=/sbin:/usr/sbin:/usr/bin" |
| |
| static int prior_state; |
| static int prev_state; /* State "init" was in last time it woke */ |
| static int new_state; /* State user wants "init" to go to. */ |
| static int lvlq_received; /* Explicit request to examine state */ |
| static int op_modes = BOOT_MODES; /* Current state of "init" */ |
| static int Gchild = 0; /* Flag to indicate "godchild" died, set in */ |
| /* childeath() and cleared in cleanaux() */ |
| static int Pfd = -1; /* fd to receive pids thru */ |
| static unsigned int spawncnt, pausecnt; |
| static int rsflag; /* Set if a respawn has taken place */ |
| static volatile int time_up; /* Flag set to TRUE by the alarm interrupt */ |
| /* routine each time an alarm interrupt */ |
| /* takes place. */ |
| static int sflg = 0; /* Set if we were booted -s to single user */ |
| static int rflg = 0; /* Set if booted -r, reconfigure devices */ |
| static int bflg = 0; /* Set if booted -b, don't run rc scripts */ |
| static pid_t init_pid; /* PID of "one true" init for current zone */ |
| |
| static struct init_state *g_state = NULL; |
| static size_t g_state_sz; |
| static int booting = 1; /* Set while we're booting. */ |
| |
| /* |
| * Array for default global environment. |
| */ |
| #define MAXENVENT 24 /* Max number of default env variables + 1 */ |
| /* init can use three itself, so this leaves */ |
| /* 20 for the administrator in ENVFILE. */ |
| static char *glob_envp[MAXENVENT]; /* Array of environment strings */ |
| static int glob_envn; /* Number of environment strings */ |
| |
| |
| static struct pollfd poll_fds[1]; |
| static int poll_nfds = 0; /* poll_fds is uninitialized */ |
| |
| /* |
| * Contracts constants |
| */ |
| #define SVC_INIT_PREFIX "init:/" |
| #define SVC_AUX_SIZE (INITTAB_ENTRY_ID_SIZE + 1) |
| #define SVC_FMRI_SIZE (sizeof (SVC_INIT_PREFIX) + INITTAB_ENTRY_ID_SIZE) |
| |
| static int legacy_tmpl = -1; /* fd for legacy contract template */ |
| static int startd_tmpl = -1; /* fd for svc.startd's template */ |
| static char startd_svc_aux[SVC_AUX_SIZE]; |
| |
| static char startd_cline[256] = ""; /* svc.startd's command line */ |
| static int do_restart_startd = 1; /* Whether to restart svc.startd. */ |
| static char *smf_options = NULL; /* Options to give to startd. */ |
| static int smf_debug = 0; /* Messages for debugging smf(5) */ |
| static time_t init_boot_time; /* Substitute for kernel boot time. */ |
| |
| #define NSTARTD_FAILURE_TIMES 3 /* trigger after 3 failures */ |
| #define STARTD_FAILURE_RATE_NS 5000000000LL /* 1 failure/5 seconds */ |
| |
| static hrtime_t startd_failure_time[NSTARTD_FAILURE_TIMES]; |
| static uint_t startd_failure_index; |
| |
| |
| static char *prog_name(char *); |
| static int state_to_mask(int); |
| static int lvlname_to_mask(char, int *); |
| static void lscf_set_runlevel(char); |
| static int state_to_flags(int); |
| static char state_to_name(int); |
| static int lvlname_to_state(char); |
| static int getcmd(struct CMD_LINE *, char *); |
| static int realcon(); |
| static int spawn_processes(); |
| static int get_ioctl_syscon(); |
| static int account(short, struct PROC_TABLE *, char *); |
| static void alarmclk(); |
| static void childeath(int); |
| static void cleanaux(); |
| static void clearent(pid_t, short); |
| static void console(boolean_t, char *, ...); |
| static void init_signals(void); |
| static void setup_pipe(); |
| static void killproc(pid_t); |
| static void init_env(); |
| static void boot_init(); |
| static void powerfail(); |
| static void remv(); |
| static void write_ioctl_syscon(); |
| static void spawn(struct PROC_TABLE *, struct CMD_LINE *); |
| static void setimer(int); |
| static void siglvl(int, siginfo_t *, ucontext_t *); |
| static void sigpoll(int); |
| static void enter_maintenance(void); |
| static void timer(int); |
| static void userinit(int, char **); |
| static void notify_pam_dead(struct utmpx *); |
| static long waitproc(struct PROC_TABLE *); |
| static struct PROC_TABLE *efork(int, struct PROC_TABLE *, int); |
| static struct PROC_TABLE *findpslot(struct CMD_LINE *); |
| static void increase_proc_table_size(); |
| static void st_init(); |
| static void st_write(); |
| static void contracts_init(); |
| static void contract_event(struct pollfd *); |
| static int startd_run(const char *, int, ctid_t); |
| static void startd_record_failure(); |
| static int startd_failure_rate_critical(); |
| static char *audit_boot_msg(); |
| static int audit_put_record(int, int, char *); |
| static void update_boot_archive(int new_state); |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| int chg_lvl_flag = FALSE, print_banner = FALSE; |
| int may_need_audit = 1; |
| int c; |
| char *msg; |
| |
| /* Get a timestamp for use as boot time, if needed. */ |
| (void) time(&init_boot_time); |
| |
| /* Get the default umask */ |
| cmask = umask(022); |
| (void) umask(cmask); |
| |
| /* Parse the arguments to init. Check for single user */ |
| opterr = 0; |
| while ((c = getopt(argc, argv, "brsm:")) != EOF) { |
| switch (c) { |
| case 'b': |
| rflg = 0; |
| bflg = 1; |
| if (!sflg) |
| sflg++; |
| break; |
| case 'r': |
| bflg = 0; |
| rflg++; |
| break; |
| case 's': |
| if (!bflg) |
| sflg++; |
| break; |
| case 'm': |
| smf_options = optarg; |
| smf_debug = (strstr(smf_options, "debug") != NULL); |
| break; |
| } |
| } |
| |
| /* |
| * Determine if we are the main init, or a user invoked init, whose job |
| * it is to inform init to change levels or perform some other action. |
| */ |
| if (zone_getattr(getzoneid(), ZONE_ATTR_INITPID, &init_pid, |
| sizeof (init_pid)) != sizeof (init_pid)) { |
| (void) fprintf(stderr, "could not get pid for init\n"); |
| return (1); |
| } |
| |
| /* |
| * If this PID is not the same as the "true" init for the zone, then we |
| * must be in 'user' mode. |
| */ |
| if (getpid() != init_pid) { |
| userinit(argc, argv); |
| } |
| |
| if (getzoneid() != GLOBAL_ZONEID) { |
| print_banner = TRUE; |
| } |
| |
| /* |
| * Initialize state (and set "booting"). |
| */ |
| st_init(); |
| |
| if (booting && print_banner) { |
| struct utsname un; |
| char buf[BUFSIZ], *isa; |
| long ret; |
| int bits = 32; |
| |
| /* |
| * We want to print the boot banner as soon as |
| * possible. In the global zone, the kernel does it, |
| * but we do not have that luxury in non-global zones, |
| * so we will print it here. |
| */ |
| (void) uname(&un); |
| ret = sysinfo(SI_ISALIST, buf, sizeof (buf)); |
| if (ret != -1L && ret <= sizeof (buf)) { |
| for (isa = strtok(buf, " "); isa; |
| isa = strtok(NULL, " ")) { |
| if (strcmp(isa, "sparcv9") == 0 || |
| strcmp(isa, "amd64") == 0) { |
| bits = 64; |
| break; |
| } |
| } |
| } |
| |
| console(B_FALSE, |
| "\n\n%s Release %s Version %s %d-bit\r\n", |
| un.sysname, un.release, un.version, bits); |
| console(B_FALSE, |
| "Copyright (c) 1983, 2010, Oracle and/or its affiliates." |
| " All rights reserved.\r\n"); |
| } |
| |
| /* |
| * Get the ioctl settings for /dev/syscon from /etc/ioctl.syscon |
| * so that it can be brought up in the state it was in when the |
| * system went down; or set to defaults if ioctl.syscon isn't |
| * valid. |
| * |
| * This needs to be done even if we're restarting so reset_modes() |
| * will work in case we need to go down to single user mode. |
| */ |
| write_ioctl = get_ioctl_syscon(); |
| |
| /* |
| * Set up all signals to be caught or ignored as appropriate. |
| */ |
| init_signals(); |
| |
| /* Load glob_envp from ENVFILE. */ |
| init_env(); |
| |
| contracts_init(); |
| |
| if (!booting) { |
| /* cur_state should have been read in. */ |
| |
| op_modes = NORMAL_MODES; |
| |
| /* Rewrite the ioctl file if it was bad. */ |
| if (write_ioctl) |
| write_ioctl_syscon(); |
| } else { |
| /* |
| * It's fine to boot up with state as zero, because |
| * startd will later tell us the real state. |
| */ |
| cur_state = 0; |
| op_modes = BOOT_MODES; |
| |
| boot_init(); |
| } |
| |
| prev_state = prior_state = cur_state; |
| |
| setup_pipe(); |
| |
| /* |
| * Here is the beginning of the main process loop. |
| */ |
| for (;;) { |
| if (lvlq_received) { |
| setup_pipe(); |
| lvlq_received = B_FALSE; |
| } |
| |
| /* |
| * Clean up any accounting records for dead "godchildren". |
| */ |
| if (Gchild) |
| cleanaux(); |
| |
| /* |
| * If in "normal" mode, check all living processes and initiate |
| * kill sequence on those that should not be there anymore. |
| */ |
| if (op_modes == NORMAL_MODES && cur_state != LVLa && |
| cur_state != LVLb && cur_state != LVLc) |
| remv(); |
| |
| /* |
| * If a change in run levels is the reason we awoke, now do |
| * the accounting to report the change in the utmp file. |
| * Also report the change on the system console. |
| */ |
| if (chg_lvl_flag) { |
| chg_lvl_flag = FALSE; |
| |
| if (state_to_flags(cur_state) & LSEL_RUNLEVEL) { |
| char rl = state_to_name(cur_state); |
| |
| if (rl != -1) |
| lscf_set_runlevel(rl); |
| } |
| |
| may_need_audit = 1; |
| } |
| |
| /* |
| * Scan the inittab file and spawn and respawn processes that |
| * should be alive in the current state. If inittab does not |
| * exist default to single user mode. |
| */ |
| if (spawn_processes() == FAILURE) { |
| prior_state = prev_state; |
| cur_state = SINGLE_USER; |
| } |
| |
| /* If any respawns occurred, take note. */ |
| if (rsflag) { |
| rsflag = 0; |
| spawncnt++; |
| } |
| |
| /* |
| * If a powerfail signal was received during the last |
| * sequence, set mode to powerfail. When spawn_processes() is |
| * entered the first thing it does is to check "powerhit". If |
| * it is in PF_MODES then it clears "powerhit" and does |
| * a powerfail sequence. If it is not in PF_MODES, then it |
| * puts itself in PF_MODES and then clears "powerhit". Should |
| * "powerhit" get set again while spawn_processes() is working |
| * on a powerfail sequence, the following code will see that |
| * spawn_processes() tries to execute the powerfail sequence |
| * again. This guarantees that the powerfail sequence will be |
| * successfully completed before further processing takes |
| * place. |
| */ |
| if (wakeup.w_flags.w_powerhit) { |
| op_modes = PF_MODES; |
| /* |
| * Make sure that cur_state != prev_state so that |
| * ONCE and WAIT types work. |
| */ |
| prev_state = 0; |
| } else if (op_modes != NORMAL_MODES) { |
| /* |
| * If spawn_processes() was not just called while in |
| * normal mode, we set the mode to normal and it will |
| * be called again to check normal modes. If we have |
| * just finished a powerfail sequence with prev_state |
| * equal to zero, we set prev_state equal to cur_state |
| * before the next pass through. |
| */ |
| if (op_modes == PF_MODES) |
| prev_state = cur_state; |
| op_modes = NORMAL_MODES; |
| } else if (cur_state == LVLa || cur_state == LVLb || |
| cur_state == LVLc) { |
| /* |
| * If it was a change of levels that awakened us and the |
| * new level is one of the demand levels then reset |
| * cur_state to the previous state and do another scan |
| * to take care of the usual respawn actions. |
| */ |
| cur_state = prior_state; |
| prior_state = prev_state; |
| prev_state = cur_state; |
| } else { |
| prev_state = cur_state; |
| |
| if (wakeup.w_mask == 0) { |
| int ret; |
| |
| if (may_need_audit && (cur_state == LVL3)) { |
| msg = audit_boot_msg(); |
| |
| may_need_audit = 0; |
| (void) audit_put_record(ADT_SUCCESS, |
| ADT_SUCCESS, msg); |
| free(msg); |
| } |
| |
| /* |
| * "init" is finished with all actions for |
| * the current wakeup. |
| */ |
| ret = poll(poll_fds, poll_nfds, |
| SLEEPTIME * MILLISEC); |
| pausecnt++; |
| if (ret > 0) |
| contract_event(&poll_fds[0]); |
| else if (ret < 0 && errno != EINTR) |
| console(B_TRUE, "poll() error: %s\n", |
| strerror(errno)); |
| } |
| |
| if (wakeup.w_flags.w_usersignal) { |
| /* |
| * Install the new level. This could be a real |
| * change in levels or a telinit [Q|a|b|c] or |
| * just a telinit to the same level at which |
| * we are running. |
| */ |
| if (new_state != cur_state) { |
| if (new_state == LVLa || |
| new_state == LVLb || |
| new_state == LVLc) { |
| prev_state = prior_state; |
| prior_state = cur_state; |
| cur_state = new_state; |
| } else { |
| prev_state = cur_state; |
| if (cur_state >= 0) |
| prior_state = cur_state; |
| cur_state = new_state; |
| chg_lvl_flag = TRUE; |
| } |
| } |
| |
| new_state = 0; |
| } |
| |
| if (wakeup.w_flags.w_powerhit) |
| op_modes = PF_MODES; |
| |
| /* |
| * Clear all wakeup reasons. |
| */ |
| wakeup.w_mask = 0; |
| } |
| } |
| |
| /*NOTREACHED*/ |
| } |
| |
| static void |
| update_boot_archive(int new_state) |
| { |
| if (new_state != LVL0 && new_state != LVL5 && new_state != LVL6) |
| return; |
| |
| if (getzoneid() != GLOBAL_ZONEID) |
| return; |
| |
| (void) system("/sbin/bootadm -ea update_all"); |
| } |
| |
| /* |
| * void enter_maintenance() |
| * A simple invocation of sulogin(1M), with no baggage, in the case that we |
| * are unable to activate svc.startd(1M). We fork; the child runs sulogin; |
| * we wait for it to exit. |
| */ |
| static void |
| enter_maintenance() |
| { |
| struct PROC_TABLE *su_process; |
| |
| console(B_FALSE, "Requesting maintenance mode\n" |
| "(See /lib/svc/share/README for additional information.)\n"); |
| (void) sighold(SIGCLD); |
| while ((su_process = efork(M_OFF, NULLPROC, NOCLEANUP)) == NO_ROOM) |
| (void) pause(); |
| (void) sigrelse(SIGCLD); |
| if (su_process == NULLPROC) { |
| int fd; |
| |
| (void) fclose(stdin); |
| (void) fclose(stdout); |
| (void) fclose(stderr); |
| closefrom(0); |
| |
| fd = open(SYSCON, O_RDWR | O_NOCTTY); |
| if (fd >= 0) { |
| (void) dup2(fd, 1); |
| (void) dup2(fd, 2); |
| } else { |
| /* |
| * Need to issue an error message somewhere. |
| */ |
| syslog(LOG_CRIT, "init[%d]: cannot open %s; %s\n", |
| getpid(), SYSCON, strerror(errno)); |
| } |
| |
| /* |
| * Execute the "su" program. |
| */ |
| (void) execle(SU, SU, "-", (char *)0, glob_envp); |
| console(B_TRUE, "execle of %s failed: %s\n", SU, |
| strerror(errno)); |
| timer(5); |
| exit(1); |
| } |
| |
| /* |
| * If we are the parent, wait around for the child to die |
| * or for "init" to be signaled to change levels. |
| */ |
| while (waitproc(su_process) == FAILURE) { |
| /* |
| * All other reasons for waking are ignored when in |
| * single-user mode. The only child we are interested |
| * in is being waited for explicitly by waitproc(). |
| */ |
| wakeup.w_mask = 0; |
| } |
| } |
| |
| /* |
| * remv() scans through "proc_table" and performs cleanup. If |
| * there is a process in the table, which shouldn't be here at |
| * the current run level, then remv() kills the process. |
| */ |
| static void |
| remv() |
| { |
| struct PROC_TABLE *process; |
| struct CMD_LINE cmd; |
| char cmd_string[MAXCMDL]; |
| int change_level; |
| |
| change_level = (cur_state != prev_state ? TRUE : FALSE); |
| |
| /* |
| * Clear the TOUCHED flag on all entries so that when we have |
| * finished scanning inittab, we will be able to tell if we |
| * have any processes for which there is no entry in inittab. |
| */ |
| for (process = proc_table; |
| (process < proc_table + num_proc); process++) { |
| process->p_flags &= ~TOUCHED; |
| } |
| |
| /* |
| * Scan all inittab entries. |
| */ |
| while (getcmd(&cmd, &cmd_string[0]) == TRUE) { |
| /* Scan for process which goes with this entry in inittab. */ |
| for (process = proc_table; |
| (process < proc_table + num_proc); process++) { |
| if ((process->p_flags & OCCUPIED) == 0 || |
| !id_eq(process->p_id, cmd.c_id)) |
| continue; |
| |
| /* |
| * This slot contains the process we are looking for. |
| */ |
| |
| /* |
| * Is the cur_state SINGLE_USER or is this process |
| * marked as "off" or was this proc started by some |
| * mechanism other than LVL{a|b|c} and the current level |
| * does not support this process? |
| */ |
| if (cur_state == SINGLE_USER || |
| cmd.c_action == M_OFF || |
| ((cmd.c_levels & state_to_mask(cur_state)) == 0 && |
| (process->p_flags & DEMANDREQUEST) == 0)) { |
| if (process->p_flags & LIVING) { |
| /* |
| * Touch this entry so we know we have |
| * treated it. Note that procs which |
| * are already dead at this point and |
| * should not be restarted are left |
| * untouched. This causes their slot to |
| * be freed later after dead accounting |
| * is done. |
| */ |
| process->p_flags |= TOUCHED; |
| |
| if ((process->p_flags & KILLED) == 0) { |
| if (change_level) { |
| process->p_flags |
| |= WARNED; |
| (void) kill( |
| process->p_pid, |
| SIGTERM); |
| } else { |
| /* |
| * Fork a killing proc |
| * so "init" can |
| * continue without |
| * having to pause for |
| * TWARN seconds. |
| */ |
| killproc( |
| process->p_pid); |
| } |
| process->p_flags |= KILLED; |
| } |
| } |
| } else { |
| /* |
| * Process can exist at current level. If it is |
| * still alive or a DEMANDREQUEST we touch it so |
| * it will be left alone. Otherwise we leave it |
| * untouched so it will be accounted for and |
| * cleaned up later in remv(). Dead |
| * DEMANDREQUESTs will be accounted but not |
| * freed. |
| */ |
| if (process->p_flags & |
| (LIVING|NOCLEANUP|DEMANDREQUEST)) |
| process->p_flags |= TOUCHED; |
| } |
| |
| break; |
| } |
| } |
| |
| st_write(); |
| |
| /* |
| * If this was a change of levels call, scan through the |
| * process table for processes that were warned to die. If any |
| * are found that haven't left yet, sleep for TWARN seconds and |
| * then send final terminations to any that haven't died yet. |
| */ |
| if (change_level) { |
| |
| /* |
| * Set the alarm for TWARN seconds on the assumption |
| * that there will be some that need to be waited for. |
| * This won't harm anything except we are guaranteed to |
| * wakeup in TWARN seconds whether we need to or not. |
| */ |
| setimer(TWARN); |
| |
| /* |
| * Scan for processes which should be dying. We hope they |
| * will die without having to be sent a SIGKILL signal. |
| */ |
| for (process = proc_table; |
| (process < proc_table + num_proc); process++) { |
| /* |
| * If this process should die, hasn't yet, and the |
| * TWARN time hasn't expired yet, wait for process |
| * to die or for timer to expire. |
| */ |
| while (time_up == FALSE && |
| (process->p_flags & (WARNED|LIVING|OCCUPIED)) == |
| (WARNED|LIVING|OCCUPIED)) |
| (void) pause(); |
| |
| if (time_up == TRUE) |
| break; |
| } |
| |
| /* |
| * If we reached the end of the table without the timer |
| * expiring, then there are no procs which will have to be |
| * sent the SIGKILL signal. If the timer has expired, then |
| * it is necessary to scan the table again and send signals |
| * to all processes which aren't going away nicely. |
| */ |
| if (time_up == TRUE) { |
| for (process = proc_table; |
| (process < proc_table + num_proc); process++) { |
| if ((process->p_flags & |
| (WARNED|LIVING|OCCUPIED)) == |
| (WARNED|LIVING|OCCUPIED)) |
| (void) kill(process->p_pid, SIGKILL); |
| } |
| } |
| setimer(0); |
| } |
| |
| /* |
| * Rescan the proc_table for two kinds of entry, those marked LIVING, |
| * NAMED, which don't have an entry in inittab (haven't been TOUCHED |
| * by the above scanning), and haven't been sent kill signals, and |
| * those entries marked not LIVING, NAMED. The former procs are killed. |
| * The latter have DEAD_PROCESS accounting done and the slot cleared. |
| */ |
| for (process = proc_table; |
| (process < proc_table + num_proc); process++) { |
| if ((process->p_flags & (LIVING|NAMED|TOUCHED|KILLED|OCCUPIED)) |
| == (LIVING|NAMED|OCCUPIED)) { |
| killproc(process->p_pid); |
| process->p_flags |= KILLED; |
| } else if ((process->p_flags & (LIVING|NAMED|OCCUPIED)) == |
| (NAMED|OCCUPIED)) { |
| (void) account(DEAD_PROCESS, process, NULL); |
| /* |
| * If this named proc hasn't been TOUCHED, then free the |
| * space. It has either died of it's own accord, but |
| * isn't respawnable or it was killed because it |
| * shouldn't exist at this level. |
| */ |
| if ((process->p_flags & TOUCHED) == 0) |
| process->p_flags = 0; |
| } |
| } |
| |
| st_write(); |
| } |
| |
| /* |
| * Extract the svc.startd command line and whether to restart it from its |
| * inittab entry. |
| */ |
| /*ARGSUSED*/ |
| static void |
| process_startd_line(struct CMD_LINE *cmd, char *cmd_string) |
| { |
| size_t sz; |
| |
| /* Save the command line. */ |
| if (sflg || rflg) { |
| /* Also append -r or -s. */ |
| (void) strlcpy(startd_cline, cmd_string, sizeof (startd_cline)); |
| (void) strlcat(startd_cline, " -", sizeof (startd_cline)); |
| if (sflg) |
| sz = strlcat(startd_cline, "s", sizeof (startd_cline)); |
| if (rflg) |
| sz = strlcat(startd_cline, "r", sizeof (startd_cline)); |
| } else { |
| sz = strlcpy(startd_cline, cmd_string, sizeof (startd_cline)); |
| } |
| |
| if (sz >= sizeof (startd_cline)) { |
| console(B_TRUE, |
| "svc.startd command line too long. Ignoring.\n"); |
| startd_cline[0] = '\0'; |
| return; |
| } |
| } |
| |
| /* |
| * spawn_processes() scans inittab for entries which should be run at this |
| * mode. Processes which should be running but are not, are started. |
| */ |
| static int |
| spawn_processes() |
| { |
| struct PROC_TABLE *pp; |
| struct CMD_LINE cmd; |
| char cmd_string[MAXCMDL]; |
| short lvl_mask; |
| int status; |
| |
| /* |
| * First check the "powerhit" flag. If it is set, make sure the modes |
| * are PF_MODES and clear the "powerhit" flag. Avoid the possible race |
| * on the "powerhit" flag by disallowing a new powerfail interrupt |
| * between the test of the powerhit flag and the clearing of it. |
| */ |
| if (wakeup.w_flags.w_powerhit) { |
| wakeup.w_flags.w_powerhit = 0; |
| op_modes = PF_MODES; |
| } |
| lvl_mask = state_to_mask(cur_state); |
| |
| /* |
| * Scan through all the entries in inittab. |
| */ |
| while ((status = getcmd(&cmd, &cmd_string[0])) == TRUE) { |
| if (id_eq(cmd.c_id, "smf")) { |
| process_startd_line(&cmd, cmd_string); |
| continue; |
| } |
| |
| retry_for_proc_slot: |
| |
| /* |
| * Find out if there is a process slot for this entry already. |
| */ |
| if ((pp = findpslot(&cmd)) == NULLPROC) { |
| /* |
| * we've run out of proc table entries |
| * increase proc_table. |
| */ |
| increase_proc_table_size(); |
| |
| /* |
| * Retry now as we have an empty proc slot. |
| * In case increase_proc_table_size() fails, |
| * we will keep retrying. |
| */ |
| goto retry_for_proc_slot; |
| } |
| |
| /* |
| * If there is an entry, and it is marked as DEMANDREQUEST, |
| * one of the levels a, b, or c is in its levels mask, and |
| * the action field is ONDEMAND and ONDEMAND is a permissable |
| * mode, and the process is dead, then respawn it. |
| */ |
| if (((pp->p_flags & (LIVING|DEMANDREQUEST)) == DEMANDREQUEST) && |
| (cmd.c_levels & MASK_abc) && |
| (cmd.c_action & op_modes) == M_ONDEMAND) { |
| spawn(pp, &cmd); |
| continue; |
| } |
| |
| /* |
| * If the action is not an action we are interested in, |
| * skip the entry. |
| */ |
| if ((cmd.c_action & op_modes) == 0 || pp->p_flags & LIVING || |
| (cmd.c_levels & lvl_mask) == 0) |
| continue; |
| |
| /* |
| * If the modes are the normal modes (ONCE, WAIT, RESPAWN, OFF, |
| * ONDEMAND) and the action field is either OFF or the action |
| * field is ONCE or WAIT and the current level is the same as |
| * the last level, then skip this entry. ONCE and WAIT only |
| * get run when the level changes. |
| */ |
| if (op_modes == NORMAL_MODES && |
| (cmd.c_action == M_OFF || |
| (cmd.c_action & (M_ONCE|M_WAIT)) && |
| cur_state == prev_state)) |
| continue; |
| |
| /* |
| * At this point we are interested in performing the action for |
| * this entry. Actions fall into two categories, spinning off |
| * a process and not waiting, and spinning off a process and |
| * waiting for it to die. If the action is ONCE, RESPAWN, |
| * ONDEMAND, POWERFAIL, or BOOT we don't wait for the process |
| * to die, for all other actions we do wait. |
| */ |
| if (cmd.c_action & (M_ONCE | M_RESPAWN | M_PF | M_BOOT)) { |
| spawn(pp, &cmd); |
| |
| } else { |
| spawn(pp, &cmd); |
| while (waitproc(pp) == FAILURE) |
| ; |
| (void) account(DEAD_PROCESS, pp, NULL); |
| pp->p_flags = 0; |
| } |
| } |
| return (status); |
| } |
| |
| /* |
| * spawn() spawns a shell, inserts the information about the process |
| * process into the proc_table, and does the startup accounting. |
| */ |
| static void |
| spawn(struct PROC_TABLE *process, struct CMD_LINE *cmd) |
| { |
| int i; |
| int modes, maxfiles; |
| time_t now; |
| struct PROC_TABLE tmproc, *oprocess; |
| |
| /* |
| * The modes to be sent to efork() are 0 unless we are |
| * spawning a LVLa, LVLb, or LVLc entry or we will be |
| * waiting for the death of the child before continuing. |
| */ |
| modes = NAMED; |
| if (process->p_flags & DEMANDREQUEST || cur_state == LVLa || |
| cur_state == LVLb || cur_state == LVLc) |
| modes |= DEMANDREQUEST; |
| if ((cmd->c_action & (M_SYSINIT | M_WAIT | M_BOOTWAIT | M_PWAIT)) != 0) |
| modes |= NOCLEANUP; |
| |
| /* |
| * If this is a respawnable process, check the threshold |
| * information to avoid excessive respawns. |
| */ |
| if (cmd->c_action & M_RESPAWN) { |
| /* |
| * Add NOCLEANUP to all respawnable commands so that the |
| * information about the frequency of respawns isn't lost. |
| */ |
| modes |= NOCLEANUP; |
| (void) time(&now); |
| |
| /* |
| * If no time is assigned, then this is the first time |
| * this command is being processed in this series. Assign |
| * the current time. |
| */ |
| if (process->p_time == 0L) |
| process->p_time = now; |
| |
| if (process->p_count++ == SPAWN_LIMIT) { |
| |
| if ((now - process->p_time) < SPAWN_INTERVAL) { |
| /* |
| * Process is respawning too rapidly. Print |
| * message and refuse to respawn it for now. |
| */ |
| console(B_TRUE, "Command is respawning too " |
| "rapidly. Check for possible errors.\n" |
| "id:%4s \"%s\"\n", |
| &cmd->c_id[0], &cmd->c_command[EXEC]); |
| return; |
| } |
| process->p_time = now; |
| process->p_count = 0; |
| |
| } else if (process->p_count > SPAWN_LIMIT) { |
| /* |
| * If process has been respawning too rapidly and |
| * the inhibit time limit hasn't expired yet, we |
| * refuse to respawn. |
| */ |
| if (now - process->p_time < SPAWN_INTERVAL + INHIBIT) |
| return; |
| process->p_time = now; |
| process->p_count = 0; |
| } |
| rsflag = TRUE; |
| } |
| |
| /* |
| * Spawn a child process to execute this command. |
| */ |
| (void) sighold(SIGCLD); |
| oprocess = process; |
| while ((process = efork(cmd->c_action, oprocess, modes)) == NO_ROOM) |
| (void) pause(); |
| |
| if (process == NULLPROC) { |
| |
| /* |
| * We are the child. We must make sure we get a different |
| * file pointer for our references to utmpx. Otherwise our |
| * seeks and reads will compete with those of the parent. |
| */ |
| endutxent(); |
| |
| /* |
| * Perform the accounting for the beginning of a process. |
| * Note that all processes are initially "INIT_PROCESS"es. |
| */ |
| tmproc.p_id[0] = cmd->c_id[0]; |
| tmproc.p_id[1] = cmd->c_id[1]; |
| tmproc.p_id[2] = cmd->c_id[2]; |
| tmproc.p_id[3] = cmd->c_id[3]; |
| tmproc.p_pid = getpid(); |
| tmproc.p_exit = 0; |
| (void) account(INIT_PROCESS, &tmproc, |
| prog_name(&cmd->c_command[EXEC])); |
| maxfiles = ulimit(UL_GDESLIM, 0); |
| for (i = 0; i < maxfiles; i++) |
| (void) fcntl(i, F_SETFD, FD_CLOEXEC); |
| |
| /* |
| * Now exec a shell with the -c option and the command |
| * from inittab. |
| */ |
| (void) execle(SH, "INITSH", "-c", cmd->c_command, (char *)0, |
| glob_envp); |
| console(B_TRUE, "Command\n\"%s\"\n failed to execute. errno " |
| "= %d (exec of shell failed)\n", cmd->c_command, errno); |
| |
| /* |
| * Don't come back so quickly that "init" doesn't have a |
| * chance to finish putting this child in "proc_table". |
| */ |
| timer(20); |
| exit(1); |
| |
| } |
| |
| /* |
| * We are the parent. Insert the necessary |
| * information in the proc_table. |
| */ |
| process->p_id[0] = cmd->c_id[0]; |
| process->p_id[1] = cmd->c_id[1]; |
| process->p_id[2] = cmd->c_id[2]; |
| process->p_id[3] = cmd->c_id[3]; |
| |
| st_write(); |
| |
| (void) sigrelse(SIGCLD); |
| } |
| |
| /* |
| * findpslot() finds the old slot in the process table for the |
| * command with the same id, or it finds an empty slot. |
| */ |
| static struct PROC_TABLE * |
| findpslot(struct CMD_LINE *cmd) |
| { |
| struct PROC_TABLE *process; |
| struct PROC_TABLE *empty = NULLPROC; |
| |
| for (process = proc_table; |
| (process < proc_table + num_proc); process++) { |
| if (process->p_flags & OCCUPIED && |
| id_eq(process->p_id, cmd->c_id)) |
| break; |
| |
| /* |
| * If the entry is totally empty and "empty" is still 0, |
| * remember where this hole is and make sure the slot is |
| * zeroed out. |
| */ |
| if (empty == NULLPROC && (process->p_flags & OCCUPIED) == 0) { |
| empty = process; |
| process->p_id[0] = '\0'; |
| process->p_id[1] = '\0'; |
| process->p_id[2] = '\0'; |
| process->p_id[3] = '\0'; |
| process->p_pid = 0; |
| process->p_time = 0L; |
| process->p_count = 0; |
| process->p_flags = 0; |
| process->p_exit = 0; |
| } |
| } |
| |
| /* |
| * If there is no entry for this slot, then there should be an |
| * empty slot. If there is no empty slot, then we've run out |
| * of proc_table space. If the latter is true, empty will be |
| * NULL and the caller will have to complain. |
| */ |
| if (process == (proc_table + num_proc)) |
| process = empty; |
| |
| return (process); |
| } |
| |
| /* |
| * getcmd() parses lines from inittab. Each time it finds a command line |
| * it will return TRUE as well as fill the passed CMD_LINE structure and |
| * the shell command string. When the end of inittab is reached, FALSE |
| * is returned inittab is automatically opened if it is not currently open |
| * and is closed when the end of the file is reached. |
| */ |
| static FILE *fp_inittab = NULL; |
| |
| static int |
| getcmd(struct CMD_LINE *cmd, char *shcmd) |
| { |
| char *ptr; |
| int c, lastc, state; |
| char *ptr1; |
| int answer, i, proceed; |
| struct stat sbuf; |
| static char *actions[] = { |
| "off", "respawn", "ondemand", "once", "wait", "boot", |
| "bootwait", "powerfail", "powerwait", "initdefault", |
| "sysinit", |
| }; |
| static short act_masks[] = { |
| M_OFF, M_RESPAWN, M_ONDEMAND, M_ONCE, M_WAIT, M_BOOT, |
| M_BOOTWAIT, M_PF, M_PWAIT, M_INITDEFAULT, M_SYSINIT, |
| }; |
| /* |
| * Only these actions will be allowed for entries which |
| * are specified for single-user mode. |
| */ |
| short su_acts = M_INITDEFAULT | M_PF | M_PWAIT | M_WAIT; |
| |
| if (fp_inittab == NULL) { |
| /* |
| * Before attempting to open inittab we stat it to make |
| * sure it currently exists and is not empty. We try |
| * several times because someone may have temporarily |
| * unlinked or truncated the file. |
| */ |
| for (i = 0; i < 3; i++) { |
| if (stat(INITTAB, &sbuf) == -1) { |
| if (i == 2) { |
| console(B_TRUE, |
| "Cannot stat %s, errno: %d\n", |
| INITTAB, errno); |
| return (FAILURE); |
| } else { |
| timer(3); |
| } |
| } else if (sbuf.st_size < 10) { |
| if (i == 2) { |
| console(B_TRUE, |
| "%s truncated or corrupted\n", |
| INITTAB); |
| return (FAILURE); |
| } else { |
| timer(3); |
| } |
| } else { |
| break; |
| } |
| } |
| |
| /* |
| * If unable to open inittab, print error message and |
| * return FAILURE to caller. |
| */ |
| if ((fp_inittab = fopen(INITTAB, "r")) == NULL) { |
| console(B_TRUE, "Cannot open %s errno: %d\n", INITTAB, |
| errno); |
| return (FAILURE); |
| } |
| } |
| |
| /* |
| * Keep getting commands from inittab until you find a |
| * good one or run out of file. |
| */ |
| for (answer = FALSE; answer == FALSE; ) { |
| /* |
| * Zero out the cmd itself before trying next line. |
| */ |
| bzero(cmd, sizeof (struct CMD_LINE)); |
| |
| /* |
| * Read in lines of inittab, parsing at colons, until a line is |
| * read in which doesn't end with a backslash. Do not start if |
| * the first character read is an EOF. Note that this means |
| * that lines which don't end in a newline are still processed, |
| * since the "for" will terminate normally once started, |
| * regardless of whether line terminates with a newline or EOF. |
| */ |
| state = FAILURE; |
| if ((c = fgetc(fp_inittab)) == EOF) { |
| answer = FALSE; |
| (void) fclose(fp_inittab); |
| fp_inittab = NULL; |
| break; |
| } |
| |
| for (proceed = TRUE, ptr = shcmd, state = ID, lastc = '\0'; |
| proceed && c != EOF; |
| lastc = c, c = fgetc(fp_inittab)) { |
| /* If we're not in the FAILURE state and haven't */ |
| /* yet reached the shell command field, process */ |
| /* the line, otherwise just look for a real end */ |
| /* of line. */ |
| if (state != FAILURE && state != COMMAND) { |
| /* |
| * Squeeze out spaces and tabs. |
| */ |
| if (c == ' ' || c == '\t') |
| continue; |
| |
| /* |
| * Ignore characters in a comment, except for the \n. |
| */ |
| if (state == COMMENT) { |
| if (c == '\n') { |
| lastc = ' '; |
| break; |
| } else { |
| continue; |
| } |
| } |
| |
| /* |
| * Detect comments (lines whose first non-whitespace |
| * character is '#') by checking that we're at the |
| * beginning of a line, have seen a '#', and haven't |
| * yet accumulated any characters. |
| */ |
| if (state == ID && c == '#' && ptr == shcmd) { |
| state = COMMENT; |
| continue; |
| } |
| |
| /* |
| * If the character is a ':', then check the |
| * previous field for correctness and advance |
| * to the next field. |
| */ |
| if (c == ':') { |
| switch (state) { |
| |
| case ID : |
| /* |
| * Check to see that there are only |
| * 1 to 4 characters for the id. |
| */ |
| if ((i = ptr - shcmd) < 1 || i > 4) { |
| state = FAILURE; |
| } else { |
| bcopy(shcmd, &cmd->c_id[0], i); |
| ptr = shcmd; |
| state = LEVELS; |
| } |
| break; |
| |
| case LEVELS : |
| /* |
| * Build a mask for all the levels for |
| * which this command will be legal. |
| */ |
| for (cmd->c_levels = 0, ptr1 = shcmd; |
| ptr1 < ptr; ptr1++) { |
| int mask; |
| if (lvlname_to_mask(*ptr1, |
| &mask) == -1) { |
| state = FAILURE; |
| break; |
| } |
| cmd->c_levels |= mask; |
| } |
| if (state != FAILURE) { |
| state = ACTION; |
| ptr = shcmd; /* Reset the buffer */ |
| } |
| break; |
| |
| case ACTION : |
| /* |
| * Null terminate the string in shcmd buffer and |
| * then try to match against legal actions. If |
| * the field is of length 0, then the default of |
| * "RESPAWN" is used if the id is numeric, |
| * otherwise the default is "OFF". |
| */ |
| if (ptr == shcmd) { |
| if (isdigit(cmd->c_id[0]) && |
| (cmd->c_id[1] == '\0' || |
| isdigit(cmd->c_id[1])) && |
| (cmd->c_id[2] == '\0' || |
| isdigit(cmd->c_id[2])) && |
| (cmd->c_id[3] == '\0' || |
| isdigit(cmd->c_id[3]))) |
| cmd->c_action = M_RESPAWN; |
| else |
| cmd->c_action = M_OFF; |
| } else { |
| for (cmd->c_action = 0, i = 0, |
| *ptr = '\0'; |
| i < |
| sizeof (actions)/sizeof (char *); |
| i++) { |
| if (strcmp(shcmd, actions[i]) == 0) { |
| if ((cmd->c_levels & MASKSU) && |
| !(act_masks[i] & su_acts)) |
| cmd->c_action = 0; |
| else |
| cmd->c_action = |
| act_masks[i]; |
| break; |
| } |
| } |
| } |
| |
| /* |
| * If the action didn't match any legal action, |
| * set state to FAILURE. |
| */ |
| if (cmd->c_action == 0) { |
| state = FAILURE; |
| } else { |
| state = COMMAND; |
| (void) strcpy(shcmd, "exec "); |
| } |
| ptr = shcmd + EXEC; |
| break; |
| } |
| continue; |
| } |
| } |
| |
| /* If the character is a '\n', then this is the end of a */ |
| /* line. If the '\n' wasn't preceded by a backslash, */ |
| /* it is also the end of an inittab command. If it was */ |
| /* preceded by a backslash then the next line is a */ |
| /* continuation. Note that the continuation '\n' falls */ |
| /* through and is treated like other characters and is */ |
| /* stored in the shell command line. */ |
| if (c == '\n' && lastc != '\\') { |
| proceed = FALSE; |
| *ptr = '\0'; |
| break; |
| } |
| |
| /* For all other characters just stuff them into the */ |
| /* command as long as there aren't too many of them. */ |
| /* Make sure there is room for a terminating '\0' also. */ |
| if (ptr >= shcmd + MAXCMDL - 1) |
| state = FAILURE; |
| else |
| *ptr++ = (char)c; |
| |
| /* If the character we just stored was a quoted */ |
| /* backslash, then change "c" to '\0', so that this */ |
| /* backslash will not cause a subsequent '\n' to appear */ |
| /* quoted. In otherwords '\' '\' '\n' is the real end */ |
| /* of a command, while '\' '\n' is a continuation. */ |
| if (c == '\\' && lastc == '\\') |
| c = '\0'; |
| } |
| |
| /* |
| * Make sure all the fields are properly specified |
| * for a good command line. |
| */ |
| if (state == COMMAND) { |
| answer = TRUE; |
| cmd->c_command = shcmd; |
| |
| /* |
| * If no default level was supplied, insert |
| * all numerical levels. |
| */ |
| if (cmd->c_levels == 0) |
| cmd->c_levels = MASK_NUMERIC; |
| |
| /* |
| * If no action has been supplied, declare this |
| * entry to be OFF. |
| */ |
| if (cmd->c_action == 0) |
| cmd->c_action = M_OFF; |
| |
| /* |
| * If no shell command has been supplied, make sure |
| * there is a null string in the command field. |
| */ |
| if (ptr == shcmd + EXEC) |
| *shcmd = '\0'; |
| } else |
| answer = FALSE; |
| |
| /* |
| * If we have reached the end of inittab, then close it |
| * and quit trying to find a good command line. |
| */ |
| if (c == EOF) { |
| (void) fclose(fp_inittab); |
| fp_inittab = NULL; |
| break; |
| } |
| } |
| return (answer); |
| } |
| |
| /* |
| * lvlname_to_state(): convert the character name of a state to its level |
| * (its corresponding signal number). |
| */ |
| static int |
| lvlname_to_state(char name) |
| { |
| int i; |
| for (i = 0; i < LVL_NELEMS; i++) { |
| if (lvls[i].lvl_name == name) |
| return (lvls[i].lvl_state); |
| } |
| return (-1); |
| } |
| |
| /* |
| * state_to_name(): convert the level to the character name. |
| */ |
| static char |
| state_to_name(int state) |
| { |
| int i; |
| for (i = 0; i < LVL_NELEMS; i++) { |
| if (lvls[i].lvl_state == state) |
| return (lvls[i].lvl_name); |
| } |
| return (-1); |
| } |
| |
| /* |
| * state_to_mask(): return the mask corresponding to a signal number |
| */ |
| static int |
| state_to_mask(int state) |
| { |
| int i; |
| for (i = 0; i < LVL_NELEMS; i++) { |
| if (lvls[i].lvl_state == state) |
| return (lvls[i].lvl_mask); |
| } |
| return (0); /* return 0, since that represents an empty mask */ |
| } |
| |
| /* |
| * lvlname_to_mask(): return the mask corresponding to a levels character name |
| */ |
| static int |
| lvlname_to_mask(char name, int *mask) |
| { |
| int i; |
| for (i = 0; i < LVL_NELEMS; i++) { |
| if (lvls[i].lvl_name == name) { |
| *mask = lvls[i].lvl_mask; |
| return (0); |
| } |
| } |
| return (-1); |
| } |
| |
| /* |
| * state_to_flags(): return the flags corresponding to a runlevel. These |
| * indicate properties of that runlevel. |
| */ |
| static int |
| state_to_flags(int state) |
| { |
| int i; |
| for (i = 0; i < LVL_NELEMS; i++) { |
| if (lvls[i].lvl_state == state) |
| return (lvls[i].lvl_flags); |
| } |
| return (0); |
| } |
| |
| /* |
| * killproc() creates a child which kills the process specified by pid. |
| */ |
| void |
| killproc(pid_t pid) |
| { |
| struct PROC_TABLE *process; |
| |
| (void) sighold(SIGCLD); |
| while ((process = efork(M_OFF, NULLPROC, 0)) == NO_ROOM) |
| (void) pause(); |
| (void) sigrelse(SIGCLD); |
| |
| if (process == NULLPROC) { |
| /* |
| * efork() sets all signal handlers to the default, so reset |
| * the ALRM handler to make timer() work as expected. |
| */ |
| (void) sigset(SIGALRM, alarmclk); |
| |
| /* |
| * We are the child. Try to terminate the process nicely |
| * first using SIGTERM and if it refuses to die in TWARN |
| * seconds kill it with SIGKILL. |
| */ |
| (void) kill(pid, SIGTERM); |
| (void) timer(TWARN); |
| (void) kill(pid, SIGKILL); |
| (void) exit(0); |
| } |
| } |
| |
| /* |
| * Set up the default environment for all procs to be forked from init. |
| * Read the values from the /etc/default/init file, except for PATH. If |
| * there's not enough room in the environment array, the environment |
| * lines that don't fit are silently discarded. |
| */ |
| void |
| init_env() |
| { |
| char line[MAXCMDL]; |
| FILE *fp; |
| int inquotes, length, wslength; |
| char *tokp, *cp1, *cp2; |
| |
| glob_envp[0] = malloc((unsigned)(strlen(DEF_PATH)+2)); |
| (void) strcpy(glob_envp[0], DEF_PATH); |
| glob_envn = 1; |
| |
| if (rflg) { |
| glob_envp[1] = |
| malloc((unsigned)(strlen("_DVFS_RECONFIG=YES")+2)); |
| (void) strcpy(glob_envp[1], "_DVFS_RECONFIG=YES"); |
| ++glob_envn; |
| } else if (bflg == 1) { |
| glob_envp[1] = |
| malloc((unsigned)(strlen("RB_NOBOOTRC=YES")+2)); |
| (void) strcpy(glob_envp[1], "RB_NOBOOTRC=YES"); |
| ++glob_envn; |
| } |
| |
| if ((fp = fopen(ENVFILE, "r")) == NULL) { |
| console(B_TRUE, |
| "Cannot open %s. Environment not initialized.\n", |
| ENVFILE); |
| } else { |
| while (fgets(line, MAXCMDL - 1, fp) != NULL && |
| glob_envn < MAXENVENT - 2) { |
| /* |
| * Toss newline |
| */ |
| length = strlen(line); |
| if (line[length - 1] == '\n') |
| line[length - 1] = '\0'; |
| |
| /* |
| * Ignore blank or comment lines. |
| */ |
| if (line[0] == '#' || line[0] == '\0' || |
| (wslength = strspn(line, " \t\n")) == |
| strlen(line) || |
| strchr(line, '#') == line + wslength) |
| continue; |
| |
| /* |
| * First make a pass through the line and change |
| * any non-quoted semi-colons to blanks so they |
| * will be treated as token separators below. |
| */ |
| inquotes = 0; |
| for (cp1 = line; *cp1 != '\0'; cp1++) { |
| if (*cp1 == '"') { |
| if (inquotes == 0) |
| inquotes = 1; |
| else |
| inquotes = 0; |
| } else if (*cp1 == ';') { |
| if (inquotes == 0) |
| *cp1 = ' '; |
| } |
| } |
| |
| /* |
| * Tokens within the line are separated by blanks |
| * and tabs. For each token in the line which |
| * contains a '=' we strip out any quotes and then |
| * stick the token in the environment array. |
| */ |
| if ((tokp = strtok(line, " \t")) == NULL) |
| continue; |
| do { |
| if (strchr(tokp, '=') == NULL) |
| continue; |
| length = strlen(tokp); |
| while ((cp1 = strpbrk(tokp, "\"\'")) != NULL) { |
| for (cp2 = cp1; |
| cp2 < &tokp[length]; cp2++) |
| *cp2 = *(cp2 + 1); |
| length--; |
| } |
| |
| if (strncmp(tokp, "CMASK=", |
| sizeof ("CMASK=") - 1) == 0) { |
| long t; |
| |
| /* We know there's an = */ |
| t = strtol(strchr(tokp, '=') + 1, NULL, |
| 8); |
| |
| /* Sanity */ |
| if (t <= 077 && t >= 0) |
| cmask = (int)t; |
| (void) umask(cmask); |
| continue; |
| } |
| glob_envp[glob_envn] = |
| malloc((unsigned)(length + 1)); |
| (void) strcpy(glob_envp[glob_envn], tokp); |
| if (++glob_envn >= MAXENVENT - 1) |
| break; |
| } while ((tokp = strtok(NULL, " \t")) != NULL); |
| } |
| |
| /* |
| * Append a null pointer to the environment array |
| * to mark its end. |
| */ |
| glob_envp[glob_envn] = NULL; |
| (void) fclose(fp); |
| } |
| } |
| |
| /* |
| * boot_init(): Do initialization things that should be done at boot. |
| */ |
| void |
| boot_init() |
| { |
| int i; |
| struct PROC_TABLE *process, *oprocess; |
| struct CMD_LINE cmd; |
| char line[MAXCMDL]; |
| char svc_aux[SVC_AUX_SIZE]; |
| char init_svc_fmri[SVC_FMRI_SIZE]; |
| char *old_path; |
| int maxfiles; |
| |
| /* Use INIT_PATH for sysinit cmds */ |
| old_path = glob_envp[0]; |
| glob_envp[0] = malloc((unsigned)(strlen(INIT_PATH)+2)); |
| (void) strcpy(glob_envp[0], INIT_PATH); |
| |
| /* |
| * Scan inittab(4) and process the special svc.startd entry, initdefault |
| * and sysinit entries. |
| */ |
| while (getcmd(&cmd, &line[0]) == TRUE) { |
| if (startd_tmpl >= 0 && id_eq(cmd.c_id, "smf")) { |
| process_startd_line(&cmd, line); |
| (void) snprintf(startd_svc_aux, SVC_AUX_SIZE, |
| INITTAB_ENTRY_ID_STR_FORMAT, cmd.c_id); |
| } else if (cmd.c_action == M_INITDEFAULT) { |
| /* |
| * initdefault is no longer meaningful, as the SMF |
| * milestone controls what (legacy) run level we |
| * boot to. |
| */ |
| console(B_TRUE, |
| "Ignoring legacy \"initdefault\" entry.\n"); |
| } else if (cmd.c_action == M_SYSINIT) { |
| /* |
| * Execute the "sysinit" entry and wait for it to |
| * complete. No bookkeeping is performed on these |
| * entries because we avoid writing to the file system |
| * until after there has been an chance to check it. |
| */ |
| if (process = findpslot(&cmd)) { |
| (void) sighold(SIGCLD); |
| (void) snprintf(svc_aux, SVC_AUX_SIZE, |
| INITTAB_ENTRY_ID_STR_FORMAT, cmd.c_id); |
| (void) snprintf(init_svc_fmri, SVC_FMRI_SIZE, |
| SVC_INIT_PREFIX INITTAB_ENTRY_ID_STR_FORMAT, |
| cmd.c_id); |
| if (legacy_tmpl >= 0) { |
| (void) ct_pr_tmpl_set_svc_fmri( |
| legacy_tmpl, init_svc_fmri); |
| (void) ct_pr_tmpl_set_svc_aux( |
| legacy_tmpl, svc_aux); |
| } |
| |
| for (oprocess = process; |
| (process = efork(M_OFF, oprocess, |
| (NAMED|NOCLEANUP))) == NO_ROOM; |
| /* CSTYLED */) |
| ; |
| (void) sigrelse(SIGCLD); |
| |
| if (process == NULLPROC) { |
| maxfiles = ulimit(UL_GDESLIM, 0); |
| |
| for (i = 0; i < maxfiles; i++) |
| (void) fcntl(i, F_SETFD, |
| FD_CLOEXEC); |
| (void) execle(SH, "INITSH", "-c", |
| cmd.c_command, |
| (char *)0, glob_envp); |
| console(B_TRUE, |
| "Command\n\"%s\"\n failed to execute. errno = %d (exec of shell failed)\n", |
| cmd.c_command, errno); |
| exit(1); |
| } else |
| while (waitproc(process) == FAILURE) |
| ; |
| process->p_flags = 0; |
| st_write(); |
| } |
| } |
| } |
| |
| /* Restore the path. */ |
| free(glob_envp[0]); |
| glob_envp[0] = old_path; |
| |
| /* |
| * This will enable st_write() to complain about init_state_file. |
| */ |
| booting = 0; |
| |
| /* |
| * If the /etc/ioctl.syscon didn't exist or had invalid contents write |
| * out a correct version. |
| */ |
| if (write_ioctl) |
| write_ioctl_syscon(); |
| |
| /* |
| * Start svc.startd(1M), which does most of the work. |
| */ |
| if (startd_cline[0] != '\0' && startd_tmpl >= 0) { |
| /* Start svc.startd. */ |
| if (startd_run(startd_cline, startd_tmpl, 0) == -1) |
| cur_state = SINGLE_USER; |
| } else { |
| console(B_TRUE, "Absent svc.startd entry or bad " |
| "contract template. Not starting svc.startd.\n"); |
| enter_maintenance(); |
| } |
| } |
| |
| /* |
| * init_signals(): Initialize all signals to either be caught or ignored. |
| */ |
| void |
| init_signals(void) |
| { |
| struct sigaction act; |
| int i; |
| |
| /* |
| * Start by ignoring all signals, then selectively re-enable some. |
| * The SIG_IGN disposition will only affect asynchronous signals: |
| * any signal that we trigger synchronously that doesn't end up |
| * being handled by siglvl() will be forcibly delivered by the kernel. |
| */ |
| for (i = SIGHUP; i <= SIGRTMAX; i++) |
| (void) sigset(i, SIG_IGN); |
| |
| /* |
| * Handle all level-changing signals using siglvl() and set sa_mask so |
| * that all level-changing signals are blocked while in siglvl(). |
| */ |
| act.sa_handler = siglvl; |
| act.sa_flags = SA_SIGINFO; |
| (void) sigemptyset(&act.sa_mask); |
| |
| (void) sigaddset(&act.sa_mask, LVLQ); |
| (void) sigaddset(&act.sa_mask, LVL0); |
| (void) sigaddset(&act.sa_mask, LVL1); |
| (void) sigaddset(&act.sa_mask, LVL2); |
| (void) sigaddset(&act.sa_mask, LVL3); |
| (void) sigaddset(&act.sa_mask, LVL4); |
| (void) sigaddset(&act.sa_mask, LVL5); |
| (void) sigaddset(&act.sa_mask, LVL6); |
| (void) sigaddset(&act.sa_mask, SINGLE_USER); |
| (void) sigaddset(&act.sa_mask, LVLa); |
| (void) sigaddset(&act.sa_mask, LVLb); |
| (void) sigaddset(&act.sa_mask, LVLc); |
| |
| (void) sigaction(LVLQ, &act, NULL); |
| (void) sigaction(LVL0, &act, NULL); |
| (void) sigaction(LVL1, &act, NULL); |
| (void) sigaction(LVL2, &act, NULL); |
| (void) sigaction(LVL3, &act, NULL); |
| (void) sigaction(LVL4, &act, NULL); |
| (void) sigaction(LVL5, &act, NULL); |
| (void) sigaction(LVL6, &act, NULL); |
| (void) sigaction(SINGLE_USER, &act, NULL); |
| (void) sigaction(LVLa, &act, NULL); |
| (void) sigaction(LVLb, &act, NULL); |
| (void) sigaction(LVLc, &act, NULL); |
| |
| (void) sigset(SIGALRM, alarmclk); |
| alarmclk(); |
| |
| (void) sigset(SIGCLD, childeath); |
| (void) sigset(SIGPWR, powerfail); |
| } |
| |
| /* |
| * Set up pipe for "godchildren". If the file exists and is a pipe just open |
| * it. Else, if the file system is r/w create it. Otherwise, defer its |
| * creation and open until after /var/run has been mounted. This function is |
| * only called on startup and when explicitly requested via LVLQ. |
| */ |
| void |
| setup_pipe() |
| { |
| struct stat stat_buf; |
| struct statvfs statvfs_buf; |
| struct sigaction act; |
| |
| /* |
| * Always close the previous pipe descriptor as the mounted filesystems |
| * may have changed. |
| */ |
| if (Pfd >= 0) |
| (void) close(Pfd); |
| |
| if ((stat(INITPIPE, &stat_buf) == 0) && |
| ((stat_buf.st_mode & (S_IFMT|S_IRUSR)) == (S_IFIFO|S_IRUSR))) |
| Pfd = open(INITPIPE, O_RDWR | O_NDELAY); |
| else |
| if ((statvfs(INITPIPE_DIR, &statvfs_buf) == 0) && |
| ((statvfs_buf.f_flag & ST_RDONLY) == 0)) { |
| (void) unlink(INITPIPE); |
| (void) mknod(INITPIPE, S_IFIFO | 0600, 0); |
| Pfd = open(INITPIPE, O_RDWR | O_NDELAY); |
| } |
| |
| if (Pfd >= 0) { |
| (void) ioctl(Pfd, I_SETSIG, S_INPUT); |
| /* |
| * Read pipe in message discard mode. |
| */ |
| (void) ioctl(Pfd, I_SRDOPT, RMSGD); |
| |
| act.sa_handler = sigpoll; |
| act.sa_flags = 0; |
| (void) sigemptyset(&act.sa_mask); |
| (void) sigaddset(&act.sa_mask, SIGCLD); |
| (void) sigaction(SIGPOLL, &act, NULL); |
| } |
| } |
| |
| /* |
| * siglvl - handle an asynchronous signal from init(1M) telling us that we |
| * should change the current run level. We set new_state accordingly. |
| */ |
| void |
| siglvl(int sig, siginfo_t *sip, ucontext_t *ucp) |
| { |
| struct PROC_TABLE *process; |
| struct sigaction act; |
| |
| /* |
| * If the signal was from the kernel (rather than init(1M)) then init |
| * itself tripped the signal. That is, we might have a bug and tripped |
| * a real SIGSEGV instead of receiving it as an alias for SIGLVLa. In |
| * such a case we reset the disposition to SIG_DFL, block all signals |
| * in uc_mask but the current one, and return to the interrupted ucp |
| * to effect an appropriate death. The kernel will then restart us. |
| * |
| * The one exception to SI_FROMKERNEL() is SIGFPE (a.k.a. LVL6), which |
| * the kernel can send us when it wants to effect an orderly reboot. |
| * For this case we must also verify si_code is zero, rather than a |
| * code such as FPE_INTDIV which a bug might have triggered. |
| */ |
| if (sip != NULL && SI_FROMKERNEL(sip) && |
| (sig != SIGFPE || sip->si_code == 0)) { |
| |
| (void) sigemptyset(&act.sa_mask); |
| act.sa_handler = SIG_DFL; |
| act.sa_flags = 0; |
| (void) sigaction(sig, &act, NULL); |
| |
| (void) sigfillset(&ucp->uc_sigmask); |
| (void) sigdelset(&ucp->uc_sigmask, sig); |
| ucp->uc_flags |= UC_SIGMASK; |
| |
| (void) setcontext(ucp); |
| } |
| |
| /* |
| * If the signal received is a LVLQ signal, do not really |
| * change levels, just restate the current level. If the |
| * signal is not a LVLQ, set the new level to the signal |
| * received. |
| */ |
| if (sig == LVLQ) { |
| new_state = cur_state; |
| lvlq_received = B_TRUE; |
| } else { |
| new_state = sig; |
| } |
| |
| /* |
| * Clear all times and repeat counts in the process table |
| * since either the level is changing or the user has editted |
| * the inittab file and wants us to look at it again. |
| * If the user has fixed a typo, we don't want residual timing |
| * data preventing the fixed command line from executing. |
| */ |
| for (process = proc_table; |
| (process < proc_table + num_proc); process++) { |
| process->p_time = 0L; |
| process->p_count = 0; |
| } |
| |
| /* |
| * Set the flag to indicate that a "user signal" was received. |
| */ |
| wakeup.w_flags.w_usersignal = 1; |
| } |
| |
| |
| /* |
| * alarmclk |
| */ |
| static void |
| alarmclk() |
| { |
| time_up = TRUE; |
| } |
| |
| /* |
| * childeath_single(): |
| * |
| * This used to be the SIGCLD handler and it was set with signal() |
| * (as opposed to sigset()). When a child exited we'd come to the |
| * handler, wait for the child, and reenable the handler with |
| * signal() just before returning. The implementation of signal() |
| * checks with waitid() for waitable children and sends a SIGCLD |
| * if there are some. If children are exiting faster than the |
| * handler can run we keep sending signals and the handler never |
| * gets to return and eventually the stack runs out and init dies. |
| * To prevent that we set the handler with sigset() so the handler |
| * doesn't need to be reset, and in childeath() (see below) we |
| * call childeath_single() as long as there are children to be |
| * waited for. If a child exits while init is in the handler a |
| * SIGCLD will be pending and delivered on return from the handler. |
| * If the child was already waited for the handler will have nothing |
| * to do and return, otherwise the child will be waited for. |
| */ |
| static void |
| childeath_single(pid_t pid, int status) |
| { |
| struct PROC_TABLE *process; |
| struct pidlist *pp; |
| |
| /* |
| * Scan the process table to see if we are interested in this process. |
| */ |
| for (process = proc_table; |
| (process < proc_table + num_proc); process++) { |
| if ((process->p_flags & (LIVING|OCCUPIED)) == |
| (LIVING|OCCUPIED) && process->p_pid == pid) { |
| |
| /* |
| * Mark this process as having died and store the exit |
| * status. Also set the wakeup flag for a dead child |
| * and break out of the loop. |
| */ |
| process->p_flags &= ~LIVING; |
| process->p_exit = (short)status; |
| wakeup.w_flags.w_childdeath = 1; |
| |
| return; |
| } |
| } |
| |
| /* |
| * No process was found above, look through auxiliary list. |
| */ |
| (void) sighold(SIGPOLL); |
| pp = Plhead; |
| while (pp) { |
| if (pid > pp->pl_pid) { |
| /* |
| * Keep on looking. |
| */ |
| pp = pp->pl_next; |
| continue; |
| } else if (pid < pp->pl_pid) { |
| /* |
| * Not in the list. |
| */ |
| break; |
| } else { |
| /* |
| * This is a dead "godchild". |
| */ |
| pp->pl_dflag = 1; |
| pp->pl_exit = (short)status; |
| wakeup.w_flags.w_childdeath = 1; |
| Gchild = 1; /* Notice to call cleanaux(). */ |
| break; |
| } |
| } |
| |
| (void) sigrelse(SIGPOLL); |
| } |
| |
| /* ARGSUSED */ |
| static void |
| childeath(int signo) |
| { |
| pid_t pid; |
| int status; |
| |
| while ((pid = waitpid(-1, &status, WNOHANG)) > 0) |
| childeath_single(pid, status); |
| } |
| |
| static void |
| powerfail() |
| { |
| (void) nice(-19); |
| wakeup.w_flags.w_powerhit = 1; |
| } |
| |
| /* |
| * efork() forks a child and the parent inserts the process in its table |
| * of processes that are directly a result of forks that it has performed. |
| * The child just changes the "global" with the process id for this process |
| * to it's new value. |
| * If efork() is called with a pointer into the proc_table it uses that slot, |
| * otherwise it searches for a free slot. Regardless of how it was called, |
| * it returns the pointer to the proc_table entry |
| * |
| * The SIGCLD signal is blocked (held) before calling efork() |
| * and is unblocked (released) after efork() returns. |
| * |
| * Ideally, this should be rewritten to use modern signal semantics. |
| */ |
| static struct PROC_TABLE * |
| efork(int action, struct PROC_TABLE *process, int modes) |
| { |
| pid_t childpid; |
| struct PROC_TABLE *proc; |
| int i; |
| /* |
| * Freshen up the proc_table, removing any entries for dead processes |
| * that don't have NOCLEANUP set. Perform the necessary accounting. |
| */ |
| for (proc = proc_table; (proc < proc_table + num_proc); proc++) { |
| if ((proc->p_flags & (OCCUPIED|LIVING|NOCLEANUP)) == |
| (OCCUPIED)) { |
| /* |
| * Is this a named process? |
| * If so, do the necessary bookkeeping. |
| */ |
| if (proc->p_flags & NAMED) |
| (void) account(DEAD_PROCESS, proc, NULL); |
| |
| /* |
| * Free this entry for new usage. |
| */ |
| proc->p_flags = 0; |
| } |
| } |
| |
| while ((childpid = fork()) == FAILURE) { |
| /* |
| * Shorten the alarm timer in case someone else's child dies |
| * and free up a slot in the process table. |
| */ |
| setimer(5); |
| |
| /* |
| * Wait for some children to die. Since efork() |
| * is always called with SIGCLD blocked, unblock |
| * it here so that child death signals can come in. |
| */ |
| (void) sigrelse(SIGCLD); |
| (void) pause(); |
| (void) sighold(SIGCLD); |
| setimer(0); |
| } |
| |
| if (childpid != 0) { |
| |
| if (process == NULLPROC) { |
| /* |
| * No proc table pointer specified so search |
| * for a free slot. |
| */ |
| for (process = proc_table; process->p_flags != 0 && |
| (process < proc_table + num_proc); process++) |
| ; |
| |
| if (process == (proc_table + num_proc)) { |
| int old_proc_table_size = num_proc; |
| |
| /* Increase the process table size */ |
| increase_proc_table_size(); |
| if (old_proc_table_size == num_proc) { |
| /* didn't grow: memory failure */ |
| return (NO_ROOM); |
| } else { |
| process = |
| proc_table + old_proc_table_size; |
| } |
| } |
| |
| process->p_time = 0L; |
| process->p_count = 0; |
| } |
| process->p_id[0] = '\0'; |
| process->p_id[1] = '\0'; |
| process->p_id[2] = '\0'; |
| process->p_id[3] = '\0'; |
| process->p_pid = childpid; |
| process->p_flags = (LIVING | OCCUPIED | modes); |
| process->p_exit = 0; |
| |
| st_write(); |
| } else { |
| if ((action & (M_WAIT | M_BOOTWAIT)) == 0) |
| (void) setpgrp(); |
| |
| process = NULLPROC; |
| |
| /* |
| * Reset all signals to the system defaults. |
| */ |
| for (i = SIGHUP; i <= SIGRTMAX; i++) |
| (void) sigset(i, SIG_DFL); |
| |
| /* |
| * POSIX B.2.2.2 advises that init should set SIGTTOU, |
| * SIGTTIN, and SIGTSTP to SIG_IGN. |
| * |
| * Make sure that SIGXCPU and SIGXFSZ also remain ignored, |
| * for backward compatibility. |
| */ |
| (void) sigset(SIGTTIN, SIG_IGN); |
| (void) sigset(SIGTTOU, SIG_IGN); |
| (void) sigset(SIGTSTP, SIG_IGN); |
| (void) sigset(SIGXCPU, SIG_IGN); |
| (void) sigset(SIGXFSZ, SIG_IGN); |
| } |
| return (process); |
| } |
| |
| |
| /* |
| * waitproc() waits for a specified process to die. For this function to |
| * work, the specified process must already in the proc_table. waitproc() |
| * returns the exit status of the specified process when it dies. |
| */ |
| static long |
| waitproc(struct PROC_TABLE *process) |
| { |
| int answer; |
| sigset_t oldmask, newmask, zeromask; |
| |
| (void) sigemptyset(&zeromask); |
| (void) sigemptyset(&newmask); |
| |
| (void) sigaddset(&newmask, SIGCLD); |
| |
| /* Block SIGCLD and save the current signal mask */ |
| if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) |
| perror("SIG_BLOCK error"); |
| |
| /* |
| * Wait around until the process dies. |
| */ |
| if (process->p_flags & LIVING) |
| (void) sigsuspend(&zeromask); |
| |
| /* Reset signal mask to unblock SIGCLD */ |
| if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) |
| perror("SIG_SETMASK error"); |
| |
| if (process->p_flags & LIVING) |
| return (FAILURE); |
| |
| /* |
| * Make sure to only return 16 bits so that answer will always |
| * be positive whenever the process of interest really died. |
| */ |
| answer = (process->p_exit & 0xffff); |
| |
| /* |
| * Free the slot in the proc_table. |
| */ |
| process->p_flags = 0; |
| return (answer); |
| } |
| |
| /* |
| * notify_pam_dead(): calls into the PAM framework to close the given session. |
| */ |
| static void |
| notify_pam_dead(struct utmpx *up) |
| { |
| pam_handle_t *pamh; |
| char user[sizeof (up->ut_user) + 1]; |
| char ttyn[sizeof (up->ut_line) + 1]; |
| char host[sizeof (up->ut_host) + 1]; |
| |
| /* |
| * PAM does not take care of updating utmpx/wtmpx. |
| */ |
| (void) snprintf(user, sizeof (user), "%s", up->ut_user); |
| (void) snprintf(ttyn, sizeof (ttyn), "%s", up->ut_line); |
| (void) snprintf(host, sizeof (host), "%s", up->ut_host); |
| |
| if (pam_start("init", user, NULL, &pamh) == PAM_SUCCESS) { |
| (void) pam_set_item(pamh, PAM_TTY, ttyn); |
| (void) pam_set_item(pamh, PAM_RHOST, host); |
| (void) pam_close_session(pamh, 0); |
| (void) pam_end(pamh, PAM_SUCCESS); |
| } |
| } |
| |
| /* |
| * Check you can access utmpx (As / may be read-only and |
| * /var may not be mounted yet). |
| */ |
| static int |
| access_utmpx(void) |
| { |
| do { |
| utmpx_ok = (access(UTMPX, R_OK|W_OK) == 0); |
| } while (!utmpx_ok && errno == EINTR); |
| |
| return (utmpx_ok); |
| } |
| |
| /* |
| * account() updates entries in utmpx and appends new entries to the end of |
| * wtmpx (assuming they exist). The program argument indicates the name of |
| * program if INIT_PROCESS, otherwise should be NULL. |
| * |
| * account() only blocks for INIT_PROCESS requests. |
| * |
| * Returns non-zero if write failed. |
| */ |
| static int |
| account(short state, struct PROC_TABLE *process, char *program) |
| { |
| struct utmpx utmpbuf, *u, *oldu; |
| int tmplen; |
| char fail_buf[UT_LINE_SZ]; |
| sigset_t block, unblock; |
| |
| if (!utmpx_ok && !access_utmpx()) { |
| return (-1); |
| } |
| |
| /* |
| * Set up the prototype for the utmp structure we want to write. |
| */ |
| u = &utmpbuf; |
| (void) memset(u, 0, sizeof (struct utmpx)); |
| |
| /* |
| * Fill in the various fields of the utmp structure. |
| */ |
| u->ut_id[0] = process->p_id[0]; |
| u->ut_id[1] = process->p_id[1]; |
| u->ut_id[2] = process->p_id[2]; |
| u->ut_id[3] = process->p_id[3]; |
| u->ut_pid = process->p_pid; |
| |
| /* |
| * Fill the "ut_exit" structure. |
| */ |
| u->ut_exit.e_termination = WTERMSIG(process->p_exit); |
| u->ut_exit.e_exit = WEXITSTATUS(process->p_exit); |
| u->ut_type = state; |
| |
| (void) time(&u->ut_tv.tv_sec); |
| |
| /* |
| * Block signals for utmp update. |
| */ |
| (void) sigfillset(&block); |
| (void) sigprocmask(SIG_BLOCK, &block, &unblock); |
| |
| /* |
| * See if there already is such an entry in the "utmpx" file. |
| */ |
| setutxent(); /* Start at beginning of utmpx file. */ |
| |
| if ((oldu = getutxid(u)) != NULL) { |
| /* |
| * Copy in the old "user", "line" and "host" fields |
| * to our new structure. |
| */ |
| bcopy(oldu->ut_user, u->ut_user, sizeof (u->ut_user)); |
| bcopy(oldu->ut_line, u->ut_line, sizeof (u->ut_line)); |
| bcopy(oldu->ut_host, u->ut_host, sizeof (u->ut_host)); |
| u->ut_syslen = (tmplen = strlen(u->ut_host)) ? |
| min(tmplen + 1, sizeof (u->ut_host)) : 0; |
| |
| if (oldu->ut_type == USER_PROCESS && state == DEAD_PROCESS) { |
| notify_pam_dead(oldu); |
| } |
| } |
| |
| /* |
| * Perform special accounting. Insert the special string into the |
| * ut_line array. For INIT_PROCESSes put in the name of the |
| * program in the "ut_user" field. |
| */ |
| switch (state) { |
| case INIT_PROCESS: |
| (void) strncpy(u->ut_user, program, sizeof (u->ut_user)); |
| (void) strcpy(fail_buf, "INIT_PROCESS"); |
| break; |
| |
| default: |
| (void) strlcpy(fail_buf, u->ut_id, sizeof (u->ut_id) + 1); |
| break; |
| } |
| |
| /* |
| * Write out the updated entry to utmpx file. |
| */ |
| if (pututxline(u) == NULL) { |
| console(B_TRUE, "Failed write of utmpx entry: \"%s\": %s\n", |
| fail_buf, strerror(errno)); |
| endutxent(); |
| (void) sigprocmask(SIG_SETMASK, &unblock, NULL); |
| return (-1); |
| } |
| |
| /* |
| * If we're able to write to utmpx, then attempt to add to the |
| * end of the wtmpx file. |
| */ |
| updwtmpx(WTMPX, u); |
| |
| endutxent(); |
| |
| (void) sigprocmask(SIG_SETMASK, &unblock, NULL); |
| |
| return (0); |
| } |
| |
| static void |
| clearent(pid_t pid, short status) |
| { |
| struct utmpx *up; |
| sigset_t block, unblock; |
| |
| /* |
| * Block signals for utmp update. |
| */ |
| (void) sigfillset(&block); |
| (void) sigprocmask(SIG_BLOCK, &block, &unblock); |
| |
| /* |
| * No error checking for now. |
| */ |
| |
| setutxent(); |
| while (up = getutxent()) { |
| if (up->ut_pid == pid) { |
| if (up->ut_type == DEAD_PROCESS) { |
| /* |
| * Cleaned up elsewhere. |
| */ |
| continue; |
| } |
| |
| notify_pam_dead(up); |
| |
| up->ut_type = DEAD_PROCESS; |
| up->ut_exit.e_termination = WTERMSIG(status); |
| up->ut_exit.e_exit = WEXITSTATUS(status); |
| (void) time(&up->ut_tv.tv_sec); |
| |
| (void) pututxline(up); |
| /* |
| * Now attempt to add to the end of the |
| * wtmp and wtmpx files. Do not create |
| * if they don't already exist. |
| */ |
| updwtmpx(WTMPX, up); |
| |
| break; |
| } |
| } |
| |
| endutxent(); |
| (void) sigprocmask(SIG_SETMASK, &unblock, NULL); |
| } |
| |
| /* |
| * prog_name() searches for the word or unix path name and |
| * returns a pointer to the last element of the pathname. |
| */ |
| static char * |
| prog_name(char *string) |
| { |
| char *ptr, *ptr2; |
| static char word[UT_USER_SZ + 1]; |
| |
| /* |
| * Search for the first word skipping leading spaces and tabs. |
| */ |
| while (*string == ' ' || *string == '\t') |
| string++; |
| |
| /* |
| * If the first non-space non-tab character is not one allowed in |
| * a word, return a pointer to a null string, otherwise parse the |
| * pathname. |
| */ |
| if (*string != '.' && *string != '/' && *string != '_' && |
| (*string < 'a' || *string > 'z') && |
| (*string < 'A' || * string > 'Z') && |
| (*string < '0' || *string > '9')) |
| return (""); |
| |
| /* |
| * Parse the pathname looking forward for '/', ' ', '\t', '\n' or |
| * '\0'. Each time a '/' is found, move "ptr" to one past the |
| * '/', thus when a ' ', '\t', '\n', or '\0' is found, "ptr" will |
| * point to the last element of the pathname. |
| */ |
| for (ptr = string; *string != ' ' && *string != '\t' && |
| *string != '\n' && *string != '\0'; string++) { |
| if (*string == '/') |
| ptr = string+1; |
| } |
| |
| /* |
| * Copy out up to the size of the "ut_user" array into "word", |
| * null terminate it and return a pointer to it. |
| */ |
| for (ptr2 = &word[0]; ptr2 < &word[UT_USER_SZ] && |
| ptr < string; /* CSTYLED */) |
| *ptr2++ = *ptr++; |
| |
| *ptr2 = '\0'; |
| return (&word[0]); |
| } |
| |
| |
| /* |
| * realcon() returns a nonzero value if there is a character device |
| * associated with SYSCON that has the same device number as CONSOLE. |
| */ |
| static int |
| realcon() |
| { |
| struct stat sconbuf, conbuf; |
| |
| if (stat(SYSCON, &sconbuf) != -1 && |
| stat(CONSOLE, &conbuf) != -1 && |
| S_ISCHR(sconbuf.st_mode) && |
| S_ISCHR(conbuf.st_mode) && |
| sconbuf.st_rdev == conbuf.st_rdev) { |
| return (1); |
| } else { |
| return (0); |
| } |
| } |
| |
| |
| /* |
| * get_ioctl_syscon() retrieves the SYSCON settings from the IOCTLSYSCON file. |
| * Returns true if the IOCTLSYSCON file needs to be written (with |
| * write_ioctl_syscon() below) |
| */ |
| static int |
| get_ioctl_syscon() |
| { |
|