[libc] add isatty
authorMichael Jones <michaelrj@google.com>
Mon, 10 Oct 2022 21:37:21 +0000 (14:37 -0700)
committerMichael Jones <michaelrj@google.com>
Mon, 10 Oct 2022 22:20:46 +0000 (15:20 -0700)
The isatty function uses the side effects of an ioctl call to determine
if a specific file descriptor is a terminal. I chose TIOCGETD (get line
discipline of terminal) because it didn't require any new structs.

Reviewed By: sivachandra, lntue

Differential Revision: https://reviews.llvm.org/D135618

15 files changed:
libc/config/linux/x86_64/entrypoints.txt
libc/config/linux/x86_64/headers.txt
libc/include/CMakeLists.txt
libc/include/llvm-libc-macros/CMakeLists.txt
libc/include/llvm-libc-macros/linux/CMakeLists.txt
libc/include/llvm-libc-macros/linux/sys-ioctl-macros.h [new file with mode: 0644]
libc/include/llvm-libc-macros/sys-ioctl-macros.h [new file with mode: 0644]
libc/include/sys/ioctl.h.def [new file with mode: 0644]
libc/spec/posix.td
libc/src/unistd/CMakeLists.txt
libc/src/unistd/isatty.h [new file with mode: 0644]
libc/src/unistd/linux/CMakeLists.txt
libc/src/unistd/linux/isatty.cpp [new file with mode: 0644]
libc/test/src/unistd/CMakeLists.txt
libc/test/src/unistd/isatty_test.cpp [new file with mode: 0644]

index 32ceb8f..2a2a031 100644 (file)
@@ -156,6 +156,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.unistd.getpid
     libc.src.unistd.getppid
     libc.src.unistd.getuid
+    libc.src.unistd.isatty
     libc.src.unistd.link
     libc.src.unistd.linkat
     libc.src.unistd.lseek
index 284f964..a745bcc 100644 (file)
@@ -11,6 +11,7 @@ set(TARGET_PUBLIC_HEADERS
     libc.include.stdio
     libc.include.stdlib
     libc.include.string
+    libc.include.sys_ioctl
     libc.include.sys_mman
     libc.include.sys_random
     libc.include.sys_syscall
index 4df498c..a4a6a14 100644 (file)
@@ -219,6 +219,15 @@ add_gen_header(
 file(MAKE_DIRECTORY "sys")
 
 add_gen_header(
+  sys_ioctl
+  DEF_FILE sys/ioctl.h.def
+  GEN_HDR sys/ioctl.h
+  DEPENDS
+    .llvm_libc_common_h
+    .llvm-libc-macros.sys_ioctl_macros
+)
+
+add_gen_header(
   sys_mman
   DEF_FILE sys/mman.h.def
   GEN_HDR sys/mman.h
index def26a1..2b69a3b 100644 (file)
@@ -37,6 +37,15 @@ add_header(
 )
 
 add_header(
+  sys_ioctl_macros
+  HDR
+    sys-ioctl-macros.h
+  DEPENDS
+    .linux.sys_ioctl_macros
+)
+
+
+add_header(
   sys_stat_macros
   HDR
     sys-stat-macros.h
index e3c9392..5857290 100644 (file)
@@ -11,6 +11,12 @@ add_header(
 )
 
 add_header(
+  sys_ioctl_macros
+  HDR
+    sys-ioctl-macros.h
+)
+
+add_header(
   sys_mman_macros
   HDR
     sys-mman-macros.h
diff --git a/libc/include/llvm-libc-macros/linux/sys-ioctl-macros.h b/libc/include/llvm-libc-macros/linux/sys-ioctl-macros.h
new file mode 100644 (file)
index 0000000..8f13a0e
--- /dev/null
@@ -0,0 +1,19 @@
+//===-- Definition of macros from sys/ioctl.h -----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef __LLVM_LIBC_MACROS_LINUX_SYS_IOCTL_MACROS_H
+#define __LLVM_LIBC_MACROS_LINUX_SYS_IOCTL_MACROS_H
+
+// TODO (michaelrj): Finish defining these macros.
+// Just defining this macro for the moment since it's all that we need right
+// now. The other macros are mostly just constants, but there's some complexity
+// around the definitions of macros like _IO, _IOR, _IOW, and _IOWR that I don't
+// think is worth digging into right now.
+#define TIOCGETD 0x5424
+
+#endif // __LLVM_LIBC_MACROS_LINUX_SYS_IOCTL_MACROS_H
diff --git a/libc/include/llvm-libc-macros/sys-ioctl-macros.h b/libc/include/llvm-libc-macros/sys-ioctl-macros.h
new file mode 100644 (file)
index 0000000..b3fbfe3
--- /dev/null
@@ -0,0 +1,16 @@
+//===-- Macros defined in sys/ioctl.h header file -------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef __LLVM_LIBC_MACROS_SYS_IOCTL_MACROS_H
+#define __LLVM_LIBC_MACROS_SYS_IOCTL_MACROS_H
+
+#ifdef __unix__
+#include "linux/sys-ioctl-macros.h"
+#endif
+
+#endif // __LLVM_LIBC_MACROS_SYS_IOCTL_MACROS_H
diff --git a/libc/include/sys/ioctl.h.def b/libc/include/sys/ioctl.h.def
new file mode 100644 (file)
index 0000000..90d91cf
--- /dev/null
@@ -0,0 +1,18 @@
+//===-- POSIX header ioctl.h ----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SYS_IOCTL_H
+#define LLVM_LIBC_SYS_IOCTL_H
+
+#include <__llvm-libc-common.h>
+
+#include <llvm-libc-macros/sys-ioctl-macros.h>
+
+%%public_api()
+
+#endif // LLVM_LIBC_SYS_IOCTL_H
index 61e7e4d..bab2750 100644 (file)
@@ -402,6 +402,11 @@ def POSIX : StandardSpec<"POSIX"> {
           [ArgSpec<VoidType>]
         >,
         FunctionSpec<
+          "isatty",
+          RetValSpec<IntType>,
+          [ArgSpec<IntType>]
+        >,
+        FunctionSpec<
           "link",
           RetValSpec<IntType>,
           [ArgSpec<ConstCharPtr>, ArgSpec<ConstCharPtr>]
@@ -1030,6 +1035,16 @@ def POSIX : StandardSpec<"POSIX"> {
     ]
   >;
 
+  HeaderSpec SysIOctl = HeaderSpec<
+    "sys/ioctl.h",
+    [
+      Macro<"TIOCGETD">,
+    ],  // Macros
+    [], // Types
+    [], // Enumerations
+    []  // Functions
+  >;
+
   let Headers = [
     CType,
     Dirent,
@@ -1039,6 +1054,7 @@ def POSIX : StandardSpec<"POSIX"> {
     Signal,
     StdIO,
     StdLib,
+    SysIOctl,
     SysMMan,
     SysResource,
     SysStat,
index ae53763..3d4fa3f 100644 (file)
@@ -115,6 +115,13 @@ add_entrypoint_object(
 )
 
 add_entrypoint_object(
+  isatty
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_OS}.isatty
+)
+
+add_entrypoint_object(
   link
   ALIAS
   DEPENDS
diff --git a/libc/src/unistd/isatty.h b/libc/src/unistd/isatty.h
new file mode 100644 (file)
index 0000000..cffee57
--- /dev/null
@@ -0,0 +1,20 @@
+//===-- Implementation header for isatty ------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_UNISTD_ISATTY_H
+#define LLVM_LIBC_SRC_UNISTD_ISATTY_H
+
+#include <unistd.h>
+
+namespace __llvm_libc {
+
+int isatty(int fd);
+
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_SRC_UNISTD_ISATTY_H
index 2a6a0e6..af45854 100644 (file)
@@ -208,6 +208,20 @@ add_entrypoint_object(
 )
 
 add_entrypoint_object(
+  isatty
+  SRCS
+    isatty.cpp
+  HDRS
+    ../isatty.h
+  DEPENDS
+    libc.include.unistd
+    libc.include.errno
+    libc.include.sys_syscall
+    libc.src.__support.OSUtil.osutil
+    libc.src.errno.errno
+)
+
+add_entrypoint_object(
   link
   SRCS
     link.cpp
diff --git a/libc/src/unistd/linux/isatty.cpp b/libc/src/unistd/linux/isatty.cpp
new file mode 100644 (file)
index 0000000..705d03c
--- /dev/null
@@ -0,0 +1,33 @@
+//===-- Linux implementation of isatty ------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/unistd/isatty.h"
+
+#include "src/__support/OSUtil/syscall.h" // For internal syscall function.
+#include "src/__support/common.h"
+
+#include <errno.h>
+#include <sys/ioctl.h>   // For ioctl numbers.
+#include <sys/syscall.h> // For syscall numbers.
+
+namespace __llvm_libc {
+
+LLVM_LIBC_FUNCTION(int, isatty, (int fd)) {
+  constexpr int INIT_VAL = 0x1234abcd;
+  int line_d_val = INIT_VAL;
+  // This gets the line dicipline of the terminal. When called on something that
+  // isn't a terminal it doesn't change line_d_val and returns -1.
+  int result = __llvm_libc::syscall_impl(SYS_ioctl, fd, TIOCGETD, &line_d_val);
+  if (result == 0)
+    return 1;
+
+  errno = -result;
+  return 0;
+}
+
+} // namespace __llvm_libc
index 3b99aca..c6a78f0 100644 (file)
@@ -353,6 +353,20 @@ add_libc_unittest(
 )
 
 add_libc_unittest(
+  isatty_test
+  SUITE
+    libc_unistd_unittests
+  SRCS
+    isatty_test.cpp
+  DEPENDS
+    libc.src.unistd.isatty
+    libc.src.fcntl.open
+    libc.src.unistd.close
+    libc.include.errno
+    libc.src.errno.errno
+)
+
+add_libc_unittest(
   geteuid_test
   SUITE
     libc_unistd_unittests
diff --git a/libc/test/src/unistd/isatty_test.cpp b/libc/test/src/unistd/isatty_test.cpp
new file mode 100644 (file)
index 0000000..28e4659
--- /dev/null
@@ -0,0 +1,54 @@
+//===-- Unittests for isatty ----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/fcntl/open.h"
+#include "src/unistd/close.h"
+#include "src/unistd/isatty.h"
+#include "test/ErrnoSetterMatcher.h"
+#include "utils/UnitTest/Test.h"
+
+#include <errno.h>
+
+using __llvm_libc::testing::ErrnoSetterMatcher::Fails;
+using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds;
+
+TEST(LlvmLibcIsATTYTest, StdInOutTests) {
+  // If stdin is connected to a terminal, assume that all of the standard i/o
+  // fds are.
+  if (__llvm_libc::isatty(0)) {
+    EXPECT_THAT(__llvm_libc::isatty(0), Succeeds(1)); // stdin
+    EXPECT_THAT(__llvm_libc::isatty(1), Succeeds(1)); // stdout
+    EXPECT_THAT(__llvm_libc::isatty(2), Succeeds(1)); // stderr
+  } else {
+    EXPECT_THAT(__llvm_libc::isatty(0), Fails(ENOTTY, 0)); // stdin
+    EXPECT_THAT(__llvm_libc::isatty(1), Fails(ENOTTY, 0)); // stdout
+    EXPECT_THAT(__llvm_libc::isatty(2), Fails(ENOTTY, 0)); // stderr
+  }
+}
+
+TEST(LlvmLibcIsATTYTest, BadFdTest) {
+  EXPECT_THAT(__llvm_libc::isatty(-1), Fails(EBADF, 0)); // invalid fd
+}
+
+TEST(LlvmLibcIsATTYTest, DevTTYTest) {
+  constexpr const char *TTY_FILE = "/dev/tty";
+  int fd = __llvm_libc::open(TTY_FILE, O_RDONLY);
+  ASSERT_EQ(errno, 0);
+  ASSERT_GT(fd, 0);
+  EXPECT_THAT(__llvm_libc::isatty(fd), Succeeds(1));
+  ASSERT_THAT(__llvm_libc::close(fd), Succeeds(0));
+}
+
+TEST(LlvmLibcIsATTYTest, FileTest) {
+  constexpr const char *TEST_FILE = "testdata/isatty.test";
+  int fd = __llvm_libc::open(TEST_FILE, O_WRONLY | O_CREAT, S_IRWXU);
+  ASSERT_EQ(errno, 0);
+  ASSERT_GT(fd, 0);
+  EXPECT_THAT(__llvm_libc::isatty(fd), Fails(ENOTTY, 0));
+  ASSERT_THAT(__llvm_libc::close(fd), Succeeds(0));
+}