local-tcsetaddr
authorGNU Libc Maintainers <debian-glibc@lists.debian.org>
Fri, 10 Jan 2020 22:21:25 +0000 (22:21 +0000)
committerAurelien Jarno <aurel32@debian.org>
Fri, 10 Jan 2020 22:21:25 +0000 (22:21 +0000)
# 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

# 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

Gbp-Pq: Topic any
Gbp-Pq: Name local-tcsetaddr.diff

sysdeps/unix/sysv/linux/tcsetattr.c

index 3fb474ed633941bbf1516f3dcf9ade570c952e81..1f690433ebe7c3fa109220f091c8ebcbb502f32a 100644 (file)
@@ -44,7 +44,12 @@ int
 __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 @@ __tcsetattr (int fd, int optional_actions, const struct termios *termios_p)
   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)