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.
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.
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]
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
27 * Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T
32 * University Copyright- Copyright (c) 1982, 1986, 1988
33 * The Regents of the University of California
36 * University Acknowledgment- Portions of this document are derived from
37 * software developed by the University of California, Berkeley, and its
41 #pragma ident "%Z%%M% %I% %E% SMI"
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 ...
52 #include <sys/types.h>
60 #include <libcmdutils.h>
65 static struct group
*gr
;
66 static struct stat stbuf
;
67 static struct stat stbuf2
;
76 static int status
= 0; /* total number of errors received */
78 static avl_tree_t
*tree
; /* search tree to store inode data */
80 static void usage(void);
81 static int isnumber(char *);
82 static int Perror(char *);
83 static void chgrpr(char *, gid_t
);
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.
93 #define FOLLOW_CL_LINKS (rflag)
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
103 #define FOLLOW_CL_LINKS (rflag && (Hflag || Lflag))
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.
112 #define FOLLOW_D_LINKS (!Hflag)
115 * Follow symlinks when traversing directories. Only follow symlinks
116 * to other parts of the file hierarchy if -L was specified.
118 #define FOLLOW_D_LINKS (Lflag)
121 #define CHOWN(f, u, g) if (chown(f, u, g) < 0) { \
122 status += Perror(f); \
125 #define LCHOWN(f, u, g) if (lchown(f, u, g) < 0) { \
126 status += Perror(f); \
129 * We're ignoring errors here because preserving the SET[UG]ID bits is just
130 * a courtesy. This is only used on directories.
132 #define SETUGID_PRESERVE(dir, mode) \
133 if (((mode) & (S_ISGID|S_ISUID)) != 0) \
134 (void) chmod((dir), (mode) & ~S_IFMT)
140 main(int argc
, char *argv
[])
144 /* set the locale for only the messages system (all else is clean) */
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 */
150 (void) textdomain(TEXT_DOMAIN
);
152 while ((c
= getopt(argc
, argv
, "RhfHLPs")) != EOF
)
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.
190 * Check for sufficient arguments
194 argv
= &argv
[optind
];
197 ((Hflag
|| Lflag
|| Pflag
) && !rflag
) ||
198 ((Hflag
|| Lflag
|| Pflag
) && hflag
)) {
203 if (sid_to_id(argv
[0], B_FALSE
, &gid
)) {
204 (void) fprintf(stderr
, gettext(
205 "chgrp: invalid group sid %s\n"), argv
[0]);
208 } else if ((gr
= getgrnam(argv
[0])) != NULL
) {
211 if (isnumber(argv
[0])) {
214 gid
= (gid_t
)strtoul(argv
[0], NULL
, 10);
216 if (errno
== ERANGE
) {
217 (void) fprintf(stderr
, gettext(
218 "chgrp: group id is too large\n"));
221 (void) fprintf(stderr
, gettext(
222 "chgrp: invalid group id\n"));
227 (void) fprintf(stderr
, "chgrp: ");
228 (void) fprintf(stderr
, gettext("unknown group: %s\n"),
234 for (c
= 1; c
< argc
; c
++) {
236 if (lstat(argv
[c
], &stbuf
) < 0) {
237 status
+= Perror(argv
[c
]);
240 if (rflag
&& ((stbuf
.st_mode
& S_IFMT
) == S_IFLNK
)) {
241 if (hflag
|| Pflag
) {
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.
248 LCHOWN(argv
[c
], -1, gid
);
250 if (stat(argv
[c
], &stbuf2
) < 0) {
251 status
+= Perror(argv
[c
]);
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
262 if (FOLLOW_CL_LINKS
) {
263 if ((stbuf2
.st_mode
& S_IFMT
)
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
274 stbuf2
.st_ino
) == 1) {
275 chgrpr(argv
[c
], gid
);
288 * as this is the first
289 * node to be added to
297 * Change the group id of the
298 * file referenced by the
301 CHOWN(argv
[c
], -1, gid
);
305 * Change the group id of the file
306 * referenced by the symbolic link.
308 CHOWN(argv
[c
], -1, gid
);
310 if ((stbuf2
.st_mode
& S_IFMT
)
312 /* Reset the SET[UG]ID bits. */
313 SETUGID_PRESERVE(argv
[c
],
314 stbuf2
.st_mode
& ~S_IFMT
);
318 } else if (rflag
&& ((stbuf
.st_mode
& S_IFMT
) == S_IFDIR
)) {
320 * Add this node to the search tree so we don't
321 * get into a endless loop.
323 if (add_tnode(&tree
, stbuf
.st_dev
,
324 stbuf
.st_ino
) == 1) {
325 chgrpr(argv
[c
], gid
);
327 /* Restore the SET[UG]ID bits. */
328 SETUGID_PRESERVE(argv
[c
],
329 stbuf
.st_mode
& ~S_IFMT
);
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.
339 status
+= Perror(argv
[c
]);
342 if (hflag
|| Pflag
) {
343 LCHOWN(argv
[c
], -1, gid
);
345 CHOWN(argv
[c
], -1, gid
);
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
);
358 * chgrpr() - recursive chown()
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.
369 chgrpr(char *dir
, gid_t gid
)
376 if (getcwd(savedir
, 1024) == 0) {
377 (void) fprintf(stderr
, "chgrp: ");
378 (void) fprintf(stderr
, gettext("%s\n"), savedir
);
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'.
390 if (chdir(dir
) < 0) {
391 status
+= Perror(dir
);
394 if ((dirp
= opendir(".")) == NULL
) {
395 status
+= Perror(dir
);
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 ".." */
403 if (lstat(dp
->d_name
, &st
) < 0) {
404 status
+= Perror(dp
->d_name
);
407 if ((st
.st_mode
& S_IFMT
) == S_IFLNK
) {
408 if (hflag
|| Pflag
) {
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
416 LCHOWN(dp
->d_name
, -1, gid
);
418 if (stat(dp
->d_name
, &st2
) < 0) {
419 status
+= Perror(dp
->d_name
);
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.
430 if (FOLLOW_D_LINKS
) {
431 if ((st2
.st_mode
& S_IFMT
) == S_IFDIR
) {
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
440 if ((rc
= add_tnode(&tree
,
443 chgrpr(dp
->d_name
, gid
);
453 } else if (rc
== 0) {
454 /* already visited */
459 * while trying to add
460 * the node to the tree.
468 * Change the group id of the
469 * file referenced by the
472 CHOWN(dp
->d_name
, -1, gid
);
477 * Change the group id of the file
478 * referenced by the symbolic link.
480 CHOWN(dp
->d_name
, -1, gid
);
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
);
489 } else if ((st
.st_mode
& S_IFMT
) == S_IFDIR
) {
491 * Add this node to the search tree so we don't
492 * get into a endless loop.
495 if ((rc
= add_tnode(&tree
, st
.st_dev
,
497 chgrpr(dp
->d_name
, gid
);
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 */
507 * An error occurred while trying
508 * to add the node to the search tree.
510 status
+= Perror(dp
->d_name
);
514 CHOWN(dp
->d_name
, -1, gid
);
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"),
531 while ((c
= *s
++) != '\0')
542 (void) fprintf(stderr
, "chgrp: ");
552 (void) fprintf(stderr
, gettext(
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"));