xref: /illumos-gate/usr/src/cmd/chmod/chmod.c (revision 0cfdb6036e046270988a17ac442e4d717d426a44)
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 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T			*/
27 /*	  All Rights Reserved						*/
28 /*									*/
29 
30 /*
31  * University Copyright- Copyright (c) 1982, 1986, 1988
32  * The Regents of the University of California
33  * All Rights Reserved
34  *
35  * University Acknowledgment- Portions of this document are derived from
36  * software developed by the University of California, Berkeley, and its
37  * contributors.
38  */
39 
40 #pragma ident	"%Z%%M%	%I%	%E% SMI"
41 
42 /*
43  * chmod option mode files
44  * where
45  *	mode is [ugoa][+-=][rwxXlstugo] or an octal number
46  *	mode is [<+|->A[# <number] ]<aclspec>
47  *	mode is S<attrspec>
48  *	option is -R, -f, and -@
49  */
50 
51 /*
52  *  Note that many convolutions are necessary
53  *  due to the re-use of bits between locking
54  *  and setgid
55  */
56 
57 #include <unistd.h>
58 #include <stdlib.h>
59 #include <stdio.h>
60 #include <sys/types.h>
61 #include <sys/stat.h>
62 #include <fcntl.h>
63 #include <dirent.h>
64 #include <locale.h>
65 #include <string.h>	/* strerror() */
66 #include <stdarg.h>
67 #include <limits.h>
68 #include <ctype.h>
69 #include <errno.h>
70 #include <sys/acl.h>
71 #include <aclutils.h>
72 #include <libnvpair.h>
73 #include <libcmdutils.h>
74 #include <libgen.h>
75 #include <attr.h>
76 
77 static int	rflag;
78 static int	fflag;
79 
80 extern int	optind;
81 extern int	errno;
82 
83 static int	mac;		/* Alternate to argc (for parseargs) */
84 static char	**mav;		/* Alternate to argv (for parseargs) */
85 
86 static char	*ms;		/* Points to the mode argument */
87 
88 #define	ACL_ADD			1
89 #define	ACL_DELETE		2
90 #define	ACL_SLOT_DELETE		3
91 #define	ACL_REPLACE		4
92 #define	ACL_STRIP		5
93 
94 #define	LEFTBRACE	'{'
95 #define	RIGHTBRACE	'}'
96 #define	A_SEP		','
97 #define	A_SEP_TOK	","
98 
99 #define	A_COMPACT_TYPE	'c'
100 #define	A_VERBOSE_TYPE	'v'
101 #define	A_ALLATTRS_TYPE	'a'
102 
103 #define	A_SET_OP	'+'
104 #define	A_INVERSE_OP	'-'
105 #define	A_REPLACE_OP	'='
106 #define	A_UNDEF_OP	'\0'
107 
108 #define	A_SET_TEXT	"set"
109 #define	A_INVERSE_TEXT	"clear"
110 
111 #define	A_SET_VAL	B_TRUE
112 #define	A_CLEAR_VAL	B_FALSE
113 
114 #define	ATTR_OPTS	0
115 #define	ATTR_NAMES	1
116 
117 #define	sec_acls	secptr.acls
118 #define	sec_attrs	secptr.attrs
119 
120 typedef struct acl_args {
121 	acl_t	*acl_aclp;
122 	int	acl_slot;
123 	int	acl_action;
124 } acl_args_t;
125 
126 typedef enum {
127 	SEC_ACL,
128 	SEC_ATTR
129 } chmod_sec_t;
130 
131 typedef struct {
132 	chmod_sec_t		sec_type;
133 	union {
134 		acl_args_t	*acls;
135 		nvlist_t	*attrs;
136 	} secptr;
137 } sec_args_t;
138 
139 typedef struct attr_name {
140 	char			*name;
141 	struct attr_name	*next;
142 } attr_name_t;
143 
144 
145 extern mode_t newmode_common(char *ms, mode_t new_mode, mode_t umsk,
146     char *file, char *path, o_mode_t *group_clear_bits,
147     o_mode_t *group_set_bits);
148 
149 static int chmodr(char *dir, char *path, mode_t mode, mode_t umsk,
150     sec_args_t *secp, attr_name_t *attrname);
151 static int doacl(char *file, struct stat *st, acl_args_t *aclp);
152 static int dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp,
153     attr_name_t *attrnames);
154 static void handle_acl(char *name, o_mode_t group_clear_bits,
155     o_mode_t group_set_bits);
156 void errmsg(int severity, int code, char *format, ...);
157 static void free_attr_names(attr_name_t *attrnames);
158 static void parseargs(int ac, char *av[]);
159 static int parse_acl_args(char *arg, sec_args_t **sec_args);
160 static int parse_attr_args(char *arg, sec_args_t **sec_args);
161 static void print_attrs(int flag);
162 static int set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist);
163 static void usage(void);
164 
165 int
166 main(int argc, char *argv[])
167 {
168 	int		i, c;
169 	int		status = 0;
170 	mode_t		umsk;
171 	sec_args_t	*sec_args = NULL;
172 	attr_name_t	*attrnames = NULL;
173 	attr_name_t	*attrend = NULL;
174 	attr_name_t	*tattr;
175 
176 	(void) setlocale(LC_ALL, "");
177 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
178 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
179 #endif
180 	(void) textdomain(TEXT_DOMAIN);
181 
182 	parseargs(argc, argv);
183 
184 	while ((c = getopt(mac, mav, "Rf@:")) != EOF) {
185 		switch (c) {
186 		case 'R':
187 			rflag++;
188 			break;
189 		case 'f':
190 			fflag++;
191 			break;
192 		case '@':
193 			if (((tattr = malloc(sizeof (attr_name_t))) == NULL) ||
194 			    ((tattr->name = strdup(optarg)) == NULL)) {
195 				perror("chmod");
196 				exit(2);
197 			}
198 			if (attrnames == NULL) {
199 				attrnames = tattr;
200 				attrnames->next = NULL;
201 			} else {
202 				attrend->next = tattr;
203 			}
204 			attrend = tattr;
205 			break;
206 		case '?':
207 			usage();
208 			exit(2);
209 		}
210 	}
211 
212 	/*
213 	 * Check for sufficient arguments
214 	 * or a usage error.
215 	 */
216 
217 	mac -= optind;
218 	mav += optind;
219 	if ((mac >= 2) && (mav[0][0] == 'A')) {
220 		if (attrnames != NULL) {
221 			free_attr_names(attrnames);
222 			attrnames = NULL;
223 		}
224 		if (parse_acl_args(*mav, &sec_args)) {
225 			usage();
226 			exit(2);
227 		}
228 	} else if ((mac >= 2) && (mav[0][0] == 'S')) {
229 		if (parse_attr_args(*mav, &sec_args)) {
230 			usage();
231 			exit(2);
232 
233 		/* A no-op attribute operation was specified. */
234 		} else if (sec_args->sec_attrs == NULL) {
235 			exit(0);
236 		}
237 	} else {
238 		if (mac < 2) {
239 			usage();
240 			exit(2);
241 		}
242 		if (attrnames != NULL) {
243 			free_attr_names(attrnames);
244 			attrnames = NULL;
245 		}
246 	}
247 
248 	ms = mav[0];
249 
250 	umsk = umask(0);
251 	(void) umask(umsk);
252 
253 	for (i = 1; i < mac; i++) {
254 		status += dochmod(mav[i], mav[i], umsk, sec_args, attrnames);
255 	}
256 
257 	return (fflag ? 0 : status);
258 }
259 
260 static void
261 free_attr_names(attr_name_t *attrnames)
262 {
263 	attr_name_t	*attrnamesptr = attrnames;
264 	attr_name_t	*tptr;
265 
266 	while (attrnamesptr != NULL) {
267 		tptr = attrnamesptr->next;
268 		if (attrnamesptr->name != NULL) {
269 			free(attrnamesptr->name);
270 		}
271 		attrnamesptr = tptr;
272 	}
273 }
274 
275 static int
276 dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp,
277     attr_name_t *attrnames)
278 {
279 	static struct stat st;
280 	int linkflg = 0;
281 	o_mode_t	group_clear_bits, group_set_bits;
282 
283 	if (lstat(name, &st) < 0) {
284 		errmsg(2, 0, gettext("can't access %s\n"), path);
285 		return (1);
286 	}
287 
288 	if ((st.st_mode & S_IFMT) == S_IFLNK) {
289 		linkflg = 1;
290 		if (stat(name, &st) < 0) {
291 			errmsg(2, 0, gettext("can't access %s\n"), path);
292 			return (1);
293 		}
294 	}
295 
296 	/* Do not recurse if directory is object of symbolic link */
297 	if (rflag && ((st.st_mode & S_IFMT) == S_IFDIR) && !linkflg) {
298 		return (chmodr(name, path, st.st_mode, umsk, secp, attrnames));
299 	}
300 
301 	if (secp != NULL) {
302 		if (secp->sec_type == SEC_ACL) {
303 			return (doacl(name, &st, secp->sec_acls));
304 		} else if (secp->sec_type == SEC_ATTR) {
305 			return (set_attrs(name, attrnames, secp->sec_attrs));
306 		} else {
307 			return (1);
308 		}
309 	} else {
310 		if (chmod(name, newmode_common(ms, st.st_mode, umsk, name, path,
311 		    &group_clear_bits, &group_set_bits)) == -1) {
312 			errmsg(2, 0, gettext("can't change %s\n"), path);
313 			return (1);
314 		}
315 	}
316 
317 	/*
318 	 * If the group permissions of the file are being modified,
319 	 * make sure that the file's ACL (if it has one) is
320 	 * modified also, since chmod is supposed to apply group
321 	 * permissions changes to both the acl mask and the
322 	 * general group permissions.
323 	 */
324 	if (group_clear_bits || group_set_bits)
325 		handle_acl(name, group_clear_bits, group_set_bits);
326 
327 	return (0);
328 }
329 
330 static int
331 chmodr(char *dir, char *path,  mode_t mode, mode_t umsk, sec_args_t *secp,
332     attr_name_t *attrnames)
333 {
334 
335 	DIR *dirp;
336 	struct dirent *dp;
337 	char savedir[PATH_MAX];			/* dir name to restore */
338 	char currdir[PATH_MAX+1];		/* current dir name + '/' */
339 	char parentdir[PATH_MAX+1];		/* parent dir name  + '/' */
340 	int ecode;
341 	struct stat st;
342 	o_mode_t	group_clear_bits, group_set_bits;
343 
344 	if (getcwd(savedir, PATH_MAX) == 0)
345 		errmsg(2, 255, gettext("chmod: could not getcwd %s\n"),
346 		    savedir);
347 
348 	/*
349 	 * Change what we are given before doing it's contents
350 	 */
351 	if (secp != NULL) {
352 		if (lstat(dir, &st) < 0) {
353 			errmsg(2, 0, gettext("can't access %s\n"), path);
354 			return (1);
355 		}
356 		if (secp->sec_type == SEC_ACL) {
357 			if (doacl(dir, &st, secp->sec_acls) != 0)
358 				return (1);
359 		} else if (secp->sec_type == SEC_ATTR) {
360 			if (set_attrs(dir, attrnames, secp->sec_attrs) != 0) {
361 				return (1);
362 			}
363 		} else {
364 			return (1);
365 		}
366 	} else if (chmod(dir, newmode_common(ms, mode, umsk, dir, path,
367 	    &group_clear_bits, &group_set_bits)) < 0) {
368 		errmsg(2, 0, gettext("can't change %s\n"), path);
369 		return (1);
370 	}
371 
372 	/*
373 	 * If the group permissions of the file are being modified,
374 	 * make sure that the file's ACL (if it has one) is
375 	 * modified also, since chmod is supposed to apply group
376 	 * permissions changes to both the acl mask and the
377 	 * general group permissions.
378 	 */
379 
380 	if (secp != NULL) {
381 		/* only necessary when not setting ACL or system attributes */
382 		if (group_clear_bits || group_set_bits)
383 			handle_acl(dir, group_clear_bits, group_set_bits);
384 	}
385 
386 	if (chdir(dir) < 0) {
387 		errmsg(2, 0, "%s/%s: %s\n", savedir, dir, strerror(errno));
388 		return (1);
389 	}
390 	if ((dirp = opendir(".")) == NULL) {
391 		errmsg(2, 0, "%s\n", strerror(errno));
392 		return (1);
393 	}
394 	ecode = 0;
395 
396 	/*
397 	 * Save parent directory path before recursive chmod.
398 	 * We'll need this for error printing purposes. Add
399 	 * a trailing '/' to the path except in the case where
400 	 * the path is just '/'
401 	 */
402 
403 	if (strlcpy(parentdir, path, PATH_MAX + 1) >= PATH_MAX + 1) {
404 		errmsg(2, 0, gettext("directory path name too long: %s\n"),
405 		    path);
406 		return (1);
407 	}
408 	if (strcmp(path, "/") != 0)
409 		if (strlcat(parentdir, "/", PATH_MAX + 1) >= PATH_MAX + 1) {
410 			errmsg(2, 0,
411 			    gettext("directory path name too long: %s/\n"),
412 			    parentdir);
413 			return (1);
414 		}
415 
416 
417 	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp))  {
418 
419 		if (strcmp(dp->d_name, ".") == 0 ||	/* skip . and .. */
420 		    strcmp(dp->d_name, "..") == 0) {
421 			continue;
422 		}
423 		if (strlcpy(currdir, parentdir, PATH_MAX + 1) >= PATH_MAX + 1) {
424 			errmsg(2, 0,
425 			    gettext("directory path name too long: %s\n"),
426 			    parentdir);
427 			return (1);
428 		}
429 		if (strlcat(currdir, dp->d_name, PATH_MAX + 1)
430 		    >= PATH_MAX + 1) {
431 			errmsg(2, 0,
432 			    gettext("directory path name too long: %s%s\n"),
433 			    currdir, dp->d_name);
434 			return (1);
435 		}
436 		ecode += dochmod(dp->d_name, currdir, umsk, secp, attrnames);
437 	}
438 	(void) closedir(dirp);
439 	if (chdir(savedir) < 0) {
440 		errmsg(2, 255, gettext("can't change back to %s\n"), savedir);
441 	}
442 	return (ecode ? 1 : 0);
443 }
444 
445 /* PRINTFLIKE3 */
446 void
447 errmsg(int severity, int code, char *format, ...)
448 {
449 	va_list ap;
450 	static char *msg[] = {
451 	"",
452 	"ERROR",
453 	"WARNING",
454 	""
455 	};
456 
457 	va_start(ap, format);
458 
459 	/*
460 	 * Always print error message if this is a fatal error (code == 0);
461 	 * otherwise, print message if fflag == 0 (no -f option specified)
462 	 */
463 	if (!fflag || (code != 0)) {
464 		(void) fprintf(stderr,
465 		    "chmod: %s: ", gettext(msg[severity]));
466 		(void) vfprintf(stderr, format, ap);
467 	}
468 
469 	va_end(ap);
470 
471 	if (code != 0)
472 		exit(fflag ? 0 : code);
473 }
474 
475 static void
476 usage(void)
477 {
478 	(void) fprintf(stderr, gettext(
479 	    "usage:\tchmod [-fR] <absolute-mode> file ...\n"));
480 
481 	(void) fprintf(stderr, gettext(
482 	    "\tchmod [-fR] [-@ attribute] ... "
483 	    "S<attribute-operation> file ...\n"));
484 
485 	(void) fprintf(stderr, gettext(
486 	    "\tchmod [-fR] <ACL-operation> file ...\n"));
487 
488 	(void) fprintf(stderr, gettext(
489 	    "\tchmod [-fR] <symbolic-mode-list> file ...\n\n"));
490 
491 	(void) fprintf(stderr, gettext(
492 	    "where \t<symbolic-mode-list> is a comma-separated list of\n"));
493 	(void) fprintf(stderr, gettext(
494 	    "\t[ugoa]{+|-|=}[rwxXlstugo]\n\n"));
495 
496 	(void) fprintf(stderr, gettext(
497 	    "where \t<attribute-operation> is a comma-separated list of\n"
498 	    "\tone or more of the following\n"));
499 	(void) fprintf(stderr, gettext(
500 	    "\t[+|-|=]c[<compact-attribute-list>|{<compact-attribute-list>}]\n"
501 	    "\t[+|-|=]v[<verbose-attribute-setting>|"
502 	    "\'{\'<verbose-attribute-setting-list>\'}\']\n"
503 	    "\t[+|-|=]a\n"));
504 	(void) fprintf(stderr, gettext(
505 	    "where \t<compact-attribute-list> is a list of zero or more of\n"));
506 	print_attrs(ATTR_OPTS);
507 	(void) fprintf(stderr, gettext(
508 	    "where \t<verbose-attribute-setting> is one of\n"));
509 	print_attrs(ATTR_NAMES);
510 	(void) fprintf(stderr, gettext(
511 	    "\tand can be, optionally, immediately preceded by \"no\"\n\n"));
512 
513 	(void) fprintf(stderr, gettext(
514 	    "where \t<ACL-operation> is one of the following\n"));
515 	(void) fprintf(stderr, gettext("\tA-<acl_specification>\n"));
516 	(void) fprintf(stderr, gettext("\tA[number]-\n"));
517 	(void) fprintf(stderr, gettext(
518 	    "\tA[number]{+|=}<acl_specification>\n"));
519 	(void) fprintf(stderr, gettext(
520 	    "where \t<acl-specification> is a comma-separated list of ACEs\n"));
521 }
522 
523 /*
524  *  parseargs - generate getopt-friendly argument list for backwards
525  *		compatibility with earlier Solaris usage (eg, chmod -w
526  *		foo).
527  *
528  *  assumes the existence of a static set of alternates to argc and argv,
529  *  (namely, mac, and mav[]).
530  *
531  */
532 
533 static void
534 parseargs(int ac, char *av[])
535 {
536 	int i;			/* current argument			*/
537 	int fflag;		/* arg list contains "--"		*/
538 	size_t mav_num;		/* number of entries in mav[]		*/
539 
540 	/*
541 	 * We add an extra argument slot, in case we need to jam a "--"
542 	 * argument into the list.
543 	 */
544 
545 	mav_num = (size_t)ac+2;
546 
547 	if ((mav = calloc(mav_num, sizeof (char *))) == NULL) {
548 		perror("chmod");
549 		exit(2);
550 	}
551 
552 	/* scan for the use of "--" in the argument list */
553 
554 	for (fflag = i = 0; i < ac; i ++) {
555 		if (strcmp(av[i], "--") == 0)
556 			fflag = 1;
557 	}
558 
559 	/* process the arguments */
560 
561 	for (i = mac = 0;
562 	    (av[i] != (char *)NULL) && (av[i][0] != (char)NULL);
563 	    i++) {
564 		if (!fflag && av[i][0] == '-') {
565 			/*
566 			 *  If there is not already a "--" argument specified,
567 			 *  and the argument starts with '-' but does not
568 			 *  contain any of the official option letters, then it
569 			 *  is probably a mode argument beginning with '-'.
570 			 *  Force a "--" into the argument stream in front of
571 			 *  it.
572 			 */
573 
574 			if ((strchr(av[i], 'R') == NULL &&
575 			    strchr(av[i], 'f') == NULL) &&
576 			    strchr(av[i], '@') == NULL) {
577 				if ((mav[mac++] = strdup("--")) == NULL) {
578 					perror("chmod");
579 					exit(2);
580 				}
581 			}
582 		}
583 
584 		if ((mav[mac++] = strdup(av[i])) == NULL) {
585 			perror("chmod");
586 			exit(2);
587 		}
588 	}
589 
590 	mav[mac] = (char *)NULL;
591 }
592 
593 static int
594 parse_acl_args(char *arg, sec_args_t **sec_args)
595 {
596 	acl_t *new_acl = NULL;
597 	int slot;
598 	int len;
599 	int action;
600 	acl_args_t *new_acl_args;
601 	char *acl_spec = NULL;
602 	char *end;
603 
604 	if (arg[0] != 'A')
605 		return (1);
606 
607 	slot = strtol(&arg[1], &end, 10);
608 
609 	len = strlen(arg);
610 	switch (*end) {
611 	case '+':
612 		action = ACL_ADD;
613 		acl_spec = ++end;
614 		break;
615 	case '-':
616 		if (len == 2 && arg[0] == 'A' && arg[1] == '-')
617 			action = ACL_STRIP;
618 		else
619 			action = ACL_DELETE;
620 		if (action != ACL_STRIP) {
621 			acl_spec = ++end;
622 			if (acl_spec[0] == '\0') {
623 				action = ACL_SLOT_DELETE;
624 				acl_spec = NULL;
625 			} else if (arg[1] != '-')
626 				return (1);
627 		}
628 		break;
629 	case '=':
630 		/*
631 		 * Was slot specified?
632 		 */
633 		if (arg[1] == '=')
634 			slot = -1;
635 		action = ACL_REPLACE;
636 		acl_spec = ++end;
637 		break;
638 	default:
639 		return (1);
640 	}
641 
642 	if ((action == ACL_REPLACE || action == ACL_ADD) && acl_spec[0] == '\0')
643 		return (1);
644 
645 	if (acl_spec) {
646 		if (acl_parse(acl_spec, &new_acl)) {
647 			exit(1);
648 		}
649 	}
650 
651 	new_acl_args = malloc(sizeof (acl_args_t));
652 	if (new_acl_args == NULL)
653 		return (1);
654 
655 	new_acl_args->acl_aclp = new_acl;
656 	new_acl_args->acl_slot = slot;
657 	new_acl_args->acl_action = action;
658 
659 	if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) {
660 		perror("chmod");
661 		exit(2);
662 	}
663 	(*sec_args)->sec_type = SEC_ACL;
664 	(*sec_args)->sec_acls = new_acl_args;
665 
666 	return (0);
667 }
668 
669 /*
670  * This function is called whenever the group permissions of a file
671  * is being modified.  According to the chmod(1) manpage, any
672  * change made to the group permissions must be applied to both
673  * the acl mask and the acl's GROUP_OBJ.  The chmod(2) already
674  * set the mask, so this routine needs to make the same change
675  * to the GROUP_OBJ.
676  */
677 static void
678 handle_acl(char *name, o_mode_t group_clear_bits, o_mode_t group_set_bits)
679 {
680 	int aclcnt, n;
681 	aclent_t *aclp, *tp;
682 	o_mode_t newperm;
683 	/*
684 	 * if this file system support ace_t acl's
685 	 * then simply return since we don't have an
686 	 * acl mask to deal with
687 	 */
688 	if (pathconf(name, _PC_ACL_ENABLED) == _ACL_ACE_ENABLED)
689 		return;
690 	if ((aclcnt = acl(name, GETACLCNT, 0, NULL)) <= MIN_ACL_ENTRIES)
691 		return;	/* it's just a trivial acl; no need to change it */
692 	if ((aclp = (aclent_t *)malloc((sizeof (aclent_t)) * aclcnt))
693 	    == NULL) {
694 		perror("chmod");
695 		exit(2);
696 	}
697 
698 	if (acl(name, GETACL, aclcnt, aclp) < 0) {
699 		free(aclp);
700 		(void) fprintf(stderr, "chmod: ");
701 		perror(name);
702 		return;
703 	}
704 	for (tp = aclp, n = aclcnt; n--; tp++) {
705 		if (tp->a_type == GROUP_OBJ) {
706 			newperm = tp->a_perm;
707 			if (group_clear_bits != 0)
708 				newperm &= ~group_clear_bits;
709 			if (group_set_bits != 0)
710 				newperm |= group_set_bits;
711 			if (newperm != tp->a_perm) {
712 				tp->a_perm = newperm;
713 				if (acl(name, SETACL, aclcnt, aclp)
714 				    < 0) {
715 					(void) fprintf(stderr, "chmod: ");
716 					perror(name);
717 				}
718 			}
719 			break;
720 		}
721 	}
722 	free(aclp);
723 }
724 
725 static int
726 doacl(char *file, struct stat *st, acl_args_t *acl_args)
727 {
728 	acl_t *aclp;
729 	acl_t *set_aclp;
730 	int error = 0;
731 	void *to, *from;
732 	int len;
733 	int isdir;
734 	isdir = S_ISDIR(st->st_mode);
735 
736 	error = acl_get(file, 0, &aclp);
737 
738 	if (error != 0) {
739 		errmsg(1, 1, "%s\n", acl_strerror(error));
740 		return (1);
741 	}
742 	switch (acl_args->acl_action) {
743 	case ACL_ADD:
744 		if ((error = acl_addentries(aclp,
745 		    acl_args->acl_aclp, acl_args->acl_slot)) != 0) {
746 			errmsg(1, 1, "%s\n", acl_strerror(error));
747 			acl_free(aclp);
748 			return (1);
749 		}
750 		set_aclp = aclp;
751 		break;
752 	case ACL_SLOT_DELETE:
753 		if (acl_args->acl_slot + 1 > aclp->acl_cnt) {
754 			errmsg(1, 1,
755 			    gettext("Invalid slot specified for removal\n"));
756 			acl_free(aclp);
757 			return (1);
758 		}
759 
760 		if (acl_args->acl_slot == 0 && aclp->acl_cnt == 1) {
761 			errmsg(1, 1,
762 			    gettext("Can't remove all ACL "
763 			    "entries from a file\n"));
764 			acl_free(aclp);
765 			return (1);
766 		}
767 
768 		/*
769 		 * remove a single entry
770 		 *
771 		 * if last entry just adjust acl_cnt
772 		 */
773 
774 		if ((acl_args->acl_slot + 1) == aclp->acl_cnt)
775 			aclp->acl_cnt--;
776 		else {
777 			to = (char *)aclp->acl_aclp +
778 			    (acl_args->acl_slot * aclp->acl_entry_size);
779 			from = (char *)to + aclp->acl_entry_size;
780 			len = (aclp->acl_cnt - acl_args->acl_slot - 1) *
781 			    aclp->acl_entry_size;
782 			(void) memmove(to, from, len);
783 			aclp->acl_cnt--;
784 		}
785 		set_aclp = aclp;
786 		break;
787 
788 	case ACL_DELETE:
789 		if ((error = acl_removeentries(aclp, acl_args->acl_aclp,
790 		    acl_args->acl_slot, ACL_REMOVE_ALL)) != 0) {
791 			errmsg(1, 1, "%s\n", acl_strerror(error));
792 			acl_free(aclp);
793 			return (1);
794 		}
795 
796 		if (aclp->acl_cnt == 0) {
797 			errmsg(1, 1,
798 			    gettext("Can't remove all ACL "
799 			    "entries from a file\n"));
800 			acl_free(aclp);
801 			return (1);
802 		}
803 
804 		set_aclp = aclp;
805 		break;
806 	case ACL_REPLACE:
807 		if (acl_args->acl_slot >= 0)  {
808 			error = acl_modifyentries(aclp, acl_args->acl_aclp,
809 			    acl_args->acl_slot);
810 			if (error) {
811 				errmsg(1, 1, "%s\n", acl_strerror(error));
812 				acl_free(aclp);
813 				return (1);
814 			}
815 			set_aclp = aclp;
816 		} else {
817 			set_aclp = acl_args->acl_aclp;
818 		}
819 		break;
820 	case ACL_STRIP:
821 		error = acl_strip(file, st->st_uid, st->st_gid, st->st_mode);
822 		if (error) {
823 			errmsg(1, 1, "%s\n", acl_strerror(error));
824 			return (1);
825 		}
826 		acl_free(aclp);
827 		return (0);
828 		/*NOTREACHED*/
829 	default:
830 		errmsg(1, 0, gettext("Unknown ACL action requested\n"));
831 		return (1);
832 		break;
833 	}
834 	error = acl_check(set_aclp, isdir);
835 
836 	if (error) {
837 		errmsg(1, 0, "%s\n%s", acl_strerror(error),
838 		    gettext("See chmod(1) for more information on "
839 		    "valid ACL syntax\n"));
840 		return (1);
841 	}
842 	if ((error = acl_set(file, set_aclp)) != 0) {
843 			errmsg(1, 0, gettext("Failed to set ACL: %s\n"),
844 			    acl_strerror(error));
845 			acl_free(aclp);
846 			return (1);
847 	}
848 	acl_free(aclp);
849 	return (0);
850 }
851 
852 /*
853  * Prints out the attributes in their verbose form:
854  *	'{'[["no"]<attribute-name>][,["no"]<attribute-name>]...'}'
855  * similar to output of ls -/v.
856  */
857 static void
858 print_nvlist(nvlist_t *attr_nvlist)
859 {
860 	int		firsttime = 1;
861 	boolean_t	value;
862 	nvlist_t	*lptr = attr_nvlist;
863 	nvpair_t	*pair = NULL;
864 
865 	(void) fprintf(stderr, "\t%c", LEFTBRACE);
866 	while (pair = nvlist_next_nvpair(lptr, pair)) {
867 		if (nvpair_value_boolean_value(pair, &value) == 0) {
868 			(void) fprintf(stderr, "%s%s%s",
869 			    firsttime ? "" : A_SEP_TOK,
870 			    (value == A_SET_VAL) ? "" : "no",
871 			    nvpair_name(pair));
872 			firsttime = 0;
873 		} else {
874 			(void) fprintf(stderr, gettext(
875 			    "<error retrieving attributes: %s>"),
876 			    strerror(errno));
877 			break;
878 		}
879 	}
880 	(void) fprintf(stderr, "%c\n", RIGHTBRACE);
881 }
882 
883 /*
884  * Add an attribute name and boolean value to an nvlist if an action is to be
885  * performed for that attribute.  The nvlist will be used later to set all the
886  * attributes in the nvlist in one operation through a call to setattrat().
887  *
888  * If a set operation ('+') was specified, then a boolean representation of the
889  * attribute's value will be added to the nvlist for that attribute name.  If an
890  * inverse operation ('-') was specified, then a boolean representation of the
891  * inverse of the attribute's value will be added to the nvlist for that
892  * attribute name.
893  *
894  * Returns an nvlist of attribute name and boolean value pairs if there are
895  * attribute actions to be performed, otherwise returns NULL.
896  */
897 static nvlist_t *
898 set_attrs_nvlist(char *attractptr, int numofattrs)
899 {
900 	int		attribute_set = 0;
901 	f_attr_t	i;
902 	nvlist_t	*attr_nvlist;
903 
904 	if (nvlist_alloc(&attr_nvlist, NV_UNIQUE_NAME, 0) != 0) {
905 		perror("chmod");
906 		exit(2);
907 	}
908 
909 	for (i = 0; i < numofattrs; i++) {
910 		if (attractptr[i] != '\0') {
911 			if ((nvlist_add_boolean_value(attr_nvlist,
912 			    attr_to_name(i),
913 			    (attractptr[i] == A_SET_OP))) != 0) {
914 				errmsg(1, 2, gettext(
915 				    "unable to propagate attribute names and"
916 				    "values: %s\n"), strerror(errno));
917 			} else {
918 				attribute_set = 1;
919 			}
920 		}
921 	}
922 	return (attribute_set ? attr_nvlist : NULL);
923 }
924 
925 /*
926  * Set the attributes of file, or if specified, of the named attribute file,
927  * attrname.  Build an nvlist of attribute names and values and call setattrat()
928  * to set the attributes in one operation.
929  *
930  * Returns 0 if successful, otherwise returns 1.
931  */
932 static int
933 set_file_attrs(char *file, char *attrname, nvlist_t *attr_nvlist)
934 {
935 	int	rc;
936 	char	*filename;
937 
938 	if (attrname != NULL) {
939 		filename = attrname;
940 	} else {
941 		filename = basename(file);
942 	}
943 
944 	if ((rc = setattrat(AT_FDCWD, XATTR_VIEW_READWRITE, filename,
945 	    attr_nvlist)) != 0) {
946 		char *emsg;
947 		switch (errno) {
948 		case EINVAL:
949 			emsg = gettext("not supported");
950 			break;
951 		case EPERM:
952 			emsg = gettext("not privileged");
953 			break;
954 		default:
955 			emsg = strerror(rc);
956 		}
957 		errmsg(1, 0, gettext(
958 		    "cannot set the following attributes on "
959 		    "%s%s%s%s: %s\n"),
960 		    (attrname == NULL) ? "" : gettext("attribute "),
961 		    (attrname == NULL) ? "" : attrname,
962 		    (attrname == NULL) ? "" : gettext(" of "),
963 		    file, emsg);
964 		print_nvlist(attr_nvlist);
965 	}
966 
967 	return (rc);
968 }
969 
970 static int
971 save_cwd(void)
972 {
973 	return (open(".", O_RDONLY));
974 }
975 
976 static void
977 rest_cwd(int cwd)
978 {
979 	if (cwd != -1) {
980 		if (fchdir(cwd) != 0) {
981 			errmsg(1, 1, gettext(
982 			    "can't change to current working directory\n"));
983 		}
984 		(void) close(cwd);
985 	}
986 }
987 
988 /*
989  * Returns 1 if filename is a system attribute file, otherwise
990  * returns 0.
991  */
992 static int
993 is_sattr(char *filename)
994 {
995 	return (sysattr_type(filename) != _NOT_SATTR);
996 }
997 
998 /*
999  * Perform the action on the specified named attribute file for the file
1000  * associated with the input file descriptor.  If the named attribute file
1001  * is "*", then the action is to be performed on all the named attribute files
1002  * of the file associated with the input file descriptor.
1003  */
1004 static int
1005 set_named_attrs(char *file, int parentfd, char *attrname, nvlist_t *attr_nvlist)
1006 {
1007 	int		dirfd;
1008 	int		error = 0;
1009 	DIR		*dirp = NULL;
1010 	struct dirent	*dp;
1011 	struct stat	st;
1012 
1013 	if ((attrname == NULL) || (strcmp(attrname, "*") != 0)) {
1014 		/*
1015 		 * Make sure the named attribute exists and extended system
1016 		 * attributes are supported on the underlying file system.
1017 		 */
1018 		if (attrname != NULL) {
1019 			if (fstatat(parentfd, attrname, &st,
1020 			    AT_SYMLINK_NOFOLLOW) < 0) {
1021 				errmsg(2, 0, gettext(
1022 				    "can't access attribute %s of %s\n"),
1023 				    attrname, file);
1024 				return (1);
1025 			}
1026 			if (sysattr_support(attrname, _PC_SATTR_ENABLED) != 1) {
1027 				errmsg(1, 0, gettext(
1028 				    "extended system attributes not supported "
1029 				    "for attribute %s of %s\n"),
1030 				    attrname, file);
1031 				return (1);
1032 			}
1033 		}
1034 
1035 		error = set_file_attrs(file, attrname, attr_nvlist);
1036 
1037 	} else {
1038 		if (((dirfd = dup(parentfd)) == -1) ||
1039 		    ((dirp = fdopendir(dirfd)) == NULL)) {
1040 			errmsg(1, 0, gettext(
1041 			    "cannot open dir pointer of file %s\n"), file);
1042 			if (dirfd > 0) {
1043 				(void) close(dirfd);
1044 			}
1045 			return (1);
1046 		}
1047 
1048 		while (dp = readdir(dirp)) {
1049 			/*
1050 			 * Process all extended attribute files except
1051 			 * ".", "..", and extended system attribute files.
1052 			 */
1053 			if ((strcmp(dp->d_name, ".") == 0) ||
1054 			    (strcmp(dp->d_name, "..") == 0) ||
1055 			    is_sattr(dp->d_name)) {
1056 				continue;
1057 			}
1058 
1059 			if (set_named_attrs(file, parentfd, dp->d_name,
1060 			    attr_nvlist) != 0) {
1061 				error++;
1062 			}
1063 		}
1064 		if (dirp != NULL) {
1065 			(void) closedir(dirp);
1066 		}
1067 	}
1068 
1069 	return ((error == 0) ? 0 : 1);
1070 }
1071 
1072 /*
1073  * Set the attributes of the specified file, or if specified with -@ on the
1074  * command line, the specified named attributes of the specified file.
1075  *
1076  * Returns 0 if successful, otherwise returns 1.
1077  */
1078 static int
1079 set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist)
1080 {
1081 	char		*parentd;
1082 	char		*tpath = NULL;
1083 	int		cwd;
1084 	int		error = 0;
1085 	int		parentfd;
1086 	attr_name_t	*tattr = attrnames;
1087 
1088 	if (attr_nvlist == NULL) {
1089 		return (0);
1090 	}
1091 
1092 	if (sysattr_support(file, _PC_SATTR_ENABLED) != 1) {
1093 		errmsg(1, 0, gettext(
1094 		    "extended system attributes not supported for %s\n"), file);
1095 		return (1);
1096 	}
1097 
1098 	/*
1099 	 * Open the parent directory and change into it before attempting
1100 	 * to set the attributes of the file.
1101 	 */
1102 	if (attrnames == NULL) {
1103 		tpath = strdup(file);
1104 		parentd = dirname(tpath);
1105 		parentfd = open(parentd, O_RDONLY);
1106 	} else {
1107 		parentfd = attropen(file, ".", O_RDONLY);
1108 	}
1109 	if (parentfd == -1) {
1110 		errmsg(1, 0, gettext(
1111 		    "cannot open attribute directory of %s\n"), file);
1112 		if (tpath != NULL) {
1113 			free(tpath);
1114 		}
1115 		return (1);
1116 	}
1117 
1118 	if ((cwd = save_cwd()) < 0) {
1119 		errmsg(1, 1, gettext(
1120 		    "can't get current working directory\n"));
1121 	}
1122 	if (fchdir(parentfd) != 0) {
1123 		errmsg(1, 0, gettext(
1124 		    "can't change to parent %sdirectory of %s\n"),
1125 		    (attrnames == NULL) ? "" : gettext("attribute "), file);
1126 		(void) close(cwd);
1127 		(void) close(parentfd);
1128 		if (tpath != NULL) {
1129 			free(tpath);
1130 		}
1131 		return (1);
1132 	}
1133 
1134 	/*
1135 	 * If no named attribute file names were provided on the command line
1136 	 * then set the attributes of the base file, otherwise, set the
1137 	 * attributes for each of the named attribute files specified.
1138 	 */
1139 	if (attrnames == NULL) {
1140 		error = set_named_attrs(file, parentfd, NULL, attr_nvlist);
1141 		free(tpath);
1142 	} else {
1143 		while (tattr != NULL) {
1144 			if (set_named_attrs(file, parentfd, tattr->name,
1145 			    attr_nvlist) != 0) {
1146 				error++;
1147 			}
1148 			tattr = tattr->next;
1149 		}
1150 	}
1151 	(void) close(parentfd);
1152 	rest_cwd(cwd);
1153 
1154 	return ((error == 0) ? 0 : 1);
1155 }
1156 
1157 /*
1158  * Prints the attributes in either the compact or verbose form indicated
1159  * by flag.
1160  */
1161 static void
1162 print_attrs(int flag)
1163 {
1164 	f_attr_t	i;
1165 	static int	numofattrs;
1166 	int		firsttime = 1;
1167 
1168 	numofattrs = attr_count();
1169 
1170 	(void) fprintf(stderr, gettext("\t["));
1171 	for (i = 0; i < numofattrs; i++) {
1172 		if ((attr_to_xattr_view(i) != XATTR_VIEW_READWRITE) ||
1173 		    (attr_to_data_type(i) != DATA_TYPE_BOOLEAN_VALUE)) {
1174 			continue;
1175 		}
1176 		(void) fprintf(stderr, "%s%s",
1177 		    (firsttime == 1) ? "" : gettext("|"),
1178 		    (flag == ATTR_OPTS) ? attr_to_option(i) : attr_to_name(i));
1179 		firsttime = 0;
1180 	}
1181 	(void) fprintf(stderr, gettext("]\n"));
1182 }
1183 
1184 /*
1185  * Record what action should be taken on the specified attribute. Only boolean
1186  * read-write attributes can be manipulated.
1187  *
1188  * Returns 0 if successful, otherwise returns 1.
1189  */
1190 static int
1191 set_attr_args(f_attr_t attr, char action, char *attractptr)
1192 {
1193 	if ((attr_to_xattr_view(attr) == XATTR_VIEW_READWRITE) &&
1194 	    (attr_to_data_type(attr) == DATA_TYPE_BOOLEAN_VALUE)) {
1195 		attractptr[attr] = action;
1196 		return (0);
1197 	}
1198 	return (1);
1199 }
1200 
1201 /*
1202  * Parses the entry and assigns the appropriate action (either '+' or '-' in
1203  * attribute's position in the character array pointed to by attractptr, where
1204  * upon exit, attractptr is positional and the value of each character specifies
1205  * whether to set (a '+'), clear (a '-'), or leave untouched (a '\0') the
1206  * attribute value.
1207  *
1208  * If the entry is an attribute name, then the A_SET_OP action is to be
1209  * performed for this attribute.  If the entry is an attribute name proceeded
1210  * with "no", then the A_INVERSE_OP action is to be performed for this
1211  * attribute.  If the entry is one or more attribute option letters, then step
1212  * through each of the option letters marking the action to be performed for
1213  * each of the attributes associated with the letter as A_SET_OP.
1214  *
1215  * Returns 0 if the entry was a valid attribute(s) and the action to be
1216  * performed on that attribute(s) has been recorded, otherwise returns 1.
1217  */
1218 static int
1219 parse_entry(char *entry, char action, char atype, int len, char *attractptr)
1220 {
1221 	char		aopt[2] = {'\0', '\0'};
1222 	char		*aptr;
1223 	f_attr_t	attr;
1224 
1225 	if (atype == A_VERBOSE_TYPE) {
1226 		if ((attr = name_to_attr(entry)) != F_ATTR_INVAL) {
1227 			return (set_attr_args(attr,
1228 			    (action == A_REPLACE_OP) ? A_SET_OP : action,
1229 			    attractptr));
1230 		} else if ((len > 2) && (strncmp(entry, "no", 2) == 0) &&
1231 		    ((attr = name_to_attr(entry + 2)) != F_ATTR_INVAL)) {
1232 			return (set_attr_args(attr, ((action == A_REPLACE_OP) ||
1233 			    (action == A_SET_OP)) ? A_INVERSE_OP : A_SET_OP,
1234 			    attractptr));
1235 		} else {
1236 			return (1);
1237 		}
1238 	} else if (atype == A_COMPACT_TYPE) {
1239 		for (aptr = entry; *aptr != '\0'; aptr++) {
1240 			*aopt = *aptr;
1241 			/*
1242 			 * The output of 'ls' can be used as the attribute mode
1243 			 * specification for chmod.  This output can contain a
1244 			 * hypen ('-') for each attribute that is not set.  If
1245 			 * so, ignore them.  If a replace action is being
1246 			 * performed, then all attributes that don't have an
1247 			 * action set here, will be cleared down the line.
1248 			 */
1249 			if (*aptr == '-') {
1250 				continue;
1251 			}
1252 			if (set_attr_args(option_to_attr(aopt),
1253 			    (action == A_REPLACE_OP) ? A_SET_OP : action,
1254 			    attractptr) != 0) {
1255 				return (1);
1256 			}
1257 		}
1258 		return (0);
1259 	}
1260 	return (1);
1261 }
1262 
1263 /*
1264  * Parse the attribute specification, aoptsstr.  Upon completion, attr_nvlist
1265  * will point to an nvlist which contains pairs of attribute names and values
1266  * to be set; attr_nvlist will be NULL if it is a no-op.
1267  *
1268  * The attribute specification format is
1269  *	S[oper]attr_type[attribute_list]
1270  * where oper is
1271  *	+	set operation of specified attributes in attribute list.
1272  *		This is the default operation.
1273  *	-	inverse operation of specified attributes in attribute list
1274  *	=	replace operation of all attributes.  All attribute operations
1275  *		depend on those specified in the attribute list.  Attributes
1276  *		not specified in the attribute list will be cleared.
1277  * where attr_type is
1278  *	c	compact type.  Each entry in the attribute list is a character
1279  *		option representing an associated attribute name.
1280  *	v	verbose type.  Each entry in the attribute list is an
1281  *		an attribute name which can optionally be preceeded with "no"
1282  *		(to imply the attribute should be cleared).
1283  *	a	all attributes type.  The oper should be applied to all
1284  *		read-write boolean system attributes.  No attribute list should
1285  *		be specified after an 'a' attribute type.
1286  *
1287  * Returns 0 if aoptsstr contained a valid attribute specification,
1288  * otherwise, returns 1.
1289  */
1290 static int
1291 parse_attr_args(char *aoptsstr, sec_args_t **sec_args)
1292 {
1293 	char		action;
1294 	char		*attractptr;
1295 	char		atype;
1296 	char		*entry;
1297 	char		*eptr;
1298 	char		*nextattr;
1299 	char		*nextentry;
1300 	char		*subentry;
1301 	char		*teptr;
1302 	char		tok[] = {'\0', '\0'};
1303 	int		len;
1304 	f_attr_t	i;
1305 	int		numofattrs;
1306 
1307 	if ((*aoptsstr != 'S') || (*(aoptsstr + 1) == '\0')) {
1308 		return (1);
1309 	}
1310 
1311 	if ((eptr = strdup(aoptsstr + 1)) == NULL) {
1312 		perror("chmod");
1313 		exit(2);
1314 	}
1315 	entry = eptr;
1316 
1317 	/*
1318 	 * Create a positional character array to determine a single attribute
1319 	 * operation to be performed, where each index represents the system
1320 	 * attribute affected, and it's value in the array represents the action
1321 	 * to be performed, i.e., a value of '+' means to set the attribute, a
1322 	 * value of '-' means to clear the attribute, and a value of '\0' means
1323 	 * to leave the attribute untouched.  Initially, this positional
1324 	 * character array is all '\0's, representing a no-op.
1325 	 */
1326 	if ((numofattrs = attr_count()) < 1) {
1327 		errmsg(1, 1, gettext("system attributes not supported\n"));
1328 	}
1329 
1330 	if ((attractptr = calloc(numofattrs, sizeof (char))) == NULL) {
1331 		perror("chmod");
1332 		exit(2);
1333 	}
1334 
1335 	if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) {
1336 		perror("chmod");
1337 		exit(2);
1338 	}
1339 	(*sec_args)->sec_type = SEC_ATTR;
1340 	(*sec_args)->sec_attrs = NULL;
1341 
1342 	/* Parse each attribute operation within the attribute specification. */
1343 	while ((entry != NULL) && (*entry != '\0')) {
1344 		action = A_SET_OP;
1345 		atype = '\0';
1346 
1347 		/* Get the operator. */
1348 		switch (*entry) {
1349 		case A_SET_OP:
1350 		case A_INVERSE_OP:
1351 		case A_REPLACE_OP:
1352 			action = *entry++;
1353 			break;
1354 		case A_COMPACT_TYPE:
1355 		case A_VERBOSE_TYPE:
1356 		case A_ALLATTRS_TYPE:
1357 			atype = *entry++;
1358 			action = A_SET_OP;
1359 			break;
1360 		default:
1361 			break;
1362 		}
1363 
1364 		/* An attribute type must be specified. */
1365 		if (atype == '\0') {
1366 			if ((*entry == A_COMPACT_TYPE) ||
1367 			    (*entry == A_VERBOSE_TYPE) ||
1368 			    (*entry == A_ALLATTRS_TYPE)) {
1369 				atype = *entry++;
1370 			} else {
1371 				return (1);
1372 			}
1373 		}
1374 
1375 		/* Get the attribute specification separator. */
1376 		if (*entry == LEFTBRACE) {
1377 			*tok = RIGHTBRACE;
1378 			entry++;
1379 		} else {
1380 			*tok = A_SEP;
1381 		}
1382 
1383 		/* Get the attribute operation */
1384 		if ((nextentry = strpbrk(entry, tok)) != NULL) {
1385 			*nextentry = '\0';
1386 			nextentry++;
1387 		}
1388 
1389 		/* Check for a no-op */
1390 		if ((*entry == '\0') && (atype != A_ALLATTRS_TYPE) &&
1391 		    (action != A_REPLACE_OP)) {
1392 			entry = nextentry;
1393 			continue;
1394 		}
1395 
1396 		/*
1397 		 * Step through the attribute operation, setting the
1398 		 * appropriate values for the specified attributes in the
1399 		 * character array, attractptr. A value of '+' will mean the
1400 		 * attribute is to be set, and a value of '-' will mean the
1401 		 * attribute is to be cleared.  If the value of an attribute
1402 		 * remains '\0', then no action is to be taken on that
1403 		 * attribute.  As multiple operations specified are
1404 		 * accumulated, a single attribute setting operation is
1405 		 * represented in attractptr.
1406 		 */
1407 		len = strlen(entry);
1408 		if ((*tok == RIGHTBRACE) || (action == A_REPLACE_OP) ||
1409 		    (atype == A_ALLATTRS_TYPE)) {
1410 
1411 			if ((action == A_REPLACE_OP) ||
1412 			    (atype == A_ALLATTRS_TYPE)) {
1413 				(void) memset(attractptr, '\0', numofattrs);
1414 			}
1415 
1416 			if (len > 0) {
1417 				if ((teptr = strdup(entry)) == NULL) {
1418 					perror("chmod");
1419 					exit(2);
1420 				}
1421 				subentry = teptr;
1422 				while (subentry != NULL) {
1423 					if ((nextattr = strpbrk(subentry,
1424 					    A_SEP_TOK)) != NULL) {
1425 						*nextattr = '\0';
1426 						nextattr++;
1427 					}
1428 					if (parse_entry(subentry, action,
1429 					    atype, len, attractptr) != 0) {
1430 						return (1);
1431 					}
1432 					subentry = nextattr;
1433 				}
1434 				free(teptr);
1435 			}
1436 
1437 			/*
1438 			 * If performing the replace action, record the
1439 			 * attributes and values for the rest of the
1440 			 * attributes that have not already been recorded,
1441 			 * otherwise record the specified action for all
1442 			 * attributes.  Note: set_attr_args() will only record
1443 			 * the attribute and action if it is a boolean
1444 			 * read-write attribute so we don't need to worry
1445 			 * about checking it here.
1446 			 */
1447 			if ((action == A_REPLACE_OP) ||
1448 			    (atype == A_ALLATTRS_TYPE)) {
1449 				for (i = 0; i < numofattrs; i++) {
1450 					if (attractptr[i] == A_UNDEF_OP) {
1451 						(void) set_attr_args(i,
1452 						    (action == A_SET_OP) ?
1453 						    A_SET_OP : A_INVERSE_OP,
1454 						    attractptr);
1455 					}
1456 				}
1457 			}
1458 
1459 		} else {
1460 			if (parse_entry(entry, action, atype, len,
1461 			    attractptr) != 0) {
1462 				return (1);
1463 			}
1464 		}
1465 		entry = nextentry;
1466 	}
1467 
1468 	/*
1469 	 * Populate an nvlist with attribute name and boolean value pairs
1470 	 * using the single attribute operation.
1471 	 */
1472 	(*sec_args)->sec_attrs = set_attrs_nvlist(attractptr, numofattrs);
1473 	free(attractptr);
1474 	free(eptr);
1475 
1476 	return (0);
1477 }
1478