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