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 /* 28 * Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T 29 * All Rights Reserved 30 */ 31 32 /* 33 * University Copyright- Copyright (c) 1982, 1986, 1988 34 * The Regents of the University of California 35 * All Rights Reserved 36 * 37 * University Acknowledgment- Portions of this document are derived from 38 * software developed by the University of California, Berkeley, and its 39 * contributors. 40 */ 41 42 #pragma ident "%Z%%M% %I% %E% SMI" 43 44 /* 45 * chgrp [-fhR] gid file ... 46 * chgrp -R [-f] [-H|-L|-P] gid file ... 47 */ 48 49 #include <stdio.h> 50 #include <ctype.h> 51 #include <sys/types.h> 52 #include <sys/stat.h> 53 #include <sys/avl.h> 54 #include <grp.h> 55 #include <dirent.h> 56 #include <unistd.h> 57 #include <stdlib.h> 58 #include <locale.h> 59 #include <libcmdutils.h> 60 #include <errno.h> 61 #include <strings.h> 62 63 static struct group *gr; 64 static struct stat stbuf; 65 static struct stat stbuf2; 66 static gid_t gid; 67 static int hflag = 0, 68 fflag = 0, 69 rflag = 0, 70 Hflag = 0, 71 Lflag = 0, 72 Pflag = 0; 73 static int status = 0; /* total number of errors received */ 74 75 static avl_tree_t *tree; /* search tree to store inode data */ 76 77 static void usage(void); 78 static int isnumber(char *); 79 static int Perror(char *); 80 static void chgrpr(char *, gid_t); 81 82 #ifdef XPG4 83 /* 84 * Check to see if we are to follow symlinks specified on the command line. 85 * This assumes we've already checked to make sure neither -h or -P was 86 * specified, so we are just looking to see if -R -L, or -R -H was specified, 87 * or, since -R has the same behavior as -R -L, if -R was specified by itself. 88 * Therefore, all we really need to check for is if -R was specified. 89 */ 90 #define FOLLOW_CL_LINKS (rflag) 91 #else 92 /* 93 * Check to see if we are to follow symlinks specified on the command line. 94 * This assumes we've already checked to make sure neither -h or -P was 95 * specified, so we are just looking to see if -R -L, or -R -H was specified. 96 * Note: -R by itself will change the group of a directory referenced by a 97 * symlink however it will not follow the symlink to any other part of the 98 * file hierarchy. 99 */ 100 #define FOLLOW_CL_LINKS (rflag && (Hflag || Lflag)) 101 #endif 102 103 #ifdef XPG4 104 /* 105 * Follow symlinks when traversing directories. Since -R behaves the 106 * same as -R -L, we always want to follow symlinks to other parts 107 * of the file hierarchy unless -H was specified. 108 */ 109 #define FOLLOW_D_LINKS (!Hflag) 110 #else 111 /* 112 * Follow symlinks when traversing directories. Only follow symlinks 113 * to other parts of the file hierarchy if -L was specified. 114 */ 115 #define FOLLOW_D_LINKS (Lflag) 116 #endif 117 118 #define CHOWN(f, u, g) if (chown(f, u, g) < 0) { \ 119 status += Perror(f); \ 120 } 121 122 #define LCHOWN(f, u, g) if (lchown(f, u, g) < 0) { \ 123 status += Perror(f); \ 124 } 125 /* 126 * We're ignoring errors here because preserving the SET[UG]ID bits is just 127 * a courtesy. This is only used on directories. 128 */ 129 #define SETUGID_PRESERVE(dir, mode) \ 130 if (((mode) & (S_ISGID|S_ISUID)) != 0) \ 131 (void) chmod((dir), (mode) & ~S_IFMT) 132 133 extern int optind; 134 135 136 int 137 main(int argc, char *argv[]) 138 { 139 int c; 140 141 /* set the locale for only the messages system (all else is clean) */ 142 143 (void) setlocale(LC_ALL, ""); 144 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 145 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 146 #endif 147 (void) textdomain(TEXT_DOMAIN); 148 149 while ((c = getopt(argc, argv, "RhfHLP")) != EOF) 150 switch (c) { 151 case 'R': 152 rflag++; 153 break; 154 case 'h': 155 hflag++; 156 break; 157 case 'f': 158 fflag++; 159 break; 160 case 'H': 161 /* 162 * If more than one of -H, -L, and -P 163 * are specified, only the last option 164 * specified determines the behavior of 165 * chgrp. In addition, make [-H|-L] 166 * mutually exclusive of -h. 167 */ 168 Lflag = Pflag = 0; 169 Hflag++; 170 break; 171 case 'L': 172 Hflag = Pflag = 0; 173 Lflag++; 174 break; 175 case 'P': 176 Hflag = Lflag = 0; 177 Pflag++; 178 break; 179 default: 180 usage(); 181 } 182 183 /* 184 * Check for sufficient arguments 185 * or a usage error. 186 */ 187 argc -= optind; 188 argv = &argv[optind]; 189 190 if ((argc < 2) || 191 ((Hflag || Lflag || Pflag) && !rflag) || 192 ((Hflag || Lflag || Pflag) && hflag)) { 193 usage(); 194 } 195 196 if (isnumber(argv[0])) { 197 errno = 0; 198 gid = (gid_t)strtol(argv[0], NULL, 10); /* gid is an int */ 199 if (errno != 0) { 200 if (errno == ERANGE) { 201 (void) fprintf(stderr, gettext( 202 "chgrp: group id is too large\n")); 203 exit(2); 204 } else { 205 (void) fprintf(stderr, gettext( 206 "chgrp: invalid group id\n")); 207 exit(2); 208 } 209 } 210 } else { 211 if ((gr = getgrnam(argv[0])) == NULL) { 212 (void) fprintf(stderr, "chgrp: "); 213 (void) fprintf(stderr, gettext("unknown group: %s\n"), 214 argv[0]); 215 exit(2); 216 } 217 gid = gr->gr_gid; 218 } 219 220 for (c = 1; c < argc; c++) { 221 tree = NULL; 222 if (lstat(argv[c], &stbuf) < 0) { 223 status += Perror(argv[c]); 224 continue; 225 } 226 if (rflag && ((stbuf.st_mode & S_IFMT) == S_IFLNK)) { 227 if (hflag || Pflag) { 228 /* 229 * Change the group id of the symbolic link 230 * specified on the command line. 231 * Don't follow the symbolic link to 232 * any other part of the file hierarchy. 233 */ 234 LCHOWN(argv[c], -1, gid); 235 } else { 236 if (stat(argv[c], &stbuf2) < 0) { 237 status += Perror(argv[c]); 238 continue; 239 } 240 /* 241 * We know that we are to change the 242 * group of the file referenced by the 243 * symlink specified on the command line. 244 * Now check to see if we are to follow 245 * the symlink to any other part of the 246 * file hierarchy. 247 */ 248 if (FOLLOW_CL_LINKS) { 249 if ((stbuf2.st_mode & S_IFMT) 250 == S_IFDIR) { 251 /* 252 * We are following symlinks so 253 * traverse into the directory. 254 * Add this node to the search 255 * tree so we don't get into an 256 * endless loop. 257 */ 258 if (add_tnode(&tree, 259 stbuf2.st_dev, 260 stbuf2.st_ino) == 1) { 261 chgrpr(argv[c], gid); 262 /* 263 * Try to restore the 264 * SET[UG]ID bits. 265 */ 266 SETUGID_PRESERVE( 267 argv[c], 268 stbuf2.st_mode & 269 ~S_IFMT); 270 } else { 271 /* 272 * Error occurred. 273 * rc can't be 0 274 * as this is the first 275 * node to be added to 276 * the search tree. 277 */ 278 status += Perror( 279 argv[c]); 280 } 281 } else { 282 /* 283 * Change the group id of the 284 * file referenced by the 285 * symbolic link. 286 */ 287 CHOWN(argv[c], -1, gid); 288 } 289 } else { 290 /* 291 * Change the group id of the file 292 * referenced by the symbolic link. 293 */ 294 CHOWN(argv[c], -1, gid); 295 296 if ((stbuf2.st_mode & S_IFMT) 297 == S_IFDIR) { 298 /* Reset the SET[UG]ID bits. */ 299 SETUGID_PRESERVE(argv[c], 300 stbuf2.st_mode & ~S_IFMT); 301 } 302 } 303 } 304 } else if (rflag && ((stbuf.st_mode & S_IFMT) == S_IFDIR)) { 305 /* 306 * Add this node to the search tree so we don't 307 * get into a endless loop. 308 */ 309 if (add_tnode(&tree, stbuf.st_dev, 310 stbuf.st_ino) == 1) { 311 chgrpr(argv[c], gid); 312 313 /* Restore the SET[UG]ID bits. */ 314 SETUGID_PRESERVE(argv[c], 315 stbuf.st_mode & ~S_IFMT); 316 } else { 317 /* 318 * An error occurred while trying 319 * to add the node to the tree. 320 * Continue on with next file 321 * specified. Note: rc shouldn't 322 * be 0 as this was the first node 323 * being added to the search tree. 324 */ 325 status += Perror(argv[c]); 326 } 327 } else { 328 if (hflag || Pflag) { 329 LCHOWN(argv[c], -1, gid); 330 } else { 331 CHOWN(argv[c], -1, gid); 332 } 333 /* If a directory, reset the SET[UG]ID bits. */ 334 if ((stbuf.st_mode & S_IFMT) == S_IFDIR) { 335 SETUGID_PRESERVE(argv[c], 336 stbuf.st_mode & ~S_IFMT); 337 } 338 } 339 } 340 return (status); 341 } 342 343 /* 344 * chgrpr() - recursive chown() 345 * 346 * Recursively chowns the input directory then its contents. rflag must 347 * have been set if chgrpr() is called. The input directory should not 348 * be a sym link (this is handled in the calling routine). In 349 * addition, the calling routine should have already added the input 350 * directory to the search tree so we do not get into endless loops. 351 * Note: chgrpr() doesn't need a return value as errors are reported 352 * through the global "status" variable. 353 */ 354 static void 355 chgrpr(char *dir, gid_t gid) 356 { 357 struct dirent *dp; 358 DIR *dirp; 359 struct stat st, st2; 360 char savedir[1024]; 361 362 if (getcwd(savedir, 1024) == 0) { 363 (void) fprintf(stderr, "chgrp: "); 364 (void) fprintf(stderr, gettext("%s\n"), savedir); 365 exit(255); 366 } 367 368 /* 369 * Attempt to chown the directory, however don't return if we 370 * can't as we still may be able to chown the contents of the 371 * directory. Note: the calling routine resets the SUID bits 372 * on this directory so we don't have to perform an extra 'stat'. 373 */ 374 CHOWN(dir, -1, gid); 375 376 if (chdir(dir) < 0) { 377 status += Perror(dir); 378 return; 379 } 380 if ((dirp = opendir(".")) == NULL) { 381 status += Perror(dir); 382 return; 383 } 384 for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { 385 if ((strcmp(dp->d_name, ".") == 0) || 386 (strcmp(dp->d_name, "..") == 0)) { 387 continue; /* skip "." and ".." */ 388 } 389 if (lstat(dp->d_name, &st) < 0) { 390 status += Perror(dp->d_name); 391 continue; 392 } 393 if ((st.st_mode & S_IFMT) == S_IFLNK) { 394 if (hflag || Pflag) { 395 /* 396 * Change the group id of the symbolic link 397 * encountered while traversing the 398 * directory. Don't follow the symbolic 399 * link to any other part of the file 400 * hierarchy. 401 */ 402 LCHOWN(dp->d_name, -1, gid); 403 } else { 404 if (stat(dp->d_name, &st2) < 0) { 405 status += Perror(dp->d_name); 406 continue; 407 } 408 /* 409 * We know that we are to change the 410 * group of the file referenced by the 411 * symlink encountered while traversing 412 * the directory. Now check to see if we 413 * are to follow the symlink to any other 414 * part of the file hierarchy. 415 */ 416 if (FOLLOW_D_LINKS) { 417 if ((st2.st_mode & S_IFMT) == S_IFDIR) { 418 /* 419 * We are following symlinks so 420 * traverse into the directory. 421 * Add this node to the search 422 * tree so we don't get into an 423 * endless loop. 424 */ 425 int rc; 426 if ((rc = add_tnode(&tree, 427 st2.st_dev, 428 st2.st_ino)) == 1) { 429 chgrpr(dp->d_name, gid); 430 431 /* 432 * Restore SET[UG]ID 433 * bits. 434 */ 435 SETUGID_PRESERVE( 436 dp->d_name, 437 st2.st_mode & 438 ~S_IFMT); 439 } else if (rc == 0) { 440 /* already visited */ 441 continue; 442 } else { 443 /* 444 * An error occurred 445 * while trying to add 446 * the node to the tree. 447 */ 448 status += Perror( 449 dp->d_name); 450 continue; 451 } 452 } else { 453 /* 454 * Change the group id of the 455 * file referenced by the 456 * symbolic link. 457 */ 458 CHOWN(dp->d_name, -1, gid); 459 460 } 461 } else { 462 /* 463 * Change the group id of the file 464 * referenced by the symbolic link. 465 */ 466 CHOWN(dp->d_name, -1, gid); 467 468 if ((st2.st_mode & S_IFMT) == S_IFDIR) { 469 /* Restore SET[UG]ID bits. */ 470 SETUGID_PRESERVE(dp->d_name, 471 st2.st_mode & ~S_IFMT); 472 } 473 } 474 } 475 } else if ((st.st_mode & S_IFMT) == S_IFDIR) { 476 /* 477 * Add this node to the search tree so we don't 478 * get into a endless loop. 479 */ 480 int rc; 481 if ((rc = add_tnode(&tree, st.st_dev, 482 st.st_ino)) == 1) { 483 chgrpr(dp->d_name, gid); 484 485 /* Restore the SET[UG]ID bits. */ 486 SETUGID_PRESERVE(dp->d_name, 487 st.st_mode & ~S_IFMT); 488 } else if (rc == 0) { 489 /* already visited */ 490 continue; 491 } else { 492 /* 493 * An error occurred while trying 494 * to add the node to the search tree. 495 */ 496 status += Perror(dp->d_name); 497 continue; 498 } 499 } else { 500 CHOWN(dp->d_name, -1, gid); 501 } 502 } 503 (void) closedir(dirp); 504 if (chdir(savedir) < 0) { 505 (void) fprintf(stderr, "chgrp: "); 506 (void) fprintf(stderr, gettext("can't change back to %s\n"), 507 savedir); 508 exit(255); 509 } 510 } 511 512 static int 513 isnumber(char *s) 514 { 515 int c; 516 517 while ((c = *s++) != '\0') 518 if (!isdigit(c)) 519 return (0); 520 return (1); 521 } 522 523 524 static int 525 Perror(char *s) 526 { 527 if (!fflag) { 528 (void) fprintf(stderr, "chgrp: "); 529 perror(s); 530 } 531 return (!fflag); 532 } 533 534 535 static void 536 usage(void) 537 { 538 (void) fprintf(stderr, gettext( 539 "usage:\n" 540 "\tchgrp [-fhR] group file ...\n" 541 "\tchgrp -R [-f] [-H|-L|-P] group file ...\n")); 542 exit(2); 543 } 544