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