[libc-commits] [libc] [libc] Add support for SYS_fchmodat2 in fchmodat (PR #223997)
via libc-commits
libc-commits at lists.llvm.org
Wed Sep 16 05:45:22 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libc
Author: Aman Maurya (amanmaurya92)
<details>
<summary>Changes</summary>
### Summary
Linux kernel 6.6 introduced the `SYS_fchmodat2` system call, which accepts a 4th `flags` argument (`int fchmodat2(int dirfd, const char *pathname, mode_t mode, unsigned int flags)`). This allows flags like `AT_SYMLINK_NOFOLLOW` and `AT_EMPTY_PATH` to be passed directly to the kernel.
Following the discussion on #<!-- -->223165, this patch adds support for `SYS_fchmodat2` in `fchmodat`, while providing fallbacks when `SYS_fchmodat2` is not supported.
Fixes #<!-- -->223953
### Changes
- Updated `linux_syscalls::fchmodat` syscall wrapper to accept `flags` and invoke `SYS_fchmodat2` when available.
- Added runtime fallback to `SYS_fchmodat` when `SYS_fchmodat2` returns `ENOSYS`, verifying `flags == 0` and returning `ENOTSUP` if non-zero.
- Added compile-time fallback to `SYS_fchmodat` under `#elif defined(SYS_fchmodat)`.
- Added `libc.hdr.errno_macros` to CMake dependencies for the `fchmodat.h` wrapper, and removed it from the `fchmodat.cpp` entrypoint.
- Simplified `fchmodat.cpp` entrypoint to pass `flags` directly to `linux_syscalls::fchmodat`.
- Updated `fchmodat_test.cpp` to verify `flags` handling (`AT_SYMLINK_NOFOLLOW`, `AT_EMPTY_PATH`, and invalid flags) under both `SYS_fchmodat2` and fallback mode.
### Testing
- Ran `clang-format` on all modified files.
- Built and ran hermetic tests: `libc.test.src.sys.stat.fchmodat_test` (all 3 tests pass: `ChangeAndOpen`, `NonExistentFile`, `Flags`).
Formatted using Gemini.
---
Full diff: https://github.com/llvm/llvm-project/pull/223997.diff
5 Files Affected:
- (modified) libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt (+3-2)
- (modified) libc/src/__support/OSUtil/linux/syscall_wrappers/fchmodat.h (+18-1)
- (modified) libc/src/sys/stat/linux/CMakeLists.txt (-1)
- (modified) libc/src/sys/stat/linux/fchmodat.cpp (+1-6)
- (modified) libc/test/src/sys/stat/fchmodat_test.cpp (+38-7)
``````````diff
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index 17665f87dfcd8..8455b5f405006 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -649,12 +649,13 @@ add_header_library(
HDRS
fchmodat.h
DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.types.mode_t
+ libc.include.sys_syscall
libc.src.__support.OSUtil.osutil
libc.src.__support.common
libc.src.__support.error_or
libc.src.__support.macros.config
- libc.hdr.types.mode_t
- libc.include.sys_syscall
)
add_header_library(
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/fchmodat.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/fchmodat.h
index b62a040e9fe54..558f097687a27 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/fchmodat.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/fchmodat.h
@@ -14,6 +14,7 @@
#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_FCHMODAT_H
#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_FCHMODAT_H
+#include "hdr/errno_macros.h"
#include "hdr/types/mode_t.h"
#include "src/__support/OSUtil/linux/syscall.h" // syscall_impl
#include "src/__support/common.h"
@@ -24,8 +25,24 @@
namespace LIBC_NAMESPACE_DECL {
namespace linux_syscalls {
-LIBC_INLINE ErrorOr<int> fchmodat(int fd, const char *path, mode_t mode) {
+LIBC_INLINE ErrorOr<int> fchmodat(int fd, const char *path, mode_t mode,
+ int flags) {
+#ifdef SYS_fchmodat2
+ int ret = syscall_impl<int>(SYS_fchmodat2, fd, path, mode, flags);
+#if defined(SYS_fchmodat)
+ if (ret == -ENOSYS) {
+ if (flags != 0)
+ return Error(ENOTSUP);
+ ret = syscall_impl<int>(SYS_fchmodat, fd, path, mode);
+ }
+#endif
+#elif defined(SYS_fchmodat)
+ if (flags != 0)
+ return Error(ENOTSUP);
int ret = syscall_impl<int>(SYS_fchmodat, fd, path, mode);
+#else
+#error "fchmodat2 and fchmodat syscalls not available."
+#endif
if (ret < 0)
return Error(-ret);
return ret;
diff --git a/libc/src/sys/stat/linux/CMakeLists.txt b/libc/src/sys/stat/linux/CMakeLists.txt
index b35cd11ea8da5..b087fc927459c 100644
--- a/libc/src/sys/stat/linux/CMakeLists.txt
+++ b/libc/src/sys/stat/linux/CMakeLists.txt
@@ -30,7 +30,6 @@ add_entrypoint_object(
HDRS
../fchmodat.h
DEPENDS
- libc.hdr.errno_macros
libc.hdr.types.mode_t
libc.src.__support.OSUtil.linux.syscall_wrappers.fchmodat
libc.src.errno.errno
diff --git a/libc/src/sys/stat/linux/fchmodat.cpp b/libc/src/sys/stat/linux/fchmodat.cpp
index 29bee319ed0ad..38e02b5d61617 100644
--- a/libc/src/sys/stat/linux/fchmodat.cpp
+++ b/libc/src/sys/stat/linux/fchmodat.cpp
@@ -8,7 +8,6 @@
#include "src/sys/stat/fchmodat.h"
-#include "hdr/errno_macros.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/fchmodat.h"
#include "src/__support/common.h"
#include "src/__support/libc_errno.h"
@@ -18,11 +17,7 @@ namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(int, fchmodat,
(int dirfd, const char *path, mode_t mode, int flags)) {
- if (flags != 0) {
- libc_errno = ENOTSUP;
- return -1;
- }
- auto result = linux_syscalls::fchmodat(dirfd, path, mode);
+ auto result = linux_syscalls::fchmodat(dirfd, path, mode, flags);
if (!result) {
libc_errno = result.error();
return -1;
diff --git a/libc/test/src/sys/stat/fchmodat_test.cpp b/libc/test/src/sys/stat/fchmodat_test.cpp
index 868cdb2bc817a..f068ce131c6cd 100644
--- a/libc/test/src/sys/stat/fchmodat_test.cpp
+++ b/libc/test/src/sys/stat/fchmodat_test.cpp
@@ -67,11 +67,42 @@ TEST_F(LlvmLibcFchmodatTest, NonExistentFile) {
Fails(ENOENT));
}
-TEST_F(LlvmLibcFchmodatTest, UnsupportedFlags) {
- ASSERT_THAT(LIBC_NAMESPACE::fchmodat(AT_FDCWD, "non-existent-file", S_IRUSR,
- AT_SYMLINK_NOFOLLOW),
- Fails(ENOTSUP));
- ASSERT_THAT(
- LIBC_NAMESPACE::fchmodat(AT_FDCWD, "non-existent-file", S_IRUSR, -1),
- Fails(ENOTSUP));
+TEST_F(LlvmLibcFchmodatTest, Flags) {
+ constexpr const char *TEST_FILE = "testdata/fchmodat_flags.test";
+ int fd = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_WRONLY, S_IRWXU);
+ ASSERT_GT(fd, 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+
+ int ret = LIBC_NAMESPACE::fchmodat(AT_FDCWD, TEST_FILE, S_IRUSR,
+ AT_SYMLINK_NOFOLLOW);
+ if (ret == 0) {
+ // SYS_fchmodat2 is supported by the kernel.
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_THAT(LIBC_NAMESPACE::fchmodat(AT_FDCWD, "non-existent-file", S_IRUSR,
+ AT_SYMLINK_NOFOLLOW),
+ Fails(ENOENT));
+ // Test AT_EMPTY_PATH on an open file descriptor.
+ fd = LIBC_NAMESPACE::open(TEST_FILE, O_PATH);
+ ASSERT_GT(fd, 0);
+ ASSERT_ERRNO_SUCCESS();
+ EXPECT_THAT(LIBC_NAMESPACE::fchmodat(fd, "", S_IRWXU, AT_EMPTY_PATH),
+ Succeeds(0));
+ ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fchmodat(AT_FDCWD, TEST_FILE, S_IRUSR, -1),
+ Fails(EINVAL));
+ } else {
+ // Kernel or compile-time headers do not support fchmodat2; non-zero flags
+ // fail with ENOTSUP.
+ ASSERT_ERRNO_EQ(ENOTSUP);
+ ASSERT_THAT(LIBC_NAMESPACE::fchmodat(AT_FDCWD, "non-existent-file", S_IRUSR,
+ AT_SYMLINK_NOFOLLOW),
+ Fails(ENOTSUP));
+ ASSERT_THAT(
+ LIBC_NAMESPACE::fchmodat(AT_FDCWD, TEST_FILE, S_IRUSR, AT_EMPTY_PATH),
+ Fails(ENOTSUP));
+ ASSERT_THAT(LIBC_NAMESPACE::fchmodat(AT_FDCWD, TEST_FILE, S_IRUSR, -1),
+ Fails(ENOTSUP));
+ }
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/223997
More information about the libc-commits
mailing list