diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -113,6 +113,9 @@ # unistd.h entrypoints libc.src.unistd.chdir libc.src.unistd.close + libc.src.unistd.dup + libc.src.unistd.dup2 + libc.src.unistd.dup3 libc.src.unistd.fchdir libc.src.unistd.fsync libc.src.unistd.ftruncate 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 @@ -113,6 +113,9 @@ # unistd.h entrypoints libc.src.unistd.chdir libc.src.unistd.close + libc.src.unistd.dup + libc.src.unistd.dup2 + libc.src.unistd.dup3 libc.src.unistd.fchdir libc.src.unistd.fsync libc.src.unistd.ftruncate diff --git a/libc/include/llvm-libc-macros/linux/fcntl-macros.h b/libc/include/llvm-libc-macros/linux/fcntl-macros.h --- a/libc/include/llvm-libc-macros/linux/fcntl-macros.h +++ b/libc/include/llvm-libc-macros/linux/fcntl-macros.h @@ -70,4 +70,11 @@ // has to perform the equivalent of "rmdir" on the path argument. #define AT_REMOVEDIR 0x200 +// Values of SYS_fcntl commands. +#define F_DUPFD 0 +#define F_GETFD 1 +#define F_SETFD 2 +#define F_GETFL 3 +#define F_SETFL 4 + #endif // __LLVM_LIBC_MACROS_LINUX_FCNTL_MACROS_H diff --git a/libc/spec/gnu_ext.td b/libc/spec/gnu_ext.td --- a/libc/spec/gnu_ext.td +++ b/libc/spec/gnu_ext.td @@ -144,6 +144,20 @@ ] >; + HeaderSpec UniStd = HeaderSpec< + "unistd.h", + [], // Macros + [], // Types + [], // Enumerations + [ + FunctionSpec< + "dup2", + RetValSpec, + [ArgSpec, ArgSpec, ArgSpec] + >, + ] + >; + let Headers = [ CType, FEnv, @@ -152,5 +166,6 @@ SendFile, StdIO, String, + UniStd, ]; } diff --git a/libc/spec/posix.td b/libc/spec/posix.td --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -266,6 +266,21 @@ RetValSpec, [ArgSpec] >, + FunctionSpec< + "dup", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< + "dup2", + RetValSpec, + [ArgSpec, ArgSpec] + >, + FunctionSpec< + "dup3", + RetValSpec, + [ArgSpec, ArgSpec, ArgSpec] + >, FunctionSpec< "fchdir", RetValSpec, 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 @@ -16,6 +16,27 @@ .${LIBC_TARGET_OS}.close ) +add_entrypoint_object( + dup + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.dup +) + +add_entrypoint_object( + dup2 + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.dup2 +) + +add_entrypoint_object( + dup3 + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.dup3 +) + add_entrypoint_object( fchdir ALIAS diff --git a/libc/src/unistd/dup.h b/libc/src/unistd/dup.h new file mode 100644 --- /dev/null +++ b/libc/src/unistd/dup.h @@ -0,0 +1,20 @@ +//===-- Implementation header for dup ---------------------------*- 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_DUP_H +#define LLVM_LIBC_SRC_UNISTD_DUP_H + +#include + +namespace __llvm_libc { + +int dup(int fd); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_UNISTD_DUP_H diff --git a/libc/src/unistd/dup2.h b/libc/src/unistd/dup2.h new file mode 100644 --- /dev/null +++ b/libc/src/unistd/dup2.h @@ -0,0 +1,20 @@ +//===-- Implementation header for dup2 --------------------------*- 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_DUP2_H +#define LLVM_LIBC_SRC_UNISTD_DUP2_H + +#include + +namespace __llvm_libc { + +int dup2(int oldfd, int newfd); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_UNISTD_DUP2_H diff --git a/libc/src/unistd/dup3.h b/libc/src/unistd/dup3.h new file mode 100644 --- /dev/null +++ b/libc/src/unistd/dup3.h @@ -0,0 +1,20 @@ +//===-- Implementation header for dup3 --------------------------*- 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_DUP3_H +#define LLVM_LIBC_SRC_UNISTD_DUP3_H + +#include + +namespace __llvm_libc { + +int dup3(int oldfd, int newfd, int flags); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_UNISTD_DUP3_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 @@ -24,6 +24,46 @@ libc.src.errno.errno ) +add_entrypoint_object( + dup + SRCS + dup.cpp + HDRS + ../dup.h + DEPENDS + libc.include.unistd + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) + +add_entrypoint_object( + dup2 + SRCS + dup2.cpp + HDRS + ../dup2.h + DEPENDS + libc.include.fcntl + libc.include.unistd + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) + +add_entrypoint_object( + dup3 + SRCS + dup3.cpp + HDRS + ../dup3.h + DEPENDS + libc.include.unistd + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) + add_entrypoint_object( fchdir SRCS diff --git a/libc/src/unistd/linux/dup.cpp b/libc/src/unistd/linux/dup.cpp new file mode 100644 --- /dev/null +++ b/libc/src/unistd/linux/dup.cpp @@ -0,0 +1,28 @@ +//===-- Linux implementation of dup ---------------------------------------===// +// +// 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/dup.h" + +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" + +#include +#include // For syscall numbers. + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, dup, (int fd)) { + long ret = __llvm_libc::syscall(SYS_dup, fd); + if (ret < 0) { + errno = -ret; + return -1; + } + return ret; +} + +} // namespace __llvm_libc diff --git a/libc/src/unistd/linux/dup2.cpp b/libc/src/unistd/linux/dup2.cpp new file mode 100644 --- /dev/null +++ b/libc/src/unistd/linux/dup2.cpp @@ -0,0 +1,47 @@ +//===-- Linux implementation of dup2 --------------------------------------===// +// +// 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/dup2.h" + +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" + +#include +#include +#include // For syscall numbers. + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, dup2, (int oldfd, int newfd)) { +#ifdef SYS_dup2 + // If dup2 syscall is available, we make use of directly. + long ret = __llvm_libc::syscall(SYS_dup2, oldfd, newfd); +#elif defined(SYS_dup3) + // If dup2 syscall is not available, we try using the dup3 syscall. However, + // dup3 fails if oldfd is the same as newfd. So, we handle that case + // separately before making the dup3 syscall. + if (oldfd == newfd) { + // Check if oldfd is actually a valid file descriptor. + long ret = __llvm_libc::syscall(SYS_fcntl, oldfd, F_GETFD); + if (ret >= 0) + return oldfd; + errno = -ret; + return -1; + } + long ret = __llvm_libc::syscall(SYS_dup3, oldfd, newfd, 0); +#else +#error "SYS_dup2 and SYS_dup3 not available for the target." +#endif + if (ret < 0) { + errno = -ret; + return -1; + } + return ret; +} + +} // namespace __llvm_libc diff --git a/libc/src/unistd/linux/dup3.cpp b/libc/src/unistd/linux/dup3.cpp new file mode 100644 --- /dev/null +++ b/libc/src/unistd/linux/dup3.cpp @@ -0,0 +1,28 @@ +//===-- Linux implementation of dup3 --------------------------------------===// +// +// 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/dup3.h" + +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" + +#include +#include // For syscall numbers. + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, dup3, (int oldfd, int newfd, int flags)) { + // If dup2 syscall is available, we make use of directly. + long ret = __llvm_libc::syscall(SYS_dup3, oldfd, newfd, flags); + if (ret >= 0) + return ret; + errno = -ret; + return -1; +} + +} // 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 @@ -17,6 +17,60 @@ libc.test.errno_setter_matcher ) +add_libc_unittest( + dup_test + SUITE + libc_unistd_unittests + SRCS + dup_test.cpp + DEPENDS + libc.include.errno + libc.include.unistd + libc.src.fcntl.open + libc.src.unistd.close + libc.src.unistd.dup + libc.src.unistd.read + libc.src.unistd.unlink + libc.src.unistd.write + libc.test.errno_setter_matcher +) + +add_libc_unittest( + dup2_test + SUITE + libc_unistd_unittests + SRCS + dup2_test.cpp + DEPENDS + libc.include.errno + libc.include.unistd + libc.src.fcntl.open + libc.src.unistd.close + libc.src.unistd.dup2 + libc.src.unistd.read + libc.src.unistd.unlink + libc.src.unistd.write + libc.test.errno_setter_matcher +) + +add_libc_unittest( + dup3_test + SUITE + libc_unistd_unittests + SRCS + dup3_test.cpp + DEPENDS + libc.include.errno + libc.include.unistd + libc.src.fcntl.open + libc.src.unistd.close + libc.src.unistd.dup3 + libc.src.unistd.read + libc.src.unistd.unlink + libc.src.unistd.write + libc.test.errno_setter_matcher +) + add_libc_unittest( fchdir_test SUITE diff --git a/libc/test/src/unistd/dup2_test.cpp b/libc/test/src/unistd/dup2_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/unistd/dup2_test.cpp @@ -0,0 +1,63 @@ +//===-- Unittests for dup -------------------------------------------------===// +// +// 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/dup2.h" +#include "src/unistd/read.h" +#include "src/unistd/unlink.h" +#include "src/unistd/write.h" +#include "test/ErrnoSetterMatcher.h" +#include "utils/UnitTest/Test.h" +#include "utils/testutils/FDReader.h" + +#include + +TEST(LlvmLibcdupTest, ReadAndWriteViaDup) { + constexpr int DUPFD = 0xD0; + errno = 0; + using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; + constexpr const char *TEST_FILE = "testdata/dup.test"; + int fd = __llvm_libc::open(TEST_FILE, O_WRONLY | O_CREAT, S_IRWXU); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + int dupfd = __llvm_libc::dup2(fd, DUPFD); + ASSERT_EQ(errno, 0); + ASSERT_EQ(dupfd, DUPFD); + + // Write something via the dup + constexpr char WRITE_DATA[] = "Hello, dup!"; + constexpr size_t WRITE_SIZE = sizeof(WRITE_DATA); + ASSERT_EQ(ssize_t(WRITE_SIZE), + __llvm_libc::write(dupfd, WRITE_DATA, WRITE_SIZE)); + ASSERT_THAT(__llvm_libc::close(dupfd), Succeeds(0)); + + // Reopen the file for reading and create a dup. + fd = __llvm_libc::open(TEST_FILE, O_RDONLY); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + dupfd = __llvm_libc::dup2(fd, DUPFD); + ASSERT_EQ(errno, 0); + ASSERT_EQ(dupfd, DUPFD); + + // Read the file content via the dup. + char buf[WRITE_SIZE]; + ASSERT_THAT(__llvm_libc::read(dupfd, buf, WRITE_SIZE), Succeeds(WRITE_SIZE)); + ASSERT_STREQ(buf, WRITE_DATA); + + // Verify that duping to the same fd value succeeds. + ASSERT_THAT(__llvm_libc::dup2(dupfd, dupfd), Succeeds(dupfd)); + + ASSERT_THAT(__llvm_libc::close(dupfd), Succeeds(0)); + ASSERT_THAT(__llvm_libc::unlink(TEST_FILE), Succeeds(0)); +} + +TEST(LlvmLibcdupTest, DupBadFD) { + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + ASSERT_THAT(__llvm_libc::dup2(-1, 123), Fails(EBADF)); +} diff --git a/libc/test/src/unistd/dup3_test.cpp b/libc/test/src/unistd/dup3_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/unistd/dup3_test.cpp @@ -0,0 +1,69 @@ +//===-- Unittests for dup3 ------------------------------------------------===// +// +// 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/dup3.h" +#include "src/unistd/read.h" +#include "src/unistd/unlink.h" +#include "src/unistd/write.h" +#include "test/ErrnoSetterMatcher.h" +#include "utils/UnitTest/Test.h" +#include "utils/testutils/FDReader.h" + +#include + +// The tests here are exactly the same as those of dup2. We only test the +// plumbing of the dup3 syscall and not the dup3 functionality itself as it is +// a simple syscall wrapper. Testing dup3 functionality is beyond the scope of +// this test. + +TEST(LlvmLibcdupTest, ReadAndWriteViaDup) { + constexpr int DUPFD = 0xD0; + errno = 0; + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; + constexpr const char *TEST_FILE = "testdata/dup.test"; + int fd = __llvm_libc::open(TEST_FILE, O_WRONLY | O_CREAT, S_IRWXU); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + int dupfd = __llvm_libc::dup3(fd, DUPFD, 0); + ASSERT_EQ(errno, 0); + ASSERT_EQ(dupfd, DUPFD); + + // Write something via the dup + constexpr char WRITE_DATA[] = "Hello, dup!"; + constexpr size_t WRITE_SIZE = sizeof(WRITE_DATA); + ASSERT_EQ(ssize_t(WRITE_SIZE), + __llvm_libc::write(dupfd, WRITE_DATA, WRITE_SIZE)); + ASSERT_THAT(__llvm_libc::close(dupfd), Succeeds(0)); + + // Reopen the file for reading and create a dup. + fd = __llvm_libc::open(TEST_FILE, O_RDONLY); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + dupfd = __llvm_libc::dup3(fd, DUPFD, 0); + ASSERT_EQ(errno, 0); + ASSERT_EQ(dupfd, DUPFD); + + // Read the file content via the dup. + char buf[WRITE_SIZE]; + ASSERT_THAT(__llvm_libc::read(dupfd, buf, WRITE_SIZE), Succeeds(WRITE_SIZE)); + ASSERT_STREQ(buf, WRITE_DATA); + + // Verify that, unlike dup2, duping to the same fd value with dup3 fails. + ASSERT_THAT(__llvm_libc::dup3(dupfd, dupfd, 0), Fails(EINVAL)); + + ASSERT_THAT(__llvm_libc::close(dupfd), Succeeds(0)); + ASSERT_THAT(__llvm_libc::unlink(TEST_FILE), Succeeds(0)); +} + +TEST(LlvmLibcdupTest, DupBadFD) { + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + ASSERT_THAT(__llvm_libc::dup3(-1, 123, 0), Fails(EBADF)); +} diff --git a/libc/test/src/unistd/dup_test.cpp b/libc/test/src/unistd/dup_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/unistd/dup_test.cpp @@ -0,0 +1,59 @@ +//===-- Unittests for dup -------------------------------------------------===// +// +// 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/dup.h" +#include "src/unistd/read.h" +#include "src/unistd/unlink.h" +#include "src/unistd/write.h" +#include "test/ErrnoSetterMatcher.h" +#include "utils/UnitTest/Test.h" +#include "utils/testutils/FDReader.h" + +#include + +TEST(LlvmLibcdupTest, ReadAndWriteViaDup) { + errno = 0; + using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; + constexpr const char *TEST_FILE = "testdata/dup.test"; + int fd = __llvm_libc::open(TEST_FILE, O_WRONLY | O_CREAT, S_IRWXU); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + int dupfd = __llvm_libc::dup(fd); + ASSERT_EQ(errno, 0); + ASSERT_GT(dupfd, 0); + + // Write something via the dup + constexpr char WRITE_DATA[] = "Hello, dup!"; + constexpr size_t WRITE_SIZE = sizeof(WRITE_DATA); + ASSERT_EQ(ssize_t(WRITE_SIZE), + __llvm_libc::write(dupfd, WRITE_DATA, WRITE_SIZE)); + ASSERT_THAT(__llvm_libc::close(dupfd), Succeeds(0)); + + // Reopen the file for reading and create a dup. + fd = __llvm_libc::open(TEST_FILE, O_RDONLY); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + dupfd = __llvm_libc::dup(fd); + ASSERT_EQ(errno, 0); + ASSERT_GT(dupfd, 0); + + // Read the file content via the dup. + char buf[WRITE_SIZE]; + ASSERT_THAT(__llvm_libc::read(dupfd, buf, WRITE_SIZE), Succeeds(WRITE_SIZE)); + ASSERT_STREQ(buf, WRITE_DATA); + + ASSERT_THAT(__llvm_libc::close(dupfd), Succeeds(0)); + ASSERT_THAT(__llvm_libc::unlink(TEST_FILE), Succeeds(0)); +} + +TEST(LlvmLibcdupTest, DupBadFD) { + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + ASSERT_THAT(__llvm_libc::dup(-1), Fails(EBADF)); +}