/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/
/*
 * Copyright (c) 1998 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma	ident	"@(#)sac.c	1.12	98/10/22 SMI"	/* SVr4.0 1.14 */

#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/stropts.h>
#include <sys/wait.h>
#include <unistd.h>
#include <utmpx.h>
#include <memory.h>
#include "msgs.h"
#include "extern.h"
#include <sac.h>
#include "misc.h"
#include "structs.h"

#include <security/pam_appl.h>

#define	RESP	1		/* pollfail via no response to sanity poll */
#define	DEATH	2		/* pollfail via child death */

/* signal whose dispositions will be changed */

static struct sigaction	Sigpoll;	/* SIGPOLL */
static struct sigaction	Sigcld;		/* SIGCLD */
static struct sigaction	Sigalrm;	/* SIGALRM */
static sigset_t Origmask;		/* original signal mask */

void usage(void);
void initialize(void);
void startpms(void);
void readutmpx(void);
int startpm(struct sactab *);
void cleanutx(struct sactab *);
void account(struct sactab *, pid_t);
void startit(struct sactab *);
char **mkargv(struct sactab *);
void pollpms(void);
void reap(int);
void pollfail(struct sactab *, int);
void readpipe(void);
int validstate(unchar);
int mk_cmd_pipe(void);
void startpoll(void);



/*
 * main - scan args for sac, initialize everything, and wait for commands
 *	  from sacadm via the command pipe
 */

main(int argc, char *argv[])
{
	int c;	/* place to hold options */
	struct sigaction sigact;	/* for signal handling */

	(void) sigprocmask(SIG_SETMASK, NULL, &Origmask);
	if (argc == 1)
		usage();
	(void) setpgrp();
	while ((c = getopt(argc, argv, "t:")) != -1) {
		switch (c) {
		case 't':
			if (Stime != 0)
				usage();
			Stime = atoi(optarg);
			if (Stime <= 0)
				usage();
			break;
		case '?':
			usage();
		}
	}
	if (optind < argc)
		usage();

	initialize();
	sigact.sa_flags = 0;
	sigact.sa_handler = pollpms;
	(void) sigemptyset(&sigact.sa_mask);
	(void) sigaddset(&sigact.sa_mask, SIGALRM);
	(void) sigaction(SIGALRM, &sigact, &Sigalrm);

/*
 * minimize time spent in STARTING or UNKNOWN, pollpms() sets alarm
 */

	pollpms();
	for (;;)
		readpipe();
}


/*
 * usage - output a usage message on the console
 */

void
usage()
{
	FILE *fp;	/* scratch file pointer */

	fp = fopen("/dev/console", "w");
	if (fp)
		(void) fprintf(fp, "SAC: Usage: sac -t sanity_interval\n");
	exit(1);
}


/*
 * initialize - initialization stuff
 */


void
initialize()
{
	int ret;			/* return code from doconfig() */
	struct sigaction sigact;	/* for signal handling */

	openlog();
	log("*** SAC starting ***");
#ifdef DEBUG
	opendebug();
	log("Debugging turned on");
#endif
	if (chdir(HOME) < 0)
		error(E_CHDIR, EXIT);

/*
 * pass an invalid fd, shouldn't be doing pushes and pops in this per-system
 * configuration script (_sysconfig)
 */

	if ((ret = doconfig(-1, SYSCONFIG, 0)) != 0) {
		if (ret == -1)
			error(E_SYSCONF, EXIT);
		else {
			(void) sprintf(Scratch,
					"Error in _sysconfig: line %d", ret);
			log(Scratch);
			error(E_BADSYSCONF, EXIT);
		}
	}

	sigact.sa_flags = 0;
	sigact.sa_handler = reap;
	(void) sigemptyset(&sigact.sa_mask);
	(void) sigaddset(&sigact.sa_mask, SIGCLD);
	(void) sigaction(SIGCLD, &sigact, &Sigcld);

/*
 * establish pipe for PMS to communicate with sac
 */

	if (access("_sacpipe", 0) != 0) {
		/* not there, create one */
		(void) umask(0);
		if (mknod("_sacpipe", S_IFIFO | 0600, 0) < 0)
			error(E_NOPIPE, EXIT);
	}
	Sfd = open("_sacpipe", O_RDWR);
	if (Sfd < 0)
		error(E_NOPIPE, EXIT);

/*
 * establish pipe for sacadm to communicate with sac
 */

	Cfd = mk_cmd_pipe();

/*
 * read in _sactab, but don't start port monitors as a by-product
 * since we may be in recovery - start them explicitly instead
 */

	read_table(FALSE);
	startpoll();
	startpms();
}


/*
 * startpms - start initial set of port monitors
 */


void
startpms()
{
	struct sactab *sp;	/* working pointer */
	int rflag;			/* recovery flag */
	pid_t checklock();

/*
 * check to see if we're really a recovering SAC (if any port monitors hold
 * locks, assume that we're in recovery), if so, start differently
 */

	rflag = 0;
	for (sp = Sactab; sp; sp = sp->sc_next) {
		if (checklock(sp)) {
			rflag = 1;
			sp->sc_sstate = sp->sc_pstate = UNKNOWN;
			sp->sc_ok = 1;
			sp->sc_exit = 0;
			(void) sprintf(Scratch, "%s/_pmpipe", sp->sc_tag);
			sp->sc_fd = open(Scratch, O_RDWR);
			if (sp->sc_fd < 0) {

/*
 * if we get into here, we're in deep trouble.  PM seems to be running
 * and we're trying to recover, but we can't talk to it.  Unfortunately,
 * there's not much that can be done other than to try and restore a
 * sane state.  By setting sp->sc_ok to 0, this will look like a poll failure
 * and if sp->rs_rsmax > 0, PM will be restarted.
 */

				(void) sprintf(Scratch,
				"Could not open _pmpipe for port monitor <%s>",
					sp->sc_tag);
				log(Scratch);
				(void) sendsig(sp, SIGTERM);
				sp->sc_ok = 0;
			}
		}
	}
	if (rflag) {
		readutmpx();
		log("SAC in recovery");
		return;
	}

/*
 * normal startup
 */

	for (sp = Sactab; sp; sp = sp->sc_next) {
		if (sp->sc_flags & X_FLAG) {
			/* System Administator specified don't start */
			continue;
		}
		(void) startpm(sp);
	}
}


/*
 * readutmpx - read the utmpx file to find out the ids of running port
 *		monitors (only called during a recover start up).  Note:
 *		after a sac failure, init will inherit all of the port
 *		monitors and should get the SIGCLD's if they die (and
 *		will clean up).  This is mainly for stuck processes,
 *		although init would get the SIGCLD when the stuckie gets
 *		killed, it doesn't hurt to have the sac check.  This is
 *		only done once.
 *
 */


void
readutmpx()
{
	struct sactab *sp;	/* working pointer */
	struct sactab *savesp;	/* rembered port monitor match */
	struct utmpx *uxp;	/* working pointer */

	setutxent();
	while (uxp = getutxent()) {
		/* we're only interested in login processes */
		if (uxp->ut_type != LOGIN_PROCESS)
			continue;
		if (uxp->ut_user[sizeof (uxp->ut_user) - 1] == '\0') {

/*
 * possible port monitor and name is short enough to do a normal compare
 */

			sp = findpm(uxp->ut_user);
			if (sp && (sp->sc_sstate == UNKNOWN)) {
				/* found one */
				(void) memcpy(sp->sc_utid, uxp->ut_id, IDLEN);
				sp->sc_pid = uxp->ut_pid;
			}
		} else {

/*
 * possible port monitor name, but it could have been truncated.  If
 * a match is found on a unique prefix, then it should be the correct
 * entry.  If an ambiguity is found, ignore the entry, init will clean
 * up the entry if it dies.
 */

			savesp = NULL;
			for (sp = Sactab; sp; sp = sp->sc_next) {
				if (strncmp(uxp->ut_user, sp->sc_tag,
				    sizeof (uxp->ut_user)) == 0) {
					if (savesp) {
						/* already found a match */
						savesp = NULL;
						(void) sprintf(Scratch,
						"ambiguous utmpx entry <%.8s>",
							sp->sc_tag);
						log(Scratch);
						break;
					} else {
						savesp = sp;
					}
				}
			}
			if (savesp && (savesp->sc_sstate == UNKNOWN)) {
				/* found it */
				(void) memcpy(savesp->sc_utid, uxp->ut_id,
								IDLEN);
				savesp->sc_pid = uxp->ut_pid;
			}
		}
	}
	endutxent();
}


/*
 * startpm - start a particular PM, return code:
 *		-1: _pid file locked
 *		-2: any other reason
 *
 *	args:	sp - pointer to sac's port monitor information for
 *		     designated port monitor
 */

int
startpm(struct sactab *sp)
{
	sigset_t cset;		/* for signal handling */
	sigset_t tset;		/* for signal handling */
	pid_t pid;		/* pid of new port monitor */
	pid_t checklock();

#ifdef DEBUG
	debug("in startpm");
#endif
	if (checklock(sp)) {
		(void) sprintf(Scratch,
			"could not start <%s> - _pid file locked", sp->sc_tag);
		log(Scratch);
		return (-1);
	}

	(void) sprintf(Scratch, "%s/_pmpipe", sp->sc_tag);
	if (access(Scratch, 0) != 0) {
		/* not there, create one */
		(void) umask(0);
		if (mknod(Scratch, S_IFIFO | 0600, 0) < 0) {
			(void) sprintf(Scratch,
		"Could not create _pmpipe for port monitor <%s>, errno is %d",
			sp->sc_tag, errno);
			log(Scratch);
			return (-2);
		}
	}
	sp->sc_fd = open(Scratch, O_RDWR);
	if (sp->sc_fd < 0) {
		(void) sprintf(Scratch,
		"Could not open _pmpipe for port monitor <%s>, errno is %d",
			sp->sc_tag, errno);
		log(Scratch);
		return (-2);
	}

	/* in case child dies too quickly */
	(void) sigprocmask(SIG_SETMASK, NULL, &cset);
	tset = cset;
	(void) sigaddset(&tset, SIGCLD);
	(void) sigprocmask(SIG_SETMASK, &tset, NULL);
	if ((pid = fork()) < 0) {
		(void) sprintf(Scratch,
			"Could not fork port monitor <%s>", sp->sc_tag);
		log(Scratch);
		return (-2);
	} else if (!pid) {
		startit(sp);
		/* no return */
	}

/*
 * clean up old utmpx if its there
 */

	cleanutx(sp);

/*
 * create a utmpx entry and set initial states
 */

	account(sp, pid);
	sp->sc_pstate = STARTING;
	if (sp->sc_lstate == NOTRUNNING)
		sp->sc_sstate = (sp->sc_flags & D_FLAG) ? DISABLED : ENABLED;
	else
		sp->sc_sstate = sp->sc_lstate;
	sp->sc_ok = 1;
	sp->sc_exit = 0;
	sp->sc_pid = pid;
	/* ok to take signals now that the table is up-to-table */
	(void) sigprocmask(SIG_SETMASK, &cset, NULL);
	return (0);
}


/*
 * cleanutx - clean out a utmpx record for a port monitor
 *
 *	args:	sp - pointer to sac's port monitor information for
 *		     designated port monitor
 */


void
cleanutx(struct sactab *sp)
{
	int i;			 /* scratch variable */
	int zerocheck;		  /* scratch variable */
	char buf[SIZE];		 /* scratch buffer */
	pam_handle_t *pamh;		/* PAM auth descriptor */
	struct utmpx ut;
	struct utmpx *up;
	int pid;
	char user[sizeof (up->ut_user) + 1];
	char ttyn[sizeof (up->ut_line) + 1];
	char rhost[sizeof (up->ut_host) + 1];
/*
 * check to see if there is a utmpx entry to clean up (indicated by a non
 * zero utmpx id
 */
	zerocheck = 0;
	for (i = 0; i < IDLEN; ++i) {
		zerocheck += sp->sc_utid[i];
	}
	if (zerocheck == 0)
		return;

	pid = sp->sc_pid;
	setutxent();
	while (up = getutxent()) {
		if (up->ut_pid == pid) {
			if (up->ut_type == DEAD_PROCESS) {
				/*
				 * Cleaned up elsewhere.
				 */
				break;
			}
			strncpy(user, up->ut_user, sizeof (up->ut_user));
			user[sizeof (up->ut_user)] = '\0';
			strncpy(ttyn, up->ut_line, sizeof (up->ut_line));
			ttyn[sizeof (up->ut_line)] = '\0';
			strncpy(rhost, up->ut_host, sizeof (up->ut_host));
			rhost[sizeof (up->ut_host)] = '\0';

			if ((pam_start("sac", user, NULL, &pamh)) ==
								PAM_SUCCESS) {
				(void) pam_set_item(pamh, PAM_TTY, ttyn);
				(void) pam_set_item(pamh, PAM_RHOST, rhost);
				(void) pam_close_session(pamh, 0);
				pam_end(pamh, PAM_SUCCESS);
			}

			up->ut_type = DEAD_PROCESS;
			up->ut_exit.e_termination = WTERMSIG(sp->sc_exit);
			up->ut_exit.e_exit = WEXITSTATUS(sp->sc_exit);
			if (sp->sc_utid != NULL)
				(void) memcpy(up->ut_id, sp->sc_utid,
					sizeof (up->ut_id));
			(void) time(&up->ut_tv.tv_sec);
			if (modutx(up) == NULL) {
				/*
				 * Since modutx failed we'll
				 * write out the new entry
				 * ourselves.
				 */
				(void) pututxline(up);
				updwtmpx("wtmpx", up);
			}
			break;
		}
	}
	endutxent();
}

/*
 * account - create a utmp record for a port monitor
 *
 *	args:	pid - process id of port monitor
 */


void
account(struct sactab *sp, pid_t pid)
{
	struct utmpx utmpx;			/* prototype utmpx entry */
	struct utmpx *up = &utmpx;		/* and a pointer to it */

	(void) memset(up, '\0', sizeof (utmpx));
	(void) strncpy(up->ut_user, sp->sc_tag, sizeof (up->ut_user));
	up->ut_pid = pid;
	up->ut_type = LOGIN_PROCESS;
	up->ut_id[0] = 'P';
	up->ut_id[1] = 'M';
	up->ut_id[2] = SC_WILDC;
	up->ut_id[3] = SC_WILDC;
	(void) time(&up->ut_xtime);
	if (makeutx(up) == NULL) {
		log("Could not create utmpx entry");
		(void) memset(sp->sc_utid, '\0', IDLEN);
	} else {
		(void) memcpy(sp->sc_utid, up->ut_id, IDLEN);
	}
}


/*
 * startit - finish starting a particular port monitor, establish environment,
 *		etc. (Note: this is the child at this point)
 *
 *	args:	sp - pointer to sac's port monitor information for
 *		     designated port monitor
 */


void
startit(struct sactab *sp)
{
	static char istate[SIZE];	/* place to put ISTATE env var. */
	static char pmtag[SIZE];	/* place to put PMTAG env var. */
	char **argvp;			/* arglist for PM */
	int i;				/* loop control variable */
	long ndesc;			/* # of file descriptors configured */
	int ret;			/* return value from doconfig */
	sigset_t cset;			/* for signal handling */
	sigset_t tset;			/* for signal handling */

/*
 * establish the home directory
 */

	if (chdir(sp->sc_tag) < 0) {
		(void) sprintf(Scratch,
			"Cannot chdir to <%s/%s>, port monitor not started",
			HOME, sp->sc_tag);
		log(Scratch);
		exit(1);
	}

/*
 * interpret the configuration script, pass an invalid fd, shouldn't be
 * doing pushes and pops in this script
 */

	(void) sigprocmask(SIG_SETMASK, NULL, &cset);
	tset = cset;
	(void) sigaddset(&tset, SIGCLD);
	(void) sigprocmask(SIG_SETMASK, &tset, NULL);
	if ((ret = doconfig(-1, "_config", 0)) != 0) {
		if (ret == -1) {
			(void) sprintf(Scratch,
				"system error in _config script for <%s>",
				sp->sc_tag);
			log(Scratch);
			exit(1);
		} else {
			(void) sprintf(Scratch,
				"Error in _config script for <%s>: line %d",
				sp->sc_tag, ret);
			log(Scratch);
			exit(1);
		}
	}

/*
 * add the promised environment variables
 */

	if (sp->sc_lstate == NOTRUNNING)
		(void) sprintf(istate, "ISTATE=%s",
			(sp->sc_flags & D_FLAG) ? "disabled" : "enabled");
	else
		(void) sprintf(istate, "ISTATE=%s",
			(sp->sc_lstate == DISABLED) ? "disabled" : "enabled");
	if (putenv(istate)) {
		(void) sprintf(Scratch,
			"can't expand port monitor <%s> environment",
			sp->sc_tag);
		log(Scratch);
		exit(1);
	}
	(void) sprintf(pmtag, "PMTAG=%s", sp->sc_tag);
	if (putenv(pmtag)) {
		(void) sprintf(Scratch,
			"can't expand port monitor <%s> environment",
			sp->sc_tag);
		log(Scratch);
		exit(1);
	}

/*
 * build an argv
 */

	argvp = mkargv(sp);

	(void) sprintf(Scratch, "starting port monitor <%s>", sp->sc_tag);
	log(Scratch);
	ndesc = ulimit(4, 0L);
	for (i = 0; i < ndesc; i++)
		(void) fcntl(i, F_SETFD, 1);
	/* restore orignal handlers and mask */
	(void) sigaction(SIGPOLL, &Sigpoll, NULL);
	(void) sigaction(SIGCLD, &Sigcld, NULL);
	(void) sigaction(SIGALRM, &Sigalrm, NULL);
	(void) sigprocmask(SIG_SETMASK, &Origmask, NULL);
	(void) execve(argvp[0], argvp, environ);
	(void) sprintf(Scratch, "exec of port monitor <%s> failed", sp->sc_tag);
	log(Scratch);
	exit(1);
}


/*
 * mkargv - Given a pointer to a struct sactab, construct argv
 *		for an exec system call.
 *
 *	args:	sp - pointer to sac's port monitor information for
 *		     designated port montior
 */


#define	NARGS	50	/* max # of args */

static char *newargv[NARGS];	/* place for argv list */
static char *delim = " \t'\"";	/* delimiter list */

char **
mkargv(struct sactab *sp)
{
	char **argvp = newargv;			/* scratch pointer */
	char *p = sp->sc_cmd;			/* working pointer */
	char delch;				/* delimiter seen */
	char *savep;				/* scratch pointer */
	char *tp;				/* scratch pointer */

	*argvp = 0;
	savep = p;
	while (p && *p) {
		if (p = strpbrk(p, delim)) {
			switch (*p) {
			case ' ':
			case '\t':
				/* "normal" cases */
				*p++ = '\0';
				*argvp++ = savep;
				/* zap trailing white space */
				while (isspace(*p))
					p++;
				savep = p;
				break;
			case '"':
			case '\'':
				/* found a string */
				delch = *p; /* remember the delimiter */
				savep = ++p;

/*
 * We work the string in place, embedded instances of the string delimiter,
 * i.e. \" must have the '\' removed.  Since we'd have to do a compare to
 * decide if a copy were needed, it's less work to just do the copy, even
 * though it is most likely unnecessary.
 */

				tp = p;
				for (;;) {
					if (*p == '\0') {
						(void) sprintf(Scratch,
	"invalid command line, non-terminated string for port monitor %s",
							sp->sc_tag);
						log(Scratch);
						exit(1);
					}
					if (*p == delch) {
						if (*(tp - 1) == '\\') {
							/* \delim */
							*(tp - 1) = *p;
							p++;
						} else { /* end of string */
							*tp = 0;
							*argvp++ = savep;
							p++;
						/* zap trailing white space */
							while (isspace(*p))
								p++;
							savep = p;
							break;
						}
					} else {
						*tp++ = *p++;
					}
				}
				break;
			default:
				log("Internal error in parse routine");
				exit(1);
			}
		}
		else
			*argvp++ = savep;
	}
	*argvp = 0;
	return (newargv);
}


/*
 * pollpms - send out sanity polls, if sc_sstate and sc_pstate are
 *	the same (everyone agrees on the state) or if SAC thinks PM
 *	should be stopping, send out a status message;
 *	otherwise, send out a message indicating the state the SAC
 *	thinks the PM should be entering
 */

void
pollpms()
{
	struct sactab *sp;	/* working pointer */
	struct sacmsg sacmsg;		/* message to send to PM */

#ifdef DEBUG
	debug("alarm went off");
#endif
	for (sp = Sactab; sp; sp = sp->sc_next) {
		if (sp->sc_pstate == NOTRUNNING || sp->sc_pstate == FAILED) {
			/* don't bother if no one is home */
			continue;
		}
		if (sp->sc_ok == 0) {
			/* PM has stopped responding */
			pollfail(sp, RESP);
			continue;
		}

/*
 * note - if we're in recovery, a SC_STATUS message is sent
 * (sc_sstate = UNKNOWN and sc_pstate = UNKNOWN)
 */

		if (sp->sc_sstate == sp->sc_pstate) {
			sacmsg.sc_type = SC_STATUS;
			sacmsg.sc_size = 0;
		} else {
			switch (sp->sc_sstate) {
			case ENABLED:
				sacmsg.sc_type = SC_ENABLE;
				sacmsg.sc_size = 0;
				break;
			case DISABLED:
				sacmsg.sc_type = SC_DISABLE;
				sacmsg.sc_size = 0;
				break;
			case STARTING:
			case STOPPING:
			case NOTRUNNING:
			case FAILED:
			case UNKNOWN:
				/*
				 * if NOTRUNNING or FAILED, PM will probably
				 * not respond to poll, that's how we detect
				 * that it's gone
				 */
				sacmsg.sc_type = SC_STATUS;
				sacmsg.sc_size = 0;
				break;
			default:
				error(E_BADSTATE, EXIT);
			}
		}

		/* send the message */
		sendpmmsg(sp, &sacmsg);
		sp->sc_ok = 0;
	}
	(void) alarm(Stime);
}


/*
 * reap - clean up dead children, equivalent to a "fast" poll failure
 *
 *	args:	signo - signal #
 */

void
reap(int signo)
{
	struct sactab *sp;		/* working pointer */
	pid_t pid;			/* returned pid from wait */
	int status;			/* returned status from wait */

	pid = wait(&status);
	for (sp = Sactab; sp; sp = sp->sc_next) {
		if (sp->sc_pid == pid)
			break;
	}
	if (sp == NULL) {
		/* not from a port monitor we know about */
		return;
	}
	sp->sc_exit = status;
	/* only call pollfail for "stuck" and stopping processes */
	if (sp->sc_pstate != NOTRUNNING && sp->sc_pstate != FAILED)
		pollfail(sp, DEATH);
}


/*
 * pollfail - handle the case where a PM stops responding to a sanity poll
 *
 *	args:	sp - pointer to sac's port monitor information for
 *		     designated port monitor
 *		reason - RESP or DEATH (indicates why pollfail called)
 */


void
pollfail(struct sactab *sp, int reason)
{
	char buf[SIZE];			/* scratch buffer */
	sigset_t cset;			/* for signal handling */
	sigset_t tset;			/* for signal handling */

#ifdef DEBUG
	debug("in pollfail");
#endif

/* first, remove the utmpx entry and clean up any links */

	cleanutx(sp);

	if (sp->sc_pstate == STOPPING) {
		(void) sprintf(buf, "<%s> has stopped", sp->sc_tag);
		log(buf);
		sp->sc_pstate = NOTRUNNING;
		sp->sc_lstate = NOTRUNNING;
		(void) close(sp->sc_fd);
	} else {

/*
 * PM in trouble - if it's still there, try to put it out of its misery
 * We play with SIGCLD here to that after SIGKILL is sent, the catcher
 * routine reap() is not called until we're ready (note: when a catcher
 * is established for SIGCLD and any zombies are present, the signal is
 * immediately received)
 */

		(void) sigprocmask(SIG_SETMASK, NULL, &cset);
		tset = cset;
		(void) sigaddset(&tset, SIGCLD);
		(void) sigprocmask(SIG_SETMASK, &tset, NULL);
		(void) sendsig(sp, SIGKILL);
		if (sp->sc_rscnt < sp->sc_rsmax) {
			/* try to restart it */
			if (reason == RESP)
				(void) sprintf(buf,
	"<%s> stopped responding to sanity polls - trying to restart",
					sp->sc_tag);
			else
				(void) sprintf(buf,
					"<%s> has died - trying to restart",
					sp->sc_tag);
			log(buf);
			sp->sc_rscnt++;
			(void) close(sp->sc_fd);
			(void) startpm(sp);
		} else {
			sp->sc_sstate = sp->sc_pstate = FAILED;
			(void) close(sp->sc_fd);
			(void) sprintf(buf, "<%s> has FAILED", sp->sc_tag);
			log(buf);
		}
	}
	(void) sigprocmask(SIG_SETMASK, &cset, NULL);
}


/*
 * readpipe - read messages from _sacpipe
 */


void
readpipe()
{
	struct pmmsg pmmsg;			/* incoming message */
	struct pmmsg *pp = &pmmsg;		/* and a pointer to it */
	struct sactab *sp;			/* working pointer */
	int ret;				/* return value from read */

/*
 * This routine's main purpose is to maintain the state associated with
 * each of the known port monitors.  Because it may be confusing, following
 * is a brief discussion of what is happening.  Three different views of
 * a port monitor's state exist: sc_sstate, sc_pstate, and sc_lstate.
 * sc_sstate is the state in which the sac has been instructed to place
 * a port monitor.  sc_lstate is essentially a shadow of this field, however,
 * it will only take on the values ENABLED, DISABLED, and NOTRUNNING.
 * sc_lstate is used if a port monitor dies to restart it in the state in
 * which it was last running.  sc_pstate is the last state that the port
 * monitor reported itself in.  Note that if the administrator specifies
 * a state change, there is a window where sc_sstate and sc_pstate will
 * be different (until the port monitor enacts and acknowledges the change).
 *
 * These states interact with the polling loop to determine which message
 * should be sent to a port monitor.  If the states agree, an SC_STATUS
 * is sent.  If they disagree, the appropriate message to put the port
 * monitor in the correct state is sent (SC_ENABLE or SC_DISABLE).  sc_pstate
 * is the state that is reported back to an AC_STATUS request.  Finally,
 * when in recovery (sc_sstate and sc_pstate both = UNKNOWN), the sac will
 * take the port monitor's reported state as the true state.  This is the
 * only instance in which a port monitor can cause sc_sstate to change.
 */

	for (;;) {
		if (read(Sfd, pp, sizeof (pmmsg)) < 0) {
			if (errno != EINTR)
				error(E_BADREAD, EXIT);
			continue;
		}

		while (pp->pm_size) {

/*
 * there's data after the header, unfortunately, we don't understand
 * any of it because only class 1 (no data) messages are defined.  Just
 * flush it
 */

			ret = read(Sfd, Scratch,
				(pp->pm_size > SIZE) ? (unsigned) SIZE :
						(unsigned) pp->pm_size);
			if (ret < 0) {
				if (errno != EINTR)
					error(E_BADREAD, EXIT);
				continue;
			}
			else
				pp->pm_size -= ret;
		}

		sp = findpm(pp->pm_tag);
		if (sp == NULL) {
			log("message from unknown process");
			continue;
		}
		switch (pp->pm_type) {
		case PM_UNKNOWN:
			(void) sprintf(Scratch,
				"port monitor <%s> didn't recognize message",
				sp->sc_tag);
			log(Scratch);
			/* fall through */
		case PM_STATUS:
			/*
			 * paranoia check, if port monitor reports garbage
			 * state, pretend it said UNKNOWN
			 */
			if (!validstate(pp->pm_state)) {
				pp->pm_state = UNKNOWN;
				(void) sprintf(Scratch,
				"port monitor <%s> reporting invalid state",
					sp->sc_tag);
				log(Scratch);
			}
			if (sp->sc_sstate == sp->sc_pstate) {
				/* everyone agrees on the current state */
				if (sp->sc_sstate == UNKNOWN) {
					/* special case for recovery */
					sp->sc_sstate = pp->pm_state;
					sp->sc_pstate = pp->pm_state;
					if (pp->pm_state == ENABLED ||
						pp->pm_state == DISABLED)
					/* sc_lstate NOTRUNNING by default */
						sp->sc_lstate = pp->pm_state;
				}
				if (pp->pm_state != sp->sc_pstate) {
					/*
					 * something isn't right here, PM
					 * changed state without orders, try
					 * to restore to correct state
					 */
					sp->sc_pstate = pp->pm_state;
				}
			} else if (sp->sc_sstate == pp->pm_state) {
				/* PM changed to state requested */
				(void) sprintf(Scratch,
			"port monitor <%s> changed state from %s to %s",
					sp->sc_tag, pstate(sp->sc_pstate),
					pstate(pp->pm_state));
				log(Scratch);
				sp->sc_pstate = pp->pm_state;
			} else if (sp->sc_pstate != pp->pm_state) {
				/*
				 * something isn't right here, PM isn't
				 * in the state it was, nor is it in the
				 * state we just tried to put it in, try
				 * to restore to correct state if we should
				 */
				if (sp->sc_pstate != STOPPING)
					sp->sc_pstate = pp->pm_state;
			}
			break;
		default:
			(void) sprintf(Scratch,
		"port monitor <%s> sent an invalid message - ignoring it",
				sp->sc_tag);
			log(Scratch);
			break;
		}
		/* no matter what, PM did answer the poll */
		sp->sc_ok = 1;
		/* Note the messages it understands */
		sp->sc_maxclass = pp->pm_maxclass;
	}
}


/*
 * validstate - determine if arg s a valid return state from a port monitor
 *	return 1 if ok, 0 otherwise
 *
 *	args:	state - state to be verified
 */
int
validstate(unchar state)
{
	switch (state) {
	case PM_ENABLED:
	case PM_DISABLED:
	case PM_STARTING:
	case PM_STOPPING:
		return (1);
	default:
		return (0);
	}
}


/*
 * mk_cmd_pipe - create the command pipe used by sacadm
 */

int
mk_cmd_pipe()
{
	int fds[2];			/* pipe endpoints */
	int fd;				/* scratch file descriptor */

	/* make sure there is a file here to mount on */
	(void) unlink(CMDPIPE);
	fd = open(CMDPIPE, O_RDWR | O_CREAT, 0600);
	if (fd < 0)
		error(E_CMDPIPE, EXIT);
	close(fd);
	if (pipe(fds) < 0)
		error(E_PIPE, EXIT);
	if (fattach(fds[0], CMDPIPE) < 0)
		error(E_FATTACH, EXIT);
	return (fds[1]);
}


/*
 * startpoll - enable polling on command pipe by setting up to catch SIGPOLL
 */


void
startpoll()
{
	struct sigaction sigact;	/* for signal handling */

	if (ioctl(Cfd, I_SETSIG, S_INPUT) < 0)
		error(E_SETSIG, EXIT);
	sigact.sa_flags = 0;
	sigact.sa_handler = sigpoll;
	(void) sigemptyset(&sigact.sa_mask);
	(void) sigaddset(&sigact.sa_mask, SIGPOLL);
	(void) sigaction(SIGPOLL, &sigact, &Sigpoll);
}
