1 /* Implement lchmod on platforms where it does not work correctly. 2 3 Copyright 2020-2021 Free Software Foundation, Inc. 4 5 This program is free software: you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <https://www.gnu.org/licenses/>. */ 17 18 /* written by Paul Eggert */ 19 20 #include <config.h> 21 22 /* Specification. */ 23 #include <sys/stat.h> 24 25 #include <errno.h> 26 #include <fcntl.h> 27 #include <stdio.h> 28 #include <unistd.h> 29 30 #ifdef __osf__ 31 /* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc 32 eliminates this include because of the preliminary #include <sys/stat.h> 33 above. */ 34 # include "sys/stat.h" 35 #else 36 # include <sys/stat.h> 37 #endif 38 39 #include <intprops.h> 40 41 /* Work like chmod, except when FILE is a symbolic link. 42 In that case, on systems where permissions on symbolic links are unsupported 43 (such as Linux), set errno to EOPNOTSUPP and return -1. */ 44 45 int 46 lchmod (char const *file, mode_t mode) /* */ 47 { 48 #if defined O_PATH && defined AT_EMPTY_PATH 49 /* Open a file descriptor with O_NOFOLLOW, to make sure we don't 50 follow symbolic links, if /proc is mounted. O_PATH is used to 51 avoid a failure if the file is not readable. 52 Cf. <https://sourceware.org/bugzilla/show_bug.cgi?id=14578> */ 53 int fd = open (file, O_PATH | O_NOFOLLOW | O_CLOEXEC); 54 if (fd < 0) 55 return fd; 56 57 /* Up to Linux 5.3 at least, when FILE refers to a symbolic link, the 58 chmod call below will change the permissions of the symbolic link 59 - which is undesired - and on many file systems (ext4, btrfs, jfs, 60 xfs, ..., but not reiserfs) fail with error EOPNOTSUPP - which is 61 misleading. Therefore test for a symbolic link explicitly. 62 Use fstatat because fstat does not work on O_PATH descriptors 63 before Linux 3.6. */ 64 struct stat st; 65 if (fstatat (fd, "", &st, AT_EMPTY_PATH) != 0) 66 { 67 int stat_errno = errno; 68 close (fd); 69 errno = stat_errno; 70 return -1; 71 } 72 if (S_ISLNK (st.st_mode)) 73 { 74 close (fd); 75 errno = EOPNOTSUPP; 76 return -1; 77 } 78 79 # if defined __linux__ || defined __ANDROID__ || defined __CYGWIN__ 80 static char const fmt[] = "/proc/self/fd/%d"; 81 char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)]; 82 sprintf (buf, fmt, fd); 83 int chmod_result = chmod (buf, mode); 84 int chmod_errno = errno; 85 close (fd); 86 if (chmod_result == 0) 87 return chmod_result; 88 if (chmod_errno != ENOENT) 89 { 90 errno = chmod_errno; 91 return chmod_result; 92 } 93 # endif 94 /* /proc is not mounted or would not work as in GNU/Linux. */ 95 96 #elif HAVE_LSTAT 97 struct stat st; 98 int lstat_result = lstat (file, &st); 99 if (lstat_result != 0) 100 return lstat_result; 101 if (S_ISLNK (st.st_mode)) 102 { 103 errno = EOPNOTSUPP; 104 return -1; 105 } 106 #endif 107 108 /* Fall back on chmod, despite a possible race. */ 109 return chmod (file, mode); 110 }