diff --git a/libc/Android.mk b/libc/Android.mk index 90f1a1213..fe7b1160f 100644 --- a/libc/Android.mk +++ b/libc/Android.mk @@ -116,6 +116,8 @@ libc_bionic_src_files := \ 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 0fa2a1e10..d68a00fd1 100644 --- a/libc/SYSCALLS.TXT +++ b/libc/SYSCALLS.TXT @@ -113,7 +113,7 @@ int writev(int, const struct iovec*, int) all 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 __getdents64:getdents64(unsigned int, struct dirent*, unsigned int) arm,ar 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 5675f0a33..ff888a17e 100644 --- a/libc/arch-arm/syscalls/fchmod.S +++ b/libc/arch-arm/syscalls/__fchmod.S @@ -2,7 +2,7 @@ #include -ENTRY(fchmod) +ENTRY(__fchmod) mov ip, r7 ldr r7, =__NR_fchmod swi #0 @@ -11,4 +11,4 @@ ENTRY(fchmod) 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 3f7e0ee24..4d10f00b8 100644 --- a/libc/arch-arm/syscalls/fchmodat.S +++ b/libc/arch-arm/syscalls/__fchmodat.S @@ -2,7 +2,7 @@ #include -ENTRY(fchmodat) +ENTRY(__fchmodat) mov ip, r7 ldr r7, =__NR_fchmodat swi #0 @@ -11,4 +11,4 @@ ENTRY(fchmodat) 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 83a80607d..05c67fc37 100644 --- a/libc/arch-arm64/syscalls/fchmod.S +++ b/libc/arch-arm64/syscalls/__fchmod.S @@ -2,7 +2,7 @@ #include -ENTRY(fchmod) +ENTRY(__fchmod) mov x8, __NR_fchmod svc #0 @@ -11,4 +11,5 @@ ENTRY(fchmod) 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 8c5bb0e13..2406ea8e3 100644 --- a/libc/arch-arm64/syscalls/fchmodat.S +++ b/libc/arch-arm64/syscalls/__fchmodat.S @@ -2,7 +2,7 @@ #include -ENTRY(fchmodat) +ENTRY(__fchmodat) mov x8, __NR_fchmodat svc #0 @@ -11,4 +11,5 @@ ENTRY(fchmodat) 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 2a95cc30d..9bc491c21 100644 --- a/libc/arch-mips/syscalls/fchmod.S +++ b/libc/arch-mips/syscalls/__fchmod.S @@ -2,7 +2,7 @@ #include -ENTRY(fchmod) +ENTRY(__fchmod) .set noreorder .cpload t9 li v0, __NR_fchmod @@ -16,4 +16,4 @@ ENTRY(fchmod) 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 d9de036dd..07ea8f8b3 100644 --- a/libc/arch-mips/syscalls/fchmodat.S +++ b/libc/arch-mips/syscalls/__fchmodat.S @@ -2,7 +2,7 @@ #include -ENTRY(fchmodat) +ENTRY(__fchmodat) .set noreorder .cpload t9 li v0, __NR_fchmodat @@ -16,4 +16,4 @@ ENTRY(fchmodat) 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 a877b781f..94dd0a1e7 100644 --- a/libc/arch-mips64/syscalls/fchmod.S +++ b/libc/arch-mips64/syscalls/__fchmod.S @@ -2,7 +2,7 @@ #include -ENTRY(fchmod) +ENTRY(__fchmod) .set push .set noreorder li v0, __NR_fchmod @@ -22,4 +22,5 @@ ENTRY(fchmod) 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 151492aa7..79f453f4c 100644 --- a/libc/arch-mips64/syscalls/fchmodat.S +++ b/libc/arch-mips64/syscalls/__fchmodat.S @@ -2,7 +2,7 @@ #include -ENTRY(fchmodat) +ENTRY(__fchmodat) .set push .set noreorder li v0, __NR_fchmodat @@ -22,4 +22,5 @@ ENTRY(fchmodat) 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 37851ff47..7ad213e7e 100644 --- a/libc/arch-x86/syscalls/fchmod.S +++ b/libc/arch-x86/syscalls/__fchmod.S @@ -2,7 +2,7 @@ #include -ENTRY(fchmod) +ENTRY(__fchmod) pushl %ebx .cfi_def_cfa_offset 8 .cfi_rel_offset ebx, 0 @@ -23,4 +23,4 @@ ENTRY(fchmod) 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 f5155120d..f03c03f41 100644 --- a/libc/arch-x86/syscalls/fchmodat.S +++ b/libc/arch-x86/syscalls/__fchmodat.S @@ -2,7 +2,7 @@ #include -ENTRY(fchmodat) +ENTRY(__fchmodat) pushl %ebx .cfi_def_cfa_offset 8 .cfi_rel_offset ebx, 0 @@ -12,13 +12,9 @@ ENTRY(fchmodat) 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 @@ ENTRY(fchmodat) 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 b35bd21ac..ba75f7487 100644 --- a/libc/arch-x86_64/syscalls/fchmod.S +++ b/libc/arch-x86_64/syscalls/__fchmod.S @@ -2,7 +2,7 @@ #include -ENTRY(fchmod) +ENTRY(__fchmod) movl $__NR_fchmod, %eax syscall cmpq $-MAX_ERRNO, %rax @@ -12,4 +12,5 @@ ENTRY(fchmod) 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 2d78d8e81..a8fae9525 100644 --- a/libc/arch-x86_64/syscalls/fchmodat.S +++ b/libc/arch-x86_64/syscalls/__fchmodat.S @@ -2,8 +2,7 @@ #include -ENTRY(fchmodat) - movq %rcx, %r10 +ENTRY(__fchmodat) movl $__NR_fchmodat, %eax syscall cmpq $-MAX_ERRNO, %rax @@ -13,4 +12,5 @@ ENTRY(fchmodat) 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 000000000..6e020b6be --- /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 +#include +#include +#include +#include +#include + +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 000000000..c28e15ac7 --- /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 +#include +#include +#include +#include + +#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 e46577469..7bbb7c665 100644 --- a/tests/sys_stat_test.cpp +++ b/tests/sys_stat_test.cpp @@ -95,3 +95,127 @@ TEST(sys_stat, stat64_lstat64_fstat64) { 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); +}