OpenSolaris_b135/cmd/ttymon/tmchild.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include	<stdio.h>
#include	<stdlib.h>
#include	<fcntl.h>
#include	<errno.h>
#include	<sys/types.h>
#include	<termio.h>
#include	<string.h>
#include	<signal.h>
#include	<poll.h>
#include	<unistd.h>
#include 	"sys/stropts.h"
#include 	<sys/resource.h>
#include	"sac.h"
#include	"ttymon.h"
#include	"tmstruct.h"
#include	"tmextern.h"
#ifdef	SYS_NAME
#include	<sys/utsname.h>
#endif

static void openline();
static void invoke_service();
static char	*do_autobaud();
static	struct	Gdef	*next_speed();
static int check_hup();

extern	struct	Gdef	*get_speed();
extern struct strbuf *peek_ptr, *do_peek();

/*
 * tmchild	- process that handles peeking data, determine baud rate
 *		  and invoke service on each individual port.
 *
 */
void
tmchild(pmtab)
struct	pmtab	*pmtab;
{
	register struct Gdef *speedef;
	char	*auto_speed = "";
	struct	sigaction sigact;

#ifdef	DEBUG
	debug("in tmchild");
#endif
	peek_ptr = NULL;
	if (pmtab->p_status != GETTY) {
		child_sigcatch();
		(void) close(PCpipe[0]); /* close parent end of the pipe */
		if (ioctl(PCpipe[1], I_SETSIG, S_HANGUP) == -1) {
			log("I_SETSIG failed: %s", strerror(errno));
			exit(1);
		}
		/*
		 * the following check is to make sure no hangup
		 * happens before registering for SIGPOLL
		 */
		if (check_hup(PCpipe[1])) {
#ifdef	DEBUG
			debug("PCpipe hungup, tmchild exiting");
#endif
			exit(1);
		}

		if (pmtab->p_ttyflags & (C_FLAG|B_FLAG)) {
			if (pmtab->p_fd > 0) {
				(void) close(pmtab->p_fd);
				pmtab->p_fd = 0;
			}
		}

		/*
		 * become the session leader so that a controlling tty
		 * will be allocated.
		 */
		(void) setsid();
	}
	speedef = get_speed(pmtab->p_ttylabel);
	openline(pmtab, speedef);
	if (pmtab->p_ttyflags & (C_FLAG|B_FLAG)) {
	    if (pmtab->p_fd >= 0) {
		if ((pmtab->p_modules != NULL)&&(*(pmtab->p_modules) != '\0')) {
		    if (push_linedisc(pmtab->p_fd, pmtab->p_modules, pmtab->p_device) == -1) {
			(void) close(pmtab->p_fd);
			return;
		    }
		}
	    }
	}
	if ((pmtab->p_ttyflags & C_FLAG) &&
	    (State != PM_DISABLED) &&
	    (!(pmtab->p_flags & X_FLAG))) {
		/*
		 * if "c" flag is set, and the port is not disabled
		 * invoke service immediately
		 */
		if (set_termio(0, speedef->g_fflags, NULL,FALSE,CANON) == -1) {
			log("set final termio failed");
			exit(1);
		}
		invoke_service(pmtab);
		exit(1);	/*NOTREACHED*/
	}
	if (speedef->g_autobaud & A_FLAG) {
		auto_speed = do_autobaud(pmtab, speedef);
	}
	if (set_termio(0, speedef->g_fflags, NULL, FALSE, CANON) == -1) {
		log("set final termio failed");
		exit(1);
	}
	if ((pmtab->p_ttyflags & (R_FLAG|A_FLAG)) ||
	    (pmtab->p_status == GETTY) || (pmtab->p_timeout > 0)) {
		write_prompt(1, pmtab, TRUE, TRUE);
		if (pmtab->p_timeout) {
			sigact.sa_flags = 0;
			sigact.sa_handler = timedout;
			(void) sigemptyset(&sigact.sa_mask);
			(void) sigaction(SIGALRM, &sigact, NULL);
			(void) alarm((unsigned)pmtab->p_timeout);
		}
	}
	else if ((pmtab->p_ttyflags & (B_FLAG)))
			write_prompt(pmtab->p_fd, pmtab, TRUE, TRUE);


	/* Loop until user is successful in invoking service. */
	for (;;) {

		/* Peek the user's typed response and respond appropriately. */
		switch (poll_data()) {
		case GOODNAME:
#ifdef	DEBUG
			debug("got GOODNAME");
#endif
			if (pmtab->p_timeout) {
				(void) alarm((unsigned)0);
				sigact.sa_flags = 0;
				sigact.sa_handler = SIG_DFL;
				(void) sigemptyset(&sigact.sa_mask);
				(void) sigaction(SIGALRM, &sigact, NULL);
			}
			if ((State == PM_DISABLED)||(pmtab->p_flags & X_FLAG)){
				write_prompt(1, pmtab, TRUE, FALSE);
				break;
			}
			if (set_termio(0, speedef->g_fflags, auto_speed,
			    FALSE, CANON) == -1) {
				log("set final termio failed");
				exit(1);
			}
			invoke_service(pmtab);
			exit(1);	/*NOTREACHED*/

		case BADSPEED:
			/* wrong speed! try next speed in the list. */
			speedef = next_speed(speedef);
#ifdef	DEBUG
			debug("BADSPEED: setup next speed");
#endif
			if (speedef->g_autobaud & A_FLAG) {
				if (auto_termio(0) == -1) {
					exit(1);
				}
				auto_speed = do_autobaud(pmtab, speedef);
			}
			else {
				auto_speed = NULL;
				/*
				 * this reset may fail if the speed is not
				 * supported by the system
				 * we just cycle through it to the next one
				 */
				if (set_termio(0, speedef->g_iflags, NULL,
				    FALSE, CANON) != 0) {
					log("Warning -- speed of <%s> may "
					    "be not supported by the system",
					    speedef->g_id);
				}
			}
			write_prompt(1, pmtab, TRUE, TRUE);
			break;

		case NONAME:
#ifdef	DEBUG
			debug("got NONAME");
#endif
			write_prompt(1, pmtab, FALSE, FALSE);
			break;

		}  /* end switch */

		peek_ptr = NULL;
		if (pmtab->p_timeout) {
			sigact.sa_flags = 0;
			sigact.sa_handler = timedout;
			(void) sigemptyset(&sigact.sa_mask);
			(void) sigaction(SIGALRM, &sigact, NULL);
			(void) alarm((unsigned)pmtab->p_timeout);
		}
	} /* end for loop */
}

static void
openline(pmtab, speedef)
struct	pmtab 	*pmtab;
struct Gdef *speedef;
{
	char	 buffer[5];
	int	 rtn = 0;
	int	 line_count;

#ifdef	DEBUG
	debug("in openline");
#endif
	if (pmtab->p_status != GETTY) {
		(void) close(0);
		/* open should return fd 0, if not, then close it */
		if ((pmtab->p_fd = open(pmtab->p_device, O_RDWR)) != 0) {
			log("open \"%s\" failed: %s", pmtab->p_device,
			    strerror(errno));
			exit(1);
		}
	}
	(void) close(1);
	(void) close(2);
	(void) dup(0);
	(void) dup(0);

	if (pmtab->p_ttyflags & R_FLAG) { /* wait_read is needed */
		if (pmtab->p_count) {
			if (peek_ptr != NULL)
				if ((peek_ptr->buf[0]&0x7F) == '\n' ||
				    (peek_ptr->buf[0]&0x7F) == '\r')
					pmtab->p_count--;

			/*
			 * - wait for "p_count" lines
			 * - datakit switch does not
			 *   know you are a host or a terminal
			 * - so it send you several lines of msg
			 * - we need to swallow that msg
			 * - we assume the baud rate is correct
			 * - if it is not, '\n' will not look like '\n'
			 * and we will wait forever here
			 */
			if (set_termio(0, speedef->g_fflags, NULL, TRUE, CANON) == -1) {
				log("set final termio failed");
				exit(1);
			}
			for (line_count = 0; line_count < pmtab->p_count; ) {
				if (read(0, buffer, 1) < 0
				    || *buffer == '\0'
				    || *buffer == '\004') {
					(void) close(0);
					exit(0);
				}
				if (*buffer == '\n')
					line_count++;
			}
		}
		else { /* wait for 1 char */
			if (peek_ptr == NULL) {
				if (set_termio(0, NULL, NULL,TRUE,RAW) == -1) {
					log("set termio RAW failed");
					exit(1);
				}
				rtn = read(0, buffer, 1);
			} else
				*buffer = (peek_ptr->buf[0]&0x7F);

			/*
			 * NOTE: Cu on a direct line when ~. is encountered will
			 * send EOTs to the other side.  EOT=\004
			 */
			if (rtn < 0 || *buffer == '\004') {
				(void) close(0);
				exit(0);
			}
		}
		peek_ptr = NULL;
		if (!(pmtab->p_ttyflags & A_FLAG)) { /* autobaud not enabled */
			if (set_termio(0, speedef->g_fflags, NULL, TRUE, CANON) == -1) {
				log("set final termio failed");
				exit(1);
			}
		}
	}
	if (pmtab->p_ttyflags & B_FLAG) { /* port is bi-directional */
		/* set advisory lock on the line */
		if (tm_lock(0) != 0) {
			/*
			 * device is locked
			 * child exits and let the parent wait for
			 * the lock to go away
			 */
			exit(0);
		}
		/* change ownership back to root */
		(void) fchown(0, ROOTUID, Tty_gid);
		(void) fchmod(0, 0620);
	}
	return;
}

/*
 *	write_prompt	- write the msg to fd
 *			- if flush is set, flush input queue
 *			- if clear is set, write a new line
 */
void
write_prompt(fd, pmtab, flush, clear)
int	fd;
struct	pmtab	*pmtab;
int	flush, clear;
{

#ifdef DEBUG
	debug("in write_prompt");
#endif
	if (flush)
		flush_input(fd);
	if (clear) {
		(void) write(fd, "\r\n", 2);
	}
#ifdef SYS_NAME
	sys_name(fd);
#endif
	/* Print prompt/disable message. */
	if ((State == PM_DISABLED) || (pmtab->p_flags & X_FLAG))
		(void)write(fd, pmtab->p_dmsg, (unsigned)strlen(pmtab->p_dmsg));
	else
		(void) write(fd, pmtab->p_prompt,
			(unsigned)strlen(pmtab->p_prompt));
}

/*
 *	timedout	- input period timed out
 */
void
timedout()
{
	exit(1);
}

#ifdef SYS_NAME
/*
 * void sys_name() - generate a msg with system id
 *		   - print out /etc/issue file if it exists
 */
void
sys_name(fd)
int	fd;
{
	char	*ptr, buffer[BUFSIZ];
	FILE	*fp;

#if 0	/* 1111333 - don't print node name, we already do this elsewhere */
	struct	utsname utsname;

	if (uname(&utsname) != FAILURE) {
		(void) sprintf(buffer, "%.9s\r\n", utsname.nodename);
		(void) write(fd, buffer, strlen(buffer));
	}
#endif

	if ((fp = fopen(ISSUEFILE, "r")) != NULL) {
		while ((ptr = fgets(buffer, sizeof (buffer), fp)) != NULL) {
			(void) write(fd, ptr, strlen(ptr));
		}
		(void) fclose(fp);
	}
}
#endif


/*
 *	do_autobaud	- do autobaud
 *			- if it succeed, set the new speed and return
 *			- if it failed, it will get the nextlabel
 *			- if next entry is also autobaud,
 *			  it will loop back to do autobaud again
 *			- otherwise, it will set new termio and return
 */
static	char	*
do_autobaud(pmtab, speedef)
struct	pmtab	*pmtab;
struct	Gdef	*speedef;
{
	int	done = FALSE;
	char	*auto_speed;
#ifdef	DEBUG
	debug("in do_autobaud");
#endif
	while (!done) {
		if ((auto_speed = autobaud(0, pmtab->p_timeout)) == NULL) {
			speedef = next_speed(speedef);
			if (speedef->g_autobaud & A_FLAG) {
				continue;
			}
			else {
				if (set_termio(0, speedef->g_iflags, NULL,
						TRUE, CANON) != 0) {
					exit(1);
				}
				done = TRUE;
			}
		}
		else {
			if (set_termio(0, speedef->g_fflags, auto_speed,
					TRUE, CANON) != 0) {
				exit(1);
			}
			done = TRUE;
		}
	}
#ifdef	DEBUG
	debug("autobaud done");
#endif
	return (auto_speed);
}

/*
 * 	next_speed(speedef)
 *	- find the next entry according to nextlabel. If "nextlabel"
 *	  is not valid, go back to the old ttylabel.
 */

static	struct	Gdef *
next_speed(speedef)
struct	Gdef *speedef;
{
	struct	Gdef *sp;

	if (strcmp(speedef->g_nextid, speedef->g_id) == 0)
		return (speedef);
	if ((sp = find_def(speedef->g_nextid)) == NULL) {
		log("%s's next speed-label (%s) is bad.", speedef->g_id,
		    speedef->g_nextid);

		/* go back to the original entry. */
		if ((sp = find_def(speedef->g_id)) == NULL) {
			/* if failed, complain and quit. */
			log("unable to find (%s) again", speedef->g_id);
			exit(1);
		}
	}
	return (sp);
}

/*
 * inform_parent()	- inform ttymon that tmchild is going to exec service
 */
static	void
inform_parent(fd)
int	fd;
{
	pid_t	pid;

	pid = getpid();
	(void) write(fd, &pid, sizeof (pid));
}

static	char	 pbuf[BUFSIZ];	/* static buf for TTYPROMPT 	*/
static	char	 hbuf[BUFSIZ];	/* static buf for HOME 		*/
static	char	 tbuf[BUFSIZ];	/* static buf for TERM		*/

/*
 * void invoke_service	- invoke the service
 */

static	void
invoke_service(pmtab)
struct	pmtab	*pmtab;
{
	char	 *argvp[MAXARGS];		/* service cmd args */
	int	 cnt = 0;			/* arg counter */
	int	 i, fd;
	struct	 sigaction	sigact;
	extern	 struct	rlimit	Rlimit;

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

	if (tcgetsid(0) != getsid(getpid())) {
		cons_printf("Warning -- ttymon cannot allocate controlling "
		    "tty on \"%s\",\n", pmtab->p_device);
		cons_printf("\tThere may be another session active on this "
		    "port.\n");

		if (strcmp("/dev/console", pmtab->p_device) != 0) {
			/*
			 * if not on console, write to stderr to warn the user
			 * also.
			 */
			(void) fprintf(stderr, "Warning -- ttymon cannot "
			    "allocate controlling tty on \"%s\",\n",
			    pmtab->p_device);
			(void) fprintf(stderr, "\tthere may be another session "
			    "active on this port.\n");
		}
	}

	if (pmtab->p_status != GETTY) {
		inform_parent(PCpipe[1]);
		sigact.sa_flags = 0;
		sigact.sa_handler = SIG_DFL;
		(void) sigemptyset(&sigact.sa_mask);
		(void) sigaction(SIGPOLL, &sigact, NULL);
	}

	if (pmtab->p_flags & U_FLAG) {
		if (account(pmtab->p_device) != 0) {
			log("invoke_service: account failed");
			exit(1);
		}
	}

	/* parse command line */
	mkargv(pmtab->p_server, &argvp[0], &cnt, MAXARGS-1);

	if (!(pmtab->p_ttyflags & C_FLAG)) {
		(void) sprintf(pbuf, "TTYPROMPT=%s", pmtab->p_prompt);
		if (putenv(pbuf)) {
			log("cannot expand service <%s> environment", argvp[0]);
			exit(1);
		}
	}
	if (pmtab->p_status != GETTY) {
		(void) sprintf(hbuf, "HOME=%s", pmtab->p_dir);
		if (putenv(hbuf)) {
			log("cannot expand service <%s> environment", argvp[0]);
			exit(1);
		}
#ifdef	DEBUG
		debug("about to run config script");
#endif
		if ((i = doconfig(0, pmtab->p_tag, 0)) != 0) {
			if (i < 0) {
				log("doconfig failed, system error");
			}
			else {
				log("doconfig failed on line %d of script %s",
				    i, pmtab->p_tag);
			}
			exit(1);
		}
	}

	if (setgid(pmtab->p_gid)) {
		log("cannot set group id to %ld: %s", pmtab->p_gid,
		    strerror(errno));
		exit(1);
	}

	if (setuid(pmtab->p_uid)) {
		log("cannot set user id to %ld: %s", pmtab->p_uid,
		    strerror(errno));
		exit(1);
	}

	if (chdir(pmtab->p_dir)) {
		log("cannot chdir to %s: %s", pmtab->p_dir, strerror(errno));
		exit(1);
	}

	if (pmtab->p_uid != ROOTUID) {
		/* change ownership and mode of device */
		(void) fchown(0, pmtab->p_uid, Tty_gid);
		(void) fchmod(0, 0620);
	}


	if (pmtab->p_status != GETTY) {
		sigact.sa_flags = 0;
		sigact.sa_handler = SIG_DFL;
		(void) sigemptyset(&sigact.sa_mask);
		(void) sigaction(SIGINT, &sigact, NULL);
		if (setrlimit(RLIMIT_NOFILE, &Rlimit) == -1) {
			log("setrlimit failed: %s", strerror(errno));
			exit(1);
		}
		/* invoke the service */
		log("Starting service (%s) on %s", argvp[0], pmtab->p_device);
	}

	if (pmtab->p_termtype != (char *)NULL) {
		(void) sprintf(tbuf, "TERM=%s", pmtab->p_termtype);
		if (putenv(tbuf)) {
			log("cannot expand service <%s> environment", argvp[0]);
			exit(1);
		}
	}
	/* restore signal handlers and mask */
	(void) sigaction(SIGINT, &Sigint, NULL);
	(void) sigaction(SIGALRM, &Sigalrm, NULL);
	(void) sigaction(SIGPOLL, &Sigpoll, NULL);
	(void) sigaction(SIGQUIT, &Sigquit, NULL);
	(void) sigaction(SIGCLD, &Sigcld, NULL);
	(void) sigaction(SIGTERM, &Sigterm, NULL);
#ifdef	DEBUG
	(void) sigaction(SIGUSR1, &Sigusr1, NULL);
	(void) sigaction(SIGUSR2, &Sigusr2, NULL);
#endif
	(void) sigprocmask(SIG_SETMASK, &Origmask, NULL);
	(void) execve(argvp[0], argvp, environ);

	/* exec returns only on failure! */
	log("tmchild: exec service failed: %s", strerror(errno));
	exit(1);
}

/*
 *	check_hup(fd)	- do a poll on fd to check if it is in hangup state
 *			- return 1 if hangup, otherwise return 0
 */

static	int
check_hup(fd)
int	fd;
{
	int	ret;
	struct	pollfd	pfd[1];

	pfd[0].fd = fd;
	pfd[0].events = POLLHUP;
	for (;;) {
		ret = poll(pfd, 1, 0);
		if (ret < 0) {
			if (errno == EINTR)
				continue;
			log("check_hup: poll failed: %s", strerror(errno));
			exit(1);
		}
		else if (ret > 0) {
			if (pfd[0].revents & POLLHUP) {
				return (1);
			}
		}
		return (0);
	}
}

/*
 * sigpoll()	- SIGPOLL handle for tmchild
 *		- when SIGPOLL is received by tmchild,
 *		  the pipe between ttymon and tmchild is broken.
 *		  Something must happen to ttymon.
 */
void
sigpoll()
{
#ifdef	DEBUG
	debug("tmchild got SIGPOLL, exiting");
#endif
	exit(1);
}