xref: /illumos-gate/usr/src/cmd/chgrp/chgrp.c (revision dd52495f0d9ba8ff6d84921ec0500be837896554)
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