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
main(int argc,char * argv[])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
chgrpr(char * dir,gid_t gid)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
isnumber(char * s)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
Perror(char * s)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
usage(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