1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 /* */ 30 31 /* 32 * University Copyright- Copyright (c) 1982, 1986, 1988 33 * The Regents of the University of California 34 * All Rights Reserved 35 * 36 * University Acknowledgment- Portions of this document are derived from 37 * software developed by the University of California, Berkeley, and its 38 * contributors. 39 */ 40 41 #pragma ident "%Z%%M% %I% %E% SMI" 42 43 /* 44 * chmod option mode files 45 * where 46 * mode is [ugoa][+-=][rwxXlstugo] or an octal number 47 * option is -R and -f 48 */ 49 50 /* 51 * Note that many convolutions are necessary 52 * due to the re-use of bits between locking 53 * and setgid 54 */ 55 56 #include <unistd.h> 57 #include <stdlib.h> 58 #include <stdio.h> 59 #include <sys/types.h> 60 #include <sys/stat.h> 61 #include <dirent.h> 62 #include <locale.h> 63 #include <string.h> /* strerror() */ 64 #include <stdarg.h> 65 #include <limits.h> 66 #include <errno.h> 67 #include <sys/acl.h> 68 69 static int rflag; 70 static int fflag; 71 72 extern int optind; 73 extern int errno; 74 75 static int mac; /* Alternate to argc (for parseargs) */ 76 static char **mav; /* Alternate to argv (for parseargs) */ 77 78 static char *ms; /* Points to the mode argument */ 79 80 extern mode_t 81 newmode_common(char *ms, mode_t new_mode, mode_t umsk, char *file, char *path, 82 o_mode_t *group_clear_bits, o_mode_t *group_set_bits); 83 84 static int 85 dochmod(char *name, char *path, mode_t umsk), 86 chmodr(char *dir, char *path, mode_t mode, mode_t umsk); 87 88 static void handle_acl(char *name, o_mode_t group_clear_bits, 89 o_mode_t group_set_bits); 90 91 static void 92 usage(void); 93 94 void 95 errmsg(int severity, int code, char *format, ...); 96 97 static void 98 parseargs(int ac, char *av[]); 99 100 int 101 main(int argc, char *argv[]) 102 { 103 int i, c; 104 int status = 0; 105 mode_t umsk; 106 107 (void) setlocale(LC_ALL, ""); 108 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 109 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 110 #endif 111 (void) textdomain(TEXT_DOMAIN); 112 113 parseargs(argc, argv); 114 115 while ((c = getopt(mac, mav, "Rf")) != EOF) { 116 switch (c) { 117 case 'R': 118 rflag++; 119 break; 120 case 'f': 121 fflag++; 122 break; 123 case '?': 124 usage(); 125 exit(2); 126 } 127 } 128 129 /* 130 * Check for sufficient arguments 131 * or a usage error. 132 */ 133 134 mac -= optind; 135 mav += optind; 136 137 if (mac < 2) { 138 usage(); 139 exit(2); 140 } 141 142 ms = mav[0]; 143 144 umsk = umask(0); 145 (void) umask(umsk); 146 147 for (i = 1; i < mac; i++) 148 status += dochmod(mav[i], mav[i], umsk); 149 150 return (fflag ? 0 : status); 151 } 152 153 static int 154 dochmod(char *name, char *path, mode_t umsk) 155 { 156 static struct stat st; 157 int linkflg = 0; 158 o_mode_t group_clear_bits, group_set_bits; 159 160 if (lstat(name, &st) < 0) { 161 errmsg(2, 0, gettext("can't access %s\n"), path); 162 return (1); 163 } 164 165 if ((st.st_mode & S_IFMT) == S_IFLNK) { 166 linkflg = 1; 167 if (stat(name, &st) < 0) { 168 errmsg(2, 0, gettext("can't access %s\n"), path); 169 return (1); 170 } 171 } 172 173 /* Do not recurse if directory is object of symbolic link */ 174 if (rflag && ((st.st_mode & S_IFMT) == S_IFDIR) && !linkflg) 175 return (chmodr(name, path, st.st_mode, umsk)); 176 177 if (chmod(name, newmode_common(ms, st.st_mode, umsk, name, path, 178 &group_clear_bits, &group_set_bits)) == -1) { 179 errmsg(2, 0, gettext("can't change %s\n"), path); 180 return (1); 181 } 182 183 /* 184 * If the group permissions of the file are being modified, 185 * make sure that the file's ACL (if it has one) is 186 * modified also, since chmod is supposed to apply group 187 * permissions changes to both the acl mask and the 188 * general group permissions. 189 */ 190 if (group_clear_bits || group_set_bits) 191 handle_acl(name, group_clear_bits, group_set_bits); 192 193 return (0); 194 } 195 196 197 static int 198 chmodr(char *dir, char *path, mode_t mode, mode_t umsk) 199 { 200 201 DIR *dirp; 202 struct dirent *dp; 203 char savedir[PATH_MAX]; /* dir name to restore */ 204 char currdir[PATH_MAX+1]; /* current dir name + '/' */ 205 char parentdir[PATH_MAX+1]; /* parent dir name + '/' */ 206 int ecode; 207 o_mode_t group_clear_bits, group_set_bits; 208 209 if (getcwd(savedir, PATH_MAX) == 0) 210 errmsg(2, 255, gettext("chmod: could not getcwd %s\n"), 211 savedir); 212 213 /* 214 * Change what we are given before doing it's contents 215 */ 216 if (chmod(dir, newmode_common(ms, mode, umsk, dir, path, 217 &group_clear_bits, &group_set_bits)) < 0) { 218 errmsg(2, 0, gettext("can't change %s\n"), path); 219 return (1); 220 } 221 222 /* 223 * If the group permissions of the file are being modified, 224 * make sure that the file's ACL (if it has one) is 225 * modified also, since chmod is supposed to apply group 226 * permissions changes to both the acl mask and the 227 * general group permissions. 228 */ 229 if (group_clear_bits || group_set_bits) 230 handle_acl(dir, group_clear_bits, group_set_bits); 231 232 if (chdir(dir) < 0) { 233 errmsg(2, 0, "%s/%s: %s\n", savedir, dir, strerror(errno)); 234 return (1); 235 } 236 if ((dirp = opendir(".")) == NULL) { 237 errmsg(2, 0, "%s\n", strerror(errno)); 238 return (1); 239 } 240 dp = readdir(dirp); 241 dp = readdir(dirp); /* read "." and ".." */ 242 ecode = 0; 243 244 /* 245 * Save parent directory path before recursive chmod. 246 * We'll need this for error printing purposes. Add 247 * a trailing '/' to the path except in the case where 248 * the path is just '/' 249 */ 250 251 (void) strcpy(parentdir, path); 252 if (strcmp(path, "/") != 0) 253 (void) strcat(parentdir, "/"); 254 255 for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { 256 (void) strcpy(currdir, parentdir); 257 (void) strcat(currdir, dp->d_name); 258 ecode += dochmod(dp->d_name, currdir, umsk); 259 } 260 (void) closedir(dirp); 261 if (chdir(savedir) < 0) { 262 errmsg(2, 255, gettext("can't change back to %s\n"), savedir); 263 } 264 return (ecode ? 1 : 0); 265 } 266 267 /* PRINTFLIKE3 */ 268 void 269 errmsg(int severity, int code, char *format, ...) 270 { 271 va_list ap; 272 static char *msg[] = { 273 "", 274 "ERROR", 275 "WARNING", 276 "" 277 }; 278 279 va_start(ap, format); 280 281 /* 282 * Always print error message if this is a fatal error (code == 0); 283 * otherwise, print message if fflag == 0 (no -f option specified) 284 */ 285 if (!fflag || (code != 0)) { 286 (void) fprintf(stderr, 287 "chmod: %s: ", gettext(msg[severity])); 288 (void) vfprintf(stderr, format, ap); 289 } 290 291 va_end(ap); 292 293 if (code != 0) 294 exit(fflag ? 0 : code); 295 } 296 297 static void 298 usage(void) 299 { 300 (void) fprintf(stderr, gettext( 301 "usage:\tchmod [-fR] <absolute-mode> file ...\n")); 302 303 (void) fprintf(stderr, gettext( 304 "\tchmod [-fR] <symbolic-mode-list> file ...\n")); 305 306 (void) fprintf(stderr, gettext( 307 "where \t<symbolic-mode-list> is a comma-separated list of\n")); 308 309 (void) fprintf(stderr, gettext( 310 "\t[ugoa]{+|-|=}[rwxXlstugo]\n")); 311 } 312 313 /* 314 * parseargs - generate getopt-friendly argument list for backwards 315 * compatibility with earlier Solaris usage (eg, chmod -w 316 * foo). 317 * 318 * assumes the existence of a static set of alternates to argc and argv, 319 * (namely, mac, and mav[]). 320 * 321 */ 322 323 static void 324 parseargs(int ac, char *av[]) 325 { 326 int i; /* current argument */ 327 int fflag; /* arg list contains "--" */ 328 size_t mav_num; /* number of entries in mav[] */ 329 330 /* 331 * We add an extra argument slot, in case we need to jam a "--" 332 * argument into the list. 333 */ 334 335 mav_num = (size_t)ac+2; 336 337 if ((mav = calloc(mav_num, sizeof (char *))) == NULL) { 338 perror("chmod"); 339 exit(2); 340 } 341 342 /* scan for the use of "--" in the argument list */ 343 344 for (fflag = i = 0; i < ac; i ++) { 345 if (strcmp(av[i], "--") == 0) 346 fflag = 1; 347 } 348 349 /* process the arguments */ 350 351 for (i = mac = 0; 352 (av[i] != (char *)NULL) && (av[i][0] != (char)NULL); 353 i++) { 354 if (!fflag && av[i][0] == '-') { 355 /* 356 * If there is not already a "--" argument specified, 357 * and the argument starts with '-' but does not 358 * contain any of the official option letters, then it 359 * is probably a mode argument beginning with '-'. 360 * Force a "--" into the argument stream in front of 361 * it. 362 */ 363 364 if ((strchr(av[i], 'R') == NULL && 365 strchr(av[i], 'f') == NULL)) { 366 mav[mac++] = strdup("--"); 367 } 368 } 369 370 mav[mac++] = strdup(av[i]); 371 } 372 373 mav[mac] = (char *)NULL; 374 } 375 376 /* 377 * This function is called whenever the group permissions of a file 378 * is being modified. According to the chmod(1) manpage, any 379 * change made to the group permissions must be applied to both 380 * the acl mask and the acl's GROUP_OBJ. The chmod(2) already 381 * set the mask, so this routine needs to make the same change 382 * to the GROUP_OBJ. 383 */ 384 static void 385 handle_acl(char *name, o_mode_t group_clear_bits, o_mode_t group_set_bits) 386 { 387 int aclcnt, n; 388 aclent_t *aclp, *tp; 389 o_mode_t newperm; 390 391 if ((aclcnt = acl(name, GETACLCNT, 0, NULL)) <= MIN_ACL_ENTRIES) 392 return; /* it's just a trivial acl; no need to change it */ 393 394 if ((aclp = (aclent_t *)malloc((sizeof (aclent_t)) * aclcnt)) 395 == NULL) { 396 perror("chmod"); 397 exit(2); 398 } 399 400 if (acl(name, GETACL, aclcnt, aclp) < 0) { 401 free(aclp); 402 (void) fprintf(stderr, "chmod: "); 403 perror(name); 404 return; 405 } 406 407 for (tp = aclp, n = aclcnt; n--; tp++) { 408 if (tp->a_type == GROUP_OBJ) { 409 newperm = tp->a_perm; 410 if (group_clear_bits != 0) 411 newperm &= ~group_clear_bits; 412 if (group_set_bits != 0) 413 newperm |= group_set_bits; 414 if (newperm != tp->a_perm) { 415 tp->a_perm = newperm; 416 if (acl(name, SETACL, aclcnt, aclp) 417 < 0) { 418 (void) fprintf(stderr, "chmod: "); 419 perror(name); 420 } 421 } 422 break; 423 } 424 } 425 free(aclp); 426 } 427