1 1.1 christos /* provide consistent interface to chown for systems that don't interpret 2 1.1 christos an ID of -1 as meaning "don't change the corresponding ID". 3 1.1 christos 4 1.1 christos Copyright (C) 1997, 2004-2007, 2009-2022 Free Software Foundation, Inc. 5 1.1 christos 6 1.1 christos This file is free software: you can redistribute it and/or modify 7 1.1 christos it under the terms of the GNU Lesser General Public License as 8 1.1 christos published by the Free Software Foundation; either version 2.1 of the 9 1.1 christos License, or (at your option) any later version. 10 1.1 christos 11 1.1 christos This file is distributed in the hope that it will be useful, 12 1.1 christos but WITHOUT ANY WARRANTY; without even the implied warranty of 13 1.1 christos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 1.1 christos GNU Lesser General Public License for more details. 15 1.1 christos 16 1.1 christos You should have received a copy of the GNU Lesser General Public License 17 1.1 christos along with this program. If not, see <https://www.gnu.org/licenses/>. */ 18 1.1 christos 19 1.1 christos /* written by Jim Meyering */ 20 1.1 christos 21 1.1 christos #include <config.h> 22 1.1 christos 23 1.1 christos /* Specification. */ 24 1.1 christos #include <unistd.h> 25 1.1 christos 26 1.1 christos #include <errno.h> 27 1.1 christos #include <fcntl.h> 28 1.1 christos #include <stdbool.h> 29 1.1 christos #include <string.h> 30 1.1 christos #include <sys/stat.h> 31 1.1 christos 32 1.1 christos #if !HAVE_CHOWN 33 1.1 christos 34 1.1 christos /* Simple stub that always fails with ENOSYS, for mingw. */ 35 1.1 christos int 36 1.1 christos chown (_GL_UNUSED const char *file, _GL_UNUSED uid_t uid, 37 1.1 christos _GL_UNUSED gid_t gid) 38 1.1 christos { 39 1.1 christos errno = ENOSYS; 40 1.1 christos return -1; 41 1.1 christos } 42 1.1 christos 43 1.1 christos #else /* HAVE_CHOWN */ 44 1.1 christos 45 1.1 christos /* Below we refer to the system's chown(). */ 46 1.1 christos # undef chown 47 1.1 christos 48 1.1 christos /* Provide a more-closely POSIX-conforming version of chown on 49 1.1 christos systems with one or both of the following problems: 50 1.1 christos - chown doesn't treat an ID of -1 as meaning 51 1.1 christos "don't change the corresponding ID". 52 1.1 christos - chown doesn't dereference symlinks. */ 53 1.1 christos 54 1.1 christos int 55 1.1 christos rpl_chown (const char *file, uid_t uid, gid_t gid) 56 1.1 christos { 57 1.1 christos struct stat st; 58 1.1 christos bool stat_valid = false; 59 1.1 christos int result; 60 1.1 christos 61 1.1 christos # if CHOWN_CHANGE_TIME_BUG 62 1.1 christos if (gid != (gid_t) -1 || uid != (uid_t) -1) 63 1.1 christos { 64 1.1 christos if (stat (file, &st)) 65 1.1 christos return -1; 66 1.1 christos stat_valid = true; 67 1.1 christos } 68 1.1 christos # endif 69 1.1 christos 70 1.1 christos # if CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE 71 1.1 christos if (gid == (gid_t) -1 || uid == (uid_t) -1) 72 1.1 christos { 73 1.1 christos /* Stat file to get id(s) that should remain unchanged. */ 74 1.1 christos if (!stat_valid && stat (file, &st)) 75 1.1 christos return -1; 76 1.1 christos if (gid == (gid_t) -1) 77 1.1 christos gid = st.st_gid; 78 1.1 christos if (uid == (uid_t) -1) 79 1.1 christos uid = st.st_uid; 80 1.1 christos } 81 1.1 christos # endif 82 1.1 christos 83 1.1 christos # if CHOWN_MODIFIES_SYMLINK 84 1.1 christos { 85 1.1 christos /* Handle the case in which the system-supplied chown function 86 1.1 christos does *not* follow symlinks. Instead, it changes permissions 87 1.1 christos on the symlink itself. To work around that, we open the 88 1.1 christos file (but this can fail due to lack of read or write permission) and 89 1.1 christos use fchown on the resulting descriptor. */ 90 1.1 christos int open_flags = O_NONBLOCK | O_NOCTTY | O_CLOEXEC; 91 1.1 christos int fd = open (file, O_RDONLY | open_flags); 92 1.1 christos if (0 <= fd 93 1.1 christos || (errno == EACCES 94 1.1 christos && 0 <= (fd = open (file, O_WRONLY | open_flags)))) 95 1.1 christos { 96 1.1 christos int saved_errno; 97 1.1 christos bool fchown_socket_failure; 98 1.1 christos 99 1.1 christos result = fchown (fd, uid, gid); 100 1.1 christos saved_errno = errno; 101 1.1 christos 102 1.1 christos /* POSIX says fchown can fail with errno == EINVAL on sockets 103 1.1 christos and pipes, so fall back on chown in that case. */ 104 1.1 christos fchown_socket_failure = 105 1.1 christos (result != 0 && saved_errno == EINVAL 106 1.1 christos && fstat (fd, &st) == 0 107 1.1 christos && (S_ISFIFO (st.st_mode) || S_ISSOCK (st.st_mode))); 108 1.1 christos 109 1.1 christos close (fd); 110 1.1 christos 111 1.1 christos if (! fchown_socket_failure) 112 1.1 christos { 113 1.1 christos errno = saved_errno; 114 1.1 christos return result; 115 1.1 christos } 116 1.1 christos } 117 1.1 christos else if (errno != EACCES) 118 1.1 christos return -1; 119 1.1 christos } 120 1.1 christos # endif 121 1.1 christos 122 1.1 christos # if CHOWN_TRAILING_SLASH_BUG 123 1.1 christos if (!stat_valid) 124 1.1 christos { 125 1.1 christos size_t len = strlen (file); 126 1.1 christos if (len && file[len - 1] == '/' && stat (file, &st)) 127 1.1 christos return -1; 128 1.1 christos } 129 1.1 christos # endif 130 1.1 christos 131 1.1 christos result = chown (file, uid, gid); 132 1.1 christos 133 1.1 christos # if CHOWN_CHANGE_TIME_BUG 134 1.1 christos if (result == 0 && stat_valid 135 1.1 christos && (uid == st.st_uid || uid == (uid_t) -1) 136 1.1 christos && (gid == st.st_gid || gid == (gid_t) -1)) 137 1.1 christos { 138 1.1 christos /* No change in ownership, but at least one argument was not -1, 139 1.1 christos so we are required to update ctime. Since chown succeeded, 140 1.1 christos we assume that chmod will do likewise. Fortunately, on all 141 1.1 christos known systems where a 'no-op' chown skips the ctime update, a 142 1.1 christos 'no-op' chmod still does the trick. */ 143 1.1 christos result = chmod (file, st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO 144 1.1 christos | S_ISUID | S_ISGID | S_ISVTX)); 145 1.1 christos } 146 1.1 christos # endif 147 1.1 christos 148 1.1 christos return result; 149 1.1 christos } 150 1.1 christos 151 1.1 christos #endif /* HAVE_CHOWN */ 152