diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt index dcb39ba7fc37..fe56176cdd92 100644 --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -115,6 +115,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.unistd.close libc.src.unistd.fchdir libc.src.unistd.fsync + libc.src.unistd.ftruncate libc.src.unistd.link libc.src.unistd.linkat libc.src.unistd.lseek @@ -124,6 +125,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.unistd.rmdir libc.src.unistd.symlink libc.src.unistd.symlinkat + libc.src.unistd.truncate libc.src.unistd.unlink libc.src.unistd.unlinkat libc.src.unistd.write diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index 307bdd3efaa7..431730677b03 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -115,6 +115,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.unistd.close libc.src.unistd.fchdir libc.src.unistd.fsync + libc.src.unistd.ftruncate libc.src.unistd.link libc.src.unistd.linkat libc.src.unistd.lseek @@ -124,6 +125,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.unistd.rmdir libc.src.unistd.symlink libc.src.unistd.symlinkat + libc.src.unistd.truncate libc.src.unistd.unlink libc.src.unistd.unlinkat libc.src.unistd.write diff --git a/libc/spec/posix.td b/libc/spec/posix.td index a03b04ff2568..4dcf09dda7e0 100644 --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -281,6 +281,11 @@ def POSIX : StandardSpec<"POSIX"> { RetValSpec, [ArgSpec] >, + FunctionSpec< + "ftruncate", + RetValSpec, + [ArgSpec, ArgSpec] + >, FunctionSpec< "link", RetValSpec, @@ -326,6 +331,11 @@ def POSIX : StandardSpec<"POSIX"> { RetValSpec, [ArgSpec, ArgSpec, ArgSpec, ArgSpec] >, + FunctionSpec< + "truncate", + RetValSpec, + [ArgSpec, ArgSpec] + >, FunctionSpec< "unlink", RetValSpec, diff --git a/libc/src/unistd/CMakeLists.txt b/libc/src/unistd/CMakeLists.txt index 849b83e6d23a..d55ed7163ca4 100644 --- a/libc/src/unistd/CMakeLists.txt +++ b/libc/src/unistd/CMakeLists.txt @@ -30,6 +30,13 @@ add_entrypoint_object( .${LIBC_TARGET_OS}.fsync ) +add_entrypoint_object( + ftruncate + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.ftruncate +) + add_entrypoint_object( link ALIAS @@ -93,6 +100,13 @@ add_entrypoint_object( .${LIBC_TARGET_OS}.symlinkat ) +add_entrypoint_object( + truncate + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.truncate +) + add_entrypoint_object( unlink ALIAS diff --git a/libc/src/unistd/ftruncate.h b/libc/src/unistd/ftruncate.h new file mode 100644 index 000000000000..1971dc53af2d --- /dev/null +++ b/libc/src/unistd/ftruncate.h @@ -0,0 +1,20 @@ +//===-- Implementation header for ftruncate ---------------------*- 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_FTRUNCATE_H +#define LLVM_LIBC_SRC_UNISTD_FTRUNCATE_H + +#include + +namespace __llvm_libc { + +int ftruncate(int, off_t); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_UNISTD_FTRUNCATE_H diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt index 132fd7b78cb7..249b057ca950 100644 --- a/libc/src/unistd/linux/CMakeLists.txt +++ b/libc/src/unistd/linux/CMakeLists.txt @@ -50,6 +50,19 @@ add_entrypoint_object( libc.src.errno.errno ) +add_entrypoint_object( + ftruncate + SRCS + ftruncate.cpp + HDRS + ../ftruncate.h + DEPENDS + libc.include.unistd + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) + add_entrypoint_object( link SRCS @@ -174,6 +187,19 @@ add_entrypoint_object( libc.src.errno.errno ) +add_entrypoint_object( + truncate + SRCS + truncate.cpp + HDRS + ../truncate.h + DEPENDS + libc.include.unistd + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) + add_entrypoint_object( unlink SRCS diff --git a/libc/src/unistd/linux/ftruncate.cpp b/libc/src/unistd/linux/ftruncate.cpp new file mode 100644 index 000000000000..38de44100162 --- /dev/null +++ b/libc/src/unistd/linux/ftruncate.cpp @@ -0,0 +1,29 @@ +//===-- Linux implementation of ftruncate ---------------------------------===// +// +// 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/ftruncate.h" + +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" + +#include +#include // For syscall numbers. +#include + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, ftruncate, (int fd, off_t len)) { + int ret = __llvm_libc::syscall(SYS_ftruncate, fd, len); + if (ret < 0) { + errno = -ret; + return -1; + } + return 0; +} + +} // namespace __llvm_libc diff --git a/libc/src/unistd/linux/truncate.cpp b/libc/src/unistd/linux/truncate.cpp new file mode 100644 index 000000000000..c24bfa8e47b7 --- /dev/null +++ b/libc/src/unistd/linux/truncate.cpp @@ -0,0 +1,29 @@ +//===-- Linux implementation of truncate ----------------------------------===// +// +// 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/truncate.h" + +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" + +#include +#include // For syscall numbers. +#include + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, truncate, (const char *path, off_t len)) { + int ret = __llvm_libc::syscall(SYS_truncate, path, len); + if (ret < 0) { + errno = -ret; + return -1; + } + return 0; +} + +} // namespace __llvm_libc diff --git a/libc/src/unistd/truncate.h b/libc/src/unistd/truncate.h new file mode 100644 index 000000000000..a6ef92146008 --- /dev/null +++ b/libc/src/unistd/truncate.h @@ -0,0 +1,20 @@ +//===-- Implementation header for truncate ----------------------*- 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_TRUNCATE_H +#define LLVM_LIBC_SRC_UNISTD_TRUNCATE_H + +#include + +namespace __llvm_libc { + +int truncate(const char *, off_t); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_UNISTD_TRUNCATE_H diff --git a/libc/test/src/unistd/CMakeLists.txt b/libc/test/src/unistd/CMakeLists.txt index 2c9369cbd51e..1ea5fcfbbebc 100644 --- a/libc/test/src/unistd/CMakeLists.txt +++ b/libc/test/src/unistd/CMakeLists.txt @@ -32,6 +32,24 @@ add_libc_unittest( libc.test.errno_setter_matcher ) +add_libc_unittest( + ftruncate_test + SUITE + libc_unistd_unittests + SRCS + ftruncate_test.cpp + DEPENDS + libc.include.errno + libc.include.unistd + libc.src.fcntl.open + libc.src.unistd.close + libc.src.unistd.read + libc.src.unistd.ftruncate + libc.src.unistd.unlink + libc.src.unistd.write + libc.src.__support.CPP.string_view +) + add_libc_unittest( read_write_test SUITE @@ -169,6 +187,24 @@ add_libc_unittest( libc.src.unistd.unlink ) +add_libc_unittest( + truncate_test + SUITE + libc_unistd_unittests + SRCS + truncate_test.cpp + DEPENDS + libc.include.errno + libc.include.unistd + libc.src.fcntl.open + libc.src.unistd.close + libc.src.unistd.read + libc.src.unistd.truncate + libc.src.unistd.unlink + libc.src.unistd.write + libc.src.__support.CPP.string_view +) + add_libc_unittest( unlink_test SUITE diff --git a/libc/test/src/unistd/ftruncate_test.cpp b/libc/test/src/unistd/ftruncate_test.cpp new file mode 100644 index 000000000000..c0bb37689cfd --- /dev/null +++ b/libc/test/src/unistd/ftruncate_test.cpp @@ -0,0 +1,71 @@ +//===-- Unittests for ftruncate -------------------------------------------===// +// +// 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/__support/CPP/string_view.h" +#include "src/fcntl/open.h" +#include "src/unistd/close.h" +#include "src/unistd/ftruncate.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 + +namespace cpp = __llvm_libc::cpp; + +TEST(LlvmLibcFtruncateTest, CreateAndTruncate) { + using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; + constexpr const char TEST_FILE[] = "testdata/ftruncate.test"; + constexpr const char WRITE_DATA[] = "hello, ftruncate"; + constexpr size_t WRITE_SIZE = sizeof(WRITE_DATA); + char buf[WRITE_SIZE]; + + // The test strategy is as follows: + // 1. Create a normal file with some data in it. + // 2. Read it to make sure what was written is actually in the file. + // 3. Truncate to 1 byte. + // 4. Try to read more than 1 byte and fail. + errno = 0; + int fd = __llvm_libc::open(TEST_FILE, O_WRONLY | O_CREAT, S_IRWXU); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + ASSERT_EQ(ssize_t(WRITE_SIZE), + __llvm_libc::write(fd, WRITE_DATA, WRITE_SIZE)); + ASSERT_THAT(__llvm_libc::close(fd), Succeeds(0)); + + fd = __llvm_libc::open(TEST_FILE, O_RDONLY); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + ASSERT_EQ(ssize_t(WRITE_SIZE), __llvm_libc::read(fd, buf, WRITE_SIZE)); + ASSERT_EQ(cpp::string_view(buf), cpp::string_view(WRITE_DATA)); + ASSERT_THAT(__llvm_libc::close(fd), Succeeds(0)); + + // For ftruncate operation to succeed, the file should be opened for + // writing. + fd = __llvm_libc::open(TEST_FILE, O_WRONLY); + ASSERT_GT(fd, 0); + ASSERT_EQ(errno, 0); + ASSERT_THAT(__llvm_libc::ftruncate(fd, off_t(1)), Succeeds(0)); + ASSERT_THAT(__llvm_libc::close(fd), Succeeds(0)); + + fd = __llvm_libc::open(TEST_FILE, O_RDONLY); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + ASSERT_EQ(ssize_t(1), __llvm_libc::read(fd, buf, WRITE_SIZE)); + ASSERT_EQ(buf[0], WRITE_DATA[0]); + ASSERT_THAT(__llvm_libc::close(fd), Succeeds(0)); + + ASSERT_THAT(__llvm_libc::unlink(TEST_FILE), Succeeds(0)); +} + +TEST(LlvmLibcFtruncateTest, TruncateBadFD) { + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + ASSERT_THAT(__llvm_libc::ftruncate(1, off_t(1)), Fails(EINVAL)); +} diff --git a/libc/test/src/unistd/truncate_test.cpp b/libc/test/src/unistd/truncate_test.cpp new file mode 100644 index 000000000000..ae7765ec6b55 --- /dev/null +++ b/libc/test/src/unistd/truncate_test.cpp @@ -0,0 +1,67 @@ +//===-- Unittests for truncate --------------------------------------------===// +// +// 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/__support/CPP/string_view.h" +#include "src/fcntl/open.h" +#include "src/unistd/close.h" +#include "src/unistd/read.h" +#include "src/unistd/truncate.h" +#include "src/unistd/unlink.h" +#include "src/unistd/write.h" +#include "test/ErrnoSetterMatcher.h" +#include "utils/UnitTest/Test.h" + +#include + +namespace cpp = __llvm_libc::cpp; + +TEST(LlvmLibcTruncateTest, CreateAndTruncate) { + using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; + constexpr const char TEST_FILE[] = "testdata/truncate.test"; + constexpr const char WRITE_DATA[] = "hello, truncate"; + constexpr size_t WRITE_SIZE = sizeof(WRITE_DATA); + char buf[WRITE_SIZE]; + + // The test strategy is as follows: + // 1. Create a normal file with some data in it. + // 2. Read it to make sure what was written is actually in the file. + // 3. Truncate to 1 byte. + // 4. Try to read more than 1 byte and fail. + errno = 0; + int fd = __llvm_libc::open(TEST_FILE, O_WRONLY | O_CREAT, S_IRWXU); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + ASSERT_EQ(ssize_t(WRITE_SIZE), + __llvm_libc::write(fd, WRITE_DATA, WRITE_SIZE)); + ASSERT_THAT(__llvm_libc::close(fd), Succeeds(0)); + + fd = __llvm_libc::open(TEST_FILE, O_RDONLY); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + ASSERT_EQ(ssize_t(WRITE_SIZE), __llvm_libc::read(fd, buf, WRITE_SIZE)); + ASSERT_EQ(cpp::string_view(buf), cpp::string_view(WRITE_DATA)); + ASSERT_THAT(__llvm_libc::close(fd), Succeeds(0)); + + ASSERT_THAT(__llvm_libc::truncate(TEST_FILE, off_t(1)), Succeeds(0)); + + fd = __llvm_libc::open(TEST_FILE, O_RDONLY); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + ASSERT_EQ(ssize_t(1), __llvm_libc::read(fd, buf, WRITE_SIZE)); + ASSERT_EQ(buf[0], WRITE_DATA[0]); + ASSERT_THAT(__llvm_libc::close(fd), Succeeds(0)); + + ASSERT_THAT(__llvm_libc::unlink(TEST_FILE), Succeeds(0)); +} + +TEST(LlvmLibcTruncateTest, TruncateNonExistentFile) { + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + ASSERT_THAT( + __llvm_libc::truncate("non-existent-dir/non-existent-file", off_t(1)), + Fails(ENOENT)); +}