stty current fails like:
$ stty 9600
$ saved=$(stty -g)
$ stty 19200 "$saved" # Sets 9600 but reports failure
stty: 'standard input': unable to perform all requested operations
Whereas these work fine:
$ stty 19200 9600
$ stty "$saved" 19200
* NEWS: Mention the bug fix.
* src/speedgen (cbaud_to_speed): Determine the speed
from the control flag encoding.
* src/stty.c (recover_mode): If no separate speed specified,
update the speed from the specified control flags.
* tests/stty/stty-save-speed.sh: Add test cases.
---
NEWS | 6 ++++++
src/speedgen | 32 ++++++++++++++++++++++++++++++++
src/stty.c | 28 ++++++++++++++++++++++++----
tests/stty/stty-save-speed.sh | 29 +++++++++++++++++++++++++++++
4 files changed, 91 insertions(+), 4 deletions(-)
diff --git a/NEWS b/NEWS
index 49c2b29d2..cffa24780 100644
--- a/NEWS
+++ b/NEWS
@@ -38,6 +38,12 @@ GNU coreutils NEWS -*-
outline -*-
GNU/Hurd with glibc-2.42 or newer.
[bug introduced with arbitrary baud rate support in coreutils-9.8]
+ 'stty' consistently supports restoring the last specified speed.
+ Previously on GNU/Linux and GNU/Hurd with glibc-2.42 or newer,
+ and restoring a saved speed after a specified but different speed,
+ it would have failed if the control flag encoding was different.
+ [This bug was present in "the beginning".]
+
'unexpand' no longer overflows a heap buffer on non-GLIBC platforms,
given whitespace characters whose display width is negative. E.g.,
this edge case can occur with \u0085 (next line) characters on musl.
diff --git a/src/speedgen b/src/speedgen
index 51ea80ec5..2629c0baa 100755
--- a/src/speedgen
+++ b/src/speedgen
@@ -100,5 +100,37 @@ cat >> "$out" <<'EOF'
# endif
}
+#if defined CBAUD && defined TERMIOS_SPEED_T_SANE
+/* Decode baud bits without assuming their platform-specific encoding. */
+static struct speed_desc
+cbaud_to_speed (tcflag_t cbaud)
+{
+ static speed_t const speeds[] =
+ {
+EOF
+
+while read n; do
+ printf '# ifdef B%s\n B%s,\n# endif\n' "$n" "$n"
+done < "$tmp" >> "$out"
+
+cat >> "$out" <<'EOF'
+ };
+
+# ifdef BOTHER
+ if (cbaud == BOTHER)
+ return (struct speed_desc) { .valid = false };
+# endif
+
+ for (idx_t i = 0; i < countof (speeds); i++)
+ {
+ struct termios mode = { 0 };
+ if (cfsetospeed (&mode, speeds[i]) == 0
+ && (mode.c_cflag & CBAUD) == cbaud)
+ return (struct speed_desc) { .baud = speeds[i], .valid = true };
+ }
+ return (struct speed_desc) { .valid = false };
+}
+#endif
+
#endif
EOF
diff --git a/src/stty.c b/src/stty.c
index b02bcc457..2c801b298 100644
--- a/src/stty.c
+++ b/src/stty.c
@@ -2196,6 +2196,9 @@ STRTOUL_TYPE (cc_t)
STRTOUL_TYPE (speed_t)
#undef STRTOUL_TYPE
+/* Autogenerated conversion functions to/from speed_t */
+#include "speedlist.h"
+
/* Parse the output of display_recoverable.
Return false if any part of it is invalid. */
static bool
@@ -2223,7 +2226,27 @@ recover_mode (char const *arg, struct termios *mode)
return false;
/* Accept the old format, which has no separate speed fields. */
if (! *p)
- return true;
+ {
+#ifdef CBAUD
+ /* Some systems keep numeric speeds separately even when the
+ control flags encode them. Synchronize those speeds after
+ restoring the flags, leaving BOTHER speeds unchanged. */
+# ifdef TERMIOS_SPEED_T_SANE
+ struct speed_desc ospeed = cbaud_to_speed (mode->c_cflag & CBAUD);
+ if (ospeed.valid && cfsetospeed (mode, ospeed.baud) != 0)
+ return false;
+# if defined CIBAUD && defined IBSHIFT
+ struct speed_desc ispeed =
+ cbaud_to_speed ((mode->c_cflag & CIBAUD) >> IBSHIFT);
+ if (ispeed.valid && cfsetispeed (mode, ispeed.baud) != 0)
+ return false;
+# endif
+# endif
+ last_ibaud.baud = cfgetispeed (mode);
+ last_obaud.baud = cfgetospeed (mode);
+#endif
+ return true;
+ }
s = p + 1;
}
@@ -2248,9 +2271,6 @@ recover_mode (char const *arg, struct termios *mode)
return true;
}
-/* Autogenerated conversion functions to/from speed_t */
-#include "speedlist.h"
-
/* Return true if MODE1 and MODE2 have equivalent speeds. */
static bool
speed_matches (struct termios const *mode1, struct termios const *mode2)
diff --git a/tests/stty/stty-save-speed.sh b/tests/stty/stty-save-speed.sh
index 202500849..0469c1ca4 100755
--- a/tests/stty/stty-save-speed.sh
+++ b/tests/stty/stty-save-speed.sh
@@ -44,6 +44,26 @@ state=$(stty -g) || framework_failure_
test "$(echo "$state" | tr -cd : | wc -c)" = "$fields" || fail=1
stty "$state" || fail=1
+# Restore standard speeds after changing them, including in one invocation.
+for prefix in '' 19200; do
+ stty 19200 || fail=1
+ stty $prefix "$state9600" || fail=1
+ stty speed > out || fail=1
+ echo 9600 > exp || framework_failure_
+ compare exp out || fail=1
+done
+
+# Restore distinct standard input and output speeds where supported.
+if stty ospeed 9600 ispeed 19200 2>/dev/null; then
+ state=$(stty -g) || framework_failure_
+ stty speed > exp || fail=1
+ stty 9600 || fail=1
+ stty "$state" || fail=1
+ stty speed > out || fail=1
+ compare exp out || fail=1
+fi
+stty 19200 || fail=1
+
# On BSD, verify standard rates are restored after a speed change too.
# Retain an old-format string to test backward compatibility below.
legacy=$state9600
@@ -75,6 +95,15 @@ for speeds in '51 51' '51 9600' '9600 51'; do
stty speed > out || fail=1
compare exp out || fail=1
+ # Restore a standard-speed state directly after an arbitrary speed.
+ stty "$state9600" || fail=1
+ stty speed > out || fail=1
+ echo 9600 > exp-standard || framework_failure_
+ compare exp-standard out || fail=1
+ stty "$state" "$state9600" || fail=1
+ stty speed > out || fail=1
+ compare exp-standard out || fail=1
+
# The last specified speed takes precedence
stty 19200 "$state" || fail=1
stty speed > out || fail=1
--
2.55.0