diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -274,6 +274,10 @@ let Types = ["size_t", "ssize_t"]; } +def SysSelectAPI : PublicAPI<"sys/select.h"> { + let Types = ["fd_set", "sigset_t", "suseconds_t", "time_t", "struct timespec", "struct timeval"]; +} + def SysResourceAPI : PublicAPI<"sys/resource.h"> { let Types = ["rlim_t", "struct rlimit"]; } 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 @@ -452,6 +452,9 @@ libc.src.unistd.execv libc.src.unistd.fork libc.src.unistd.__llvm_libc_syscall + + # sys/select.h entrypoints + libc.src.sys.select.select ) endif() 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 @@ -25,6 +25,7 @@ libc.include.sys_prctl libc.include.sys_random libc.include.sys_resource + libc.include.sys_select libc.include.sys_stat libc.include.sys_syscall libc.include.sys_time diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -301,6 +301,22 @@ .llvm-libc-types.struct_stat ) +add_gen_header( + sys_select + DEF_FILE sys/select.h.def + GEN_HDR sys/select.h + DEPENDS + .llvm_libc_common_h + .llvm-libc-macros.sys_select_macros + .llvm-libc-types.fd_set + .llvm-libc-types.sigset_t + .llvm-libc-types.struct_timespec + .llvm-libc-types.struct_timeval + .llvm-libc-types.suseconds_t + .llvm-libc-types.time_t + .llvm-libc-types.ssize_t +) + add_gen_header( sys_sendfile DEF_FILE sys/sendfile.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 @@ -83,6 +83,12 @@ .linux.sys_resource_macros ) +add_header( + sys_select_macros + HDR + sys-select-macros.h +) + add_header( sys_time_macros HDR diff --git a/libc/include/llvm-libc-macros/sys-select-macros.h b/libc/include/llvm-libc-macros/sys-select-macros.h new file mode 100644 --- /dev/null +++ b/libc/include/llvm-libc-macros/sys-select-macros.h @@ -0,0 +1,35 @@ +//===-- Macros defined in sys/select.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_SELECT_MACROS_H +#define __LLVM_LIBC_MACROS_SYS_SELECT_MACROS_H + +#define FD_SETSIZE 1024 +#define __FD_SET_WORD_TYPE unsigned long +#define __FD_SET_WORD_SIZE (sizeof(__FD_SET_WORD_TYPE) * 8) +#define __FD_SET_ARRAYSIZE (FD_SETSIZE / __FD_SET_WORD_SIZE) + +#define FD_ZERO(set) \ + do { \ + unsigned i; \ + for (i = 0; i < __FD_SET_ARRAYSIZE; ++i) \ + (set)->__set[i] = 0; \ + } while (0) + +#define __FD_WORD(fd) ((fd) / __FD_SET_WORD_SIZE) +#define __FD_MASK(fd) \ + ((__FD_SET_WORD_TYPE)1) << ((__FD_SET_WORD_TYPE)((fd) % __FD_SET_WORD_SIZE)) + +#define FD_CLR(fd, set) (void)((set)->__set[__FD_WORD(fd)] &= ~__FD_MASK(fd)) + +#define FD_SET(fd, set) (void)((set)->__set[__FD_WORD(fd)] |= __FD_MASK(fd)) + +#define FD_ISSET(fd, set) \ + (int)(((set)->__set[__FD_WORD(fd)] & __FD_MASK(fd)) != 0) + +#endif // __LLVM_LIBC_MACROS_SYS_SELECT_MACROS_H diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt --- a/libc/include/llvm-libc-types/CMakeLists.txt +++ b/libc/include/llvm-libc-types/CMakeLists.txt @@ -27,6 +27,7 @@ add_header(ldiv_t HDR ldiv_t.h) add_header(lldiv_t HDR lldiv_t.h) add_header(FILE HDR FILE.h) +add_header(fd_set HDR fd_set.h DEPENDS libc.include.llvm-libc-macros.sys_select_macros) add_header(fenv_t HDR fenv_t.h) add_header(fexcept_t HDR fexcept_t.h) add_header(float_t HDR float_t.h) diff --git a/libc/include/llvm-libc-types/fd_set.h b/libc/include/llvm-libc-types/fd_set.h new file mode 100644 --- /dev/null +++ b/libc/include/llvm-libc-types/fd_set.h @@ -0,0 +1,18 @@ +//===-- Definition of fd_set type -----------------------------------------===// +// +// 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_TYPES_FD_SET_H__ +#define __LLVM_LIBC_TYPES_FD_SET_H__ + +#include // FD_SETSIZE + +typedef struct { + __FD_SET_WORD_TYPE __set[__FD_SET_ARRAYSIZE]; +} fd_set; + +#endif // __LLVM_LIBC_TYPES_FD_SET_H__ diff --git a/libc/include/sys/select.h.def b/libc/include/sys/select.h.def new file mode 100644 --- /dev/null +++ b/libc/include/sys/select.h.def @@ -0,0 +1,18 @@ +//===-- Linux sys/select.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_SELECT_H +#define LLVM_LIBC_SYS_SELECT_H + +#include <__llvm-libc-common.h> + +#include + +%%public_api() + +#endif // LLVM_LIBC_SYS_SELECT_H diff --git a/libc/spec/linux.td b/libc/spec/linux.td --- a/libc/spec/linux.td +++ b/libc/spec/linux.td @@ -1,5 +1,3 @@ -def StructTimevalType : NamedType<"struct timeval">; - def Linux : StandardSpec<"Linux"> { HeaderSpec Errno = HeaderSpec< "errno.h", diff --git a/libc/spec/posix.td b/libc/spec/posix.td --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -70,6 +70,10 @@ def RestrictedStackTPtr : RestrictedPtrType; def ConstRestrictedStackTPtr : ConstType; +def FdSet : NamedType<"fd_set">; +def FdSetPtr : PtrType; +def RestrictedFdSetPtr : RestrictedPtrType; + def POSIX : StandardSpec<"POSIX"> { PtrType CharPtr = PtrType; RestrictedPtrType RestrictedCharPtr = RestrictedPtrType; @@ -1184,6 +1188,23 @@ ] >; + HeaderSpec SysSelect = HeaderSpec< + "sys/select.h", + [], // Macros + [FdSet, SigSetType, StructTimevalType, StructTimeSpec, SuSecondsT, TimeTType], + [], // Enumerations + [ + FunctionSpec< + "select", + RetValSpec, + [ + ArgSpec, ArgSpec, ArgSpec, + ArgSpec, ArgSpec + ] + > + ] + >; + let Headers = [ CType, Dirent, @@ -1197,6 +1218,7 @@ SysIOctl, SysMMan, SysResource, + SysSelect, SysStat, SysUtsName, SysWait, diff --git a/libc/spec/spec.td b/libc/spec/spec.td --- a/libc/spec/spec.td +++ b/libc/spec/spec.td @@ -121,6 +121,12 @@ def StructRUsage : NamedType<"struct rusage">; def StructRUsagePtr : PtrType; +def StructTimevalType : NamedType<"struct timeval">; +def StructTimevalPtr : PtrType; +def RestrictedStructTimevalPtr : RestrictedPtrType; + +def SuSecondsT : NamedType<"suseconds_t">; + //added because __assert_fail needs it. def UnsignedType : NamedType<"unsigned">; diff --git a/libc/src/sys/CMakeLists.txt b/libc/src/sys/CMakeLists.txt --- a/libc/src/sys/CMakeLists.txt +++ b/libc/src/sys/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(mman) add_subdirectory(random) add_subdirectory(resource) +add_subdirectory(select) add_subdirectory(sendfile) add_subdirectory(stat) add_subdirectory(utsname) diff --git a/libc/src/sys/select/CMakeLists.txt b/libc/src/sys/select/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/src/sys/select/CMakeLists.txt @@ -0,0 +1,10 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) +endif() + +add_entrypoint_object( + select + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.select +) diff --git a/libc/src/sys/select/linux/CMakeLists.txt b/libc/src/sys/select/linux/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/src/sys/select/linux/CMakeLists.txt @@ -0,0 +1,12 @@ +add_entrypoint_object( + select + SRCS + select.cpp + HDRS + ../select.h + DEPENDS + libc.include.sys_select + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) diff --git a/libc/src/sys/select/linux/select.cpp b/libc/src/sys/select/linux/select.cpp new file mode 100644 --- /dev/null +++ b/libc/src/sys/select/linux/select.cpp @@ -0,0 +1,65 @@ +//===-- Linux implementation of select ------------------------------------===// +// +// 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/sys/select/select.h" + +#include "src/__support/CPP/limits.h" +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" + +#include +#include +#include // For size_t +#include +#include // For syscall numbers. + +namespace __llvm_libc { + +struct pselect6_sigset_t { + sigset_t *ss; + size_t ss_len; +}; + +LLVM_LIBC_FUNCTION(int, select, + (int nfds, fd_set *__restrict read_set, + fd_set *__restrict write_set, fd_set *__restrict error_set, + struct timeval *__restrict timeout)) { + // Linux has a SYS_select syscall but it is not available on all + // architectures. So, we use the SYS_pselect6 syscall which is more + // widely available. However, SYS_pselect6 takes a struct timespec argument + // instead of a struct timeval argument. Also, it takes an additional + // argument which is a pointer to an object of a type defined above as + // "pselect6_sigset_t". + struct timespec ts { + 0, 0 + }; + if (timeout != nullptr) { + // In general, if the tv_sec and tv_usec in |timeout| are correctly set, + // then converting tv_usec to nanoseconds will not be a problem. However, + // if tv_usec in |timeout| is more than a second, it can lead to overflows. + // So, we detect such cases and adjust. + constexpr time_t TIME_MAX = cpp::numeric_limits::max(); + if ((TIME_MAX - timeout->tv_sec) < (timeout->tv_usec / 1000000)) { + ts.tv_sec = TIME_MAX; + ts.tv_nsec = 999999999; + } else { + ts.tv_sec = timeout->tv_sec + timeout->tv_usec / 1000000; + ts.tv_nsec = timeout->tv_usec * 1000; + } + } + pselect6_sigset_t pss{nullptr, sizeof(sigset_t)}; + long ret = __llvm_libc::syscall_impl(SYS_pselect6, nfds, read_set, write_set, + error_set, &ts, &pss); + if (ret < 0) { + errno = -ret; + return -1; + } + return ret; +} + +} // namespace __llvm_libc diff --git a/libc/src/sys/select/select.h b/libc/src/sys/select/select.h new file mode 100644 --- /dev/null +++ b/libc/src/sys/select/select.h @@ -0,0 +1,21 @@ +//===-- Implementation header for select ------------------------*- 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_SYS_SELECT_SELECT_H +#define LLVM_LIBC_SRC_SYS_SELECT_SELECT_H + +#include + +namespace __llvm_libc { + +int select(int nfds, fd_set *__restrict read_set, fd_set *__restrict write_set, + fd_set *__restrict error_set, struct timeval *__restrict timeout); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_SYS_SELECT_SELECT_H diff --git a/libc/test/src/sys/CMakeLists.txt b/libc/test/src/sys/CMakeLists.txt --- a/libc/test/src/sys/CMakeLists.txt +++ b/libc/test/src/sys/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(mman) add_subdirectory(random) add_subdirectory(resource) +add_subdirectory(select) add_subdirectory(sendfile) add_subdirectory(stat) add_subdirectory(utsname) diff --git a/libc/test/src/sys/select/CMakeLists.txt b/libc/test/src/sys/select/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/test/src/sys/select/CMakeLists.txt @@ -0,0 +1,31 @@ +add_libc_testsuite(libc_sys_select_unittests) + +add_libc_unittest( + select_ui_test + NO_RUN_POSTBUILD + SUITE + libc_sys_select_unittests + SRCS + select_ui_test.cpp + DEPENDS + libc.include.errno + libc.include.unistd + libc.src.sys.select.select + libc.src.unistd.read +) + +add_libc_unittest( + select_failure_test + SUITE + libc_sys_select_unittests + SRCS + select_failure_test.cpp + DEPENDS + libc.include.errno + libc.include.unistd + libc.src.sys.select.select + libc.src.unistd.read + libc.test.errno_setter_matcher +) + +add_subdirectory(testdata) diff --git a/libc/test/src/sys/select/select_failure_test.cpp b/libc/test/src/sys/select/select_failure_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/sys/select/select_failure_test.cpp @@ -0,0 +1,28 @@ +//===-- Failure unittests for select --------------------------------------===// +// +// 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/sys/select/select.h" +#include "src/unistd/read.h" +#include "test/ErrnoSetterMatcher.h" +#include "utils/UnitTest/Test.h" + +#include +#include +#include + +using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + +TEST(LlvmLibcSelectTest, SelectInvalidFD) { + fd_set set; + FD_ZERO(&set); + struct timeval timeout { + 0, 0 + }; + ASSERT_THAT(__llvm_libc::select(-1, &set, nullptr, nullptr, &timeout), + Fails(EINVAL)); +} diff --git a/libc/test/src/sys/select/select_ui_test.cpp b/libc/test/src/sys/select/select_ui_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/sys/select/select_ui_test.cpp @@ -0,0 +1,49 @@ +//===-- Interactive unittests for select ----------------------------------===// +// +// 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/sys/select/select.h" +#include "src/unistd/read.h" +#include "utils/UnitTest/Test.h" + +#include +#include +#include + +// This test is not be run automatically as part of the libc testsuite. +// Instead, one has to run it manually and press a key on the keyboard +// to make the test succeed. +TEST(LlvmLibcSelectTest, ReadStdinAfterSelect) { + errno = 0; + constexpr int STDIN_FD = 0; + fd_set set; + FD_ZERO(&set); + FD_SET(STDIN_FD, &set); + struct timeval zero { + 0, 0 + }; // No wait + struct timeval hr { + 3600, 0 + }; // Wait for an hour. + + // Zero timeout means we don't wait for input. So, select should return + // immediately. + int count = __llvm_libc::select(STDIN_FD + 1, &set, nullptr, nullptr, &zero); + // The set should indicate that stdin is NOT ready for reading. + ASSERT_EQ(0, FD_ISSET(STDIN_FD, &set)); + + FD_SET(STDIN_FD, &set); + // Wait for an hour and give the user a chance to hit a key. + count = __llvm_libc::select(STDIN_FD + 1, &set, nullptr, nullptr, &hr); + ASSERT_EQ(count, 1); + // The set should indicate that stdin is ready for reading. + ASSERT_EQ(1, FD_ISSET(STDIN_FD, &set)); + + // Verify that atleast one character can be read. + char c; + ASSERT_EQ(__llvm_libc::read(STDIN_FD, &c, 1), ssize_t(1)); +} diff --git a/libc/test/src/sys/select/testdata/CMakeLists.txt b/libc/test/src/sys/select/testdata/CMakeLists.txt new file mode 100644