On 01/09/2026 04:54, H. Peter Anvin wrote:
eq_mode (), which is used to determine if tcsetattr () was successful,
failed unnecessarily after speed setting under certain circumstances:

    1. the kernel MAY round the speed to the nearest supported
       value, so allow a variation of +/- 1/4 bit time per character from
       what was originally requested.

    2. There MAY be more than one internal binary encoding of a specific
       speed (e.g. on Linux.) On systems where that information is
       affects c_cflag, this may introduce inconsistencies; filter out
       the known speed-related flags in c_cflag.

    3. It is undefined in POSIX if cfsetispeed (..., 0) is retained or
       normalized across tcsetattr () ... tcgetattr (). */ If
       cfgetispeed () returns 0, compare the output speed instead.

Signed-off-by: H. Peter Anvin <[email protected]>
---
  src/stty.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++--------
  1 file changed, 77 insertions(+), 12 deletions(-)

diff --git a/src/stty.c b/src/stty.c
index d2de216980f7..26432b74e374 100644
--- a/src/stty.c
+++ b/src/stty.c
@@ -439,7 +439,7 @@ static bool recover_mode (char const *arg, struct termios 
*mode);
  static int screen_columns (void);
  static bool set_mode (struct mode_info const *info, bool reversed,
                        struct termios *mode);
-static bool eq_mode (struct termios *mode1, struct termios *mode2);
+static bool eq_mode (const struct termios *mode1, const struct termios *mode2);
  static uintmax_t integer_arg (char const *s, uintmax_t max);
  static speed_t string_to_baud (char const *arg);
  static tcflag_t *mode_type_flag (enum mode_type type, struct termios *mode);
@@ -1436,7 +1436,7 @@ main (int argc, char **argv)
           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.  */
+         compare them to the requested ones. */
if (tcgetattr (STDIN_FILENO, &new_mode))
          error (EXIT_FAILURE, errno, "%s", quotef (device_name));
@@ -1464,21 +1464,86 @@ main (int argc, char **argv)
    return EXIT_SUCCESS;
  }
-/* Return true if modes are equivalent. */
+/* Return true if modes are equivalent.
+
+   However, the speed detection is a bit finicky:
+
+   1. the kernel MAY round the speed to the nearest supported
+      value, so allow a variation of +/- 1/4 bit time per character from
+      what was originally requested.
+
+   2. There MAY be more than one internal binary encoding of a specific
+      speed (e.g. on Linux.) On systems where that information is
+      affects c_cflag, this may introduce inconsistencies.
+
+   3. It is undefined in POSIX if cfsetispeed (..., 0) is retained or
+      normalized across tcsetattr () ... tcgetattr (). */
static bool
-eq_mode (struct termios *mode1, struct termios *mode2)
+eq_mode (const struct termios *mode1, const struct termios *mode2)
  {
-  return mode1->c_iflag == mode2->c_iflag
-      && mode1->c_oflag == mode2->c_oflag
-      && mode1->c_cflag == mode2->c_cflag
-      && mode1->c_lflag == mode2->c_lflag
+  const struct termios * const mode[2] = { mode1, mode2 };
+  unsigned long speed[2][2];
+  tcflag_t cflag_mask;
+  unsigned int bits;
+  int i;
+
+  if (mode1->c_oflag != mode2->c_oflag
+      || mode1->c_lflag != mode2->c_lflag
  #ifdef HAVE_C_LINE
-      && mode1->c_line == mode2->c_line
+      || mode1->c_line != mode2->c_line
  #endif
-      && memeq (mode1->c_cc, mode2->c_cc, sizeof (mode1->c_cc))
-      && cfgetispeed (mode1) == cfgetispeed (mode2)
-      && cfgetospeed (mode1) == cfgetospeed (mode2);
+      || !memeq (mode1->c_cc, mode2->c_cc, sizeof (mode1->c_cc)))
+    return false;
+
+  /* Strip known baud-related flags from c_cflag before comparison */
+  cflag_mask = ~(tcflag_t)0;
+#ifdef CBAUD
+  cflag_mask &= ~CBAUD;
+#endif
+#ifdef CIBAUD
+  cflag_mask &= ~CIBAUD;
+#endif
+
+  if ((mode1->c_cflag ^ mode2->c_cflag) & cflag_mask)
+    return false;
+
+  bits = (mode1->c_cflag & CSTOPB) ? 3 : 2; /* Start and stop bits */
+  switch (mode1->c_cflag & CSIZE) {
+  case CS5:
+    bits += 5;
+    break;
+  case CS6:
+    bits += 6;
+    break;
+  case CS7:
+    bits += 7;
+    break;
+  default:
+    bits += 8;
+    break;
+  }
+
+  for (i = 0; i < 2; i++)
+    {
+      speed_t ospeed = cfgetospeed (mode[i]);
+      speed_t ispeed = cfgetispeed (mode[i]);
+      speed[i][0] = baud_to_value (ospeed);
+      speed[i][1] = baud_to_value (ispeed ? ispeed : ospeed);
+    }
+
+  for (i = 0; i < 2; i++)
+    {
+      unsigned long s1 = speed[0][i];
+      unsigned long s2 = speed[1][i];
+      unsigned long delta = (s1 < s2) ? s2 - s1 : s1 - s2;
+
+      /* s1 is assumed to be the *desired* value, s2 the *actual* value */
+      if (delta > s1/(bits * 4))
+       return false;
+    }
+
+  return true;
  }
/* Return false if not applied because not reversible; otherwise

This dropped comparison of c_iflag.
Also the parity bit wasn't included in the number of bits.
Also baud_to_value() is not guaranteed to match and can return ULONG_MAX.

Is it ok to merge the attached changes into your patch?

thanks,
Padraig

commit 33e4668cd67684825655c7d705cdc8497ff11b9f
Author: Pádraig Brady <[email protected]>
Date:   Tue Sep 1 13:56:17 2026 +0100

    stty: fixups
    
    TODO

diff --git a/src/stty.c b/src/stty.c
index 26432b74e..394078383 100644
--- a/src/stty.c
+++ b/src/stty.c
@@ -439,7 +439,10 @@ static bool recover_mode (char const *arg, struct termios *mode);
 static int screen_columns (void);
 static bool set_mode (struct mode_info const *info, bool reversed,
                       struct termios *mode);
-static bool eq_mode (const struct termios *mode1, const struct termios *mode2);
+static bool eq_mode (struct termios const *mode1,
+                     struct termios const *mode2);
+static bool speed_matches (struct termios const *mode1,
+                           struct termios const *mode2);
 static uintmax_t integer_arg (char const *s, uintmax_t max);
 static speed_t string_to_baud (char const *arg);
 static tcflag_t *mode_type_flag (enum mode_type type, struct termios *mode);
@@ -1436,7 +1439,7 @@ main (int argc, char **argv)
          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. */
+         compare them to the requested ones.  */
 
       if (tcgetattr (STDIN_FILENO, &new_mode))
         error (EXIT_FAILURE, errno, "%s", quotef (device_name));
@@ -1473,22 +1476,18 @@ main (int argc, char **argv)
       what was originally requested.
 
    2. There MAY be more than one internal binary encoding of a specific
-      speed (e.g. on Linux.) On systems where that information is
+      speed (e.g. on Linux).  On systems where that information
       affects c_cflag, this may introduce inconsistencies.
 
-   3. It is undefined in POSIX if cfsetispeed (..., 0) is retained or
-      normalized across tcsetattr () ... tcgetattr (). */
-
+   3. An input speed of zero may be retained or normalized to the output
+      speed across tcsetattr () ... tcgetattr ().  */
 static bool
-eq_mode (const struct termios *mode1, const struct termios *mode2)
+eq_mode (struct termios const *mode1, struct termios const *mode2)
 {
-  const struct termios * const mode[2] = { mode1, mode2 };
-  unsigned long speed[2][2];
   tcflag_t cflag_mask;
-  unsigned int bits;
-  int i;
 
-  if (mode1->c_oflag != mode2->c_oflag
+  if (mode1->c_iflag != mode2->c_iflag
+      || mode1->c_oflag != mode2->c_oflag
       || mode1->c_lflag != mode2->c_lflag
 #ifdef HAVE_C_LINE
       || mode1->c_line != mode2->c_line
@@ -1496,54 +1495,19 @@ eq_mode (const struct termios *mode1, const struct termios *mode2)
       || !memeq (mode1->c_cc, mode2->c_cc, sizeof (mode1->c_cc)))
     return false;
 
-  /* Strip known baud-related flags from c_cflag before comparison */
-  cflag_mask = ~(tcflag_t)0;
+  /* Strip known baud-related flags from c_cflag before comparison.  */
+  cflag_mask = ~(tcflag_t) 0;
 #ifdef CBAUD
-  cflag_mask &= ~CBAUD;
+  cflag_mask &= ~(tcflag_t) CBAUD;
 #endif
 #ifdef CIBAUD
-  cflag_mask &= ~CIBAUD;
+  cflag_mask &= ~(tcflag_t) CIBAUD;
 #endif
 
   if ((mode1->c_cflag ^ mode2->c_cflag) & cflag_mask)
     return false;
 
-  bits = (mode1->c_cflag & CSTOPB) ? 3 : 2; /* Start and stop bits */
-  switch (mode1->c_cflag & CSIZE) {
-  case CS5:
-    bits += 5;
-    break;
-  case CS6:
-    bits += 6;
-    break;
-  case CS7:
-    bits += 7;
-    break;
-  default:
-    bits += 8;
-    break;
-  }
-
-  for (i = 0; i < 2; i++)
-    {
-      speed_t ospeed = cfgetospeed (mode[i]);
-      speed_t ispeed = cfgetispeed (mode[i]);
-      speed[i][0] = baud_to_value (ospeed);
-      speed[i][1] = baud_to_value (ispeed ? ispeed : ospeed);
-    }
-
-  for (i = 0; i < 2; i++)
-    {
-      unsigned long s1 = speed[0][i];
-      unsigned long s2 = speed[1][i];
-      unsigned long delta = (s1 < s2) ? s2 - s1 : s1 - s2;
-
-      /* s1 is assumed to be the *desired* value, s2 the *actual* value */
-      if (delta > s1/(bits * 4))
-	return false;
-    }
-
-  return true;
+  return speed_matches (mode1, mode2);
 }
 
 /* Return false if not applied because not reversible; otherwise
@@ -2238,6 +2202,64 @@ recover_mode (char const *arg, struct termios *mode)
 /* 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)
+{
+  speed_t speed1[2] = { cfgetospeed (mode1), cfgetispeed (mode1) };
+  speed_t speed2[2] = { cfgetospeed (mode2), cfgetispeed (mode2) };
+  if (speed1[1] == 0)
+    speed1[1] = speed1[0];
+  if (speed2[1] == 0)
+    speed2[1] = speed2[0];
+
+  unsigned int bits = 2;  /* Start and stop bits.  */
+  bits += !!(mode1->c_cflag & CSTOPB);  /* Extra stop bit.  */
+  bits += !!(mode1->c_cflag & PARENB);  /* Parity bit.  */
+
+  /* Data bits.  */
+  switch (mode1->c_cflag & CSIZE)
+    {
+    case CS5:
+      bits += 5;
+      break;
+    case CS6:
+      bits += 6;
+      break;
+    case CS7:
+      bits += 7;
+      break;
+    default:
+      bits += 8;
+      break;
+    }
+
+  for (int i = 0; i < 2; i++)
+    {
+      unsigned long int value1;
+      unsigned long int value2;
+      unsigned long int delta;
+
+      if (speed1[i] == speed2[i])
+        continue;
+
+      value1 = baud_to_value (speed1[i]);
+      value2 = baud_to_value (speed2[i]);
+
+#ifndef TERMIOS_SPEED_T_SANE
+      /* baud_to_value uses ULONG_MAX for an unrecognized encoding.  */
+      if (value1 == ULONG_MAX || value2 == ULONG_MAX)
+        return false;
+#endif
+
+      delta = value1 < value2 ? value2 - value1 : value1 - value2;
+      if (delta > value1 / (bits * 4))
+        return false;
+    }
+
+  return true;
+}
+
 ATTRIBUTE_PURE
 static speed_t
 string_to_baud (char const *arg)

Reply via email to