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