diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -156,6 +156,7 @@ 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 diff --git a/libc/config/linux/x86_64/headers.txt b/libc/config/linux/x86_64/headers.txt --- a/libc/config/linux/x86_64/headers.txt +++ b/libc/config/linux/x86_64/headers.txt @@ -11,6 +11,7 @@ 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 diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -218,6 +218,15 @@ # them. 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 diff --git a/libc/include/llvm-libc-macros/CMakeLists.txt b/libc/include/llvm-libc-macros/CMakeLists.txt --- a/libc/include/llvm-libc-macros/CMakeLists.txt +++ b/libc/include/llvm-libc-macros/CMakeLists.txt @@ -36,6 +36,15 @@ stdlib-macros.h ) +add_header( + sys_ioctl_macros + HDR + sys-ioctl-macros.h + DEPENDS + .linux.sys_ioctl_macros +) + + add_header( sys_stat_macros HDR diff --git a/libc/include/llvm-libc-macros/linux/CMakeLists.txt b/libc/include/llvm-libc-macros/linux/CMakeLists.txt --- a/libc/include/llvm-libc-macros/linux/CMakeLists.txt +++ b/libc/include/llvm-libc-macros/linux/CMakeLists.txt @@ -10,6 +10,12 @@ sched-macros.h ) +add_header( + sys_ioctl_macros + HDR + sys-ioctl-macros.h +) + add_header( sys_mman_macros HDR 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 --- /dev/null +++ b/libc/include/llvm-libc-macros/linux/sys-ioctl-macros.h @@ -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 --- /dev/null +++ b/libc/include/llvm-libc-macros/sys-ioctl-macros.h @@ -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 --- /dev/null +++ b/libc/include/sys/ioctl.h.def @@ -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 + +%%public_api() + +#endif // LLVM_LIBC_SYS_IOCTL_H diff --git a/libc/spec/posix.td b/libc/spec/posix.td --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -401,6 +401,11 @@ RetValSpec, [ArgSpec] >, + FunctionSpec< + "isatty", + RetValSpec, + [ArgSpec] + >, FunctionSpec< "link", RetValSpec, @@ -1030,6 +1035,16 @@ ] >; + HeaderSpec SysIOctl = HeaderSpec< + "sys/ioctl.h", + [ + Macro<"TIOCGETD">, + ], // Macros + [], // Types + [], // Enumerations + [] // Functions + >; + let Headers = [ CType, Dirent, @@ -1039,6 +1054,7 @@ Signal, StdIO, StdLib, + SysIOctl, SysMMan, SysResource, SysStat, diff --git a/libc/src/unistd/CMakeLists.txt b/libc/src/unistd/CMakeLists.txt --- a/libc/src/unistd/CMakeLists.txt +++ b/libc/src/unistd/CMakeLists.txt @@ -114,6 +114,13 @@ .${LIBC_TARGET_OS}.getuid ) +add_entrypoint_object( + isatty + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.isatty +) + add_entrypoint_object( link ALIAS diff --git a/libc/src/unistd/isatty.h b/libc/src/unistd/isatty.h new file mode 100644 --- /dev/null +++ b/libc/src/unistd/isatty.h @@ -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 + +namespace __llvm_libc { + +int isatty(int fd); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_UNISTD_ISATTY_H diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt --- a/libc/src/unistd/linux/CMakeLists.txt +++ b/libc/src/unistd/linux/CMakeLists.txt @@ -207,6 +207,20 @@ libc.src.__support.OSUtil.osutil ) +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 diff --git a/libc/src/unistd/linux/isatty.cpp b/libc/src/unistd/linux/isatty.cpp new file mode 100644 --- /dev/null +++ b/libc/src/unistd/linux/isatty.cpp @@ -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 +#include // For ioctl numbers. +#include // 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 diff --git a/libc/test/src/unistd/CMakeLists.txt b/libc/test/src/unistd/CMakeLists.txt --- a/libc/test/src/unistd/CMakeLists.txt +++ b/libc/test/src/unistd/CMakeLists.txt @@ -352,6 +352,20 @@ libc.src.unistd.getuid ) +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 diff --git a/libc/test/src/unistd/isatty_test.cpp b/libc/test/src/unistd/isatty_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/unistd/isatty_test.cpp @@ -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 + +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)); +}