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


Reply via email to