xref: /titanic_50/usr/src/lib/libcmd/common/stty.c (revision b6c3f7863936abeae522e48a13887dddeb691a45)
1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *           Copyright (c) 1992-2007 AT&T Knowledge Ventures            *
5 *                      and is licensed under the                       *
6 *                  Common Public License, Version 1.0                  *
7 *                      by AT&T Knowledge Ventures                      *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *            http://www.opensource.org/licenses/cpl1.0.txt             *
11 *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                 Glenn Fowler <gsf@research.att.com>                  *
18 *                  David Korn <dgk@research.att.com>                   *
19 *                                                                      *
20 ***********************************************************************/
21 #pragma prototyped
22 /*
23  * stty.c
24  * Written by David Korn
25  * Tue Apr  4 10:46:00 EDT 1995
26  */
27 
28 static const char usage[] =
29 "[-?@(#)$Id: stty (AT&T Research) 2006-10-31 $\n]"
30 USAGE_LICENSE
31 "[+NAME?stty - set or get terminal modes]"
32 "[+DESCRIPTION?\bstty\b sets certain terminal I/O modes for the device "
33 	"that is the current standard input; without arguments, it writes "
34 	"the settings of certain modes to standard output.]"
35 
36 "[a:all?Writes to standard output all of the mode settings.]"
37 "[g:save?Writes the current settings to standard output in a form that can "
38 	"be used as an argument to another \bstty\b command.  The \brows\b "
39 	"and \bcolumns\b values are not included.]"
40 "\n"
41 "\n[mode ...]\n"
42 "\n"
43 "[+EXTENDED DESCRIPTION?Modes are specified either as a single name or "
44 	"as a name followed by a value.  As indicated below, many of the "
45 	"mode names can be preceded by a \b-\b to negate its meaning.  "
46 	"Modes are listed by group corresponding to field in the "
47 	"\btermios\b structure defined in \b<termios.h>\b.  Modes "
48 	"in the last group are implemented using options in the previous "
49 	"groups.  Note that many combinations of modes make no sense, but "
50 	"no sanity checking is performed.  The modes are selected from the "
51 	"following:]{\fabc\f}"
52 
53 "[+EXIT STATUS?]{"
54       "[+0?All modes reported or set successfully.]"
55         "[+>0?Standard input not a terminaol or one or more modes failed.]"
56 "}"
57 "[+SEE ALSO?\btegetattr\b(2), \btcsetattr\b(2), \bioctl\b(2)]"
58 ;
59 
60 
61 #include	<cmd.h>
62 #include	<ccode.h>
63 #include	<ctype.h>
64 #include	<ast_tty.h>
65 #if _sys_ioctl
66 #include	<sys/ioctl.h>
67 #endif
68 
69 #define C(x)	ERROR_catalog(x)
70 
71 #ifndef _POSIX_VDISABLE
72 #   define _POSIX_VDISABLE 0
73 #endif
74 
75 #ifndef NCCS
76 #   ifdef NCC
77 #	define NCCS	NCC
78 #   else
79 #	define NCCS	elementsof(((struct termio*)0)->c_cc)
80 #   endif
81 #endif
82 
83 /* command options */
84 #define A_FLAG	1
85 #define G_FLAG	2
86 
87 /* termios fields */
88 #define C_FLAG	1
89 #define C_LINE	2
90 #define C_SPEED	3
91 #define I_FLAG	4
92 #define O_FLAG	5
93 #define L_FLAG	6
94 #define T_CHAR	7
95 #define W_SIZE	8
96 
97 #define BIT	1
98 #define BITS	2
99 #define NUM	3
100 #define CHAR	4
101 #define SPEED	5
102 #define SIZE	6
103 #define MIXED	7
104 #define SANE	8
105 #define COOKED	9
106 #define CASE	10
107 #define TABS	11
108 #define WIND	12
109 
110 #undef	SS			/* who co-opted this namespace?	*/
111 
112 #define IG	0x0001		/* ignore display		*/
113 #define NL	0x0002		/* entry ends line of display	*/
114 #define SS	0x0004		/* set in sane mode		*/
115 #define US	0x0010		/* unset in sane mode		*/
116 
117 typedef struct tty_s
118 {
119 	const char	name[8];
120 	unsigned char	type;
121 	unsigned char	field;
122 	short		flags;
123 	unsigned long	mask;
124 	unsigned long	val;
125 	const char	description[76];
126 } Tty_t;
127 
128 static const Tty_t Ttable[] =
129 {
130 #ifdef CBAUD
131 { "ispeed",	NUM,	C_SPEED,0,	CBAUD, 0, C("\an\a is the input baud rate") },
132 { "ospeed",	NUM,	C_SPEED,0,	CBAUD, 0, C("\an\a is the output baud rate") },
133 { "speed",	NUM,	C_SPEED,IG,	CBAUD },
134 #endif
135 { "0",		SPEED,	C_FLAG,	0,	B0 },
136 { "50",		SPEED,	C_FLAG,	0,	B50 },
137 { "75",		SPEED,	C_FLAG,	0,	B75 },
138 { "110",	SPEED,	C_FLAG,	0,	B110 },
139 { "134",	SPEED,	C_FLAG,	0,	B134 },
140 { "150",	SPEED,	C_FLAG,	0,	B150 },
141 { "200",	SPEED,	C_FLAG,	0,	B200 },
142 { "300",	SPEED,	C_FLAG,	0,	B300 },
143 { "600",	SPEED,	C_FLAG,	0,	B600 },
144 { "1200",	SPEED,	C_FLAG,	0,	B1200 },
145 { "1800",	SPEED,	C_FLAG,	0,	B1800 },
146 { "2400",	SPEED,	C_FLAG,	0,	B2400 },
147 { "4800",	SPEED,	C_FLAG,	0,	B4800 },
148 { "9600",	SPEED,	C_FLAG,	0,	B9600 },
149 { "19200",	SPEED,	C_FLAG,	0,	B19200 },
150 { "38400",	SPEED,	C_FLAG,	0,	B38400 },
151 
152 #ifdef TIOCSWINSZ
153 { "rows",	WIND,	W_SIZE,	IG,	0, 24, C("\an\a is the number of lines for display") },
154 { "cols",	WIND,	W_SIZE,	IG,	1, 80, C("\an\a is the number of columns for display") },
155 { "columns",	WIND,	W_SIZE,	IG,	1, 80, C("Same as \bcols\b") },
156 #endif
157 { "intr",	CHAR,	T_CHAR,	SS,	VINTR, 'C', C("Send an interrupt signal") },
158 { "quit",	CHAR,	T_CHAR,	SS,	VQUIT, '|', C("Send a quit signal") },
159 { "erase",	CHAR,	T_CHAR,	SS,	VERASE, 'H', C("Erase the last character entered") },
160 { "kill",	CHAR,	T_CHAR,	NL|SS,	VKILL, 'U', C("Erase the current line") },
161 { "eof",	CHAR,	T_CHAR,	SS,	VEOF, 'D', C("Send an end of file") },
162 #ifdef VEOL2
163 { "eol2",	CHAR,	T_CHAR,	US,	VEOL2, _POSIX_VDISABLE, C("Alternate character to end the line") },
164 #endif /* VEOL2 */
165 #ifdef VSWTCH
166 { "swtch",	CHAR,	T_CHAR,	US,	VSWTCH, _POSIX_VDISABLE, C("Switch to a different shell layer") },
167 #endif /* VSWTCH */
168 { "eol",	CHAR,	T_CHAR,	NL|US,	VEOL, _POSIX_VDISABLE, C("End the line") },
169 #ifdef VSTART
170 { "start",	CHAR,	T_CHAR,	SS,	VSTART, 'Q', C("Restart the output after stopping it") },
171 #endif /* VSTART */
172 #ifdef VSTOP
173 { "stop",	CHAR,	T_CHAR,	SS,	VSTOP, 'S', C("Stop the output") },
174 #endif /* VSTOP */
175 #ifdef VDSUSP
176 { "dsusp",	CHAR,	T_CHAR,	SS,	VDSUSP, 'Y', C("Send a terminal stop signal after flushing the input") },
177 #endif /* VDSUSP */
178 #ifdef VSUSP
179 { "susp",	CHAR,	T_CHAR,	NL|SS,	VSUSP, 'Z', C("Send a terminal stop signal") },
180 #endif /* VSUSP */
181 #ifdef VREPRINT
182 { "rprnt",	CHAR,	T_CHAR,	SS,	VREPRINT, 'R', C("Redraw the current line") },
183 #endif /* VREPRINT */
184 #ifdef VDISCARD
185 { "flush",	CHAR,	T_CHAR,	SS,	VDISCARD, 'O', C("Discard output") },
186 #endif /* VDISCARD */
187 #ifdef VWERASE
188 { "werase",	CHAR,	T_CHAR,	SS,	VWERASE, 'W', C("Erase the last word entered") },
189 #endif /* VWERASE */
190 #ifdef VLNEXT
191 { "lnext",	CHAR,	T_CHAR,	NL|SS,	VLNEXT, 'V', C("Enter the next input character literally") },
192 #endif /* VLNEXT */
193 
194 #if _mem_c_line_termios
195 { "line",	NUM,	C_LINE,	0,	0, 0, C("Line discipline number") },
196 #endif
197 { "min",	NUM,	T_CHAR,	0,	VMIN, 0, C("Mininmum number of characters to read in raw mode") },
198 { "time",	NUM,	T_CHAR,	0,	VTIME, 0, C("Number of .1 second intervals with raw mode") },
199 
200 { "parenb",	BIT,	C_FLAG,	0,	PARENB,	PARENB, C("Enable (disable) parity generation and detection") },
201 { "parodd",	BIT,	C_FLAG,	0,	PARODD, PARODD, C("Use odd (even) parity") },
202 #ifdef PAREXT
203 { "parext",	BIT,	C_FLAG,	0,	PAREXT, PAREXT },
204 #endif /* PAREXT */
205 #ifdef CREAD
206 { "cread",	BIT,	C_FLAG,	SS,	CREAD, CREAD, C("Enable (disable) input") },
207 #endif /* CREAD */
208 { "cs5",	SIZE,	C_FLAG,	0,	CSIZE,	CS5 , C("Char size 5") },
209 { "cs6",	SIZE,	C_FLAG,	0,	CSIZE,	CS6 , C("Char size 6") },
210 { "cs7",	SIZE,	C_FLAG,	0,	CSIZE,	CS7 , C("Char size 7") },
211 { "cs8",	SIZE,	C_FLAG,	0,	CSIZE,	CS8 , C("Char size 8") },
212 { "hupcl",	BIT,	C_FLAG,	0,	HUPCL, HUPCL, C("Hangup (do not hangup) connection on last close") },
213 { "hup",	BIT,	C_FLAG,	IG,	HUPCL, HUPCL, C("Same as \bhupcl\b") },
214 { "cstopb",	BIT,	C_FLAG,	0,	CSTOPB, CSTOPB, C("Use two (one) stop bits") },
215 #ifdef CRTSCTS
216 { "crtscts",	BIT,	C_FLAG,	0,	CRTSCTS, CRTSCTS, C("Enable (disable) RTS/CTS handshaking") },
217 #endif /* CRTSCTS */
218 { "clocal",	BIT,	C_FLAG,	NL,	CLOCAL, CLOCAL, C("Disable (enable) modem control signals") },
219 
220 { "ignbrk",	BIT,	I_FLAG,	US,	IGNBRK, IGNBRK, C("Ignore (do not ignore) break characters") },
221 { "brkint",	BIT,	I_FLAG,	SS,	BRKINT, BRKINT, C("Generate (do not generate) INTR signal on break") },
222 { "ignpar",	BIT,	I_FLAG,	0,	IGNPAR, IGNPAR, C("Ignore (do not ignore) characters with parity errors") },
223 { "parmrk",	BIT,	I_FLAG,	0,	PARMRK, PARMRK, C("Mark (do not mark) parity errors") },
224 { "inpck",	BIT,	I_FLAG,	0,	INPCK, INPCK, C("Enable (disable) input parity checking") },
225 { "istrip",	BIT,	I_FLAG,	0,	ISTRIP, ISTRIP, C("Clear (do not clear) high bit of input characters") },
226 { "inlcr",	BIT,	I_FLAG,	US,	INLCR, INLCR, C("Translate (do not translate) carriage return to newline") },
227 { "igncr",	BIT,	I_FLAG,	US,	IGNCR, IGNCR, C("Ignore (do not ignore) carriage return") },
228 #ifdef IUCLC
229 { "iuclc",	BIT,	I_FLAG,	US,	IUCLC, IUCLC, C("Map (do not map) upper-case to lower case") },
230 #endif /* IUCLC */
231 { "ixon",	BIT,	I_FLAG,	0,	IXON, IXON, C("Enable (disable) XON/XOFF flow control. \bstop\b character stops output") },
232 #ifdef IXANY
233 { "ixany",	BIT,	I_FLAG,	US,	IXANY, IXANY, C("Any character (only start character) can restart output.") },
234 { "decctlq",	BIT,	I_FLAG,	IG,	IXANY, 0, C("Same as \b-ixany\b") },
235 #endif /* IXANY */
236 { "ixoff",	BIT,	I_FLAG,	US,	IXOFF, IXOFF, C("Disable (enable) XON/XOFF flow control") },
237 #ifdef IMAXBEL
238 { "imaxbel",	BIT,	I_FLAG,	SS,	IMAXBEL, IMAXBEL, C("Beep (do not beep) if a character arrives with full input buffer") },
239 #endif /* IMAXBEL */
240 { "icrnl",	BIT,	I_FLAG,	NL|SS,	ICRNL, ICRNL, C("Translate (do not translate) carriage return to newline") },
241 
242 { "isig",	BIT,	L_FLAG,	SS,	ISIG, ISIG, C("Enable (disable) \bintr\b, \bquit\b, and \bsusp\b special characters") },
243 { "icanon",	BIT,	L_FLAG,	SS,	ICANON, ICANON, C("Enable (disable) \berase\b, \bkill\b, \bwerase\b, and \brprnt\b special characters") },
244 { "icannon",	BIT,	L_FLAG,	SS,	ICANON, ICANON },
245 #ifdef IEXTEN
246 { "iexten",	BIT,	L_FLAG,	SS,	IEXTEN, IEXTEN, C("Enable (disable) non-POSIX special characters") },
247 #endif /* IEXTEN */
248 { "echo",	BIT,	L_FLAG,	SS,	ECHO|ECHONL, ECHO|ECHONL, C("Echo (do not echo) input characters") },
249 { "echoe",	BIT,	L_FLAG,	SS,	ECHOE, ECHOE, C("Echo (do not echo) erase characters as backspace-space-backspace") },
250 { "echok",	BIT,	L_FLAG,	SS,	ECHOK, ECHOK, C("Echo (do not echo) a newline after a kill character") },
251 #ifdef ECHOKE
252 { "echoke",	BIT,	L_FLAG,	SS,	ECHOKE, ECHOKE, C("Echo (do not echo) a newline after a kill character") },
253 #endif
254 { "lfkc",	BIT,	L_FLAG,	IG,	ECHOK, ECHOK, C("Same as \bechok\b (\b-echok\b); obsolete") },
255 { "echonl",	BIT,	L_FLAG,	SS,	ECHONL, ECHONL,"Echo (do not echo) newline even if not echoing other character" },
256 #ifdef ECHOCTL
257 { "echoctl",	BIT,	L_FLAG,	SS,	ECHOCTL, ECHOCTL, C("Echo (do not echo) control characters as \b^\b\ac\a") },
258 #else
259 #define ECHOCTL		0
260 #endif /* ECHOCTL */
261 #ifdef ECHOPRT
262 { "echoprt",	BIT,	L_FLAG,	US,	ECHOPRT, ECHOPRT, C("Echo (do not echo) erased characters backward, between '\\' and '/'") },
263 #else
264 #define ECHOPRT		0
265 #endif /* ECHOPRT */
266 #ifdef XCASE
267 { "xcase",	BIT,	L_FLAG,	US,	XCASE, XCASE, C("Enable (disable) \bicanon\b uppercase as lowercase with '\\' prefix") },
268 #endif /* XCASE */
269 #ifdef DEFECHO
270 { "defecho",	BIT,	L_FLAG,	0,	DEFECHO, DEFECHO },
271 #endif /* DEFECHO */
272 #ifdef FLUSHO
273 { "flusho",	BIT,	L_FLAG,	0,	FLUSHO, FLUSHO, C("Discard (do not discard) written data. Cleared by subsequent input") },
274 #endif /* FLUSHO */
275 #ifdef PENDIN
276 { "pendin",	BIT,	L_FLAG,	0,	PENDIN, PENDIN, C("Redisplay pending input at next read and then automatically clear \bpendin\b") },
277 #endif /* PENDIN */
278 { "noflsh",	BIT,	L_FLAG,	US,	NOFLSH, NOFLSH, C("Disable (enable) flushing after \bintr\b and \bquit\b special characters") },
279 #ifdef TOSTOP
280 { "tostop",	BIT,	L_FLAG,	NL|US,	TOSTOP, TOSTOP, C("Stop (do not stop) background jobs that try to write to the terminal") },
281 #endif /* TOSTOP */
282 #ifdef OLCUC
283 { "olcuc",	BIT,	O_FLAG,	US,	OLCUC, OLCUC, C("Translate (do not translate) lowercase characters to uppercase") },
284 #endif /* OLCUC */
285 #ifdef ONLCR
286 { "onlcr",	BIT,	O_FLAG,	SS,	ONLCR, ONLCR, C("Translate (do not translate) newline to carriage return-newline") },
287 #endif /* ONLCR */
288 #ifdef ONLRET
289 { "onlret",	BIT,	O_FLAG,	US,	ONLRET, ONLRET, C("Newline performs (does not perform) a carriage return") },
290 #endif /* ONLRET */
291 #ifdef OCRNL
292 { "ocrnl",	BIT,	O_FLAG,	US,	OCRNL, OCRNL, C("Translate (do not translate) carriage return to newline") },
293 #endif /* OCRNL */
294 #ifdef ONOCR
295 { "onocr",	BIT,	O_FLAG,	US,	ONOCR, ONOCR, C("Do not (do) print carriage returns in the first column") },
296 #endif /* ONOCR */
297 #ifdef OFILL
298 { "ofill",	BIT,	O_FLAG,	US,	OFILL, OFILL, C("Use fill characters (use timing) for delays") },
299 #endif /* OFILL */
300 #ifdef OFDEL
301 { "ofdel",	BIT,	O_FLAG,	US,	OFDEL, OFDEL, C("Use DEL (NUL) as fill characters for delays") },
302 #endif /* OFDEL */
303 { "opost",	BIT,	O_FLAG,	SS,	OPOST, OPOST, C(" Postprocess (do not postprocess) output") },
304 #ifdef CRDLY
305 { "cr0",	BITS,	O_FLAG,	IG|SS,	CRDLY, CR0  },
306 { "cr1",	BITS,	O_FLAG,	US,	CRDLY, CR1  },
307 { "cr2",	BITS,	O_FLAG,	US,	CRDLY, CR2  },
308 { "cr3",	BITS,	O_FLAG,	US,	CRDLY, CR3  },
309 #endif
310 #ifdef NLDLY
311 { "nl0",	BITS,	O_FLAG,	IG|US,	NLDLY, NL0  },
312 { "nl1",	BITS,	O_FLAG,	US,	NLDLY, NL1  },
313 #endif
314 #ifdef TABDLY
315 { "tabs",	TABS,	O_FLAG,	IG,	TABDLY, TAB3, C("Preserve (expand to spaces) tabs") },
316 { "tab0",	BITS,	O_FLAG,	IG|SS,	TABDLY, TAB0  },
317 { "tab1",	BITS,	O_FLAG,	US,	TABDLY, TAB1  },
318 { "tab2",	BITS,	O_FLAG,	US,	TABDLY, TAB2  },
319 { "tab3",	BITS,	O_FLAG,	US,	TABDLY, TAB3  },
320 #endif
321 #ifdef BSDLY
322 { "bs0",	BITS,	O_FLAG,	IG|SS,	BSDLY, BS0 },
323 { "bs1",	BITS,	O_FLAG,	US,	BSDLY, BS1  },
324 #endif
325 #ifdef VTDLY
326 { "vt0",	BITS,	O_FLAG,	IG|SS,	VTDLY, VT0  },
327 { "vt1",	BITS,	O_FLAG,	US,	VTDLY, VT1  },
328 #endif
329 #ifdef FFDLY
330 { "ff0",	BITS,	O_FLAG,	IG|SS,	FFDLY, FF0 },
331 { "ff1",	BITS,	O_FLAG,	US,	FFDLY, FF1 },
332 #endif
333 { "",		MIXED,	O_FLAG,	NL|IG },
334 
335 { "evenp",	MIXED,	C_FLAG,	IG,	PARENB, 0, C("Same as \bparenb -parodd cs7\b") },
336 { "oddp",	MIXED,	C_FLAG,	IG,	PARODD, 0, C("Same as \bparenb parodd cs7\b") },
337 { "parity",	MIXED,	C_FLAG,	IG,	0, 0, C("Same as parenb \b-parodd cs7\b") },
338 { "ek",		MIXED,	C_FLAG,	IG,	0, 0, C("Reset the \berase\b and \bkill\b special characters to their default values") },
339 { "sane",	SANE,	C_FLAG,	IG,	0, 0, C("Reset all modes to some reasonable values") },
340 { "cooked",	COOKED,	C_FLAG,	IG,	0, 0, C("Disable raw input and output") },
341 { "raw",	COOKED,	C_FLAG,	IG,	0, 0, C("Enable raw input and output") },
342 { "lcase",	CASE,	C_FLAG,	IG,	0 , 0, C("Set \bxcase\b, \biuclc\b, and \bolcuc\b") },
343 { "LCASE",	CASE,	C_FLAG,	IG,	0 , 0, C("Same as \blcase\b") }
344 };
345 
346 #if CC_NATIVE == CC_ASCII
347 #define cntl(x)		(((x)=='?')?0177:((x)&037))
348 #else
349 #define cntl(x)		(((x)=='?')?ccmapc(0177,CC_ASCII,CC_NATIVE):ccmapc(ccmapc(x,CC_NATIVE,CC_ASCII)&037,CC_ASCII,CC_NATIVE))
350 #endif
351 
352 static void sane(register struct termios *sp)
353 {
354 	register const Tty_t*	tp;
355 
356 	for (tp = Ttable; tp < &Ttable[elementsof(Ttable)]; tp++)
357 		if (tp->flags & (SS|US))
358 			switch (tp->type)
359 			{
360 			case BIT:
361 			case BITS:
362 				switch (tp->field)
363 				{
364 				case C_FLAG:
365 					if (tp->flags & SS)
366 						sp->c_cflag |= tp->mask;
367 					else
368 						sp->c_cflag &= ~tp->mask;
369 					break;
370 				case I_FLAG:
371 					if (tp->flags & SS)
372 						sp->c_iflag |= tp->mask;
373 					else
374 						sp->c_iflag &= ~tp->mask;
375 					break;
376 				case O_FLAG:
377 					if (tp->flags & SS)
378 						sp->c_oflag |= tp->mask;
379 					else
380 						sp->c_oflag &= ~tp->mask;
381 					break;
382 				case L_FLAG:
383 					if (tp->flags & SS)
384 						sp->c_lflag |= tp->mask;
385 					else
386 						sp->c_lflag &= ~tp->mask;
387 					break;
388 				}
389 				break;
390 			case CHAR:
391 				sp->c_cc[tp->mask] = cntl(tp->val);
392 				break;
393 			}
394 }
395 
396 static int gin(char *arg,struct termios *sp)
397 {
398 	register int i;
399 	if(*arg++ != ':')
400 		return(0);
401 	sp->c_iflag = strtol(arg,&arg,16);
402 	if(*arg++ != ':')
403 		return(0);
404 	sp->c_oflag = strtol(arg,&arg,16);
405 	if(*arg++ != ':')
406 		return(0);
407 	sp->c_cflag = strtol(arg,&arg,16);
408 	if(*arg++ != ':')
409 		return(0);
410 	sp->c_lflag = strtol(arg,&arg,16);
411 	if(*arg++ != ':')
412 		return(0);
413 	for(i=0;i< NCCS; i++)
414 	{
415 		sp->c_cc[i] = strtol(arg,&arg,16);
416 		if(*arg++ != ':')
417 			return(0);
418 	}
419 #if _mem_c_line_termios
420 	sp->c_line =
421 #endif
422 		strtol(arg,&arg,16);
423 	if(*arg++ != ':')
424 		return(0);
425 	i = strtol(arg,&arg,16);
426 	if(*arg++ != ':')
427 		return(0);
428 	cfsetispeed(sp, i);
429 	i = strtol(arg,&arg,16);
430 	if(*arg++ != ':')
431 		return(0);
432 	cfsetospeed(sp, i);
433 	if(*arg)
434 		return(0);
435 	return(1);
436 }
437 
438 static void gout(struct termios *sp)
439 {
440 	register int i;
441 	sfprintf(sfstdout,":%x",sp->c_iflag);
442 	sfprintf(sfstdout,":%x",sp->c_oflag);
443 	sfprintf(sfstdout,":%x",sp->c_cflag);
444 	sfprintf(sfstdout,":%x",sp->c_lflag);
445 	for(i=0;i< NCCS; i++)
446 		sfprintf(sfstdout,":%x",sp->c_cc[i]);
447 #if _mem_c_line_termios
448 	sfprintf(sfstdout,":%x", sp->c_line);
449 #else
450 	sfprintf(sfstdout,":%x", 0);
451 #endif
452 	sfprintf(sfstdout,":%x",cfgetispeed(sp));
453 	sfprintf(sfstdout,":%x",cfgetospeed(sp));
454 	sfprintf(sfstdout,":\n");
455 }
456 
457 static void output(struct termios *sp, int flags)
458 {
459 	const Tty_t *tp;
460 	struct termios tty;
461 	register int delim = ' ';
462 	register int i,off,off2;
463 	char schar[2];
464 	unsigned int ispeed = cfgetispeed(sp);
465 	unsigned int ospeed = cfgetospeed(sp);
466 	if(flags&G_FLAG)
467 	{
468 		gout(sp);
469 		return;
470 	}
471 	tty = *sp;
472 	sane(&tty);
473 	for(i=0; i < elementsof(Ttable); i++)
474 	{
475 		tp= &Ttable[i];
476 		if(tp->flags&IG)
477 		{
478 			if(tp->flags&NL)
479 				sfputc(sfstdout,'\n');
480 			continue;
481 		}
482 		switch(tp->type)
483 		{
484 		    case BIT:
485 		    case BITS:
486 			off = 1;
487 			switch(tp->field)
488 			{
489 			    case C_FLAG:
490 				if(sp->c_cflag&tp->mask)
491 					off = 0;
492 				if(tty.c_cflag&tp->mask)
493 					off2 = 0;
494 				break;
495 			    case I_FLAG:
496 				if(sp->c_iflag&tp->mask)
497 					off = 0;
498 				if(tty.c_iflag&tp->mask)
499 					off2 = 0;
500 				break;
501 			    case O_FLAG:
502 				if((sp->c_oflag&tp->mask)==tp->val)
503 					off = 0;
504 				if(tty.c_oflag&tp->mask)
505 					off2 = 0;
506 				break;
507 			    case L_FLAG:
508 				if(sp->c_lflag&tp->mask)
509 					off = 0;
510 				if(tty.c_lflag&tp->mask)
511 					off2 = 0;
512 			}
513 			if(tp->flags&NL)
514 				delim = '\n';
515 			if(!flags && off==off2)
516 				continue;
517 			if(!off)
518 				sfprintf(sfstdout,"%s%c",tp->name,delim);
519 			else if(tp->type==BIT)
520 				sfprintf(sfstdout,"-%s%c",tp->name,delim);
521 			delim = ' ';
522 			break;
523 
524 		    case CHAR:
525 			off = sp->c_cc[tp->mask];
526 			if(tp->flags&NL)
527 				delim = '\n';
528 			if(!flags && off==(unsigned char)tty.c_cc[tp->mask])
529 				continue;
530 			if(off==_POSIX_VDISABLE)
531 				sfprintf(sfstdout,"%s = <undef>;%c",tp->name,delim);
532 			else if(isprint(off&0xff))
533 				sfprintf(sfstdout,"%s = %c;%c",tp->name,off,delim);
534 			else
535 #if CC_NATIVE == CC_ASCII
536 			sfprintf(sfstdout,"%s = ^%c;%c",tp->name,off==0177?'?':(off^0100),delim);
537 #else
538 			{
539 				off = ccmapc(off, CC_NATIVE, CC_ASCII);
540 				sfprintf(sfstdout,"%s = ^%c;%c",tp->name,off==0177?'?':ccmapc(off^0100,CC_ASCII,CC_NATIVE),delim);
541 			}
542 #endif
543 			delim = ' ';
544 			break;
545 		    case SIZE:
546 			if((sp->c_cflag&CSIZE)!=tp->mask)
547 				continue;
548 			if(flags || (sp->c_cflag&CSIZE) != (tty.c_cflag&CSIZE))
549 				sfprintf(sfstdout,"%s ",tp->name);
550 			break;
551 		    case SPEED:
552 			if(tp->mask==ispeed)
553 			{
554 				if(ispeed!=ospeed)
555 					schar[0]='i';
556 				else
557 					schar[0]=0;
558 			}
559 			else if(tp->mask==ospeed)
560 				schar[0]='o';
561 			else
562 				continue;
563 			schar[1] = 0;
564 #ifdef TIOCSWINSZ
565 			{
566 				struct winsize win;
567 				off = ioctl(0,TIOCGWINSZ,&win);
568 				if(off>=0)
569 					sfprintf(sfstdout,"%sspeed %s baud; rows %d; columns %d;\n",schar,tp->name,win.ws_row,win.ws_col);
570 			}
571 			if(off<0)
572 #endif
573 				sfprintf(sfstdout,"%sspeed %s baud;\n",schar,tp->name);
574 		}
575 	}
576 	if(delim=='\n')
577 		sfputc(sfstdout,'\n');
578 }
579 
580 static const Tty_t *lookup(const char *name)
581 {
582 	register int i;
583 	for(i=0; i < elementsof(Ttable); i++)
584 	{
585 		if(strcmp(Ttable[i].name,name)==0)
586 			return(&Ttable[i]);
587 	}
588 	return(0);
589 
590 }
591 
592 static const Tty_t *getspeed(unsigned long val)
593 {
594 	register int i;
595 	for(i=0; i < elementsof(Ttable); i++)
596 	{
597 		if(Ttable[i].type==SPEED && Ttable[i].mask==val)
598 			return(&Ttable[i]);
599 	}
600 	return(0);
601 }
602 
603 static int gettchar(register const char *cp)
604 {
605 	if(*cp==0)
606 		return(-1);
607 	if(cp[1]==0)
608 		return((unsigned)cp[0]);
609 	if(*cp=='^' && cp[1] && cp[2]==0)
610 	{
611 		switch(cp[1])
612 		{
613 		    case '-':
614 			return(-1);
615 		    default:
616 			return(cntl(cp[1]));
617 		}
618 	}
619 	if(streq(cp,"undef") || streq(cp,"<undef>"))
620 		return(-1);
621 	return(*((unsigned char*)cp));
622 }
623 
624 static void set(char *argv[], struct termios *sp)
625 {
626 	const Tty_t *tp;
627 	register int c,off;
628 	char *cp;
629 	char *ep;
630 	while(cp = *argv++)
631 	{
632 		off = 0;
633 		if(*cp=='-')
634 		{
635 			cp++;
636 			off=1;
637 		}
638 		if(!(tp=lookup(cp)) || (off && (tp->type!=BIT) && (tp->type!=TABS)))
639 			error(ERROR_exit(1),"%s: unknown mode",cp);
640 		switch(tp->type)
641 		{
642 		    case CHAR:
643 			if(off)
644 				error(ERROR_exit(1),"%s: unknown mode",cp);
645 			if(!*argv)
646 				error(ERROR_exit(1),"missing argument to %s",cp);
647 			c = gettchar(*argv++);
648 			if(c>=0)
649 				sp->c_cc[tp->mask] = c;
650 			else
651 				sp->c_cc[tp->mask] = _POSIX_VDISABLE;
652 			break;
653 		    case BIT: case BITS:
654 			switch(tp->field)
655 			{
656 			    case C_FLAG:
657 				if(off)
658 					sp->c_cflag &= ~tp->mask;
659 				else
660 					sp->c_cflag |= tp->mask;
661 				break;
662 			    case I_FLAG:
663 				if(off)
664 					sp->c_iflag &= ~tp->mask;
665 				else
666 					sp->c_iflag |= tp->mask;
667 				break;
668 			    case O_FLAG:
669 				sp->c_oflag &= ~tp->mask;
670 				sp->c_oflag |= tp->val;
671 				break;
672 			    case L_FLAG:
673 				if(off)
674 					sp->c_lflag &= ~tp->mask;
675 				else
676 					sp->c_lflag |= tp->mask;
677 				break;
678 			}
679 			break;
680 		    case TABS:
681 			sp->c_oflag &= ~tp->mask;
682 			if(off)
683 				sp->c_oflag |= tp->val;
684 			break;
685 #ifdef TIOCSWINSZ
686 		    case WIND:
687 		    {
688 			struct winsize win;
689 			int n;
690 			if(ioctl(0,TIOCGWINSZ,&win)<0)
691 				error(ERROR_system(1),"cannot set %s",tp->name);
692 			if(!(cp= *argv))
693 			{
694 				sfprintf(sfstdout,"%d\n",tp->mask?win.ws_col:win.ws_row);
695 				break;
696 			}
697 			argv++;
698 			n=strtol(cp,&cp,10);
699 			if(*cp)
700 				error(ERROR_system(1),"%d: invalid number of %s",argv[-1],tp->name);
701 			if(tp->mask)
702 				win.ws_col = n;
703 			else
704 				win.ws_row = n;
705 			if(ioctl(0,TIOCSWINSZ,&win)<0)
706 				error(ERROR_system(1),"cannot set %s",tp->name);
707 			break;
708 		    }
709 #endif
710 		    case NUM:
711 			cp = *argv;
712 			if (!cp)
713 			{
714 				if (tp->field == C_SPEED)
715 				{
716 					if (tp = getspeed(*tp->name == 'i' ? cfgetispeed(sp) : cfgetospeed(sp)))
717 						sfprintf(sfstdout, "%s\n", tp->name);
718 					break;
719 				}
720 				error(ERROR_exit(1), "%s: missing numeric argument", tp->name);
721 			}
722 			argv++;
723 			c = (int)strtol(cp, &ep, 10);
724 			if (*ep)
725 				error(ERROR_exit(1), "%s: %s: numeric argument expected", tp->name, cp);
726 			switch (tp->field)
727 			{
728 #if _mem_c_line_termios
729 			case C_LINE:
730 				sp->c_line = c;
731 				break;
732 #endif
733 			case C_SPEED:
734 				if(getspeed(c))
735 				{
736 					if (*tp->name != 'o')
737 						cfsetispeed(sp, c);
738 					if (*tp->name != 'i')
739 						cfsetospeed(sp, c);
740 				}
741 				else
742 					error(ERROR_exit(1), "%s: %s: invalid speed", tp->name, cp);
743 				break;
744 			case T_CHAR:
745 				sp->c_cc[tp->mask] = c;
746 				break;
747 			}
748 			break;
749 		    case SPEED:
750 			cfsetospeed(sp, tp->mask);
751 			cfsetispeed(sp, tp->mask);
752 			break;
753 		    case SIZE:
754 			sp->c_cflag &= ~CSIZE;
755 			sp->c_cflag |= tp->mask;
756 			break;
757 		    case SANE:
758 			sane(sp);
759 			break;
760 #if defined(OLCUC) && defined(IUCLC)
761 		    case CASE:
762 			if(off)
763 			{
764 				sp->c_iflag |= IUCLC;
765 				sp->c_oflag |= OLCUC;
766 			}
767 			else
768 			{
769 				sp->c_iflag &= ~IUCLC;
770 				sp->c_oflag &= ~OLCUC;
771 			}
772 			break;
773 #endif /* OLCUC && IUCLC */
774 		}
775 	}
776 }
777 
778 
779 static void listchars(Sfio_t *sp,int type)
780 {
781 	int i,c;
782 	c = (type==CHAR?'c':'n');
783 	for(i=0; i < elementsof(Ttable); i++)
784 	{
785 		if(Ttable[i].type==type && *Ttable[i].description)
786 			sfprintf(sp,"[+%s \a%c\a?%s.]",Ttable[i].name,c,Ttable[i].description);
787 	}
788 }
789 
790 static void listgroup(Sfio_t *sp,int type, const char *description)
791 {
792 	int i;
793 	sfprintf(sp,"[+");
794 	for(i=0; i < elementsof(Ttable); i++)
795 	{
796 		if(Ttable[i].type==type)
797 			sfprintf(sp,"%s ",Ttable[i].name);
798 	}
799 	sfprintf(sp,"?%s.]",description);
800 }
801 
802 static void listmask(Sfio_t *sp,unsigned int mask,const char *description)
803 {
804 	int i;
805 	sfprintf(sp,"[+");
806 	for(i=0; i < elementsof(Ttable); i++)
807 	{
808 		if(Ttable[i].mask==mask && Ttable[i].type==BITS)
809 			sfprintf(sp,"%s ",Ttable[i].name);
810 	}
811 	sfprintf(sp,"?%s.]",description);
812 }
813 
814 static void listfields(Sfio_t *sp,int field)
815 {
816 	int i;
817 	for(i=0; i < elementsof(Ttable); i++)
818 	{
819 		if(Ttable[i].field==field &&  Ttable[i].type==BIT && *Ttable[i].description)
820 			sfprintf(sp,"[+%s (-%s)?%s.]",Ttable[i].name,Ttable[i].name,Ttable[i].description);
821 	}
822 }
823 
824 static void listmode(Sfio_t *sp,const char *name)
825 {
826 	sfprintf(sp,"[+%s?%s.]",name,lookup(name)->description);
827 }
828 
829 static int infof(Opt_t* op, Sfio_t* sp, const char* s, Optdisc_t* dp)
830 {
831 	NoP(op);
832 	NoP(s);
833 	NoP(dp);
834 	sfprintf(sp,"[+Control Modes.]{");
835 	listfields(sp,C_FLAG);
836 	listgroup(sp,SPEED,"Attempt to set input and output baud rate to number given.  A value of \b0\b causes immediate hangup");
837 	listchars(sp,NUM);
838 	listgroup(sp,SIZE,"Number of bits in a character");
839 	sfprintf(sp,"}[+Input Modes.]{");
840 	listfields(sp,I_FLAG);
841 	sfprintf(sp,"}[+Output Modes.]{");
842 	listfields(sp,O_FLAG);
843 #ifdef CRDLY
844 	listmask(sp,CRDLY,"Carriage return delay style");
845 #endif
846 #ifdef NLDLY
847 	listmask(sp,NLDLY,"Newline delay style");
848 #endif
849 #ifdef TABDLY
850 	listmask(sp,TABDLY,"Horizontal tab delay style");
851 #endif
852 #ifdef BSDLY
853 	listmask(sp,BSDLY,"Backspace delay style");
854 #endif
855 #ifdef FFDLY
856 	listmask(sp,FFDLY,"Form feed delay style");
857 #endif
858 #ifdef VTDLY
859 	listmask(sp,VTDLY,"Vertical tab delay style");
860 #endif
861 	sfprintf(sp,"}[+Local Modes.]{");
862 	listfields(sp,L_FLAG);
863 	sfprintf(sp,"}[+Control Assignments.?If \ac\a is \bundef\b or an empty "
864 		"string then the control assignment is disabled.]{");
865 	listchars(sp,WIND);
866 	listchars(sp,CHAR);
867 	sfprintf(sp,"}[+Combination Modes.]{");
868 	listmode(sp,"ek");
869 	listmode(sp,"evenp");
870 	listmode(sp,"lcase");
871 	listmode(sp,"oddp");
872 	listmode(sp,"parity");
873 	listmode(sp,"sane");
874 	listmode(sp,"tabs");
875 	listmode(sp,"LCASE");
876 	sfputc(sp,'}');
877 	return(1);
878 }
879 
880 int
881 b_stty(int argc, char** argv, void* context)
882 {
883 	struct termios		tty;
884 	register int		n;
885 	register int		flags = 0;
886 	const Tty_t*		tp;
887 	Optdisc_t		disc;
888 
889 	cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_INTERACTIVE);
890 	if (tcgetattr(0, &tty) < 0)
891 		error(ERROR_system(1),"not a tty");
892 	memset(&disc, 0, sizeof(disc));
893 	disc.version = OPT_VERSION;
894 	disc.infof = infof;
895 	opt_info.disc = &disc;
896 	for (;;)
897 	{
898 		switch (n = optget(argv, usage))
899 		{
900 		case 'a':
901 		case 'g':
902 			if (!opt_info.offset || !argv[opt_info.index][opt_info.offset])
903 			{
904 				switch (n)
905 				{
906 				case 'a':
907 					flags |= A_FLAG;
908 					break;
909 				case 'g':
910 					flags |= G_FLAG;
911 					break;
912 				}
913 				continue;
914 			}
915 			/*FALLTHROUGH*/
916 		case ':':
917 			if (!opt_info.offset)
918 				error(2, "%s", opt_info.arg);
919 			else if (!(tp = lookup(argv[opt_info.index]+1)) || (tp->type != BIT && tp->type != TABS))
920 				error(ERROR_exit(1), "%s: unknown mode", argv[opt_info.index]);
921 			break;
922 		case '?':
923 			error(ERROR_usage(2), "%s", opt_info.arg);
924 			break;
925 		}
926 		break;
927 	}
928 	argv += opt_info.index;
929 	if (error_info.errors || (flags && *argv))
930 		error(ERROR_usage(2), "%s", optusage(NiL));
931 	if (*argv)
932 	{
933 		if (!argv[1] && **argv == ':')
934 			gin(*argv, &tty);
935 		else
936 			set(argv, &tty);
937 		if (tcsetattr(0, TCSANOW, &tty) < 0)
938 			error(ERROR_system(1), "cannot set tty");
939 	}
940 	else
941 		output(&tty, flags);
942 	return error_info.errors;
943 }
944