<sys/stat.h>: add lchmod().
Bug: http://b/361334945
Change-Id: I6a76db7bd2f657ad9b058c2672bf7a4e687017f9
diff --git a/docs/status.md b/docs/status.md
index 174c005..d4bb5ca 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -57,6 +57,7 @@
New libc functions in API level 36:
* `qsort_r`, `sig2str`/`str2sig` (POSIX Issue 8 additions).
+ * GNU/BSD extension `lchmod`.
* New system call wrapper: `mseal` (`<sys/mman.h>`).
New libc functions in V (API level 35):
diff --git a/libc/Android.bp b/libc/Android.bp
index 107db88..a81999d 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -913,6 +913,7 @@
"bionic/isatty.cpp",
"bionic/killpg.cpp",
"bionic/langinfo.cpp",
+ "bionic/lchmod.cpp",
"bionic/lchown.cpp",
"bionic/lfs64_support.cpp",
"bionic/libc_init_common.cpp",
diff --git a/libc/bionic/lchmod.cpp b/libc/bionic/lchmod.cpp
new file mode 100644
index 0000000..928a724
--- /dev/null
+++ b/libc/bionic/lchmod.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+int lchmod(const char* path, mode_t mode) {
+ return fchmodat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW);
+}
diff --git a/libc/include/sys/stat.h b/libc/include/sys/stat.h
index 0ea596b..bb188fe 100644
--- a/libc/include/sys/stat.h
+++ b/libc/include/sys/stat.h
@@ -99,7 +99,13 @@
#endif
+/** The file information returned by fstat()/fstatat()/lstat()/stat(). */
struct stat { __STAT64_BODY };
+
+/**
+ * A synonym for `struct stat` on Android,
+ * provided for source compatibility with other systems.
+ */
struct stat64 { __STAT64_BODY };
#undef __STAT64_BODY
@@ -136,32 +142,145 @@
#define S_TYPEISSHM(__sb) 0
#define S_TYPEISTMO(__sb) 0
+/**
+ * [chmod(2)](https://man7.org/linux/man-pages/man2/chmod.2.html)
+ * changes the mode of a file given a path.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
int chmod(const char* _Nonnull __path, mode_t __mode);
+
+/**
+ * [fchmod(2)](https://man7.org/linux/man-pages/man2/fchmod.2.html)
+ * changes the mode of a file given a file descriptor.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
int fchmod(int __fd, mode_t __mode);
+
+/**
+ * [fchmodat(2)](https://man7.org/linux/man-pages/man2/fchmodat.2.html)
+ * changes the mode of a file.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
+int fchmodat(int __dir_fd, const char* _Nonnull __path, mode_t __mode, int __flags);
+
+/**
+ * [chmod(2)](https://man7.org/linux/man-pages/man2/chmod.2.html)
+ * changes the mode of a file given a path, without following symlinks.
+ *
+ * Equivalent to `fchmodat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW)`.
+ *
+ * Available since API 36.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
+int lchmod(const char* _Nonnull __path, mode_t __mode) __INTRODUCED_IN(36);
+
+/**
+ * [mkdir(2)](https://man7.org/linux/man-pages/man2/mkdir.2.html)
+ * creates a directory.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
int mkdir(const char* _Nonnull __path, mode_t __mode);
+/**
+ * [mkdirat(2)](https://man7.org/linux/man-pages/man2/mkdirat.2.html)
+ * creates a directory.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
+int mkdirat(int __dir_fd, const char* _Nonnull __path, mode_t __mode);
+
+/**
+ * [fstat(2)](https://man7.org/linux/man-pages/man2/fstat.2.html)
+ * gets file status given a file descriptor.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
int fstat(int __fd, struct stat* _Nonnull __buf);
+
+/** An alias for fstat(). */
int fstat64(int __fd, struct stat64* _Nonnull __buf);
+
+/**
+ * [fstatat(2)](https://man7.org/linux/man-pages/man2/fstatat.2.html)
+ * gets file status.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
int fstatat(int __dir_fd, const char* _Nonnull __path, struct stat* _Nonnull __buf, int __flags);
+
+/** An alias for fstatat(). */
int fstatat64(int __dir_fd, const char* _Nonnull __path, struct stat64* _Nonnull __buf, int __flags);
+
+/**
+ * [lstat(2)](https://man7.org/linux/man-pages/man2/lstat.2.html)
+ * gets file status given a path, without following symlinks.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
int lstat(const char* _Nonnull __path, struct stat* _Nonnull __buf);
+
+/** An alias for lstat(). */
int lstat64(const char* _Nonnull __path, struct stat64* _Nonnull __buf);
+
+/**
+ * [stat(2)](https://man7.org/linux/man-pages/man2/stat.2.html)
+ * gets file status given a path.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
int stat(const char* _Nonnull __path, struct stat* _Nonnull __buf);
+
+/** An alias for stat(). */
int stat64(const char* _Nonnull __path, struct stat64* _Nonnull __buf);
+/**
+ * [mknod(2)](https://man7.org/linux/man-pages/man2/mknod.2.html)
+ * creates a directory, special, or regular file.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
int mknod(const char* _Nonnull __path, mode_t __mode, dev_t __dev);
+
+/**
+ * [mknodat(2)](https://man7.org/linux/man-pages/man2/mknodat.2.html)
+ * creates a directory, special, or regular file.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
+int mknodat(int __dir_fd, const char* _Nonnull __path, mode_t __mode, dev_t __dev);
+
+/**
+ * [umask(2)](https://man7.org/linux/man-pages/man2/umask.2.html)
+ * gets and sets the process-wide file mode creation mask.
+ *
+ * Returns the previous file mode creation mask.
+ */
mode_t umask(mode_t __mask);
#if defined(__BIONIC_INCLUDE_FORTIFY_HEADERS)
#include <bits/fortify/stat.h>
#endif
+/**
+ * [mkfifo(2)](https://man7.org/linux/man-pages/man2/mkfifo.2.html)
+ * creates a FIFO.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
int mkfifo(const char* _Nonnull __path, mode_t __mode);
-int mkfifoat(int __dir_fd, const char* _Nonnull __path, mode_t __mode) __INTRODUCED_IN(23);
-int fchmodat(int __dir_fd, const char* _Nonnull __path, mode_t __mode, int __flags);
-int mkdirat(int __dir_fd, const char* _Nonnull __path, mode_t __mode);
-int mknodat(int __dir_fd, const char* _Nonnull __path, mode_t __mode, dev_t __dev);
+/**
+ * [mkfifoat(2)](https://man7.org/linux/man-pages/man2/mkfifoat.2.html)
+ * creates a FIFO.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
+int mkfifoat(int __dir_fd, const char* _Nonnull __path, mode_t __mode) __INTRODUCED_IN(23);
/**
* Used in the tv_nsec field of an argument to utimensat()/futimens()
diff --git a/libc/libc.map.txt b/libc/libc.map.txt
index 8bb84eb..0d2e42f 100644
--- a/libc/libc.map.txt
+++ b/libc/libc.map.txt
@@ -591,6 +591,7 @@
killpg;
klogctl;
labs;
+ lchmod; # introduced=36
lchown;
lcong48; # introduced=23
ldexp;
diff --git a/tests/sys_stat_test.cpp b/tests/sys_stat_test.cpp
index 126f469..50c50df 100644
--- a/tests/sys_stat_test.cpp
+++ b/tests/sys_stat_test.cpp
@@ -151,12 +151,12 @@
ASSERT_ERRNO(EINVAL);
}
-TEST(sys_stat, fchmodat_nonexistant_file) {
+TEST(sys_stat, fchmodat_nonexistent_file) {
ASSERT_EQ(-1, fchmodat(AT_FDCWD, "/blah", 0751, 0));
ASSERT_ERRNO(ENOENT);
}
-TEST(sys_stat, fchmodat_AT_SYMLINK_NOFOLLOW_nonexistant_file) {
+TEST(sys_stat, fchmodat_AT_SYMLINK_NOFOLLOW_nonexistent_file) {
ASSERT_EQ(-1, fchmodat(AT_FDCWD, "/blah", 0751, AT_SYMLINK_NOFOLLOW));
#if defined(__BIONIC__)
ASSERT_ERRNO(ENOENT);
@@ -305,7 +305,7 @@
ASSERT_EQ(0, faccessat(AT_FDCWD, "/dev/null", R_OK|W_OK, 0));
}
-TEST(sys_stat, faccessat_nonexistant) {
+TEST(sys_stat, faccessat_nonexistent) {
ASSERT_EQ(-1, faccessat(AT_FDCWD, "/blah", F_OK, AT_SYMLINK_NOFOLLOW));
#if defined(__BIONIC__)
// Android doesn't support AT_SYMLINK_NOFOLLOW
@@ -314,3 +314,26 @@
ASSERT_ERRNO(ENOENT);
#endif
}
+
+TEST(sys_stat, lchmod) {
+ TemporaryFile tf;
+ struct stat tf_sb;
+ ASSERT_EQ(0, stat(tf.path, &tf_sb));
+
+ char linkname[255];
+ snprintf(linkname, sizeof(linkname), "%s.link", tf.path);
+
+ ASSERT_EQ(0, symlink(tf.path, linkname));
+ int result = lchmod(linkname, 0751);
+ // Whether or not chmod is allowed on a symlink depends on the kernel.
+ if (result == 0) {
+ AssertSymlinkModeEquals(0751, linkname);
+ } else {
+ ASSERT_EQ(-1, result);
+ ASSERT_ERRNO(ENOTSUP);
+ }
+
+ // The target file mode shouldn't be modified.
+ AssertFileModeEquals(tf_sb.st_mode, tf.path);
+ unlink(linkname);
+}