xref: /illumos-gate/usr/src/cmd/chmod/chmod.c (revision b91bbe3818919523e866b0d622e2dd56874530a7)
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 (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
23  * Copyright 2011 Nexenta Systems, Inc. All rights reserved.
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 /*
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 -@
47  */
48 
49 /*
50  *  Note that many convolutions are necessary
51  *  due to the re-use of bits between locking
52  *  and setgid
53  */
54 
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>
74 
75 static int	rflag;
76 static int	fflag;
77 
78 extern int	optind;
79 extern int	errno;
80 
81 static int	mac;		/* Alternate to argc (for parseargs) */
82 static char	**mav;		/* Alternate to argv (for parseargs) */
83 
84 static char	*ms;		/* Points to the mode argument */
85 
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
91 
92 #define	LEFTBRACE	'{'
93 #define	RIGHTBRACE	'}'
94 #define	A_SEP		','
95 #define	A_SEP_TOK	","
96 
97 #define	A_COMPACT_TYPE	'c'
98 #define	A_VERBOSE_TYPE	'v'
99 #define	A_ALLATTRS_TYPE	'a'
100 
101 #define	A_SET_OP	'+'
102 #define	A_INVERSE_OP	'-'
103 #define	A_REPLACE_OP	'='
104 #define	A_UNDEF_OP	'\0'
105 
106 #define	A_SET_TEXT	"set"
107 #define	A_INVERSE_TEXT	"clear"
108 
109 #define	A_SET_VAL	B_TRUE
110 #define	A_CLEAR_VAL	B_FALSE
111 
112 #define	ATTR_OPTS	0
113 #define	ATTR_NAMES	1
114 
115 #define	sec_acls	secptr.acls
116 #define	sec_attrs	secptr.attrs
117 
118 typedef struct acl_args {
119 	acl_t	*acl_aclp;
120 	int	acl_slot;
121 	int	acl_action;
122 } acl_args_t;
123 
124 typedef enum {
125 	SEC_ACL,
126 	SEC_ATTR
127 } chmod_sec_t;
128 
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;
136 
137 typedef struct attr_name {
138 	char			*name;
139 	struct attr_name	*next;
140 } attr_name_t;
141 
142 
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);
146 
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);
162 
163 int
164 main(int argc, char *argv[])
165 {
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;
173 
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);
179 
180 	parseargs(argc, argv);
181 
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);
195 			}
196 			if (attrnames == NULL) {
197 				attrnames = tattr;
198 				attrnames->next = NULL;
199 			} else {
200 				attrend->next = tattr;
201 			}
202 			attrend = tattr;
203 			break;
204 		case '?':
205 			usage();
206 			exit(2);
207 		}
208 	}
209 
210 	/*
211 	 * Check for sufficient arguments
212 	 * or a usage error.
213 	 */
214 
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;
221 		}
222 		if (parse_acl_args(*mav, &sec_args)) {
223 			usage();
224 			exit(2);
225 		}
226 	} else if ((mac >= 2) && (mav[0][0] == 'S')) {
227 		if (parse_attr_args(*mav, &sec_args)) {
228 			usage();
229 			exit(2);
230 
231 		/* A no-op attribute operation was specified. */
232 		} else if (sec_args->sec_attrs == NULL) {
233 			exit(0);
234 		}
235 	} else {
236 		if (mac < 2) {
237 			usage();
238 			exit(2);
239 		}
240 		if (attrnames != NULL) {
241 			free_attr_names(attrnames);
242 			attrnames = NULL;
243 		}
244 	}
245 
246 	ms = mav[0];
247 
248 	umsk = umask(0);
249 	(void) umask(umsk);
250 
251 	for (i = 1; i < mac; i++) {
252 		status += dochmod(mav[i], mav[i], umsk, sec_args, attrnames);
253 	}
254 
255 	return (fflag ? 0 : status);
256 }
257 
258 static void
259 free_attr_names(attr_name_t *attrnames)
260 {
261 	attr_name_t	*attrnamesptr = attrnames;
262 	attr_name_t	*tptr;
263 
264 	while (attrnamesptr != NULL) {
265 		tptr = attrnamesptr->next;
266 		if (attrnamesptr->name != NULL) {
267 			free(attrnamesptr->name);
268 		}
269 		attrnamesptr = tptr;
270 	}
271 }
272 
273 static int
274 dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp,
275     attr_name_t *attrnames)
276 {
277 	static struct stat st;
278 	int linkflg = 0;
279 	o_mode_t	group_clear_bits, group_set_bits;
280 
281 	if (lstat(name, &st) < 0) {
282 		errmsg(2, 0, gettext("can't access %s\n"), path);
283 		return (1);
284 	}
285 
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);
291 		}
292 	}
293 
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));
297 	}
298 
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);
306 		}
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);
312 		}
313 	}
314 
315 	/*
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.
321 	 */
322 	if (group_clear_bits || group_set_bits)
323 		handle_acl(name, group_clear_bits, group_set_bits);
324 
325 	return (0);
326 }
327 
328 static int
329 chmodr(char *dir, char *path,  mode_t mode, mode_t umsk, sec_args_t *secp,
330     attr_name_t *attrnames)
331 {
332 
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;
341 
342 	if (getcwd(savedir, PATH_MAX) == 0)
343 		errmsg(2, 255, gettext("chmod: could not getcwd %s\n"),
344 		    savedir);
345 
346 	/*
347 	 * Change what we are given before doing it's contents
348 	 */
349 	if (secp != NULL) {
350 		if (lstat(dir, &st) < 0) {
351 			errmsg(2, 0, gettext("can't access %s\n"), path);
352 			return (1);
353 		}
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);
360 		}
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);
364 	}
365 
366 	/*
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.
372 	 */
373 
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);
378 	}
379 
380 	if (chdir(dir) < 0) {
381 		errmsg(2, 0, "%s/%s: %s\n", savedir, dir, strerror(errno));
382 		return (1);
383 	}
384 	if ((dirp = opendir(".")) == NULL) {
385 		errmsg(2, 0, "%s\n", strerror(errno));
386 		return (1);
387 	}
388 	ecode = 0;
389 
390 	/*
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 '/'
395 	 */
396 
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);
401 	}
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);
408 		}
409 
410 
411 	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp))  {
412 
413 		if (strcmp(dp->d_name, ".") == 0 ||	/* skip . and .. */
414 		    strcmp(dp->d_name, "..") == 0) {
415 			continue;
416 		}
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);
422 		}
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);
429 		}
430 		ecode += dochmod(dp->d_name, currdir, umsk, secp, attrnames);
431 	}
432 	(void) closedir(dirp);
433 	if (chdir(savedir) < 0) {
434 		errmsg(2, 255, gettext("can't change back to %s\n"), savedir);
435 	}
436 	return (ecode ? 1 : 0);
437 }
438 
439 /* PRINTFLIKE3 */
440 void
441 errmsg(int severity, int code, char *format, ...)
442 {
443 	va_list ap;
444 	static char *msg[] = {
445 	"",
446 	"ERROR",
447 	"WARNING",
448 	""
449 	};
450 
451 	va_start(ap, format);
452 
453 	/*
454 	 * Always print error message if this is a fatal error (code != 0);
455 	 * otherwise, print message if fflag == 0 (no -f option specified)
456 	 */
457 	if (!fflag || (code != 0)) {
458 		(void) fprintf(stderr,
459 		    "chmod: %s: ", gettext(msg[severity]));
460 		(void) vfprintf(stderr, format, ap);
461 	}
462 
463 	va_end(ap);
464 
465 	if (code != 0)
466 		exit(fflag ? 0 : code);
467 }
468 
469 static void
470 usage(void)
471 {
472 	(void) fprintf(stderr, gettext(
473 	    "usage:\tchmod [-fR] <absolute-mode> file ...\n"));
474 
475 	(void) fprintf(stderr, gettext(
476 	    "\tchmod [-fR] [-@ attribute] ... "
477 	    "S<attribute-operation> file ...\n"));
478 
479 	(void) fprintf(stderr, gettext(
480 	    "\tchmod [-fR] <ACL-operation> file ...\n"));
481 
482 	(void) fprintf(stderr, gettext(
483 	    "\tchmod [-fR] <symbolic-mode-list> file ...\n\n"));
484 
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"));
489 
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"));
506 
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"));
515 }
516 
517 /*
518  *  parseargs - generate getopt-friendly argument list for backwards
519  *		compatibility with earlier Solaris usage (eg, chmod -w
520  *		foo).
521  *
522  *  assumes the existence of a static set of alternates to argc and argv,
523  *  (namely, mac, and mav[]).
524  *
525  */
526 
527 static void
528 parseargs(int ac, char *av[])
529 {
530 	int i;			/* current argument			*/
531 	int fflag;		/* arg list contains "--"		*/
532 	size_t mav_num;		/* number of entries in mav[]		*/
533 
534 	/*
535 	 * We add an extra argument slot, in case we need to jam a "--"
536 	 * argument into the list.
537 	 */
538 
539 	mav_num = (size_t)ac+2;
540 
541 	if ((mav = calloc(mav_num, sizeof (char *))) == NULL) {
542 		perror("chmod");
543 		exit(2);
544 	}
545 
546 	/* scan for the use of "--" in the argument list */
547 
548 	for (fflag = i = 0; i < ac; i ++) {
549 		if (strcmp(av[i], "--") == 0)
550 			fflag = 1;
551 	}
552 
553 	/* process the arguments */
554 
555 	for (i = mac = 0;
556 	    (av[i] != NULL) && (av[i][0] != '\0');
557 	    i++) {
558 		if (!fflag && av[i][0] == '-') {
559 			/*
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.
566 			 */
567 
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);
574 				}
575 			}
576 		}
577 
578 		if ((mav[mac++] = strdup(av[i])) == NULL) {
579 			perror("chmod");
580 			exit(2);
581 		}
582 	}
583 
584 	mav[mac] = (char *)NULL;
585 }
586 
587 static int
588 parse_acl_args(char *arg, sec_args_t **sec_args)
589 {
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;
597 
598 	if (arg[0] != 'A')
599 		return (1);
600 
601 	slot = strtol(&arg[1], &end, 10);
602 
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);
621 		}
622 		break;
623 	case '=':
624 		/*
625 		 * Was slot specified?
626 		 */
627 		if (arg[1] == '=')
628 			slot = -1;
629 		action = ACL_REPLACE;
630 		acl_spec = ++end;
631 		break;
632 	default:
633 		return (1);
634 	}
635 
636 	if ((action == ACL_REPLACE || action == ACL_ADD) && acl_spec[0] == '\0')
637 		return (1);
638 
639 	if (acl_spec) {
640 		if (acl_parse(acl_spec, &new_acl)) {
641 			exit(1);
642 		}
643 	}
644 
645 	new_acl_args = malloc(sizeof (acl_args_t));
646 	if (new_acl_args == NULL)
647 		return (1);
648 
649 	new_acl_args->acl_aclp = new_acl;
650 	new_acl_args->acl_slot = slot;
651 	new_acl_args->acl_action = action;
652 
653 	if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) {
654 		perror("chmod");
655 		exit(2);
656 	}
657 	(*sec_args)->sec_type = SEC_ACL;
658 	(*sec_args)->sec_acls = new_acl_args;
659 
660 	return (0);
661 }
662 
663 /*
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.
670  */
671 static void
672 handle_acl(char *name, o_mode_t group_clear_bits, o_mode_t group_set_bits)
673 {
674 	int aclcnt, n;
675 	aclent_t *aclp, *tp;
676 	o_mode_t newperm;
677 	/*
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
681 	 */
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);
690 	}
691 
692 	if (acl(name, GETACL, aclcnt, aclp) < 0) {
693 		free(aclp);
694 		(void) fprintf(stderr, "chmod: ");
695 		perror(name);
696 		return;
697 	}
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);
711 				}
712 			}
713 			break;
714 		}
715 	}
716 	free(aclp);
717 }
718 
719 static int
720 doacl(char *file, struct stat *st, acl_args_t *acl_args)
721 {
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);
729 
730 	error = acl_get(file, 0, &aclp);
731 
732 	if (error != 0) {
733 		errmsg(1, 0, "%s\n", acl_strerror(error));
734 		return (1);
735 	}
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);
743 		}
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);
752 		}
753 
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);
760 		}
761 
762 		/*
763 		 * remove a single entry
764 		 *
765 		 * if last entry just adjust acl_cnt
766 		 */
767 
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--;
778 		}
779 		set_aclp = aclp;
780 		break;
781 
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);
788 		}
789 
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);
796 		}
797 
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);
808 			}
809 			set_aclp = aclp;
810 		} else {
811 			set_aclp = acl_args->acl_aclp;
812 		}
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);
820 		}
821 		acl_free(aclp);
822 		return (0);
823 		/*NOTREACHED*/
824 	default:
825 		errmsg(1, 2, gettext("Unknown ACL action requested\n"));
826 		/*NOTREACHED*/
827 	}
828 	error = acl_check(set_aclp, isdir);
829 
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"));
834 	}
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);
840 	}
841 	acl_free(aclp);
842 	return (0);
843 }
844 
845 /*
846  * Prints out the attributes in their verbose form:
847  *	'{'[["no"]<attribute-name>][,["no"]<attribute-name>]...'}'
848  * similar to output of ls -/v.
849  */
850 static void
851 print_nvlist(nvlist_t *attr_nvlist)
852 {
853 	int		firsttime = 1;
854 	boolean_t	value;
855 	nvlist_t	*lptr = attr_nvlist;
856 	nvpair_t	*pair = NULL;
857 
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;
871 		}
872 	}
873 	(void) fprintf(stderr, "%c\n", RIGHTBRACE);
874 }
875 
876 /*
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().
880  *
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.
886  *
887  * Returns an nvlist of attribute name and boolean value pairs if there are
888  * attribute actions to be performed, otherwise returns NULL.
889  */
890 static nvlist_t *
891 set_attrs_nvlist(char *attractptr, int numofattrs)
892 {
893 	int		attribute_set = 0;
894 	f_attr_t	i;
895 	nvlist_t	*attr_nvlist;
896 
897 	if (nvlist_alloc(&attr_nvlist, NV_UNIQUE_NAME, 0) != 0) {
898 		perror("chmod");
899 		exit(2);
900 	}
901 
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;
912 			}
913 		}
914 	}
915 	return (attribute_set ? attr_nvlist : NULL);
916 }
917 
918 /*
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.
922  *
923  * Returns 0 if successful, otherwise returns 1.
924  */
925 static int
926 set_file_attrs(char *file, char *attrname, nvlist_t *attr_nvlist)
927 {
928 	int	rc;
929 	char	*filename;
930 
931 	if (attrname != NULL) {
932 		filename = attrname;
933 	} else {
934 		filename = basename(file);
935 	}
936 
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);
949 		}
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);
958 	}
959 
960 	return (rc);
961 }
962 
963 static int
964 save_cwd(void)
965 {
966 	return (open(".", O_RDONLY));
967 }
968 
969 static void
970 rest_cwd(int cwd)
971 {
972 	if (cwd != -1) {
973 		if (fchdir(cwd) != 0) {
974 			errmsg(1, 1, gettext(
975 			    "can't change to current working directory\n"));
976 		}
977 		(void) close(cwd);
978 	}
979 }
980 
981 /*
982  * Returns 1 if filename is a system attribute file, otherwise
983  * returns 0.
984  */
985 static int
986 is_sattr(char *filename)
987 {
988 	return (sysattr_type(filename) != _NOT_SATTR);
989 }
990 
991 /*
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.
996  */
997 static int
998 set_named_attrs(char *file, int parentfd, char *attrname, nvlist_t *attr_nvlist)
999 {
1000 	int		dirfd;
1001 	int		error = 0;
1002 	DIR		*dirp = NULL;
1003 	struct dirent	*dp;
1004 	struct stat	st;
1005 
1006 	if ((attrname == NULL) || (strcmp(attrname, "*") != 0)) {
1007 		/*
1008 		 * Make sure the named attribute exists and extended system
1009 		 * attributes are supported on the underlying file system.
1010 		 */
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);
1018 			}
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);
1025 			}
1026 		}
1027 
1028 		error = set_file_attrs(file, attrname, attr_nvlist);
1029 
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);
1037 			}
1038 			return (1);
1039 		}
1040 
1041 		while (dp = readdir(dirp)) {
1042 			/*
1043 			 * Process all extended attribute files except
1044 			 * ".", "..", and extended system attribute files.
1045 			 */
1046 			if ((strcmp(dp->d_name, ".") == 0) ||
1047 			    (strcmp(dp->d_name, "..") == 0) ||
1048 			    is_sattr(dp->d_name)) {
1049 				continue;
1050 			}
1051 
1052 			if (set_named_attrs(file, parentfd, dp->d_name,
1053 			    attr_nvlist) != 0) {
1054 				error++;
1055 			}
1056 		}
1057 		if (dirp != NULL) {
1058 			(void) closedir(dirp);
1059 		}
1060 	}
1061 
1062 	return ((error == 0) ? 0 : 1);
1063 }
1064 
1065 /*
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.
1068  *
1069  * Returns 0 if successful, otherwise returns 1.
1070  */
1071 static int
1072 set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist)
1073 {
1074 	char		*parentd;
1075 	char		*tpath = NULL;
1076 	int		cwd;
1077 	int		error = 0;
1078 	int		parentfd;
1079 	attr_name_t	*tattr = attrnames;
1080 
1081 	if (attr_nvlist == NULL) {
1082 		return (0);
1083 	}
1084 
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);
1089 	}
1090 
1091 	/*
1092 	 * Open the parent directory and change into it before attempting
1093 	 * to set the attributes of the file.
1094 	 */
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);
1101 	}
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);
1107 		}
1108 		return (1);
1109 	}
1110 
1111 	if ((cwd = save_cwd()) < 0) {
1112 		errmsg(1, 1, gettext(
1113 		    "can't get current working directory\n"));
1114 	}
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);
1123 		}
1124 		return (1);
1125 	}
1126 
1127 	/*
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.
1131 	 */
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++;
1140 			}
1141 			tattr = tattr->next;
1142 		}
1143 	}
1144 	(void) close(parentfd);
1145 	rest_cwd(cwd);
1146 
1147 	return ((error == 0) ? 0 : 1);
1148 }
1149 
1150 /*
1151  * Prints the attributes in either the compact or verbose form indicated
1152  * by flag.
1153  */
1154 static void
1155 print_attrs(int flag)
1156 {
1157 	f_attr_t	i;
1158 	static int	numofattrs;
1159 	int		firsttime = 1;
1160 
1161 	numofattrs = attr_count();
1162 
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;
1168 		}
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;
1173 	}
1174 	(void) fprintf(stderr, gettext("]\n"));
1175 }
1176 
1177 /*
1178  * Record what action should be taken on the specified attribute. Only boolean
1179  * read-write attributes can be manipulated.
1180  *
1181  * Returns 0 if successful, otherwise returns 1.
1182  */
1183 static int
1184 set_attr_args(f_attr_t attr, char action, char *attractptr)
1185 {
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);
1190 	}
1191 	return (1);
1192 }
1193 
1194 /*
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.
1200  *
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.
1207  *
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.
1210  */
1211 static int
1212 parse_entry(char *entry, char action, char atype, int len, char *attractptr)
1213 {
1214 	char		aopt[2] = {'\0', '\0'};
1215 	char		*aptr;
1216 	f_attr_t	attr;
1217 
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);
1230 		}
1231 	} else if (atype == A_COMPACT_TYPE) {
1232 		for (aptr = entry; *aptr != '\0'; aptr++) {
1233 			*aopt = *aptr;
1234 			/*
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.
1241 			 */
1242 			if (*aptr == '-') {
1243 				continue;
1244 			}
1245 			if (set_attr_args(option_to_attr(aopt),
1246 			    (action == A_REPLACE_OP) ? A_SET_OP : action,
1247 			    attractptr) != 0) {
1248 				return (1);
1249 			}
1250 		}
1251 		return (0);
1252 	}
1253 	return (1);
1254 }
1255 
1256 /*
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.
1260  *
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.
1279  *
1280  * Returns 0 if aoptsstr contained a valid attribute specification,
1281  * otherwise, returns 1.
1282  */
1283 static int
1284 parse_attr_args(char *aoptsstr, sec_args_t **sec_args)
1285 {
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;
1299 
1300 	if ((*aoptsstr != 'S') || (*(aoptsstr + 1) == '\0')) {
1301 		return (1);
1302 	}
1303 
1304 	if ((eptr = strdup(aoptsstr + 1)) == NULL) {
1305 		perror("chmod");
1306 		exit(2);
1307 	}
1308 	entry = eptr;
1309 
1310 	/*
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.
1318 	 */
1319 	if ((numofattrs = attr_count()) < 1) {
1320 		errmsg(1, 1, gettext("system attributes not supported\n"));
1321 	}
1322 
1323 	if ((attractptr = calloc(numofattrs, sizeof (char))) == NULL) {
1324 		perror("chmod");
1325 		exit(2);
1326 	}
1327 
1328 	if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) {
1329 		perror("chmod");
1330 		exit(2);
1331 	}
1332 	(*sec_args)->sec_type = SEC_ATTR;
1333 	(*sec_args)->sec_attrs = NULL;
1334 
1335 	/* Parse each attribute operation within the attribute specification. */
1336 	while ((entry != NULL) && (*entry != '\0')) {
1337 		action = A_SET_OP;
1338 		atype = '\0';
1339 
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;
1355 		}
1356 
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);
1365 			}
1366 		}
1367 
1368 		/* Get the attribute specification separator. */
1369 		if (*entry == LEFTBRACE) {
1370 			*tok = RIGHTBRACE;
1371 			entry++;
1372 		} else {
1373 			*tok = A_SEP;
1374 		}
1375 
1376 		/* Get the attribute operation */
1377 		if ((nextentry = strpbrk(entry, tok)) != NULL) {
1378 			*nextentry = '\0';
1379 			nextentry++;
1380 		}
1381 
1382 		/* Check for a no-op */
1383 		if ((*entry == '\0') && (atype != A_ALLATTRS_TYPE) &&
1384 		    (action != A_REPLACE_OP)) {
1385 			entry = nextentry;
1386 			continue;
1387 		}
1388 
1389 		/*
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.
1399 		 */
1400 		len = strlen(entry);
1401 		if ((*tok == RIGHTBRACE) || (action == A_REPLACE_OP) ||
1402 		    (atype == A_ALLATTRS_TYPE)) {
1403 
1404 			if ((action == A_REPLACE_OP) ||
1405 			    (atype == A_ALLATTRS_TYPE)) {
1406 				(void) memset(attractptr, '\0', numofattrs);
1407 			}
1408 
1409 			if (len > 0) {
1410 				if ((teptr = strdup(entry)) == NULL) {
1411 					perror("chmod");
1412 					exit(2);
1413 				}
1414 				subentry = teptr;
1415 				while (subentry != NULL) {
1416 					if ((nextattr = strpbrk(subentry,
1417 					    A_SEP_TOK)) != NULL) {
1418 						*nextattr = '\0';
1419 						nextattr++;
1420 					}
1421 					if (parse_entry(subentry, action,
1422 					    atype, len, attractptr) != 0) {
1423 						return (1);
1424 					}
1425 					subentry = nextattr;
1426 				}
1427 				free(teptr);
1428 			}
1429 
1430 			/*
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.
1439 			 */
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);
1448 					}
1449 				}
1450 			}
1451 
1452 		} else {
1453 			if (parse_entry(entry, action, atype, len,
1454 			    attractptr) != 0) {
1455 				return (1);
1456 			}
1457 		}
1458 		entry = nextentry;
1459 	}
1460 
1461 	/*
1462 	 * Populate an nvlist with attribute name and boolean value pairs
1463 	 * using the single attribute operation.
1464 	 */
1465 	(*sec_args)->sec_attrs = set_attrs_nvlist(attractptr, numofattrs);
1466 	free(attractptr);
1467 	free(eptr);
1468 
1469 	return (0);
1470 }
1471