xref: /illumos-gate/usr/src/cmd/streams/strcmd/strchg.c (revision 8e458de0baeb1fee50643403223bc7e909a48464)
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 
94 static char		*Cmd_namep;		/* how was it invoked?	*/
95 static struct str_mlist	Oldmods[NMODULES];	/* modlist for Oldlist	*/
96 static struct str_list	Oldlist;		/* original modules	*/
97 
98 static int	pop_modules(int);
99 static int	push_module(const char *);
100 static int	more_modules(struct str_list *, int);
101 static void	restore(int, int);
102 
103 int
104 main(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  */
453 static int
454 pop_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  */
474 static int
475 push_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  */
494 static void
495 restore(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 
531 static int
532 more_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