Commit Vladimir's latest version of stty.c.  Nice work.
 -Erik
diff --git a/Changelog b/Changelog
index 45babb7..ed4c766 100644
--- a/Changelog
+++ b/Changelog
@@ -1,5 +1,10 @@
 0.50
 	* Evin Robertson -- new pivot_root applet 
+	* Bjorn Wesen -- new ifconfig and route applet (taken from 
+	    work done be Axis Communications).
+	* Vladimir N. Oleynik -- new stty applet 
+
+	<lots of other things -- fixme>
 
 
 	 -Erik Andersen, not yet released
diff --git a/Config.h b/Config.h
index 38a9559..6626fa9 100644
--- a/Config.h
+++ b/Config.h
@@ -95,6 +95,7 @@
 #define BB_SH
 #define BB_SLEEP
 #define BB_SORT
+//#define BB_STTY
 #define BB_SWAPONOFF
 #define BB_SYNC
 #define BB_SYSLOGD
diff --git a/TODO b/TODO
index 3e0121b..325fe6c 100644
--- a/TODO
+++ b/TODO
@@ -26,12 +26,16 @@
 Possible apps to include some time:
 
 * hwclock
-* stty
 * group/commonize strings, remove dups (for i18n, l10n)
 
+-----------
+
+Write a fixup_globals function to do just that right before calling
+non-forking applets.  Or, just always fork...
+
 -----------------------
 
-Running the following:
+Run the following:
 
     rm -f busybox && make LDFLAGS+=-nostdlib 2>&1 | \
 	sed -ne 's/.*undefined reference to `\(.*\)..*/\1/gp' | sort | uniq
diff --git a/applets.h b/applets.h
index 9aa65da..0b70a61 100644
--- a/applets.h
+++ b/applets.h
@@ -308,6 +308,9 @@
 #ifdef BB_SORT
 	APPLET("sort", sort_main, _BB_DIR_USR_BIN, sort_usage)
 #endif
+#ifdef BB_STTY
+	APPLET("stty", stty_main, _BB_DIR_BIN, stty_usage)
+#endif
 #ifdef BB_SWAPONOFF
 	APPLET("swapoff", swap_on_off_main, _BB_DIR_SBIN, swapoff_usage)
 #endif
diff --git a/applets/usage.c b/applets/usage.c
index bdd4d3d..7f99808 100644
--- a/applets/usage.c
+++ b/applets/usage.c
@@ -1243,6 +1243,20 @@
 	;
 #endif
 
+#if defined BB_STTY
+const char stty_usage[] =
+	"stty [-a|g] [-F device] [SETTING]..."
+#ifndef BB_FEATURE_TRIVIAL_HELP
+	"\n\nWithout arguments, prints baud rate, line discipline,"
+	"\nand deviations from stty sane."
+	"\n -F device  open and use the specified device instead of stdin"
+	"\n -a         print all current settings in human-readable form. Or"
+	"\n -g         print in a stty-readable form"
+	"\n [SETTING]  see in documentation"
+#endif
+	;
+#endif
+
 #if defined BB_SWAPONOFF
 const char swapoff_usage[] =
 	"swapoff [OPTION] [device]"
diff --git a/coreutils/stty.c b/coreutils/stty.c
new file mode 100644
index 0000000..0fc0bfb
--- /dev/null
+++ b/coreutils/stty.c
@@ -0,0 +1,1392 @@
+/* vi: set sw=4 ts=4: */
+/* stty -- change and print terminal line settings
+   Copyright (C) 1990-1999 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+/* Usage: stty [-ag] [-F device] [setting...]
+
+   Options:
+   -a Write all current settings to stdout in human-readable form.
+   -g Write all current settings to stdout in stty-readable form.
+   -F Open and use the specified device instead of stdin
+
+   If no args are given, write to stdout the baud rate and settings that
+   have been changed from their defaults.  Mode reading and changes
+   are done on the specified device, or stdin if none was specified.
+
+   David MacKenzie <djm@gnu.ai.mit.edu>
+
+   Special for busybox ported by vodz@usa.net 2001
+
+   */
+
+#include "busybox.h"
+
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <getopt.h>
+
+#include <sys/param.h>
+#include <unistd.h>
+
+#ifndef STDIN_FILENO
+# define STDIN_FILENO 0
+#endif
+
+#ifndef STDOUT_FILENO
+# define STDOUT_FILENO 1
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <memory.h>
+#include <fcntl.h>
+
+#define STREQ(a, b) (strcmp ((a), (b)) == 0)
+
+
+#ifndef _POSIX_VDISABLE
+# define _POSIX_VDISABLE ((unsigned char) 0)
+#endif
+
+#define Control(c) ((c) & 0x1f)
+/* Canonical values for control characters. */
+#ifndef CINTR
+# define CINTR Control ('c')
+#endif
+#ifndef CQUIT
+# define CQUIT 28
+#endif
+#ifndef CERASE
+# define CERASE 127
+#endif
+#ifndef CKILL
+# define CKILL Control ('u')
+#endif
+#ifndef CEOF
+# define CEOF Control ('d')
+#endif
+#ifndef CEOL
+# define CEOL _POSIX_VDISABLE
+#endif
+#ifndef CSTART
+# define CSTART Control ('q')
+#endif
+#ifndef CSTOP
+# define CSTOP Control ('s')
+#endif
+#ifndef CSUSP
+# define CSUSP Control ('z')
+#endif
+#if defined(VEOL2) && !defined(CEOL2)
+# define CEOL2 _POSIX_VDISABLE
+#endif
+/* ISC renamed swtch to susp for termios, but we'll accept either name.  */
+#if defined(VSUSP) && !defined(VSWTCH)
+# define VSWTCH VSUSP
+# define CSWTCH CSUSP
+#endif
+#if defined(VSWTCH) && !defined(CSWTCH)
+# define CSWTCH _POSIX_VDISABLE
+#endif
+
+/* SunOS 5.3 loses (^Z doesn't work) if `swtch' is the same as `susp'.
+   So the default is to disable `swtch.'  */
+#if defined (__sparc__) && defined (__svr4__)
+# undef CSWTCH
+# define CSWTCH _POSIX_VDISABLE
+#endif
+
+#if defined(VWERSE) && !defined (VWERASE)	/* AIX-3.2.5 */
+# define VWERASE VWERSE
+#endif
+#if defined(VDSUSP) && !defined (CDSUSP)
+# define CDSUSP Control ('y')
+#endif
+#if !defined(VREPRINT) && defined(VRPRNT)	/* Irix 4.0.5 */
+# define VREPRINT VRPRNT
+#endif
+#if defined(VREPRINT) && !defined(CRPRNT)
+# define CRPRNT Control ('r')
+#endif
+#if defined(VWERASE) && !defined(CWERASE)
+# define CWERASE Control ('w')
+#endif
+#if defined(VLNEXT) && !defined(CLNEXT)
+# define CLNEXT Control ('v')
+#endif
+#if defined(VDISCARD) && !defined(VFLUSHO)
+# define VFLUSHO VDISCARD
+#endif
+#if defined(VFLUSH) && !defined(VFLUSHO)	/* Ultrix 4.2 */
+# define VFLUSHO VFLUSH
+#endif
+#if defined(CTLECH) && !defined(ECHOCTL)	/* Ultrix 4.3 */
+# define ECHOCTL CTLECH
+#endif
+#if defined(TCTLECH) && !defined(ECHOCTL)	/* Ultrix 4.2 */
+# define ECHOCTL TCTLECH
+#endif
+#if defined(CRTKIL) && !defined(ECHOKE)	/* Ultrix 4.2 and 4.3 */
+# define ECHOKE CRTKIL
+#endif
+#if defined(VFLUSHO) && !defined(CFLUSHO)
+# define CFLUSHO Control ('o')
+#endif
+#if defined(VSTATUS) && !defined(CSTATUS)
+# define CSTATUS Control ('t')
+#endif
+
+/* Which speeds to set.  */
+enum speed_setting {
+	input_speed, output_speed, both_speeds
+};
+
+/* What to output and how.  */
+enum output_type {
+	changed, all, recoverable	/* Default, -a, -g.  */
+};
+
+/* Which member(s) of `struct termios' a mode uses.  */
+enum mode_type {
+	control, input, output, local, combination
+};
+
+
+static const char evenp[] = "evenp";
+static const char raw[] = "raw";
+static const char stty_min[] = "min";
+static const char stty_time[] = "time";
+static const char stty_swtch[] = "swtch";
+static const char stty_eol[] = "eol";
+static const char stty_eof[] = "eof";
+static const char parity[] = "parity";
+static const char stty_oddp[] = "oddp";
+static const char stty_nl[] = "nl";
+static const char stty_ek[] = "ek";
+static const char stty_sane[] = "sane";
+static const char cbreak[] = "cbreak";
+static const char stty_pass8[] = "pass8";
+static const char litout[] = "litout";
+static const char cooked[] = "cooked";
+static const char decctlq[] = "decctlq";
+static const char stty_tabs[] = "tabs";
+static const char stty_lcase[] = "lcase";
+static const char stty_LCASE[] = "LCASE";
+static const char stty_crt[] = "crt";
+static const char stty_dec[] = "dec";
+
+
+/* Flags for `struct mode_info'. */
+#define SANE_SET 1				/* Set in `sane' mode. */
+#define SANE_UNSET 2			/* Unset in `sane' mode. */
+#define REV 4					/* Can be turned off by prepending `-'. */
+#define OMIT 8					/* Don't display value. */
+
+/* Each mode.  */
+struct mode_info {
+	const char *name;			/* Name given on command line.  */
+	enum mode_type type;		/* Which structure element to change. */
+	char flags;					/* Setting and display options.  */
+	unsigned long bits;			/* Bits to set for this mode.  */
+	unsigned long mask;			/* Other bits to turn off for this mode.  */
+};
+
+static const struct mode_info mode_info[] = {
+	{"parenb", control, REV, PARENB, 0},
+	{"parodd", control, REV, PARODD, 0},
+	{"cs5", control, 0, CS5, CSIZE},
+	{"cs6", control, 0, CS6, CSIZE},
+	{"cs7", control, 0, CS7, CSIZE},
+	{"cs8", control, 0, CS8, CSIZE},
+	{"hupcl", control, REV, HUPCL, 0},
+	{"hup", control, REV | OMIT, HUPCL, 0},
+	{"cstopb", control, REV, CSTOPB, 0},
+	{"cread", control, SANE_SET | REV, CREAD, 0},
+	{"clocal", control, REV, CLOCAL, 0},
+#ifdef CRTSCTS
+	{"crtscts", control, REV, CRTSCTS, 0},
+#endif
+
+	{"ignbrk", input, SANE_UNSET | REV, IGNBRK, 0},
+	{"brkint", input, SANE_SET | REV, BRKINT, 0},
+	{"ignpar", input, REV, IGNPAR, 0},
+	{"parmrk", input, REV, PARMRK, 0},
+	{"inpck", input, REV, INPCK, 0},
+	{"istrip", input, REV, ISTRIP, 0},
+	{"inlcr", input, SANE_UNSET | REV, INLCR, 0},
+	{"igncr", input, SANE_UNSET | REV, IGNCR, 0},
+	{"icrnl", input, SANE_SET | REV, ICRNL, 0},
+	{"ixon", input, REV, IXON, 0},
+	{"ixoff", input, SANE_UNSET | REV, IXOFF, 0},
+	{"tandem", input, REV | OMIT, IXOFF, 0},
+#ifdef IUCLC
+	{"iuclc", input, SANE_UNSET | REV, IUCLC, 0},
+#endif
+#ifdef IXANY
+	{"ixany", input, SANE_UNSET | REV, IXANY, 0},
+#endif
+#ifdef IMAXBEL
+	{"imaxbel", input, SANE_SET | REV, IMAXBEL, 0},
+#endif
+
+	{"opost", output, SANE_SET | REV, OPOST, 0},
+#ifdef OLCUC
+	{"olcuc", output, SANE_UNSET | REV, OLCUC, 0},
+#endif
+#ifdef OCRNL
+	{"ocrnl", output, SANE_UNSET | REV, OCRNL, 0},
+#endif
+#ifdef ONLCR
+	{"onlcr", output, SANE_SET | REV, ONLCR, 0},
+#endif
+#ifdef ONOCR
+	{"onocr", output, SANE_UNSET | REV, ONOCR, 0},
+#endif
+#ifdef ONLRET
+	{"onlret", output, SANE_UNSET | REV, ONLRET, 0},
+#endif
+#ifdef OFILL
+	{"ofill", output, SANE_UNSET | REV, OFILL, 0},
+#endif
+#ifdef OFDEL
+	{"ofdel", output, SANE_UNSET | REV, OFDEL, 0},
+#endif
+#ifdef NLDLY
+	{"nl1", output, SANE_UNSET, NL1, NLDLY},
+	{"nl0", output, SANE_SET, NL0, NLDLY},
+#endif
+#ifdef CRDLY
+	{"cr3", output, SANE_UNSET, CR3, CRDLY},
+	{"cr2", output, SANE_UNSET, CR2, CRDLY},
+	{"cr1", output, SANE_UNSET, CR1, CRDLY},
+	{"cr0", output, SANE_SET, CR0, CRDLY},
+#endif
+#ifdef TABDLY
+	{"tab3", output, SANE_UNSET, TAB3, TABDLY},
+	{"tab2", output, SANE_UNSET, TAB2, TABDLY},
+	{"tab1", output, SANE_UNSET, TAB1, TABDLY},
+	{"tab0", output, SANE_SET, TAB0, TABDLY},
+#else
+# ifdef OXTABS
+	{"tab3", output, SANE_UNSET, OXTABS, 0},
+# endif
+#endif
+#ifdef BSDLY
+	{"bs1", output, SANE_UNSET, BS1, BSDLY},
+	{"bs0", output, SANE_SET, BS0, BSDLY},
+#endif
+#ifdef VTDLY
+	{"vt1", output, SANE_UNSET, VT1, VTDLY},
+	{"vt0", output, SANE_SET, VT0, VTDLY},
+#endif
+#ifdef FFDLY
+	{"ff1", output, SANE_UNSET, FF1, FFDLY},
+	{"ff0", output, SANE_SET, FF0, FFDLY},
+#endif
+
+	{"isig", local, SANE_SET | REV, ISIG, 0},
+	{"icanon", local, SANE_SET | REV, ICANON, 0},
+#ifdef IEXTEN
+	{"iexten", local, SANE_SET | REV, IEXTEN, 0},
+#endif
+	{"echo", local, SANE_SET | REV, ECHO, 0},
+	{"echoe", local, SANE_SET | REV, ECHOE, 0},
+	{"crterase", local, REV | OMIT, ECHOE, 0},
+	{"echok", local, SANE_SET | REV, ECHOK, 0},
+	{"echonl", local, SANE_UNSET | REV, ECHONL, 0},
+	{"noflsh", local, SANE_UNSET | REV, NOFLSH, 0},
+#ifdef XCASE
+	{"xcase", local, SANE_UNSET | REV, XCASE, 0},
+#endif
+#ifdef TOSTOP
+	{"tostop", local, SANE_UNSET | REV, TOSTOP, 0},
+#endif
+#ifdef ECHOPRT
+	{"echoprt", local, SANE_UNSET | REV, ECHOPRT, 0},
+	{"prterase", local, REV | OMIT, ECHOPRT, 0},
+#endif
+#ifdef ECHOCTL
+	{"echoctl", local, SANE_SET | REV, ECHOCTL, 0},
+	{"ctlecho", local, REV | OMIT, ECHOCTL, 0},
+#endif
+#ifdef ECHOKE
+	{"echoke", local, SANE_SET | REV, ECHOKE, 0},
+	{"crtkill", local, REV | OMIT, ECHOKE, 0},
+#endif
+
+	{evenp, combination, REV | OMIT, 0, 0},
+	{parity, combination, REV | OMIT, 0, 0},
+	{stty_oddp, combination, REV | OMIT, 0, 0},
+	{stty_nl, combination, REV | OMIT, 0, 0},
+	{stty_ek, combination, OMIT, 0, 0},
+	{stty_sane, combination, OMIT, 0, 0},
+	{cooked, combination, REV | OMIT, 0, 0},
+	{raw, combination, REV | OMIT, 0, 0},
+	{stty_pass8, combination, REV | OMIT, 0, 0},
+	{litout, combination, REV | OMIT, 0, 0},
+	{cbreak, combination, REV | OMIT, 0, 0},
+#ifdef IXANY
+	{decctlq, combination, REV | OMIT, 0, 0},
+#endif
+#if defined (TABDLY) || defined (OXTABS)
+	{stty_tabs, combination, REV | OMIT, 0, 0},
+#endif
+#if defined(XCASE) && defined(IUCLC) && defined(OLCUC)
+	{stty_lcase, combination, REV | OMIT, 0, 0},
+	{stty_LCASE, combination, REV | OMIT, 0, 0},
+#endif
+	{stty_crt, combination, OMIT, 0, 0},
+	{stty_dec, combination, OMIT, 0, 0},
+};
+
+static const int NUM_mode_info =
+
+	(sizeof(mode_info) / sizeof(struct mode_info));
+
+/* Control character settings.  */
+struct control_info {
+	const char *name;			/* Name given on command line.  */
+	unsigned char saneval;		/* Value to set for `stty sane'.  */
+	int offset;					/* Offset in c_cc.  */
+};
+
+/* Control characters. */
+
+static const struct control_info control_info[] = {
+	{"intr", CINTR, VINTR},
+	{"quit", CQUIT, VQUIT},
+	{"erase", CERASE, VERASE},
+	{"kill", CKILL, VKILL},
+	{stty_eof, CEOF, VEOF},
+	{stty_eol, CEOL, VEOL},
+#ifdef VEOL2
+	{"eol2", CEOL2, VEOL2},
+#endif
+#ifdef VSWTCH
+	{stty_swtch, CSWTCH, VSWTCH},
+#endif
+	{"start", CSTART, VSTART},
+	{"stop", CSTOP, VSTOP},
+	{"susp", CSUSP, VSUSP},
+#ifdef VDSUSP
+	{"dsusp", CDSUSP, VDSUSP},
+#endif
+#ifdef VREPRINT
+	{"rprnt", CRPRNT, VREPRINT},
+#endif
+#ifdef VWERASE
+	{"werase", CWERASE, VWERASE},
+#endif
+#ifdef VLNEXT
+	{"lnext", CLNEXT, VLNEXT},
+#endif
+#ifdef VFLUSHO
+	{"flush", CFLUSHO, VFLUSHO},
+#endif
+#ifdef VSTATUS
+	{"status", CSTATUS, VSTATUS},
+#endif
+
+	/* These must be last because of the display routines. */
+	{stty_min, 1, VMIN},
+	{stty_time, 0, VTIME},
+};
+
+static const int NUM_control_info =
+	(sizeof(control_info) / sizeof(struct control_info));
+
+
+static const char *visible(unsigned int ch);
+static unsigned long baud_to_value(speed_t speed);
+static int recover_mode(char *arg, struct termios *mode);
+static int screen_columns(void);
+static int set_mode(const struct mode_info *info,
+		int reversed, struct termios *mode);
+static speed_t string_to_baud(const char *arg);
+static tcflag_t *mode_type_flag(enum mode_type type, struct termios *mode);
+static void display_all(struct termios *mode, int fd,
+		const char *device_name);
+static void display_changed(struct termios *mode);
+static void display_recoverable(struct termios *mode);
+static void display_settings(enum output_type output_type, 
+		struct termios *mode, int fd,
+		const char *device_name);
+static void display_speed(struct termios *mode, int fancy);
+static void display_window_size(int fancy, int fd,
+		const char *device_name);
+static void sane_mode(struct termios *mode);
+static void set_control_char(const struct control_info *info,
+		const char *arg, struct termios *mode);
+static void set_speed(enum speed_setting type,
+		const char *arg, struct termios *mode);
+static void set_window_size(int rows, int cols, int fd,
+
+							const char *device_name);
+
+/* The width of the screen, for output wrapping. */
+static int max_col;
+
+/* Current position, to know when to wrap. */
+static int current_col;
+
+/* Print format string MESSAGE and optional args.
+   Wrap to next line first if it won't fit.
+   Print a space first unless MESSAGE will start a new line. */
+
+static void wrapf(const char *message, ...)
+{
+	va_list args;
+	char buf[1024];				/* Plenty long for our needs. */
+	int buflen;
+
+	va_start(args, message);
+	vsprintf(buf, message, args);
+	va_end(args);
+	buflen = strlen(buf);
+	if (current_col + (current_col > 0) + buflen >= max_col) {
+		putchar('\n');
+		current_col = 0;
+	}
+	if (current_col > 0) {
+		putchar(' ');
+		current_col++;
+	}
+	fputs(buf, stdout);
+	current_col += buflen;
+}
+
+static const struct suffix_mult stty_suffixes[] = {
+	{"b", 512},
+	{"k", 1024},
+	{"B", 1024},
+	{NULL, 0}
+};
+
+extern int stty_main(int argc, char **argv)
+{
+	struct termios mode;
+	enum output_type output_type;
+	int optc;
+	int require_set_attr;
+	int speed_was_set;
+	int verbose_output;
+	int recoverable_output;
+	int k;
+	int noargs = 1;
+	char *file_name = NULL;
+	int fd;
+	const char *device_name;
+
+	output_type = changed;
+	verbose_output = 0;
+	recoverable_output = 0;
+
+	/* Don't print error messages for unrecognized options.  */
+	opterr = 0;
+
+	while ((optc = getopt(argc, argv, "agF:")) != -1) {
+		switch (optc) {
+		case 'a':
+			verbose_output = 1;
+			output_type = all;
+			break;
+
+		case 'g':
+			recoverable_output = 1;
+			output_type = recoverable;
+			break;
+
+		case 'F':
+			if (file_name)
+				error_msg_and_die("only one device may be specified");
+			file_name = optarg;
+			break;
+
+		default:				/* unrecognized option */
+			noargs = 0;
+			break;
+		}
+
+		if (noargs == 0)
+			break;
+	}
+
+	if (optind < argc)
+		noargs = 0;
+
+	/* Specifying both -a and -g gets an error.  */
+	if (verbose_output && recoverable_output)
+		error_msg_and_die ("verbose and stty-readable output styles are mutually exclusive");
+
+	/* Specifying any other arguments with -a or -g gets an error.  */
+	if (!noargs && (verbose_output || recoverable_output))
+		error_msg_and_die ("modes may not be set when specifying an output style");
+
+	/* FIXME: it'd be better not to open the file until we've verified
+	   that all arguments are valid.  Otherwise, we could end up doing
+	   only some of the requested operations and then failing, probably
+	   leaving things in an undesirable state.  */
+
+	if (file_name) {
+		int fdflags;
+
+		device_name = file_name;
+		fd = open(device_name, O_RDONLY | O_NONBLOCK);
+		if (fd < 0)
+			perror_msg_and_die("%s", device_name);
+		if ((fdflags = fcntl(fd, F_GETFL)) == -1
+			|| fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0)
+			perror_msg_and_die("%s: couldn't reset non-blocking mode",
+							   device_name);
+	} else {
+		fd = 0;
+		device_name = "standard input";
+	}
+
+	/* Initialize to all zeroes so there is no risk memcmp will report a
+	   spurious difference in an uninitialized portion of the structure.  */
+	memset(&mode, 0, sizeof(mode));
+	if (tcgetattr(fd, &mode))
+		perror_msg_and_die("%s", device_name);
+
+	if (verbose_output || recoverable_output || noargs) {
+		max_col = screen_columns();
+		current_col = 0;
+		display_settings(output_type, &mode, fd, device_name);
+		return EXIT_SUCCESS;
+	}
+
+	speed_was_set = 0;
+	require_set_attr = 0;
+	k = optind;
+	while (k < argc) {
+		int match_found = 0;
+		int reversed = 0;
+		int i;
+
+		if (argv[k][0] == '-') {
+			++argv[k];
+			reversed = 1;
+		}
+		for (i = 0; i < NUM_mode_info; ++i) {
+			if (STREQ(argv[k], mode_info[i].name)) {
+				match_found = set_mode(&mode_info[i], reversed, &mode);
+				require_set_attr = 1;
+				break;
+			}
+		}
+		if (match_found == 0 && reversed) {
+			error_msg_and_die("invalid argument `%s'", --argv[k]);
+		}
+		if (match_found == 0) {
+			for (i = 0; i < NUM_control_info; ++i) {
+				if (STREQ(argv[k], control_info[i].name)) {
+					if (k == argc - 1) {
+						error_msg_and_die("missing argument to `%s'", argv[k]);
+					}
+					match_found = 1;
+					++k;
+					set_control_char(&control_info[i], argv[k], &mode);
+					require_set_attr = 1;
+					break;
+				}
+			}
+		}
+		if (match_found == 0) {
+			if (STREQ(argv[k], "ispeed")) {
+				if (k == argc - 1) {
+					error_msg_and_die("missing argument to `%s'", argv[k]);
+				}
+				++k;
+				set_speed(input_speed, argv[k], &mode);
+				speed_was_set = 1;
+				require_set_attr = 1;
+			} else if (STREQ(argv[k], "ospeed")) {
+				if (k == argc - 1) {
+					error_msg_and_die("missing argument to `%s'", argv[k]);
+				}
+				++k;
+				set_speed(output_speed, argv[k], &mode);
+				speed_was_set = 1;
+				require_set_attr = 1;
+			}
+#ifdef TIOCGWINSZ
+			else if (STREQ(argv[k], "rows")) {
+				if (k == argc - 1) {
+					error_msg_and_die("missing argument to `%s'", argv[k]);
+				}
+				++k;
+				set_window_size((int) parse_number(argv[k], stty_suffixes),
+								-1, fd, device_name);
+			} else if (STREQ(argv[k], "cols") || STREQ(argv[k], "columns")) {
+				if (k == argc - 1) {
+					error_msg_and_die("missing argument to `%s'", argv[k]);
+				}
+				++k;
+				set_window_size(-1,
+								(int) parse_number(argv[k], stty_suffixes),
+								fd, device_name);
+			} else if (STREQ(argv[k], "size")) {
+				max_col = screen_columns();
+				current_col = 0;
+				display_window_size(0, fd, device_name);
+			}
+#endif
+#ifdef HAVE_C_LINE
+			else if (STREQ(argv[k], "line")) {
+				if (k == argc - 1) {
+					error_msg_and_die("missing argument to `%s'", argv[k]);
+				}
+				++k;
+				mode.c_line = parse_number(argv[k], stty_suffixes);
+				require_set_attr = 1;
+			}
+#endif
+			else if (STREQ(argv[k], "speed")) {
+				max_col = screen_columns();
+				display_speed(&mode, 0);
+			} else if (string_to_baud(argv[k]) != (speed_t) - 1) {
+				set_speed(both_speeds, argv[k], &mode);
+				speed_was_set = 1;
+				require_set_attr = 1;
+			} else {
+				if (recover_mode(argv[k], &mode) == 0) {
+					error_msg_and_die("invalid argument `%s'", argv[k]);
+				}
+				require_set_attr = 1;
+			}
+		}
+		k++;
+	}
+
+	if (require_set_attr) {
+		struct termios new_mode;
+
+		if (tcsetattr(fd, TCSADRAIN, &mode))
+			perror_msg_and_die("%s", device_name);
+
+		/* POSIX (according to Zlotnick's book) tcsetattr returns zero if
+		   it performs *any* of the requested operations.  This means it
+		   can report `success' when it has actually failed to perform
+		   some proper subset of the requested operations.  To detect
+		   this partial failure, get the current terminal attributes and
+		   compare them to the requested ones.  */
+
+		/* Initialize to all zeroes so there is no risk memcmp will report a
+		   spurious difference in an uninitialized portion of the structure.  */
+		memset(&new_mode, 0, sizeof(new_mode));
+		if (tcgetattr(fd, &new_mode))
+			perror_msg_and_die("%s", device_name);
+
+		/* Normally, one shouldn't use memcmp to compare structures that
+		   may have `holes' containing uninitialized data, but we have been
+		   careful to initialize the storage of these two variables to all
+		   zeroes.  One might think it more efficient simply to compare the
+		   modified fields, but that would require enumerating those fields --
+		   and not all systems have the same fields in this structure.  */
+
+		if (memcmp(&mode, &new_mode, sizeof(mode)) != 0) {
+#ifdef CIBAUD
+			/* SunOS 4.1.3 (at least) has the problem that after this sequence,
+			   tcgetattr (&m1); tcsetattr (&m1); tcgetattr (&m2);
+			   sometimes (m1 != m2).  The only difference is in the four bits
+			   of the c_cflag field corresponding to the baud rate.  To save
+			   Sun users a little confusion, don't report an error if this
+			   happens.  But suppress the error only if we haven't tried to
+			   set the baud rate explicitly -- otherwise we'd never give an
+			   error for a true failure to set the baud rate.  */
+
+			new_mode.c_cflag &= (~CIBAUD);
+			if (speed_was_set || memcmp(&mode, &new_mode, sizeof(mode)) != 0)
+#endif
+			{
+				error_msg_and_die ("%s: unable to perform all requested operations",
+					 device_name);
+#ifdef TESTING
+				{
+					size_t i;
+
+					printf("new_mode: mode\n");
+					for (i = 0; i < sizeof(new_mode); i++)
+						printf("0x%02x: 0x%02x\n",
+							   *(((unsigned char *) &new_mode) + i),
+							   *(((unsigned char *) &mode) + i));
+				}
+#endif
+			}
+		}
+	}
+
+	return EXIT_SUCCESS;
+}
+
+/* Return 0 if not applied because not reversible; otherwise return 1.  */
+
+static int
+set_mode(const struct mode_info *info, int reversed, struct termios *mode)
+{
+	tcflag_t *bitsp;
+
+	if (reversed && (info->flags & REV) == 0)
+		return 0;
+
+	bitsp = mode_type_flag(info->type, mode);
+
+	if (bitsp == NULL) {
+		/* Combination mode. */
+		if (info->name == evenp || info->name == parity) {
+			if (reversed)
+				mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
+			else
+				mode->c_cflag =
+					(mode->c_cflag & ~PARODD & ~CSIZE) | PARENB | CS7;
+		} else if (info->name == stty_oddp) {
+			if (reversed)
+				mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
+			else
+				mode->c_cflag =
+					(mode->c_cflag & ~CSIZE) | CS7 | PARODD | PARENB;
+		} else if (info->name == stty_nl) {
+			if (reversed) {
+				mode->c_iflag = (mode->c_iflag | ICRNL) & ~INLCR & ~IGNCR;
+				mode->c_oflag = (mode->c_oflag
+#ifdef ONLCR
+								 | ONLCR
+#endif
+					)
+#ifdef OCRNL
+					& ~OCRNL
+#endif
+#ifdef ONLRET
+					& ~ONLRET
+#endif
+					;
+			} else {
+				mode->c_iflag = mode->c_iflag & ~ICRNL;
+#ifdef ONLCR
+				mode->c_oflag = mode->c_oflag & ~ONLCR;
+#endif
+			}
+		} else if (info->name == stty_ek) {
+			mode->c_cc[VERASE] = CERASE;
+			mode->c_cc[VKILL] = CKILL;
+		} else if (info->name == stty_sane)
+			sane_mode(mode);
+		else if (info->name == cbreak) {
+			if (reversed)
+				mode->c_lflag |= ICANON;
+			else
+				mode->c_lflag &= ~ICANON;
+		} else if (info->name == stty_pass8) {
+			if (reversed) {
+				mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB;
+				mode->c_iflag |= ISTRIP;
+			} else {
+				mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
+				mode->c_iflag &= ~ISTRIP;
+			}
+		} else if (info->name == litout) {
+			if (reversed) {
+				mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB;
+				mode->c_iflag |= ISTRIP;
+				mode->c_oflag |= OPOST;
+			} else {
+				mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
+				mode->c_iflag &= ~ISTRIP;
+				mode->c_oflag &= ~OPOST;
+			}
+		} else if (info->name == raw || info->name == cooked) {
+			if ((info->name[0] == 'r' && reversed)
+				|| (info->name[0] == 'c' && !reversed)) {
+				/* Cooked mode. */
+				mode->c_iflag |= BRKINT | IGNPAR | ISTRIP | ICRNL | IXON;
+				mode->c_oflag |= OPOST;
+				mode->c_lflag |= ISIG | ICANON;
+#if VMIN == VEOF
+				mode->c_cc[VEOF] = CEOF;
+#endif
+#if VTIME == VEOL
+				mode->c_cc[VEOL] = CEOL;
+#endif
+			} else {
+				/* Raw mode. */
+				mode->c_iflag = 0;
+				mode->c_oflag &= ~OPOST;
+				mode->c_lflag &= ~(ISIG | ICANON
+#ifdef XCASE
+								   | XCASE
+#endif
+					);
+				mode->c_cc[VMIN] = 1;
+				mode->c_cc[VTIME] = 0;
+			}
+		}
+#ifdef IXANY
+		else if (info->name == decctlq) {
+			if (reversed)
+				mode->c_iflag |= IXANY;
+			else
+				mode->c_iflag &= ~IXANY;
+		}
+#endif
+#ifdef TABDLY
+		else if (info->name == stty_tabs) {
+			if (reversed)
+				mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB3;
+			else
+				mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB0;
+		}
+#else
+# ifdef OXTABS
+		else if (info->name == stty_tabs) {
+			if (reversed)
+				mode->c_oflag = mode->c_oflag | OXTABS;
+			else
+				mode->c_oflag = mode->c_oflag & ~OXTABS;
+		}
+# endif
+#endif
+#if defined(XCASE) && defined(IUCLC) && defined(OLCUC)
+		else if (info->name == stty_lcase || info->name == stty_LCASE) {
+			if (reversed) {
+				mode->c_lflag &= ~XCASE;
+				mode->c_iflag &= ~IUCLC;
+				mode->c_oflag &= ~OLCUC;
+			} else {
+				mode->c_lflag |= XCASE;
+				mode->c_iflag |= IUCLC;
+				mode->c_oflag |= OLCUC;
+			}
+		}
+#endif
+		else if (info->name == stty_crt)
+			mode->c_lflag |= ECHOE
+#ifdef ECHOCTL
+				| ECHOCTL
+#endif
+#ifdef ECHOKE
+				| ECHOKE
+#endif
+				;
+		else if (info->name == stty_dec) {
+			mode->c_cc[VINTR] = 3;	/* ^C */
+			mode->c_cc[VERASE] = 127;	/* DEL */
+			mode->c_cc[VKILL] = 21;	/* ^U */
+			mode->c_lflag |= ECHOE
+#ifdef ECHOCTL
+				| ECHOCTL
+#endif
+#ifdef ECHOKE
+				| ECHOKE
+#endif
+				;
+#ifdef IXANY
+			mode->c_iflag &= ~IXANY;
+#endif
+		}
+	} else if (reversed)
+		*bitsp = *bitsp & ~info->mask & ~info->bits;
+	else
+		*bitsp = (*bitsp & ~info->mask) | info->bits;
+
+	return 1;
+}
+
+static void
+set_control_char(const struct control_info *info, const char *arg,
+				 struct termios *mode)
+{
+	unsigned char value;
+
+	if (info->name == stty_min || info->name == stty_time)
+		value = parse_number(arg, stty_suffixes);
+	else if (arg[0] == '\0' || arg[1] == '\0')
+		value = arg[0];
+	else if (STREQ(arg, "^-") || STREQ(arg, "undef"))
+		value = _POSIX_VDISABLE;
+	else if (arg[0] == '^' && arg[1] != '\0') {	/* Ignore any trailing junk. */
+		if (arg[1] == '?')
+			value = 127;
+		else
+			value = arg[1] & ~0140;	/* Non-letters get weird results. */
+	} else
+		value = parse_number(arg, stty_suffixes);
+	mode->c_cc[info->offset] = value;
+}
+
+static void
+set_speed(enum speed_setting type, const char *arg, struct termios *mode)
+{
+	speed_t baud;
+
+	baud = string_to_baud(arg);
+	if (type == input_speed || type == both_speeds)
+		cfsetispeed(mode, baud);
+	if (type == output_speed || type == both_speeds)
+		cfsetospeed(mode, baud);
+}
+
+#ifdef TIOCGWINSZ
+
+static int get_win_size(int fd, struct winsize *win)
+{
+	int err = ioctl(fd, TIOCGWINSZ, (char *) win);
+
+	return err;
+}
+
+static void
+set_window_size(int rows, int cols, int fd, const char *device_name)
+{
+	struct winsize win;
+
+	if (get_win_size(fd, &win)) {
+		if (errno != EINVAL)
+			perror_msg_and_die("%s", device_name);
+		memset(&win, 0, sizeof(win));
+	}
+
+	if (rows >= 0)
+		win.ws_row = rows;
+	if (cols >= 0)
+		win.ws_col = cols;
+
+# ifdef TIOCSSIZE
+	/* Alexander Dupuy <dupuy@cs.columbia.edu> wrote:
+	   The following code deals with a bug in the SunOS 4.x (and 3.x?) kernel.
+	   This comment from sys/ttold.h describes Sun's twisted logic - a better
+	   test would have been (ts_lines > 64k || ts_cols > 64k || ts_cols == 0).
+	   At any rate, the problem is gone in Solaris 2.x. */
+
+	if (win.ws_row == 0 || win.ws_col == 0) {
+		struct ttysize ttysz;
+
+		ttysz.ts_lines = win.ws_row;
+		ttysz.ts_cols = win.ws_col;
+
+		win.ws_row = 1;
+		win.ws_col = 1;
+
+		if (ioctl(fd, TIOCSWINSZ, (char *) &win))
+			perror_msg_and_die("%s", device_name);
+
+		if (ioctl(fd, TIOCSSIZE, (char *) &ttysz))
+			perror_msg_and_die("%s", device_name);
+		return;
+	}
+# endif
+
+	if (ioctl(fd, TIOCSWINSZ, (char *) &win))
+		perror_msg_and_die("%s", device_name);
+}
+
+static void display_window_size(int fancy, int fd, const char *device_name)
+{
+	struct winsize win;
+
+	if (get_win_size(fd, &win)) {
+		if (errno != EINVAL)
+			perror_msg_and_die("%s", device_name);
+		if (!fancy)
+			perror_msg_and_die("%s: no size information for this device",
+							   device_name);
+	} else {
+		wrapf(fancy ? "rows %d; columns %d;" : "%d %d\n",
+			  win.ws_row, win.ws_col);
+		if (!fancy)
+			current_col = 0;
+	}
+}
+#endif
+
+static int screen_columns(void)
+{
+#ifdef TIOCGWINSZ
+	struct winsize win;
+
+	/* With Solaris 2.[123], this ioctl fails and errno is set to
+	   EINVAL for telnet (but not rlogin) sessions.
+	   On ISC 3.0, it fails for the console and the serial port
+	   (but it works for ptys).
+	   It can also fail on any system when stdout isn't a tty.
+	   In case of any failure, just use the default.  */
+	if (get_win_size(STDOUT_FILENO, &win) == 0 && win.ws_col > 0)
+		return win.ws_col;
+#endif
+
+	if (getenv("COLUMNS"))
+		return atoi(getenv("COLUMNS"));
+	return 80;
+}
+
+static tcflag_t *mode_type_flag(enum mode_type type, struct termios *mode)
+{
+	switch (type) {
+	case control:
+		return &mode->c_cflag;
+
+	case input:
+		return &mode->c_iflag;
+
+	case output:
+		return &mode->c_oflag;
+
+	case local:
+		return &mode->c_lflag;
+
+	default:					/* combination: */
+		return NULL;
+	}
+}
+
+static void
+display_settings(enum output_type output_type, struct termios *mode,
+				 int fd, const char *device_name)
+{
+	switch (output_type) {
+	case changed:
+		display_changed(mode);
+		break;
+
+	case all:
+		display_all(mode, fd, device_name);
+		break;
+
+	case recoverable:
+		display_recoverable(mode);
+		break;
+	}
+}
+
+static void display_changed(struct termios *mode)
+{
+	int i;
+	int empty_line;
+	tcflag_t *bitsp;
+	unsigned long mask;
+	enum mode_type prev_type = control;
+
+	display_speed(mode, 1);
+#ifdef HAVE_C_LINE
+	wrapf("line = %d;", mode->c_line);
+#endif
+	putchar('\n');
+	current_col = 0;
+
+	empty_line = 1;
+	for (i = 0; control_info[i].name != stty_min; ++i) {
+		if (mode->c_cc[control_info[i].offset] == control_info[i].saneval)
+			continue;
+		/* If swtch is the same as susp, don't print both.  */
+#if VSWTCH == VSUSP
+		if (control_info[i].name == stty_swtch)
+			continue;
+#endif
+		/* If eof uses the same slot as min, only print whichever applies.  */
+#if VEOF == VMIN
+		if ((mode->c_lflag & ICANON) == 0
+			&& (control_info[i].name == stty_eof
+				|| control_info[i].name == stty_eol)) continue;
+#endif
+
+		empty_line = 0;
+		wrapf("%s = %s;", control_info[i].name,
+			  visible(mode->c_cc[control_info[i].offset]));
+	}
+	if ((mode->c_lflag & ICANON) == 0) {
+		wrapf("min = %d; time = %d;\n", (int) mode->c_cc[VMIN],
+			  (int) mode->c_cc[VTIME]);
+	} else if (empty_line == 0)
+		putchar('\n');
+	current_col = 0;
+
+	empty_line = 1;
+	for (i = 0; i < NUM_mode_info; ++i) {
+		if (mode_info[i].flags & OMIT)
+			continue;
+		if (mode_info[i].type != prev_type) {
+			if (empty_line == 0) {
+				putchar('\n');
+				current_col = 0;
+				empty_line = 1;
+			}
+			prev_type = mode_info[i].type;
+		}
+
+		bitsp = mode_type_flag(mode_info[i].type, mode);
+		mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits;
+		if ((*bitsp & mask) == mode_info[i].bits) {
+			if (mode_info[i].flags & SANE_UNSET) {
+				wrapf("%s", mode_info[i].name);
+				empty_line = 0;
+			}
+		}
+			else if ((mode_info[i].flags & (SANE_SET | REV)) ==
+					 (SANE_SET | REV)) {
+			wrapf("-%s", mode_info[i].name);
+			empty_line = 0;
+		}
+	}
+	if (empty_line == 0)
+		putchar('\n');
+	current_col = 0;
+}
+
+static void
+display_all(struct termios *mode, int fd, const char *device_name)
+{
+	int i;
+	tcflag_t *bitsp;
+	unsigned long mask;
+	enum mode_type prev_type = control;
+
+	display_speed(mode, 1);
+#ifdef TIOCGWINSZ
+	display_window_size(1, fd, device_name);
+#endif
+#ifdef HAVE_C_LINE
+	wrapf("line = %d;", mode->c_line);
+#endif
+	putchar('\n');
+	current_col = 0;
+
+	for (i = 0; control_info[i].name != stty_min; ++i) {
+		/* If swtch is the same as susp, don't print both.  */
+#if VSWTCH == VSUSP
+		if (control_info[i].name == stty_swtch)
+			continue;
+#endif
+		/* If eof uses the same slot as min, only print whichever applies.  */
+#if VEOF == VMIN
+		if ((mode->c_lflag & ICANON) == 0
+			&& (control_info[i].name == stty_eof
+				|| control_info[i].name == stty_eol)) continue;
+#endif
+		wrapf("%s = %s;", control_info[i].name,
+			  visible(mode->c_cc[control_info[i].offset]));
+	}
+#if VEOF == VMIN
+	if ((mode->c_lflag & ICANON) == 0)
+#endif
+		wrapf("min = %d; time = %d;", mode->c_cc[VMIN], mode->c_cc[VTIME]);
+	if (current_col != 0)
+		putchar('\n');
+	current_col = 0;
+
+	for (i = 0; i < NUM_mode_info; ++i) {
+		if (mode_info[i].flags & OMIT)
+			continue;
+		if (mode_info[i].type != prev_type) {
+			putchar('\n');
+			current_col = 0;
+			prev_type = mode_info[i].type;
+		}
+
+		bitsp = mode_type_flag(mode_info[i].type, mode);
+		mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits;
+		if ((*bitsp & mask) == mode_info[i].bits)
+			wrapf("%s", mode_info[i].name);
+		else if (mode_info[i].flags & REV)
+			wrapf("-%s", mode_info[i].name);
+	}
+	putchar('\n');
+	current_col = 0;
+}
+
+static void display_speed(struct termios *mode, int fancy)
+{
+	if (cfgetispeed(mode) == 0 || cfgetispeed(mode) == cfgetospeed(mode))
+		wrapf(fancy ? "speed %lu baud;" : "%lu\n",
+			  baud_to_value(cfgetospeed(mode)));
+	else
+		wrapf(fancy ? "ispeed %lu baud; ospeed %lu baud;" : "%lu %lu\n",
+			  baud_to_value(cfgetispeed(mode)),
+			  baud_to_value(cfgetospeed(mode)));
+	if (!fancy)
+		current_col = 0;
+}
+
+static void display_recoverable(struct termios *mode)
+{
+	int i;
+
+	printf("%lx:%lx:%lx:%lx",
+		   (unsigned long) mode->c_iflag, (unsigned long) mode->c_oflag,
+		   (unsigned long) mode->c_cflag, (unsigned long) mode->c_lflag);
+	for (i = 0; i < NCCS; ++i)
+		printf(":%x", (unsigned int) mode->c_cc[i]);
+	putchar('\n');
+}
+
+static int recover_mode(char *arg, struct termios *mode)
+{
+	int i, n;
+	unsigned int chr;
+	unsigned long iflag, oflag, cflag, lflag;
+
+	/* Scan into temporaries since it is too much trouble to figure out
+	   the right format for `tcflag_t'.  */
+	if (sscanf(arg, "%lx:%lx:%lx:%lx%n",
+			   &iflag, &oflag, &cflag, &lflag, &n) != 4)
+		return 0;
+	mode->c_iflag = iflag;
+	mode->c_oflag = oflag;
+	mode->c_cflag = cflag;
+	mode->c_lflag = lflag;
+	arg += n;
+	for (i = 0; i < NCCS; ++i) {
+		if (sscanf(arg, ":%x%n", &chr, &n) != 1)
+			return 0;
+		mode->c_cc[i] = chr;
+		arg += n;
+	}
+
+	/* Fail if there are too many fields.  */
+	if (*arg != '\0')
+		return 0;
+
+	return 1;
+}
+
+struct speed_map {
+	speed_t speed;				/* Internal form. */
+	unsigned long value;		/* Numeric value. */
+};
+
+static const struct speed_map speeds[] = {
+	{B0, 0},
+	{B50, 50},
+	{B75, 75},
+	{B110, 110},
+	{B134, 134},
+	{B150, 150},
+	{B200, 200},
+	{B300, 300},
+	{B600, 600},
+	{B1200, 1200},
+	{B1800, 1800},
+	{B2400, 2400},
+	{B4800, 4800},
+	{B9600, 9600},
+	{B19200, 19200},
+	{B38400, 38400},
+#ifdef B57600
+	{B57600, 57600},
+#endif
+#ifdef B115200
+	{B115200, 115200},
+#endif
+#ifdef B230400
+	{B230400, 230400},
+#endif
+#ifdef B460800
+	{B460800, 460800},
+#endif
+};
+
+static const int NUM_SPEEDS = (sizeof(speeds) / sizeof(struct speed_map));
+
+static speed_t string_to_baud(const char *arg)
+{
+	int i;
+	static const struct suffix_mult stty_zerosuff = { NULL, 0 };
+
+	for (i = 0; i < NUM_SPEEDS; ++i)
+		if (parse_number(arg, &stty_zerosuff) == speeds[i].value)
+			return speeds[i].speed;
+	return (speed_t) - 1;
+}
+
+static unsigned long baud_to_value(speed_t speed)
+{
+	int i;
+
+	for (i = 0; i < NUM_SPEEDS; ++i)
+		if (speed == speeds[i].speed)
+			return speeds[i].value;
+	return 0;
+}
+
+static void sane_mode(struct termios *mode)
+{
+	int i;
+	tcflag_t *bitsp;
+
+	for (i = 0; i < NUM_control_info; ++i) {
+#if VMIN == VEOF
+		if (control_info[i].name == stty_min)
+			break;
+#endif
+		mode->c_cc[control_info[i].offset] = control_info[i].saneval;
+	}
+
+	for (i = 0; i < NUM_mode_info; ++i) {
+		if (mode_info[i].flags & SANE_SET) {
+			bitsp = mode_type_flag(mode_info[i].type, mode);
+			*bitsp = (*bitsp & ~mode_info[i].mask) | mode_info[i].bits;
+		} else if (mode_info[i].flags & SANE_UNSET) {
+			bitsp = mode_type_flag(mode_info[i].type, mode);
+			*bitsp = *bitsp & ~mode_info[i].mask & ~mode_info[i].bits;
+		}
+	}
+}
+
+/* Return a string that is the printable representation of character CH.  */
+/* Adapted from `cat' by Torbjorn Granlund.  */
+
+static const char *visible(unsigned int ch)
+{
+	static char buf[10];
+	char *bpout = buf;
+
+	if (ch == _POSIX_VDISABLE)
+		return "<undef>";
+
+	if (ch >= 32) {
+		if (ch < 127)
+			*bpout++ = ch;
+		else if (ch == 127) {
+			*bpout++ = '^';
+			*bpout++ = '?';
+		} else {
+			*bpout++ = 'M', *bpout++ = '-';
+			if (ch >= 128 + 32) {
+				if (ch < 128 + 127)
+					*bpout++ = ch - 128;
+				else {
+					*bpout++ = '^';
+					*bpout++ = '?';
+				}
+			} else {
+				*bpout++ = '^';
+				*bpout++ = ch - 128 + 64;
+			}
+		}
+	} else {
+		*bpout++ = '^';
+		*bpout++ = ch + 64;
+	}
+	*bpout = '\0';
+	return (const char *) buf;
+}
+
+/*
+Local Variables:
+c-file-style: "linux"
+c-basic-offset: 4
+tab-width: 4
+End:
+*/
diff --git a/include/applets.h b/include/applets.h
index 9aa65da..0b70a61 100644
--- a/include/applets.h
+++ b/include/applets.h
@@ -308,6 +308,9 @@
 #ifdef BB_SORT
 	APPLET("sort", sort_main, _BB_DIR_USR_BIN, sort_usage)
 #endif
+#ifdef BB_STTY
+	APPLET("stty", stty_main, _BB_DIR_BIN, stty_usage)
+#endif
 #ifdef BB_SWAPONOFF
 	APPLET("swapoff", swap_on_off_main, _BB_DIR_SBIN, swapoff_usage)
 #endif
diff --git a/stty.c b/stty.c
new file mode 100644
index 0000000..0fc0bfb
--- /dev/null
+++ b/stty.c
@@ -0,0 +1,1392 @@
+/* vi: set sw=4 ts=4: */
+/* stty -- change and print terminal line settings
+   Copyright (C) 1990-1999 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+/* Usage: stty [-ag] [-F device] [setting...]
+
+   Options:
+   -a Write all current settings to stdout in human-readable form.
+   -g Write all current settings to stdout in stty-readable form.
+   -F Open and use the specified device instead of stdin
+
+   If no args are given, write to stdout the baud rate and settings that
+   have been changed from their defaults.  Mode reading and changes
+   are done on the specified device, or stdin if none was specified.
+
+   David MacKenzie <djm@gnu.ai.mit.edu>
+
+   Special for busybox ported by vodz@usa.net 2001
+
+   */
+
+#include "busybox.h"
+
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <getopt.h>
+
+#include <sys/param.h>
+#include <unistd.h>
+
+#ifndef STDIN_FILENO
+# define STDIN_FILENO 0
+#endif
+
+#ifndef STDOUT_FILENO
+# define STDOUT_FILENO 1
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <memory.h>
+#include <fcntl.h>
+
+#define STREQ(a, b) (strcmp ((a), (b)) == 0)
+
+
+#ifndef _POSIX_VDISABLE
+# define _POSIX_VDISABLE ((unsigned char) 0)
+#endif
+
+#define Control(c) ((c) & 0x1f)
+/* Canonical values for control characters. */
+#ifndef CINTR
+# define CINTR Control ('c')
+#endif
+#ifndef CQUIT
+# define CQUIT 28
+#endif
+#ifndef CERASE
+# define CERASE 127
+#endif
+#ifndef CKILL
+# define CKILL Control ('u')
+#endif
+#ifndef CEOF
+# define CEOF Control ('d')
+#endif
+#ifndef CEOL
+# define CEOL _POSIX_VDISABLE
+#endif
+#ifndef CSTART
+# define CSTART Control ('q')
+#endif
+#ifndef CSTOP
+# define CSTOP Control ('s')
+#endif
+#ifndef CSUSP
+# define CSUSP Control ('z')
+#endif
+#if defined(VEOL2) && !defined(CEOL2)
+# define CEOL2 _POSIX_VDISABLE
+#endif
+/* ISC renamed swtch to susp for termios, but we'll accept either name.  */
+#if defined(VSUSP) && !defined(VSWTCH)
+# define VSWTCH VSUSP
+# define CSWTCH CSUSP
+#endif
+#if defined(VSWTCH) && !defined(CSWTCH)
+# define CSWTCH _POSIX_VDISABLE
+#endif
+
+/* SunOS 5.3 loses (^Z doesn't work) if `swtch' is the same as `susp'.
+   So the default is to disable `swtch.'  */
+#if defined (__sparc__) && defined (__svr4__)
+# undef CSWTCH
+# define CSWTCH _POSIX_VDISABLE
+#endif
+
+#if defined(VWERSE) && !defined (VWERASE)	/* AIX-3.2.5 */
+# define VWERASE VWERSE
+#endif
+#if defined(VDSUSP) && !defined (CDSUSP)
+# define CDSUSP Control ('y')
+#endif
+#if !defined(VREPRINT) && defined(VRPRNT)	/* Irix 4.0.5 */
+# define VREPRINT VRPRNT
+#endif
+#if defined(VREPRINT) && !defined(CRPRNT)
+# define CRPRNT Control ('r')
+#endif
+#if defined(VWERASE) && !defined(CWERASE)
+# define CWERASE Control ('w')
+#endif
+#if defined(VLNEXT) && !defined(CLNEXT)
+# define CLNEXT Control ('v')
+#endif
+#if defined(VDISCARD) && !defined(VFLUSHO)
+# define VFLUSHO VDISCARD
+#endif
+#if defined(VFLUSH) && !defined(VFLUSHO)	/* Ultrix 4.2 */
+# define VFLUSHO VFLUSH
+#endif
+#if defined(CTLECH) && !defined(ECHOCTL)	/* Ultrix 4.3 */
+# define ECHOCTL CTLECH
+#endif
+#if defined(TCTLECH) && !defined(ECHOCTL)	/* Ultrix 4.2 */
+# define ECHOCTL TCTLECH
+#endif
+#if defined(CRTKIL) && !defined(ECHOKE)	/* Ultrix 4.2 and 4.3 */
+# define ECHOKE CRTKIL
+#endif
+#if defined(VFLUSHO) && !defined(CFLUSHO)
+# define CFLUSHO Control ('o')
+#endif
+#if defined(VSTATUS) && !defined(CSTATUS)
+# define CSTATUS Control ('t')
+#endif
+
+/* Which speeds to set.  */
+enum speed_setting {
+	input_speed, output_speed, both_speeds
+};
+
+/* What to output and how.  */
+enum output_type {
+	changed, all, recoverable	/* Default, -a, -g.  */
+};
+
+/* Which member(s) of `struct termios' a mode uses.  */
+enum mode_type {
+	control, input, output, local, combination
+};
+
+
+static const char evenp[] = "evenp";
+static const char raw[] = "raw";
+static const char stty_min[] = "min";
+static const char stty_time[] = "time";
+static const char stty_swtch[] = "swtch";
+static const char stty_eol[] = "eol";
+static const char stty_eof[] = "eof";
+static const char parity[] = "parity";
+static const char stty_oddp[] = "oddp";
+static const char stty_nl[] = "nl";
+static const char stty_ek[] = "ek";
+static const char stty_sane[] = "sane";
+static const char cbreak[] = "cbreak";
+static const char stty_pass8[] = "pass8";
+static const char litout[] = "litout";
+static const char cooked[] = "cooked";
+static const char decctlq[] = "decctlq";
+static const char stty_tabs[] = "tabs";
+static const char stty_lcase[] = "lcase";
+static const char stty_LCASE[] = "LCASE";
+static const char stty_crt[] = "crt";
+static const char stty_dec[] = "dec";
+
+
+/* Flags for `struct mode_info'. */
+#define SANE_SET 1				/* Set in `sane' mode. */
+#define SANE_UNSET 2			/* Unset in `sane' mode. */
+#define REV 4					/* Can be turned off by prepending `-'. */
+#define OMIT 8					/* Don't display value. */
+
+/* Each mode.  */
+struct mode_info {
+	const char *name;			/* Name given on command line.  */
+	enum mode_type type;		/* Which structure element to change. */
+	char flags;					/* Setting and display options.  */
+	unsigned long bits;			/* Bits to set for this mode.  */
+	unsigned long mask;			/* Other bits to turn off for this mode.  */
+};
+
+static const struct mode_info mode_info[] = {
+	{"parenb", control, REV, PARENB, 0},
+	{"parodd", control, REV, PARODD, 0},
+	{"cs5", control, 0, CS5, CSIZE},
+	{"cs6", control, 0, CS6, CSIZE},
+	{"cs7", control, 0, CS7, CSIZE},
+	{"cs8", control, 0, CS8, CSIZE},
+	{"hupcl", control, REV, HUPCL, 0},
+	{"hup", control, REV | OMIT, HUPCL, 0},
+	{"cstopb", control, REV, CSTOPB, 0},
+	{"cread", control, SANE_SET | REV, CREAD, 0},
+	{"clocal", control, REV, CLOCAL, 0},
+#ifdef CRTSCTS
+	{"crtscts", control, REV, CRTSCTS, 0},
+#endif
+
+	{"ignbrk", input, SANE_UNSET | REV, IGNBRK, 0},
+	{"brkint", input, SANE_SET | REV, BRKINT, 0},
+	{"ignpar", input, REV, IGNPAR, 0},
+	{"parmrk", input, REV, PARMRK, 0},
+	{"inpck", input, REV, INPCK, 0},
+	{"istrip", input, REV, ISTRIP, 0},
+	{"inlcr", input, SANE_UNSET | REV, INLCR, 0},
+	{"igncr", input, SANE_UNSET | REV, IGNCR, 0},
+	{"icrnl", input, SANE_SET | REV, ICRNL, 0},
+	{"ixon", input, REV, IXON, 0},
+	{"ixoff", input, SANE_UNSET | REV, IXOFF, 0},
+	{"tandem", input, REV | OMIT, IXOFF, 0},
+#ifdef IUCLC
+	{"iuclc", input, SANE_UNSET | REV, IUCLC, 0},
+#endif
+#ifdef IXANY
+	{"ixany", input, SANE_UNSET | REV, IXANY, 0},
+#endif
+#ifdef IMAXBEL
+	{"imaxbel", input, SANE_SET | REV, IMAXBEL, 0},
+#endif
+
+	{"opost", output, SANE_SET | REV, OPOST, 0},
+#ifdef OLCUC
+	{"olcuc", output, SANE_UNSET | REV, OLCUC, 0},
+#endif
+#ifdef OCRNL
+	{"ocrnl", output, SANE_UNSET | REV, OCRNL, 0},
+#endif
+#ifdef ONLCR
+	{"onlcr", output, SANE_SET | REV, ONLCR, 0},
+#endif
+#ifdef ONOCR
+	{"onocr", output, SANE_UNSET | REV, ONOCR, 0},
+#endif
+#ifdef ONLRET
+	{"onlret", output, SANE_UNSET | REV, ONLRET, 0},
+#endif
+#ifdef OFILL
+	{"ofill", output, SANE_UNSET | REV, OFILL, 0},
+#endif
+#ifdef OFDEL
+	{"ofdel", output, SANE_UNSET | REV, OFDEL, 0},
+#endif
+#ifdef NLDLY
+	{"nl1", output, SANE_UNSET, NL1, NLDLY},
+	{"nl0", output, SANE_SET, NL0, NLDLY},
+#endif
+#ifdef CRDLY
+	{"cr3", output, SANE_UNSET, CR3, CRDLY},
+	{"cr2", output, SANE_UNSET, CR2, CRDLY},
+	{"cr1", output, SANE_UNSET, CR1, CRDLY},
+	{"cr0", output, SANE_SET, CR0, CRDLY},
+#endif
+#ifdef TABDLY
+	{"tab3", output, SANE_UNSET, TAB3, TABDLY},
+	{"tab2", output, SANE_UNSET, TAB2, TABDLY},
+	{"tab1", output, SANE_UNSET, TAB1, TABDLY},
+	{"tab0", output, SANE_SET, TAB0, TABDLY},
+#else
+# ifdef OXTABS
+	{"tab3", output, SANE_UNSET, OXTABS, 0},
+# endif
+#endif
+#ifdef BSDLY
+	{"bs1", output, SANE_UNSET, BS1, BSDLY},
+	{"bs0", output, SANE_SET, BS0, BSDLY},
+#endif
+#ifdef VTDLY
+	{"vt1", output, SANE_UNSET, VT1, VTDLY},
+	{"vt0", output, SANE_SET, VT0, VTDLY},
+#endif
+#ifdef FFDLY
+	{"ff1", output, SANE_UNSET, FF1, FFDLY},
+	{"ff0", output, SANE_SET, FF0, FFDLY},
+#endif
+
+	{"isig", local, SANE_SET | REV, ISIG, 0},
+	{"icanon", local, SANE_SET | REV, ICANON, 0},
+#ifdef IEXTEN
+	{"iexten", local, SANE_SET | REV, IEXTEN, 0},
+#endif
+	{"echo", local, SANE_SET | REV, ECHO, 0},
+	{"echoe", local, SANE_SET | REV, ECHOE, 0},
+	{"crterase", local, REV | OMIT, ECHOE, 0},
+	{"echok", local, SANE_SET | REV, ECHOK, 0},
+	{"echonl", local, SANE_UNSET | REV, ECHONL, 0},
+	{"noflsh", local, SANE_UNSET | REV, NOFLSH, 0},
+#ifdef XCASE
+	{"xcase", local, SANE_UNSET | REV, XCASE, 0},
+#endif
+#ifdef TOSTOP
+	{"tostop", local, SANE_UNSET | REV, TOSTOP, 0},
+#endif
+#ifdef ECHOPRT
+	{"echoprt", local, SANE_UNSET | REV, ECHOPRT, 0},
+	{"prterase", local, REV | OMIT, ECHOPRT, 0},
+#endif
+#ifdef ECHOCTL
+	{"echoctl", local, SANE_SET | REV, ECHOCTL, 0},
+	{"ctlecho", local, REV | OMIT, ECHOCTL, 0},
+#endif
+#ifdef ECHOKE
+	{"echoke", local, SANE_SET | REV, ECHOKE, 0},
+	{"crtkill", local, REV | OMIT, ECHOKE, 0},
+#endif
+
+	{evenp, combination, REV | OMIT, 0, 0},
+	{parity, combination, REV | OMIT, 0, 0},
+	{stty_oddp, combination, REV | OMIT, 0, 0},
+	{stty_nl, combination, REV | OMIT, 0, 0},
+	{stty_ek, combination, OMIT, 0, 0},
+	{stty_sane, combination, OMIT, 0, 0},
+	{cooked, combination, REV | OMIT, 0, 0},
+	{raw, combination, REV | OMIT, 0, 0},
+	{stty_pass8, combination, REV | OMIT, 0, 0},
+	{litout, combination, REV | OMIT, 0, 0},
+	{cbreak, combination, REV | OMIT, 0, 0},
+#ifdef IXANY
+	{decctlq, combination, REV | OMIT, 0, 0},
+#endif
+#if defined (TABDLY) || defined (OXTABS)
+	{stty_tabs, combination, REV | OMIT, 0, 0},
+#endif
+#if defined(XCASE) && defined(IUCLC) && defined(OLCUC)
+	{stty_lcase, combination, REV | OMIT, 0, 0},
+	{stty_LCASE, combination, REV | OMIT, 0, 0},
+#endif
+	{stty_crt, combination, OMIT, 0, 0},
+	{stty_dec, combination, OMIT, 0, 0},
+};
+
+static const int NUM_mode_info =
+
+	(sizeof(mode_info) / sizeof(struct mode_info));
+
+/* Control character settings.  */
+struct control_info {
+	const char *name;			/* Name given on command line.  */
+	unsigned char saneval;		/* Value to set for `stty sane'.  */
+	int offset;					/* Offset in c_cc.  */
+};
+
+/* Control characters. */
+
+static const struct control_info control_info[] = {
+	{"intr", CINTR, VINTR},
+	{"quit", CQUIT, VQUIT},
+	{"erase", CERASE, VERASE},
+	{"kill", CKILL, VKILL},
+	{stty_eof, CEOF, VEOF},
+	{stty_eol, CEOL, VEOL},
+#ifdef VEOL2
+	{"eol2", CEOL2, VEOL2},
+#endif
+#ifdef VSWTCH
+	{stty_swtch, CSWTCH, VSWTCH},
+#endif
+	{"start", CSTART, VSTART},
+	{"stop", CSTOP, VSTOP},
+	{"susp", CSUSP, VSUSP},
+#ifdef VDSUSP
+	{"dsusp", CDSUSP, VDSUSP},
+#endif
+#ifdef VREPRINT
+	{"rprnt", CRPRNT, VREPRINT},
+#endif
+#ifdef VWERASE
+	{"werase", CWERASE, VWERASE},
+#endif
+#ifdef VLNEXT
+	{"lnext", CLNEXT, VLNEXT},
+#endif
+#ifdef VFLUSHO
+	{"flush", CFLUSHO, VFLUSHO},
+#endif
+#ifdef VSTATUS
+	{"status", CSTATUS, VSTATUS},
+#endif
+
+	/* These must be last because of the display routines. */
+	{stty_min, 1, VMIN},
+	{stty_time, 0, VTIME},
+};
+
+static const int NUM_control_info =
+	(sizeof(control_info) / sizeof(struct control_info));
+
+
+static const char *visible(unsigned int ch);
+static unsigned long baud_to_value(speed_t speed);
+static int recover_mode(char *arg, struct termios *mode);
+static int screen_columns(void);
+static int set_mode(const struct mode_info *info,
+		int reversed, struct termios *mode);
+static speed_t string_to_baud(const char *arg);
+static tcflag_t *mode_type_flag(enum mode_type type, struct termios *mode);
+static void display_all(struct termios *mode, int fd,
+		const char *device_name);
+static void display_changed(struct termios *mode);
+static void display_recoverable(struct termios *mode);
+static void display_settings(enum output_type output_type, 
+		struct termios *mode, int fd,
+		const char *device_name);
+static void display_speed(struct termios *mode, int fancy);
+static void display_window_size(int fancy, int fd,
+		const char *device_name);
+static void sane_mode(struct termios *mode);
+static void set_control_char(const struct control_info *info,
+		const char *arg, struct termios *mode);
+static void set_speed(enum speed_setting type,
+		const char *arg, struct termios *mode);
+static void set_window_size(int rows, int cols, int fd,
+
+							const char *device_name);
+
+/* The width of the screen, for output wrapping. */
+static int max_col;
+
+/* Current position, to know when to wrap. */
+static int current_col;
+
+/* Print format string MESSAGE and optional args.
+   Wrap to next line first if it won't fit.
+   Print a space first unless MESSAGE will start a new line. */
+
+static void wrapf(const char *message, ...)
+{
+	va_list args;
+	char buf[1024];				/* Plenty long for our needs. */
+	int buflen;
+
+	va_start(args, message);
+	vsprintf(buf, message, args);
+	va_end(args);
+	buflen = strlen(buf);
+	if (current_col + (current_col > 0) + buflen >= max_col) {
+		putchar('\n');
+		current_col = 0;
+	}
+	if (current_col > 0) {
+		putchar(' ');
+		current_col++;
+	}
+	fputs(buf, stdout);
+	current_col += buflen;
+}
+
+static const struct suffix_mult stty_suffixes[] = {
+	{"b", 512},
+	{"k", 1024},
+	{"B", 1024},
+	{NULL, 0}
+};
+
+extern int stty_main(int argc, char **argv)
+{
+	struct termios mode;
+	enum output_type output_type;
+	int optc;
+	int require_set_attr;
+	int speed_was_set;
+	int verbose_output;
+	int recoverable_output;
+	int k;
+	int noargs = 1;
+	char *file_name = NULL;
+	int fd;
+	const char *device_name;
+
+	output_type = changed;
+	verbose_output = 0;
+	recoverable_output = 0;
+
+	/* Don't print error messages for unrecognized options.  */
+	opterr = 0;
+
+	while ((optc = getopt(argc, argv, "agF:")) != -1) {
+		switch (optc) {
+		case 'a':
+			verbose_output = 1;
+			output_type = all;
+			break;
+
+		case 'g':
+			recoverable_output = 1;
+			output_type = recoverable;
+			break;
+
+		case 'F':
+			if (file_name)
+				error_msg_and_die("only one device may be specified");
+			file_name = optarg;
+			break;
+
+		default:				/* unrecognized option */
+			noargs = 0;
+			break;
+		}
+
+		if (noargs == 0)
+			break;
+	}
+
+	if (optind < argc)
+		noargs = 0;
+
+	/* Specifying both -a and -g gets an error.  */
+	if (verbose_output && recoverable_output)
+		error_msg_and_die ("verbose and stty-readable output styles are mutually exclusive");
+
+	/* Specifying any other arguments with -a or -g gets an error.  */
+	if (!noargs && (verbose_output || recoverable_output))
+		error_msg_and_die ("modes may not be set when specifying an output style");
+
+	/* FIXME: it'd be better not to open the file until we've verified
+	   that all arguments are valid.  Otherwise, we could end up doing
+	   only some of the requested operations and then failing, probably
+	   leaving things in an undesirable state.  */
+
+	if (file_name) {
+		int fdflags;
+
+		device_name = file_name;
+		fd = open(device_name, O_RDONLY | O_NONBLOCK);
+		if (fd < 0)
+			perror_msg_and_die("%s", device_name);
+		if ((fdflags = fcntl(fd, F_GETFL)) == -1
+			|| fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0)
+			perror_msg_and_die("%s: couldn't reset non-blocking mode",
+							   device_name);
+	} else {
+		fd = 0;
+		device_name = "standard input";
+	}
+
+	/* Initialize to all zeroes so there is no risk memcmp will report a
+	   spurious difference in an uninitialized portion of the structure.  */
+	memset(&mode, 0, sizeof(mode));
+	if (tcgetattr(fd, &mode))
+		perror_msg_and_die("%s", device_name);
+
+	if (verbose_output || recoverable_output || noargs) {
+		max_col = screen_columns();
+		current_col = 0;
+		display_settings(output_type, &mode, fd, device_name);
+		return EXIT_SUCCESS;
+	}
+
+	speed_was_set = 0;
+	require_set_attr = 0;
+	k = optind;
+	while (k < argc) {
+		int match_found = 0;
+		int reversed = 0;
+		int i;
+
+		if (argv[k][0] == '-') {
+			++argv[k];
+			reversed = 1;
+		}
+		for (i = 0; i < NUM_mode_info; ++i) {
+			if (STREQ(argv[k], mode_info[i].name)) {
+				match_found = set_mode(&mode_info[i], reversed, &mode);
+				require_set_attr = 1;
+				break;
+			}
+		}
+		if (match_found == 0 && reversed) {
+			error_msg_and_die("invalid argument `%s'", --argv[k]);
+		}
+		if (match_found == 0) {
+			for (i = 0; i < NUM_control_info; ++i) {
+				if (STREQ(argv[k], control_info[i].name)) {
+					if (k == argc - 1) {
+						error_msg_and_die("missing argument to `%s'", argv[k]);
+					}
+					match_found = 1;
+					++k;
+					set_control_char(&control_info[i], argv[k], &mode);
+					require_set_attr = 1;
+					break;
+				}
+			}
+		}
+		if (match_found == 0) {
+			if (STREQ(argv[k], "ispeed")) {
+				if (k == argc - 1) {
+					error_msg_and_die("missing argument to `%s'", argv[k]);
+				}
+				++k;
+				set_speed(input_speed, argv[k], &mode);
+				speed_was_set = 1;
+				require_set_attr = 1;
+			} else if (STREQ(argv[k], "ospeed")) {
+				if (k == argc - 1) {
+					error_msg_and_die("missing argument to `%s'", argv[k]);
+				}
+				++k;
+				set_speed(output_speed, argv[k], &mode);
+				speed_was_set = 1;
+				require_set_attr = 1;
+			}
+#ifdef TIOCGWINSZ
+			else if (STREQ(argv[k], "rows")) {
+				if (k == argc - 1) {
+					error_msg_and_die("missing argument to `%s'", argv[k]);
+				}
+				++k;
+				set_window_size((int) parse_number(argv[k], stty_suffixes),
+								-1, fd, device_name);
+			} else if (STREQ(argv[k], "cols") || STREQ(argv[k], "columns")) {
+				if (k == argc - 1) {
+					error_msg_and_die("missing argument to `%s'", argv[k]);
+				}
+				++k;
+				set_window_size(-1,
+								(int) parse_number(argv[k], stty_suffixes),
+								fd, device_name);
+			} else if (STREQ(argv[k], "size")) {
+				max_col = screen_columns();
+				current_col = 0;
+				display_window_size(0, fd, device_name);
+			}
+#endif
+#ifdef HAVE_C_LINE
+			else if (STREQ(argv[k], "line")) {
+				if (k == argc - 1) {
+					error_msg_and_die("missing argument to `%s'", argv[k]);
+				}
+				++k;
+				mode.c_line = parse_number(argv[k], stty_suffixes);
+				require_set_attr = 1;
+			}
+#endif
+			else if (STREQ(argv[k], "speed")) {
+				max_col = screen_columns();
+				display_speed(&mode, 0);
+			} else if (string_to_baud(argv[k]) != (speed_t) - 1) {
+				set_speed(both_speeds, argv[k], &mode);
+				speed_was_set = 1;
+				require_set_attr = 1;
+			} else {
+				if (recover_mode(argv[k], &mode) == 0) {
+					error_msg_and_die("invalid argument `%s'", argv[k]);
+				}
+				require_set_attr = 1;
+			}
+		}
+		k++;
+	}
+
+	if (require_set_attr) {
+		struct termios new_mode;
+
+		if (tcsetattr(fd, TCSADRAIN, &mode))
+			perror_msg_and_die("%s", device_name);
+
+		/* POSIX (according to Zlotnick's book) tcsetattr returns zero if
+		   it performs *any* of the requested operations.  This means it
+		   can report `success' when it has actually failed to perform
+		   some proper subset of the requested operations.  To detect
+		   this partial failure, get the current terminal attributes and
+		   compare them to the requested ones.  */
+
+		/* Initialize to all zeroes so there is no risk memcmp will report a
+		   spurious difference in an uninitialized portion of the structure.  */
+		memset(&new_mode, 0, sizeof(new_mode));
+		if (tcgetattr(fd, &new_mode))
+			perror_msg_and_die("%s", device_name);
+
+		/* Normally, one shouldn't use memcmp to compare structures that
+		   may have `holes' containing uninitialized data, but we have been
+		   careful to initialize the storage of these two variables to all
+		   zeroes.  One might think it more efficient simply to compare the
+		   modified fields, but that would require enumerating those fields --
+		   and not all systems have the same fields in this structure.  */
+
+		if (memcmp(&mode, &new_mode, sizeof(mode)) != 0) {
+#ifdef CIBAUD
+			/* SunOS 4.1.3 (at least) has the problem that after this sequence,
+			   tcgetattr (&m1); tcsetattr (&m1); tcgetattr (&m2);
+			   sometimes (m1 != m2).  The only difference is in the four bits
+			   of the c_cflag field corresponding to the baud rate.  To save
+			   Sun users a little confusion, don't report an error if this
+			   happens.  But suppress the error only if we haven't tried to
+			   set the baud rate explicitly -- otherwise we'd never give an
+			   error for a true failure to set the baud rate.  */
+
+			new_mode.c_cflag &= (~CIBAUD);
+			if (speed_was_set || memcmp(&mode, &new_mode, sizeof(mode)) != 0)
+#endif
+			{
+				error_msg_and_die ("%s: unable to perform all requested operations",
+					 device_name);
+#ifdef TESTING
+				{
+					size_t i;
+
+					printf("new_mode: mode\n");
+					for (i = 0; i < sizeof(new_mode); i++)
+						printf("0x%02x: 0x%02x\n",
+							   *(((unsigned char *) &new_mode) + i),
+							   *(((unsigned char *) &mode) + i));
+				}
+#endif
+			}
+		}
+	}
+
+	return EXIT_SUCCESS;
+}
+
+/* Return 0 if not applied because not reversible; otherwise return 1.  */
+
+static int
+set_mode(const struct mode_info *info, int reversed, struct termios *mode)
+{
+	tcflag_t *bitsp;
+
+	if (reversed && (info->flags & REV) == 0)
+		return 0;
+
+	bitsp = mode_type_flag(info->type, mode);
+
+	if (bitsp == NULL) {
+		/* Combination mode. */
+		if (info->name == evenp || info->name == parity) {
+			if (reversed)
+				mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
+			else
+				mode->c_cflag =
+					(mode->c_cflag & ~PARODD & ~CSIZE) | PARENB | CS7;
+		} else if (info->name == stty_oddp) {
+			if (reversed)
+				mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
+			else
+				mode->c_cflag =
+					(mode->c_cflag & ~CSIZE) | CS7 | PARODD | PARENB;
+		} else if (info->name == stty_nl) {
+			if (reversed) {
+				mode->c_iflag = (mode->c_iflag | ICRNL) & ~INLCR & ~IGNCR;
+				mode->c_oflag = (mode->c_oflag
+#ifdef ONLCR
+								 | ONLCR
+#endif
+					)
+#ifdef OCRNL
+					& ~OCRNL
+#endif
+#ifdef ONLRET
+					& ~ONLRET
+#endif
+					;
+			} else {
+				mode->c_iflag = mode->c_iflag & ~ICRNL;
+#ifdef ONLCR
+				mode->c_oflag = mode->c_oflag & ~ONLCR;
+#endif
+			}
+		} else if (info->name == stty_ek) {
+			mode->c_cc[VERASE] = CERASE;
+			mode->c_cc[VKILL] = CKILL;
+		} else if (info->name == stty_sane)
+			sane_mode(mode);
+		else if (info->name == cbreak) {
+			if (reversed)
+				mode->c_lflag |= ICANON;
+			else
+				mode->c_lflag &= ~ICANON;
+		} else if (info->name == stty_pass8) {
+			if (reversed) {
+				mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB;
+				mode->c_iflag |= ISTRIP;
+			} else {
+				mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
+				mode->c_iflag &= ~ISTRIP;
+			}
+		} else if (info->name == litout) {
+			if (reversed) {
+				mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB;
+				mode->c_iflag |= ISTRIP;
+				mode->c_oflag |= OPOST;
+			} else {
+				mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
+				mode->c_iflag &= ~ISTRIP;
+				mode->c_oflag &= ~OPOST;
+			}
+		} else if (info->name == raw || info->name == cooked) {
+			if ((info->name[0] == 'r' && reversed)
+				|| (info->name[0] == 'c' && !reversed)) {
+				/* Cooked mode. */
+				mode->c_iflag |= BRKINT | IGNPAR | ISTRIP | ICRNL | IXON;
+				mode->c_oflag |= OPOST;
+				mode->c_lflag |= ISIG | ICANON;
+#if VMIN == VEOF
+				mode->c_cc[VEOF] = CEOF;
+#endif
+#if VTIME == VEOL
+				mode->c_cc[VEOL] = CEOL;
+#endif
+			} else {
+				/* Raw mode. */
+				mode->c_iflag = 0;
+				mode->c_oflag &= ~OPOST;
+				mode->c_lflag &= ~(ISIG | ICANON
+#ifdef XCASE
+								   | XCASE
+#endif
+					);
+				mode->c_cc[VMIN] = 1;
+				mode->c_cc[VTIME] = 0;
+			}
+		}
+#ifdef IXANY
+		else if (info->name == decctlq) {
+			if (reversed)
+				mode->c_iflag |= IXANY;
+			else
+				mode->c_iflag &= ~IXANY;
+		}
+#endif
+#ifdef TABDLY
+		else if (info->name == stty_tabs) {
+			if (reversed)
+				mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB3;
+			else
+				mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB0;
+		}
+#else
+# ifdef OXTABS
+		else if (info->name == stty_tabs) {
+			if (reversed)
+				mode->c_oflag = mode->c_oflag | OXTABS;
+			else
+				mode->c_oflag = mode->c_oflag & ~OXTABS;
+		}
+# endif
+#endif
+#if defined(XCASE) && defined(IUCLC) && defined(OLCUC)
+		else if (info->name == stty_lcase || info->name == stty_LCASE) {
+			if (reversed) {
+				mode->c_lflag &= ~XCASE;
+				mode->c_iflag &= ~IUCLC;
+				mode->c_oflag &= ~OLCUC;
+			} else {
+				mode->c_lflag |= XCASE;
+				mode->c_iflag |= IUCLC;
+				mode->c_oflag |= OLCUC;
+			}
+		}
+#endif
+		else if (info->name == stty_crt)
+			mode->c_lflag |= ECHOE
+#ifdef ECHOCTL
+				| ECHOCTL
+#endif
+#ifdef ECHOKE
+				| ECHOKE
+#endif
+				;
+		else if (info->name == stty_dec) {
+			mode->c_cc[VINTR] = 3;	/* ^C */
+			mode->c_cc[VERASE] = 127;	/* DEL */
+			mode->c_cc[VKILL] = 21;	/* ^U */
+			mode->c_lflag |= ECHOE
+#ifdef ECHOCTL
+				| ECHOCTL
+#endif
+#ifdef ECHOKE
+				| ECHOKE
+#endif
+				;
+#ifdef IXANY
+			mode->c_iflag &= ~IXANY;
+#endif
+		}
+	} else if (reversed)
+		*bitsp = *bitsp & ~info->mask & ~info->bits;
+	else
+		*bitsp = (*bitsp & ~info->mask) | info->bits;
+
+	return 1;
+}
+
+static void
+set_control_char(const struct control_info *info, const char *arg,
+				 struct termios *mode)
+{
+	unsigned char value;
+
+	if (info->name == stty_min || info->name == stty_time)
+		value = parse_number(arg, stty_suffixes);
+	else if (arg[0] == '\0' || arg[1] == '\0')
+		value = arg[0];
+	else if (STREQ(arg, "^-") || STREQ(arg, "undef"))
+		value = _POSIX_VDISABLE;
+	else if (arg[0] == '^' && arg[1] != '\0') {	/* Ignore any trailing junk. */
+		if (arg[1] == '?')
+			value = 127;
+		else
+			value = arg[1] & ~0140;	/* Non-letters get weird results. */
+	} else
+		value = parse_number(arg, stty_suffixes);
+	mode->c_cc[info->offset] = value;
+}
+
+static void
+set_speed(enum speed_setting type, const char *arg, struct termios *mode)
+{
+	speed_t baud;
+
+	baud = string_to_baud(arg);
+	if (type == input_speed || type == both_speeds)
+		cfsetispeed(mode, baud);
+	if (type == output_speed || type == both_speeds)
+		cfsetospeed(mode, baud);
+}
+
+#ifdef TIOCGWINSZ
+
+static int get_win_size(int fd, struct winsize *win)
+{
+	int err = ioctl(fd, TIOCGWINSZ, (char *) win);
+
+	return err;
+}
+
+static void
+set_window_size(int rows, int cols, int fd, const char *device_name)
+{
+	struct winsize win;
+
+	if (get_win_size(fd, &win)) {
+		if (errno != EINVAL)
+			perror_msg_and_die("%s", device_name);
+		memset(&win, 0, sizeof(win));
+	}
+
+	if (rows >= 0)
+		win.ws_row = rows;
+	if (cols >= 0)
+		win.ws_col = cols;
+
+# ifdef TIOCSSIZE
+	/* Alexander Dupuy <dupuy@cs.columbia.edu> wrote:
+	   The following code deals with a bug in the SunOS 4.x (and 3.x?) kernel.
+	   This comment from sys/ttold.h describes Sun's twisted logic - a better
+	   test would have been (ts_lines > 64k || ts_cols > 64k || ts_cols == 0).
+	   At any rate, the problem is gone in Solaris 2.x. */
+
+	if (win.ws_row == 0 || win.ws_col == 0) {
+		struct ttysize ttysz;
+
+		ttysz.ts_lines = win.ws_row;
+		ttysz.ts_cols = win.ws_col;
+
+		win.ws_row = 1;
+		win.ws_col = 1;
+
+		if (ioctl(fd, TIOCSWINSZ, (char *) &win))
+			perror_msg_and_die("%s", device_name);
+
+		if (ioctl(fd, TIOCSSIZE, (char *) &ttysz))
+			perror_msg_and_die("%s", device_name);
+		return;
+	}
+# endif
+
+	if (ioctl(fd, TIOCSWINSZ, (char *) &win))
+		perror_msg_and_die("%s", device_name);
+}
+
+static void display_window_size(int fancy, int fd, const char *device_name)
+{
+	struct winsize win;
+
+	if (get_win_size(fd, &win)) {
+		if (errno != EINVAL)
+			perror_msg_and_die("%s", device_name);
+		if (!fancy)
+			perror_msg_and_die("%s: no size information for this device",
+							   device_name);
+	} else {
+		wrapf(fancy ? "rows %d; columns %d;" : "%d %d\n",
+			  win.ws_row, win.ws_col);
+		if (!fancy)
+			current_col = 0;
+	}
+}
+#endif
+
+static int screen_columns(void)
+{
+#ifdef TIOCGWINSZ
+	struct winsize win;
+
+	/* With Solaris 2.[123], this ioctl fails and errno is set to
+	   EINVAL for telnet (but not rlogin) sessions.
+	   On ISC 3.0, it fails for the console and the serial port
+	   (but it works for ptys).
+	   It can also fail on any system when stdout isn't a tty.
+	   In case of any failure, just use the default.  */
+	if (get_win_size(STDOUT_FILENO, &win) == 0 && win.ws_col > 0)
+		return win.ws_col;
+#endif
+
+	if (getenv("COLUMNS"))
+		return atoi(getenv("COLUMNS"));
+	return 80;
+}
+
+static tcflag_t *mode_type_flag(enum mode_type type, struct termios *mode)
+{
+	switch (type) {
+	case control:
+		return &mode->c_cflag;
+
+	case input:
+		return &mode->c_iflag;
+
+	case output:
+		return &mode->c_oflag;
+
+	case local:
+		return &mode->c_lflag;
+
+	default:					/* combination: */
+		return NULL;
+	}
+}
+
+static void
+display_settings(enum output_type output_type, struct termios *mode,
+				 int fd, const char *device_name)
+{
+	switch (output_type) {
+	case changed:
+		display_changed(mode);
+		break;
+
+	case all:
+		display_all(mode, fd, device_name);
+		break;
+
+	case recoverable:
+		display_recoverable(mode);
+		break;
+	}
+}
+
+static void display_changed(struct termios *mode)
+{
+	int i;
+	int empty_line;
+	tcflag_t *bitsp;
+	unsigned long mask;
+	enum mode_type prev_type = control;
+
+	display_speed(mode, 1);
+#ifdef HAVE_C_LINE
+	wrapf("line = %d;", mode->c_line);
+#endif
+	putchar('\n');
+	current_col = 0;
+
+	empty_line = 1;
+	for (i = 0; control_info[i].name != stty_min; ++i) {
+		if (mode->c_cc[control_info[i].offset] == control_info[i].saneval)
+			continue;
+		/* If swtch is the same as susp, don't print both.  */
+#if VSWTCH == VSUSP
+		if (control_info[i].name == stty_swtch)
+			continue;
+#endif
+		/* If eof uses the same slot as min, only print whichever applies.  */
+#if VEOF == VMIN
+		if ((mode->c_lflag & ICANON) == 0
+			&& (control_info[i].name == stty_eof
+				|| control_info[i].name == stty_eol)) continue;
+#endif
+
+		empty_line = 0;
+		wrapf("%s = %s;", control_info[i].name,
+			  visible(mode->c_cc[control_info[i].offset]));
+	}
+	if ((mode->c_lflag & ICANON) == 0) {
+		wrapf("min = %d; time = %d;\n", (int) mode->c_cc[VMIN],
+			  (int) mode->c_cc[VTIME]);
+	} else if (empty_line == 0)
+		putchar('\n');
+	current_col = 0;
+
+	empty_line = 1;
+	for (i = 0; i < NUM_mode_info; ++i) {
+		if (mode_info[i].flags & OMIT)
+			continue;
+		if (mode_info[i].type != prev_type) {
+			if (empty_line == 0) {
+				putchar('\n');
+				current_col = 0;
+				empty_line = 1;
+			}
+			prev_type = mode_info[i].type;
+		}
+
+		bitsp = mode_type_flag(mode_info[i].type, mode);
+		mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits;
+		if ((*bitsp & mask) == mode_info[i].bits) {
+			if (mode_info[i].flags & SANE_UNSET) {
+				wrapf("%s", mode_info[i].name);
+				empty_line = 0;
+			}
+		}
+			else if ((mode_info[i].flags & (SANE_SET | REV)) ==
+					 (SANE_SET | REV)) {
+			wrapf("-%s", mode_info[i].name);
+			empty_line = 0;
+		}
+	}
+	if (empty_line == 0)
+		putchar('\n');
+	current_col = 0;
+}
+
+static void
+display_all(struct termios *mode, int fd, const char *device_name)
+{
+	int i;
+	tcflag_t *bitsp;
+	unsigned long mask;
+	enum mode_type prev_type = control;
+
+	display_speed(mode, 1);
+#ifdef TIOCGWINSZ
+	display_window_size(1, fd, device_name);
+#endif
+#ifdef HAVE_C_LINE
+	wrapf("line = %d;", mode->c_line);
+#endif
+	putchar('\n');
+	current_col = 0;
+
+	for (i = 0; control_info[i].name != stty_min; ++i) {
+		/* If swtch is the same as susp, don't print both.  */
+#if VSWTCH == VSUSP
+		if (control_info[i].name == stty_swtch)
+			continue;
+#endif
+		/* If eof uses the same slot as min, only print whichever applies.  */
+#if VEOF == VMIN
+		if ((mode->c_lflag & ICANON) == 0
+			&& (control_info[i].name == stty_eof
+				|| control_info[i].name == stty_eol)) continue;
+#endif
+		wrapf("%s = %s;", control_info[i].name,
+			  visible(mode->c_cc[control_info[i].offset]));
+	}
+#if VEOF == VMIN
+	if ((mode->c_lflag & ICANON) == 0)
+#endif
+		wrapf("min = %d; time = %d;", mode->c_cc[VMIN], mode->c_cc[VTIME]);
+	if (current_col != 0)
+		putchar('\n');
+	current_col = 0;
+
+	for (i = 0; i < NUM_mode_info; ++i) {
+		if (mode_info[i].flags & OMIT)
+			continue;
+		if (mode_info[i].type != prev_type) {
+			putchar('\n');
+			current_col = 0;
+			prev_type = mode_info[i].type;
+		}
+
+		bitsp = mode_type_flag(mode_info[i].type, mode);
+		mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits;
+		if ((*bitsp & mask) == mode_info[i].bits)
+			wrapf("%s", mode_info[i].name);
+		else if (mode_info[i].flags & REV)
+			wrapf("-%s", mode_info[i].name);
+	}
+	putchar('\n');
+	current_col = 0;
+}
+
+static void display_speed(struct termios *mode, int fancy)
+{
+	if (cfgetispeed(mode) == 0 || cfgetispeed(mode) == cfgetospeed(mode))
+		wrapf(fancy ? "speed %lu baud;" : "%lu\n",
+			  baud_to_value(cfgetospeed(mode)));
+	else
+		wrapf(fancy ? "ispeed %lu baud; ospeed %lu baud;" : "%lu %lu\n",
+			  baud_to_value(cfgetispeed(mode)),
+			  baud_to_value(cfgetospeed(mode)));
+	if (!fancy)
+		current_col = 0;
+}
+
+static void display_recoverable(struct termios *mode)
+{
+	int i;
+
+	printf("%lx:%lx:%lx:%lx",
+		   (unsigned long) mode->c_iflag, (unsigned long) mode->c_oflag,
+		   (unsigned long) mode->c_cflag, (unsigned long) mode->c_lflag);
+	for (i = 0; i < NCCS; ++i)
+		printf(":%x", (unsigned int) mode->c_cc[i]);
+	putchar('\n');
+}
+
+static int recover_mode(char *arg, struct termios *mode)
+{
+	int i, n;
+	unsigned int chr;
+	unsigned long iflag, oflag, cflag, lflag;
+
+	/* Scan into temporaries since it is too much trouble to figure out
+	   the right format for `tcflag_t'.  */
+	if (sscanf(arg, "%lx:%lx:%lx:%lx%n",
+			   &iflag, &oflag, &cflag, &lflag, &n) != 4)
+		return 0;
+	mode->c_iflag = iflag;
+	mode->c_oflag = oflag;
+	mode->c_cflag = cflag;
+	mode->c_lflag = lflag;
+	arg += n;
+	for (i = 0; i < NCCS; ++i) {
+		if (sscanf(arg, ":%x%n", &chr, &n) != 1)
+			return 0;
+		mode->c_cc[i] = chr;
+		arg += n;
+	}
+
+	/* Fail if there are too many fields.  */
+	if (*arg != '\0')
+		return 0;
+
+	return 1;
+}
+
+struct speed_map {
+	speed_t speed;				/* Internal form. */
+	unsigned long value;		/* Numeric value. */
+};
+
+static const struct speed_map speeds[] = {
+	{B0, 0},
+	{B50, 50},
+	{B75, 75},
+	{B110, 110},
+	{B134, 134},
+	{B150, 150},
+	{B200, 200},
+	{B300, 300},
+	{B600, 600},
+	{B1200, 1200},
+	{B1800, 1800},
+	{B2400, 2400},
+	{B4800, 4800},
+	{B9600, 9600},
+	{B19200, 19200},
+	{B38400, 38400},
+#ifdef B57600
+	{B57600, 57600},
+#endif
+#ifdef B115200
+	{B115200, 115200},
+#endif
+#ifdef B230400
+	{B230400, 230400},
+#endif
+#ifdef B460800
+	{B460800, 460800},
+#endif
+};
+
+static const int NUM_SPEEDS = (sizeof(speeds) / sizeof(struct speed_map));
+
+static speed_t string_to_baud(const char *arg)
+{
+	int i;
+	static const struct suffix_mult stty_zerosuff = { NULL, 0 };
+
+	for (i = 0; i < NUM_SPEEDS; ++i)
+		if (parse_number(arg, &stty_zerosuff) == speeds[i].value)
+			return speeds[i].speed;
+	return (speed_t) - 1;
+}
+
+static unsigned long baud_to_value(speed_t speed)
+{
+	int i;
+
+	for (i = 0; i < NUM_SPEEDS; ++i)
+		if (speed == speeds[i].speed)
+			return speeds[i].value;
+	return 0;
+}
+
+static void sane_mode(struct termios *mode)
+{
+	int i;
+	tcflag_t *bitsp;
+
+	for (i = 0; i < NUM_control_info; ++i) {
+#if VMIN == VEOF
+		if (control_info[i].name == stty_min)
+			break;
+#endif
+		mode->c_cc[control_info[i].offset] = control_info[i].saneval;
+	}
+
+	for (i = 0; i < NUM_mode_info; ++i) {
+		if (mode_info[i].flags & SANE_SET) {
+			bitsp = mode_type_flag(mode_info[i].type, mode);
+			*bitsp = (*bitsp & ~mode_info[i].mask) | mode_info[i].bits;
+		} else if (mode_info[i].flags & SANE_UNSET) {
+			bitsp = mode_type_flag(mode_info[i].type, mode);
+			*bitsp = *bitsp & ~mode_info[i].mask & ~mode_info[i].bits;
+		}
+	}
+}
+
+/* Return a string that is the printable representation of character CH.  */
+/* Adapted from `cat' by Torbjorn Granlund.  */
+
+static const char *visible(unsigned int ch)
+{
+	static char buf[10];
+	char *bpout = buf;
+
+	if (ch == _POSIX_VDISABLE)
+		return "<undef>";
+
+	if (ch >= 32) {
+		if (ch < 127)
+			*bpout++ = ch;
+		else if (ch == 127) {
+			*bpout++ = '^';
+			*bpout++ = '?';
+		} else {
+			*bpout++ = 'M', *bpout++ = '-';
+			if (ch >= 128 + 32) {
+				if (ch < 128 + 127)
+					*bpout++ = ch - 128;
+				else {
+					*bpout++ = '^';
+					*bpout++ = '?';
+				}
+			} else {
+				*bpout++ = '^';
+				*bpout++ = ch - 128 + 64;
+			}
+		}
+	} else {
+		*bpout++ = '^';
+		*bpout++ = ch + 64;
+	}
+	*bpout = '\0';
+	return (const char *) buf;
+}
+
+/*
+Local Variables:
+c-file-style: "linux"
+c-basic-offset: 4
+tab-width: 4
+End:
+*/
diff --git a/usage.c b/usage.c
index bdd4d3d..7f99808 100644
--- a/usage.c
+++ b/usage.c
@@ -1243,6 +1243,20 @@
 	;
 #endif
 
+#if defined BB_STTY
+const char stty_usage[] =
+	"stty [-a|g] [-F device] [SETTING]..."
+#ifndef BB_FEATURE_TRIVIAL_HELP
+	"\n\nWithout arguments, prints baud rate, line discipline,"
+	"\nand deviations from stty sane."
+	"\n -F device  open and use the specified device instead of stdin"
+	"\n -a         print all current settings in human-readable form. Or"
+	"\n -g         print in a stty-readable form"
+	"\n [SETTING]  see in documentation"
+#endif
+	;
+#endif
+
 #if defined BB_SWAPONOFF
 const char swapoff_usage[] =
 	"swapoff [OPTION] [device]"