/*	Copyright (c) 1990 UNIX System Laboratories, Inc.	*/
/*	Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF     	*/
/*	UNIX System Laboratories, Inc.                     	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/


# ident	"@(#)saf:misc.c	1.6"

# include <stdio.h>
# include <unistd.h>
# include <fcntl.h>
# include <sys/types.h>
# include <signal.h>
# include <sys/stat.h>
# include <poll.h>
# include "misc.h"
# include "msgs.h"
# include "extern.h"
# include <sac.h>
# include "adm.h"
# include "structs.h"


/*
 * findpm - find a port monitor entry
 *
 *	args:	tag - tag of desired port monitor
 */


struct sactab *
findpm(tag)
register char *tag;
{
	register struct sactab *sp;	/* working pointer */

	for (sp = Sactab; sp; sp = sp->sc_next) {
		if (!strcmp(tag, sp->sc_tag))
			return(sp);
	}
	return(NULL);
}


/*
 * sigpoll - handle messages coming in on the command pipe (SIGPOLL signal
 *		handler)
 */


void
sigpoll()
{
	struct pollfd fds;			/* array of fds to poll */
	struct admcmd cmd;			/* incoming command */
	register struct admcmd *ap = &cmd;	/* and a pointer to it */
	struct admack ack;			/* acknowledgment */
	register struct admack *ak = &ack;	/* and a pointer to it */
	register struct sactab *sp;		/* working pointer */
	struct sacmsg sacmsg;			/* message to port monitor */
	char **data;				/* "dumped" sactab */
	char *p;				/* scratch pointer */
	register int i;				/* loop control variable */
	int ret;				/* return value */
	sigset_t cset;				/* for signal handling */
	sigset_t tset;				/* for signal handling */

# ifdef DEBUG
	debug("in sigpoll");
# endif
	fds.fd = Cfd;
	fds.events = POLLIN;
	fds.revents = 0;
	if (poll(&fds, 1, 0) < 0)
		error(E_POLL, EXIT);
	switch (fds.revents) {
	case POLLIN:
		if (read(Cfd, ap, sizeof(struct admcmd)) < 0) {
			error(E_READ, EXIT);
		}
		switch (ap->ac_mtype) {

/*
 * request to start a port monitor
 */

		case AC_START:
# ifdef DEBUG
			(void) sprintf(Scratch, "Got AC_START for <%s>", ap->ac_tag);
			log(Scratch);
# endif
			if ((sp = findpm(ap->ac_tag)) == NULL) {
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_NOPM;
				ak->ak_size = 0;
				sendack(ak);
				break;
			}
			switch (sp->sc_sstate) {
			case UNKNOWN:
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_RECOVER;
				ak->ak_size = 0;
				sendack(ak);
				break;
			case FAILED:
			case NOTRUNNING:
				sp->sc_rscnt = 0;	/* fresh start in life */
				if (ret = startpm(sp)) {
					ak->ak_pid = ap->ac_pid;
					if (ret == -1)
						ak->ak_resp = AK_PMLOCK;
					else
						ak->ak_resp = AK_REQFAIL;
					ak->ak_size = 0;
					sendack(ak);
					break;
				}
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_ACK;
				ak->ak_size = 0;
				sendack(ak);
				break;
			case ENABLED:
			case DISABLED:
			case STARTING:
			case STOPPING:
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_PMRUN;
				ak->ak_size = 0;
				sendack(ak);
				break;
			}
			break;

/*
 * request to kill a port monitor
 */

		case AC_KILL:
# ifdef DEBUG
			(void) sprintf(Scratch, "Got AC_KILL for <%s>", ap->ac_tag);
			log(Scratch);
# endif
			if ((sp = findpm(ap->ac_tag)) == NULL) {
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_NOPM;
				ak->ak_size = 0;
				sendack(ak);
				break;
			}
			switch (sp->sc_sstate) {
			case UNKNOWN:
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_RECOVER;
				ak->ak_size = 0;
				sendack(ak);
				break;
			case NOTRUNNING:
			case FAILED:
			case STOPPING:
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_PMNOTRUN;
				ak->ak_size = 0;
				sendack(ak);
				break;
			case STARTING:
			case ENABLED:
			case DISABLED:
				(void) sigprocmask(SIG_SETMASK, NULL, &cset);
				tset = cset;
				(void) sigaddset(&tset, SIGALRM);
				(void) sigaddset(&tset, SIGCLD);
				(void) sigprocmask(SIG_SETMASK, &tset, NULL);
				if (sendsig(sp, SIGTERM)) {
					(void) sprintf(Scratch, "could not send SIGTERM to <%s>", sp->sc_tag);
					log(Scratch);
					ak->ak_pid = ap->ac_pid;
					ak->ak_resp = AK_NOCONTACT;
					ak->ak_size = 0;
					sendack(ak);
					(void) sigprocmask(SIG_SETMASK, &cset, NULL);
					break;
				}
				/* signal sent ok */
				sp->sc_lstate = NOTRUNNING;
				sp->sc_sstate = NOTRUNNING;
				sp->sc_pstate = STOPPING;
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_ACK;
				ak->ak_size = 0;
				sendack(ak);
				(void) sprintf(Scratch, "terminating <%s>", sp->sc_tag);
				log(Scratch);
				(void) sigprocmask(SIG_SETMASK, &cset, NULL);
				break;
			}
			break;

/*
 * request to enable a port monitor
 */

		case AC_ENABLE:
# ifdef DEBUG
			(void) sprintf(Scratch, "Got AC_ENABLE for <%s>", ap->ac_tag);
			log(Scratch);
# endif
			if ((sp = findpm(ap->ac_tag)) == NULL) {
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_NOPM;
				ak->ak_size = 0;
				sendack(ak);
				break;
			}
			switch (sp->sc_sstate) {
			case UNKNOWN:
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_RECOVER;
				ak->ak_size = 0;
				sendack(ak);
				break;
			case NOTRUNNING:
			case FAILED:
			case STOPPING:
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_PMNOTRUN;
				ak->ak_size = 0;
				sendack(ak);
				break;
			case STARTING:
			case DISABLED:
				sacmsg.sc_type = SC_ENABLE;
				sacmsg.sc_size = 0;
				sp->sc_sstate = ENABLED;
				sp->sc_lstate = ENABLED;
				sendpmmsg(sp, &sacmsg);
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_ACK;
				ak->ak_size = 0;
				sendack(ak);
				break;
			case ENABLED:
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_ACK;
				ak->ak_size = 0;
				sendack(ak);
				break;
			}
			break;

/*
 * request to disable a port monitor
 */

		case AC_DISABLE:
# ifdef DEBUG
			(void) sprintf(Scratch, "Got AC_DISABLE for <%s>", ap->ac_tag);
			log(Scratch);
# endif
			if ((sp = findpm(ap->ac_tag)) == NULL) {
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_NOPM;
				ak->ak_size = 0;
				sendack(ak);
				break;
			}
			switch (sp->sc_sstate) {
			case UNKNOWN:
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_RECOVER;
				ak->ak_size = 0;
				sendack(ak);
				break;
			case NOTRUNNING:
			case FAILED:
			case STOPPING:
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_PMNOTRUN;
				ak->ak_size = 0;
				sendack(ak);
				break;
			case STARTING:
			case ENABLED:
				sacmsg.sc_type = SC_DISABLE;
				sacmsg.sc_size = 0;
				sp->sc_sstate = DISABLED;
				sp->sc_lstate = DISABLED;
				sendpmmsg(sp, &sacmsg);
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_ACK;
				ak->ak_size = 0;
				sendack(ak);
				break;
			case DISABLED:
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_ACK;
				ak->ak_size = 0;
				sendack(ak);
				break;
			}
			break;

/*
 * request for port monitor status information
 */

		case AC_STATUS:
# ifdef DEBUG
			log("Got AC_STATUS");
# endif
			/* get all the info in one convenient place */
			data = dump_table();
			if ((data == NULL) && (Nentries > 0)) {
				/* something bad happened in dump_table */
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_REQFAIL;
				ak->ak_size = 0;
				sendack(ak);
				break;
			}
			/* count how big it is */
			ak->ak_size = 0;
			for (i = 0; i < Nentries; ++i)
				ak->ak_size += strlen(data[i]);
# ifdef DEBUG
			(void) sprintf(Scratch, "ak_size is %d", ak->ak_size);
			debug(Scratch);
# endif
			/* get a contiguous chunk */
			if ((p = malloc((unsigned) (ak->ak_size + 1))) == NULL) {
				error(E_MALLOC, CONT);
				for (i = 0; i < Nentries; ++i)
					free(data[i]);
				free((char *) data);
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_REQFAIL;
				ak->ak_size = 0;
				sendack(ak);
				break;
			}
			/* condense the data into the contiguous chunk */
			*p = '\0';
			for (i = 0; i < Nentries; ++i) {
				(void) strcat(p, data[i]);
				free(data[i]);
			}
# ifdef DEBUG
			debug(p);
# endif
			if (data)
				free((char *) data);
			/* ak->ak_size was set above */
			ak->ak_pid = ap->ac_pid;
			ak->ak_resp = AK_ACK;
			sendack(ak);
			if (ak->ak_size)
				if (write(Cfd, p, (unsigned) ak->ak_size) != ak->ak_size)
					log("could not send info");
			free(p);
			break;

/*
 * request for sac to read sactab
 */

		case AC_SACREAD:
# ifdef DEBUG
			log("Got AC_SACREAD");
# endif
			ak->ak_pid = ap->ac_pid;
			ak->ak_resp = AK_ACK;
			ak->ak_size = 0;
			read_table(TRUE);
			sendack(ak);
			break;

/*
 * request for port monitor to read _pmtab
 */

		case AC_PMREAD:
# ifdef DEBUG
			(void) sprintf(Scratch, "Got AC_PMREAD for <%s>", ap->ac_tag);
			log(Scratch);
# endif
			if ((sp = findpm(ap->ac_tag)) == NULL) {
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_NOPM;
				ak->ak_size = 0;
				sendack(ak);
				break;
			}
			switch (sp->sc_sstate) {
			case UNKNOWN:
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_RECOVER;
				ak->ak_size = 0;
				sendack(ak);
				break;
			case NOTRUNNING:
			case FAILED:
			case STOPPING:
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_PMNOTRUN;
				ak->ak_size = 0;
				sendack(ak);
				break;
			case STARTING:
			case ENABLED:
			case DISABLED:
				sacmsg.sc_type = SC_READDB;
				sacmsg.sc_size = 0;
				sendpmmsg(sp, &sacmsg);
				ak->ak_pid = ap->ac_pid;
				ak->ak_resp = AK_ACK;
				ak->ak_size = 0;
				sendack(ak);
				break;
			}
			break;
/*
 * garbled message
 */

		default:
			(void) sprintf(Scratch, "Got unknown message for <%s>", ap->ac_tag);
			log(Scratch);
			ak->ak_pid = ap->ac_pid;
			ak->ak_resp = AK_UNKNOWN;
			ak->ak_size = 0;
			sendack(ak);
			break;
		}
		break;
	default:
		error(E_POLL, EXIT);
	}
}


/*
 * sendack - send a response to the administrative command
 *
 *	args:	ap - pointer to acknowlegment message
 */

void
sendack(ap)
struct admack *ap;
{
# ifdef DEBUG
	debug("in sendack");
# endif
	if (write(Cfd, ap, sizeof(struct admack)) != sizeof(struct admack))
		log("Could not send ack");
}


/*
 * sendpmmsg - send a message to a PM.  Note: sc_size is always 0 in
 *	       this version so just send the header.
 *
 *	args:	sp - pointer to sac's port monitor information for
 *		     designated port monitor
 *		sm - pointer to message to send
 */

void
sendpmmsg(sp, sm)
register struct sactab *sp;
register struct sacmsg *sm;
{
	char buf[SIZE];			/* scratch buffer */

# ifdef DEBUG
	debug("in sendpmmsg");
# endif
	if (write(sp->sc_fd, sm, sizeof(struct sacmsg)) != sizeof(struct sacmsg)) {
		(void) sprintf(buf, "message to <%s> failed", sp->sc_tag);
		log(buf);
	}
}

/*
 * sendsig - send a signal to the port monitor
 * 
 *	args:	sp - pointer to sac's port monitor infomation for
 *		     designated port monitor
 *		signo - signal number to send
 */


sendsig(sp, signo)
register struct sactab *sp;
int signo;
{
	pid_t pid;	/* pid of designated port monitor */
	pid_t checklock();

# ifdef DEBUG
	(void) sprintf(Scratch, "in sendsig - sending signo %d to %s", signo, sp->sc_tag);
	debug(Scratch);
# endif
	if (pid = checklock(sp)) {
		if (kill(pid, signo) < 0) {
# ifdef DEBUG
			debug("in sendsig - kill failed");
# endif
			return(-1);
		}
		else
			return(0);
	}
	else {
# ifdef DEBUG
		debug("in sendsig - checklock failed");
# endif
		return(-1);
	}
}


/*
 * checklock - check to see if a _pid file is locked
 *		if so, return pid in file, else 0
 *
 *	args:	sp - pointer to sac's port monitor infomation for
 *		     designated port monitor
 */

pid_t
checklock(sp)
register struct sactab *sp;
{
	int fd;			/* scratch file descriptor */
	char buf[SIZE];		/* scratch buffer */
	int ret;		/* return value */

# ifdef DEBUG
	debug("in checklock");
# endif
	(void) sprintf(Scratch, "%s/%s/_pid", HOME, sp->sc_tag);
	fd = open(Scratch, O_RDONLY);
	if (fd < 0) {
		(void) sprintf(Scratch, "can not open _pid file for <%s>", sp->sc_tag);
		log(Scratch);
		return((pid_t)0);
	}
	if (lockf(fd, F_TEST, 0) < 0) {
		if ((ret = read(fd, buf, SIZE - 1)) < 0) {
			(void) close(fd);
			return((pid_t)0);
		}
		(void) close(fd);
		/* in case pid wasn't null-terminated */
		buf[ret] = '\0';
		return((pid_t)atol(buf));
	}
	(void) close(fd);
	return((pid_t)0);
}
