stty: fix bug reported by Paul Albrecht <albrecht@rdi1.com> -
[oweals/busybox.git] / coreutils / stty.c
1 /* vi: set sw=4 ts=4: */
2 /* stty -- change and print terminal line settings
3    Copyright (C) 1990-1999 Free Software Foundation, Inc.
4
5    Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
6 */
7 /* Usage: stty [-ag] [-F device] [setting...]
8
9    Options:
10    -a Write all current settings to stdout in human-readable form.
11    -g Write all current settings to stdout in stty-readable form.
12    -F Open and use the specified device instead of stdin
13
14    If no args are given, write to stdout the baud rate and settings that
15    have been changed from their defaults.  Mode reading and changes
16    are done on the specified device, or stdin if none was specified.
17
18    David MacKenzie <djm@gnu.ai.mit.edu>
19
20    Special for busybox ported by Vladimir Oleynik <dzo@simtreas.ru> 2001
21
22    */
23
24 #include "busybox.h"
25
26 #ifndef _POSIX_VDISABLE
27 # define _POSIX_VDISABLE ((unsigned char) 0)
28 #endif
29
30 #define Control(c) ((c) & 0x1f)
31 /* Canonical values for control characters */
32 #ifndef CINTR
33 # define CINTR Control('c')
34 #endif
35 #ifndef CQUIT
36 # define CQUIT 28
37 #endif
38 #ifndef CERASE
39 # define CERASE 127
40 #endif
41 #ifndef CKILL
42 # define CKILL Control('u')
43 #endif
44 #ifndef CEOF
45 # define CEOF Control('d')
46 #endif
47 #ifndef CEOL
48 # define CEOL _POSIX_VDISABLE
49 #endif
50 #ifndef CSTART
51 # define CSTART Control('q')
52 #endif
53 #ifndef CSTOP
54 # define CSTOP Control('s')
55 #endif
56 #ifndef CSUSP
57 # define CSUSP Control('z')
58 #endif
59 #if defined(VEOL2) && !defined(CEOL2)
60 # define CEOL2 _POSIX_VDISABLE
61 #endif
62 /* ISC renamed swtch to susp for termios, but we'll accept either name */
63 #if defined(VSUSP) && !defined(VSWTCH)
64 # define VSWTCH VSUSP
65 # define CSWTCH CSUSP
66 #endif
67 #if defined(VSWTCH) && !defined(CSWTCH)
68 # define CSWTCH _POSIX_VDISABLE
69 #endif
70
71 /* SunOS 5.3 loses (^Z doesn't work) if 'swtch' is the same as 'susp'.
72    So the default is to disable 'swtch.'  */
73 #if defined (__sparc__) && defined (__svr4__)
74 # undef CSWTCH
75 # define CSWTCH _POSIX_VDISABLE
76 #endif
77
78 #if defined(VWERSE) && !defined (VWERASE)       /* AIX-3.2.5 */
79 # define VWERASE VWERSE
80 #endif
81 #if defined(VDSUSP) && !defined (CDSUSP)
82 # define CDSUSP Control('y')
83 #endif
84 #if !defined(VREPRINT) && defined(VRPRNT)       /* Irix 4.0.5 */
85 # define VREPRINT VRPRNT
86 #endif
87 #if defined(VREPRINT) && !defined(CRPRNT)
88 # define CRPRNT Control('r')
89 #endif
90 #if defined(VWERASE) && !defined(CWERASE)
91 # define CWERASE Control('w')
92 #endif
93 #if defined(VLNEXT) && !defined(CLNEXT)
94 # define CLNEXT Control('v')
95 #endif
96 #if defined(VDISCARD) && !defined(VFLUSHO)
97 # define VFLUSHO VDISCARD
98 #endif
99 #if defined(VFLUSH) && !defined(VFLUSHO)        /* Ultrix 4.2 */
100 # define VFLUSHO VFLUSH
101 #endif
102 #if defined(CTLECH) && !defined(ECHOCTL)        /* Ultrix 4.3 */
103 # define ECHOCTL CTLECH
104 #endif
105 #if defined(TCTLECH) && !defined(ECHOCTL)       /* Ultrix 4.2 */
106 # define ECHOCTL TCTLECH
107 #endif
108 #if defined(CRTKIL) && !defined(ECHOKE)         /* Ultrix 4.2 and 4.3 */
109 # define ECHOKE CRTKIL
110 #endif
111 #if defined(VFLUSHO) && !defined(CFLUSHO)
112 # define CFLUSHO Control('o')
113 #endif
114 #if defined(VSTATUS) && !defined(CSTATUS)
115 # define CSTATUS Control('t')
116 #endif
117
118 /* Which speeds to set */
119 enum speed_setting {
120         input_speed, output_speed, both_speeds
121 };
122
123 /* Which member(s) of 'struct termios' a mode uses */
124 enum {
125         /* Do NOT change the order or values, as mode_type_flag()
126          * depends on them */
127         control, input, output, local, combination
128 };
129
130 static const char evenp     [] = "evenp";
131 static const char raw       [] = "raw";
132 static const char stty_min  [] = "min";
133 static const char stty_time [] = "time";
134 static const char stty_swtch[] = "swtch";
135 static const char stty_eol  [] = "eol";
136 static const char stty_eof  [] = "eof";
137 static const char parity    [] = "parity";
138 static const char stty_oddp [] = "oddp";
139 static const char stty_nl   [] = "nl";
140 static const char stty_ek   [] = "ek";
141 static const char stty_sane [] = "sane";
142 static const char cbreak    [] = "cbreak";
143 static const char stty_pass8[] = "pass8";
144 static const char litout    [] = "litout";
145 static const char cooked    [] = "cooked";
146 static const char decctlq   [] = "decctlq";
147 static const char stty_tabs [] = "tabs";
148 static const char stty_lcase[] = "lcase";
149 static const char stty_LCASE[] = "LCASE";
150 static const char stty_crt  [] = "crt";
151 static const char stty_dec  [] = "dec";
152
153 /* Flags for 'struct mode_info' */
154 #define SANE_SET 1              /* Set in 'sane' mode                  */
155 #define SANE_UNSET 2            /* Unset in 'sane' mode                */
156 #define REV 4                   /* Can be turned off by prepending '-' */
157 #define OMIT 8                  /* Don't display value                 */
158
159 /* Each mode */
160 struct mode_info {
161         const char *name;       /* Name given on command line           */
162         char type;              /* Which structure element to change    */
163         char flags;             /* Setting and display options          */
164         /* were using short here, but ppc32 was unhappy: */
165         tcflag_t mask;          /* Other bits to turn off for this mode */
166         tcflag_t bits;          /* Bits to set for this mode            */
167 };
168
169 /* We can optimize it further by using name[8] instead of char *name */
170 /* but beware of "if (info->name == evenp)" checks! */
171 /* Need to replace them with "if (info == &mode_info[EVENP_INDX])" */
172
173 #define MI_ENTRY(N,T,F,B,M) { N, T, F, M, B }
174
175 static const struct mode_info mode_info[] = {
176         MI_ENTRY("parenb",   control,     REV,               PARENB,     0 ),
177         MI_ENTRY("parodd",   control,     REV,               PARODD,     0 ),
178         MI_ENTRY("cs5",      control,     0,                 CS5,     CSIZE),
179         MI_ENTRY("cs6",      control,     0,                 CS6,     CSIZE),
180         MI_ENTRY("cs7",      control,     0,                 CS7,     CSIZE),
181         MI_ENTRY("cs8",      control,     0,                 CS8,     CSIZE),
182         MI_ENTRY("hupcl",    control,     REV,               HUPCL,      0 ),
183         MI_ENTRY("hup",      control,     REV        | OMIT, HUPCL,      0 ),
184         MI_ENTRY("cstopb",   control,     REV,               CSTOPB,     0 ),
185         MI_ENTRY("cread",    control,     SANE_SET   | REV,  CREAD,      0 ),
186         MI_ENTRY("clocal",   control,     REV,               CLOCAL,     0 ),
187 #ifdef CRTSCTS
188         MI_ENTRY("crtscts",  control,     REV,               CRTSCTS,    0 ),
189 #endif
190         MI_ENTRY("ignbrk",   input,       SANE_UNSET | REV,  IGNBRK,     0 ),
191         MI_ENTRY("brkint",   input,       SANE_SET   | REV,  BRKINT,     0 ),
192         MI_ENTRY("ignpar",   input,       REV,               IGNPAR,     0 ),
193         MI_ENTRY("parmrk",   input,       REV,               PARMRK,     0 ),
194         MI_ENTRY("inpck",    input,       REV,               INPCK,      0 ),
195         MI_ENTRY("istrip",   input,       REV,               ISTRIP,     0 ),
196         MI_ENTRY("inlcr",    input,       SANE_UNSET | REV,  INLCR,      0 ),
197         MI_ENTRY("igncr",    input,       SANE_UNSET | REV,  IGNCR,      0 ),
198         MI_ENTRY("icrnl",    input,       SANE_SET   | REV,  ICRNL,      0 ),
199         MI_ENTRY("ixon",     input,       REV,               IXON,       0 ),
200         MI_ENTRY("ixoff",    input,       SANE_UNSET | REV,  IXOFF,      0 ),
201         MI_ENTRY("tandem",   input,       REV        | OMIT, IXOFF,      0 ),
202 #ifdef IUCLC
203         MI_ENTRY("iuclc",    input,       SANE_UNSET | REV,  IUCLC,      0 ),
204 #endif
205 #ifdef IXANY
206         MI_ENTRY("ixany",    input,       SANE_UNSET | REV,  IXANY,      0 ),
207 #endif
208 #ifdef IMAXBEL
209         MI_ENTRY("imaxbel",  input,       SANE_SET   | REV,  IMAXBEL,    0 ),
210 #endif
211         MI_ENTRY("opost",    output,      SANE_SET   | REV,  OPOST,      0 ),
212 #ifdef OLCUC
213         MI_ENTRY("olcuc",    output,      SANE_UNSET | REV,  OLCUC,      0 ),
214 #endif
215 #ifdef OCRNL
216         MI_ENTRY("ocrnl",    output,      SANE_UNSET | REV,  OCRNL,      0 ),
217 #endif
218 #ifdef ONLCR
219         MI_ENTRY("onlcr",    output,      SANE_SET   | REV,  ONLCR,      0 ),
220 #endif
221 #ifdef ONOCR
222         MI_ENTRY("onocr",    output,      SANE_UNSET | REV,  ONOCR,      0 ),
223 #endif
224 #ifdef ONLRET
225         MI_ENTRY("onlret",   output,      SANE_UNSET | REV,  ONLRET,     0 ),
226 #endif
227 #ifdef OFILL
228         MI_ENTRY("ofill",    output,      SANE_UNSET | REV,  OFILL,      0 ),
229 #endif
230 #ifdef OFDEL
231         MI_ENTRY("ofdel",    output,      SANE_UNSET | REV,  OFDEL,      0 ),
232 #endif
233 #ifdef NLDLY
234         MI_ENTRY("nl1",      output,      SANE_UNSET,        NL1,     NLDLY),
235         MI_ENTRY("nl0",      output,      SANE_SET,          NL0,     NLDLY),
236 #endif
237 #ifdef CRDLY
238         MI_ENTRY("cr3",      output,      SANE_UNSET,        CR3,     CRDLY),
239         MI_ENTRY("cr2",      output,      SANE_UNSET,        CR2,     CRDLY),
240         MI_ENTRY("cr1",      output,      SANE_UNSET,        CR1,     CRDLY),
241         MI_ENTRY("cr0",      output,      SANE_SET,          CR0,     CRDLY),
242 #endif
243
244 #ifdef TABDLY
245         MI_ENTRY("tab3",     output,      SANE_UNSET,        TAB3,   TABDLY),
246         MI_ENTRY("tab2",     output,      SANE_UNSET,        TAB2,   TABDLY),
247         MI_ENTRY("tab1",     output,      SANE_UNSET,        TAB1,   TABDLY),
248         MI_ENTRY("tab0",     output,      SANE_SET,          TAB0,   TABDLY),
249 #else
250 # ifdef OXTABS
251         MI_ENTRY("tab3",     output,      SANE_UNSET,        OXTABS,     0 ),
252 # endif
253 #endif
254
255 #ifdef BSDLY
256         MI_ENTRY("bs1",      output,      SANE_UNSET,        BS1,     BSDLY),
257         MI_ENTRY("bs0",      output,      SANE_SET,          BS0,     BSDLY),
258 #endif
259 #ifdef VTDLY
260         MI_ENTRY("vt1",      output,      SANE_UNSET,        VT1,     VTDLY),
261         MI_ENTRY("vt0",      output,      SANE_SET,          VT0,     VTDLY),
262 #endif
263 #ifdef FFDLY
264         MI_ENTRY("ff1",      output,      SANE_UNSET,        FF1,     FFDLY),
265         MI_ENTRY("ff0",      output,      SANE_SET,          FF0,     FFDLY),
266 #endif
267         MI_ENTRY("isig",     local,       SANE_SET   | REV,  ISIG,       0 ),
268         MI_ENTRY("icanon",   local,       SANE_SET   | REV,  ICANON,     0 ),
269 #ifdef IEXTEN
270         MI_ENTRY("iexten",   local,       SANE_SET   | REV,  IEXTEN,     0 ),
271 #endif
272         MI_ENTRY("echo",     local,       SANE_SET   | REV,  ECHO,       0 ),
273         MI_ENTRY("echoe",    local,       SANE_SET   | REV,  ECHOE,      0 ),
274         MI_ENTRY("crterase", local,       REV        | OMIT, ECHOE,      0 ),
275         MI_ENTRY("echok",    local,       SANE_SET   | REV,  ECHOK,      0 ),
276         MI_ENTRY("echonl",   local,       SANE_UNSET | REV,  ECHONL,     0 ),
277         MI_ENTRY("noflsh",   local,       SANE_UNSET | REV,  NOFLSH,     0 ),
278 #ifdef XCASE
279         MI_ENTRY("xcase",    local,       SANE_UNSET | REV,  XCASE,      0 ),
280 #endif
281 #ifdef TOSTOP
282         MI_ENTRY("tostop",   local,       SANE_UNSET | REV,  TOSTOP,     0 ),
283 #endif
284 #ifdef ECHOPRT
285         MI_ENTRY("echoprt",  local,       SANE_UNSET | REV,  ECHOPRT,    0 ),
286         MI_ENTRY("prterase", local,       REV | OMIT,        ECHOPRT,    0 ),
287 #endif
288 #ifdef ECHOCTL
289         MI_ENTRY("echoctl",  local,       SANE_SET   | REV,  ECHOCTL,    0 ),
290         MI_ENTRY("ctlecho",  local,       REV        | OMIT, ECHOCTL,    0 ),
291 #endif
292 #ifdef ECHOKE
293         MI_ENTRY("echoke",   local,       SANE_SET   | REV,  ECHOKE,     0 ),
294         MI_ENTRY("crtkill",  local,       REV        | OMIT, ECHOKE,     0 ),
295 #endif
296         MI_ENTRY(evenp,      combination, REV        | OMIT, 0,          0 ),
297         MI_ENTRY(parity,     combination, REV        | OMIT, 0,          0 ),
298         MI_ENTRY(stty_oddp,  combination, REV        | OMIT, 0,          0 ),
299         MI_ENTRY(stty_nl,    combination, REV        | OMIT, 0,          0 ),
300         MI_ENTRY(stty_ek,    combination, OMIT,              0,          0 ),
301         MI_ENTRY(stty_sane,  combination, OMIT,              0,          0 ),
302         MI_ENTRY(cooked,     combination, REV        | OMIT, 0,          0 ),
303         MI_ENTRY(raw,        combination, REV        | OMIT, 0,          0 ),
304         MI_ENTRY(stty_pass8, combination, REV        | OMIT, 0,          0 ),
305         MI_ENTRY(litout,     combination, REV        | OMIT, 0,          0 ),
306         MI_ENTRY(cbreak,     combination, REV        | OMIT, 0,          0 ),
307 #ifdef IXANY
308         MI_ENTRY(decctlq,    combination, REV        | OMIT, 0,          0 ),
309 #endif
310 #if defined(TABDLY) || defined(OXTABS)
311         MI_ENTRY(stty_tabs,  combination, REV        | OMIT, 0,          0 ),
312 #endif
313 #if defined(XCASE) && defined(IUCLC) && defined(OLCUC)
314         MI_ENTRY(stty_lcase, combination, REV        | OMIT, 0,          0 ),
315         MI_ENTRY(stty_LCASE, combination, REV        | OMIT, 0,          0 ),
316 #endif
317         MI_ENTRY(stty_crt,   combination, OMIT,              0,          0 ),
318         MI_ENTRY(stty_dec,   combination, OMIT,              0,          0 ),
319 };
320
321 enum {
322         NUM_mode_info = (sizeof(mode_info) / sizeof(mode_info[0]))
323 };
324
325 /* Control character settings */
326 struct control_info {
327         const char *name;               /* Name given on command line */
328         unsigned char saneval;          /* Value to set for 'stty sane' */
329         unsigned char offset;           /* Offset in c_cc */
330 };
331
332 /* Control characters */
333
334 static const struct control_info control_info[] = {
335         {"intr",     CINTR,   VINTR},
336         {"quit",     CQUIT,   VQUIT},
337         {"erase",    CERASE,  VERASE},
338         {"kill",     CKILL,   VKILL},
339         {stty_eof,   CEOF,    VEOF},
340         {stty_eol,   CEOL,    VEOL},
341 #ifdef VEOL2
342         {"eol2",     CEOL2,   VEOL2},
343 #endif
344 #ifdef VSWTCH
345         {stty_swtch, CSWTCH,  VSWTCH},
346 #endif
347         {"start",    CSTART,  VSTART},
348         {"stop",     CSTOP,   VSTOP},
349         {"susp",     CSUSP,   VSUSP},
350 #ifdef VDSUSP
351         {"dsusp",    CDSUSP,  VDSUSP},
352 #endif
353 #ifdef VREPRINT
354         {"rprnt",    CRPRNT,  VREPRINT},
355 #endif
356 #ifdef VWERASE
357         {"werase",   CWERASE, VWERASE},
358 #endif
359 #ifdef VLNEXT
360         {"lnext",    CLNEXT,  VLNEXT},
361 #endif
362 #ifdef VFLUSHO
363         {"flush",    CFLUSHO, VFLUSHO},
364 #endif
365 #ifdef VSTATUS
366         {"status",   CSTATUS, VSTATUS},
367 #endif
368         /* These must be last because of the display routines */
369         {stty_min,   1,       VMIN},
370         {stty_time,  0,       VTIME},
371 };
372
373 enum {
374         NUM_control_info = (sizeof(control_info) / sizeof(control_info[0]))
375 };
376
377 /* The width of the screen, for output wrapping */
378 static unsigned max_col = 80; /* default */
379 /* Current position, to know when to wrap */
380 static unsigned current_col;
381 static const char *device_name = bb_msg_standard_input;
382
383 /* Return a string that is the printable representation of character CH */
384 /* Adapted from 'cat' by Torbjorn Granlund */
385 static const char *visible(unsigned int ch)
386 {
387         static char buf[10];
388         char *bpout = buf;
389
390         if (ch == _POSIX_VDISABLE)
391                 return "<undef>";
392
393         if (ch >= 128) {
394                 ch -= 128;
395                 *bpout++ = 'M';
396                 *bpout++ = '-';
397         }
398
399         if (ch < 32) {
400                 *bpout++ = '^';
401                 *bpout++ = ch + 64;
402         } else if (ch < 127) {
403                 *bpout++ = ch;
404         } else {
405                 *bpout++ = '^';
406                 *bpout++ = '?';
407         }
408
409         *bpout = '\0';
410         return buf;
411 }
412
413 static tcflag_t *mode_type_flag(unsigned type, const struct termios *mode)
414 {
415         static const unsigned char tcflag_offsets[] = {
416                 offsetof(struct termios, c_cflag), /* control */
417                 offsetof(struct termios, c_iflag), /* input */
418                 offsetof(struct termios, c_oflag), /* output */
419                 offsetof(struct termios, c_lflag), /* local */
420         };
421
422         if (type <= local) {
423                 return (tcflag_t*) (((char*)mode) + tcflag_offsets[type]);
424         }
425         return NULL;
426 }
427
428 static speed_t string_to_baud_or_die(const char *arg)
429 {
430         return tty_value_to_baud(xatou(arg));
431 }
432
433 static void set_speed_or_die(enum speed_setting type, const char *arg,
434                                         struct termios *mode)
435 {
436         speed_t baud;
437
438         baud = string_to_baud_or_die(arg);
439
440         if (type != output_speed) {     /* either input or both */
441                 cfsetispeed(mode, baud);
442         }
443         if (type != input_speed) {      /* either output or both */
444                 cfsetospeed(mode, baud);
445         }
446 }
447
448 static ATTRIBUTE_NORETURN void perror_on_device_and_die(const char *fmt)
449 {
450         bb_perror_msg_and_die(fmt, device_name);
451 }
452
453 static void perror_on_device(const char *fmt)
454 {
455         bb_perror_msg(fmt, device_name);
456 }
457
458 /* No, inline won't be as efficient (gcc 3.4.3) */
459 #define streq(a,b) (!strcmp((a),(b)))
460
461 /* Print format string MESSAGE and optional args.
462    Wrap to next line first if it won't fit.
463    Print a space first unless MESSAGE will start a new line */
464 static void wrapf(const char *message, ...)
465 {
466         char buf[128];
467         va_list args;
468         int buflen;
469
470         va_start(args, message);
471         vsnprintf(buf, sizeof(buf), message, args);
472         va_end(args);
473         buflen = strlen(buf);
474         if (!buflen) return;
475
476         if (current_col > 0) {
477                 current_col++;
478                 if (buf[0] != '\n') {
479                         if (current_col + buflen >= max_col) {
480                                 putchar('\n');
481                                 current_col = 0;
482                         } else
483                                 putchar(' ');
484                 }
485         }
486         fputs(buf, stdout);
487         current_col += buflen;
488         if (buf[buflen-1] == '\n')
489                 current_col = 0;
490 }
491
492 #ifdef TIOCGWINSZ
493
494 static int get_win_size(int fd, struct winsize *win)
495 {
496         return ioctl(fd, TIOCGWINSZ, (char *) win);
497 }
498
499 static void set_window_size(int rows, int cols)
500 {
501         struct winsize win;
502
503         if (get_win_size(STDIN_FILENO, &win)) {
504                 if (errno != EINVAL) {
505                         perror_on_device("%s");
506                         return;
507                 }
508                 memset(&win, 0, sizeof(win));
509         }
510
511         if (rows >= 0)
512                 win.ws_row = rows;
513         if (cols >= 0)
514                 win.ws_col = cols;
515
516 # ifdef TIOCSSIZE
517         /* Alexander Dupuy <dupuy@cs.columbia.edu> wrote:
518            The following code deals with a bug in the SunOS 4.x (and 3.x?) kernel.
519            This comment from sys/ttold.h describes Sun's twisted logic - a better
520            test would have been (ts_lines > 64k || ts_cols > 64k || ts_cols == 0).
521            At any rate, the problem is gone in Solaris 2.x */
522
523         if (win.ws_row == 0 || win.ws_col == 0) {
524                 struct ttysize ttysz;
525
526                 ttysz.ts_lines = win.ws_row;
527                 ttysz.ts_cols = win.ws_col;
528
529                 win.ws_row = win.ws_col = 1;
530
531                 if ((ioctl(STDIN_FILENO, TIOCSWINSZ, (char *) &win) != 0)
532                 || (ioctl(STDIN_FILENO, TIOCSSIZE, (char *) &ttysz) != 0)) {
533                         perror_on_device("%s");
534                 }
535                 return;
536         }
537 # endif
538
539         if (ioctl(STDIN_FILENO, TIOCSWINSZ, (char *) &win))
540                 perror_on_device("%s");
541 }
542
543 static void display_window_size(int fancy)
544 {
545         const char *fmt_str = "%s\0%s: no size information for this device";
546         struct winsize win;
547
548         if (get_win_size(STDIN_FILENO, &win)) {
549                 if ((errno != EINVAL) || ((fmt_str += 2), !fancy)) {
550                         perror_on_device(fmt_str);
551                 }
552         } else {
553                 wrapf(fancy ? "rows %d; columns %d;" : "%d %d\n",
554                                 win.ws_row, win.ws_col);
555         }
556 }
557
558 #else /* !TIOCGWINSZ */
559
560 static inline void display_window_size(int fancy) {}
561
562 #endif /* !TIOCGWINSZ */
563
564 static int screen_columns_or_die(void)
565 {
566         const char *s;
567
568 #ifdef TIOCGWINSZ
569         struct winsize win;
570
571         /* With Solaris 2.[123], this ioctl fails and errno is set to
572            EINVAL for telnet (but not rlogin) sessions.
573            On ISC 3.0, it fails for the console and the serial port
574            (but it works for ptys).
575            It can also fail on any system when stdout isn't a tty.
576            In case of any failure, just use the default */
577         if (get_win_size(STDOUT_FILENO, &win) == 0 && win.ws_col > 0)
578                 return win.ws_col;
579 #endif
580
581         s = getenv("COLUMNS");
582         if (s)
583                 return xatoi_u(s);
584         return 80;
585 }
586
587 static const struct suffix_mult stty_suffixes[] = {
588         {"b",  512 },
589         {"k",  1024},
590         {"B",  1024},
591         {NULL, 0   }
592 };
593
594 static const struct mode_info *find_mode(const char *name)
595 {
596         int i;
597         for (i = 0; i < NUM_mode_info; ++i)
598                 if (streq(name, mode_info[i].name))
599                         return &mode_info[i];
600         return 0;
601 }
602
603 static const struct control_info *find_control(const char *name)
604 {
605         int i;
606         for (i = 0; i < NUM_control_info; ++i)
607                 if (streq(name, control_info[i].name))
608                         return &control_info[i];
609         return 0;
610 }
611
612 enum {
613         param_need_arg = 0x80,
614         param_line   = 1 | 0x80,
615         param_rows   = 2 | 0x80,
616         param_cols   = 3 | 0x80,
617         param_size   = 4,
618         param_speed  = 5,
619         param_ispeed = 6 | 0x80,
620         param_ospeed = 7 | 0x80,
621 };
622
623 static int find_param(const char *name)
624 {
625 #ifdef HAVE_C_LINE
626         if (streq(name, "line")) return param_line;
627 #endif
628 #ifdef TIOCGWINSZ
629         if (streq(name, "rows")) return param_rows;
630         if (streq(name, "cols")) return param_cols;
631         if (streq(name, "columns")) return param_cols;
632         if (streq(name, "size")) return param_size;
633 #endif
634         if (streq(name, "speed")) return param_speed;
635         if (streq(name, "ispeed")) return param_ispeed;
636         if (streq(name, "ospeed")) return param_ospeed;
637         return 0;
638 }
639
640 static int recover_mode(const char *arg, struct termios *mode)
641 {
642         int i, n;
643         unsigned int chr;
644         unsigned long iflag, oflag, cflag, lflag;
645
646         /* Scan into temporaries since it is too much trouble to figure out
647            the right format for 'tcflag_t' */
648         if (sscanf(arg, "%lx:%lx:%lx:%lx%n",
649                            &iflag, &oflag, &cflag, &lflag, &n) != 4)
650                 return 0;
651         mode->c_iflag = iflag;
652         mode->c_oflag = oflag;
653         mode->c_cflag = cflag;
654         mode->c_lflag = lflag;
655         arg += n;
656         for (i = 0; i < NCCS; ++i) {
657                 if (sscanf(arg, ":%x%n", &chr, &n) != 1)
658                         return 0;
659                 mode->c_cc[i] = chr;
660                 arg += n;
661         }
662
663         /* Fail if there are too many fields */
664         if (*arg != '\0')
665                 return 0;
666
667         return 1;
668 }
669
670 static void display_recoverable(const struct termios *mode)
671 {
672         int i;
673         printf("%lx:%lx:%lx:%lx",
674                    (unsigned long) mode->c_iflag, (unsigned long) mode->c_oflag,
675                    (unsigned long) mode->c_cflag, (unsigned long) mode->c_lflag);
676         for (i = 0; i < NCCS; ++i)
677                 printf(":%x", (unsigned int) mode->c_cc[i]);
678         putchar('\n');
679 }
680
681 static void display_speed(const struct termios *mode, int fancy)
682 {
683                              //01234567 8 9
684         const char *fmt_str = "%lu %lu\n\0ispeed %lu baud; ospeed %lu baud;";
685         unsigned long ispeed, ospeed;
686
687         ospeed = ispeed = cfgetispeed(mode);
688         if (ispeed == 0 || ispeed == (ospeed = cfgetospeed(mode))) {
689                 ispeed = ospeed;                /* in case ispeed was 0 */
690                          //0123 4 5 6 7 8 9
691                 fmt_str = "%lu\n\0\0\0\0\0speed %lu baud;";
692         }
693         if (fancy) fmt_str += 9;
694         wrapf(fmt_str, tty_baud_to_value(ispeed), tty_baud_to_value(ospeed));
695 }
696
697 static void display_all(const struct termios *mode)
698 {
699         int i;
700         tcflag_t *bitsp;
701         unsigned long mask;
702         int prev_type = control;
703
704         display_speed(mode, 1);
705         display_window_size(1);
706 #ifdef HAVE_C_LINE
707         wrapf("line = %d;\n", mode->c_line);
708 #else
709         wrapf("\n");
710 #endif
711
712         for (i = 0; control_info[i].name != stty_min; ++i) {
713                 /* If swtch is the same as susp, don't print both */
714 #if VSWTCH == VSUSP
715                 if (control_info[i].name == stty_swtch)
716                         continue;
717 #endif
718                 /* If eof uses the same slot as min, only print whichever applies */
719 #if VEOF == VMIN
720                 if ((mode->c_lflag & ICANON) == 0
721                         && (control_info[i].name == stty_eof
722                                 || control_info[i].name == stty_eol)) continue;
723 #endif
724                 wrapf("%s = %s;", control_info[i].name,
725                           visible(mode->c_cc[control_info[i].offset]));
726         }
727 #if VEOF == VMIN
728         if ((mode->c_lflag & ICANON) == 0)
729 #endif
730                 wrapf("min = %d; time = %d;", mode->c_cc[VMIN], mode->c_cc[VTIME]);
731         if (current_col) wrapf("\n");
732
733         for (i = 0; i < NUM_mode_info; ++i) {
734                 if (mode_info[i].flags & OMIT)
735                         continue;
736                 if (mode_info[i].type != prev_type) {
737                         wrapf("\n");
738                         prev_type = mode_info[i].type;
739                 }
740
741                 bitsp = mode_type_flag(mode_info[i].type, mode);
742                 mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits;
743                 if ((*bitsp & mask) == mode_info[i].bits)
744                         wrapf("%s", mode_info[i].name);
745                 else if (mode_info[i].flags & REV)
746                         wrapf("-%s", mode_info[i].name);
747         }
748         if (current_col) wrapf("\n");
749 }
750
751 static void sane_mode(struct termios *mode)
752 {
753         int i;
754         tcflag_t *bitsp;
755
756         for (i = 0; i < NUM_control_info; ++i) {
757 #if VMIN == VEOF
758                 if (control_info[i].name == stty_min)
759                         break;
760 #endif
761                 mode->c_cc[control_info[i].offset] = control_info[i].saneval;
762         }
763
764         for (i = 0; i < NUM_mode_info; ++i) {
765                 if (mode_info[i].flags & SANE_SET) {
766                         bitsp = mode_type_flag(mode_info[i].type, mode);
767                         *bitsp = (*bitsp & ~((unsigned long)mode_info[i].mask))
768                                 | mode_info[i].bits;
769                 } else if (mode_info[i].flags & SANE_UNSET) {
770                         bitsp = mode_type_flag(mode_info[i].type, mode);
771                         *bitsp = *bitsp & ~((unsigned long)mode_info[i].mask)
772                                 & ~mode_info[i].bits;
773                 }
774         }
775 }
776
777 /* Save set_mode from #ifdef forest plague */
778 #ifndef ONLCR
779 #define ONLCR 0
780 #endif
781 #ifndef OCRNL
782 #define OCRNL 0
783 #endif
784 #ifndef ONLRET
785 #define ONLRET 0
786 #endif
787 #ifndef XCASE
788 #define XCASE 0
789 #endif
790 #ifndef IXANY
791 #define IXANY 0
792 #endif
793 #ifndef TABDLY
794 #define TABDLY 0
795 #endif
796 #ifndef OXTABS
797 #define OXTABS 0
798 #endif
799 #ifndef IUCLC
800 #define IUCLC 0
801 #endif
802 #ifndef OLCUC
803 #define OLCUC 0
804 #endif
805 #ifndef ECHOCTL
806 #define ECHOCTL 0
807 #endif
808 #ifndef ECHOKE
809 #define ECHOKE 0
810 #endif
811
812 static void set_mode(const struct mode_info *info, int reversed,
813                                         struct termios *mode)
814 {
815         tcflag_t *bitsp;
816
817         bitsp = mode_type_flag(info->type, mode);
818
819         if (bitsp) {
820                 if (reversed)
821                         *bitsp = *bitsp & ~info->mask & ~info->bits;
822                 else
823                         *bitsp = (*bitsp & ~info->mask) | info->bits;
824                 return;
825         }
826
827         /* Combination mode */
828         if (info->name == evenp || info->name == parity) {
829                 if (reversed)
830                         mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
831                 else
832                         mode->c_cflag = (mode->c_cflag & ~PARODD & ~CSIZE) | PARENB | CS7;
833         } else if (info->name == stty_oddp) {
834                 if (reversed)
835                         mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
836                 else
837                         mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARODD | PARENB;
838         } else if (info->name == stty_nl) {
839                 if (reversed) {
840                         mode->c_iflag = (mode->c_iflag | ICRNL) & ~INLCR & ~IGNCR;
841                         mode->c_oflag = (mode->c_oflag | ONLCR) & ~OCRNL & ~ONLRET;
842                 } else {
843                         mode->c_iflag = mode->c_iflag & ~ICRNL;
844                         if (ONLCR) mode->c_oflag = mode->c_oflag & ~ONLCR;
845                 }
846         } else if (info->name == stty_ek) {
847                 mode->c_cc[VERASE] = CERASE;
848                 mode->c_cc[VKILL] = CKILL;
849         } else if (info->name == stty_sane) {
850                 sane_mode(mode);
851         }
852         else if (info->name == cbreak) {
853                 if (reversed)
854                         mode->c_lflag |= ICANON;
855                 else
856                         mode->c_lflag &= ~ICANON;
857         } else if (info->name == stty_pass8) {
858                 if (reversed) {
859                         mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB;
860                         mode->c_iflag |= ISTRIP;
861                 } else {
862                         mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
863                         mode->c_iflag &= ~ISTRIP;
864                 }
865         } else if (info->name == litout) {
866                 if (reversed) {
867                         mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB;
868                         mode->c_iflag |= ISTRIP;
869                         mode->c_oflag |= OPOST;
870                 } else {
871                         mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
872                         mode->c_iflag &= ~ISTRIP;
873                         mode->c_oflag &= ~OPOST;
874                 }
875         } else if (info->name == raw || info->name == cooked) {
876                 if ((info->name[0] == 'r' && reversed)
877                         || (info->name[0] == 'c' && !reversed)) {
878                         /* Cooked mode */
879                         mode->c_iflag |= BRKINT | IGNPAR | ISTRIP | ICRNL | IXON;
880                         mode->c_oflag |= OPOST;
881                         mode->c_lflag |= ISIG | ICANON;
882 #if VMIN == VEOF
883                         mode->c_cc[VEOF] = CEOF;
884 #endif
885 #if VTIME == VEOL
886                         mode->c_cc[VEOL] = CEOL;
887 #endif
888                 } else {
889                         /* Raw mode */
890                         mode->c_iflag = 0;
891                         mode->c_oflag &= ~OPOST;
892                         mode->c_lflag &= ~(ISIG | ICANON | XCASE);
893                         mode->c_cc[VMIN] = 1;
894                         mode->c_cc[VTIME] = 0;
895                 }
896         }
897         else if (IXANY && info->name == decctlq) {
898                 if (reversed)
899                         mode->c_iflag |= IXANY;
900                 else
901                         mode->c_iflag &= ~IXANY;
902         }
903         else if (TABDLY && info->name == stty_tabs) {
904                 if (reversed)
905                         mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB3;
906                 else
907                         mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB0;
908         }
909         else if (OXTABS && info->name == stty_tabs) {
910                 if (reversed)
911                         mode->c_oflag |= OXTABS;
912                 else
913                         mode->c_oflag &= ~OXTABS;
914         }
915         else if (XCASE && IUCLC && OLCUC
916         && (info->name == stty_lcase || info->name == stty_LCASE)) {
917                 if (reversed) {
918                         mode->c_lflag &= ~XCASE;
919                         mode->c_iflag &= ~IUCLC;
920                         mode->c_oflag &= ~OLCUC;
921                 } else {
922                         mode->c_lflag |= XCASE;
923                         mode->c_iflag |= IUCLC;
924                         mode->c_oflag |= OLCUC;
925                 }
926         }
927         else if (info->name == stty_crt) {
928                 mode->c_lflag |= ECHOE | ECHOCTL | ECHOKE;
929         }
930         else if (info->name == stty_dec) {
931                 mode->c_cc[VINTR] = 3; /* ^C */
932                 mode->c_cc[VERASE] = 127; /* DEL */
933                 mode->c_cc[VKILL] = 21; /* ^U */
934                 mode->c_lflag |= ECHOE | ECHOCTL | ECHOKE;
935                 if (IXANY) mode->c_iflag &= ~IXANY;
936         }
937 }
938
939 static void display_changed(const struct termios *mode)
940 {
941         int i;
942         tcflag_t *bitsp;
943         unsigned long mask;
944         int prev_type = control;
945
946         display_speed(mode, 1);
947 #ifdef HAVE_C_LINE
948         wrapf("line = %d;\n", mode->c_line);
949 #else
950         wrapf("\n");
951 #endif
952
953         for (i = 0; control_info[i].name != stty_min; ++i) {
954                 if (mode->c_cc[control_info[i].offset] == control_info[i].saneval)
955                         continue;
956                 /* If swtch is the same as susp, don't print both */
957 #if VSWTCH == VSUSP
958                 if (control_info[i].name == stty_swtch)
959                         continue;
960 #endif
961                 /* If eof uses the same slot as min, only print whichever applies */
962 #if VEOF == VMIN
963                 if ((mode->c_lflag & ICANON) == 0
964                         && (control_info[i].name == stty_eof
965                                 || control_info[i].name == stty_eol)) continue;
966 #endif
967                 wrapf("%s = %s;", control_info[i].name,
968                           visible(mode->c_cc[control_info[i].offset]));
969         }
970         if ((mode->c_lflag & ICANON) == 0) {
971                 wrapf("min = %d; time = %d;", (int) mode->c_cc[VMIN],
972                           (int) mode->c_cc[VTIME]);
973         }
974         if (current_col) wrapf("\n");
975
976         for (i = 0; i < NUM_mode_info; ++i) {
977                 if (mode_info[i].flags & OMIT)
978                         continue;
979                 if (mode_info[i].type != prev_type) {
980                         if (current_col) wrapf("\n");
981                         prev_type = mode_info[i].type;
982                 }
983
984                 bitsp = mode_type_flag(mode_info[i].type, mode);
985                 mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits;
986                 if ((*bitsp & mask) == mode_info[i].bits) {
987                         if (mode_info[i].flags & SANE_UNSET) {
988                                 wrapf("%s", mode_info[i].name);
989                         }
990                 } else if ((mode_info[i].flags & (SANE_SET | REV)) == (SANE_SET | REV)) {
991                         wrapf("-%s", mode_info[i].name);
992                 }
993         }
994         if (current_col) wrapf("\n");
995 }
996
997 static void set_control_char_or_die(const struct control_info *info,
998                         const char *arg, struct termios *mode)
999 {
1000         unsigned char value;
1001
1002         if (info->name == stty_min || info->name == stty_time)
1003                 value = xatoul_range_sfx(arg, 0, 0xff, stty_suffixes);
1004         else if (arg[0] == '\0' || arg[1] == '\0')
1005                 value = arg[0];
1006         else if (streq(arg, "^-") || streq(arg, "undef"))
1007                 value = _POSIX_VDISABLE;
1008         else if (arg[0] == '^') { /* Ignore any trailing junk (^Cjunk) */
1009                 value = arg[1] & 0x1f; /* Non-letters get weird results */
1010                 if (arg[1] == '?')
1011                         value = 127;
1012         } else
1013                 value = xatoul_range_sfx(arg, 0, 0xff, stty_suffixes);
1014         mode->c_cc[info->offset] = value;
1015 }
1016
1017 int stty_main(int argc, char **argv)
1018 {
1019         struct termios mode;
1020         void (*output_func)(const struct termios *);
1021         const char *file_name = NULL;
1022         int require_set_attr;
1023         int speed_was_set;
1024         int verbose_output;
1025         int recoverable_output;
1026         int noargs;
1027         int k;
1028
1029         output_func = display_changed;
1030         noargs = 1;
1031         speed_was_set = 0;
1032         require_set_attr = 0;
1033         verbose_output = 0;
1034         recoverable_output = 0;
1035
1036         /* First pass: only parse/verify command line params */
1037         k = 0;
1038         while (argv[++k]) {
1039                 const struct mode_info *mp;
1040                 const struct control_info *cp;
1041                 const char *arg = argv[k];
1042                 const char *argnext = argv[k+1];
1043                 int param;
1044
1045                 if (arg[0] == '-') {
1046                         int i;
1047                         mp = find_mode(arg+1);
1048                         if (mp) {
1049                                 if (!(mp->flags & REV))
1050                                         bb_error_msg_and_die("invalid argument '%s'", arg);
1051                                 noargs = 0;
1052                                 continue;
1053                         }
1054                         /* It is an option - parse it */
1055                         i = 0;
1056                         while (arg[++i]) {
1057                                 switch (arg[i]) {
1058                                 case 'a':
1059                                         verbose_output = 1;
1060                                         output_func = display_all;
1061                                         break;
1062                                 case 'g':
1063                                         recoverable_output = 1;
1064                                         output_func = display_recoverable;
1065                                         break;
1066                                 case 'F':
1067                                         if (file_name)
1068                                                 bb_error_msg_and_die("only one device may be specified");
1069                                         file_name = &arg[i+1]; /* "-Fdevice" ? */
1070                                         if (!file_name[0]) { /* nope, "-F device" */
1071                                                 int p = k+1; /* argv[p] is argnext */
1072                                                 file_name = argnext;
1073                                                 if (!file_name)
1074                                                         bb_error_msg_and_die(bb_msg_requires_arg, "-F");
1075                                                 /* remove -F param from arg[vc] */
1076                                                 --argc;
1077                                                 while (argv[p]) { argv[p] = argv[p+1]; ++p; }
1078                                         }
1079                                         goto end_option;
1080                                 default:
1081                                         bb_error_msg_and_die("invalid argument '%s'", arg);
1082                                 }
1083                         }
1084 end_option:
1085                         continue;
1086                 }
1087
1088                 mp = find_mode(arg);
1089                 if (mp) {
1090                         noargs = 0;
1091                         continue;
1092                 }
1093
1094                 cp = find_control(arg);
1095                 if (cp) {
1096                         if (!argnext)
1097                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
1098                         /* called for the side effect of xfunc death only */
1099                         set_control_char_or_die(cp, argnext, &mode);
1100                         noargs = 0;
1101                         ++k;
1102                         continue;
1103                 }
1104
1105                 param = find_param(arg);
1106                 if (param & param_need_arg) {
1107                         if (!argnext)
1108                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
1109                         ++k;
1110                 }
1111
1112                 switch (param) {
1113 #ifdef HAVE_C_LINE
1114                 case param_line:
1115 # ifndef TIOCGWINSZ
1116                         xatoul_range_sfx(argnext, 1, INT_MAX, stty_suffixes);
1117                         break;
1118 # endif /* else fall-through */
1119 #endif
1120 #ifdef TIOCGWINSZ
1121                 case param_rows:
1122                 case param_cols:
1123                         xatoul_range_sfx(argnext, 1, INT_MAX, stty_suffixes);
1124                         break;
1125                 case param_size:
1126 #endif
1127                 case param_speed:
1128                         break;
1129                 case param_ispeed:
1130                         /* called for the side effect of xfunc death only */
1131                         set_speed_or_die(input_speed, argnext, &mode);
1132                         break;
1133                 case param_ospeed:
1134                         /* called for the side effect of xfunc death only */
1135                         set_speed_or_die(output_speed, argnext, &mode);
1136                         break;
1137                 default:
1138                         if (recover_mode(arg, &mode) == 1) break;
1139                         if (string_to_baud_or_die(arg) != (speed_t) -1) break;
1140                         bb_error_msg_and_die("invalid argument '%s'", arg);
1141                 }
1142                 noargs = 0;
1143         }
1144
1145         /* Specifying both -a and -g is an error */
1146         if (verbose_output && recoverable_output)
1147                 bb_error_msg_and_die("verbose and stty-readable output styles are mutually exclusive");
1148         /* Specifying -a or -g with non-options is an error */
1149         if (!noargs && (verbose_output || recoverable_output))
1150                 bb_error_msg_and_die("modes may not be set when specifying an output style");
1151
1152         /* Now it is safe to start doing things */
1153         if (file_name) {
1154                 int fd, fdflags;
1155                 device_name = file_name;
1156                 fd = xopen(device_name, O_RDONLY | O_NONBLOCK);
1157                 if (fd != STDIN_FILENO) {
1158                         dup2(fd, STDIN_FILENO);
1159                         close(fd);
1160                 }
1161                 fdflags = fcntl(STDIN_FILENO, F_GETFL);
1162                 if (fdflags == -1 || fcntl(STDIN_FILENO, F_SETFL, fdflags & ~O_NONBLOCK) < 0)
1163                         perror_on_device_and_die("%s: cannot reset non-blocking mode");
1164         }
1165
1166         /* Initialize to all zeroes so there is no risk memcmp will report a
1167            spurious difference in an uninitialized portion of the structure */
1168         memset(&mode, 0, sizeof(mode));
1169         if (tcgetattr(STDIN_FILENO, &mode))
1170                 perror_on_device_and_die("%s");
1171
1172         if (verbose_output || recoverable_output || noargs) {
1173                 max_col = screen_columns_or_die();
1174                 output_func(&mode);
1175                 return EXIT_SUCCESS;
1176         }
1177
1178         /* Second pass: perform actions */
1179         k = 0;
1180         while (argv[++k]) {
1181                 const struct mode_info *mp;
1182                 const struct control_info *cp;
1183                 const char *arg = argv[k];
1184                 const char *argnext = argv[k+1];
1185                 int param;
1186
1187                 if (arg[0] == '-') {
1188                         mp = find_mode(arg+1);
1189                         if (mp) {
1190                                 set_mode(mp, 1 /* reversed */, &mode);
1191                                 require_set_attr = 1;
1192                         }
1193                         /* It is an option - already parsed. Skip it */
1194                         continue;
1195                 }
1196
1197                 mp = find_mode(arg);
1198                 if (mp) {
1199                         set_mode(mp, 0 /* non-reversed */, &mode);
1200                         require_set_attr = 1;
1201                         continue;
1202                 }
1203
1204                 cp = find_control(arg);
1205                 if (cp) {
1206                         ++k;
1207                         set_control_char_or_die(cp, argnext, &mode);
1208                         require_set_attr = 1;
1209                         continue;
1210                 }
1211
1212                 param = find_param(arg);
1213                 if (param & param_need_arg) {
1214                         ++k;
1215                 }
1216
1217                 switch (param) {
1218 #ifdef HAVE_C_LINE
1219                 case param_line:
1220                         mode.c_line = xatoul_sfx(argnext, stty_suffixes);
1221                         require_set_attr = 1;
1222                         break;
1223 #endif
1224 #ifdef TIOCGWINSZ
1225                 case param_cols:
1226                         set_window_size(-1, xatoul_sfx(argnext, stty_suffixes));
1227                         break;
1228                 case param_size:
1229                         display_window_size(0);
1230                         break;
1231                 case param_rows:
1232                         set_window_size(xatoul_sfx(argnext, stty_suffixes), -1);
1233                         break;
1234 #endif
1235                 case param_speed:
1236                         display_speed(&mode, 0);
1237                         break;
1238                 case param_ispeed:
1239                         set_speed_or_die(input_speed, argnext, &mode);
1240                         speed_was_set = 1;
1241                         require_set_attr = 1;
1242                         break;
1243                 case param_ospeed:
1244                         set_speed_or_die(output_speed, argnext, &mode);
1245                         speed_was_set = 1;
1246                         require_set_attr = 1;
1247                         break;
1248                 default:
1249                         if (recover_mode(arg, &mode) == 1)
1250                                 require_set_attr = 1;
1251                         else /* true: if (string_to_baud_or_die(arg) != (speed_t) -1) */ {
1252                                 set_speed_or_die(both_speeds, arg, &mode);
1253                                 speed_was_set = 1;
1254                                 require_set_attr = 1;
1255                         } /* else - impossible (caught in the first pass):
1256                                 bb_error_msg_and_die("invalid argument '%s'", arg); */
1257                 }
1258         }
1259
1260         if (require_set_attr) {
1261                 struct termios new_mode;
1262
1263                 if (tcsetattr(STDIN_FILENO, TCSADRAIN, &mode))
1264                         perror_on_device_and_die("%s");
1265
1266                 /* POSIX (according to Zlotnick's book) tcsetattr returns zero if
1267                    it performs *any* of the requested operations.  This means it
1268                    can report 'success' when it has actually failed to perform
1269                    some proper subset of the requested operations.  To detect
1270                    this partial failure, get the current terminal attributes and
1271                    compare them to the requested ones */
1272
1273                 /* Initialize to all zeroes so there is no risk memcmp will report a
1274                    spurious difference in an uninitialized portion of the structure */
1275                 memset(&new_mode, 0, sizeof(new_mode));
1276                 if (tcgetattr(STDIN_FILENO, &new_mode))
1277                         perror_on_device_and_die("%s");
1278
1279                 if (memcmp(&mode, &new_mode, sizeof(mode)) != 0) {
1280 #ifdef CIBAUD
1281                         /* SunOS 4.1.3 (at least) has the problem that after this sequence,
1282                            tcgetattr (&m1); tcsetattr (&m1); tcgetattr (&m2);
1283                            sometimes (m1 != m2).  The only difference is in the four bits
1284                            of the c_cflag field corresponding to the baud rate.  To save
1285                            Sun users a little confusion, don't report an error if this
1286                            happens.  But suppress the error only if we haven't tried to
1287                            set the baud rate explicitly -- otherwise we'd never give an
1288                            error for a true failure to set the baud rate */
1289
1290                         new_mode.c_cflag &= (~CIBAUD);
1291                         if (speed_was_set || memcmp(&mode, &new_mode, sizeof(mode)) != 0)
1292 #endif
1293                                 perror_on_device_and_die("%s: cannot perform all requested operations");
1294                 }
1295         }
1296
1297         return EXIT_SUCCESS;
1298 }