Package: glibc / 2.41-10

any/local-tcsetaddr.diff Patch series | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# All lines beginning with `# DP:' are a description of the patch.
# DP: Description: tcsetattr sanity check on PARENB/CREAD/CSIZE for ptys
# DP: Related bugs: 218131
# DP: Author: Jeff Licquia <licquia@progeny.com>
# DP: Upstream status: [In CVS | Debian-Specific | Pending | Not submitted ]
# DP: Status Details: 
# DP: Date: 2003-10-29

---
 sysdeps/unix/sysv/linux/tcsetattr.c |   55 +++++++++++++++++++++++++++++++++++-
 1 file changed, 54 insertions(+), 1 deletion(-)

--- a/sysdeps/unix/sysv/linux/tcsetattr.c
+++ b/sysdeps/unix/sysv/linux/tcsetattr.c
@@ -44,7 +44,12 @@
 __tcsetattr (int fd, int optional_actions, const struct termios *termios_p)
 {
   struct __kernel_termios k_termios;
+  struct __kernel_termios k_termios_old;
   unsigned long int cmd;
+  int retval, old_retval;
+
+  /* Preserve the previous termios state if we can. */
+  old_retval = INLINE_SYSCALL (ioctl, 3, fd, TCGETS, &k_termios_old);
 
   switch (optional_actions)
     {
@@ -75,7 +80,55 @@
   memcpy (&k_termios.c_cc[0], &termios_p->c_cc[0],
 	  __KERNEL_NCCS * sizeof (cc_t));
 
-  return INLINE_SYSCALL (ioctl, 3, fd, cmd, &k_termios);
+  retval = INLINE_SYSCALL (ioctl, 3, fd, cmd, &k_termios);
+
+  /* The Linux kernel silently ignores the invalid c_cflag on pty.
+     We have to check it here, and return an error.  But if some other
+     setting was successfully changed, POSIX requires us to report
+     success. */
+  if ((retval == 0) && (old_retval == 0))
+    {
+      int save = errno;
+      retval = INLINE_SYSCALL (ioctl, 3, fd, TCGETS, &k_termios);
+      if (retval)
+	{
+	  /* We cannot verify if the setting is ok. We don't return
+	     an error (?). */
+	  __set_errno (save);
+	  retval = 0;
+	}
+      else if ((k_termios_old.c_oflag != k_termios.c_oflag) ||
+	       (k_termios_old.c_lflag != k_termios.c_lflag) ||
+	       (k_termios_old.c_line != k_termios.c_line) ||
+	       ((k_termios_old.c_iflag | IBAUD0) != (k_termios.c_iflag | IBAUD0)))
+	{
+	  /* Some other setting was successfully changed, which
+	     means we should not return an error. */
+	  __set_errno (save);
+	  retval = 0;
+	}
+      else if ((k_termios_old.c_cflag & ~(PARENB | CREAD | CSIZE)) !=
+	       (k_termios.c_cflag & ~(PARENB | CREAD | CSIZE)))
+	{
+	  /* Some other c_cflag setting was successfully changed, which
+	     means we should not return an error. */
+	  __set_errno (save);
+	  retval = 0;
+	}
+      else if ((termios_p->c_cflag & (PARENB | CREAD))
+			!= (k_termios.c_cflag & (PARENB | CREAD))
+	       || ((termios_p->c_cflag & CSIZE)
+		   && (termios_p->c_cflag & CSIZE)
+			!= (k_termios.c_cflag & CSIZE)))
+	{
+	  /* It looks like the Linux kernel silently changed the
+	     PARENB/CREAD/CSIZE bits in c_cflag. Report it as an
+	     error. */
+	  __set_errno (EINVAL);
+	  retval = -1;
+	}
+    }
+   return retval;
 }
 weak_alias (__tcsetattr, tcsetattr)
 libc_hidden_def (tcsetattr)