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