xref: /onnv-gate/usr/src/cmd/chmod/chmod.c (revision 5331)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
51591Scasper  * Common Development and Distribution License (the "License").
61591Scasper  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
210Sstevel@tonic-gate /*
223520Sas145665  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
230Sstevel@tonic-gate  * Use is subject to license terms.
240Sstevel@tonic-gate  */
250Sstevel@tonic-gate 
260Sstevel@tonic-gate /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T			*/
270Sstevel@tonic-gate /*	  All Rights Reserved						*/
280Sstevel@tonic-gate /*									*/
290Sstevel@tonic-gate 
300Sstevel@tonic-gate /*
310Sstevel@tonic-gate  * University Copyright- Copyright (c) 1982, 1986, 1988
320Sstevel@tonic-gate  * The Regents of the University of California
330Sstevel@tonic-gate  * All Rights Reserved
340Sstevel@tonic-gate  *
350Sstevel@tonic-gate  * University Acknowledgment- Portions of this document are derived from
360Sstevel@tonic-gate  * software developed by the University of California, Berkeley, and its
370Sstevel@tonic-gate  * contributors.
380Sstevel@tonic-gate  */
390Sstevel@tonic-gate 
400Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
410Sstevel@tonic-gate 
420Sstevel@tonic-gate /*
430Sstevel@tonic-gate  * chmod option mode files
440Sstevel@tonic-gate  * where
450Sstevel@tonic-gate  *	mode is [ugoa][+-=][rwxXlstugo] or an octal number
46789Sahrens  *	mode is [<+|->A[# <number] ]<aclspec>
47*5331Samw  *	mode is S<attrspec>
48*5331Samw  *	option is -R, -f, and -@
490Sstevel@tonic-gate  */
500Sstevel@tonic-gate 
510Sstevel@tonic-gate /*
520Sstevel@tonic-gate  *  Note that many convolutions are necessary
530Sstevel@tonic-gate  *  due to the re-use of bits between locking
540Sstevel@tonic-gate  *  and setgid
550Sstevel@tonic-gate  */
560Sstevel@tonic-gate 
570Sstevel@tonic-gate #include <unistd.h>
580Sstevel@tonic-gate #include <stdlib.h>
590Sstevel@tonic-gate #include <stdio.h>
600Sstevel@tonic-gate #include <sys/types.h>
610Sstevel@tonic-gate #include <sys/stat.h>
62*5331Samw #include <fcntl.h>
630Sstevel@tonic-gate #include <dirent.h>
640Sstevel@tonic-gate #include <locale.h>
650Sstevel@tonic-gate #include <string.h>	/* strerror() */
660Sstevel@tonic-gate #include <stdarg.h>
670Sstevel@tonic-gate #include <limits.h>
68789Sahrens #include <ctype.h>
690Sstevel@tonic-gate #include <errno.h>
700Sstevel@tonic-gate #include <sys/acl.h>
71789Sahrens #include <aclutils.h>
72*5331Samw #include <libnvpair.h>
73*5331Samw #include <libcmdutils.h>
74*5331Samw #include <libgen.h>
75*5331Samw #include <attr.h>
760Sstevel@tonic-gate 
770Sstevel@tonic-gate static int	rflag;
780Sstevel@tonic-gate static int	fflag;
790Sstevel@tonic-gate 
800Sstevel@tonic-gate extern int	optind;
810Sstevel@tonic-gate extern int	errno;
820Sstevel@tonic-gate 
830Sstevel@tonic-gate static int	mac;		/* Alternate to argc (for parseargs) */
840Sstevel@tonic-gate static char	**mav;		/* Alternate to argv (for parseargs) */
850Sstevel@tonic-gate 
860Sstevel@tonic-gate static char	*ms;		/* Points to the mode argument */
870Sstevel@tonic-gate 
88*5331Samw #define	ACL_ADD			1
89*5331Samw #define	ACL_DELETE		2
90*5331Samw #define	ACL_SLOT_DELETE		3
91*5331Samw #define	ACL_REPLACE		4
92*5331Samw #define	ACL_STRIP		5
93*5331Samw 
94*5331Samw #define	LEFTBRACE	'{'
95*5331Samw #define	RIGHTBRACE	'}'
96*5331Samw #define	A_SEP		','
97*5331Samw #define	A_SEP_TOK	","
98*5331Samw 
99*5331Samw #define	A_COMPACT_TYPE	'c'
100*5331Samw #define	A_VERBOSE_TYPE	'v'
101*5331Samw #define	A_ALLATTRS_TYPE	'a'
102*5331Samw 
103*5331Samw #define	A_SET_OP	'+'
104*5331Samw #define	A_INVERSE_OP	'-'
105*5331Samw #define	A_REPLACE_OP	'='
106*5331Samw #define	A_UNDEF_OP	'\0'
107*5331Samw 
108*5331Samw #define	A_SET_TEXT	"set"
109*5331Samw #define	A_INVERSE_TEXT	"clear"
110*5331Samw 
111*5331Samw #define	A_SET_VAL	B_TRUE
112*5331Samw #define	A_CLEAR_VAL	B_FALSE
113*5331Samw 
114*5331Samw #define	ATTR_OPTS	0
115*5331Samw #define	ATTR_NAMES	1
116*5331Samw 
117*5331Samw #define	sec_acls	secptr.acls
118*5331Samw #define	sec_attrs	secptr.attrs
119789Sahrens 
120789Sahrens typedef struct acl_args {
121789Sahrens 	acl_t	*acl_aclp;
122789Sahrens 	int	acl_slot;
123789Sahrens 	int	acl_action;
124789Sahrens } acl_args_t;
125789Sahrens 
126*5331Samw typedef enum {
127*5331Samw 	SEC_ACL,
128*5331Samw 	SEC_ATTR
129*5331Samw } chmod_sec_t;
130*5331Samw 
131*5331Samw typedef struct {
132*5331Samw 	chmod_sec_t		sec_type;
133*5331Samw 	union {
134*5331Samw 		acl_args_t	*acls;
135*5331Samw 		nvlist_t	*attrs;
136*5331Samw 	} secptr;
137*5331Samw } sec_args_t;
1380Sstevel@tonic-gate 
139*5331Samw typedef struct attr_name {
140*5331Samw 	char			*name;
141*5331Samw 	struct attr_name	*next;
142*5331Samw } attr_name_t;
143*5331Samw 
144*5331Samw 
145*5331Samw extern mode_t newmode_common(char *ms, mode_t new_mode, mode_t umsk,
146*5331Samw     char *file, char *path, o_mode_t *group_clear_bits,
147*5331Samw     o_mode_t *group_set_bits);
148*5331Samw 
149*5331Samw static int chmodr(char *dir, char *path, mode_t mode, mode_t umsk,
150*5331Samw     sec_args_t *secp, attr_name_t *attrname);
151789Sahrens static int doacl(char *file, struct stat *st, acl_args_t *aclp);
152*5331Samw static int dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp,
153*5331Samw     attr_name_t *attrnames);
1540Sstevel@tonic-gate static void handle_acl(char *name, o_mode_t group_clear_bits,
155789Sahrens     o_mode_t group_set_bits);
156*5331Samw void errmsg(int severity, int code, char *format, ...);
157*5331Samw static void free_attr_names(attr_name_t *attrnames);
158*5331Samw static void parseargs(int ac, char *av[]);
159*5331Samw static int parse_acl_args(char *arg, sec_args_t **sec_args);
160*5331Samw static int parse_attr_args(char *arg, sec_args_t **sec_args);
161*5331Samw static void print_attrs(int flag);
162*5331Samw static int set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist);
163789Sahrens static void usage(void);
1640Sstevel@tonic-gate 
1650Sstevel@tonic-gate int
1660Sstevel@tonic-gate main(int argc, char *argv[])
1670Sstevel@tonic-gate {
168*5331Samw 	int		i, c;
169*5331Samw 	int		status = 0;
170*5331Samw 	mode_t		umsk;
171*5331Samw 	sec_args_t	*sec_args = NULL;
172*5331Samw 	attr_name_t	*attrnames = NULL;
173*5331Samw 	attr_name_t	*attrend = NULL;
174*5331Samw 	attr_name_t	*tattr;
1750Sstevel@tonic-gate 
1760Sstevel@tonic-gate 	(void) setlocale(LC_ALL, "");
1770Sstevel@tonic-gate #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
1780Sstevel@tonic-gate #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
1790Sstevel@tonic-gate #endif
1800Sstevel@tonic-gate 	(void) textdomain(TEXT_DOMAIN);
1810Sstevel@tonic-gate 
1820Sstevel@tonic-gate 	parseargs(argc, argv);
1830Sstevel@tonic-gate 
184*5331Samw 	while ((c = getopt(mac, mav, "Rf@:")) != EOF) {
1850Sstevel@tonic-gate 		switch (c) {
1860Sstevel@tonic-gate 		case 'R':
1870Sstevel@tonic-gate 			rflag++;
1880Sstevel@tonic-gate 			break;
1890Sstevel@tonic-gate 		case 'f':
1900Sstevel@tonic-gate 			fflag++;
1910Sstevel@tonic-gate 			break;
192*5331Samw 		case '@':
193*5331Samw 			if (((tattr = malloc(sizeof (attr_name_t))) == NULL) ||
194*5331Samw 			    ((tattr->name = strdup(optarg)) == NULL)) {
195*5331Samw 				perror("chmod");
196*5331Samw 				exit(2);
197*5331Samw 			}
198*5331Samw 			if (attrnames == NULL) {
199*5331Samw 				attrnames = tattr;
200*5331Samw 				attrnames->next = NULL;
201*5331Samw 			} else {
202*5331Samw 				attrend->next = tattr;
203*5331Samw 			}
204*5331Samw 			attrend = tattr;
205*5331Samw 			break;
2060Sstevel@tonic-gate 		case '?':
2070Sstevel@tonic-gate 			usage();
2080Sstevel@tonic-gate 			exit(2);
2090Sstevel@tonic-gate 		}
2100Sstevel@tonic-gate 	}
2110Sstevel@tonic-gate 
2120Sstevel@tonic-gate 	/*
2130Sstevel@tonic-gate 	 * Check for sufficient arguments
2140Sstevel@tonic-gate 	 * or a usage error.
2150Sstevel@tonic-gate 	 */
2160Sstevel@tonic-gate 
2170Sstevel@tonic-gate 	mac -= optind;
2180Sstevel@tonic-gate 	mav += optind;
219*5331Samw 	if ((mac >= 2) && (mav[0][0] == 'A')) {
220*5331Samw 		if (attrnames != NULL) {
221*5331Samw 			free_attr_names(attrnames);
222*5331Samw 			attrnames = NULL;
223*5331Samw 		}
224*5331Samw 		if (parse_acl_args(*mav, &sec_args)) {
225789Sahrens 			usage();
226789Sahrens 			exit(2);
227789Sahrens 		}
228*5331Samw 	} else if ((mac >= 2) && (mav[0][0] == 'S')) {
229*5331Samw 		if (parse_attr_args(*mav, &sec_args)) {
230*5331Samw 			usage();
231*5331Samw 			exit(2);
232*5331Samw 
233*5331Samw 		/* A no-op attribute operation was specified. */
234*5331Samw 		} else if (sec_args->sec_attrs == NULL) {
235*5331Samw 			exit(0);
236*5331Samw 		}
237789Sahrens 	} else {
238789Sahrens 		if (mac < 2) {
239789Sahrens 			usage();
240789Sahrens 			exit(2);
241789Sahrens 		}
242*5331Samw 		if (attrnames != NULL) {
243*5331Samw 			free_attr_names(attrnames);
244*5331Samw 			attrnames = NULL;
245*5331Samw 		}
2460Sstevel@tonic-gate 	}
2470Sstevel@tonic-gate 
2480Sstevel@tonic-gate 	ms = mav[0];
2490Sstevel@tonic-gate 
2500Sstevel@tonic-gate 	umsk = umask(0);
2510Sstevel@tonic-gate 	(void) umask(umsk);
2520Sstevel@tonic-gate 
253789Sahrens 	for (i = 1; i < mac; i++) {
254*5331Samw 		status += dochmod(mav[i], mav[i], umsk, sec_args, attrnames);
255789Sahrens 	}
2560Sstevel@tonic-gate 
2570Sstevel@tonic-gate 	return (fflag ? 0 : status);
2580Sstevel@tonic-gate }
2590Sstevel@tonic-gate 
260*5331Samw static void
261*5331Samw free_attr_names(attr_name_t *attrnames)
262*5331Samw {
263*5331Samw 	attr_name_t	*attrnamesptr = attrnames;
264*5331Samw 	attr_name_t	*tptr;
265*5331Samw 
266*5331Samw 	while (attrnamesptr != NULL) {
267*5331Samw 		tptr = attrnamesptr->next;
268*5331Samw 		if (attrnamesptr->name != NULL) {
269*5331Samw 			free(attrnamesptr->name);
270*5331Samw 		}
271*5331Samw 		attrnamesptr = tptr;
272*5331Samw 	}
273*5331Samw }
274*5331Samw 
2750Sstevel@tonic-gate static int
276*5331Samw dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp,
277*5331Samw     attr_name_t *attrnames)
2780Sstevel@tonic-gate {
2790Sstevel@tonic-gate 	static struct stat st;
2800Sstevel@tonic-gate 	int linkflg = 0;
2810Sstevel@tonic-gate 	o_mode_t	group_clear_bits, group_set_bits;
2820Sstevel@tonic-gate 
2830Sstevel@tonic-gate 	if (lstat(name, &st) < 0) {
2840Sstevel@tonic-gate 		errmsg(2, 0, gettext("can't access %s\n"), path);
2850Sstevel@tonic-gate 		return (1);
2860Sstevel@tonic-gate 	}
2870Sstevel@tonic-gate 
2880Sstevel@tonic-gate 	if ((st.st_mode & S_IFMT) == S_IFLNK) {
2890Sstevel@tonic-gate 		linkflg = 1;
2900Sstevel@tonic-gate 		if (stat(name, &st) < 0) {
2910Sstevel@tonic-gate 			errmsg(2, 0, gettext("can't access %s\n"), path);
2920Sstevel@tonic-gate 			return (1);
2930Sstevel@tonic-gate 		}
2940Sstevel@tonic-gate 	}
2950Sstevel@tonic-gate 
2960Sstevel@tonic-gate 	/* Do not recurse if directory is object of symbolic link */
297*5331Samw 	if (rflag && ((st.st_mode & S_IFMT) == S_IFDIR) && !linkflg) {
298*5331Samw 		return (chmodr(name, path, st.st_mode, umsk, secp, attrnames));
299*5331Samw 	}
3000Sstevel@tonic-gate 
301*5331Samw 	if (secp != NULL) {
302*5331Samw 		if (secp->sec_type == SEC_ACL) {
303*5331Samw 			return (doacl(name, &st, secp->sec_acls));
304*5331Samw 		} else if (secp->sec_type == SEC_ATTR) {
305*5331Samw 			return (set_attrs(name, attrnames, secp->sec_attrs));
306*5331Samw 		} else {
307*5331Samw 			return (1);
308*5331Samw 		}
309*5331Samw 	} else {
310*5331Samw 		if (chmod(name, newmode_common(ms, st.st_mode, umsk, name, path,
311*5331Samw 		    &group_clear_bits, &group_set_bits)) == -1) {
312*5331Samw 			errmsg(2, 0, gettext("can't change %s\n"), path);
313*5331Samw 			return (1);
314*5331Samw 		}
3150Sstevel@tonic-gate 	}
3160Sstevel@tonic-gate 
3170Sstevel@tonic-gate 	/*
3180Sstevel@tonic-gate 	 * If the group permissions of the file are being modified,
3190Sstevel@tonic-gate 	 * make sure that the file's ACL (if it has one) is
3200Sstevel@tonic-gate 	 * modified also, since chmod is supposed to apply group
3210Sstevel@tonic-gate 	 * permissions changes to both the acl mask and the
3220Sstevel@tonic-gate 	 * general group permissions.
3230Sstevel@tonic-gate 	 */
3240Sstevel@tonic-gate 	if (group_clear_bits || group_set_bits)
3250Sstevel@tonic-gate 		handle_acl(name, group_clear_bits, group_set_bits);
3260Sstevel@tonic-gate 
3270Sstevel@tonic-gate 	return (0);
3280Sstevel@tonic-gate }
3290Sstevel@tonic-gate 
3300Sstevel@tonic-gate static int
331*5331Samw chmodr(char *dir, char *path,  mode_t mode, mode_t umsk, sec_args_t *secp,
332*5331Samw     attr_name_t *attrnames)
3330Sstevel@tonic-gate {
3340Sstevel@tonic-gate 
3350Sstevel@tonic-gate 	DIR *dirp;
3360Sstevel@tonic-gate 	struct dirent *dp;
3370Sstevel@tonic-gate 	char savedir[PATH_MAX];			/* dir name to restore */
3380Sstevel@tonic-gate 	char currdir[PATH_MAX+1];		/* current dir name + '/' */
3390Sstevel@tonic-gate 	char parentdir[PATH_MAX+1];		/* parent dir name  + '/' */
3400Sstevel@tonic-gate 	int ecode;
341789Sahrens 	struct stat st;
3420Sstevel@tonic-gate 	o_mode_t	group_clear_bits, group_set_bits;
3430Sstevel@tonic-gate 
3440Sstevel@tonic-gate 	if (getcwd(savedir, PATH_MAX) == 0)
3450Sstevel@tonic-gate 		errmsg(2, 255, gettext("chmod: could not getcwd %s\n"),
3460Sstevel@tonic-gate 		    savedir);
3470Sstevel@tonic-gate 
3480Sstevel@tonic-gate 	/*
3490Sstevel@tonic-gate 	 * Change what we are given before doing it's contents
3500Sstevel@tonic-gate 	 */
351*5331Samw 	if (secp != NULL) {
352789Sahrens 		if (lstat(dir, &st) < 0) {
353789Sahrens 			errmsg(2, 0, gettext("can't access %s\n"), path);
354789Sahrens 			return (1);
355789Sahrens 		}
356*5331Samw 		if (secp->sec_type == SEC_ACL) {
357*5331Samw 			if (doacl(dir, &st, secp->sec_acls) != 0)
358*5331Samw 				return (1);
359*5331Samw 		} else if (secp->sec_type == SEC_ATTR) {
360*5331Samw 			if (set_attrs(dir, attrnames, secp->sec_attrs) != 0) {
361*5331Samw 				return (1);
362*5331Samw 			}
363*5331Samw 		} else {
364789Sahrens 			return (1);
365*5331Samw 		}
366789Sahrens 	} else if (chmod(dir, newmode_common(ms, mode, umsk, dir, path,
3670Sstevel@tonic-gate 	    &group_clear_bits, &group_set_bits)) < 0) {
3680Sstevel@tonic-gate 		errmsg(2, 0, gettext("can't change %s\n"), path);
3690Sstevel@tonic-gate 		return (1);
3700Sstevel@tonic-gate 	}
3710Sstevel@tonic-gate 
3720Sstevel@tonic-gate 	/*
3730Sstevel@tonic-gate 	 * If the group permissions of the file are being modified,
3740Sstevel@tonic-gate 	 * make sure that the file's ACL (if it has one) is
3750Sstevel@tonic-gate 	 * modified also, since chmod is supposed to apply group
3760Sstevel@tonic-gate 	 * permissions changes to both the acl mask and the
3770Sstevel@tonic-gate 	 * general group permissions.
3780Sstevel@tonic-gate 	 */
379789Sahrens 
380*5331Samw 	if (secp != NULL) {
381*5331Samw 		/* only necessary when not setting ACL or system attributes */
382789Sahrens 		if (group_clear_bits || group_set_bits)
383789Sahrens 			handle_acl(dir, group_clear_bits, group_set_bits);
384789Sahrens 	}
3850Sstevel@tonic-gate 
3860Sstevel@tonic-gate 	if (chdir(dir) < 0) {
3870Sstevel@tonic-gate 		errmsg(2, 0, "%s/%s: %s\n", savedir, dir, strerror(errno));
3880Sstevel@tonic-gate 		return (1);
3890Sstevel@tonic-gate 	}
3900Sstevel@tonic-gate 	if ((dirp = opendir(".")) == NULL) {
3910Sstevel@tonic-gate 		errmsg(2, 0, "%s\n", strerror(errno));
3920Sstevel@tonic-gate 		return (1);
3930Sstevel@tonic-gate 	}
3940Sstevel@tonic-gate 	ecode = 0;
3950Sstevel@tonic-gate 
3960Sstevel@tonic-gate 	/*
3970Sstevel@tonic-gate 	 * Save parent directory path before recursive chmod.
3980Sstevel@tonic-gate 	 * We'll need this for error printing purposes. Add
3990Sstevel@tonic-gate 	 * a trailing '/' to the path except in the case where
4000Sstevel@tonic-gate 	 * the path is just '/'
4010Sstevel@tonic-gate 	 */
4020Sstevel@tonic-gate 
4033520Sas145665 	if (strlcpy(parentdir, path, PATH_MAX + 1) >= PATH_MAX + 1) {
4043520Sas145665 		errmsg(2, 0, gettext("directory path name too long: %s\n"),
4053520Sas145665 		    path);
4063520Sas145665 		return (1);
4073520Sas145665 	}
4080Sstevel@tonic-gate 	if (strcmp(path, "/") != 0)
4093520Sas145665 		if (strlcat(parentdir, "/", PATH_MAX + 1) >= PATH_MAX + 1) {
4103520Sas145665 			errmsg(2, 0,
4113520Sas145665 			    gettext("directory path name too long: %s/\n"),
4123520Sas145665 			    parentdir);
4133520Sas145665 			return (1);
4143520Sas145665 		}
4153520Sas145665 
4160Sstevel@tonic-gate 
4170Sstevel@tonic-gate 	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp))  {
4183520Sas145665 
4191591Scasper 		if (strcmp(dp->d_name, ".") == 0 ||	/* skip . and .. */
4201591Scasper 		    strcmp(dp->d_name, "..") == 0) {
4211591Scasper 			continue;
4221591Scasper 		}
4233520Sas145665 		if (strlcpy(currdir, parentdir, PATH_MAX + 1) >= PATH_MAX + 1) {
4243520Sas145665 			errmsg(2, 0,
4253520Sas145665 			    gettext("directory path name too long: %s\n"),
4263520Sas145665 			    parentdir);
4273520Sas145665 			return (1);
4283520Sas145665 		}
4293520Sas145665 		if (strlcat(currdir, dp->d_name, PATH_MAX + 1)
4303520Sas145665 		    >= PATH_MAX + 1) {
4313520Sas145665 			errmsg(2, 0,
4323520Sas145665 			    gettext("directory path name too long: %s%s\n"),
4333520Sas145665 			    currdir, dp->d_name);
4343520Sas145665 			return (1);
4353520Sas145665 		}
436*5331Samw 		ecode += dochmod(dp->d_name, currdir, umsk, secp, attrnames);
4370Sstevel@tonic-gate 	}
4380Sstevel@tonic-gate 	(void) closedir(dirp);
4390Sstevel@tonic-gate 	if (chdir(savedir) < 0) {
4400Sstevel@tonic-gate 		errmsg(2, 255, gettext("can't change back to %s\n"), savedir);
4410Sstevel@tonic-gate 	}
4420Sstevel@tonic-gate 	return (ecode ? 1 : 0);
4430Sstevel@tonic-gate }
4440Sstevel@tonic-gate 
4450Sstevel@tonic-gate /* PRINTFLIKE3 */
4460Sstevel@tonic-gate void
4470Sstevel@tonic-gate errmsg(int severity, int code, char *format, ...)
4480Sstevel@tonic-gate {
4490Sstevel@tonic-gate 	va_list ap;
4500Sstevel@tonic-gate 	static char *msg[] = {
4510Sstevel@tonic-gate 	"",
4520Sstevel@tonic-gate 	"ERROR",
4530Sstevel@tonic-gate 	"WARNING",
4540Sstevel@tonic-gate 	""
4550Sstevel@tonic-gate 	};
4560Sstevel@tonic-gate 
4570Sstevel@tonic-gate 	va_start(ap, format);
4580Sstevel@tonic-gate 
4590Sstevel@tonic-gate 	/*
4600Sstevel@tonic-gate 	 * Always print error message if this is a fatal error (code == 0);
4610Sstevel@tonic-gate 	 * otherwise, print message if fflag == 0 (no -f option specified)
4620Sstevel@tonic-gate 	 */
4630Sstevel@tonic-gate 	if (!fflag || (code != 0)) {
4640Sstevel@tonic-gate 		(void) fprintf(stderr,
465*5331Samw 		    "chmod: %s: ", gettext(msg[severity]));
4660Sstevel@tonic-gate 		(void) vfprintf(stderr, format, ap);
4670Sstevel@tonic-gate 	}
4680Sstevel@tonic-gate 
4690Sstevel@tonic-gate 	va_end(ap);
4700Sstevel@tonic-gate 
4710Sstevel@tonic-gate 	if (code != 0)
4720Sstevel@tonic-gate 		exit(fflag ? 0 : code);
4730Sstevel@tonic-gate }
4740Sstevel@tonic-gate 
4750Sstevel@tonic-gate static void
4760Sstevel@tonic-gate usage(void)
4770Sstevel@tonic-gate {
4780Sstevel@tonic-gate 	(void) fprintf(stderr, gettext(
4790Sstevel@tonic-gate 	    "usage:\tchmod [-fR] <absolute-mode> file ...\n"));
4800Sstevel@tonic-gate 
4810Sstevel@tonic-gate 	(void) fprintf(stderr, gettext(
482*5331Samw 	    "\tchmod [-fR] [-@ attribute] ... "
483*5331Samw 	    "S<attribute-operation> file ...\n"));
484*5331Samw 
485*5331Samw 	(void) fprintf(stderr, gettext(
486789Sahrens 	    "\tchmod [-fR] <ACL-operation> file ...\n"));
487789Sahrens 
488789Sahrens 	(void) fprintf(stderr, gettext(
489*5331Samw 	    "\tchmod [-fR] <symbolic-mode-list> file ...\n\n"));
490789Sahrens 
4910Sstevel@tonic-gate 	(void) fprintf(stderr, gettext(
4920Sstevel@tonic-gate 	    "where \t<symbolic-mode-list> is a comma-separated list of\n"));
493*5331Samw 	(void) fprintf(stderr, gettext(
494*5331Samw 	    "\t[ugoa]{+|-|=}[rwxXlstugo]\n\n"));
4950Sstevel@tonic-gate 
4960Sstevel@tonic-gate 	(void) fprintf(stderr, gettext(
497*5331Samw 	    "where \t<attribute-operation> is a comma-separated list of\n"
498*5331Samw 	    "\tone or more of the following\n"));
499*5331Samw 	(void) fprintf(stderr, gettext(
500*5331Samw 	    "\t[+|-|=]c[<compact-attribute-list>|{<compact-attribute-list>}]\n"
501*5331Samw 	    "\t[+|-|=]v[<verbose-attribute-setting>|"
502*5331Samw 	    "\'{\'<verbose-attribute-setting-list>\'}\']\n"
503*5331Samw 	    "\t[+|-|=]a\n"));
504*5331Samw 	(void) fprintf(stderr, gettext(
505*5331Samw 	    "where \t<compact-attribute-list> is a list of zero or more of\n"));
506*5331Samw 	print_attrs(ATTR_OPTS);
507*5331Samw 	(void) fprintf(stderr, gettext(
508*5331Samw 	    "where \t<verbose-attribute-setting> is one of\n"));
509*5331Samw 	print_attrs(ATTR_NAMES);
510*5331Samw 	(void) fprintf(stderr, gettext(
511*5331Samw 	    "\tand can be, optionally, immediately preceded by \"no\"\n\n"));
512789Sahrens 
513789Sahrens 	(void) fprintf(stderr, gettext(
514789Sahrens 	    "where \t<ACL-operation> is one of the following\n"));
515789Sahrens 	(void) fprintf(stderr, gettext("\tA-<acl_specification>\n"));
516789Sahrens 	(void) fprintf(stderr, gettext("\tA[number]-\n"));
517789Sahrens 	(void) fprintf(stderr, gettext(
518789Sahrens 	    "\tA[number]{+|=}<acl_specification>\n"));
519789Sahrens 	(void) fprintf(stderr, gettext(
520789Sahrens 	    "where \t<acl-specification> is a comma-separated list of ACEs\n"));
5210Sstevel@tonic-gate }
5220Sstevel@tonic-gate 
5230Sstevel@tonic-gate /*
5240Sstevel@tonic-gate  *  parseargs - generate getopt-friendly argument list for backwards
5250Sstevel@tonic-gate  *		compatibility with earlier Solaris usage (eg, chmod -w
5260Sstevel@tonic-gate  *		foo).
5270Sstevel@tonic-gate  *
5280Sstevel@tonic-gate  *  assumes the existence of a static set of alternates to argc and argv,
5290Sstevel@tonic-gate  *  (namely, mac, and mav[]).
5300Sstevel@tonic-gate  *
5310Sstevel@tonic-gate  */
5320Sstevel@tonic-gate 
5330Sstevel@tonic-gate static void
5340Sstevel@tonic-gate parseargs(int ac, char *av[])
5350Sstevel@tonic-gate {
5360Sstevel@tonic-gate 	int i;			/* current argument			*/
5370Sstevel@tonic-gate 	int fflag;		/* arg list contains "--"		*/
5380Sstevel@tonic-gate 	size_t mav_num;		/* number of entries in mav[]		*/
5390Sstevel@tonic-gate 
5400Sstevel@tonic-gate 	/*
5410Sstevel@tonic-gate 	 * We add an extra argument slot, in case we need to jam a "--"
5420Sstevel@tonic-gate 	 * argument into the list.
5430Sstevel@tonic-gate 	 */
5440Sstevel@tonic-gate 
5450Sstevel@tonic-gate 	mav_num = (size_t)ac+2;
5460Sstevel@tonic-gate 
5470Sstevel@tonic-gate 	if ((mav = calloc(mav_num, sizeof (char *))) == NULL) {
5480Sstevel@tonic-gate 		perror("chmod");
5490Sstevel@tonic-gate 		exit(2);
5500Sstevel@tonic-gate 	}
5510Sstevel@tonic-gate 
5520Sstevel@tonic-gate 	/* scan for the use of "--" in the argument list */
5530Sstevel@tonic-gate 
5540Sstevel@tonic-gate 	for (fflag = i = 0; i < ac; i ++) {
5550Sstevel@tonic-gate 		if (strcmp(av[i], "--") == 0)
556*5331Samw 			fflag = 1;
5570Sstevel@tonic-gate 	}
5580Sstevel@tonic-gate 
5590Sstevel@tonic-gate 	/* process the arguments */
5600Sstevel@tonic-gate 
5610Sstevel@tonic-gate 	for (i = mac = 0;
5620Sstevel@tonic-gate 	    (av[i] != (char *)NULL) && (av[i][0] != (char)NULL);
5630Sstevel@tonic-gate 	    i++) {
5640Sstevel@tonic-gate 		if (!fflag && av[i][0] == '-') {
5650Sstevel@tonic-gate 			/*
5660Sstevel@tonic-gate 			 *  If there is not already a "--" argument specified,
5670Sstevel@tonic-gate 			 *  and the argument starts with '-' but does not
5680Sstevel@tonic-gate 			 *  contain any of the official option letters, then it
5690Sstevel@tonic-gate 			 *  is probably a mode argument beginning with '-'.
5700Sstevel@tonic-gate 			 *  Force a "--" into the argument stream in front of
5710Sstevel@tonic-gate 			 *  it.
5720Sstevel@tonic-gate 			 */
5730Sstevel@tonic-gate 
5740Sstevel@tonic-gate 			if ((strchr(av[i], 'R') == NULL &&
575*5331Samw 			    strchr(av[i], 'f') == NULL) &&
576*5331Samw 			    strchr(av[i], '@') == NULL) {
577*5331Samw 				if ((mav[mac++] = strdup("--")) == NULL) {
578*5331Samw 					perror("chmod");
579*5331Samw 					exit(2);
580*5331Samw 				}
5810Sstevel@tonic-gate 			}
5820Sstevel@tonic-gate 		}
5830Sstevel@tonic-gate 
584*5331Samw 		if ((mav[mac++] = strdup(av[i])) == NULL) {
585*5331Samw 			perror("chmod");
586*5331Samw 			exit(2);
587*5331Samw 		}
5880Sstevel@tonic-gate 	}
5890Sstevel@tonic-gate 
5900Sstevel@tonic-gate 	mav[mac] = (char *)NULL;
5910Sstevel@tonic-gate }
5920Sstevel@tonic-gate 
593*5331Samw static int
594*5331Samw parse_acl_args(char *arg, sec_args_t **sec_args)
595789Sahrens {
596789Sahrens 	acl_t *new_acl = NULL;
597789Sahrens 	int slot;
598789Sahrens 	int len;
599789Sahrens 	int action;
600789Sahrens 	acl_args_t *new_acl_args;
601789Sahrens 	char *acl_spec = NULL;
602789Sahrens 	char *end;
603789Sahrens 
604789Sahrens 	if (arg[0] != 'A')
605789Sahrens 		return (1);
606789Sahrens 
607789Sahrens 	slot = strtol(&arg[1], &end, 10);
608789Sahrens 
609789Sahrens 	len = strlen(arg);
610789Sahrens 	switch (*end) {
611789Sahrens 	case '+':
612789Sahrens 		action = ACL_ADD;
613789Sahrens 		acl_spec = ++end;
614789Sahrens 		break;
615789Sahrens 	case '-':
616789Sahrens 		if (len == 2 && arg[0] == 'A' && arg[1] == '-')
617789Sahrens 			action = ACL_STRIP;
618789Sahrens 		else
619789Sahrens 			action = ACL_DELETE;
620789Sahrens 		if (action != ACL_STRIP) {
621789Sahrens 			acl_spec = ++end;
622789Sahrens 			if (acl_spec[0] == '\0') {
623789Sahrens 				action = ACL_SLOT_DELETE;
624789Sahrens 				acl_spec = NULL;
625789Sahrens 			} else if (arg[1] != '-')
626789Sahrens 				return (1);
627789Sahrens 		}
628789Sahrens 		break;
629789Sahrens 	case '=':
630865Smarks 		/*
631865Smarks 		 * Was slot specified?
632865Smarks 		 */
633865Smarks 		if (arg[1] == '=')
634865Smarks 			slot = -1;
635789Sahrens 		action = ACL_REPLACE;
636789Sahrens 		acl_spec = ++end;
637789Sahrens 		break;
638789Sahrens 	default:
639789Sahrens 		return (1);
640789Sahrens 	}
641789Sahrens 
642789Sahrens 	if ((action == ACL_REPLACE || action == ACL_ADD) && acl_spec[0] == '\0')
643789Sahrens 		return (1);
644789Sahrens 
645789Sahrens 	if (acl_spec) {
6461420Smarks 		if (acl_parse(acl_spec, &new_acl)) {
6471420Smarks 			exit(1);
648789Sahrens 		}
649789Sahrens 	}
650789Sahrens 
651789Sahrens 	new_acl_args = malloc(sizeof (acl_args_t));
652789Sahrens 	if (new_acl_args == NULL)
653789Sahrens 		return (1);
654789Sahrens 
655789Sahrens 	new_acl_args->acl_aclp = new_acl;
656789Sahrens 	new_acl_args->acl_slot = slot;
657789Sahrens 	new_acl_args->acl_action = action;
658789Sahrens 
659*5331Samw 	if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) {
660*5331Samw 		perror("chmod");
661*5331Samw 		exit(2);
662*5331Samw 	}
663*5331Samw 	(*sec_args)->sec_type = SEC_ACL;
664*5331Samw 	(*sec_args)->sec_acls = new_acl_args;
665789Sahrens 
666789Sahrens 	return (0);
667789Sahrens }
668789Sahrens 
6690Sstevel@tonic-gate /*
6700Sstevel@tonic-gate  * This function is called whenever the group permissions of a file
6710Sstevel@tonic-gate  * is being modified.  According to the chmod(1) manpage, any
6720Sstevel@tonic-gate  * change made to the group permissions must be applied to both
6730Sstevel@tonic-gate  * the acl mask and the acl's GROUP_OBJ.  The chmod(2) already
6740Sstevel@tonic-gate  * set the mask, so this routine needs to make the same change
6750Sstevel@tonic-gate  * to the GROUP_OBJ.
6760Sstevel@tonic-gate  */
6770Sstevel@tonic-gate static void
6780Sstevel@tonic-gate handle_acl(char *name, o_mode_t group_clear_bits, o_mode_t group_set_bits)
6790Sstevel@tonic-gate {
6800Sstevel@tonic-gate 	int aclcnt, n;
6810Sstevel@tonic-gate 	aclent_t *aclp, *tp;
6820Sstevel@tonic-gate 	o_mode_t newperm;
683789Sahrens 	/*
684789Sahrens 	 * if this file system support ace_t acl's
685789Sahrens 	 * then simply return since we don't have an
686789Sahrens 	 * acl mask to deal with
687789Sahrens 	 */
688789Sahrens 	if (pathconf(name, _PC_ACL_ENABLED) == _ACL_ACE_ENABLED)
689789Sahrens 		return;
6900Sstevel@tonic-gate 	if ((aclcnt = acl(name, GETACLCNT, 0, NULL)) <= MIN_ACL_ENTRIES)
6910Sstevel@tonic-gate 		return;	/* it's just a trivial acl; no need to change it */
6920Sstevel@tonic-gate 	if ((aclp = (aclent_t *)malloc((sizeof (aclent_t)) * aclcnt))
6930Sstevel@tonic-gate 	    == NULL) {
6940Sstevel@tonic-gate 		perror("chmod");
6950Sstevel@tonic-gate 		exit(2);
6960Sstevel@tonic-gate 	}
6970Sstevel@tonic-gate 
6980Sstevel@tonic-gate 	if (acl(name, GETACL, aclcnt, aclp) < 0) {
6990Sstevel@tonic-gate 		free(aclp);
7000Sstevel@tonic-gate 		(void) fprintf(stderr, "chmod: ");
7010Sstevel@tonic-gate 		perror(name);
7020Sstevel@tonic-gate 		return;
7030Sstevel@tonic-gate 	}
7040Sstevel@tonic-gate 	for (tp = aclp, n = aclcnt; n--; tp++) {
7050Sstevel@tonic-gate 		if (tp->a_type == GROUP_OBJ) {
7060Sstevel@tonic-gate 			newperm = tp->a_perm;
7070Sstevel@tonic-gate 			if (group_clear_bits != 0)
7080Sstevel@tonic-gate 				newperm &= ~group_clear_bits;
7090Sstevel@tonic-gate 			if (group_set_bits != 0)
7100Sstevel@tonic-gate 				newperm |= group_set_bits;
7110Sstevel@tonic-gate 			if (newperm != tp->a_perm) {
7120Sstevel@tonic-gate 				tp->a_perm = newperm;
7130Sstevel@tonic-gate 				if (acl(name, SETACL, aclcnt, aclp)
7140Sstevel@tonic-gate 				    < 0) {
7150Sstevel@tonic-gate 					(void) fprintf(stderr, "chmod: ");
7160Sstevel@tonic-gate 					perror(name);
7170Sstevel@tonic-gate 				}
7180Sstevel@tonic-gate 			}
7190Sstevel@tonic-gate 			break;
7200Sstevel@tonic-gate 		}
7210Sstevel@tonic-gate 	}
7220Sstevel@tonic-gate 	free(aclp);
7230Sstevel@tonic-gate }
724789Sahrens 
725789Sahrens static int
726789Sahrens doacl(char *file, struct stat *st, acl_args_t *acl_args)
727789Sahrens {
728789Sahrens 	acl_t *aclp;
729789Sahrens 	acl_t *set_aclp;
730789Sahrens 	int error = 0;
731789Sahrens 	void *to, *from;
732789Sahrens 	int len;
733789Sahrens 	int isdir;
734789Sahrens 	isdir = S_ISDIR(st->st_mode);
735789Sahrens 
736789Sahrens 	error = acl_get(file, 0, &aclp);
737789Sahrens 
738789Sahrens 	if (error != 0) {
739789Sahrens 		errmsg(1, 1, "%s\n", acl_strerror(error));
740789Sahrens 		return (1);
741789Sahrens 	}
742789Sahrens 	switch (acl_args->acl_action) {
743789Sahrens 	case ACL_ADD:
744789Sahrens 		if ((error = acl_addentries(aclp,
745*5331Samw 		    acl_args->acl_aclp, acl_args->acl_slot)) != 0) {
746*5331Samw 			errmsg(1, 1, "%s\n", acl_strerror(error));
747*5331Samw 			acl_free(aclp);
748*5331Samw 			return (1);
749789Sahrens 		}
750789Sahrens 		set_aclp = aclp;
751789Sahrens 		break;
752789Sahrens 	case ACL_SLOT_DELETE:
753789Sahrens 		if (acl_args->acl_slot + 1 > aclp->acl_cnt) {
754789Sahrens 			errmsg(1, 1,
755789Sahrens 			    gettext("Invalid slot specified for removal\n"));
756789Sahrens 			acl_free(aclp);
757789Sahrens 			return (1);
758789Sahrens 		}
759789Sahrens 
760789Sahrens 		if (acl_args->acl_slot == 0 && aclp->acl_cnt == 1) {
761789Sahrens 			errmsg(1, 1,
762789Sahrens 			    gettext("Can't remove all ACL "
763789Sahrens 			    "entries from a file\n"));
764789Sahrens 			acl_free(aclp);
765789Sahrens 			return (1);
766789Sahrens 		}
767789Sahrens 
768789Sahrens 		/*
769789Sahrens 		 * remove a single entry
770789Sahrens 		 *
771789Sahrens 		 * if last entry just adjust acl_cnt
772789Sahrens 		 */
773789Sahrens 
774789Sahrens 		if ((acl_args->acl_slot + 1) == aclp->acl_cnt)
775789Sahrens 			aclp->acl_cnt--;
776789Sahrens 		else {
777789Sahrens 			to = (char *)aclp->acl_aclp +
778789Sahrens 			    (acl_args->acl_slot * aclp->acl_entry_size);
779789Sahrens 			from = (char *)to + aclp->acl_entry_size;
780789Sahrens 			len = (aclp->acl_cnt - acl_args->acl_slot - 1) *
781789Sahrens 			    aclp->acl_entry_size;
782789Sahrens 			(void) memmove(to, from, len);
783789Sahrens 			aclp->acl_cnt--;
784789Sahrens 		}
785789Sahrens 		set_aclp = aclp;
786789Sahrens 		break;
787789Sahrens 
788789Sahrens 	case ACL_DELETE:
789789Sahrens 		if ((error = acl_removeentries(aclp, acl_args->acl_aclp,
790789Sahrens 		    acl_args->acl_slot, ACL_REMOVE_ALL)) != 0) {
791789Sahrens 			errmsg(1, 1, "%s\n", acl_strerror(error));
792789Sahrens 			acl_free(aclp);
793789Sahrens 			return (1);
794789Sahrens 		}
795789Sahrens 
796789Sahrens 		if (aclp->acl_cnt == 0) {
797789Sahrens 			errmsg(1, 1,
798789Sahrens 			    gettext("Can't remove all ACL "
799789Sahrens 			    "entries from a file\n"));
800789Sahrens 			acl_free(aclp);
801789Sahrens 			return (1);
802789Sahrens 		}
803789Sahrens 
804789Sahrens 		set_aclp = aclp;
805789Sahrens 		break;
806789Sahrens 	case ACL_REPLACE:
807789Sahrens 		if (acl_args->acl_slot >= 0)  {
808789Sahrens 			error = acl_modifyentries(aclp, acl_args->acl_aclp,
809789Sahrens 			    acl_args->acl_slot);
810789Sahrens 			if (error) {
811789Sahrens 				errmsg(1, 1, "%s\n", acl_strerror(error));
812789Sahrens 				acl_free(aclp);
813789Sahrens 				return (1);
814789Sahrens 			}
815789Sahrens 			set_aclp = aclp;
816789Sahrens 		} else {
817789Sahrens 			set_aclp = acl_args->acl_aclp;
818789Sahrens 		}
819789Sahrens 		break;
820789Sahrens 	case ACL_STRIP:
821789Sahrens 		error = acl_strip(file, st->st_uid, st->st_gid, st->st_mode);
822789Sahrens 		if (error) {
823789Sahrens 			errmsg(1, 1, "%s\n", acl_strerror(error));
824789Sahrens 			return (1);
825789Sahrens 		}
826789Sahrens 		acl_free(aclp);
827789Sahrens 		return (0);
828789Sahrens 		/*NOTREACHED*/
829789Sahrens 	default:
830789Sahrens 		errmsg(1, 0, gettext("Unknown ACL action requested\n"));
831789Sahrens 		return (1);
832789Sahrens 		break;
833789Sahrens 	}
834789Sahrens 	error = acl_check(set_aclp, isdir);
835789Sahrens 
836789Sahrens 	if (error) {
837789Sahrens 		errmsg(1, 0, "%s\n%s", acl_strerror(error),
838789Sahrens 		    gettext("See chmod(1) for more information on "
839789Sahrens 		    "valid ACL syntax\n"));
840789Sahrens 		return (1);
841789Sahrens 	}
842789Sahrens 	if ((error = acl_set(file, set_aclp)) != 0) {
843789Sahrens 			errmsg(1, 0, gettext("Failed to set ACL: %s\n"),
844789Sahrens 			    acl_strerror(error));
845789Sahrens 			acl_free(aclp);
846789Sahrens 			return (1);
847789Sahrens 	}
848789Sahrens 	acl_free(aclp);
849789Sahrens 	return (0);
850789Sahrens }
851*5331Samw 
852*5331Samw /*
853*5331Samw  * Prints out the attributes in their verbose form:
854*5331Samw  *	'{'[["no"]<attribute-name>][,["no"]<attribute-name>]...'}'
855*5331Samw  * similar to output of ls -/v.
856*5331Samw  */
857*5331Samw static void
858*5331Samw print_nvlist(nvlist_t *attr_nvlist)
859*5331Samw {
860*5331Samw 	int		firsttime = 1;
861*5331Samw 	boolean_t	value;
862*5331Samw 	nvlist_t	*lptr = attr_nvlist;
863*5331Samw 	nvpair_t	*pair = NULL;
864*5331Samw 
865*5331Samw 	(void) fprintf(stderr, "\t%c", LEFTBRACE);
866*5331Samw 	while (pair = nvlist_next_nvpair(lptr, pair)) {
867*5331Samw 		if (nvpair_value_boolean_value(pair, &value) == 0) {
868*5331Samw 			(void) fprintf(stderr, "%s%s%s",
869*5331Samw 			    firsttime ? "" : A_SEP_TOK,
870*5331Samw 			    (value == A_SET_VAL) ? "" : "no",
871*5331Samw 			    nvpair_name(pair));
872*5331Samw 			firsttime = 0;
873*5331Samw 		} else {
874*5331Samw 			(void) fprintf(stderr, gettext(
875*5331Samw 			    "<error retrieving attributes: %s>"),
876*5331Samw 			    strerror(errno));
877*5331Samw 			break;
878*5331Samw 		}
879*5331Samw 	}
880*5331Samw 	(void) fprintf(stderr, "%c\n", RIGHTBRACE);
881*5331Samw }
882*5331Samw 
883*5331Samw /*
884*5331Samw  * Add an attribute name and boolean value to an nvlist if an action is to be
885*5331Samw  * performed for that attribute.  The nvlist will be used later to set all the
886*5331Samw  * attributes in the nvlist in one operation through a call to setattrat().
887*5331Samw  *
888*5331Samw  * If a set operation ('+') was specified, then a boolean representation of the
889*5331Samw  * attribute's value will be added to the nvlist for that attribute name.  If an
890*5331Samw  * inverse operation ('-') was specified, then a boolean representation of the
891*5331Samw  * inverse of the attribute's value will be added to the nvlist for that
892*5331Samw  * attribute name.
893*5331Samw  *
894*5331Samw  * Returns an nvlist of attribute name and boolean value pairs if there are
895*5331Samw  * attribute actions to be performed, otherwise returns NULL.
896*5331Samw  */
897*5331Samw static nvlist_t *
898*5331Samw set_attrs_nvlist(char *attractptr, int numofattrs)
899*5331Samw {
900*5331Samw 	int		attribute_set = 0;
901*5331Samw 	f_attr_t	i;
902*5331Samw 	nvlist_t	*attr_nvlist;
903*5331Samw 
904*5331Samw 	if (nvlist_alloc(&attr_nvlist, NV_UNIQUE_NAME, 0) != 0) {
905*5331Samw 		perror("chmod");
906*5331Samw 		exit(2);
907*5331Samw 	}
908*5331Samw 
909*5331Samw 	for (i = 0; i < numofattrs; i++) {
910*5331Samw 		if (attractptr[i] != '\0') {
911*5331Samw 			if ((nvlist_add_boolean_value(attr_nvlist,
912*5331Samw 			    attr_to_name(i),
913*5331Samw 			    (attractptr[i] == A_SET_OP))) != 0) {
914*5331Samw 				errmsg(1, 2, gettext(
915*5331Samw 				    "unable to propagate attribute names and"
916*5331Samw 				    "values: %s\n"), strerror(errno));
917*5331Samw 			} else {
918*5331Samw 				attribute_set = 1;
919*5331Samw 			}
920*5331Samw 		}
921*5331Samw 	}
922*5331Samw 	return (attribute_set ? attr_nvlist : NULL);
923*5331Samw }
924*5331Samw 
925*5331Samw /*
926*5331Samw  * Set the attributes of file, or if specified, of the named attribute file,
927*5331Samw  * attrname.  Build an nvlist of attribute names and values and call setattrat()
928*5331Samw  * to set the attributes in one operation.
929*5331Samw  *
930*5331Samw  * Returns 0 if successful, otherwise returns 1.
931*5331Samw  */
932*5331Samw static int
933*5331Samw set_file_attrs(char *file, char *attrname, nvlist_t *attr_nvlist)
934*5331Samw {
935*5331Samw 	int	rc;
936*5331Samw 	char	*filename;
937*5331Samw 
938*5331Samw 	if (attrname != NULL) {
939*5331Samw 		filename = attrname;
940*5331Samw 	} else {
941*5331Samw 		filename = basename(file);
942*5331Samw 	}
943*5331Samw 
944*5331Samw 	if ((rc = setattrat(AT_FDCWD, XATTR_VIEW_READWRITE, filename,
945*5331Samw 	    attr_nvlist)) != 0) {
946*5331Samw 		char *emsg;
947*5331Samw 		switch (errno) {
948*5331Samw 		case EINVAL:
949*5331Samw 			emsg = gettext("not supported");
950*5331Samw 			break;
951*5331Samw 		case EPERM:
952*5331Samw 			emsg = gettext("not privileged");
953*5331Samw 			break;
954*5331Samw 		default:
955*5331Samw 			emsg = strerror(rc);
956*5331Samw 		}
957*5331Samw 		errmsg(1, 0, gettext(
958*5331Samw 		    "cannot set the following attributes on "
959*5331Samw 		    "%s%s%s%s: %s\n"),
960*5331Samw 		    (attrname == NULL) ? "" : gettext("attribute "),
961*5331Samw 		    (attrname == NULL) ? "" : attrname,
962*5331Samw 		    (attrname == NULL) ? "" : gettext(" of "),
963*5331Samw 		    file, emsg);
964*5331Samw 		print_nvlist(attr_nvlist);
965*5331Samw 	}
966*5331Samw 
967*5331Samw 	return (rc);
968*5331Samw }
969*5331Samw 
970*5331Samw static int
971*5331Samw save_cwd(void)
972*5331Samw {
973*5331Samw 	return (open(".", O_RDONLY));
974*5331Samw }
975*5331Samw 
976*5331Samw static void
977*5331Samw rest_cwd(int cwd)
978*5331Samw {
979*5331Samw 	if (cwd != -1) {
980*5331Samw 		if (fchdir(cwd) != 0) {
981*5331Samw 			errmsg(1, 1, gettext(
982*5331Samw 			    "can't change to current working directory\n"));
983*5331Samw 		}
984*5331Samw 		(void) close(cwd);
985*5331Samw 	}
986*5331Samw }
987*5331Samw 
988*5331Samw /*
989*5331Samw  * Returns 1 if filename is a system attribute file, otherwise
990*5331Samw  * returns 0.
991*5331Samw  */
992*5331Samw static int
993*5331Samw is_sattr(char *filename)
994*5331Samw {
995*5331Samw 	return (sysattr_type(filename) != _NOT_SATTR);
996*5331Samw }
997*5331Samw 
998*5331Samw /*
999*5331Samw  * Perform the action on the specified named attribute file for the file
1000*5331Samw  * associated with the input file descriptor.  If the named attribute file
1001*5331Samw  * is "*", then the action is to be performed on all the named attribute files
1002*5331Samw  * of the file associated with the input file descriptor.
1003*5331Samw  */
1004*5331Samw static int
1005*5331Samw set_named_attrs(char *file, int parentfd, char *attrname, nvlist_t *attr_nvlist)
1006*5331Samw {
1007*5331Samw 	int		dirfd;
1008*5331Samw 	int		error = 0;
1009*5331Samw 	DIR		*dirp = NULL;
1010*5331Samw 	struct dirent	*dp;
1011*5331Samw 	struct stat	st;
1012*5331Samw 
1013*5331Samw 	if ((attrname == NULL) || (strcmp(attrname, "*") != 0)) {
1014*5331Samw 		/*
1015*5331Samw 		 * Make sure the named attribute exists and extended system
1016*5331Samw 		 * attributes are supported on the underlying file system.
1017*5331Samw 		 */
1018*5331Samw 		if (attrname != NULL) {
1019*5331Samw 			if (fstatat(parentfd, attrname, &st,
1020*5331Samw 			    AT_SYMLINK_NOFOLLOW) < 0) {
1021*5331Samw 				errmsg(2, 0, gettext(
1022*5331Samw 				    "can't access attribute %s of %s\n"),
1023*5331Samw 				    attrname, file);
1024*5331Samw 				return (1);
1025*5331Samw 			}
1026*5331Samw 			if (sysattr_support(attrname, _PC_SATTR_ENABLED) != 1) {
1027*5331Samw 				errmsg(1, 0, gettext(
1028*5331Samw 				    "extended system attributes not supported "
1029*5331Samw 				    "for attribute %s of %s\n"),
1030*5331Samw 				    attrname, file);
1031*5331Samw 				return (1);
1032*5331Samw 			}
1033*5331Samw 		}
1034*5331Samw 
1035*5331Samw 		error = set_file_attrs(file, attrname, attr_nvlist);
1036*5331Samw 
1037*5331Samw 	} else {
1038*5331Samw 		if (((dirfd = dup(parentfd)) == -1) ||
1039*5331Samw 		    ((dirp = fdopendir(dirfd)) == NULL)) {
1040*5331Samw 			errmsg(1, 0, gettext(
1041*5331Samw 			    "cannot open dir pointer of file %s\n"), file);
1042*5331Samw 			if (dirfd > 0) {
1043*5331Samw 				(void) close(dirfd);
1044*5331Samw 			}
1045*5331Samw 			return (1);
1046*5331Samw 		}
1047*5331Samw 
1048*5331Samw 		while (dp = readdir(dirp)) {
1049*5331Samw 			/*
1050*5331Samw 			 * Process all extended attribute files except
1051*5331Samw 			 * ".", "..", and extended system attribute files.
1052*5331Samw 			 */
1053*5331Samw 			if ((strcmp(dp->d_name, ".") == 0) ||
1054*5331Samw 			    (strcmp(dp->d_name, "..") == 0) ||
1055*5331Samw 			    is_sattr(dp->d_name)) {
1056*5331Samw 				continue;
1057*5331Samw 			}
1058*5331Samw 
1059*5331Samw 			if (set_named_attrs(file, parentfd, dp->d_name,
1060*5331Samw 			    attr_nvlist) != 0) {
1061*5331Samw 				error++;
1062*5331Samw 			}
1063*5331Samw 		}
1064*5331Samw 		if (dirp != NULL) {
1065*5331Samw 			(void) closedir(dirp);
1066*5331Samw 		}
1067*5331Samw 	}
1068*5331Samw 
1069*5331Samw 	return ((error == 0) ? 0 : 1);
1070*5331Samw }
1071*5331Samw 
1072*5331Samw /*
1073*5331Samw  * Set the attributes of the specified file, or if specified with -@ on the
1074*5331Samw  * command line, the specified named attributes of the specified file.
1075*5331Samw  *
1076*5331Samw  * Returns 0 if successful, otherwise returns 1.
1077*5331Samw  */
1078*5331Samw static int
1079*5331Samw set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist)
1080*5331Samw {
1081*5331Samw 	char		*parentd;
1082*5331Samw 	char		*tpath = NULL;
1083*5331Samw 	int		cwd;
1084*5331Samw 	int		error = 0;
1085*5331Samw 	int		parentfd;
1086*5331Samw 	attr_name_t	*tattr = attrnames;
1087*5331Samw 
1088*5331Samw 	if (attr_nvlist == NULL) {
1089*5331Samw 		return (0);
1090*5331Samw 	}
1091*5331Samw 
1092*5331Samw 	if (sysattr_support(file, _PC_SATTR_ENABLED) != 1) {
1093*5331Samw 		errmsg(1, 0, gettext(
1094*5331Samw 		    "extended system attributes not supported for %s\n"), file);
1095*5331Samw 		return (1);
1096*5331Samw 	}
1097*5331Samw 
1098*5331Samw 	/*
1099*5331Samw 	 * Open the parent directory and change into it before attempting
1100*5331Samw 	 * to set the attributes of the file.
1101*5331Samw 	 */
1102*5331Samw 	if (attrnames == NULL) {
1103*5331Samw 		tpath = strdup(file);
1104*5331Samw 		parentd = dirname(tpath);
1105*5331Samw 		parentfd = open(parentd, O_RDONLY);
1106*5331Samw 	} else {
1107*5331Samw 		parentfd = attropen(file, ".", O_RDONLY);
1108*5331Samw 	}
1109*5331Samw 	if (parentfd == -1) {
1110*5331Samw 		errmsg(1, 0, gettext(
1111*5331Samw 		    "cannot open attribute directory of %s\n"), file);
1112*5331Samw 		if (tpath != NULL) {
1113*5331Samw 			free(tpath);
1114*5331Samw 		}
1115*5331Samw 		return (1);
1116*5331Samw 	}
1117*5331Samw 
1118*5331Samw 	if ((cwd = save_cwd()) < 0) {
1119*5331Samw 		errmsg(1, 1, gettext(
1120*5331Samw 		    "can't get current working directory\n"));
1121*5331Samw 	}
1122*5331Samw 	if (fchdir(parentfd) != 0) {
1123*5331Samw 		errmsg(1, 0, gettext(
1124*5331Samw 		    "can't change to parent %sdirectory of %s\n"),
1125*5331Samw 		    (attrnames == NULL) ? "" : gettext("attribute "), file);
1126*5331Samw 		(void) close(cwd);
1127*5331Samw 		(void) close(parentfd);
1128*5331Samw 		if (tpath != NULL) {
1129*5331Samw 			free(tpath);
1130*5331Samw 		}
1131*5331Samw 		return (1);
1132*5331Samw 	}
1133*5331Samw 
1134*5331Samw 	/*
1135*5331Samw 	 * If no named attribute file names were provided on the command line
1136*5331Samw 	 * then set the attributes of the base file, otherwise, set the
1137*5331Samw 	 * attributes for each of the named attribute files specified.
1138*5331Samw 	 */
1139*5331Samw 	if (attrnames == NULL) {
1140*5331Samw 		error = set_named_attrs(file, parentfd, NULL, attr_nvlist);
1141*5331Samw 		free(tpath);
1142*5331Samw 	} else {
1143*5331Samw 		while (tattr != NULL) {
1144*5331Samw 			if (set_named_attrs(file, parentfd, tattr->name,
1145*5331Samw 			    attr_nvlist) != 0) {
1146*5331Samw 				error++;
1147*5331Samw 			}
1148*5331Samw 			tattr = tattr->next;
1149*5331Samw 		}
1150*5331Samw 	}
1151*5331Samw 	(void) close(parentfd);
1152*5331Samw 	rest_cwd(cwd);
1153*5331Samw 
1154*5331Samw 	return ((error == 0) ? 0 : 1);
1155*5331Samw }
1156*5331Samw 
1157*5331Samw /*
1158*5331Samw  * Prints the attributes in either the compact or verbose form indicated
1159*5331Samw  * by flag.
1160*5331Samw  */
1161*5331Samw static void
1162*5331Samw print_attrs(int flag)
1163*5331Samw {
1164*5331Samw 	f_attr_t	i;
1165*5331Samw 	static int	numofattrs;
1166*5331Samw 	int		firsttime = 1;
1167*5331Samw 
1168*5331Samw 	numofattrs = attr_count();
1169*5331Samw 
1170*5331Samw 	(void) fprintf(stderr, gettext("\t["));
1171*5331Samw 	for (i = 0; i < numofattrs; i++) {
1172*5331Samw 		if ((attr_to_xattr_view(i) != XATTR_VIEW_READWRITE) ||
1173*5331Samw 		    (attr_to_data_type(i) != DATA_TYPE_BOOLEAN_VALUE)) {
1174*5331Samw 			continue;
1175*5331Samw 		}
1176*5331Samw 		(void) fprintf(stderr, "%s%s",
1177*5331Samw 		    (firsttime == 1) ? "" : gettext("|"),
1178*5331Samw 		    (flag == ATTR_OPTS) ? attr_to_option(i) : attr_to_name(i));
1179*5331Samw 		firsttime = 0;
1180*5331Samw 	}
1181*5331Samw 	(void) fprintf(stderr, gettext("]\n"));
1182*5331Samw }
1183*5331Samw 
1184*5331Samw /*
1185*5331Samw  * Record what action should be taken on the specified attribute. Only boolean
1186*5331Samw  * read-write attributes can be manipulated.
1187*5331Samw  *
1188*5331Samw  * Returns 0 if successful, otherwise returns 1.
1189*5331Samw  */
1190*5331Samw static int
1191*5331Samw set_attr_args(f_attr_t attr, char action, char *attractptr)
1192*5331Samw {
1193*5331Samw 	if ((attr_to_xattr_view(attr) == XATTR_VIEW_READWRITE) &&
1194*5331Samw 	    (attr_to_data_type(attr) == DATA_TYPE_BOOLEAN_VALUE)) {
1195*5331Samw 		attractptr[attr] = action;
1196*5331Samw 		return (0);
1197*5331Samw 	}
1198*5331Samw 	return (1);
1199*5331Samw }
1200*5331Samw 
1201*5331Samw /*
1202*5331Samw  * Parses the entry and assigns the appropriate action (either '+' or '-' in
1203*5331Samw  * attribute's position in the character array pointed to by attractptr, where
1204*5331Samw  * upon exit, attractptr is positional and the value of each character specifies
1205*5331Samw  * whether to set (a '+'), clear (a '-'), or leave untouched (a '\0') the
1206*5331Samw  * attribute value.
1207*5331Samw  *
1208*5331Samw  * If the entry is an attribute name, then the A_SET_OP action is to be
1209*5331Samw  * performed for this attribute.  If the entry is an attribute name proceeded
1210*5331Samw  * with "no", then the A_INVERSE_OP action is to be performed for this
1211*5331Samw  * attribute.  If the entry is one or more attribute option letters, then step
1212*5331Samw  * through each of the option letters marking the action to be performed for
1213*5331Samw  * each of the attributes associated with the letter as A_SET_OP.
1214*5331Samw  *
1215*5331Samw  * Returns 0 if the entry was a valid attribute(s) and the action to be
1216*5331Samw  * performed on that attribute(s) has been recorded, otherwise returns 1.
1217*5331Samw  */
1218*5331Samw static int
1219*5331Samw parse_entry(char *entry, char action, char atype, int len, char *attractptr)
1220*5331Samw {
1221*5331Samw 	char		aopt[2] = {'\0', '\0'};
1222*5331Samw 	char		*aptr;
1223*5331Samw 	f_attr_t	attr;
1224*5331Samw 
1225*5331Samw 	if (atype == A_VERBOSE_TYPE) {
1226*5331Samw 		if ((attr = name_to_attr(entry)) != F_ATTR_INVAL) {
1227*5331Samw 			return (set_attr_args(attr,
1228*5331Samw 			    (action == A_REPLACE_OP) ? A_SET_OP : action,
1229*5331Samw 			    attractptr));
1230*5331Samw 		} else if ((len > 2) && (strncmp(entry, "no", 2) == 0) &&
1231*5331Samw 		    ((attr = name_to_attr(entry + 2)) != F_ATTR_INVAL)) {
1232*5331Samw 			return (set_attr_args(attr, ((action == A_REPLACE_OP) ||
1233*5331Samw 			    (action == A_SET_OP)) ? A_INVERSE_OP : A_SET_OP,
1234*5331Samw 			    attractptr));
1235*5331Samw 		} else {
1236*5331Samw 			return (1);
1237*5331Samw 		}
1238*5331Samw 	} else if (atype == A_COMPACT_TYPE) {
1239*5331Samw 		for (aptr = entry; *aptr != '\0'; aptr++) {
1240*5331Samw 			*aopt = *aptr;
1241*5331Samw 			/*
1242*5331Samw 			 * The output of 'ls' can be used as the attribute mode
1243*5331Samw 			 * specification for chmod.  This output can contain a
1244*5331Samw 			 * hypen ('-') for each attribute that is not set.  If
1245*5331Samw 			 * so, ignore them.  If a replace action is being
1246*5331Samw 			 * performed, then all attributes that don't have an
1247*5331Samw 			 * action set here, will be cleared down the line.
1248*5331Samw 			 */
1249*5331Samw 			if (*aptr == '-') {
1250*5331Samw 				continue;
1251*5331Samw 			}
1252*5331Samw 			if (set_attr_args(option_to_attr(aopt),
1253*5331Samw 			    (action == A_REPLACE_OP) ? A_SET_OP : action,
1254*5331Samw 			    attractptr) != 0) {
1255*5331Samw 				return (1);
1256*5331Samw 			}
1257*5331Samw 		}
1258*5331Samw 		return (0);
1259*5331Samw 	}
1260*5331Samw 	return (1);
1261*5331Samw }
1262*5331Samw 
1263*5331Samw /*
1264*5331Samw  * Parse the attribute specification, aoptsstr.  Upon completion, attr_nvlist
1265*5331Samw  * will point to an nvlist which contains pairs of attribute names and values
1266*5331Samw  * to be set; attr_nvlist will be NULL if it is a no-op.
1267*5331Samw  *
1268*5331Samw  * The attribute specification format is
1269*5331Samw  *	S[oper]attr_type[attribute_list]
1270*5331Samw  * where oper is
1271*5331Samw  *	+	set operation of specified attributes in attribute list.
1272*5331Samw  *		This is the default operation.
1273*5331Samw  *	-	inverse operation of specified attributes in attribute list
1274*5331Samw  *	=	replace operation of all attributes.  All attribute operations
1275*5331Samw  *		depend on those specified in the attribute list.  Attributes
1276*5331Samw  *		not specified in the attribute list will be cleared.
1277*5331Samw  * where attr_type is
1278*5331Samw  *	c	compact type.  Each entry in the attribute list is a character
1279*5331Samw  *		option representing an associated attribute name.
1280*5331Samw  *	v	verbose type.  Each entry in the attribute list is an
1281*5331Samw  *		an attribute name which can optionally be preceeded with "no"
1282*5331Samw  *		(to imply the attribute should be cleared).
1283*5331Samw  *	a	all attributes type.  The oper should be applied to all
1284*5331Samw  *		read-write boolean system attributes.  No attribute list should
1285*5331Samw  *		be specified after an 'a' attribute type.
1286*5331Samw  *
1287*5331Samw  * Returns 0 if aoptsstr contained a valid attribute specification,
1288*5331Samw  * otherwise, returns 1.
1289*5331Samw  */
1290*5331Samw static int
1291*5331Samw parse_attr_args(char *aoptsstr, sec_args_t **sec_args)
1292*5331Samw {
1293*5331Samw 	char		action;
1294*5331Samw 	char		*attractptr;
1295*5331Samw 	char		atype;
1296*5331Samw 	char		*entry;
1297*5331Samw 	char		*eptr;
1298*5331Samw 	char		*nextattr;
1299*5331Samw 	char		*nextentry;
1300*5331Samw 	char		*subentry;
1301*5331Samw 	char		*teptr;
1302*5331Samw 	char		tok[] = {'\0', '\0'};
1303*5331Samw 	int		len;
1304*5331Samw 	f_attr_t	i;
1305*5331Samw 	int		numofattrs;
1306*5331Samw 
1307*5331Samw 	if ((*aoptsstr != 'S') || (*(aoptsstr + 1) == '\0')) {
1308*5331Samw 		return (1);
1309*5331Samw 	}
1310*5331Samw 
1311*5331Samw 	if ((eptr = strdup(aoptsstr + 1)) == NULL) {
1312*5331Samw 		perror("chmod");
1313*5331Samw 		exit(2);
1314*5331Samw 	}
1315*5331Samw 	entry = eptr;
1316*5331Samw 
1317*5331Samw 	/*
1318*5331Samw 	 * Create a positional character array to determine a single attribute
1319*5331Samw 	 * operation to be performed, where each index represents the system
1320*5331Samw 	 * attribute affected, and it's value in the array represents the action
1321*5331Samw 	 * to be performed, i.e., a value of '+' means to set the attribute, a
1322*5331Samw 	 * value of '-' means to clear the attribute, and a value of '\0' means
1323*5331Samw 	 * to leave the attribute untouched.  Initially, this positional
1324*5331Samw 	 * character array is all '\0's, representing a no-op.
1325*5331Samw 	 */
1326*5331Samw 	if ((numofattrs = attr_count()) < 1) {
1327*5331Samw 		errmsg(1, 1, gettext("system attributes not supported\n"));
1328*5331Samw 	}
1329*5331Samw 
1330*5331Samw 	if ((attractptr = calloc(numofattrs, sizeof (char))) == NULL) {
1331*5331Samw 		perror("chmod");
1332*5331Samw 		exit(2);
1333*5331Samw 	}
1334*5331Samw 
1335*5331Samw 	if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) {
1336*5331Samw 		perror("chmod");
1337*5331Samw 		exit(2);
1338*5331Samw 	}
1339*5331Samw 	(*sec_args)->sec_type = SEC_ATTR;
1340*5331Samw 	(*sec_args)->sec_attrs = NULL;
1341*5331Samw 
1342*5331Samw 	/* Parse each attribute operation within the attribute specification. */
1343*5331Samw 	while ((entry != NULL) && (*entry != '\0')) {
1344*5331Samw 		action = A_SET_OP;
1345*5331Samw 		atype = '\0';
1346*5331Samw 
1347*5331Samw 		/* Get the operator. */
1348*5331Samw 		switch (*entry) {
1349*5331Samw 		case A_SET_OP:
1350*5331Samw 		case A_INVERSE_OP:
1351*5331Samw 		case A_REPLACE_OP:
1352*5331Samw 			action = *entry++;
1353*5331Samw 			break;
1354*5331Samw 		case A_COMPACT_TYPE:
1355*5331Samw 		case A_VERBOSE_TYPE:
1356*5331Samw 		case A_ALLATTRS_TYPE:
1357*5331Samw 			atype = *entry++;
1358*5331Samw 			action = A_SET_OP;
1359*5331Samw 			break;
1360*5331Samw 		default:
1361*5331Samw 			break;
1362*5331Samw 		}
1363*5331Samw 
1364*5331Samw 		/* An attribute type must be specified. */
1365*5331Samw 		if (atype == '\0') {
1366*5331Samw 			if ((*entry == A_COMPACT_TYPE) ||
1367*5331Samw 			    (*entry == A_VERBOSE_TYPE) ||
1368*5331Samw 			    (*entry == A_ALLATTRS_TYPE)) {
1369*5331Samw 				atype = *entry++;
1370*5331Samw 			} else {
1371*5331Samw 				return (1);
1372*5331Samw 			}
1373*5331Samw 		}
1374*5331Samw 
1375*5331Samw 		/* Get the attribute specification separator. */
1376*5331Samw 		if (*entry == LEFTBRACE) {
1377*5331Samw 			*tok = RIGHTBRACE;
1378*5331Samw 			entry++;
1379*5331Samw 		} else {
1380*5331Samw 			*tok = A_SEP;
1381*5331Samw 		}
1382*5331Samw 
1383*5331Samw 		/* Get the attribute operation */
1384*5331Samw 		if ((nextentry = strpbrk(entry, tok)) != NULL) {
1385*5331Samw 			*nextentry = '\0';
1386*5331Samw 			nextentry++;
1387*5331Samw 		}
1388*5331Samw 
1389*5331Samw 		/* Check for a no-op */
1390*5331Samw 		if ((*entry == '\0') && (atype != A_ALLATTRS_TYPE) &&
1391*5331Samw 		    (action != A_REPLACE_OP)) {
1392*5331Samw 			entry = nextentry;
1393*5331Samw 			continue;
1394*5331Samw 		}
1395*5331Samw 
1396*5331Samw 		/*
1397*5331Samw 		 * Step through the attribute operation, setting the
1398*5331Samw 		 * appropriate values for the specified attributes in the
1399*5331Samw 		 * character array, attractptr. A value of '+' will mean the
1400*5331Samw 		 * attribute is to be set, and a value of '-' will mean the
1401*5331Samw 		 * attribute is to be cleared.  If the value of an attribute
1402*5331Samw 		 * remains '\0', then no action is to be taken on that
1403*5331Samw 		 * attribute.  As multiple operations specified are
1404*5331Samw 		 * accumulated, a single attribute setting operation is
1405*5331Samw 		 * represented in attractptr.
1406*5331Samw 		 */
1407*5331Samw 		len = strlen(entry);
1408*5331Samw 		if ((*tok == RIGHTBRACE) || (action == A_REPLACE_OP) ||
1409*5331Samw 		    (atype == A_ALLATTRS_TYPE)) {
1410*5331Samw 
1411*5331Samw 			if ((action == A_REPLACE_OP) ||
1412*5331Samw 			    (atype == A_ALLATTRS_TYPE)) {
1413*5331Samw 				(void) memset(attractptr, '\0', numofattrs);
1414*5331Samw 			}
1415*5331Samw 
1416*5331Samw 			if (len > 0) {
1417*5331Samw 				if ((teptr = strdup(entry)) == NULL) {
1418*5331Samw 					perror("chmod");
1419*5331Samw 					exit(2);
1420*5331Samw 				}
1421*5331Samw 				subentry = teptr;
1422*5331Samw 				while (subentry != NULL) {
1423*5331Samw 					if ((nextattr = strpbrk(subentry,
1424*5331Samw 					    A_SEP_TOK)) != NULL) {
1425*5331Samw 						*nextattr = '\0';
1426*5331Samw 						nextattr++;
1427*5331Samw 					}
1428*5331Samw 					if (parse_entry(subentry, action,
1429*5331Samw 					    atype, len, attractptr) != 0) {
1430*5331Samw 						return (1);
1431*5331Samw 					}
1432*5331Samw 					subentry = nextattr;
1433*5331Samw 				}
1434*5331Samw 				free(teptr);
1435*5331Samw 			}
1436*5331Samw 
1437*5331Samw 			/*
1438*5331Samw 			 * If performing the replace action, record the
1439*5331Samw 			 * attributes and values for the rest of the
1440*5331Samw 			 * attributes that have not already been recorded,
1441*5331Samw 			 * otherwise record the specified action for all
1442*5331Samw 			 * attributes.  Note: set_attr_args() will only record
1443*5331Samw 			 * the attribute and action if it is a boolean
1444*5331Samw 			 * read-write attribute so we don't need to worry
1445*5331Samw 			 * about checking it here.
1446*5331Samw 			 */
1447*5331Samw 			if ((action == A_REPLACE_OP) ||
1448*5331Samw 			    (atype == A_ALLATTRS_TYPE)) {
1449*5331Samw 				for (i = 0; i < numofattrs; i++) {
1450*5331Samw 					if (attractptr[i] == A_UNDEF_OP) {
1451*5331Samw 						(void) set_attr_args(i,
1452*5331Samw 						    (action == A_SET_OP) ?
1453*5331Samw 						    A_SET_OP : A_INVERSE_OP,
1454*5331Samw 						    attractptr);
1455*5331Samw 					}
1456*5331Samw 				}
1457*5331Samw 			}
1458*5331Samw 
1459*5331Samw 		} else {
1460*5331Samw 			if (parse_entry(entry, action, atype, len,
1461*5331Samw 			    attractptr) != 0) {
1462*5331Samw 				return (1);
1463*5331Samw 			}
1464*5331Samw 		}
1465*5331Samw 		entry = nextentry;
1466*5331Samw 	}
1467*5331Samw 
1468*5331Samw 	/*
1469*5331Samw 	 * Populate an nvlist with attribute name and boolean value pairs
1470*5331Samw 	 * using the single attribute operation.
1471*5331Samw 	 */
1472*5331Samw 	(*sec_args)->sec_attrs = set_attrs_nvlist(attractptr, numofattrs);
1473*5331Samw 	free(attractptr);
1474*5331Samw 	free(eptr);
1475*5331Samw 
1476*5331Samw 	return (0);
1477*5331Samw }
1478