1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22
23/*
24 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
25 * Use is subject to license terms.
26 */
27
28/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
29/*	  All Rights Reserved  	*/
30
31#pragma ident	"%Z%%M%	%I%	%E% SMI"	/* SVr4.0 1.6	*/
32
33/*
34 * Streams Command strchg:	change the configuration of the
35 *				stream associated with stdin.
36 *
37 * USAGE:	strchg -h module1[,module2,module3 ...]
38 *    or:	strchg -p
39 *    or:	strchg -p -a
40 *    or:	strchg -p -u module
41 *    or:	strchg -f file
42 *
43 * -h		pusHes the named module(s) onto the stdin stream
44 * -p		poPs the topmost module from the stdin stream
45 * -p -a	poPs All modules
46 * -p -u module	poPs all modules Up to, but not including, the named module
47 * -f file	reads a list of modules from the named File, pops all modules,
48 *		then pushes the list of modules
49 *
50 * RETURNS:
51 *	0	SUCCESS		it worked
52 *	1	ERR_USAGE	bad invocation
53 *	2	ERR_MODULE	bad module name(s)
54 *	3	ERR_STDIN	an ioctl or stat on the stdin stream failed
55 *	4	ERR_MEM		couldn't allocate memory
56 *	5	ERR_OPEN	couldn't open file in -f opt
57 *	6	ERR_PERM	not owner or superuser
58 *
59 */
60
61
62#include <stdio.h>
63#include <sys/stropts.h>
64#include <sys/termio.h>
65#include <sys/types.h>
66#include <sys/stat.h>
67#include <string.h>
68#include <stdlib.h>
69#include <unistd.h>
70
71#define	FALSE		0
72#define	TRUE		1
73
74#define	SUCCESS		0
75#define	FAILURE		1
76
77#define	NMODULES	16	/* "reasonable" # of modules to push	  */
78				/* 	(can push more if you like)	  */
79#define	MAXMODULES	2048	/* max # of modules to push		  */
80
81#define	OPTLIST		"af:h:pu:"
82#define	USAGE		"Usage:\t%s -h module1[,module2 ... ]\n\t%s -f file"\
83			"\n\t%s -p [-a | -u module ]\n"
84
85#define	ERR_USAGE	1	/* bad invocation			  */
86#define	ERR_MODULE	2	/* bad module name(s) or too many modules */
87#define	ERR_STDIN	3	/* an ioctl or stat on stdin failed	  */
88#define	ERR_MEM		4	/* couldn't allocate memory		  */
89#define	ERR_OPEN	5	/* couldn't open file in -f opt		  */
90#define	ERR_PERM	6	/* not owner or superuser		  */
91
92#define	STDIN		0
93
94static char		*Cmd_namep;		/* how was it invoked?	*/
95static struct str_mlist	Oldmods[NMODULES];	/* modlist for Oldlist	*/
96static struct str_list	Oldlist;		/* original modules	*/
97
98static int	pop_modules(int);
99static int	push_module(const char *);
100static int	more_modules(struct str_list *, int);
101static void	restore(int, int);
102
103int
104main(int argc, char **argv)
105{
106	char		buf[BUFSIZ];	/* input buffer			*/
107	char 		*file_namep;	/* file from -f opt		*/
108	char		*modnamep;	/* mods from -h or -u opt	*/
109	char		*modp;		/* for walking thru modnamep	*/
110
111	FILE		*fp;		/* file pointer for -f file	*/
112
113	int		i;		/* loop index and junk var	*/
114	int		j;		/* loop index and junk var	*/
115	int		euid;		/* effective uid		*/
116
117	short		error;		/* TRUE if usage error		*/
118	short		fromfile;	/* TRUE if -f file		*/
119	short		is_a_tty;	/* TRUE if TCGETA succeeds	*/
120	short		pop;		/* TRUE if -p			*/
121	short		popall;		/* TRUE if -p -a		*/
122	short		popupto;	/* TRUE if -p -u module		*/
123	short		push;		/* TRUE if -h mod1[,mod2 ...]	*/
124
125	struct str_mlist newmods[NMODULES]; /* mod list for new list	*/
126	struct stat	stats;		/* stream stats			*/
127	struct str_list	newlist;	/* modules to be pushed		*/
128	struct termio	termio;		/* save state of tty		*/
129
130	/*
131	 *	init
132	 */
133
134	Cmd_namep = argv[0];
135	error = fromfile = is_a_tty = pop = popall = popupto = push = FALSE;
136	Oldlist.sl_modlist = Oldmods;
137	Oldlist.sl_nmods = NMODULES;
138	newlist.sl_modlist = newmods;
139	newlist.sl_nmods = NMODULES;
140
141	/*
142	 *	only owner and root can change stream configuration
143	 */
144	if ((euid = geteuid()) != 0) {
145		if (fstat(0, &stats) < 0) {
146			perror("fstat");
147			(void) fprintf(stderr, "%s: fstat of stdin failed\n",
148				Cmd_namep);
149			return (ERR_STDIN);
150		}
151		if (euid != stats.st_uid) {
152			(void) fprintf(stderr,
153				"%s: not owner of stdin\n", Cmd_namep);
154			return (ERR_PERM);
155		}
156	}
157
158
159	/*
160	 *	parse args
161	 */
162
163	if (argc == 1) {
164		(void) fprintf(stderr, USAGE, Cmd_namep, Cmd_namep, Cmd_namep);
165		return (ERR_USAGE);
166	}
167
168	while (!error && (i = getopt(argc, argv, OPTLIST)) != -1) {
169
170		switch (i) {
171
172		case 'a':				/* pop All	*/
173			if (fromfile || popupto || push)
174				error = TRUE;
175			else
176				popall = TRUE;
177			break;
178
179		case 'f':				/* read from File */
180			if (pop || push)
181				error = TRUE;
182			else {
183				fromfile = TRUE;
184				file_namep = optarg;
185			}
186			break;
187
188		case 'h':				/* pusH		*/
189			if (fromfile || pop)
190				error = TRUE;
191			else {
192				push = TRUE;
193				modnamep = optarg;
194			}
195			break;
196
197		case 'p':				/* poP		*/
198			if (fromfile || push)
199				error = TRUE;
200			else
201				pop = TRUE;
202			break;
203
204		case 'u':				/* pop Upto	*/
205			if (fromfile || popall || push)
206				error = TRUE;
207			else {
208				popupto = TRUE;
209				modnamep = optarg;
210			}
211			break;
212
213		default:
214			(void) fprintf(stderr,
215				USAGE, Cmd_namep, Cmd_namep, Cmd_namep);
216			return (ERR_USAGE);
217			/*NOTREACHED*/
218		}
219	}
220
221	if (error || optind < argc)  {
222		(void) fprintf(stderr, USAGE, Cmd_namep, Cmd_namep, Cmd_namep);
223		return (ERR_USAGE);
224	}
225
226	if (!pop && (popall || popupto)) {
227		(void) fprintf(stderr,
228		    "%s: -p option must be used with -a or -u to pop modules\n",
229		    Cmd_namep);
230		(void) fprintf(stderr, USAGE, Cmd_namep, Cmd_namep, Cmd_namep);
231		return (ERR_USAGE);
232	}
233
234
235	/*
236	 * Save state so can restore if something goes wrong
237	 * (If are only going to push modules, don't need to
238	 * save original module list for restore.)
239	 */
240	if (fromfile || pop) {
241
242		/*
243		 * get number of modules on stream
244		 * allocate more room if needed
245		 */
246		if ((i =  ioctl(STDIN, I_LIST, NULL)) < 0) {
247			perror("I_LIST");
248			(void) fprintf(stderr,
249				"%s: I_LIST ioctl failed\n", Cmd_namep);
250			return (ERR_STDIN);
251		}
252		if (i > Oldlist.sl_nmods &&
253		    more_modules(&Oldlist, i) != SUCCESS)
254				return (ERR_MEM);
255
256		/*
257		 * get list of modules on stream
258		 */
259		Oldlist.sl_nmods = i;
260		if (ioctl(STDIN, I_LIST, &Oldlist) < 0) {
261			perror("I_LIST");
262			(void) fprintf(stderr,
263				"%s: I_LIST ioctl failed\n", Cmd_namep);
264			return (ERR_STDIN);
265		}
266
267		/*
268		 * The following attempts to avoid leaving a
269		 * terminal line that does not respond to anything
270		 * if the strchg -h or -f options failed due to
271		 * specifying invalid module names for pushing
272		 */
273		if (ioctl(STDIN, TCGETA, &termio) >= 0)
274			is_a_tty = TRUE;
275	}
276
277
278	/*
279	 *	push modules on stream
280	 */
281	if (push) {
282		/*
283		 * pull mod names out of comma-separated list
284		 */
285		for (i = 0, modp = strtok(modnamep, ",");
286		    modp != NULL; ++i, modp = strtok(NULL, ",")) {
287			if (push_module(modp) == FAILURE) {
288				/* pop the 'i' modules we just added */
289				restore(i, 0);
290				return (ERR_STDIN);
291			}
292		}
293		return (SUCCESS);
294	}
295
296	/*
297	 *	read configuration from a file
298	 */
299	if (fromfile) {
300
301		if ((fp = fopen(file_namep, "r")) == NULL) {
302			perror("fopen");
303			(void) fprintf(stderr,
304				"%s: could not open file '%s'\n",
305				Cmd_namep, file_namep);
306			return (ERR_OPEN);
307		}
308
309		/*
310		 * read file and construct a new strlist
311		 */
312		i = 0;
313		while (fgets(buf, BUFSIZ, fp) != NULL) {
314
315			if (buf[0] == '#')
316				continue;	/* skip comments */
317
318			/*
319			 * skip trailing newline, trailing and leading
320			 * whitespace
321			 */
322			if ((modp = strtok(buf, " \t\n")) == NULL)
323				continue;	/* blank line */
324
325			(void) strncpy(newlist.sl_modlist[i].l_name,
326			    modp, FMNAMESZ);
327			++i;
328			if ((modp = strtok(NULL, " \t\n")) != NULL) {
329				/*
330				 * bad format
331				 * should only be one name per line
332				 */
333				(void) fprintf(stderr,
334				    "%s: error on line %d in file %s: "
335				    "multiple module names??\n",
336				    Cmd_namep, i, file_namep);
337				return (ERR_MODULE);
338			}
339			if (i > newlist.sl_nmods)
340				if (more_modules(&newlist, i) != SUCCESS)
341					return (ERR_MEM);
342		}
343		newlist.sl_nmods = i;
344
345		/*
346		 * If an empty file, exit silently
347		 */
348		if (i == 0)
349			return (SUCCESS);
350
351		/*
352		 * Pop all modules currently on the stream.
353		 */
354		if ((i = pop_modules(Oldlist.sl_nmods - 1))
355		    != (Oldlist.sl_nmods - 1)) {
356			/* put back whatever we've popped */
357			restore(0, i);
358			return (ERR_STDIN);
359		}
360
361		/*
362		 * Push new modules
363		 */
364		for (i = newlist.sl_nmods - 1; i >= 0; --i) {
365			if (push_module(newlist.sl_modlist[i].l_name) ==
366			    FAILURE) {
367
368				/*
369				 * pop whatever new modules we've pushed
370				 * then push old module list back on
371				 */
372				restore((newlist.sl_nmods - 1 - i),
373				    (Oldlist.sl_nmods - 1));
374
375				/*
376				 * If the stream is a tty line, at least try
377				 * to set the state to what it was before.
378				 */
379				if (is_a_tty &&
380				    ioctl(STDIN, TCSETA, &termio) < 0) {
381					perror("TCSETA");
382					(void) fprintf(stderr,
383					    "%s: WARNING: Could not restore "
384					    "the states of the terminal line "
385					    "discipline\n", Cmd_namep);
386				}
387				return (ERR_STDIN);
388			}
389		}
390		return (SUCCESS);
391	}	/* end if-fromfile */
392
393
394	/*
395	 *	pop all modules (except driver)
396	 */
397	if (popall) {
398		if (Oldlist.sl_nmods > 1) {
399			if ((i = pop_modules(Oldlist.sl_nmods - 1)) !=
400			    (Oldlist.sl_nmods - 1)) {
401				restore(0, i);
402				return (ERR_STDIN);
403			}
404		}
405		return (SUCCESS);
406	}
407
408	/*
409	 *	pop up to (but not including) a module
410	 */
411	if (popupto) {
412		/*
413		 * check that the module is in fact on the stream
414		 */
415		for (i = 0; i < Oldlist.sl_nmods; ++i)
416			if (strncmp(Oldlist.sl_modlist[i].l_name, modnamep,
417			    FMNAMESZ) == 0)
418				break;
419		if (i == Oldlist.sl_nmods) {
420			/* no match found */
421			(void) fprintf(stderr, "%s: %s not found on stream\n",
422							Cmd_namep, modnamep);
423			return (ERR_MODULE);
424		}
425
426		if ((j = pop_modules(i)) != i) {
427			/* put back whatever we've popped */
428			restore(0, j);
429			return (ERR_STDIN);
430		}
431		return (SUCCESS);
432	}
433
434	/*
435	 *	pop the topmost module
436	 */
437	if (pop) {
438		if (Oldlist.sl_nmods > 1)
439			if (pop_modules(1) != 1)
440				/* no need to restore */
441				return (ERR_STDIN);
442		return (SUCCESS);
443	}
444
445	return (SUCCESS);
446}
447
448/*
449 * pop_module(n)		pop 'n' modules from stream
450 *
451 * returns # of modules popped
452 */
453static int
454pop_modules(int num_modules)
455{
456	int i;
457
458	for (i = 0; i < num_modules; i++) {
459		if (ioctl(STDIN, I_POP, 0) < 0) {
460			perror("I_POP");
461			(void) fprintf(stderr,
462			    "%s: I_POP ioctl failed\n", Cmd_namep);
463			return (i);
464		}
465	}
466	return (i);
467}
468
469/*
470 * push_module(modnamep)	pushes 'modnamep' module on stream
471 *
472 * returns SUCCESS or FAILURE
473 */
474static int
475push_module(const char *modnamep)
476{
477	if (ioctl(STDIN, I_PUSH, modnamep) < 0) {
478		perror("I_PUSH");
479		(void) fprintf(stderr,
480		    "%s: I_PUSH ioctl of %s failed\n", Cmd_namep, modnamep);
481		return (FAILURE);
482	}
483	return (SUCCESS);
484}
485
486
487/*
488 * restore(npop, npush)		restore original state of stream
489 *
490 * pops 'npop' modules, then pushes the topmost 'npush' modules from
491 * Oldlist
492 *
493 */
494static void
495restore(int npop, int npush)
496{
497	int	i;
498
499	if ((i = pop_modules(npop)) != npop) {
500		(void) fprintf(stderr,
501		    "%s: WARNING: could not restore state of stream\n",
502		    Cmd_namep);
503		return;
504	}
505
506	if (npush >= Oldlist.sl_nmods) {	/* "cannot" happen */
507		(void) fprintf(stderr,
508		    "%s: internal logic error in restore\n", Cmd_namep);
509		(void) fprintf(stderr,
510		    "%s: WARNING: could not restore state of stream\n",
511		    Cmd_namep);
512		return;
513	}
514
515	for (i = npush - 1; i >= 0; --i) {
516		if (push_module(Oldlist.sl_modlist[i].l_name) == FAILURE) {
517			(void) fprintf(stderr,
518			    "%s: WARNING: could not restore state of stream\n",
519			    Cmd_namep);
520			return;
521		}
522	}
523}
524
525/*
526 * more_modules(listp, n)	allocate space for 'n' modules in 'listp'
527 *
528 * returns:	SUCCESS or FAILURE
529 */
530
531static int
532more_modules(struct str_list *listp, int n)
533{
534	int			i;
535	struct str_mlist	*modp;
536
537	if (n > MAXMODULES) {
538		(void) fprintf(stderr,
539		    "%s: too many modules (%d) -- max is %d\n",
540		    Cmd_namep, n, MAXMODULES);
541		return (FAILURE);
542	}
543
544	if ((modp = calloc(n, sizeof (struct str_mlist))) == NULL) {
545		perror("calloc");
546		(void) fprintf(stderr,
547		    "%s: failed to allocate space for module list\n",
548		    Cmd_namep);
549		return (FAILURE);
550	}
551
552	for (i = 0; i < listp->sl_nmods; ++i)
553		(void) strncpy(modp[i].l_name, listp->sl_modlist[i].l_name,
554		    FMNAMESZ);
555	listp->sl_nmods = n;
556	listp->sl_modlist = modp;
557	return (SUCCESS);
558}
559