dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / cmd / chmod / chmod.c
blob0735d3a37ec69e08e73ad7ca8e004bf3e490dc87
1 /*
2 * CDDL HEADER START
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]
19 * CDDL HEADER END
22 * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright 2011 Nexenta Systems, Inc. All rights reserved.
26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
27 /* All Rights Reserved */
28 /* */
31 * University Copyright- Copyright (c) 1982, 1986, 1988
32 * The Regents of the University of California
33 * All Rights Reserved
35 * University Acknowledgment- Portions of this document are derived from
36 * software developed by the University of California, Berkeley, and its
37 * contributors.
41 * chmod option mode files
42 * where
43 * mode is [ugoa][+-=][rwxXlstugo] or an octal number
44 * mode is [<+|->A[# <number] ]<aclspec>
45 * mode is S<attrspec>
46 * option is -R, -f, and -@
50 * Note that many convolutions are necessary
51 * due to the re-use of bits between locking
52 * and setgid
55 #include <unistd.h>
56 #include <stdlib.h>
57 #include <stdio.h>
58 #include <sys/types.h>
59 #include <sys/stat.h>
60 #include <fcntl.h>
61 #include <dirent.h>
62 #include <locale.h>
63 #include <string.h> /* strerror() */
64 #include <stdarg.h>
65 #include <limits.h>
66 #include <ctype.h>
67 #include <errno.h>
68 #include <sys/acl.h>
69 #include <aclutils.h>
70 #include <libnvpair.h>
71 #include <libcmdutils.h>
72 #include <libgen.h>
73 #include <attr.h>
75 static int rflag;
76 static int fflag;
78 extern int optind;
79 extern int errno;
81 static int mac; /* Alternate to argc (for parseargs) */
82 static char **mav; /* Alternate to argv (for parseargs) */
84 static char *ms; /* Points to the mode argument */
86 #define ACL_ADD 1
87 #define ACL_DELETE 2
88 #define ACL_SLOT_DELETE 3
89 #define ACL_REPLACE 4
90 #define ACL_STRIP 5
92 #define LEFTBRACE '{'
93 #define RIGHTBRACE '}'
94 #define A_SEP ','
95 #define A_SEP_TOK ","
97 #define A_COMPACT_TYPE 'c'
98 #define A_VERBOSE_TYPE 'v'
99 #define A_ALLATTRS_TYPE 'a'
101 #define A_SET_OP '+'
102 #define A_INVERSE_OP '-'
103 #define A_REPLACE_OP '='
104 #define A_UNDEF_OP '\0'
106 #define A_SET_TEXT "set"
107 #define A_INVERSE_TEXT "clear"
109 #define A_SET_VAL B_TRUE
110 #define A_CLEAR_VAL B_FALSE
112 #define ATTR_OPTS 0
113 #define ATTR_NAMES 1
115 #define sec_acls secptr.acls
116 #define sec_attrs secptr.attrs
118 typedef struct acl_args {
119 acl_t *acl_aclp;
120 int acl_slot;
121 int acl_action;
122 } acl_args_t;
124 typedef enum {
125 SEC_ACL,
126 SEC_ATTR
127 } chmod_sec_t;
129 typedef struct {
130 chmod_sec_t sec_type;
131 union {
132 acl_args_t *acls;
133 nvlist_t *attrs;
134 } secptr;
135 } sec_args_t;
137 typedef struct attr_name {
138 char *name;
139 struct attr_name *next;
140 } attr_name_t;
143 extern mode_t newmode_common(char *ms, mode_t new_mode, mode_t umsk,
144 char *file, char *path, o_mode_t *group_clear_bits,
145 o_mode_t *group_set_bits);
147 static int chmodr(char *dir, char *path, mode_t mode, mode_t umsk,
148 sec_args_t *secp, attr_name_t *attrname);
149 static int doacl(char *file, struct stat *st, acl_args_t *aclp);
150 static int dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp,
151 attr_name_t *attrnames);
152 static void handle_acl(char *name, o_mode_t group_clear_bits,
153 o_mode_t group_set_bits);
154 void errmsg(int severity, int code, char *format, ...);
155 static void free_attr_names(attr_name_t *attrnames);
156 static void parseargs(int ac, char *av[]);
157 static int parse_acl_args(char *arg, sec_args_t **sec_args);
158 static int parse_attr_args(char *arg, sec_args_t **sec_args);
159 static void print_attrs(int flag);
160 static int set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist);
161 static void usage(void);
164 main(int argc, char *argv[])
166 int i, c;
167 int status = 0;
168 mode_t umsk;
169 sec_args_t *sec_args = NULL;
170 attr_name_t *attrnames = NULL;
171 attr_name_t *attrend = NULL;
172 attr_name_t *tattr;
174 (void) setlocale(LC_ALL, "");
175 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
176 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
177 #endif
178 (void) textdomain(TEXT_DOMAIN);
180 parseargs(argc, argv);
182 while ((c = getopt(mac, mav, "Rf@:")) != EOF) {
183 switch (c) {
184 case 'R':
185 rflag++;
186 break;
187 case 'f':
188 fflag++;
189 break;
190 case '@':
191 if (((tattr = malloc(sizeof (attr_name_t))) == NULL) ||
192 ((tattr->name = strdup(optarg)) == NULL)) {
193 perror("chmod");
194 exit(2);
196 if (attrnames == NULL) {
197 attrnames = tattr;
198 attrnames->next = NULL;
199 } else {
200 attrend->next = tattr;
202 attrend = tattr;
203 break;
204 case '?':
205 usage();
206 exit(2);
211 * Check for sufficient arguments
212 * or a usage error.
215 mac -= optind;
216 mav += optind;
217 if ((mac >= 2) && (mav[0][0] == 'A')) {
218 if (attrnames != NULL) {
219 free_attr_names(attrnames);
220 attrnames = NULL;
222 if (parse_acl_args(*mav, &sec_args)) {
223 usage();
224 exit(2);
226 } else if ((mac >= 2) && (mav[0][0] == 'S')) {
227 if (parse_attr_args(*mav, &sec_args)) {
228 usage();
229 exit(2);
231 /* A no-op attribute operation was specified. */
232 } else if (sec_args->sec_attrs == NULL) {
233 exit(0);
235 } else {
236 if (mac < 2) {
237 usage();
238 exit(2);
240 if (attrnames != NULL) {
241 free_attr_names(attrnames);
242 attrnames = NULL;
246 ms = mav[0];
248 umsk = umask(0);
249 (void) umask(umsk);
251 for (i = 1; i < mac; i++) {
252 status += dochmod(mav[i], mav[i], umsk, sec_args, attrnames);
255 return (fflag ? 0 : status);
258 static void
259 free_attr_names(attr_name_t *attrnames)
261 attr_name_t *attrnamesptr = attrnames;
262 attr_name_t *tptr;
264 while (attrnamesptr != NULL) {
265 tptr = attrnamesptr->next;
266 if (attrnamesptr->name != NULL) {
267 free(attrnamesptr->name);
269 attrnamesptr = tptr;
273 static int
274 dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp,
275 attr_name_t *attrnames)
277 static struct stat st;
278 int linkflg = 0;
279 o_mode_t group_clear_bits, group_set_bits;
281 if (lstat(name, &st) < 0) {
282 errmsg(2, 0, gettext("can't access %s\n"), path);
283 return (1);
286 if ((st.st_mode & S_IFMT) == S_IFLNK) {
287 linkflg = 1;
288 if (stat(name, &st) < 0) {
289 errmsg(2, 0, gettext("can't access %s\n"), path);
290 return (1);
294 /* Do not recurse if directory is object of symbolic link */
295 if (rflag && ((st.st_mode & S_IFMT) == S_IFDIR) && !linkflg) {
296 return (chmodr(name, path, st.st_mode, umsk, secp, attrnames));
299 if (secp != NULL) {
300 if (secp->sec_type == SEC_ACL) {
301 return (doacl(name, &st, secp->sec_acls));
302 } else if (secp->sec_type == SEC_ATTR) {
303 return (set_attrs(name, attrnames, secp->sec_attrs));
304 } else {
305 return (1);
307 } else {
308 if (chmod(name, newmode_common(ms, st.st_mode, umsk, name, path,
309 &group_clear_bits, &group_set_bits)) == -1) {
310 errmsg(2, 0, gettext("can't change %s\n"), path);
311 return (1);
316 * If the group permissions of the file are being modified,
317 * make sure that the file's ACL (if it has one) is
318 * modified also, since chmod is supposed to apply group
319 * permissions changes to both the acl mask and the
320 * general group permissions.
322 if (group_clear_bits || group_set_bits)
323 handle_acl(name, group_clear_bits, group_set_bits);
325 return (0);
328 static int
329 chmodr(char *dir, char *path, mode_t mode, mode_t umsk, sec_args_t *secp,
330 attr_name_t *attrnames)
333 DIR *dirp;
334 struct dirent *dp;
335 char savedir[PATH_MAX]; /* dir name to restore */
336 char currdir[PATH_MAX+1]; /* current dir name + '/' */
337 char parentdir[PATH_MAX+1]; /* parent dir name + '/' */
338 int ecode;
339 struct stat st;
340 o_mode_t group_clear_bits, group_set_bits;
342 if (getcwd(savedir, PATH_MAX) == 0)
343 errmsg(2, 255, gettext("chmod: could not getcwd %s\n"),
344 savedir);
347 * Change what we are given before doing it's contents
349 if (secp != NULL) {
350 if (lstat(dir, &st) < 0) {
351 errmsg(2, 0, gettext("can't access %s\n"), path);
352 return (1);
354 if (secp->sec_type == SEC_ACL) {
355 (void) doacl(dir, &st, secp->sec_acls);
356 } else if (secp->sec_type == SEC_ATTR) {
357 (void) set_attrs(dir, attrnames, secp->sec_attrs);
358 } else {
359 return (1);
361 } else if (chmod(dir, newmode_common(ms, mode, umsk, dir, path,
362 &group_clear_bits, &group_set_bits)) < 0) {
363 errmsg(2, 0, gettext("can't change %s\n"), path);
367 * If the group permissions of the file are being modified,
368 * make sure that the file's ACL (if it has one) is
369 * modified also, since chmod is supposed to apply group
370 * permissions changes to both the acl mask and the
371 * general group permissions.
374 if (secp != NULL) {
375 /* only necessary when not setting ACL or system attributes */
376 if (group_clear_bits || group_set_bits)
377 handle_acl(dir, group_clear_bits, group_set_bits);
380 if (chdir(dir) < 0) {
381 errmsg(2, 0, "%s/%s: %s\n", savedir, dir, strerror(errno));
382 return (1);
384 if ((dirp = opendir(".")) == NULL) {
385 errmsg(2, 0, "%s\n", strerror(errno));
386 return (1);
388 ecode = 0;
391 * Save parent directory path before recursive chmod.
392 * We'll need this for error printing purposes. Add
393 * a trailing '/' to the path except in the case where
394 * the path is just '/'
397 if (strlcpy(parentdir, path, PATH_MAX + 1) >= PATH_MAX + 1) {
398 errmsg(2, 0, gettext("directory path name too long: %s\n"),
399 path);
400 return (1);
402 if (strcmp(path, "/") != 0)
403 if (strlcat(parentdir, "/", PATH_MAX + 1) >= PATH_MAX + 1) {
404 errmsg(2, 0,
405 gettext("directory path name too long: %s/\n"),
406 parentdir);
407 return (1);
411 for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
413 if (strcmp(dp->d_name, ".") == 0 || /* skip . and .. */
414 strcmp(dp->d_name, "..") == 0) {
415 continue;
417 if (strlcpy(currdir, parentdir, PATH_MAX + 1) >= PATH_MAX + 1) {
418 errmsg(2, 0,
419 gettext("directory path name too long: %s\n"),
420 parentdir);
421 return (1);
423 if (strlcat(currdir, dp->d_name, PATH_MAX + 1)
424 >= PATH_MAX + 1) {
425 errmsg(2, 0,
426 gettext("directory path name too long: %s%s\n"),
427 currdir, dp->d_name);
428 return (1);
430 ecode += dochmod(dp->d_name, currdir, umsk, secp, attrnames);
432 (void) closedir(dirp);
433 if (chdir(savedir) < 0) {
434 errmsg(2, 255, gettext("can't change back to %s\n"), savedir);
436 return (ecode ? 1 : 0);
439 /* PRINTFLIKE3 */
440 void
441 errmsg(int severity, int code, char *format, ...)
443 va_list ap;
444 static char *msg[] = {
446 "ERROR",
447 "WARNING",
451 va_start(ap, format);
454 * Always print error message if this is a fatal error (code != 0);
455 * otherwise, print message if fflag == 0 (no -f option specified)
457 if (!fflag || (code != 0)) {
458 (void) fprintf(stderr,
459 "chmod: %s: ", gettext(msg[severity]));
460 (void) vfprintf(stderr, format, ap);
463 va_end(ap);
465 if (code != 0)
466 exit(fflag ? 0 : code);
469 static void
470 usage(void)
472 (void) fprintf(stderr, gettext(
473 "usage:\tchmod [-fR] <absolute-mode> file ...\n"));
475 (void) fprintf(stderr, gettext(
476 "\tchmod [-fR] [-@ attribute] ... "
477 "S<attribute-operation> file ...\n"));
479 (void) fprintf(stderr, gettext(
480 "\tchmod [-fR] <ACL-operation> file ...\n"));
482 (void) fprintf(stderr, gettext(
483 "\tchmod [-fR] <symbolic-mode-list> file ...\n\n"));
485 (void) fprintf(stderr, gettext(
486 "where \t<symbolic-mode-list> is a comma-separated list of\n"));
487 (void) fprintf(stderr, gettext(
488 "\t[ugoa]{+|-|=}[rwxXlstugo]\n\n"));
490 (void) fprintf(stderr, gettext(
491 "where \t<attribute-operation> is a comma-separated list of\n"
492 "\tone or more of the following\n"));
493 (void) fprintf(stderr, gettext(
494 "\t[+|-|=]c[<compact-attribute-list>|{<compact-attribute-list>}]\n"
495 "\t[+|-|=]v[<verbose-attribute-setting>|"
496 "\'{\'<verbose-attribute-setting-list>\'}\']\n"
497 "\t[+|-|=]a\n"));
498 (void) fprintf(stderr, gettext(
499 "where \t<compact-attribute-list> is a list of zero or more of\n"));
500 print_attrs(ATTR_OPTS);
501 (void) fprintf(stderr, gettext(
502 "where \t<verbose-attribute-setting> is one of\n"));
503 print_attrs(ATTR_NAMES);
504 (void) fprintf(stderr, gettext(
505 "\tand can be, optionally, immediately preceded by \"no\"\n\n"));
507 (void) fprintf(stderr, gettext(
508 "where \t<ACL-operation> is one of the following\n"));
509 (void) fprintf(stderr, gettext("\tA-<acl_specification>\n"));
510 (void) fprintf(stderr, gettext("\tA[number]-\n"));
511 (void) fprintf(stderr, gettext(
512 "\tA[number]{+|=}<acl_specification>\n"));
513 (void) fprintf(stderr, gettext(
514 "where \t<acl-specification> is a comma-separated list of ACEs\n"));
518 * parseargs - generate getopt-friendly argument list for backwards
519 * compatibility with earlier Solaris usage (eg, chmod -w
520 * foo).
522 * assumes the existence of a static set of alternates to argc and argv,
523 * (namely, mac, and mav[]).
527 static void
528 parseargs(int ac, char *av[])
530 int i; /* current argument */
531 int fflag; /* arg list contains "--" */
532 size_t mav_num; /* number of entries in mav[] */
535 * We add an extra argument slot, in case we need to jam a "--"
536 * argument into the list.
539 mav_num = (size_t)ac+2;
541 if ((mav = calloc(mav_num, sizeof (char *))) == NULL) {
542 perror("chmod");
543 exit(2);
546 /* scan for the use of "--" in the argument list */
548 for (fflag = i = 0; i < ac; i ++) {
549 if (strcmp(av[i], "--") == 0)
550 fflag = 1;
553 /* process the arguments */
555 for (i = mac = 0;
556 (av[i] != NULL) && (av[i][0] != '\0');
557 i++) {
558 if (!fflag && av[i][0] == '-') {
560 * If there is not already a "--" argument specified,
561 * and the argument starts with '-' but does not
562 * contain any of the official option letters, then it
563 * is probably a mode argument beginning with '-'.
564 * Force a "--" into the argument stream in front of
565 * it.
568 if ((strchr(av[i], 'R') == NULL &&
569 strchr(av[i], 'f') == NULL) &&
570 strchr(av[i], '@') == NULL) {
571 if ((mav[mac++] = strdup("--")) == NULL) {
572 perror("chmod");
573 exit(2);
578 if ((mav[mac++] = strdup(av[i])) == NULL) {
579 perror("chmod");
580 exit(2);
584 mav[mac] = NULL;
587 static int
588 parse_acl_args(char *arg, sec_args_t **sec_args)
590 acl_t *new_acl = NULL;
591 int slot;
592 int len;
593 int action;
594 acl_args_t *new_acl_args;
595 char *acl_spec = NULL;
596 char *end;
598 if (arg[0] != 'A')
599 return (1);
601 slot = strtol(&arg[1], &end, 10);
603 len = strlen(arg);
604 switch (*end) {
605 case '+':
606 action = ACL_ADD;
607 acl_spec = ++end;
608 break;
609 case '-':
610 if (len == 2 && arg[0] == 'A' && arg[1] == '-')
611 action = ACL_STRIP;
612 else
613 action = ACL_DELETE;
614 if (action != ACL_STRIP) {
615 acl_spec = ++end;
616 if (acl_spec[0] == '\0') {
617 action = ACL_SLOT_DELETE;
618 acl_spec = NULL;
619 } else if (arg[1] != '-')
620 return (1);
622 break;
623 case '=':
625 * Was slot specified?
627 if (arg[1] == '=')
628 slot = -1;
629 action = ACL_REPLACE;
630 acl_spec = ++end;
631 break;
632 default:
633 return (1);
636 if ((action == ACL_REPLACE || action == ACL_ADD) && acl_spec[0] == '\0')
637 return (1);
639 if (acl_spec) {
640 if (acl_parse(acl_spec, &new_acl)) {
641 exit(1);
645 new_acl_args = malloc(sizeof (acl_args_t));
646 if (new_acl_args == NULL)
647 return (1);
649 new_acl_args->acl_aclp = new_acl;
650 new_acl_args->acl_slot = slot;
651 new_acl_args->acl_action = action;
653 if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) {
654 perror("chmod");
655 exit(2);
657 (*sec_args)->sec_type = SEC_ACL;
658 (*sec_args)->sec_acls = new_acl_args;
660 return (0);
664 * This function is called whenever the group permissions of a file
665 * is being modified. According to the chmod(1) manpage, any
666 * change made to the group permissions must be applied to both
667 * the acl mask and the acl's GROUP_OBJ. The chmod(2) already
668 * set the mask, so this routine needs to make the same change
669 * to the GROUP_OBJ.
671 static void
672 handle_acl(char *name, o_mode_t group_clear_bits, o_mode_t group_set_bits)
674 int aclcnt, n;
675 aclent_t *aclp, *tp;
676 o_mode_t newperm;
678 * if this file system support ace_t acl's
679 * then simply return since we don't have an
680 * acl mask to deal with
682 if (pathconf(name, _PC_ACL_ENABLED) == _ACL_ACE_ENABLED)
683 return;
684 if ((aclcnt = acl(name, GETACLCNT, 0, NULL)) <= MIN_ACL_ENTRIES)
685 return; /* it's just a trivial acl; no need to change it */
686 if ((aclp = (aclent_t *)malloc((sizeof (aclent_t)) * aclcnt))
687 == NULL) {
688 perror("chmod");
689 exit(2);
692 if (acl(name, GETACL, aclcnt, aclp) < 0) {
693 free(aclp);
694 (void) fprintf(stderr, "chmod: ");
695 perror(name);
696 return;
698 for (tp = aclp, n = aclcnt; n--; tp++) {
699 if (tp->a_type == GROUP_OBJ) {
700 newperm = tp->a_perm;
701 if (group_clear_bits != 0)
702 newperm &= ~group_clear_bits;
703 if (group_set_bits != 0)
704 newperm |= group_set_bits;
705 if (newperm != tp->a_perm) {
706 tp->a_perm = newperm;
707 if (acl(name, SETACL, aclcnt, aclp)
708 < 0) {
709 (void) fprintf(stderr, "chmod: ");
710 perror(name);
713 break;
716 free(aclp);
719 static int
720 doacl(char *file, struct stat *st, acl_args_t *acl_args)
722 acl_t *aclp;
723 acl_t *set_aclp;
724 int error = 0;
725 void *to, *from;
726 int len;
727 int isdir;
728 isdir = S_ISDIR(st->st_mode);
730 error = acl_get(file, 0, &aclp);
732 if (error != 0) {
733 errmsg(1, 0, "%s\n", acl_strerror(error));
734 return (1);
736 switch (acl_args->acl_action) {
737 case ACL_ADD:
738 if ((error = acl_addentries(aclp,
739 acl_args->acl_aclp, acl_args->acl_slot)) != 0) {
740 errmsg(1, 0, "%s\n", acl_strerror(error));
741 acl_free(aclp);
742 return (1);
744 set_aclp = aclp;
745 break;
746 case ACL_SLOT_DELETE:
747 if (acl_args->acl_slot + 1 > aclp->acl_cnt) {
748 errmsg(1, 0,
749 gettext("Invalid slot specified for removal\n"));
750 acl_free(aclp);
751 return (1);
754 if (acl_args->acl_slot == 0 && aclp->acl_cnt == 1) {
755 errmsg(1, 0,
756 gettext("Can't remove all ACL "
757 "entries from a file\n"));
758 acl_free(aclp);
759 return (1);
763 * remove a single entry
765 * if last entry just adjust acl_cnt
768 if ((acl_args->acl_slot + 1) == aclp->acl_cnt)
769 aclp->acl_cnt--;
770 else {
771 to = (char *)aclp->acl_aclp +
772 (acl_args->acl_slot * aclp->acl_entry_size);
773 from = (char *)to + aclp->acl_entry_size;
774 len = (aclp->acl_cnt - acl_args->acl_slot - 1) *
775 aclp->acl_entry_size;
776 (void) memmove(to, from, len);
777 aclp->acl_cnt--;
779 set_aclp = aclp;
780 break;
782 case ACL_DELETE:
783 if ((error = acl_removeentries(aclp, acl_args->acl_aclp,
784 acl_args->acl_slot, ACL_REMOVE_ALL)) != 0) {
785 errmsg(1, 0, "%s\n", acl_strerror(error));
786 acl_free(aclp);
787 return (1);
790 if (aclp->acl_cnt == 0) {
791 errmsg(1, 0,
792 gettext("Can't remove all ACL "
793 "entries from a file\n"));
794 acl_free(aclp);
795 return (1);
798 set_aclp = aclp;
799 break;
800 case ACL_REPLACE:
801 if (acl_args->acl_slot >= 0) {
802 error = acl_modifyentries(aclp, acl_args->acl_aclp,
803 acl_args->acl_slot);
804 if (error) {
805 errmsg(1, 0, "%s\n", acl_strerror(error));
806 acl_free(aclp);
807 return (1);
809 set_aclp = aclp;
810 } else {
811 set_aclp = acl_args->acl_aclp;
813 break;
814 case ACL_STRIP:
815 error = acl_strip(file, st->st_uid, st->st_gid, st->st_mode);
816 if (error) {
817 errmsg(1, 0, "%s\n", acl_strerror(error));
818 acl_free(aclp);
819 return (1);
821 acl_free(aclp);
822 return (0);
823 /*NOTREACHED*/
824 default:
825 errmsg(1, 2, gettext("Unknown ACL action requested\n"));
826 /*NOTREACHED*/
828 error = acl_check(set_aclp, isdir);
830 if (error) {
831 errmsg(1, 2, "%s\n%s", acl_strerror(error),
832 gettext("See chmod(1) for more information on "
833 "valid ACL syntax\n"));
835 if ((error = acl_set(file, set_aclp)) != 0) {
836 errmsg(1, 0, gettext("Failed to set ACL: %s\n"),
837 acl_strerror(error));
838 acl_free(aclp);
839 return (1);
841 acl_free(aclp);
842 return (0);
846 * Prints out the attributes in their verbose form:
847 * '{'[["no"]<attribute-name>][,["no"]<attribute-name>]...'}'
848 * similar to output of ls -/v.
850 static void
851 print_nvlist(nvlist_t *attr_nvlist)
853 int firsttime = 1;
854 boolean_t value;
855 nvlist_t *lptr = attr_nvlist;
856 nvpair_t *pair = NULL;
858 (void) fprintf(stderr, "\t%c", LEFTBRACE);
859 while (pair = nvlist_next_nvpair(lptr, pair)) {
860 if (nvpair_value_boolean_value(pair, &value) == 0) {
861 (void) fprintf(stderr, "%s%s%s",
862 firsttime ? "" : A_SEP_TOK,
863 (value == A_SET_VAL) ? "" : "no",
864 nvpair_name(pair));
865 firsttime = 0;
866 } else {
867 (void) fprintf(stderr, gettext(
868 "<error retrieving attributes: %s>"),
869 strerror(errno));
870 break;
873 (void) fprintf(stderr, "%c\n", RIGHTBRACE);
877 * Add an attribute name and boolean value to an nvlist if an action is to be
878 * performed for that attribute. The nvlist will be used later to set all the
879 * attributes in the nvlist in one operation through a call to setattrat().
881 * If a set operation ('+') was specified, then a boolean representation of the
882 * attribute's value will be added to the nvlist for that attribute name. If an
883 * inverse operation ('-') was specified, then a boolean representation of the
884 * inverse of the attribute's value will be added to the nvlist for that
885 * attribute name.
887 * Returns an nvlist of attribute name and boolean value pairs if there are
888 * attribute actions to be performed, otherwise returns NULL.
890 static nvlist_t *
891 set_attrs_nvlist(char *attractptr, int numofattrs)
893 int attribute_set = 0;
894 f_attr_t i;
895 nvlist_t *attr_nvlist;
897 if (nvlist_alloc(&attr_nvlist, NV_UNIQUE_NAME, 0) != 0) {
898 perror("chmod");
899 exit(2);
902 for (i = 0; i < numofattrs; i++) {
903 if (attractptr[i] != '\0') {
904 if ((nvlist_add_boolean_value(attr_nvlist,
905 attr_to_name(i),
906 (attractptr[i] == A_SET_OP))) != 0) {
907 errmsg(1, 2, gettext(
908 "unable to propagate attribute names and"
909 "values: %s\n"), strerror(errno));
910 } else {
911 attribute_set = 1;
915 return (attribute_set ? attr_nvlist : NULL);
919 * Set the attributes of file, or if specified, of the named attribute file,
920 * attrname. Build an nvlist of attribute names and values and call setattrat()
921 * to set the attributes in one operation.
923 * Returns 0 if successful, otherwise returns 1.
925 static int
926 set_file_attrs(char *file, char *attrname, nvlist_t *attr_nvlist)
928 int rc;
929 char *filename;
931 if (attrname != NULL) {
932 filename = attrname;
933 } else {
934 filename = basename(file);
937 if ((rc = setattrat(AT_FDCWD, XATTR_VIEW_READWRITE, filename,
938 attr_nvlist)) != 0) {
939 char *emsg;
940 switch (errno) {
941 case EINVAL:
942 emsg = gettext("not supported");
943 break;
944 case EPERM:
945 emsg = gettext("not privileged");
946 break;
947 default:
948 emsg = strerror(rc);
950 errmsg(1, 0, gettext(
951 "cannot set the following attributes on "
952 "%s%s%s%s: %s\n"),
953 (attrname == NULL) ? "" : gettext("attribute "),
954 (attrname == NULL) ? "" : attrname,
955 (attrname == NULL) ? "" : gettext(" of "),
956 file, emsg);
957 print_nvlist(attr_nvlist);
960 return (rc);
963 static int
964 save_cwd(void)
966 return (open(".", O_RDONLY));
969 static void
970 rest_cwd(int cwd)
972 if (cwd != -1) {
973 if (fchdir(cwd) != 0) {
974 errmsg(1, 1, gettext(
975 "can't change to current working directory\n"));
977 (void) close(cwd);
982 * Returns 1 if filename is a system attribute file, otherwise
983 * returns 0.
985 static int
986 is_sattr(char *filename)
988 return (sysattr_type(filename) != _NOT_SATTR);
992 * Perform the action on the specified named attribute file for the file
993 * associated with the input file descriptor. If the named attribute file
994 * is "*", then the action is to be performed on all the named attribute files
995 * of the file associated with the input file descriptor.
997 static int
998 set_named_attrs(char *file, int parentfd, char *attrname, nvlist_t *attr_nvlist)
1000 int dirfd;
1001 int error = 0;
1002 DIR *dirp = NULL;
1003 struct dirent *dp;
1004 struct stat st;
1006 if ((attrname == NULL) || (strcmp(attrname, "*") != 0)) {
1008 * Make sure the named attribute exists and extended system
1009 * attributes are supported on the underlying file system.
1011 if (attrname != NULL) {
1012 if (fstatat(parentfd, attrname, &st,
1013 AT_SYMLINK_NOFOLLOW) < 0) {
1014 errmsg(2, 0, gettext(
1015 "can't access attribute %s of %s\n"),
1016 attrname, file);
1017 return (1);
1019 if (sysattr_support(attrname, _PC_SATTR_ENABLED) != 1) {
1020 errmsg(1, 0, gettext(
1021 "extended system attributes not supported "
1022 "for attribute %s of %s\n"),
1023 attrname, file);
1024 return (1);
1028 error = set_file_attrs(file, attrname, attr_nvlist);
1030 } else {
1031 if (((dirfd = dup(parentfd)) == -1) ||
1032 ((dirp = fdopendir(dirfd)) == NULL)) {
1033 errmsg(1, 0, gettext(
1034 "cannot open dir pointer of file %s\n"), file);
1035 if (dirfd > 0) {
1036 (void) close(dirfd);
1038 return (1);
1041 while (dp = readdir(dirp)) {
1043 * Process all extended attribute files except
1044 * ".", "..", and extended system attribute files.
1046 if ((strcmp(dp->d_name, ".") == 0) ||
1047 (strcmp(dp->d_name, "..") == 0) ||
1048 is_sattr(dp->d_name)) {
1049 continue;
1052 if (set_named_attrs(file, parentfd, dp->d_name,
1053 attr_nvlist) != 0) {
1054 error++;
1057 if (dirp != NULL) {
1058 (void) closedir(dirp);
1062 return ((error == 0) ? 0 : 1);
1066 * Set the attributes of the specified file, or if specified with -@ on the
1067 * command line, the specified named attributes of the specified file.
1069 * Returns 0 if successful, otherwise returns 1.
1071 static int
1072 set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist)
1074 char *parentd;
1075 char *tpath = NULL;
1076 int cwd;
1077 int error = 0;
1078 int parentfd;
1079 attr_name_t *tattr = attrnames;
1081 if (attr_nvlist == NULL) {
1082 return (0);
1085 if (sysattr_support(file, _PC_SATTR_ENABLED) != 1) {
1086 errmsg(1, 0, gettext(
1087 "extended system attributes not supported for %s\n"), file);
1088 return (1);
1092 * Open the parent directory and change into it before attempting
1093 * to set the attributes of the file.
1095 if (attrnames == NULL) {
1096 tpath = strdup(file);
1097 parentd = dirname(tpath);
1098 parentfd = open(parentd, O_RDONLY);
1099 } else {
1100 parentfd = attropen(file, ".", O_RDONLY);
1102 if (parentfd == -1) {
1103 errmsg(1, 0, gettext(
1104 "cannot open attribute directory of %s\n"), file);
1105 if (tpath != NULL) {
1106 free(tpath);
1108 return (1);
1111 if ((cwd = save_cwd()) < 0) {
1112 errmsg(1, 1, gettext(
1113 "can't get current working directory\n"));
1115 if (fchdir(parentfd) != 0) {
1116 errmsg(1, 0, gettext(
1117 "can't change to parent %sdirectory of %s\n"),
1118 (attrnames == NULL) ? "" : gettext("attribute "), file);
1119 (void) close(cwd);
1120 (void) close(parentfd);
1121 if (tpath != NULL) {
1122 free(tpath);
1124 return (1);
1128 * If no named attribute file names were provided on the command line
1129 * then set the attributes of the base file, otherwise, set the
1130 * attributes for each of the named attribute files specified.
1132 if (attrnames == NULL) {
1133 error = set_named_attrs(file, parentfd, NULL, attr_nvlist);
1134 free(tpath);
1135 } else {
1136 while (tattr != NULL) {
1137 if (set_named_attrs(file, parentfd, tattr->name,
1138 attr_nvlist) != 0) {
1139 error++;
1141 tattr = tattr->next;
1144 (void) close(parentfd);
1145 rest_cwd(cwd);
1147 return ((error == 0) ? 0 : 1);
1151 * Prints the attributes in either the compact or verbose form indicated
1152 * by flag.
1154 static void
1155 print_attrs(int flag)
1157 f_attr_t i;
1158 static int numofattrs;
1159 int firsttime = 1;
1161 numofattrs = attr_count();
1163 (void) fprintf(stderr, gettext("\t["));
1164 for (i = 0; i < numofattrs; i++) {
1165 if ((attr_to_xattr_view(i) != XATTR_VIEW_READWRITE) ||
1166 (attr_to_data_type(i) != DATA_TYPE_BOOLEAN_VALUE)) {
1167 continue;
1169 (void) fprintf(stderr, "%s%s",
1170 (firsttime == 1) ? "" : gettext("|"),
1171 (flag == ATTR_OPTS) ? attr_to_option(i) : attr_to_name(i));
1172 firsttime = 0;
1174 (void) fprintf(stderr, gettext("]\n"));
1178 * Record what action should be taken on the specified attribute. Only boolean
1179 * read-write attributes can be manipulated.
1181 * Returns 0 if successful, otherwise returns 1.
1183 static int
1184 set_attr_args(f_attr_t attr, char action, char *attractptr)
1186 if ((attr_to_xattr_view(attr) == XATTR_VIEW_READWRITE) &&
1187 (attr_to_data_type(attr) == DATA_TYPE_BOOLEAN_VALUE)) {
1188 attractptr[attr] = action;
1189 return (0);
1191 return (1);
1195 * Parses the entry and assigns the appropriate action (either '+' or '-' in
1196 * attribute's position in the character array pointed to by attractptr, where
1197 * upon exit, attractptr is positional and the value of each character specifies
1198 * whether to set (a '+'), clear (a '-'), or leave untouched (a '\0') the
1199 * attribute value.
1201 * If the entry is an attribute name, then the A_SET_OP action is to be
1202 * performed for this attribute. If the entry is an attribute name proceeded
1203 * with "no", then the A_INVERSE_OP action is to be performed for this
1204 * attribute. If the entry is one or more attribute option letters, then step
1205 * through each of the option letters marking the action to be performed for
1206 * each of the attributes associated with the letter as A_SET_OP.
1208 * Returns 0 if the entry was a valid attribute(s) and the action to be
1209 * performed on that attribute(s) has been recorded, otherwise returns 1.
1211 static int
1212 parse_entry(char *entry, char action, char atype, int len, char *attractptr)
1214 char aopt[2] = {'\0', '\0'};
1215 char *aptr;
1216 f_attr_t attr;
1218 if (atype == A_VERBOSE_TYPE) {
1219 if ((attr = name_to_attr(entry)) != F_ATTR_INVAL) {
1220 return (set_attr_args(attr,
1221 (action == A_REPLACE_OP) ? A_SET_OP : action,
1222 attractptr));
1223 } else if ((len > 2) && (strncmp(entry, "no", 2) == 0) &&
1224 ((attr = name_to_attr(entry + 2)) != F_ATTR_INVAL)) {
1225 return (set_attr_args(attr, ((action == A_REPLACE_OP) ||
1226 (action == A_SET_OP)) ? A_INVERSE_OP : A_SET_OP,
1227 attractptr));
1228 } else {
1229 return (1);
1231 } else if (atype == A_COMPACT_TYPE) {
1232 for (aptr = entry; *aptr != '\0'; aptr++) {
1233 *aopt = *aptr;
1235 * The output of 'ls' can be used as the attribute mode
1236 * specification for chmod. This output can contain a
1237 * hypen ('-') for each attribute that is not set. If
1238 * so, ignore them. If a replace action is being
1239 * performed, then all attributes that don't have an
1240 * action set here, will be cleared down the line.
1242 if (*aptr == '-') {
1243 continue;
1245 if (set_attr_args(option_to_attr(aopt),
1246 (action == A_REPLACE_OP) ? A_SET_OP : action,
1247 attractptr) != 0) {
1248 return (1);
1251 return (0);
1253 return (1);
1257 * Parse the attribute specification, aoptsstr. Upon completion, attr_nvlist
1258 * will point to an nvlist which contains pairs of attribute names and values
1259 * to be set; attr_nvlist will be NULL if it is a no-op.
1261 * The attribute specification format is
1262 * S[oper]attr_type[attribute_list]
1263 * where oper is
1264 * + set operation of specified attributes in attribute list.
1265 * This is the default operation.
1266 * - inverse operation of specified attributes in attribute list
1267 * = replace operation of all attributes. All attribute operations
1268 * depend on those specified in the attribute list. Attributes
1269 * not specified in the attribute list will be cleared.
1270 * where attr_type is
1271 * c compact type. Each entry in the attribute list is a character
1272 * option representing an associated attribute name.
1273 * v verbose type. Each entry in the attribute list is an
1274 * an attribute name which can optionally be preceeded with "no"
1275 * (to imply the attribute should be cleared).
1276 * a all attributes type. The oper should be applied to all
1277 * read-write boolean system attributes. No attribute list should
1278 * be specified after an 'a' attribute type.
1280 * Returns 0 if aoptsstr contained a valid attribute specification,
1281 * otherwise, returns 1.
1283 static int
1284 parse_attr_args(char *aoptsstr, sec_args_t **sec_args)
1286 char action;
1287 char *attractptr;
1288 char atype;
1289 char *entry;
1290 char *eptr;
1291 char *nextattr;
1292 char *nextentry;
1293 char *subentry;
1294 char *teptr;
1295 char tok[] = {'\0', '\0'};
1296 int len;
1297 f_attr_t i;
1298 int numofattrs;
1300 if ((*aoptsstr != 'S') || (*(aoptsstr + 1) == '\0')) {
1301 return (1);
1304 if ((eptr = strdup(aoptsstr + 1)) == NULL) {
1305 perror("chmod");
1306 exit(2);
1308 entry = eptr;
1311 * Create a positional character array to determine a single attribute
1312 * operation to be performed, where each index represents the system
1313 * attribute affected, and it's value in the array represents the action
1314 * to be performed, i.e., a value of '+' means to set the attribute, a
1315 * value of '-' means to clear the attribute, and a value of '\0' means
1316 * to leave the attribute untouched. Initially, this positional
1317 * character array is all '\0's, representing a no-op.
1319 if ((numofattrs = attr_count()) < 1) {
1320 errmsg(1, 1, gettext("system attributes not supported\n"));
1323 if ((attractptr = calloc(numofattrs, sizeof (char))) == NULL) {
1324 perror("chmod");
1325 exit(2);
1328 if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) {
1329 perror("chmod");
1330 exit(2);
1332 (*sec_args)->sec_type = SEC_ATTR;
1333 (*sec_args)->sec_attrs = NULL;
1335 /* Parse each attribute operation within the attribute specification. */
1336 while ((entry != NULL) && (*entry != '\0')) {
1337 action = A_SET_OP;
1338 atype = '\0';
1340 /* Get the operator. */
1341 switch (*entry) {
1342 case A_SET_OP:
1343 case A_INVERSE_OP:
1344 case A_REPLACE_OP:
1345 action = *entry++;
1346 break;
1347 case A_COMPACT_TYPE:
1348 case A_VERBOSE_TYPE:
1349 case A_ALLATTRS_TYPE:
1350 atype = *entry++;
1351 action = A_SET_OP;
1352 break;
1353 default:
1354 break;
1357 /* An attribute type must be specified. */
1358 if (atype == '\0') {
1359 if ((*entry == A_COMPACT_TYPE) ||
1360 (*entry == A_VERBOSE_TYPE) ||
1361 (*entry == A_ALLATTRS_TYPE)) {
1362 atype = *entry++;
1363 } else {
1364 return (1);
1368 /* Get the attribute specification separator. */
1369 if (*entry == LEFTBRACE) {
1370 *tok = RIGHTBRACE;
1371 entry++;
1372 } else {
1373 *tok = A_SEP;
1376 /* Get the attribute operation */
1377 if ((nextentry = strpbrk(entry, tok)) != NULL) {
1378 *nextentry = '\0';
1379 nextentry++;
1382 /* Check for a no-op */
1383 if ((*entry == '\0') && (atype != A_ALLATTRS_TYPE) &&
1384 (action != A_REPLACE_OP)) {
1385 entry = nextentry;
1386 continue;
1390 * Step through the attribute operation, setting the
1391 * appropriate values for the specified attributes in the
1392 * character array, attractptr. A value of '+' will mean the
1393 * attribute is to be set, and a value of '-' will mean the
1394 * attribute is to be cleared. If the value of an attribute
1395 * remains '\0', then no action is to be taken on that
1396 * attribute. As multiple operations specified are
1397 * accumulated, a single attribute setting operation is
1398 * represented in attractptr.
1400 len = strlen(entry);
1401 if ((*tok == RIGHTBRACE) || (action == A_REPLACE_OP) ||
1402 (atype == A_ALLATTRS_TYPE)) {
1404 if ((action == A_REPLACE_OP) ||
1405 (atype == A_ALLATTRS_TYPE)) {
1406 (void) memset(attractptr, '\0', numofattrs);
1409 if (len > 0) {
1410 if ((teptr = strdup(entry)) == NULL) {
1411 perror("chmod");
1412 exit(2);
1414 subentry = teptr;
1415 while (subentry != NULL) {
1416 if ((nextattr = strpbrk(subentry,
1417 A_SEP_TOK)) != NULL) {
1418 *nextattr = '\0';
1419 nextattr++;
1421 if (parse_entry(subentry, action,
1422 atype, len, attractptr) != 0) {
1423 return (1);
1425 subentry = nextattr;
1427 free(teptr);
1431 * If performing the replace action, record the
1432 * attributes and values for the rest of the
1433 * attributes that have not already been recorded,
1434 * otherwise record the specified action for all
1435 * attributes. Note: set_attr_args() will only record
1436 * the attribute and action if it is a boolean
1437 * read-write attribute so we don't need to worry
1438 * about checking it here.
1440 if ((action == A_REPLACE_OP) ||
1441 (atype == A_ALLATTRS_TYPE)) {
1442 for (i = 0; i < numofattrs; i++) {
1443 if (attractptr[i] == A_UNDEF_OP) {
1444 (void) set_attr_args(i,
1445 (action == A_SET_OP) ?
1446 A_SET_OP : A_INVERSE_OP,
1447 attractptr);
1452 } else {
1453 if (parse_entry(entry, action, atype, len,
1454 attractptr) != 0) {
1455 return (1);
1458 entry = nextentry;
1462 * Populate an nvlist with attribute name and boolean value pairs
1463 * using the single attribute operation.
1465 (*sec_args)->sec_attrs = set_attrs_nvlist(attractptr, numofattrs);
1466 free(attractptr);
1467 free(eptr);
1469 return (0);