1 /* $NetBSD: chown.c,v 1.12 2023/05/04 18:34:55 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1988, 1993, 1994, 2003 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 __COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994, 2003\ 35 The Regents of the University of California. All rights reserved."); 36 #endif /* not lint */ 37 38 #ifndef lint 39 #if 0 40 static char sccsid[] = "@(#)chown.c 8.8 (Berkeley) 4/4/94"; 41 #else 42 __RCSID("$NetBSD: chown.c,v 1.12 2023/05/04 18:34:55 christos Exp $"); 43 #endif 44 #endif /* not lint */ 45 46 #include <sys/types.h> 47 #include <sys/stat.h> 48 49 #include <ctype.h> 50 #include <dirent.h> 51 #include <err.h> 52 #include <errno.h> 53 #include <locale.h> 54 #include <fts.h> 55 #include <grp.h> 56 #include <pwd.h> 57 #include <stdio.h> 58 #include <stdlib.h> 59 #include <string.h> 60 #include <unistd.h> 61 #include <getopt.h> 62 63 static void a_gid(const char *); 64 static void a_uid(const char *); 65 static id_t id(const char *, const char *); 66 __dead static void usage(void); 67 68 static uid_t uid; 69 static gid_t gid; 70 static int ischown; 71 static const char *myname; 72 73 struct option chown_longopts[] = { 74 { "reference", required_argument, 0, 75 1 }, 76 { NULL, 0, 0, 77 0 }, 78 }; 79 80 int 81 main(int argc, char **argv) 82 { 83 FTS *ftsp; 84 FTSENT *p; 85 int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval, vflag, dflag; 86 char *cp, *reference; 87 int (*change_owner)(const char *, uid_t, gid_t); 88 89 setprogname(*argv); 90 91 (void)setlocale(LC_ALL, ""); 92 93 myname = getprogname(); 94 ischown = (myname[2] == 'o'); 95 reference = NULL; 96 97 Hflag = Lflag = Rflag = fflag = hflag = vflag = dflag = 0; 98 while ((ch = getopt_long(argc, argv, "HLPRdfhv", 99 chown_longopts, NULL)) != -1) 100 switch (ch) { 101 case 1: 102 reference = optarg; 103 break; 104 case 'd': 105 dflag = 1; 106 break; 107 case 'H': 108 Hflag = 1; 109 Lflag = 0; 110 break; 111 case 'L': 112 Lflag = 1; 113 Hflag = 0; 114 break; 115 case 'P': 116 Hflag = Lflag = 0; 117 break; 118 case 'R': 119 Rflag = 1; 120 break; 121 case 'f': 122 fflag = 1; 123 break; 124 case 'h': 125 /* 126 * In System V the -h option causes chown/chgrp to 127 * change the owner/group of the symbolic link. 128 * 4.4BSD's symbolic links didn't have owners/groups, 129 * so it was an undocumented noop. 130 * In NetBSD 1.3, lchown(2) is introduced. 131 */ 132 hflag = 1; 133 break; 134 case 'v': 135 vflag = 1; 136 break; 137 case '?': 138 default: 139 usage(); 140 } 141 argv += optind; 142 argc -= optind; 143 144 if (argc == 0 || (argc == 1 && reference == NULL)) 145 usage(); 146 147 fts_options = FTS_PHYSICAL; 148 if (Rflag) { 149 if (Hflag) 150 fts_options |= FTS_COMFOLLOW; 151 if (Lflag) { 152 if (hflag) 153 errx(EXIT_FAILURE, 154 "the -L and -h options " 155 "may not be specified together."); 156 fts_options &= ~FTS_PHYSICAL; 157 fts_options |= FTS_LOGICAL; 158 } 159 } else if (!hflag) 160 fts_options |= FTS_COMFOLLOW; 161 162 uid = (uid_t)-1; 163 gid = (gid_t)-1; 164 if (reference == NULL) { 165 if (ischown) { 166 if ((cp = strchr(*argv, ':')) != NULL) { 167 *cp++ = '\0'; 168 a_gid(cp); 169 } 170 #ifdef SUPPORT_DOT 171 else if ((cp = strrchr(*argv, '.')) != NULL) { 172 if (uid_from_user(*argv, &uid) == -1) { 173 *cp++ = '\0'; 174 a_gid(cp); 175 } 176 } 177 #endif 178 a_uid(*argv); 179 } else 180 a_gid(*argv); 181 argv++; 182 } else { 183 struct stat st; 184 185 if (stat(reference, &st) == -1) 186 err(EXIT_FAILURE, "Cannot stat `%s'", reference); 187 if (ischown) 188 uid = st.st_uid; 189 gid = st.st_gid; 190 } 191 192 if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL) 193 err(EXIT_FAILURE, "fts_open"); 194 195 for (rval = EXIT_SUCCESS; (p = fts_read(ftsp)) != NULL;) { 196 change_owner = chown; 197 switch (p->fts_info) { 198 case FTS_D: 199 if (!Rflag) /* Change it at FTS_DP. */ 200 fts_set(ftsp, p, FTS_SKIP); 201 continue; 202 case FTS_DNR: /* Warn, chown, continue. */ 203 warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); 204 rval = EXIT_FAILURE; 205 break; 206 case FTS_ERR: /* Warn, continue. */ 207 case FTS_NS: 208 warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); 209 rval = EXIT_FAILURE; 210 continue; 211 case FTS_SL: /* Ignore unless -h. */ 212 /* 213 * All symlinks we found while doing a physical 214 * walk end up here. 215 */ 216 if (!hflag) 217 continue; 218 /* 219 * Note that if we follow a symlink, fts_info is 220 * not FTS_SL but FTS_F or whatever. And we should 221 * use lchown only for FTS_SL and should use chown 222 * for others. 223 */ 224 change_owner = lchown; 225 break; 226 case FTS_SLNONE: /* Ignore. */ 227 /* 228 * The only symlinks that end up here are ones that 229 * don't point to anything. Note that if we are 230 * doing a physical walk, we never reach here unless 231 * we asked to follow explicitly. 232 */ 233 continue; 234 default: 235 break; 236 } 237 238 /* 239 * If dflag was set, and the owner and group are already 240 * set to the right values and the set-user-id and 241 * set-group-id bits are both already clear, skip any 242 * attempt to update. 243 */ 244 if (dflag && 245 (uid == (uid_t)-1 || p->fts_statp->st_uid == uid) && 246 (gid == (gid_t)-1 || p->fts_statp->st_gid == gid) && 247 (p->fts_statp->st_mode & 07000) == 0) 248 continue; 249 250 if ((*change_owner)(p->fts_accpath, uid, gid) && !fflag) { 251 warn("%s", p->fts_path); 252 rval = EXIT_FAILURE; 253 } else { 254 if (vflag) 255 printf("%s\n", p->fts_path); 256 } 257 } 258 if (errno) 259 err(EXIT_FAILURE, "fts_read"); 260 exit(rval); 261 /* NOTREACHED */ 262 } 263 264 static void 265 a_gid(const char *s) 266 { 267 struct group *gr; 268 269 if (*s == '\0') /* Argument was "uid[:.]". */ 270 return; 271 gr = *s == '#' ? NULL : getgrnam(s); 272 if (gr == NULL) 273 gid = id(s, "group"); 274 else 275 gid = gr->gr_gid; 276 return; 277 } 278 279 static void 280 a_uid(const char *s) 281 { 282 if (*s == '\0') /* Argument was "[:.]gid". */ 283 return; 284 if (*s == '#' || uid_from_user(s, &uid) == -1) { 285 uid = id(s, "user"); 286 } 287 return; 288 } 289 290 static id_t 291 id(const char *name, const char *type) 292 { 293 id_t val; 294 char *ep; 295 296 errno = 0; 297 if (*name == '#') 298 name++; 299 val = (id_t)strtoul(name, &ep, 10); 300 if (errno) 301 err(EXIT_FAILURE, "%s", name); 302 if (*ep != '\0') 303 errx(EXIT_FAILURE, "%s: invalid %s name", name, type); 304 return (val); 305 } 306 307 static void 308 usage(void) 309 { 310 311 (void)fprintf(stderr, 312 "Usage: %s [-R [-H | -L | -P]] [-dfhv] %s file ...\n" 313 "\t%s [-R [-H | -L | -P]] [-dfhv] --reference=rfile file ...\n", 314 myname, ischown ? "owner:group|owner|:group" : "group", 315 myname); 316 exit(EXIT_FAILURE); 317 } 318