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 @@ -103,6 +103,7 @@ # sys/stat.h entrypoints libc.src.sys.stat.chmod libc.src.sys.stat.fchmod + libc.src.sys.stat.fchmodat libc.src.sys.stat.mkdir libc.src.sys.stat.mkdirat 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 @@ -103,6 +103,7 @@ # sys/stat.h entrypoints libc.src.sys.stat.chmod libc.src.sys.stat.fchmod + libc.src.sys.stat.fchmodat libc.src.sys.stat.mkdir libc.src.sys.stat.mkdirat diff --git a/libc/spec/posix.td b/libc/spec/posix.td --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -406,12 +406,17 @@ FunctionSpec< "chmod", RetValSpec, - [ArgSpec] + [ArgSpec, ArgSpec] >, FunctionSpec< "fchmod", RetValSpec, - [ArgSpec] + [ArgSpec, ArgSpec] + >, + FunctionSpec< + "fchmodat", + RetValSpec, + [ArgSpec, ArgSpec, ArgSpec, ArgSpec] >, FunctionSpec< "mkdir", diff --git a/libc/src/sys/stat/CMakeLists.txt b/libc/src/sys/stat/CMakeLists.txt --- a/libc/src/sys/stat/CMakeLists.txt +++ b/libc/src/sys/stat/CMakeLists.txt @@ -9,6 +9,13 @@ .${LIBC_TARGET_OS}.chmod ) +add_entrypoint_object( + fchmodat + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.fchmodat +) + add_entrypoint_object( fchmod ALIAS diff --git a/libc/src/sys/stat/fchmodat.h b/libc/src/sys/stat/fchmodat.h new file mode 100644 --- /dev/null +++ b/libc/src/sys/stat/fchmodat.h @@ -0,0 +1,20 @@ +//===-- Implementation header for fchmodat ----------------------*- 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_STAT_FCHMODAT_H +#define LLVM_LIBC_SRC_SYS_STAT_FCHMODAT_H + +#include + +namespace __llvm_libc { + +int fchmodat(int dirfd, const char *path, mode_t mode, int flags); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_SYS_STAT_FCHMODAT_H diff --git a/libc/src/sys/stat/linux/CMakeLists.txt b/libc/src/sys/stat/linux/CMakeLists.txt --- a/libc/src/sys/stat/linux/CMakeLists.txt +++ b/libc/src/sys/stat/linux/CMakeLists.txt @@ -25,6 +25,19 @@ libc.src.errno.errno ) +add_entrypoint_object( + fchmodat + SRCS + fchmodat.cpp + HDRS + ../fchmod.h + DEPENDS + libc.include.sys_stat + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) + add_entrypoint_object( mkdir SRCS diff --git a/libc/src/sys/stat/linux/fchmodat.cpp b/libc/src/sys/stat/linux/fchmodat.cpp new file mode 100644 --- /dev/null +++ b/libc/src/sys/stat/linux/fchmodat.cpp @@ -0,0 +1,30 @@ +//===-- Linux implementation of fchmodat ----------------------------------===// +// +// 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/stat/fchmodat.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, fchmodat, + (int dirfd, const char *path, mode_t mode, int flags)) { + long ret = __llvm_libc::syscall(SYS_fchmodat, dirfd, path, mode, flags); + if (ret < 0) { + errno = -ret; + return -1; + } + return 0; +} + +} // namespace __llvm_libc diff --git a/libc/test/src/sys/stat/CMakeLists.txt b/libc/test/src/sys/stat/CMakeLists.txt --- a/libc/test/src/sys/stat/CMakeLists.txt +++ b/libc/test/src/sys/stat/CMakeLists.txt @@ -18,6 +18,22 @@ libc.src.unistd.write ) +add_libc_unittest( + fchmodat_test + SUITE + libc_sys_stat_unittests + SRCS + fchmodat_test.cpp + DEPENDS + libc.include.errno + libc.include.fcntl + libc.include.sys_stat + libc.src.fcntl.open + libc.src.sys.stat.fchmodat + libc.src.unistd.close + libc.src.unistd.write +) + add_libc_unittest( fchmod_test SUITE diff --git a/libc/test/src/sys/stat/fchmodat_test.cpp b/libc/test/src/sys/stat/fchmodat_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/sys/stat/fchmodat_test.cpp @@ -0,0 +1,71 @@ +//===-- Unittests for fchmodat --------------------------------------------===// +// +// 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/sys/stat/fchmodat.h" +#include "src/unistd/close.h" +#include "src/unistd/write.h" +#include "test/ErrnoSetterMatcher.h" +#include "utils/UnitTest/Test.h" +#include "utils/testutils/FDReader.h" + +#include +#include +#include + +TEST(LlvmLibcFchmodatTest, ChangeAndOpen) { + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; + + // The test file is initially writable. We open it for writing and ensure + // that it indeed can be opened for writing. Next, we close the file and + // make it readonly using chmod. We test that chmod actually succeeded by + // trying to open the file for writing and failing. + constexpr const char *TEST_FILE = "testdata/fchmodat.test"; + constexpr const char *TEST_DIR = "testdata"; + constexpr const char *TEST_FILE_BASENAME = "fchmodat.test"; + const char WRITE_DATA[] = "fchmodat test"; + constexpr ssize_t WRITE_SIZE = ssize_t(sizeof(WRITE_DATA)); + errno = 0; + + int fd = __llvm_libc::open(TEST_FILE, O_CREAT | O_WRONLY, S_IRWXU); + ASSERT_GT(fd, 0); + ASSERT_EQ(errno, 0); + ASSERT_EQ(__llvm_libc::write(fd, WRITE_DATA, sizeof(WRITE_DATA)), WRITE_SIZE); + ASSERT_THAT(__llvm_libc::close(fd), Succeeds(0)); + + int dirfd = __llvm_libc::open(TEST_DIR, O_DIRECTORY); + ASSERT_GT(dirfd, 0); + ASSERT_EQ(errno, 0); + + EXPECT_THAT(__llvm_libc::fchmodat(dirfd, TEST_FILE_BASENAME, S_IRUSR, 0), + Succeeds(0)); + + // Opening for writing should fail. + EXPECT_EQ(__llvm_libc::open(TEST_FILE, O_APPEND | O_WRONLY), -1); + EXPECT_NE(errno, 0); + errno = 0; + // But opening for reading should succeed. + fd = __llvm_libc::open(TEST_FILE, O_APPEND | O_RDONLY); + EXPECT_GT(fd, 0); + EXPECT_EQ(errno, 0); + + EXPECT_THAT(__llvm_libc::close(fd), Succeeds(0)); + EXPECT_THAT(__llvm_libc::fchmodat(dirfd, TEST_FILE_BASENAME, S_IRWXU, 0), + Succeeds(0)); + + EXPECT_THAT(__llvm_libc::close(dirfd), Succeeds(0)); +} + +TEST(LlvmLibcFchmodatTest, NonExistentFile) { + errno = 0; + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + ASSERT_THAT(__llvm_libc::fchmodat(AT_FDCWD, "non-existent-file", S_IRUSR, 0), + Fails(ENOENT)); + errno = 0; +} diff --git a/libc/test/src/sys/stat/testdata/CMakeLists.txt b/libc/test/src/sys/stat/testdata/CMakeLists.txt --- a/libc/test/src/sys/stat/testdata/CMakeLists.txt +++ b/libc/test/src/sys/stat/testdata/CMakeLists.txt @@ -1,4 +1,5 @@ # This directory will be used to create test files. file(GENERATE OUTPUT chmod.test CONTENT "chmod test") +file(GENERATE OUTPUT fchmodat.test CONTENT "fchmodat test") file(GENERATE OUTPUT fchmod.test CONTENT "fchmod test")