Merge "Add fchmodat(AT_SYMLINK_NOFOLLOW) and fchmod O_PATH support"
diff --git a/libc/Android.mk b/libc/Android.mk
index 90f1a12..fe7b116 100644
--- a/libc/Android.mk
+++ b/libc/Android.mk
@@ -116,6 +116,8 @@
     bionic/error.cpp \
     bionic/eventfd_read.cpp \
     bionic/eventfd_write.cpp \
+    bionic/fchmod.cpp \
+    bionic/fchmodat.cpp \
     bionic/ffs.cpp \
     bionic/flockfile.cpp \
     bionic/fork.cpp \
diff --git a/libc/SYSCALLS.TXT b/libc/SYSCALLS.TXT
index 0fa2a1e..d68a00f 100644
--- a/libc/SYSCALLS.TXT
+++ b/libc/SYSCALLS.TXT
@@ -113,7 +113,7 @@
 int         __fcntl64:fcntl64(int, int, void*)  arm,mips,x86
 int         fcntl(int, int, void*)  arm64,mips64,x86_64
 int         flock(int, int)   all
-int         fchmod(int, mode_t)  all
+int         __fchmod:fchmod(int, mode_t)  all
 int         dup(int)  all
 int         pipe2(int*, int) all
 int         dup3(int, int, int)   all
@@ -131,7 +131,7 @@
 
 int __openat:openat(int, const char*, int, mode_t) all
 int faccessat(int, const char*, int, int)  all
-int fchmodat(int, const char*, mode_t, int)  all
+int __fchmodat:fchmodat(int, const char*, mode_t)  all
 int fchownat(int, const char*, uid_t, gid_t, int)  all
 int fstatat64|fstatat:fstatat64(int, const char*, struct stat*, int)   arm,mips,x86
 int fstatat64|fstatat:newfstatat(int, const char*, struct stat*, int)  arm64,x86_64
diff --git a/libc/arch-arm/syscalls/fchmod.S b/libc/arch-arm/syscalls/__fchmod.S
similarity index 89%
rename from libc/arch-arm/syscalls/fchmod.S
rename to libc/arch-arm/syscalls/__fchmod.S
index 5675f0a..ff888a1 100644
--- a/libc/arch-arm/syscalls/fchmod.S
+++ b/libc/arch-arm/syscalls/__fchmod.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(fchmod)
+ENTRY(__fchmod)
     mov     ip, r7
     ldr     r7, =__NR_fchmod
     swi     #0
@@ -11,4 +11,4 @@
     bxls    lr
     neg     r0, r0
     b       __set_errno_internal
-END(fchmod)
+END(__fchmod)
diff --git a/libc/arch-arm/syscalls/fchmodat.S b/libc/arch-arm/syscalls/__fchmodat.S
similarity index 88%
rename from libc/arch-arm/syscalls/fchmodat.S
rename to libc/arch-arm/syscalls/__fchmodat.S
index 3f7e0ee..4d10f00 100644
--- a/libc/arch-arm/syscalls/fchmodat.S
+++ b/libc/arch-arm/syscalls/__fchmodat.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(fchmodat)
+ENTRY(__fchmodat)
     mov     ip, r7
     ldr     r7, =__NR_fchmodat
     swi     #0
@@ -11,4 +11,4 @@
     bxls    lr
     neg     r0, r0
     b       __set_errno_internal
-END(fchmodat)
+END(__fchmodat)
diff --git a/libc/arch-arm64/syscalls/fchmod.S b/libc/arch-arm64/syscalls/__fchmod.S
similarity index 82%
rename from libc/arch-arm64/syscalls/fchmod.S
rename to libc/arch-arm64/syscalls/__fchmod.S
index 83a8060..05c67fc 100644
--- a/libc/arch-arm64/syscalls/fchmod.S
+++ b/libc/arch-arm64/syscalls/__fchmod.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(fchmod)
+ENTRY(__fchmod)
     mov     x8, __NR_fchmod
     svc     #0
 
@@ -11,4 +11,5 @@
     b.hi    __set_errno_internal
 
     ret
-END(fchmod)
+END(__fchmod)
+.hidden __fchmod
diff --git a/libc/arch-arm64/syscalls/fchmodat.S b/libc/arch-arm64/syscalls/__fchmodat.S
similarity index 81%
rename from libc/arch-arm64/syscalls/fchmodat.S
rename to libc/arch-arm64/syscalls/__fchmodat.S
index 8c5bb0e..2406ea8 100644
--- a/libc/arch-arm64/syscalls/fchmodat.S
+++ b/libc/arch-arm64/syscalls/__fchmodat.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(fchmodat)
+ENTRY(__fchmodat)
     mov     x8, __NR_fchmodat
     svc     #0
 
@@ -11,4 +11,5 @@
     b.hi    __set_errno_internal
 
     ret
-END(fchmodat)
+END(__fchmodat)
+.hidden __fchmodat
diff --git a/libc/arch-mips/syscalls/fchmod.S b/libc/arch-mips/syscalls/__fchmod.S
similarity index 89%
rename from libc/arch-mips/syscalls/fchmod.S
rename to libc/arch-mips/syscalls/__fchmod.S
index 2a95cc3..9bc491c 100644
--- a/libc/arch-mips/syscalls/fchmod.S
+++ b/libc/arch-mips/syscalls/__fchmod.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(fchmod)
+ENTRY(__fchmod)
     .set noreorder
     .cpload t9
     li v0, __NR_fchmod
@@ -16,4 +16,4 @@
     j t9
     nop
     .set reorder
-END(fchmod)
+END(__fchmod)
diff --git a/libc/arch-mips/syscalls/fchmodat.S b/libc/arch-mips/syscalls/__fchmodat.S
similarity index 88%
rename from libc/arch-mips/syscalls/fchmodat.S
rename to libc/arch-mips/syscalls/__fchmodat.S
index d9de036..07ea8f8 100644
--- a/libc/arch-mips/syscalls/fchmodat.S
+++ b/libc/arch-mips/syscalls/__fchmodat.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(fchmodat)
+ENTRY(__fchmodat)
     .set noreorder
     .cpload t9
     li v0, __NR_fchmodat
@@ -16,4 +16,4 @@
     j t9
     nop
     .set reorder
-END(fchmodat)
+END(__fchmodat)
diff --git a/libc/arch-mips64/syscalls/fchmod.S b/libc/arch-mips64/syscalls/__fchmod.S
similarity index 88%
rename from libc/arch-mips64/syscalls/fchmod.S
rename to libc/arch-mips64/syscalls/__fchmod.S
index a877b78..94dd0a1 100644
--- a/libc/arch-mips64/syscalls/fchmod.S
+++ b/libc/arch-mips64/syscalls/__fchmod.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(fchmod)
+ENTRY(__fchmod)
     .set push
     .set noreorder
     li v0, __NR_fchmod
@@ -22,4 +22,5 @@
     j t9
     move ra, t0
     .set pop
-END(fchmod)
+END(__fchmod)
+.hidden __fchmod
diff --git a/libc/arch-mips64/syscalls/fchmodat.S b/libc/arch-mips64/syscalls/__fchmodat.S
similarity index 86%
rename from libc/arch-mips64/syscalls/fchmodat.S
rename to libc/arch-mips64/syscalls/__fchmodat.S
index 151492a..79f453f 100644
--- a/libc/arch-mips64/syscalls/fchmodat.S
+++ b/libc/arch-mips64/syscalls/__fchmodat.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(fchmodat)
+ENTRY(__fchmodat)
     .set push
     .set noreorder
     li v0, __NR_fchmodat
@@ -22,4 +22,5 @@
     j t9
     move ra, t0
     .set pop
-END(fchmodat)
+END(__fchmodat)
+.hidden __fchmodat
diff --git a/libc/arch-x86/syscalls/fchmod.S b/libc/arch-x86/syscalls/__fchmod.S
similarity index 94%
rename from libc/arch-x86/syscalls/fchmod.S
rename to libc/arch-x86/syscalls/__fchmod.S
index 37851ff..7ad213e 100644
--- a/libc/arch-x86/syscalls/fchmod.S
+++ b/libc/arch-x86/syscalls/__fchmod.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(fchmod)
+ENTRY(__fchmod)
     pushl   %ebx
     .cfi_def_cfa_offset 8
     .cfi_rel_offset ebx, 0
@@ -23,4 +23,4 @@
     popl    %ecx
     popl    %ebx
     ret
-END(fchmod)
+END(__fchmod)
diff --git a/libc/arch-x86/syscalls/fchmodat.S b/libc/arch-x86/syscalls/__fchmodat.S
similarity index 70%
rename from libc/arch-x86/syscalls/fchmodat.S
rename to libc/arch-x86/syscalls/__fchmodat.S
index f515512..f03c03f 100644
--- a/libc/arch-x86/syscalls/fchmodat.S
+++ b/libc/arch-x86/syscalls/__fchmodat.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(fchmodat)
+ENTRY(__fchmodat)
     pushl   %ebx
     .cfi_def_cfa_offset 8
     .cfi_rel_offset ebx, 0
@@ -12,13 +12,9 @@
     pushl   %edx
     .cfi_adjust_cfa_offset 4
     .cfi_rel_offset edx, 0
-    pushl   %esi
-    .cfi_adjust_cfa_offset 4
-    .cfi_rel_offset esi, 0
-    mov     20(%esp), %ebx
-    mov     24(%esp), %ecx
-    mov     28(%esp), %edx
-    mov     32(%esp), %esi
+    mov     16(%esp), %ebx
+    mov     20(%esp), %ecx
+    mov     24(%esp), %edx
     movl    $__NR_fchmodat, %eax
     int     $0x80
     cmpl    $-MAX_ERRNO, %eax
@@ -28,9 +24,8 @@
     call    __set_errno_internal
     addl    $4, %esp
 1:
-    popl    %esi
     popl    %edx
     popl    %ecx
     popl    %ebx
     ret
-END(fchmodat)
+END(__fchmodat)
diff --git a/libc/arch-x86_64/syscalls/fchmod.S b/libc/arch-x86_64/syscalls/__fchmod.S
similarity index 84%
rename from libc/arch-x86_64/syscalls/fchmod.S
rename to libc/arch-x86_64/syscalls/__fchmod.S
index b35bd21..ba75f74 100644
--- a/libc/arch-x86_64/syscalls/fchmod.S
+++ b/libc/arch-x86_64/syscalls/__fchmod.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(fchmod)
+ENTRY(__fchmod)
     movl    $__NR_fchmod, %eax
     syscall
     cmpq    $-MAX_ERRNO, %rax
@@ -12,4 +12,5 @@
     call    __set_errno_internal
 1:
     ret
-END(fchmod)
+END(__fchmod)
+.hidden __fchmod
diff --git a/libc/arch-x86_64/syscalls/fchmodat.S b/libc/arch-x86_64/syscalls/__fchmodat.S
similarity index 82%
rename from libc/arch-x86_64/syscalls/fchmodat.S
rename to libc/arch-x86_64/syscalls/__fchmodat.S
index 2d78d8e..a8fae95 100644
--- a/libc/arch-x86_64/syscalls/fchmodat.S
+++ b/libc/arch-x86_64/syscalls/__fchmodat.S
@@ -2,8 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(fchmodat)
-    movq    %rcx, %r10
+ENTRY(__fchmodat)
     movl    $__NR_fchmodat, %eax
     syscall
     cmpq    $-MAX_ERRNO, %rax
@@ -13,4 +12,5 @@
     call    __set_errno_internal
 1:
     ret
-END(fchmodat)
+END(__fchmodat)
+.hidden __fchmodat
diff --git a/libc/bionic/fchmod.cpp b/libc/bionic/fchmod.cpp
new file mode 100644
index 0000000..6e020b6
--- /dev/null
+++ b/libc/bionic/fchmod.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 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 <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+
+extern "C" int __fchmod(int, mode_t);
+
+int fchmod(int fd, mode_t mode) {
+  int saved_errno = errno;
+  int result = __fchmod(fd, mode);
+
+  if ((result == 0) || (errno != EBADF)) {
+    return result;
+  }
+
+  // fd could be an O_PATH file descriptor, and the kernel
+  // may not directly support fchmod() on such a file descriptor.
+  // Use /proc/self/fd instead to emulate this support.
+  // https://sourceware.org/bugzilla/show_bug.cgi?id=14578
+  //
+  // As of February 2015, there are no kernels which support fchmod
+  // on an O_PATH file descriptor, and "man open" documents fchmod
+  // on O_PATH file descriptors as returning EBADF.
+  int fd_flag = fcntl(fd, F_GETFL);
+  if ((fd_flag == -1) || ((fd_flag & O_PATH) == 0)) {
+    errno = EBADF;
+    return -1;
+  }
+
+  char buf[40];
+  snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
+  errno = saved_errno;
+  result = chmod(buf, mode);
+  if ((result == -1) && (errno == ELOOP)) {
+    // Linux does not support changing the mode of a symlink.
+    // For fchmodat(AT_SYMLINK_NOFOLLOW), POSIX requires a return
+    // value of ENOTSUP. Assume that's true here too.
+    errno = ENOTSUP;
+  }
+
+  return result;
+}
diff --git a/libc/bionic/fchmodat.cpp b/libc/bionic/fchmodat.cpp
new file mode 100644
index 0000000..c28e15a
--- /dev/null
+++ b/libc/bionic/fchmodat.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 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 <errno.h>
+#include <unistd.h>
+
+#include "private/ErrnoRestorer.h"
+
+extern "C" int __fchmodat(int, const char*, mode_t);
+
+int fchmodat(int dirfd, const char* pathname, mode_t mode, int flags) {
+  if ((flags & ~AT_SYMLINK_NOFOLLOW) != 0) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  if (flags & AT_SYMLINK_NOFOLLOW) {
+    // Emulate AT_SYMLINK_NOFOLLOW using the mechanism described
+    // at https://sourceware.org/bugzilla/show_bug.cgi?id=14578
+    // comment #10
+
+    int fd = openat(dirfd, pathname, O_PATH | O_NOFOLLOW | O_CLOEXEC);
+    if (fd == -1) {
+      return -1; // returns errno from openat
+    }
+
+    // POSIX requires that ENOTSUP be returned when the system
+    // doesn't support setting the mode of a symbolic link.
+    // This is true for all Linux kernels.
+    // We rely on the O_PATH compatibility layer added in the
+    // fchmod() function to get errno correct.
+    int result = fchmod(fd, mode);
+    ErrnoRestorer errno_restorer; // don't let close() clobber errno
+    close(fd);
+    return result;
+  }
+
+  return __fchmodat(dirfd, pathname, mode);
+}
diff --git a/tests/sys_stat_test.cpp b/tests/sys_stat_test.cpp
index e465774..7bbb7c6 100644
--- a/tests/sys_stat_test.cpp
+++ b/tests/sys_stat_test.cpp
@@ -95,3 +95,127 @@
   ASSERT_EQ(0, fstat64(fd, &sb));
   close(fd);
 }
+
+TEST(sys_stat, fchmodat_EFAULT_file) {
+  ASSERT_EQ(-1, fchmodat(AT_FDCWD, (char *) 0x1, 0751, 0));
+  ASSERT_EQ(EFAULT, errno);
+}
+
+TEST(sys_stat, fchmodat_AT_SYMLINK_NOFOLLOW_EFAULT_file) {
+  ASSERT_EQ(-1, fchmodat(AT_FDCWD, (char *) 0x1, 0751, AT_SYMLINK_NOFOLLOW));
+#if defined(__BIONIC__)
+  ASSERT_EQ(EFAULT, errno);
+#else
+  // glibc 2.19 does not implement AT_SYMLINK_NOFOLLOW and always
+  // returns ENOTSUP
+  ASSERT_EQ(ENOTSUP, errno);
+#endif
+}
+
+TEST(sys_stat, fchmodat_bad_flags) {
+  ASSERT_EQ(-1, fchmodat(AT_FDCWD, "/blah", 0751, ~AT_SYMLINK_NOFOLLOW));
+  ASSERT_EQ(EINVAL, errno);
+}
+
+TEST(sys_stat, fchmodat_bad_flags_ALL) {
+  ASSERT_EQ(-1, fchmodat(AT_FDCWD, "/blah", 0751, ~0));
+  ASSERT_EQ(EINVAL, errno);
+}
+
+TEST(sys_stat, fchmodat_nonexistant_file) {
+  ASSERT_EQ(-1, fchmodat(AT_FDCWD, "/blah", 0751, 0));
+  ASSERT_EQ(ENOENT, errno);
+}
+
+TEST(sys_stat, fchmodat_AT_SYMLINK_NOFOLLOW_nonexistant_file) {
+  ASSERT_EQ(-1, fchmodat(AT_FDCWD, "/blah", 0751, AT_SYMLINK_NOFOLLOW));
+#if defined(__BIONIC__)
+  ASSERT_EQ(ENOENT, errno);
+#else
+  // glibc 2.19 does not implement AT_SYMLINK_NOFOLLOW and always
+  // returns ENOTSUP
+  ASSERT_EQ(ENOTSUP, errno);
+#endif
+}
+
+TEST(sys_stat, fchmodat_file) {
+  TemporaryFile tf;
+  struct stat sb;
+
+  ASSERT_EQ(0, fchmodat(AT_FDCWD, tf.filename, 0751, 0));
+  ASSERT_EQ(0, fstat(tf.fd, &sb));
+  ASSERT_TRUE(0751 == (sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)));
+}
+
+TEST(sys_stat, fchmodat_AT_SYMLINK_NOFOLLOW_file) {
+  TemporaryFile tf;
+  errno = 0;
+  int result = fchmodat(AT_FDCWD, tf.filename, 0751, AT_SYMLINK_NOFOLLOW);
+
+#if defined(__BIONIC__)
+  struct stat sb;
+  ASSERT_EQ(0, result);
+  ASSERT_EQ(0, errno);
+  ASSERT_EQ(0, fstat(tf.fd, &sb));
+  ASSERT_TRUE(0751 == (sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)));
+#else
+  // glibc 2.19 does not implement AT_SYMLINK_NOFOLLOW and always
+  // returns ENOTSUP
+  ASSERT_EQ(-1, result);
+  ASSERT_EQ(ENOTSUP, errno);
+#endif
+}
+
+TEST(sys_stat, fchmodat_symlink) {
+  TemporaryFile tf;
+  char linkname[255];
+  struct stat sb;
+
+  snprintf(linkname, sizeof(linkname), "%s.link", tf.filename);
+
+  ASSERT_EQ(0, symlink(tf.filename, linkname));
+  ASSERT_EQ(0, fchmodat(AT_FDCWD, linkname, 0751, 0));
+  ASSERT_EQ(0, fstat(tf.fd, &sb));
+  ASSERT_TRUE(0751 == (sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)));
+  unlink(linkname);
+}
+
+TEST(sys_stat, fchmodat_dangling_symlink) {
+  TemporaryFile tf;
+  char linkname[255];
+  char target[255];
+
+  snprintf(linkname, sizeof(linkname), "%s.link", tf.filename);
+  snprintf(target, sizeof(target), "%s.doesnotexist", tf.filename);
+
+  ASSERT_EQ(0, symlink(target, linkname));
+  ASSERT_EQ(-1, fchmodat(AT_FDCWD, linkname, 0751, 0));
+  ASSERT_EQ(ENOENT, errno);
+  unlink(linkname);
+}
+
+TEST(sys_stat, fchmodat_AT_SYMLINK_NOFOLLOW_with_symlink) {
+  TemporaryFile tf;
+  char linkname[255];
+
+  snprintf(linkname, sizeof(linkname), "%s.link", tf.filename);
+
+  ASSERT_EQ(0, symlink(tf.filename, linkname));
+  ASSERT_EQ(-1, fchmodat(AT_FDCWD, linkname, 0751, AT_SYMLINK_NOFOLLOW));
+  ASSERT_EQ(ENOTSUP, errno);
+  unlink(linkname);
+}
+
+TEST(sys_stat, fchmodat_AT_SYMLINK_NOFOLLOW_with_dangling_symlink) {
+  TemporaryFile tf;
+  char linkname[255];
+  char target[255];
+
+  snprintf(linkname, sizeof(linkname), "%s.link", tf.filename);
+  snprintf(target, sizeof(target), "%s.doesnotexist", tf.filename);
+
+  ASSERT_EQ(0, symlink(target, linkname));
+  ASSERT_EQ(-1, fchmodat(AT_FDCWD, linkname, 0751, AT_SYMLINK_NOFOLLOW));
+  ASSERT_EQ(ENOTSUP, errno);
+  unlink(linkname);
+}