/*	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	"@(#)cmd-streams:strcmd/strchg.c	1.6.1.2"

/* 
 * Streams Command strchg:	change the configuration of the
 *				stream associated with stdin.
 *
 * USAGE:	strchg -h module1[,module2,module3 ...]
 *    or:	strchg -p
 *    or:	strchg -p -a
 *    or:	strchg -p -u module
 *    or:	strchg -f file
 *
 * -h		pusHes the named module(s) onto the stdin stream
 * -p		poPs the topmost module from the stdin stream
 * -p -a	poPs All modules
 * -p -u module	poPs all modules Up to, but not including, the named module
 * -f file	reads a list of modules from the named File, pops all modules,
 *		then pushes the list of modules
 *
 * RETURNS:
 *	0	SUCCESS		it worked
 *	1	ERR_USAGE	bad invocation
 *	2	ERR_MODULE	bad module name(s)
 *	3	ERR_STDIN	an ioctl or stat on the stdin stream failed
 *	4	ERR_MEM		couldn't allocate memory
 *	5	ERR_OPEN	couldn't open file in -f opt
 *	6	ERR_PERM	not owner or superuser
 *
 */


#include	<stdio.h>
#include	<sys/stropts.h>
#include	<sys/termio.h>
#include	<sys/types.h>
#include	<sys/stat.h>

#define FALSE		0
#define	TRUE		1

#define SUCCESS		0
#define FAILURE		1

#define	NMODULES	16	/* "reasonable" # of modules to push	  */
				/* 	(can push more if you like)	  */
#define	MAXMODULES	2048	/* max # of modules to push		  */

#define	OPTLIST		"af:h:pu:"
#define	USAGE		"Usage:\t%s -h module1[,module2 ... ]\n\t%s -f file\n\t%s -p [-a || -u module ]\n"

#define	ERR_USAGE	1	/* bad invocation			  */
#define	ERR_MODULE	2	/* bad module name(s) or too many modules */
#define	ERR_STDIN	3	/* an ioctl or stat on stdin failed	  */
#define	ERR_MEM		4	/* couldn't allocate memory		  */
#define	ERR_OPEN	5	/* couldn't open file in -f opt		  */
#define	ERR_PERM	6	/* not owner or superuser		  */

#define	STDIN		0
#define	CNULL		(char *)NULL
#define	SAME		0	/* return from str[n]cmp if match	*/

static char		 *Cmd_namep;		/* how was it invoked?	*/
static struct str_mlist	Oldmods[NMODULES];	/* modlist for Oldlist	*/
static struct str_list	Oldlist;		/* original modules	*/

extern char		*strncpy();
extern char		*strtok();
extern int		getopt();
extern unsigned short	geteuid();
extern int		ioctl();
extern int		strncmp();
extern void		perror();

static int	pop_modules();	/* pop 'n' modules		*/
static int	push_module();	/* push a module		*/
static int	more_modules();	/* increase size of mod lists	*/
static void	restore();	/* restore state of stdin	*/

main( argc, argv)
int	argc;
char	*argv[];
{
	char		buf[BUFSIZ];	/* input buffer			*/
	char 		*file_namep;	/* file from -f opt		*/
	char		*modnamep;	/* mods from -h or -u opt	*/
	char		*modp;		/* for walking thru modnamep	*/

	FILE		*fp;		/* file pointer for -f file	*/

	register int	i;		/* loop index and junk var	*/
	register int	j;		/* loop index and junk var	*/
	int		euid;		/* effective uid		*/

	short		error;		/* TRUE if usage error		*/
	short		fromfile;	/* TRUE if -f file		*/
	short		is_a_tty;	/* TRUE if TCGETA succeeds	*/
	short		pop;		/* TRUE if -p			*/
	short		popall;		/* TRUE if -p -a		*/
	short		popupto;	/* TRUE if -p -u module		*/
	short		push;		/* TRUE if -h mod1[,mod2 ...]	*/

	struct str_mlist
			newmods[NMODULES];/* mod list for new list	*/
	struct stat	stats;		/* stream stats			*/
	struct str_list	newlist;	/* modules to be pushed		*/
	struct termio	termio;		/* save state of tty		*/

	extern char	*optarg;	/* for getopt()			*/
	extern int	optind;		/* for getopt()			*/

	/*
	 *	init
	 */

	Cmd_namep = argv[0];
	error = fromfile = is_a_tty = pop = popall = popupto = push = FALSE;
	Oldlist.sl_modlist = Oldmods;
	Oldlist.sl_nmods = NMODULES;
	newlist.sl_modlist = newmods;
	newlist.sl_nmods = NMODULES;

	/*
	 *	only owner and root can change stream configuration
	 */
	if ( (euid = geteuid()) != 0 ) {
		if ( fstat(0, &stats) < 0 ) {
			perror("fstat");
			(void) fprintf(stderr, "%s: fstat of stdin failed\n",
				Cmd_namep);
			return(ERR_STDIN);
		}
		if ( euid != stats.st_uid ) {
			(void) fprintf(stderr,
				"%s: not owner of stdin\n", Cmd_namep);
			return(ERR_PERM);
		}
	}


	/*
	 *	parse args
	 */

	if ( argc == 1 ) {
		(void) fprintf(stderr, USAGE, Cmd_namep, Cmd_namep, Cmd_namep);
		return(ERR_USAGE);
	}

	while ( !error && (i = getopt( argc, argv, OPTLIST)) != -1 ) {

		switch (i) {

		case 'a':				/* pop All	*/
			if ( fromfile || popupto || push )
				error = TRUE;
			else
				popall = TRUE;
			break;

		case 'f':				/* read from File*/
			if ( pop || push )
				error = TRUE;
			else {
				fromfile = TRUE;
				file_namep = optarg;
			}
			break;

		case 'h':				/* pusH		*/
			if ( fromfile || pop )
				error = TRUE;
			else {
				push = TRUE;
				modnamep = optarg;
			}
			break;

		case 'p':				/* poP		*/
			if ( fromfile || push )
				error = TRUE;
			else
				pop = TRUE;
			break;

		case 'u':				/* pop Upto	*/
			if ( fromfile || popall || push )
				error = TRUE;
			else {
				popupto = TRUE;
				modnamep = optarg;
			}
			break;

		default:
			(void) fprintf(stderr,
				USAGE, Cmd_namep, Cmd_namep, Cmd_namep);
			return(ERR_USAGE);
			/*NOTREACHED*/
		}
	}

	if ( error || optind < argc )  {
		(void) fprintf(stderr, USAGE, Cmd_namep, Cmd_namep, Cmd_namep);
		return(ERR_USAGE);
	}

	if ( !pop && ( popall || popupto ) ) {
		(void) fprintf(stderr,
			"%s: -p option must be used with -a or -u to pop modules\n",
			Cmd_namep);
		(void) fprintf(stderr, USAGE, Cmd_namep, Cmd_namep, Cmd_namep);
		return(ERR_USAGE);
	}


	/*
	 * Save state so can restore if something goes wrong
	 * (If are only going to push modules, don't need to 
	 * save original module list for restore.)
	 */
	if ( fromfile || pop ) {

		/*
		 * get number of modules on stream
		 * allocate more room if needed
		 */
		if ( (i =  ioctl(STDIN, I_LIST, (struct str_list *)NULL))
		 < 0 ) {
			perror("I_LIST");
			(void) fprintf(stderr,
				"%s: I_LIST ioctl failed\n", Cmd_namep);
			return(ERR_STDIN);
		}
		if ( i > Oldlist.sl_nmods )
			if ( more_modules(&Oldlist, i) != SUCCESS )
				return(ERR_MEM);

		/*
		 * get list of modules on stream
		 */
		Oldlist.sl_nmods = i;
		if ( ioctl(STDIN, I_LIST, &Oldlist) < 0 ) {
			perror("I_LIST");
			(void) fprintf(stderr,
				"%s: I_LIST ioctl failed\n", Cmd_namep);
			return(ERR_STDIN);
		}

		/*
		 * The following attempts to avoid leaving a
		 * terminal line that does not respond to anything
		 * if the strchg -h or -f options failed due to
		 * specifying invalid module names for pushing
		 */
		if (ioctl(STDIN, TCGETA, &termio) >= 0 )
			is_a_tty = TRUE;
	}


	/*
	 *	push modules on stream
	 */
	if ( push ) {
		/*
		 * pull mod names out of comma-separated list
		 */
		for ( i = 0, modp = strtok(modnamep, ",");
		modp != CNULL; ++i, modp = strtok(CNULL, ",") ) {
			if ( push_module(modp) == FAILURE) {
				/* pop the 'i' modules we just added */
				restore(i, 0);
				return(ERR_STDIN);
			}
		}
		return(SUCCESS);
	}

	/*
	 *	read configuration from a file
	 */
	if ( fromfile ) {

		if ( (fp = fopen(file_namep, "r")) == (FILE *)NULL ) {
			perror("fopen");
			(void) fprintf(stderr,
				"%s: could not open file '%s'\n",
				Cmd_namep, file_namep);
			return(ERR_OPEN);
		}

		/*
		 * read file and construct a new strlist
		 */
		i = 0;
		while ( fgets(buf, BUFSIZ, fp) != CNULL ) {

			if ( buf[0] == '#' )
				continue;	/* skip comments */

			/*
			 * skip trailing newline, trailing and leading
			 * whitespace
			 */
			if ( (modp = strtok(buf, " \t\n")) == CNULL )
				continue;	/* blank line */

			(void)strncpy(newlist.sl_modlist[i].l_name,
							modp, FMNAMESZ);
			++i;
			if ( (modp = strtok(CNULL, " \t\n")) != CNULL ) {
				/*
				 * bad format 
				 * should only be one name per line
				 */
				(void) fprintf(stderr,
				"%s: error on line %d in file %s: multiple module names??\n",
				Cmd_namep, i, file_namep);
				return(ERR_MODULE);
			}
			if ( i > newlist.sl_nmods )
				if ( more_modules(&newlist, i) != SUCCESS )
					return(ERR_MEM);
		}
		newlist.sl_nmods = i;

		/*
		 * If an empty file, exit silently
		 */
		if ( i == 0 )
			return(SUCCESS);

		/* 
		 * Pop all modules currently on the stream.
		 */
		
		if ( (i = pop_modules(Oldlist.sl_nmods - 1))
		!= (Oldlist.sl_nmods - 1) ) {
			/* put back whatever we've popped */
			restore(0, i);
			return(ERR_STDIN);
		}

		/*
		 * Push new modules
		 */
		for ( i = newlist.sl_nmods - 1; i >= 0; --i ) {

			if ( push_module(newlist.sl_modlist[i].l_name)
			== FAILURE ) {

				/*
				 * pop whatever new modules we've pushed
				 * then push old module list back on
				 */
				restore((newlist.sl_nmods - 1 - i),
						(Oldlist.sl_nmods - 1));

				/*
				 * If the stream is a tty line, at least try
				 * to set the state to what it was before.
				 */
				if ( is_a_tty ) {
					if ( ioctl(STDIN, TCSETA, &termio) < 0 ) {
						perror("TCSETA");
						(void) fprintf(stderr,
						"%s: WARNING: Could not restore the states of the terminal line discipline\n",
						Cmd_namep);
					}
				}
				return(ERR_STDIN);
			}
		} 
		return(SUCCESS);
	}	/* end if-fromfile */


	/*
	 *	pop all modules (except driver)
	 */
	if ( popall ) {
		if ( Oldlist.sl_nmods > 1 ) {
			if ( (i = pop_modules(Oldlist.sl_nmods - 1))
			!= (Oldlist.sl_nmods - 1) ) {
				restore(0, i);
				return(ERR_STDIN);
			}
		}
		return(SUCCESS);
	}

	/*
	 *	pop up to (but not including) a module
	 */
	if ( popupto ) {
		/*
		 * check that the module is in fact on the stream
		 */
		for ( i = 0; i < Oldlist.sl_nmods; ++i )
			if ( strncmp(Oldlist.sl_modlist[i].l_name, modnamep,
							FMNAMESZ) == SAME )
				break;
		if ( i == Oldlist.sl_nmods ) {
			/* no match found */
			(void) fprintf(stderr, "%s: %s not found on stream\n",
							Cmd_namep, modnamep);
			return(ERR_MODULE);
		}

		if ( (j = pop_modules(i)) != i ) {
			/* put back whatever we've popped */
			restore(0, j);
			return(ERR_STDIN);
		}
		return(SUCCESS);
	}

	/*
	 *	pop the topmost module
	 */
	if ( pop ) {
		if ( Oldlist.sl_nmods > 1 )
			if ( pop_modules(1) != 1 )
				/* no need to restore */
				return(ERR_STDIN);
		return(SUCCESS);
	}

	/*NOTREACHED*/
}

/* 
 * pop_module(n)		pop 'n' modules from stream
 *
 * returns # of modules popped
 */
static int
pop_modules(num_modules)
int num_modules;
{

	register short i;	/* the ubiquitous loop variable */

	for ( i = 0; i < num_modules; i++ ) {
		if ( ioctl(STDIN, I_POP, 0) < 0 ) {
			perror("I_POP");
			(void) fprintf(stderr,
				"%s: I_POP ioctl failed\n", Cmd_namep);
			return(i);
		}
	}
	return(i);
}

/*
 * push_module(modnamep)	pushes 'modnamep' module on stream
 *
 * returns SUCCESS or FAILURE
 */
static int
push_module(modnamep)
char *modnamep;
{
	if ( ioctl(STDIN, I_PUSH, modnamep) < 0 ) {
		perror("I_PUSH");
		(void) fprintf(stderr,
			"%s: I_PUSH ioctl of %s failed\n", Cmd_namep, modnamep);
		return(FAILURE);
	}
	return (SUCCESS);
}


/*
 * restore(npop, npush)		restore original state of stream
 *
 * pops 'npop' modules, then pushes the topmost 'npush' modules from
 * Oldlist
 *
 */
static void
restore(npop, npush)
int	npop;
int	npush;
{
	register int	i;

	if ( (i = pop_modules(npop)) != npop ) {
		(void) fprintf(stderr,
		"%s: WARNING: could not restore state of stream\n", Cmd_namep);
		return;
	}
	if ( npush >= Oldlist.sl_nmods ) {	/* "cannot" happen */
		(void) fprintf(stderr,
		"%s: internal logic error in restore\n", Cmd_namep);
		(void) fprintf(stderr,
		"%s: WARNING: could not restore state of stream\n", Cmd_namep);
		return;
	}
	for ( i = npush - 1; i >= 0; --i ) {
		if ( push_module(Oldlist.sl_modlist[i].l_name) == FAILURE ) {
			(void) fprintf(stderr,
			"%s: WARNING: could not restore state of stream\n",
			Cmd_namep);
			return;
		}
	}
	return;
}

/*
 * more_modules(listp, n)	allocate space for 'n' modules in 'listp'
 *
 * returns:	SUCCESS or FAILURE
 */

static int
more_modules(listp, n)
struct str_list	*listp;		/* streams module list	*/
int		n;		/* # of modules		*/
{
	register int			i;
	register struct str_mlist	*modp;

	extern char	*calloc();

	if ( n > MAXMODULES ) {
		(void) fprintf(stderr,
			"%s: too many modules (%d) -- max is %d\n",
			Cmd_namep, n, MAXMODULES);
		return(FAILURE);
	}

	if ( (modp = (struct str_mlist *)calloc((unsigned)n,
	(unsigned)sizeof(struct str_mlist))) == (struct str_mlist *)NULL ) {
		perror("calloc");
		(void) fprintf(stderr,
			"%s: failed to allocate space for module list\n",
			Cmd_namep);
		return(FAILURE);
	}

	for ( i = 0; i < listp->sl_nmods; ++i )
		(void) strncpy(modp[i].l_name, listp->sl_modlist[i].l_name,
			FMNAMESZ);
	listp->sl_nmods = n;
	listp->sl_modlist = modp;
	return(SUCCESS);
}
