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
--
2.55.0