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