<termios.h>: add two new POSIX functions.

musl already added tcgetwinsize() and tcsetwinsize(), but I didn't
notice.

Trivial single-line inlines added to a header that's already written
that way.

Test: treehugger
Change-Id: Iac95ea6a89f3872025c512f7e61987b81d0aafa7
diff --git a/docs/status.md b/docs/status.md
index de2fa10..ad7e1c5 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -56,6 +56,7 @@
 Current libc symbols: https://android.googlesource.com/platform/bionic/+/master/libc/libc.map.txt
 
 New libc functions in V (API level 35):
+  * `tcgetwinsize`, `tcsetwinsize` (POSIX Issue 8 additions).
   * `timespec_getres` (C23 addition).
   * `localtime_rz`, `mktime_z`, `tzalloc`, and `tzfree` (NetBSD
     extensions implemented in tzcode, and the "least non-standard"
diff --git a/libc/bionic/termios.cpp b/libc/bionic/termios.cpp
index 5fe8eb0..57b34b7 100644
--- a/libc/bionic/termios.cpp
+++ b/libc/bionic/termios.cpp
@@ -34,6 +34,10 @@
 #define __BIONIC_TERMIOS_INLINE /* Out of line. */
 #include <bits/termios_inlines.h>
 
+// POSIX added a couple more functions much later, so do the same for them.
+#define __BIONIC_TERMIOS_WINSIZE_INLINE /* Out of line. */
+#include <bits/termios_winsize_inlines.h>
+
 // Actually declared in <unistd.h>, present on all API levels.
 pid_t tcgetpgrp(int fd) {
   pid_t pid;
diff --git a/libc/include/android/legacy_termios_inlines.h b/libc/include/android/legacy_termios_inlines.h
index 6222786..a816b40 100644
--- a/libc/include/android/legacy_termios_inlines.h
+++ b/libc/include/android/legacy_termios_inlines.h
@@ -43,3 +43,10 @@
 #include <bits/termios_inlines.h>
 
 #endif
+
+#if __ANDROID_API__ < 35
+
+#define __BIONIC_TERMIOS_WINSIZE_INLINE static __inline
+#include <bits/termios_winsize_inlines.h>
+
+#endif
diff --git a/libc/include/bits/termios_winsize_inlines.h b/libc/include/bits/termios_winsize_inlines.h
new file mode 100644
index 0000000..ae246e4
--- /dev/null
+++ b/libc/include/bits/termios_winsize_inlines.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <errno.h>
+#include <sys/cdefs.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+#include <linux/termios.h>
+
+#if !defined(__BIONIC_TERMIOS_WINSIZE_INLINE)
+#define __BIONIC_TERMIOS_WINSIZE_INLINE static __inline
+#endif
+
+__BEGIN_DECLS
+
+__BIONIC_TERMIOS_WINSIZE_INLINE int tcgetwinsize(int __fd, struct winsize* _Nonnull __size) {
+  return ioctl(__fd, TIOCGWINSZ, __size);
+}
+
+__BIONIC_TERMIOS_WINSIZE_INLINE int tcsetwinsize(int __fd, const struct winsize* _Nonnull __size) {
+  return ioctl(__fd, TIOCSWINSZ, __size);
+}
+
+__END_DECLS
diff --git a/libc/include/termios.h b/libc/include/termios.h
index 853b4eb..7abff5d 100644
--- a/libc/include/termios.h
+++ b/libc/include/termios.h
@@ -149,6 +149,25 @@
 
 #endif
 
+#if __ANDROID_API__ >= 35
+// These two functions were POSIX Issue 8 additions that we can also trivially
+// implement as inlines for older OS version.
+
+/**
+ * tcgetwinsize(3) gets the window size of the given terminal.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
+int tcgetwinsize(int __fd, struct winsize* _Nonnull __size);
+
+/**
+ * tcsetwinsize(3) sets the window size of the given terminal.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
+int tcsetwinsize(int __fd, const struct winsize* _Nonnull __size);
+#endif
+
 __END_DECLS
 
 #include <android/legacy_termios_inlines.h>
diff --git a/libc/libc.map.txt b/libc/libc.map.txt
index e90618d..b3ef185 100644
--- a/libc/libc.map.txt
+++ b/libc/libc.map.txt
@@ -1591,6 +1591,8 @@
     mktime_z;
     __riscv_flush_icache; # riscv64
     __riscv_hwprobe; # riscv64
+    tcgetwinsize;
+    tcsetwinsize;
     timespec_getres;
     tzalloc;
     tzfree;
diff --git a/tests/headers/posix/termios_h.c b/tests/headers/posix/termios_h.c
index 1255c16..0a67eaa 100644
--- a/tests/headers/posix/termios_h.c
+++ b/tests/headers/posix/termios_h.c
@@ -42,6 +42,12 @@
   STRUCT_MEMBER(struct termios, tcflag_t, c_lflag);
   STRUCT_MEMBER_ARRAY(struct termios, cc_t/*[]*/, c_cc);
 
+#if !defined(__GLIBC__)  // Our glibc is too old.
+  TYPE(struct winsize);
+  STRUCT_MEMBER(struct winsize, unsigned short, ws_row);
+  STRUCT_MEMBER(struct winsize, unsigned short, ws_col);
+#endif
+
   MACRO(NCCS);
 
   MACRO(VEOF);
@@ -162,6 +168,12 @@
   FUNCTION(tcflush, int (*f)(int, int));
   FUNCTION(tcgetattr, int (*f)(int, struct termios*));
   FUNCTION(tcgetsid, pid_t (*f)(int));
+#if !defined(__GLIBC__)  // Our glibc is too old.
+  FUNCTION(tcgetwinsize, int (*f)(int, struct winsize*));
+#endif
   FUNCTION(tcsendbreak, int (*f)(int, int));
   FUNCTION(tcsetattr, int (*f)(int, int, const struct termios*));
+#if !defined(__GLIBC__)  // Our glibc is too old.
+  FUNCTION(tcsetwinsize, int (*f)(int, const struct winsize*));
+#endif
 }
diff --git a/tests/termios_test.cpp b/tests/termios_test.cpp
index 6290771..a8d5890 100644
--- a/tests/termios_test.cpp
+++ b/tests/termios_test.cpp
@@ -29,6 +29,8 @@
 #include <termios.h>
 
 #include <errno.h>
+#include <fcntl.h>
+#include <pty.h>
 
 #include <gtest/gtest.h>
 
@@ -96,3 +98,49 @@
   EXPECT_EQ(1, t.c_cc[VMIN]);
   EXPECT_EQ(0, t.c_cc[VTIME]);
 }
+
+TEST(termios, tcgetwinsize_tcsetwinsize_invalid) {
+#if !defined(__GLIBC__)
+  winsize ws = {};
+
+  errno = 0;
+  ASSERT_EQ(-1, tcgetwinsize(-1, &ws));
+  ASSERT_EQ(EBADF, errno);
+
+  errno = 0;
+  ASSERT_EQ(-1, tcsetwinsize(-1, &ws));
+  ASSERT_EQ(EBADF, errno);
+#else
+  GTEST_SKIP() << "glibc too old";
+#endif
+}
+
+TEST(termios, tcgetwinsize_tcsetwinsize) {
+#if !defined(__GLIBC__)
+  int pty, tty;
+  winsize ws = {123, 456, 9999, 9999};
+  ASSERT_EQ(0, openpty(&pty, &tty, nullptr, nullptr, &ws));
+
+  winsize actual = {};
+  ASSERT_EQ(0, tcgetwinsize(tty, &actual));
+  EXPECT_EQ(ws.ws_xpixel, actual.ws_xpixel);
+  EXPECT_EQ(ws.ws_ypixel, actual.ws_ypixel);
+  EXPECT_EQ(ws.ws_row, actual.ws_row);
+  EXPECT_EQ(ws.ws_col, actual.ws_col);
+
+  ws = {1, 2, 3, 4};
+  ASSERT_EQ(0, tcsetwinsize(tty, &ws));
+
+  actual = {};
+  ASSERT_EQ(0, tcgetwinsize(tty, &actual));
+  EXPECT_EQ(ws.ws_xpixel, actual.ws_xpixel);
+  EXPECT_EQ(ws.ws_ypixel, actual.ws_ypixel);
+  EXPECT_EQ(ws.ws_row, actual.ws_row);
+  EXPECT_EQ(ws.ws_col, actual.ws_col);
+
+  close(pty);
+  close(tty);
+#else
+  GTEST_SKIP() << "glibc too old";
+#endif
+}