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