/*
* 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
* 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) 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
*
* 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
*
* 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/systeminfo.h>
#include <bsm/adt_event.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>
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define FALSE 0
/*
* SLEEPTIME The number of seconds "init" sleeps between wakeups if
* nothing else requires this "init" wakeup.
*/
/*
* MAXCMDL The maximum length of a command string in inittab.
*/
/*
* EXEC The length of the prefix string added to all comamnds
* found in inittab.
*/
/*
* TWARN The amount of time between warning signal, SIGTERM,
* and the fatal kill signal, SIGKILL.
*/
/*
* The kernel's default umask is 022 these days; since some processes inherit
* 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.
*/
/*
* Bit Mask for each level. Used to determine legal levels.
*/
/*
* Flags to indicate properties of various states.
*/
typedef struct lvl {
int lvl_state;
int lvl_mask;
char lvl_name;
int lvl_flags;
} lvl_t;
{ LVLQ, 0, 'Q', 0 },
{ LVLQ, 0, 'q', 0 },
};
/*
* Legal action field values.
*/
/* States for the inittab parser in getcmd(). */
/*
* inittab entry id constants
*/
/* 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.
*/
struct PROC_TABLE {
/* process */
/* the current series */
short p_flags;
};
/*
* 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.
*/
/*
* 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.
*/
/*
* The maximum number of decimal digits for an id_t. (ceil(log10 (max_id)))
*/
struct CMD_LINE {
/* process to be affected by */
/* action */
};
struct pidrec {
};
/*
* pd_type's
*/
static struct pidlist {
/*
* and should match the default contents of /etc/ioctl.syscon. It should also
* be kept in-sync with base_termios in uts/common/io/ttcompat.c.
*/
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0
};
static union WAKEUP {
struct WAKEFLAGS {
} w_flags;
int w_mask;
} wakeup;
struct init_state {
int ist_runlevel;
int ist_num_proc;
int ist_utmpx_ok;
};
/* Contract cookies. */
#define ORDINARY_COOKIE 0
#ifndef NDEBUG
abort(); \
}
#else
#endif
/*
* Useful file and device names.
*/
static const char * const init_next_state_file =
INIT_STATE_DIR "/init-next.state";
/*
* Default Path. /sbin is included in path only during sysinit phase
*/
static int prior_state;
/* childeath() and cleared in cleanaux() */
/* routine each time an alarm interrupt */
/* takes place. */
/*
* Array for default global environment.
*/
/* init can use three itself, so this leaves */
/* 20 for the administrator in ENVFILE. */
/*
* Contracts constants
*/
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 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 init_signals(void);
static void setup_pipe();
static void init_env();
static void boot_init();
static void powerfail();
static void remv();
static void write_ioctl_syscon();
static void setimer(int);
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 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
{
int c;
char *msg;
/* Get a timestamp for use as boot time, if needed. */
(void) time(&init_boot_time);
/* Get the default umask */
/* Parse the arguments to init. Check for single user */
opterr = 0;
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':
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.
*/
return (1);
}
/*
* If this PID is not the same as the "true" init for the zone, then we
* must be in 'user' mode.
*/
}
if (getzoneid() != GLOBAL_ZONEID) {
print_banner = TRUE;
}
/*
* Initialize state (and set "booting").
*/
st_init();
if (booting && print_banner) {
long ret;
/*
* 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.
*/
bits = 64;
break;
}
}
}
"\n\n%s Release %s Version %s %d-bit\r\n",
" 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.
*/
/*
* Set up all signals to be caught or ignored as appropriate.
*/
init_signals();
/* Load glob_envp from ENVFILE. */
init_env();
if (!booting) {
/* cur_state should have been read in. */
/* Rewrite the ioctl file if it was bad. */
if (write_ioctl)
} else {
/*
* It's fine to boot up with state as zero, because
* startd will later tell us the real state.
*/
cur_state = 0;
boot_init();
}
setup_pipe();
/*
* Here is the beginning of the main process loop.
*/
for (;;) {
if (lvlq_received) {
setup_pipe();
}
/*
* 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.
*/
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) {
if (rl != -1)
}
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) {
}
/* 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.
*/
/*
* 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 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.
*/
} else {
int ret;
msg = audit_boot_msg();
may_need_audit = 0;
(void) audit_put_record(ADT_SUCCESS,
ADT_SUCCESS, msg);
}
/*
* "init" is finished with all actions for
* the current wakeup.
*/
pausecnt++;
if (ret > 0)
contract_event(&poll_fds[0]);
}
/*
* 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.
*/
} else {
if (cur_state >= 0)
chg_lvl_flag = TRUE;
}
}
new_state = 0;
}
/*
* Clear all wakeup reasons.
*/
}
}
/*NOTREACHED*/
}
static void
{
return;
if (getzoneid() != GLOBAL_ZONEID)
return;
}
/*
* 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
{
(void) pause();
if (su_process == NULLPROC) {
int fd;
closefrom(0);
if (fd >= 0) {
} else {
/*
* Need to issue an error message somewhere.
*/
}
/*
* Execute the "su" program.
*/
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.
*/
/*
* 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().
*/
}
}
/*
* 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()
{
int change_level;
/*
* 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;
}
/*
* Scan all inittab entries.
*/
/* Scan for process which goes with this entry in inittab. */
for (process = proc_table;
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 ||
/*
* 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.
*/
if (change_level) {
|= WARNED;
(void) kill(
SIGTERM);
} else {
/*
* Fork a killing proc
* so "init" can
* continue without
* having to pause for
* TWARN seconds.
*/
}
}
}
} 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.
*/
}
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.
*/
/*
* Scan for processes which should be dying. We hope they
* will die without having to be sent a SIGKILL signal.
*/
for (process = proc_table;
/*
* 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.
*/
(void) pause();
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.
*/
for (process = proc_table;
}
}
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;
/*
* 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.
*/
}
}
st_write();
}
/*
* Extract the svc.startd command line and whether to restart it from its
* inittab entry.
*/
/*ARGSUSED*/
static void
{
/* Save the command line. */
/* Also append -r or -s. */
if (sflg)
if (rflg)
} else {
}
if (sz >= sizeof (startd_cline)) {
"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
{
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.
*/
}
/*
* Scan through all the entries in inittab.
*/
continue;
}
/*
* Find out if there is a process slot for this entry already.
*/
/*
* we've run out of proc table entries
* increase proc_table.
*/
/*
* 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.
*/
continue;
}
/*
* If the action is not an action we are interested in,
* skip the entry.
*/
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 &&
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.
*/
} else {
;
}
}
return (status);
}
/*
* spawn() spawns a shell, inserts the information about the process
* process into the proc_table, and does the startup accounting.
*/
static void
{
int i;
/*
* 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 |= DEMANDREQUEST;
/*
* If this is a respawnable process, check the threshold
* information to avoid excessive respawns.
*/
/*
* Add NOCLEANUP to all respawnable commands so that the
* information about the frequency of respawns isn't lost.
*/
/*
* If no time is assigned, then this is the first time
* this command is being processed in this series. Assign
* the current time.
*/
/*
* Process is respawning too rapidly. Print
* message and refuse to respawn it for now.
*/
"rapidly. Check for possible errors.\n"
"id:%4s \"%s\"\n",
return;
}
/*
* If process has been respawning too rapidly and
* the inhibit time limit hasn't expired yet, we
* refuse to respawn.
*/
return;
}
}
/*
* Spawn a child process to execute this command.
*/
(void) pause();
/*
* 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.
*/
for (i = 0; i < maxfiles; i++)
/*
* Now exec a shell with the -c option and the command
* from inittab.
*/
/*
* 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.
*/
st_write();
}
/*
* 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 *
{
for (process = proc_table;
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 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.
*/
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 int
{
char *ptr;
char *ptr1;
static char *actions[] = {
"off", "respawn", "ondemand", "once", "wait", "boot",
"bootwait", "powerfail", "powerwait", "initdefault",
"sysinit",
};
static short act_masks[] = {
};
/*
* Only these actions will be allowed for entries which
* are specified for single-user mode.
*/
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 (i == 2) {
"Cannot stat %s, errno: %d\n",
return (FAILURE);
} else {
timer(3);
}
if (i == 2) {
"%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.
*/
errno);
return (FAILURE);
}
}
/*
* Keep getting commands from inittab until you find a
* good one or run out of file.
*/
/*
* Zero out the cmd itself before trying next 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.
*/
(void) fclose(fp_inittab);
fp_inittab = NULL;
break;
}
/* 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. */
/*
* Squeeze out spaces and tabs.
*/
if (c == ' ' || c == '\t')
continue;
/*
* Ignore characters in a comment, except for the \n.
*/
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.
*/
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.
*/
} else {
}
break;
case LEVELS :
/*
* Build a mask for all the levels for
* which this command will be legal.
*/
int mask;
if (lvlname_to_mask(*ptr1,
&mask) == -1) {
break;
}
}
}
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".
*/
else
} else {
*ptr = '\0';
i <
sizeof (actions)/sizeof (char *);
i++) {
else
act_masks[i];
break;
}
}
}
/*
* If the action didn't match any legal action,
* set state to FAILURE.
*/
} else {
}
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. */
*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. */
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. */
c = '\0';
}
/*
* Make sure all the fields are properly specified
* for a good command line.
*/
/*
* If no default level was supplied, insert
* all numerical levels.
*/
/*
* If no action has been supplied, declare this
* entry to be OFF.
*/
/*
* If no shell command has been supplied, make sure
* there is a null string in the command field.
*/
*shcmd = '\0';
} else
/*
* 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
{
int i;
for (i = 0; i < LVL_NELEMS; i++) {
}
return (-1);
}
/*
* state_to_name(): convert the level to the character name.
*/
static char
{
int i;
for (i = 0; i < LVL_NELEMS; i++) {
}
return (-1);
}
/*
* state_to_mask(): return the mask corresponding to a signal number
*/
static int
{
int i;
for (i = 0; i < LVL_NELEMS; i++) {
}
return (0); /* return 0, since that represents an empty mask */
}
/*
* lvlname_to_mask(): return the mask corresponding to a levels character name
*/
static int
{
int i;
for (i = 0; i < LVL_NELEMS; i++) {
return (0);
}
}
return (-1);
}
/*
* state_to_flags(): return the flags corresponding to a runlevel. These
* indicate properties of that runlevel.
*/
static int
{
int i;
for (i = 0; i < LVL_NELEMS; i++) {
}
return (0);
}
/*
* killproc() creates a child which kills the process specified by pid.
*/
void
{
(void) pause();
/*
* efork() sets all signal handlers to the default, so reset
* the ALRM handler to make timer() work as expected.
*/
/*
* 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) exit(0);
}
}
/*
* Set up the default environment for all procs to be forked from init.
* there's not enough room in the environment array, the environment
* lines that don't fit are silently discarded.
*/
void
init_env()
{
glob_envn = 1;
if (rflg) {
glob_envp[1] =
++glob_envn;
} else if (bflg == 1) {
glob_envp[1] =
++glob_envn;
}
"Cannot open %s. Environment not initialized.\n",
ENVFILE);
} else {
/*
* Toss newline
*/
/*
* Ignore blank or comment lines.
*/
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;
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.
*/
continue;
do {
continue;
length--;
}
sizeof ("CMASK=") - 1) == 0) {
long t;
/* We know there's an = */
8);
/* Sanity */
if (t <= 077 && t >= 0)
cmask = (int)t;
continue;
}
break;
}
/*
* Append a null pointer to the environment array
* to mark its end.
*/
}
}
/*
* boot_init(): Do initialization things that should be done at boot.
*/
void
{
int i;
char *old_path;
int maxfiles;
/* Use INIT_PATH for sysinit cmds */
/*
* Scan inittab(4) and process the special svc.startd entry, initdefault
* and sysinit entries.
*/
/*
* initdefault is no longer meaningful, as the SMF
* milestone controls what (legacy) run level we
* boot to.
*/
"Ignoring legacy \"initdefault\" entry.\n");
/*
* 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 (legacy_tmpl >= 0) {
(void) ct_pr_tmpl_set_svc_fmri(
(void) ct_pr_tmpl_set_svc_aux(
}
/* CSTYLED */)
;
for (i = 0; i < maxfiles; i++)
(char *)0, glob_envp);
"Command\n\"%s\"\n failed to execute. errno = %d (exec of shell failed)\n",
exit(1);
} else
;
st_write();
}
}
}
/* Restore the 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)
/*
* Start svc.startd(1M), which does most of the work.
*/
/* Start svc.startd. */
} else {
"contract template. Not starting svc.startd.\n");
}
}
/*
* init_signals(): Initialize all signals to either be caught or ignored.
*/
void
init_signals(void)
{
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.
*/
/*
* Handle all level-changing signals using siglvl() and set sa_mask so
* that all level-changing signals are blocked while in siglvl().
*/
alarmclk();
}
/*
* 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
* only called on startup and when explicitly requested via LVLQ.
*/
void
{
/*
* Always close the previous pipe descriptor as the mounted filesystems
* may have changed.
*/
if (Pfd >= 0)
else
}
if (Pfd >= 0) {
/*
* Read pipe in message discard mode.
*/
}
}
/*
* siglvl - handle an asynchronous signal from init(1M) telling us that we
* should change the current run level. We set new_state accordingly.
*/
void
{
/*
* 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.
*/
(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.
*/
} else {
}
/*
* 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;
}
/*
* Set the flag to indicate that a "user signal" was received.
*/
}
/*
* alarmclk
*/
static void
alarmclk()
{
}
/*
* 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
{
/*
* Scan the process table to see if we are interested in this process.
*/
for (process = proc_table;
/*
* 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.
*/
return;
}
}
/*
* No process was found above, look through auxiliary list.
*/
while (pp) {
/*
* Keep on looking.
*/
continue;
/*
* Not in the list.
*/
break;
} else {
/*
* This is a dead "godchild".
*/
break;
}
}
}
/* ARGSUSED */
static void
{
int status;
}
static void
{
(void) nice(-19);
}
/*
* 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 *
{
int i;
/*
* Freshen up the proc_table, removing any entries for dead processes
* that don't have NOCLEANUP set. Perform the necessary accounting.
*/
(OCCUPIED)) {
/*
* Is this a named process?
* If so, do the necessary bookkeeping.
*/
/*
* Free this entry for new usage.
*/
}
}
/*
* 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) pause();
setimer(0);
}
if (childpid != 0) {
/*
* No proc table pointer specified so search
* for a free slot.
*/
;
/* Increase the process table size */
if (old_proc_table_size == num_proc) {
/* didn't grow: memory failure */
return (NO_ROOM);
} else {
process =
}
}
}
st_write();
} else {
(void) setpgrp();
/*
* Reset all signals to the system defaults.
*/
/*
* 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.
*/
}
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
{
int answer;
(void) sigemptyset(&zeromask);
(void) sigemptyset(&newmask);
/* Block SIGCLD and save the current signal mask */
perror("SIG_BLOCK error");
/*
* Wait around until the process dies.
*/
(void) sigsuspend(&zeromask);
/* Reset signal mask to unblock SIGCLD */
perror("SIG_SETMASK error");
return (FAILURE);
/*
* Make sure to only return 16 bits so that answer will always
* be positive whenever the process of interest really died.
*/
/*
* Free the slot in the proc_table.
*/
return (answer);
}
/*
* notify_pam_dead(): calls into the PAM framework to close the given session.
*/
static void
{
/*
*/
(void) pam_close_session(pamh, 0);
}
}
/*
* Check you can access utmpx (As / may be read-only and
* /var may not be mounted yet).
*/
static int
access_utmpx(void)
{
do {
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
{
int tmplen;
if (!utmpx_ok && !access_utmpx()) {
return (-1);
}
/*
* Set up the prototype for the utmp structure we want to write.
*/
u = &utmpbuf;
/*
* Fill in the various fields of the utmp structure.
*/
/*
* Fill the "ut_exit" structure.
*/
/*
* Block signals for utmp update.
*/
(void) sigfillset(&block);
/*
* See if there already is such an entry in the "utmpx" file.
*/
setutxent(); /* Start at beginning of utmpx file. */
/*
* Copy in the old "user", "line" and "host" fields
* to our new structure.
*/
}
}
/*
* 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:
break;
default:
break;
}
/*
* Write out the updated entry to utmpx file.
*/
if (pututxline(u) == NULL) {
endutxent();
return (-1);
}
/*
* If we're able to write to utmpx, then attempt to add to the
* end of the wtmpx file.
*/
endutxent();
return (0);
}
static void
{
/*
* Block signals for utmp update.
*/
(void) sigfillset(&block);
/*
* No error checking for now.
*/
setutxent();
/*
* Cleaned up elsewhere.
*/
continue;
}
(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.
*/
break;
}
}
endutxent();
}
/*
* prog_name() searches for the word or unix path name and
* returns a pointer to the last element of the pathname.
*/
static char *
{
/*
* Search for the first word skipping leading spaces and tabs.
*/
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.
*/
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.
*/
if (*string == '/')
}
/*
* Copy out up to the size of the "ut_user" array into "word",
* null terminate it and return a pointer to it.
*/
*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()
{
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
{
int i, valid_format = 0;
/*
* Read in the previous modes for SYSCON from IOCTLSYSCON.
*/
"warning:%s does not exist, default settings assumed\n",
} else {
"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x",
if (i == 22) {
for (i = 0; i < 18; i++)
valid_format = 1;
} else if (i == 13) {
/*
* If the file is formatted properly, use the values to
* initialize the console terminal condition.
*/
for (i = 0; i < 8; i++)
valid_format = 1;
}
/* If the file is badly formatted, use the default settings. */
if (!valid_format)
}
/* If the file had a bad format, rewrite it later. */
return (!valid_format);
}
static void
{
int i;
(void) umask(022);
for (i = 0; i < 8; ++i)
}
/*
* void console(boolean_t, char *, ...)
* Outputs the requested message to the system console. Note that the number
* of arguments passed to console() should be determined by the print format.
*
* The "prefix" parameter indicates whether or not "INIT: " should precede the
* message.
*
* To make sure we write to the console in a sane fashion, we use the modes
* we keep in stored_syscon_termios (which we read out of /etc/ioctl.syscon).
* Afterwards we restore whatever modes were already there.
*/
/* PRINTFLIKE2 */
static void
{
FILE *f;
/*
* We open SYSCON anew each time in case it has changed (see
* userinit()).
*/
if (prefix)
if (fd >= 0)
return;
}
if (realcon())
/* Don't overwrite cflag of real console. */
if (prefix)
(void) fprintf(f, "\nINIT: ");
if (getret == 0)
(void) fclose(f);
}
/*
* timer() is a substitute for sleep() which uses alarm() and pause().
*/
static void
{
(void) pause();
}
static void
{
alarmclk();
}
/*
* Fails with
* ENOMEM - out of memory
* ECONNABORTED - repository connection broken
* EPERM - permission denied
* EACCES - backend access denied
* EROFS - backend readonly
*/
static int
{
scf_handle_t *h;
int ret = 0;
h = scf_instance_handle(inst);
return (0);
switch (scf_error()) {
return (ECONNABORTED);
case SCF_ERROR_NOT_FOUND:
break;
default:
}
/* Make sure we're right, since we're adding piece-by-piece. */
goto out;
}
switch (scf_error()) {
ret = ECONNABORTED;
goto out;
case SCF_ERROR_NOT_FOUND:
"smf(5) repository missing local scope.\n"),
stderr);
exit(1);
/* NOTREACHED */
default:
}
}
switch (scf_error()) {
ret = ECONNABORTED;
goto out;
case SCF_ERROR_DELETED:
goto get_scope;
case SCF_ERROR_NOT_FOUND:
break;
case SCF_ERROR_NOT_SET:
default:
}
0) {
switch (scf_error()) {
ret = ECONNABORTED;
goto out;
case SCF_ERROR_EXISTS:
goto get_svc;
goto out;
case SCF_ERROR_BACKEND_ACCESS:
goto out;
goto out;
case SCF_ERROR_NOT_SET:
default:
}
}
}
switch (scf_error()) {
ret = ECONNABORTED;
goto out;
case SCF_ERROR_DELETED:
goto add_svc;
case SCF_ERROR_NOT_FOUND:
break;
case SCF_ERROR_NOT_SET:
default:
}
0) {
switch (scf_error()) {
ret = ECONNABORTED;
goto out;
case SCF_ERROR_DELETED:
goto add_svc;
case SCF_ERROR_EXISTS:
goto get_inst;
goto out;
case SCF_ERROR_BACKEND_ACCESS:
goto out;
goto out;
case SCF_ERROR_NOT_SET:
default:
bad_error("scf_service_add_instance",
scf_error());
}
}
}
ret = 0;
out:
return (ret);
}
/*
* Fails with
* ECONNABORTED - repository connection broken
* ECANCELED - the transaction's property group was deleted
*/
static int
{
return (0);
switch (scf_error()) {
return (ECONNABORTED);
case SCF_ERROR_DELETED:
return (ECANCELED);
case SCF_ERROR_NOT_FOUND:
goto new;
case SCF_ERROR_NOT_BOUND:
case SCF_ERROR_NOT_SET:
default:
}
new:
return (0);
switch (scf_error()) {
return (ECONNABORTED);
case SCF_ERROR_DELETED:
return (ECANCELED);
case SCF_ERROR_EXISTS:
goto change_type;
case SCF_ERROR_NOT_BOUND:
case SCF_ERROR_NOT_SET:
default:
/* NOTREACHED */
}
}
static void
scferr(void)
{
switch (scf_error()) {
case SCF_ERROR_NO_MEMORY:
break;
"Connection to smf(5) repository server broken.\n"));
break;
case SCF_ERROR_NO_RESOURCES:
"smf(5) repository server is out of memory.\n"));
break;
break;
default:
scf_strerror(scf_error()));
}
}
static void
{
scf_handle_t *h;
int r;
h = scf_handle_create(SCF_VERSION);
if (h == NULL) {
scferr();
return;
}
if (scf_handle_bind(h) != 0) {
switch (scf_error()) {
case SCF_ERROR_NO_SERVER:
gettext("smf(5) repository server not running.\n"));
goto bail;
default:
scferr();
goto bail;
}
}
scferr();
goto bail;
}
r = get_or_add_startd(inst);
switch (r) {
case 0:
break;
case ENOMEM:
case ECONNABORTED:
case EPERM:
case EACCES:
case EROFS:
scferr();
goto bail;
default:
bad_error("get_or_add_startd", r);
}
switch (scf_error()) {
scferr();
goto bail;
case SCF_ERROR_DELETED:
goto get_inst;
case SCF_ERROR_NOT_FOUND:
break;
case SCF_ERROR_NOT_SET:
default:
}
0) {
switch (scf_error()) {
case SCF_ERROR_BACKEND_ACCESS:
scferr();
goto bail;
case SCF_ERROR_DELETED:
goto get_inst;
case SCF_ERROR_EXISTS:
goto get_pg;
case SCF_ERROR_NOT_SET:
default:
}
}
}
assert(r == 0);
for (;;) {
switch (scf_error()) {
case SCF_ERROR_BACKEND_ACCESS:
scferr();
goto bail;
case SCF_ERROR_DELETED:
goto add_pg;
case SCF_ERROR_NOT_BOUND:
case SCF_ERROR_IN_USE:
case SCF_ERROR_NOT_SET:
default:
}
}
switch (r) {
case 0:
break;
case ECONNABORTED:
scferr();
goto bail;
case ECANCELED:
goto add_pg;
default:
bad_error("transaction_add_set", r);
}
assert(r == 0);
r = scf_transaction_commit(tx);
if (r == 1)
break;
if (r != 0) {
switch (scf_error()) {
case SCF_ERROR_BACKEND_ACCESS:
scferr();
goto bail;
case SCF_ERROR_DELETED:
goto add_pg;
case SCF_ERROR_NOT_BOUND:
case SCF_ERROR_NOT_SET:
default:
bad_error("scf_transaction_commit",
scf_error());
}
}
(void) scf_pg_update(pg);
}
bail:
(void) scf_handle_unbind(h);
}
/*
* Function to handle requests from users to main init running as process 1.
*/
static void
{
char *ln;
int init_signal;
/*
* We are a user invoked init. Is there an argument and is it
* a single character? If not, print usage message and quit.
*/
exit(0);
}
argv[1]);
exit(1);
}
if (init_signal == SINGLE_USER) {
/*
* Make sure this process is talking to a legal tty line
*/
"Standard input not a tty line\n");
(void) audit_put_record(ADT_FAILURE,
exit(1);
}
/*
*/
"Run command on the system console.\n");
(void) audit_put_record(ADT_FAILURE,
exit(1);
}
/* Try to leave a syscon */
(void) audit_put_record(ADT_FAILURE,
exit(1);
}
/*
* Try to leave a message on system console saying where
*/
"\n**** SYSCON CHANGED TO %s ****\n",
ln);
}
}
}
/*
* Signal init; init will take care of telling svc.startd.
*/
(void) audit_put_record(ADT_FAILURE,
exit(1);
}
exit(0);
}
/* ARGSUSED */
void
sigpoll(int n)
{
int i;
if (Pfd < 0) {
return;
}
for (;;) {
/*
* Important Note: Either read will really fail (in which case
* return is all we can do) or will get EAGAIN (Pfd was opened
* O_NDELAY), in which case we also want to return.
* Always return from here!
*/
sizeof (struct pidrec)) {
return;
}
switch (p->pd_type) {
case ADDPID:
/*
* New "godchild", add to list.
*/
sizeof (struct pidlist));
/* Can't save pid */
break;
}
/*
* Point at 2nd record allocated, we'll use plp.
*/
/*
* Link them into a chain.
*/
for (i = 0; i < DELTA - 2; i++) {
tp++;
}
} else {
}
/*
* Note - pid list is kept in increasing order of pids.
*/
/* Back up to read next record */
break;
} else {
while (tp) {
continue;
} else {
}
break;
} else {
/* Already in list! */
break;
}
}
/* Add to end of list */
}
}
/* Back up to read next record. */
break;
case REMPID:
/*
* This one was handled by someone else,
* purge it from the list.
*/
/* Back up to read next record. */
break;
}
while (tp) {
/* Keep on looking. */
continue;
/* Not in list. */
break;
} else {
/* Found it. */
else
break;
}
}
/* Back up to read next record. */
break;
default:
break;
}
}
}
static void
cleanaux()
{
short status;
Gchild = 0; /* Note - Safe to do this here since no SIGCLDs */
while (p) {
if (p->pl_dflag) {
/*
* Found an entry to delete,
* remove it from list first.
*/
if (p == Plhead) {
Plfree = p;
} else {
Plfree = p;
}
continue;
}
savep = p;
p = p->pl_next;
}
}
/*
* array. Double the size of proc_table to accomodate the extra entries.
*/
static void
{
void *ptr;
/*
* Block signals for realloc.
*/
(void) sigfillset(&block);
/*
* On failure we just return because callers of this function check
* for failure.
*/
do
;
/* ensure that the new part is initialized to zero */
g_state_sz += delta;
num_proc <<= 1;
}
/* unblock our signals before returning */
}
/*
* Sanity check g_state.
*/
static int
st_sane()
{
int i;
/* Note: cur_state is encoded as a signal number */
return (0);
/* Check num_proc */
sizeof (struct PROC_TABLE))
return (0);
/* Check proc_table */
/* skip unoccupied entries */
continue;
/* p_flags has no bits outside of PF_MASK */
return (0);
/* 5 <= pid <= MAXPID */
return (0);
/* p_count >= 0 */
return (0);
/* p_time >= 0 */
return (0);
}
return (1);
}
/*
* Initialize our state.
*
* If the system just booted, then init_state_file, which is located on an
* everpresent tmpfs filesystem, should not exist.
*
* If we were restarted, then init_state_file should exist, in
* which case we'll read it in, sanity check it, and use it.
*
* Note: You can't call console() until proc_table is ready.
*/
void
st_init()
{
char *ptr;
booting = 1;
do {
/*
* If we can exclusively create the file, then we're the
* initial invocation of init(1M).
*/
if (st_fd != -1)
goto new_state;
booting = 0;
do {
if (st_fd == -1)
goto new_state;
/* Get the size of the file. */
do
;
if (ret == -1)
goto new_state;
do
;
goto new_state;
while (to_be_read > 0) {
if (read_ret < 0) {
continue;
goto new_state;
}
to_be_read -= read_ret;
}
if (st_sane()) {
return;
}
insane = 1;
if (st_fd >= 0)
else
(void) unlink(init_state_file);
/* Something went wrong, so allocate new state. */
g_state_sz = sizeof (struct init_state) +
do
;
/* Fatal error! */
}
if (!booting) {
/* Overwrite the bad state file. */
st_write();
if (!insane) {
"Error accessing persistent state file `%s'. "
"Ignored.\n", init_state_file);
} else {
"Persistent state file `%s' is invalid and was "
"ignored.\n", init_state_file);
}
}
}
/*
* Write g_state out to the state file.
*/
void
st_write()
{
static int complained = 0;
int st_fd;
char *cp;
do {
if (st_fd < 0)
goto err;
sz = g_state_sz;
while (sz > 0) {
if (ret < 0) {
continue;
goto err;
}
}
st_fd = -1;
(void) unlink(init_next_state_file);
goto err;
}
complained = 0;
return;
err:
if (st_fd >= 0)
if (!booting && !complained) {
/*
* Only complain after the filesystem should have come up.
* And only do it once so we don't loop between console()
* & efork().
*/
complained = 1;
if (st_fd)
"file `%s'.\n", init_state_file);
else
"file `%s' to `%s'.\n", init_next_state_file,
}
}
/*
* Create a contract with these parameters.
*/
static int
{
char *ioctl_tset_emsg =
"Couldn't set \"%s\" contract template parameter: %s.\n";
do
;
if (fd < 0) {
return (-1);
}
/*
* These errors result in a misconfigured template, which is better
* than no template at all, so warn but don't abort.
*/
return (fd);
}
/*
* Create the templates and open an event file descriptor. We use dup2(2) to
*/
static void
{
/*
* Create & configure a legacy template. We only want empty events so
* we know when to abandon them.
*/
if (legacy_tmpl >= 0) {
if (err != 0) {
(void) close(legacy_tmpl);
legacy_tmpl = -1;
"Couldn't activate legacy template (%s); "
"legacy services will be in init's contract.\n",
}
} else
"Legacy services will be in init's contract.\n");
} else {
(void) close(legacy_tmpl);
legacy_tmpl = 255;
}
} else {
(void) close(startd_tmpl);
startd_tmpl = 254;
}
if (legacy_tmpl < 0 && startd_tmpl < 0) {
/* The creation errors have already been reported. */
"Ignoring contract events. Core smf(5) services will not "
"be restarted.\n");
return;
}
/*
* Open an event endpoint.
*/
do
;
if (fd < 0) {
"Couldn't open process pbundle: %s. Core smf(5) services "
return;
}
} else {
fd = 253;
}
/* Reset in case we've been restarted. */
(void) ct_event_reset(fd);
poll_nfds = 1;
}
static int
{
int fd;
do
;
if (fd < 0)
return (fd);
}
static int
{
if (fd < 0)
return (-1);
if (err != 0) {
return (-1);
}
return (0);
}
static void
{
int fd;
if (ct_event_get_flags(e) & CTE_INFO)
return;
if (fd < 0)
return;
}
/*
* Process a contract event.
*/
static void
{
ct_evthdl_t e;
int err;
"Unknown poll error on my process contract "
"pbundle.\n");
return;
}
if (err != 0) {
return;
}
ctid = ct_event_get_ctid(e);
if (ct_event_get_type(e) == CT_PR_EV_EMPTY) {
/* If it's svc.startd, restart it. Else, abandon. */
if (ret == 0) {
if (cookie == STARTD_COOKIE &&
if (smf_debug)
"svc.startd.\n");
/*
* Account for the failure. If the failure rate
* exceeds a threshold, then drop to maintenance
* mode.
*/
if (startd_failure_rate_critical())
if (startd_tmpl < 0)
"Restarting svc.startd in "
"improper contract (bad "
"template).\n");
ctid);
abandon = 0;
}
}
}
/*
* No need to acknowledge the event since either way the
* originating contract should be abandoned.
*/
} else {
"Received contract event of unexpected type %d from "
/* Allow unexpected critical events to be released. */
contract_ack(e);
}
ct_event_free(e);
}
/*
* svc.startd(1M) Management
*/
/*
* (Re)start svc.startd(1M). old_ctid should be the contract ID of the old
* contract, or 0 if we're starting it for the first time. If wait is true
* we'll wait for and return the exit value of the child.
*/
static int
{
if (cline[0] == '\0')
return (-1);
/*
* Don't restart startd if the system is rebooting or shutting down.
*/
do {
if (ret == 0) {
if (smf_debug)
(void) pause();
return (-1);
}
}
if (err != 0) {
"Couldn't set transfer parameter of contract template: "
}
SCF_SERVICE_STARTD)) != 0)
"Can not set svc_fmri in contract template: %s\n",
startd_svc_aux)) != 0)
"Can not set svc_aux in contract template: %s\n",
if (!did_activate)
"Template activation failed; not starting \"%s\" in "
"proper contract.\n", cline);
/* Hold SIGCLD so we can wait if necessary. */
/* Now that's a doozy. */
exit(1);
}
"fork() for svc.startd failed: %s. Will retry in 1 "
(void) sleep(1);
/* Eventually give up? */
}
if (pid == 0) {
/* child */
/* See the comment in efork() */
else
}
if (smf_options != NULL) {
/* Put smf_options in the environment. */
/* LINTED */
"SMF_OPTIONS=%s", smf_options);
} else {
"Could not set SMF_OPTIONS (%s).\n",
}
}
if (smf_debug)
exit(1);
}
/* parent */
if (did_activate) {
(void) ct_tmpl_clear(tmpl);
}
/* Clear the old_ctid reference so the kernel can reclaim it. */
if (old_ctid != 0)
(void) ct_pr_tmpl_set_transfer(tmpl, 0);
return (0);
}
/*
* void startd_record_failure(void)
* Place the current time in our circular array of svc.startd failures.
*/
void
{
}
/*
* int startd_failure_rate_critical(void)
* Return true if the average failure interval is less than the permitted
* interval. Implicit success if insufficient measurements for an average
* exist.
*/
int
{
int n = startd_failure_index;
return (0);
avg_ns =
return (avg_ns < STARTD_FAILURE_RATE_NS);
}
/*
* returns string that must be free'd
*/
static char
{
char *b, *p;
if (b == NULL)
return (b);
p = b;
if (zid != GLOBAL_ZONEID) {
}
return (b);
}
/*
* Generate AUE_init_solaris audit record. Return 1 if
* auditing is enabled in case the caller cares.
*
* In the case of userint() or a local zone invocation of
* one_true_init, the process initially contains the audit
* characteristics of the process that invoked init. The first pass
* through here uses those characteristics then for the case of
* one_true_init in a local zone, clears them so subsequent system
* state changes won't be attributed to the person who booted the
* zone.
*/
static int
{
if (!adt_audit_enabled())
return (0);
/*
* the PROC_DATA picks up the context to tell whether this is
* an attributed record (auid = -2 is unattributed)
*/
return (1);
}
(void) adt_end_session(ah);
return (1);
}
(void) adt_end_session(ah);
return (1);
}
(void) adt_end_session(ah);
return (1);
}