[libc-commits] [libc] f17893d - [libc] Fix chmod syscall arguments and add symlink test (#226144)
via libc-commits
libc-commits at lists.llvm.org
Fri Sep 25 01:36:38 PDT 2026
Author: Pavel Labath
Date: 2026-09-25T10:36:32+02:00
New Revision: f17893d88ac11a34fc26dacd78f18bde2836334b
URL: https://github.com/llvm/llvm-project/commit/f17893d88ac11a34fc26dacd78f18bde2836334b
DIFF: https://github.com/llvm/llvm-project/commit/f17893d88ac11a34fc26dacd78f18bde2836334b.diff
LOG: [libc] Fix chmod syscall arguments and add symlink test (#226144)
Our Linux chmod wrapper had two bugs in how it invoked syscalls:
- the fchmodat syscall only takes three arguments (dfd, filename, mode),
but we were passing four (with a trailing 0 for flags).
- for fchmodat2, we were (mistakenly) passing AT_SYMLINK_NOFOLLOW, even
though chmod is specified to follow symlinks.
I reorder the checks to prefer fchmodat2 (with flags=0) if available,
and otherwise invoke fchmodat with the three arguments it expects.
I also add a test to verify that chmod follows symlinks and updates the
target file's permissions, and that attempting to chmod a dangling
symlink fails with ENOENT.
Assisted-by: Gemini
Added:
Modified:
libc/src/__support/OSUtil/linux/syscall_wrappers/chmod.h
libc/src/sys/stat/linux/CMakeLists.txt
libc/test/src/sys/stat/CMakeLists.txt
libc/test/src/sys/stat/chmod_test.cpp
Removed:
################################################################################
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/chmod.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/chmod.h
index d2937c61651fa..e936b8ffd798d 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/chmod.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/chmod.h
@@ -26,11 +26,10 @@ namespace LIBC_NAMESPACE_DECL {
namespace linux_syscalls {
LIBC_INLINE ErrorOr<int> chmod(const char *path, mode_t mode) {
-#ifdef SYS_fchmodat
- return syscall_checked<int>(SYS_fchmodat, AT_FDCWD, path, mode, 0);
-#elif defined(SYS_fchmodat2)
- return syscall_checked<int>(SYS_fchmodat2, AT_FDCWD, path, mode, 0,
- AT_SYMLINK_NOFOLLOW);
+#if defined(SYS_fchmodat2)
+ return syscall_checked<int>(SYS_fchmodat2, AT_FDCWD, path, mode, 0);
+#elif defined(SYS_fchmodat)
+ return syscall_checked<int>(SYS_fchmodat, AT_FDCWD, path, mode);
#elif defined(SYS_chmod)
return syscall_checked<int>(SYS_chmod, path, mode);
#else
diff --git a/libc/src/sys/stat/linux/CMakeLists.txt b/libc/src/sys/stat/linux/CMakeLists.txt
index f9fb0cf5ae7b1..78fc6bc1ef03d 100644
--- a/libc/src/sys/stat/linux/CMakeLists.txt
+++ b/libc/src/sys/stat/linux/CMakeLists.txt
@@ -6,9 +6,10 @@ add_entrypoint_object(
../chmod.h
DEPENDS
libc.hdr.types.mode_t
- libc.hdr.fcntl_macros
libc.src.__support.OSUtil.linux.syscall_wrappers.chmod
- libc.src.errno.errno
+ libc.src.__support.common
+ libc.src.__support.libc_errno
+ libc.src.__support.macros.config
)
add_entrypoint_object(
diff --git a/libc/test/src/sys/stat/CMakeLists.txt b/libc/test/src/sys/stat/CMakeLists.txt
index 8847aa89db44a..9851488f67c03 100644
--- a/libc/test/src/sys/stat/CMakeLists.txt
+++ b/libc/test/src/sys/stat/CMakeLists.txt
@@ -9,13 +9,20 @@ add_libc_test(
SRCS
chmod_test.cpp
DEPENDS
+ libc.hdr.errno_macros
libc.hdr.fcntl_macros
libc.hdr.sys_stat_macros
+ libc.hdr.types.mode_t
libc.hdr.types.struct_stat
+ libc.src.__support.CPP.scope
libc.src.errno.errno
libc.src.fcntl.open
libc.src.sys.stat.chmod
+ libc.src.sys.stat.lstat
+ libc.src.sys.stat.stat
libc.src.unistd.close
+ libc.src.unistd.symlink
+ libc.src.unistd.unlink
libc.src.unistd.write
libc.test.UnitTest.ErrnoCheckingTest
libc.test.UnitTest.ErrnoSetterMatcher
diff --git a/libc/test/src/sys/stat/chmod_test.cpp b/libc/test/src/sys/stat/chmod_test.cpp
index 0a8884d7aa84b..34d0ed00e44dc 100644
--- a/libc/test/src/sys/stat/chmod_test.cpp
+++ b/libc/test/src/sys/stat/chmod_test.cpp
@@ -1,17 +1,29 @@
-//===-- Unittests for chmod -----------------------------------------------===//
+//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unit tests for chmod.
+///
+//===----------------------------------------------------------------------===//
+#include "hdr/errno_macros.h"
#include "hdr/fcntl_macros.h"
#include "hdr/sys_stat_macros.h"
+#include "hdr/types/mode_t.h"
#include "hdr/types/struct_stat.h"
+#include "src/__support/CPP/scope.h"
#include "src/fcntl/open.h"
#include "src/sys/stat/chmod.h"
+#include "src/sys/stat/lstat.h"
+#include "src/sys/stat/stat.h"
#include "src/unistd/close.h"
+#include "src/unistd/symlink.h"
+#include "src/unistd/unlink.h"
#include "src/unistd/write.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/ErrnoSetterMatcher.h"
@@ -58,3 +70,62 @@ TEST_F(LlvmLibcChmodTest, NonExistentFile) {
ASSERT_THAT(LIBC_NAMESPACE::chmod("non-existent-file", S_IRUSR),
Fails(ENOENT));
}
+
+TEST_F(LlvmLibcChmodTest, Symlink) {
+ const auto TEST_FILE = libc_make_test_file_path("chmod_symlink_target.test");
+ const auto TEST_FILE_LINK = libc_make_test_file_path("chmod_symlink.test");
+ const auto TEST_DANGLING_LINK =
+ libc_make_test_file_path("chmod_dangling.test");
+
+ int fd =
+ LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_WRONLY | O_TRUNC, S_IRWXU);
+ ASSERT_GT(fd, 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+
+ LIBC_NAMESPACE::cpp::scope_exit cleanup_target(
+ [&] { EXPECT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE), Succeeds(0)); });
+
+ // Ensure initial permissions are 0700.
+ ASSERT_THAT(LIBC_NAMESPACE::chmod(TEST_FILE, S_IRWXU), Succeeds(0));
+
+ struct stat statbuf;
+ ASSERT_THAT(LIBC_NAMESPACE::stat(TEST_FILE, &statbuf), Succeeds(0));
+ ASSERT_EQ(statbuf.st_mode & 0777, static_cast<mode_t>(S_IRWXU));
+
+ // Create symlink pointing to TEST_FILE.
+ ASSERT_THAT(LIBC_NAMESPACE::symlink(TEST_FILE, TEST_FILE_LINK), Succeeds(0));
+
+ LIBC_NAMESPACE::cpp::scope_exit cleanup_link([&] {
+ EXPECT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE_LINK), Succeeds(0));
+ });
+
+ // chmod on the symlink should modify the target file.
+ EXPECT_THAT(LIBC_NAMESPACE::chmod(TEST_FILE_LINK, S_IRUSR), Succeeds(0));
+
+ // Check that the target file mode has been modified.
+ ASSERT_THAT(LIBC_NAMESPACE::stat(TEST_FILE, &statbuf), Succeeds(0));
+ EXPECT_EQ(statbuf.st_mode & 0777, static_cast<mode_t>(S_IRUSR));
+
+ // Check stat via symlink also reflects the new mode.
+ ASSERT_THAT(LIBC_NAMESPACE::stat(TEST_FILE_LINK, &statbuf), Succeeds(0));
+ EXPECT_EQ(statbuf.st_mode & 0777, static_cast<mode_t>(S_IRUSR));
+
+ // Verify that the symlink itself is still a symbolic link.
+ struct stat link_statbuf;
+ ASSERT_THAT(LIBC_NAMESPACE::lstat(TEST_FILE_LINK, &link_statbuf),
+ Succeeds(0));
+ EXPECT_TRUE(S_ISLNK(link_statbuf.st_mode));
+
+ // A dangling symlink should fail with ENOENT.
+ ASSERT_THAT(
+ LIBC_NAMESPACE::symlink("non-existent-target", TEST_DANGLING_LINK),
+ Succeeds(0));
+
+ LIBC_NAMESPACE::cpp::scope_exit cleanup_dangling([&] {
+ EXPECT_THAT(LIBC_NAMESPACE::unlink(TEST_DANGLING_LINK), Succeeds(0));
+ });
+
+ EXPECT_THAT(LIBC_NAMESPACE::chmod(TEST_DANGLING_LINK, S_IRUSR),
+ Fails(ENOENT));
+}
More information about the libc-commits
mailing list