Re: Serial problems with 2.1.x

tytso@mit.edu
Tue, 29 Sep 1998 20:39:13 -0400


From: Keith Owens <kaos@ocs.com.au>
Date: Mon, 28 Sep 1998 15:19:34 +1000

pppd 2.3.5. 2.1.12[23], also occurred on earleir versions.

stty -a < /dev/ttyS1 shows clocal.
Dialout a pppd link on ttyS1.
stty -a < /dev/ttyS1 shows -clocal.
Turn the external modem off, pppd dies.
Turn modem back on, try to dial out again.

stty -a < /dev/ttyS1 now hangs in open, as does any other attempt to
open ttyS1.

This isn't a bug, it's how POSIX.1 specifies things are supposed to
work. If CLOCAL is off, then trying to open a tty will hang until
carrier detect is raised, UNLESS O_NDELAY is set. The problem is that
programs like bash don't know that they should set the O_NDELAY flag
when opening tty devices. Programs that are designed to deal with
modems, such as mgetty, sendfax, etc. do know about the O_NDELAY flag.

The problem is that with the POSIX tty handling, the traditional way
of using stty on another device "stty < /dev/ttyXXXX" doesn't work if
CLOCAL is off. The fix is to add a new option to stty which allows it
to open the device for us, with the appropriate options. The
following patch has been accepted by the GNU shellutils maintainer,
but unfortunately a new release of shellutils hasn't happened yet.
But if you pull down the shell-utils 1.16, the following patch will
allow you to use stty the way you would like.

(You'll note this patch was done back in April; I'll have to bug the
shell-utils maintainer if he could get a new release of shell-utils
out, given that Linux 2.2 should be coming out soon. Linux
distribution folks, please take note! Even if shell-utils 1.17
doesn't ship soon, you really probably want to include this patch when
you start shipping a distribution which uses the new kernel, given
that the new kernel very strongly discourages the use of /dev/cuaXX.)

- Ted

Patch generated: on Wed Apr 1 13:34:53 EST 1998 by tytso@rsts-11.mit.edu
against shellutils version 1.16

===================================================================
RCS file: ChangeLog,v
retrieving revision 1.1
diff -u -r1.1 ChangeLog
--- ChangeLog 1998/04/01 14:39:04 1.1
+++ ChangeLog 1998/04/01 18:14:44
@@ -1,3 +1,17 @@
+1998-03-31 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * src/stty.c (main): Fix broken options parsing that worked only
+ by serendipity (getopt_long_only already parsed short options; no
+ need to parse them again manually!). Add support for the --file
+ option, which allows the user to specify the device whose line
+ settings are to be set. This is necessary because POSIX ttys will
+ block waiting for carrier detect to go high if CLOCAL is not set,
+ unless the device is opened with the O_NONBLOCK flag.
+ Unfortunately, the shell doesn't use this flag, so users lose.
+ Opening the device in stty is the easist way to fix this.
+ (speeds): Add support for 230400 and 460800 line speeds, which are
+ supported by Linux.
+
Sun Jan 26 12:51:05 1997 Jim Meyering <meyering@na-net.ornl.gov>

* Version 1.16.
===================================================================
RCS file: src/RCS/stty.c,v
retrieving revision 1.1
diff -u -r1.1 src/stty.c
--- src/stty.c 1998/04/01 07:01:02 1.1
+++ src/stty.c 1998/04/01 18:34:51
@@ -15,15 +15,16 @@
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] [--all] [--save] [setting...]
+/* Usage: stty [-ag] [--all] [--save] [-F device] [--file=device] [setting...]

Options:
-a, --all Write all current settings to stdout in human-readable form.
-g, --save Write all current settings to stdout in stty-readable form.
+ -F, --file 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 stdin.
+ are done on the specified device, or stdin if none was specified.

David MacKenzie <djm@gnu.ai.mit.edu> */

@@ -401,19 +402,23 @@
static speed_t string_to_baud __P ((const char *arg));
static tcflag_t *mode_type_flag __P ((enum mode_type type,
struct termios *mode));
-static void display_all __P ((struct termios *mode));
+static void display_all __P ((struct termios *mode, int fd,
+ const char *device));
static void display_changed __P ((struct termios *mode));
static void display_recoverable __P ((struct termios *mode));
static void display_settings __P ((enum output_type output_type,
- struct termios *mode));
+ struct termios *mode, int fd,
+ const char *device));
static void display_speed __P ((struct termios *mode, int fancy));
-static void display_window_size __P ((int fancy));
+static void display_window_size __P ((int fancy, int fd,
+ const char *device));
static void sane_mode __P ((struct termios *mode));
static void set_control_char __P ((struct control_info *info, const char *arg,
struct termios *mode));
static void set_speed __P ((enum speed_setting type, const char *arg,
struct termios *mode));
-static void set_window_size __P ((int rows, int cols));
+static void set_window_size __P ((int rows, int cols, int fd,
+ const char *device));

/* The width of the screen, for output wrapping. */
static int max_col;
@@ -425,6 +430,7 @@
{
{"all", no_argument, NULL, 'a'},
{"save", no_argument, NULL, 'g'},
+ {"file", required_argument, NULL, 'F'},
{NULL, 0, NULL, 0}
};

@@ -476,15 +482,17 @@
else
{
printf (_("\
-Usage: %s [SETTING]...\n\
- or: %s OPTION\n\
+Usage: %s [-F device] [--file=device] [SETTING]...\n\
+ or: %s [-F device] [--file=device] [-a|--all]\n\
+ or: %s [-F device] [--file=device] [-g|--save]\n\
"),
- program_name, program_name);
+ program_name, program_name, program_name);
printf (_("\
Print or change terminal characteristics.\n\
\n\
-a, --all print all current settings in human-readable form\n\
-g, --save print all current settings in a stty-readable form\n\
+ -F, --file open and use the specified device instead of stdin\n\
--help display this help and exit\n\
--version output version information and exit\n\
\n\
@@ -648,6 +656,29 @@
exit (status);
}

+/*
+ * Return 1 if the string only contains valid options
+ */
+int valid_options(char *opt, const char *valid_opts,
+ const char *valid_arg_opts)
+{
+ char ch;
+
+ if (*opt++ != '-')
+ return 0;
+
+ while (ch = *opt) {
+ opt++;
+ if (strchr(valid_opts, ch))
+ continue;
+ if (strchr(valid_arg_opts, ch))
+ return 1;
+ return 0;
+ }
+ return 1;
+}
+
+
int
main (int argc, char **argv)
{
@@ -659,6 +690,11 @@
int verbose_output;
int recoverable_output;
int k;
+ int noargs = 1;
+ char *file_name = NULL, *cp;
+ int fd, fdflags;
+ const char *device_name;
+ const char *posixly_correct = getenv("POSIXLY_CORRECT");

program_name = argv[0];
setlocale (LC_ALL, "");
@@ -673,7 +709,7 @@

/* Recognize the long options only. */
opterr = 0;
- while ((optc = getopt_long_only (argc, argv, "ag", longopts, (int *) 0))
+ while ((optc = getopt_long_only (argc, argv, "agF:", longopts, (int *) 0))
!= EOF)
{
switch (optc)
@@ -688,44 +724,80 @@
output_type = recoverable;
break;

+ case 'f':
+ if (file_name)
+ error(2, 0, _("Only one device can be specified.\n"));
+ file_name = optarg;
+ break;
+
default:
break;
}
}

- /* Recognize short options and combinations: -a, -g, -ag, and -ga.
- They need not precede non-options. We cannot use GNU getopt because
- it would treat -tabs and -ixany as uses of the -a option. */
- for (k = optind; k < argc; k++)
+ /*
+ * Clear out the options that have been parsed. This is kind of
+ * gross, but it's needed because stty SETTINGS look like options to
+ * getopt(), so we need to work around things in a really horrible
+ * way. If any new options are ever added to stty, the short option
+ * MUST NOT be a letter which is the first letter of one of the
+ * possible stty settings. (NOTE: I didn't add this brokeness, I
+ * just fixed the existing code so that it worked correctly in all
+ * cases of --, POSIXLY_CORRECT, etc. [tytso:19980401.1316EST])
+ */
+ for (k = 1; k < argc; k++)
{
- if (argv[k][0] == '-')
+ if (!argv[k])
+ continue;
+ /* Handle --, and set noargs if there are arguments following it */
+ if (!strcmp(argv[k], "--"))
{
- if (argv[k][1] == 'a'
- && argv[k][2] == '\0')
- {
- ++optind;
- verbose_output = 1;
- }
- else if (argv[k][1] == 'g'
- && argv[k][2] == '\0')
- {
- ++optind;
- recoverable_output = 1;
- }
- else if ((argv[k][1] == 'g'
- && argv[k][2] == 'a'
- && argv[k][3] == '\0')
- || (argv[k][1] == 'a'
- && argv[k][2] == 'g'
- && argv[k][3] == '\0'))
- {
- ++optind;
- verbose_output = 1;
- recoverable_output = 1;
- }
- }
+ argv[k] = 0;
+ if (k < argc-1)
+ noargs = 0;
+ break;
+ }
+ /* Handle "--file device" */
+ if (!strcmp(argv[k], "--file"))
+ {
+ argv[k+1] = 0;
+ argv[k] = 0;
+ }
+ /* Handle "--all", "--save", and "--file=device" */
+ else if (!strcmp(argv[k], "--all") ||
+ !strcmp(argv[k], "--save") ||
+ !strncmp(argv[k], "--file=", 7))
+ argv[k] = 0;
+ /* Handle "-a", "-ag", "-aF/dev/foo", "-aF /dev/foo", etc. */
+ else if (valid_options(argv[k], "ag", "F"))
+ {
+ if (!strcmp(argv[k], "-file") ||
+ argv[k][strlen(argv[k])-1] == 'F')
+ argv[k+1] = 0;
+ argv[k] = 0;
+ }
+ /* Everything else must be a normal, non-option argument. */
+ else
+ {
+ noargs = 0;
+ if (posixly_correct)
+ break;
+ }
}

+#if 0
+ /* For debugging purposes, print out the argument */
+ for (k=1; k < argc; k++) {
+ if (argv[k])
+ printf("arg: %s\n", argv[k]);
+ else
+ printf("arg: none\n");
+ }
+ printf("File_name = %s\n", file_name ? file_name : "NONE");
+ printf("noargs = %d\n", noargs);
+#endif
+
+
/* Specifying both -a and -g gets an error. */
if (verbose_output && recoverable_output)
error (2, 0,
@@ -733,20 +805,35 @@
mutually exclusive"));

/* Specifying any other arguments with -a or -g gets an error. */
- if (argc - optind > 0 && (verbose_output || recoverable_output))
+ if (!noargs && (verbose_output || recoverable_output))
error (2, 0, _("when specifying an output style, modes may not be set"));

+ if (file_name)
+ {
+ device_name = file_name;
+ fd = open(device_name, O_RDONLY|O_NONBLOCK);
+ if (fd < 0)
+ error(1, errno, device_name);
+ if ((fdflags = fcntl(fd, F_GETFL)) == -1
+ || fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0)
+ error(1, errno, _("couldn't reset non-blocking mode"));
+ } 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 (0, &mode))
- error (1, errno, _("standard input"));
+ if (tcgetattr (fd, &mode))
+ error (1, errno, device_name);

- if (verbose_output || recoverable_output || argc == 1)
+ if (verbose_output || recoverable_output || noargs)
{
max_col = screen_columns ();
current_col = 0;
- display_settings (output_type, &mode);
+ display_settings (output_type, &mode, fd, device_name);
exit (0);
}

@@ -759,6 +846,11 @@
int reversed = 0;
int i;

+ if (argv[k] == 0) {
+ k++;
+ continue;
+ }
+
if (argv[k][0] == '-')
{
++argv[k];
@@ -832,7 +924,8 @@
usage (1);
}
++k;
- set_window_size ((int) integer_arg (argv[k]), -1);
+ set_window_size ((int) integer_arg (argv[k]), -1,
+ fd, device_name);
}
else if (!strcmp (argv[k], "cols")
|| !strcmp (argv[k], "columns"))
@@ -843,13 +936,14 @@
usage (1);
}
++k;
- set_window_size (-1, (int) integer_arg (argv[k]));
+ set_window_size (-1, (int) integer_arg (argv[k]),
+ fd, device_name);
}
else if (!strcmp (argv[k], "size"))
{
max_col = screen_columns ();
current_col = 0;
- display_window_size (0);
+ display_window_size (0, fd, device_name);
}
#endif
#ifdef HAVE_C_LINE
@@ -893,8 +987,8 @@
{
struct termios new_mode;

- if (tcsetattr (0, TCSADRAIN, &mode))
- error (1, errno, _("standard input"));
+ if (tcsetattr (fd, TCSADRAIN, &mode))
+ error (1, errno, device_name);

/* POSIX (according to Zlotnick's book) tcsetattr returns zero if
it performs *any* of the requested operations. This means it
@@ -906,8 +1000,8 @@
/* 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 (0, &new_mode))
- error (1, errno, _("standard input"));
+ if (tcgetattr (fd, &new_mode))
+ error (1, errno, device_name);

/* Normally, one shouldn't use memcmp to compare structures that
may have `holes' containing uninitialized data, but we have been
@@ -934,7 +1028,8 @@
{
size_t i;
error (1, 0,
- _("standard input: unable to perform all requested operations"));
+ _("%s: unable to perform all requested operations"),
+ device_name);
printf (_("new_mode: mode\n"));
for (i = 0; i < sizeof (new_mode); i++)
printf ("0x%02x: 0x%02x\n",
@@ -1201,14 +1296,14 @@
}

static void
-set_window_size (int rows, int cols)
+set_window_size (int rows, int cols, int fd, const char *device)
{
struct winsize win;

- if (get_win_size (STDIN_FILENO, &win))
+ if (get_win_size (fd, &win))
{
if (errno != EINVAL)
- error (1, errno, _("standard input"));
+ error (1, errno, device);
memset (&win, 0, sizeof (win));
}

@@ -1249,28 +1344,28 @@
win.ws_row = 1;
win.ws_col = 1;

- if (ioctl (STDIN_FILENO, TIOCSWINSZ, (char *) &win))
- error (1, errno, _("standard input"));
+ if (ioctl (fd, TIOCSWINSZ, (char *) &win))
+ error (1, errno, device);

- if (ioctl (STDIN_FILENO, TIOCSSIZE, (char *) &ttysz))
- error (1, errno, _("standard input"));
+ if (ioctl (fd, TIOCSSIZE, (char *) &ttysz))
+ error (1, errno, device);
return;
}
# endif

- if (ioctl (STDIN_FILENO, TIOCSWINSZ, (char *) &win))
- error (1, errno, _("standard input"));
+ if (ioctl (fd, TIOCSWINSZ, (char *) &win))
+ error (1, errno, device);
}

static void
-display_window_size (int fancy)
+display_window_size (int fancy, int fd, const char *device)
{
struct winsize win;

- if (get_win_size (STDIN_FILENO, &win))
+ if (get_win_size (fd, &win))
{
if (errno != EINVAL)
- error (1, errno, _("standard input"));
+ error (1, errno, device);
if (!fancy)
error (1, 0, _("no size information for this device"));
}
@@ -1331,7 +1426,8 @@
}

static void
-display_settings (enum output_type output_type, struct termios *mode)
+display_settings (enum output_type output_type, struct termios *mode,
+ int fd, const char *device)
{
switch (output_type)
{
@@ -1340,7 +1436,7 @@
break;

case all:
- display_all (mode);
+ display_all (mode, fd, device);
break;

case recoverable:
@@ -1434,7 +1530,7 @@
}

static void
-display_all (struct termios *mode)
+display_all (struct termios *mode, int fd, const char *device)
{
int i;
tcflag_t *bitsp;
@@ -1443,7 +1539,7 @@

display_speed (mode, 1);
#ifdef TIOCGWINSZ
- display_window_size (1);
+ display_window_size (1, fd, device);
#endif
#ifdef HAVE_C_LINE
wrapf ("line = %d;", mode->c_line);
@@ -1590,6 +1686,12 @@
#endif
#ifdef B115200
{"115200", B115200, 115200},
+#endif
+#ifdef B230400
+ {"230400", B230400, 230400},
+#endif
+#ifdef B460800
+ {"460800", B460800, 460800},
#endif
{NULL, 0, 0}
};
===================================================================
RCS file: man/RCS/stty.1,v
retrieving revision 1.1
diff -u -r1.1 man/stty.1
--- man/stty.1 1998/04/01 09:26:14 1.1
+++ man/stty.1 1998/04/01 18:14:16
@@ -2,10 +2,12 @@
.SH NAME
stty \- change and print terminal line settings
.SH SYNOPSIS
-.B stty
+.B stty
+[-F device] [--file=device]
[setting...]
.br
.B stty
+[-F device] [--file=device]
{\-a,\-\-all,\-g,\-\-help,\-\-save,\-\-version}
.SH DESCRIPTION
This documentation is no longer being maintained and may be inaccurate
@@ -271,14 +273,17 @@
.I "\-a, \-\-all"
Print all current settings in human-readable form.
.TP
-.I "\-\-help"
-Print a usage message on standard output and exit successfully.
+.I "\-F, \-\-file device"
+Open and use the specified device instead of stdin.
.TP
.I "\-g, \-\-save"
Print all current settings in a form that can be used as an argument
to another
.B stty
command to restore the current settings.
+.I "\-\-help"
+Print a usage message on standard output and exit successfully.
+.TP
.TP
.I "\-\-version"
Print version information on standard output then exit successfully.
===================================================================
RCS file: doc/sh-utils.texi,v
retrieving revision 1.1
diff -u -r1.1 doc/sh-utils.texi
--- doc/sh-utils.texi 1998/04/01 14:29:18 1.1
+++ doc/sh-utils.texi 1998/04/01 18:14:36
@@ -1109,15 +1109,16 @@
Synopses:

@example
-stty [@var{setting}]@dots{}
+stty [@var{option}] [@var{setting}]@dots{}
stty [@var{option}]
@end example

-If given no arguments, @code{stty} prints the baud rate, line
+If given no line settings, @code{stty} prints the baud rate, line
discipline number (on systems that support it), and line settings
that have been changed from the values set by @samp{stty sane}.
-Mode reading and setting are performed on the tty line connected to
-standard input.
+By default, mode reading and setting are performed on the tty line
+connected to standard input, although this can be modified by the
+@samp{--file} option.

@code{stty} accepts many non-option arguments that change aspects of
the terminal line operation, as described below.
@@ -1129,7 +1130,19 @@
@itemx --all
@opindex -a
@opindex --all
-Print all current settings in human-readable form.
+Print all current settings in human-readable form. This option may not
+be used in combination with any line settings.
+
+@item -F @var{device}
+@itemx --file @var{device}
+@opindex -F
+@opindex --file
+Set the line opened by the filename specified in @var{device} instead of
+the tty line connected to standard input. This option is necessary
+because opening a POSIX tty requires the @code{O_NONDELAY} flag to prevent
+a POSIX tty from blocking until the carrier detect line is high if
+the @code{clocal} flag is not set. Hence, it is not possible to allow
+the shell to open the device in the traditional manner.

@item -g
@itemx --save
@@ -1137,7 +1150,8 @@
@opindex --save
@cindex machine-readable @code{stty} output
Print all current settings in a form that can be used as an argument to
-another @code{stty} command to restore the current settings.
+another @code{stty} command to restore the current settings. This option
+may not be used in combination with any line settings.

@end table

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/