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