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)