Home | History | Annotate | Download | only in chmod
      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