xref: /onnv-gate/usr/src/cmd/chmod/chmod.c (revision 3520)
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 /*
22*3520Sas145665  * 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>
470Sstevel@tonic-gate  *	option is -R and -f
480Sstevel@tonic-gate  */
490Sstevel@tonic-gate 
500Sstevel@tonic-gate /*
510Sstevel@tonic-gate  *  Note that many convolutions are necessary
520Sstevel@tonic-gate  *  due to the re-use of bits between locking
530Sstevel@tonic-gate  *  and setgid
540Sstevel@tonic-gate  */
550Sstevel@tonic-gate 
560Sstevel@tonic-gate #include <unistd.h>
570Sstevel@tonic-gate #include <stdlib.h>
580Sstevel@tonic-gate #include <stdio.h>
590Sstevel@tonic-gate #include <sys/types.h>
600Sstevel@tonic-gate #include <sys/stat.h>
610Sstevel@tonic-gate #include <dirent.h>
620Sstevel@tonic-gate #include <locale.h>
630Sstevel@tonic-gate #include <string.h>	/* strerror() */
640Sstevel@tonic-gate #include <stdarg.h>
650Sstevel@tonic-gate #include <limits.h>
66789Sahrens #include <ctype.h>
670Sstevel@tonic-gate #include <errno.h>
680Sstevel@tonic-gate #include <sys/acl.h>
69789Sahrens #include <aclutils.h>
700Sstevel@tonic-gate 
710Sstevel@tonic-gate static int	rflag;
720Sstevel@tonic-gate static int	fflag;
730Sstevel@tonic-gate 
740Sstevel@tonic-gate extern int	optind;
750Sstevel@tonic-gate extern int	errno;
760Sstevel@tonic-gate 
770Sstevel@tonic-gate static int	mac;		/* Alternate to argc (for parseargs) */
780Sstevel@tonic-gate static char	**mav;		/* Alternate to argv (for parseargs) */
790Sstevel@tonic-gate 
800Sstevel@tonic-gate static char	*ms;		/* Points to the mode argument */
810Sstevel@tonic-gate 
82789Sahrens #define	ACL_ADD		1
83789Sahrens #define	ACL_DELETE	2
84789Sahrens #define	ACL_SLOT_DELETE 3
85789Sahrens #define	ACL_REPLACE	4
86789Sahrens #define	ACL_STRIP	5
87789Sahrens 
88789Sahrens typedef struct acl_args {
89789Sahrens 	acl_t	*acl_aclp;
90789Sahrens 	int	acl_slot;
91789Sahrens 	int	acl_action;
92789Sahrens } acl_args_t;
93789Sahrens 
940Sstevel@tonic-gate extern mode_t
950Sstevel@tonic-gate newmode_common(char *ms, mode_t new_mode, mode_t umsk, char *file, char *path,
960Sstevel@tonic-gate 	o_mode_t *group_clear_bits, o_mode_t *group_set_bits);
970Sstevel@tonic-gate 
980Sstevel@tonic-gate static int
99789Sahrens dochmod(char *name, char *path, mode_t umsk, acl_args_t *aclp),
100789Sahrens chmodr(char *dir, char *path, mode_t mode, mode_t umsk, acl_args_t *aclp);
101789Sahrens static int doacl(char *file, struct stat *st, acl_args_t *aclp);
1020Sstevel@tonic-gate 
1030Sstevel@tonic-gate static void handle_acl(char *name, o_mode_t group_clear_bits,
104789Sahrens     o_mode_t group_set_bits);
1050Sstevel@tonic-gate 
106789Sahrens static void usage(void);
1070Sstevel@tonic-gate 
108789Sahrens void errmsg(int severity, int code, char *format, ...);
1090Sstevel@tonic-gate 
110789Sahrens static void parseargs(int ac, char *av[]);
111789Sahrens 
112789Sahrens int
113789Sahrens parse_acl_args(char *arg, acl_args_t **acl_args);
1140Sstevel@tonic-gate 
1150Sstevel@tonic-gate int
1160Sstevel@tonic-gate main(int argc, char *argv[])
1170Sstevel@tonic-gate {
1180Sstevel@tonic-gate 	int i, c;
1190Sstevel@tonic-gate 	int status = 0;
1200Sstevel@tonic-gate 	mode_t umsk;
121789Sahrens 	acl_args_t *acl_args = NULL;
1220Sstevel@tonic-gate 
1230Sstevel@tonic-gate 	(void) setlocale(LC_ALL, "");
1240Sstevel@tonic-gate #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
1250Sstevel@tonic-gate #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
1260Sstevel@tonic-gate #endif
1270Sstevel@tonic-gate 	(void) textdomain(TEXT_DOMAIN);
1280Sstevel@tonic-gate 
1290Sstevel@tonic-gate 	parseargs(argc, argv);
1300Sstevel@tonic-gate 
1310Sstevel@tonic-gate 	while ((c = getopt(mac, mav, "Rf")) != EOF) {
1320Sstevel@tonic-gate 		switch (c) {
1330Sstevel@tonic-gate 		case 'R':
1340Sstevel@tonic-gate 			rflag++;
1350Sstevel@tonic-gate 			break;
1360Sstevel@tonic-gate 		case 'f':
1370Sstevel@tonic-gate 			fflag++;
1380Sstevel@tonic-gate 			break;
1390Sstevel@tonic-gate 		case '?':
1400Sstevel@tonic-gate 			usage();
1410Sstevel@tonic-gate 			exit(2);
1420Sstevel@tonic-gate 		}
1430Sstevel@tonic-gate 	}
1440Sstevel@tonic-gate 
1450Sstevel@tonic-gate 	/*
1460Sstevel@tonic-gate 	 * Check for sufficient arguments
1470Sstevel@tonic-gate 	 * or a usage error.
1480Sstevel@tonic-gate 	 */
1490Sstevel@tonic-gate 
1500Sstevel@tonic-gate 	mac -= optind;
1510Sstevel@tonic-gate 	mav += optind;
152789Sahrens 	if (mac >= 2 && (mav[0][0] == 'A')) {
153789Sahrens 		if (parse_acl_args(*mav, &acl_args)) {
154789Sahrens 			usage();
155789Sahrens 			exit(2);
156789Sahrens 		}
157789Sahrens 	} else {
158789Sahrens 		if (mac < 2) {
159789Sahrens 			usage();
160789Sahrens 			exit(2);
161789Sahrens 		}
1620Sstevel@tonic-gate 	}
1630Sstevel@tonic-gate 
1640Sstevel@tonic-gate 	ms = mav[0];
1650Sstevel@tonic-gate 
1660Sstevel@tonic-gate 	umsk = umask(0);
1670Sstevel@tonic-gate 	(void) umask(umsk);
1680Sstevel@tonic-gate 
169789Sahrens 	for (i = 1; i < mac; i++) {
170789Sahrens 		status += dochmod(mav[i], mav[i], umsk, acl_args);
171789Sahrens 	}
1720Sstevel@tonic-gate 
1730Sstevel@tonic-gate 	return (fflag ? 0 : status);
1740Sstevel@tonic-gate }
1750Sstevel@tonic-gate 
1760Sstevel@tonic-gate static int
177789Sahrens dochmod(char *name, char *path, mode_t umsk, acl_args_t *aclp)
1780Sstevel@tonic-gate {
1790Sstevel@tonic-gate 	static struct stat st;
1800Sstevel@tonic-gate 	int linkflg = 0;
1810Sstevel@tonic-gate 	o_mode_t	group_clear_bits, group_set_bits;
1820Sstevel@tonic-gate 
1830Sstevel@tonic-gate 	if (lstat(name, &st) < 0) {
1840Sstevel@tonic-gate 		errmsg(2, 0, gettext("can't access %s\n"), path);
1850Sstevel@tonic-gate 		return (1);
1860Sstevel@tonic-gate 	}
1870Sstevel@tonic-gate 
1880Sstevel@tonic-gate 	if ((st.st_mode & S_IFMT) == S_IFLNK) {
1890Sstevel@tonic-gate 		linkflg = 1;
1900Sstevel@tonic-gate 		if (stat(name, &st) < 0) {
1910Sstevel@tonic-gate 			errmsg(2, 0, gettext("can't access %s\n"), path);
1920Sstevel@tonic-gate 			return (1);
1930Sstevel@tonic-gate 		}
1940Sstevel@tonic-gate 	}
1950Sstevel@tonic-gate 
1960Sstevel@tonic-gate 	/* Do not recurse if directory is object of symbolic link */
1970Sstevel@tonic-gate 	if (rflag && ((st.st_mode & S_IFMT) == S_IFDIR) && !linkflg)
198789Sahrens 		return (chmodr(name, path, st.st_mode, umsk, aclp));
1990Sstevel@tonic-gate 
200789Sahrens 	if (aclp) {
201789Sahrens 		return (doacl(name, &st, aclp));
202789Sahrens 	} else if (chmod(name, newmode_common(ms, st.st_mode, umsk, name, path,
2030Sstevel@tonic-gate 	    &group_clear_bits, &group_set_bits)) == -1) {
2040Sstevel@tonic-gate 		errmsg(2, 0, gettext("can't change %s\n"), path);
2050Sstevel@tonic-gate 		return (1);
2060Sstevel@tonic-gate 	}
2070Sstevel@tonic-gate 
2080Sstevel@tonic-gate 	/*
2090Sstevel@tonic-gate 	 * If the group permissions of the file are being modified,
2100Sstevel@tonic-gate 	 * make sure that the file's ACL (if it has one) is
2110Sstevel@tonic-gate 	 * modified also, since chmod is supposed to apply group
2120Sstevel@tonic-gate 	 * permissions changes to both the acl mask and the
2130Sstevel@tonic-gate 	 * general group permissions.
2140Sstevel@tonic-gate 	 */
2150Sstevel@tonic-gate 	if (group_clear_bits || group_set_bits)
2160Sstevel@tonic-gate 		handle_acl(name, group_clear_bits, group_set_bits);
2170Sstevel@tonic-gate 
2180Sstevel@tonic-gate 	return (0);
2190Sstevel@tonic-gate }
2200Sstevel@tonic-gate 
2210Sstevel@tonic-gate 
2220Sstevel@tonic-gate static int
223789Sahrens chmodr(char *dir, char *path,  mode_t mode, mode_t umsk, acl_args_t *aclp)
2240Sstevel@tonic-gate {
2250Sstevel@tonic-gate 
2260Sstevel@tonic-gate 	DIR *dirp;
2270Sstevel@tonic-gate 	struct dirent *dp;
2280Sstevel@tonic-gate 	char savedir[PATH_MAX];			/* dir name to restore */
2290Sstevel@tonic-gate 	char currdir[PATH_MAX+1];		/* current dir name + '/' */
2300Sstevel@tonic-gate 	char parentdir[PATH_MAX+1];		/* parent dir name  + '/' */
2310Sstevel@tonic-gate 	int ecode;
232789Sahrens 	struct stat st;
2330Sstevel@tonic-gate 	o_mode_t	group_clear_bits, group_set_bits;
2340Sstevel@tonic-gate 
2350Sstevel@tonic-gate 	if (getcwd(savedir, PATH_MAX) == 0)
2360Sstevel@tonic-gate 		errmsg(2, 255, gettext("chmod: could not getcwd %s\n"),
2370Sstevel@tonic-gate 		    savedir);
2380Sstevel@tonic-gate 
2390Sstevel@tonic-gate 	/*
2400Sstevel@tonic-gate 	 * Change what we are given before doing it's contents
2410Sstevel@tonic-gate 	 */
242789Sahrens 	if (aclp) {
243789Sahrens 		if (lstat(dir, &st) < 0) {
244789Sahrens 			errmsg(2, 0, gettext("can't access %s\n"), path);
245789Sahrens 			return (1);
246789Sahrens 		}
247789Sahrens 		if (doacl(dir, &st, aclp) != 0)
248789Sahrens 			return (1);
249789Sahrens 	} else if (chmod(dir, newmode_common(ms, mode, umsk, dir, path,
2500Sstevel@tonic-gate 	    &group_clear_bits, &group_set_bits)) < 0) {
2510Sstevel@tonic-gate 		errmsg(2, 0, gettext("can't change %s\n"), path);
2520Sstevel@tonic-gate 		return (1);
2530Sstevel@tonic-gate 	}
2540Sstevel@tonic-gate 
2550Sstevel@tonic-gate 	/*
2560Sstevel@tonic-gate 	 * If the group permissions of the file are being modified,
2570Sstevel@tonic-gate 	 * make sure that the file's ACL (if it has one) is
2580Sstevel@tonic-gate 	 * modified also, since chmod is supposed to apply group
2590Sstevel@tonic-gate 	 * permissions changes to both the acl mask and the
2600Sstevel@tonic-gate 	 * general group permissions.
2610Sstevel@tonic-gate 	 */
262789Sahrens 
263789Sahrens 	if (aclp == NULL) { /* only necessary when not setting ACL */
264789Sahrens 		if (group_clear_bits || group_set_bits)
265789Sahrens 			handle_acl(dir, group_clear_bits, group_set_bits);
266789Sahrens 	}
2670Sstevel@tonic-gate 
2680Sstevel@tonic-gate 	if (chdir(dir) < 0) {
2690Sstevel@tonic-gate 		errmsg(2, 0, "%s/%s: %s\n", savedir, dir, strerror(errno));
2700Sstevel@tonic-gate 		return (1);
2710Sstevel@tonic-gate 	}
2720Sstevel@tonic-gate 	if ((dirp = opendir(".")) == NULL) {
2730Sstevel@tonic-gate 		errmsg(2, 0, "%s\n", strerror(errno));
2740Sstevel@tonic-gate 		return (1);
2750Sstevel@tonic-gate 	}
2760Sstevel@tonic-gate 	ecode = 0;
2770Sstevel@tonic-gate 
2780Sstevel@tonic-gate 	/*
2790Sstevel@tonic-gate 	 * Save parent directory path before recursive chmod.
2800Sstevel@tonic-gate 	 * We'll need this for error printing purposes. Add
2810Sstevel@tonic-gate 	 * a trailing '/' to the path except in the case where
2820Sstevel@tonic-gate 	 * the path is just '/'
2830Sstevel@tonic-gate 	 */
2840Sstevel@tonic-gate 
285*3520Sas145665 	if (strlcpy(parentdir, path, PATH_MAX + 1) >= PATH_MAX + 1) {
286*3520Sas145665 		errmsg(2, 0, gettext("directory path name too long: %s\n"),
287*3520Sas145665 		    path);
288*3520Sas145665 		return (1);
289*3520Sas145665 	}
2900Sstevel@tonic-gate 	if (strcmp(path, "/") != 0)
291*3520Sas145665 		if (strlcat(parentdir, "/", PATH_MAX + 1) >= PATH_MAX + 1) {
292*3520Sas145665 			errmsg(2, 0,
293*3520Sas145665 			    gettext("directory path name too long: %s/\n"),
294*3520Sas145665 			    parentdir);
295*3520Sas145665 			return (1);
296*3520Sas145665 		}
297*3520Sas145665 
2980Sstevel@tonic-gate 
2990Sstevel@tonic-gate 	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp))  {
300*3520Sas145665 
3011591Scasper 		if (strcmp(dp->d_name, ".") == 0 ||	/* skip . and .. */
3021591Scasper 		    strcmp(dp->d_name, "..") == 0) {
3031591Scasper 			continue;
3041591Scasper 		}
305*3520Sas145665 		if (strlcpy(currdir, parentdir, PATH_MAX + 1) >= PATH_MAX + 1) {
306*3520Sas145665 			errmsg(2, 0,
307*3520Sas145665 			    gettext("directory path name too long: %s\n"),
308*3520Sas145665 			    parentdir);
309*3520Sas145665 			return (1);
310*3520Sas145665 		}
311*3520Sas145665 		if (strlcat(currdir, dp->d_name, PATH_MAX + 1)
312*3520Sas145665 		    >= PATH_MAX + 1) {
313*3520Sas145665 			errmsg(2, 0,
314*3520Sas145665 			    gettext("directory path name too long: %s%s\n"),
315*3520Sas145665 			    currdir, dp->d_name);
316*3520Sas145665 			return (1);
317*3520Sas145665 		}
318789Sahrens 		ecode += dochmod(dp->d_name, currdir, umsk, aclp);
3190Sstevel@tonic-gate 	}
3200Sstevel@tonic-gate 	(void) closedir(dirp);
3210Sstevel@tonic-gate 	if (chdir(savedir) < 0) {
3220Sstevel@tonic-gate 		errmsg(2, 255, gettext("can't change back to %s\n"), savedir);
3230Sstevel@tonic-gate 	}
3240Sstevel@tonic-gate 	return (ecode ? 1 : 0);
3250Sstevel@tonic-gate }
3260Sstevel@tonic-gate 
3270Sstevel@tonic-gate /* PRINTFLIKE3 */
3280Sstevel@tonic-gate void
3290Sstevel@tonic-gate errmsg(int severity, int code, char *format, ...)
3300Sstevel@tonic-gate {
3310Sstevel@tonic-gate 	va_list ap;
3320Sstevel@tonic-gate 	static char *msg[] = {
3330Sstevel@tonic-gate 	"",
3340Sstevel@tonic-gate 	"ERROR",
3350Sstevel@tonic-gate 	"WARNING",
3360Sstevel@tonic-gate 	""
3370Sstevel@tonic-gate 	};
3380Sstevel@tonic-gate 
3390Sstevel@tonic-gate 	va_start(ap, format);
3400Sstevel@tonic-gate 
3410Sstevel@tonic-gate 	/*
3420Sstevel@tonic-gate 	 * Always print error message if this is a fatal error (code == 0);
3430Sstevel@tonic-gate 	 * otherwise, print message if fflag == 0 (no -f option specified)
3440Sstevel@tonic-gate 	 */
3450Sstevel@tonic-gate 	if (!fflag || (code != 0)) {
3460Sstevel@tonic-gate 		(void) fprintf(stderr,
3470Sstevel@tonic-gate 			"chmod: %s: ", gettext(msg[severity]));
3480Sstevel@tonic-gate 		(void) vfprintf(stderr, format, ap);
3490Sstevel@tonic-gate 	}
3500Sstevel@tonic-gate 
3510Sstevel@tonic-gate 	va_end(ap);
3520Sstevel@tonic-gate 
3530Sstevel@tonic-gate 	if (code != 0)
3540Sstevel@tonic-gate 		exit(fflag ? 0 : code);
3550Sstevel@tonic-gate }
3560Sstevel@tonic-gate 
3570Sstevel@tonic-gate static void
3580Sstevel@tonic-gate usage(void)
3590Sstevel@tonic-gate {
3600Sstevel@tonic-gate 	(void) fprintf(stderr, gettext(
3610Sstevel@tonic-gate 	    "usage:\tchmod [-fR] <absolute-mode> file ...\n"));
3620Sstevel@tonic-gate 
3630Sstevel@tonic-gate 	(void) fprintf(stderr, gettext(
364789Sahrens 	    "\tchmod [-fR] <ACL-operation> file ...\n"));
365789Sahrens 
366789Sahrens 	(void) fprintf(stderr, gettext(
3670Sstevel@tonic-gate 	    "\tchmod [-fR] <symbolic-mode-list> file ...\n"));
3680Sstevel@tonic-gate 
369789Sahrens 
3700Sstevel@tonic-gate 	(void) fprintf(stderr, gettext(
3710Sstevel@tonic-gate 	    "where \t<symbolic-mode-list> is a comma-separated list of\n"));
3720Sstevel@tonic-gate 
3730Sstevel@tonic-gate 	(void) fprintf(stderr, gettext(
3740Sstevel@tonic-gate 	    "\t[ugoa]{+|-|=}[rwxXlstugo]\n"));
375789Sahrens 
376789Sahrens 	(void) fprintf(stderr, gettext(
377789Sahrens 	    "where \t<ACL-operation> is one of the following\n"));
378789Sahrens 	(void) fprintf(stderr, gettext("\tA-<acl_specification>\n"));
379789Sahrens 	(void) fprintf(stderr, gettext("\tA[number]-\n"));
380789Sahrens 	(void) fprintf(stderr, gettext(
381789Sahrens 	    "\tA[number]{+|=}<acl_specification>\n"));
382789Sahrens 	(void) fprintf(stderr, gettext(
383789Sahrens 	    "where \t<acl-specification> is a comma-separated list of ACEs\n"));
3840Sstevel@tonic-gate }
3850Sstevel@tonic-gate 
3860Sstevel@tonic-gate /*
3870Sstevel@tonic-gate  *  parseargs - generate getopt-friendly argument list for backwards
3880Sstevel@tonic-gate  *		compatibility with earlier Solaris usage (eg, chmod -w
3890Sstevel@tonic-gate  *		foo).
3900Sstevel@tonic-gate  *
3910Sstevel@tonic-gate  *  assumes the existence of a static set of alternates to argc and argv,
3920Sstevel@tonic-gate  *  (namely, mac, and mav[]).
3930Sstevel@tonic-gate  *
3940Sstevel@tonic-gate  */
3950Sstevel@tonic-gate 
3960Sstevel@tonic-gate static void
3970Sstevel@tonic-gate parseargs(int ac, char *av[])
3980Sstevel@tonic-gate {
3990Sstevel@tonic-gate 	int i;			/* current argument			*/
4000Sstevel@tonic-gate 	int fflag;		/* arg list contains "--"		*/
4010Sstevel@tonic-gate 	size_t mav_num;		/* number of entries in mav[]		*/
4020Sstevel@tonic-gate 
4030Sstevel@tonic-gate 	/*
4040Sstevel@tonic-gate 	 * We add an extra argument slot, in case we need to jam a "--"
4050Sstevel@tonic-gate 	 * argument into the list.
4060Sstevel@tonic-gate 	 */
4070Sstevel@tonic-gate 
4080Sstevel@tonic-gate 	mav_num = (size_t)ac+2;
4090Sstevel@tonic-gate 
4100Sstevel@tonic-gate 	if ((mav = calloc(mav_num, sizeof (char *))) == NULL) {
4110Sstevel@tonic-gate 		perror("chmod");
4120Sstevel@tonic-gate 		exit(2);
4130Sstevel@tonic-gate 	}
4140Sstevel@tonic-gate 
4150Sstevel@tonic-gate 	/* scan for the use of "--" in the argument list */
4160Sstevel@tonic-gate 
4170Sstevel@tonic-gate 	for (fflag = i = 0; i < ac; i ++) {
4180Sstevel@tonic-gate 		if (strcmp(av[i], "--") == 0)
4190Sstevel@tonic-gate 		    fflag = 1;
4200Sstevel@tonic-gate 	}
4210Sstevel@tonic-gate 
4220Sstevel@tonic-gate 	/* process the arguments */
4230Sstevel@tonic-gate 
4240Sstevel@tonic-gate 	for (i = mac = 0;
4250Sstevel@tonic-gate 	    (av[i] != (char *)NULL) && (av[i][0] != (char)NULL);
4260Sstevel@tonic-gate 	    i++) {
4270Sstevel@tonic-gate 		if (!fflag && av[i][0] == '-') {
4280Sstevel@tonic-gate 			/*
4290Sstevel@tonic-gate 			 *  If there is not already a "--" argument specified,
4300Sstevel@tonic-gate 			 *  and the argument starts with '-' but does not
4310Sstevel@tonic-gate 			 *  contain any of the official option letters, then it
4320Sstevel@tonic-gate 			 *  is probably a mode argument beginning with '-'.
4330Sstevel@tonic-gate 			 *  Force a "--" into the argument stream in front of
4340Sstevel@tonic-gate 			 *  it.
4350Sstevel@tonic-gate 			 */
4360Sstevel@tonic-gate 
4370Sstevel@tonic-gate 			if ((strchr(av[i], 'R') == NULL &&
4380Sstevel@tonic-gate 			    strchr(av[i], 'f') == NULL)) {
4390Sstevel@tonic-gate 				mav[mac++] = strdup("--");
4400Sstevel@tonic-gate 			}
4410Sstevel@tonic-gate 		}
4420Sstevel@tonic-gate 
4430Sstevel@tonic-gate 		mav[mac++] = strdup(av[i]);
4440Sstevel@tonic-gate 	}
4450Sstevel@tonic-gate 
4460Sstevel@tonic-gate 	mav[mac] = (char *)NULL;
4470Sstevel@tonic-gate }
4480Sstevel@tonic-gate 
449789Sahrens int
450789Sahrens parse_acl_args(char *arg, acl_args_t **acl_args)
451789Sahrens {
452789Sahrens 	acl_t *new_acl = NULL;
453789Sahrens 	int slot;
454789Sahrens 	int len;
455789Sahrens 	int action;
456789Sahrens 	acl_args_t *new_acl_args;
457789Sahrens 	char *acl_spec = NULL;
458789Sahrens 	char *end;
459789Sahrens 
460789Sahrens 	if (arg[0] != 'A')
461789Sahrens 		return (1);
462789Sahrens 
463789Sahrens 	slot = strtol(&arg[1], &end, 10);
464789Sahrens 
465789Sahrens 	len = strlen(arg);
466789Sahrens 	switch (*end) {
467789Sahrens 	case '+':
468789Sahrens 		action = ACL_ADD;
469789Sahrens 		acl_spec = ++end;
470789Sahrens 		break;
471789Sahrens 	case '-':
472789Sahrens 		if (len == 2 && arg[0] == 'A' && arg[1] == '-')
473789Sahrens 			action = ACL_STRIP;
474789Sahrens 		else
475789Sahrens 			action = ACL_DELETE;
476789Sahrens 		if (action != ACL_STRIP) {
477789Sahrens 			acl_spec = ++end;
478789Sahrens 			if (acl_spec[0] == '\0') {
479789Sahrens 				action = ACL_SLOT_DELETE;
480789Sahrens 				acl_spec = NULL;
481789Sahrens 			} else if (arg[1] != '-')
482789Sahrens 				return (1);
483789Sahrens 		}
484789Sahrens 		break;
485789Sahrens 	case '=':
486865Smarks 		/*
487865Smarks 		 * Was slot specified?
488865Smarks 		 */
489865Smarks 		if (arg[1] == '=')
490865Smarks 			slot = -1;
491789Sahrens 		action = ACL_REPLACE;
492789Sahrens 		acl_spec = ++end;
493789Sahrens 		break;
494789Sahrens 	default:
495789Sahrens 		return (1);
496789Sahrens 	}
497789Sahrens 
498789Sahrens 	if ((action == ACL_REPLACE || action == ACL_ADD) && acl_spec[0] == '\0')
499789Sahrens 		return (1);
500789Sahrens 
501789Sahrens 	if (acl_spec) {
5021420Smarks 		if (acl_parse(acl_spec, &new_acl)) {
5031420Smarks 			exit(1);
504789Sahrens 		}
505789Sahrens 	}
506789Sahrens 
507789Sahrens 	new_acl_args = malloc(sizeof (acl_args_t));
508789Sahrens 	if (new_acl_args == NULL)
509789Sahrens 		return (1);
510789Sahrens 
511789Sahrens 	new_acl_args->acl_aclp = new_acl;
512789Sahrens 	new_acl_args->acl_slot = slot;
513789Sahrens 	new_acl_args->acl_action = action;
514789Sahrens 
515789Sahrens 	*acl_args = new_acl_args;
516789Sahrens 
517789Sahrens 	return (0);
518789Sahrens }
519789Sahrens 
5200Sstevel@tonic-gate /*
5210Sstevel@tonic-gate  * This function is called whenever the group permissions of a file
5220Sstevel@tonic-gate  * is being modified.  According to the chmod(1) manpage, any
5230Sstevel@tonic-gate  * change made to the group permissions must be applied to both
5240Sstevel@tonic-gate  * the acl mask and the acl's GROUP_OBJ.  The chmod(2) already
5250Sstevel@tonic-gate  * set the mask, so this routine needs to make the same change
5260Sstevel@tonic-gate  * to the GROUP_OBJ.
5270Sstevel@tonic-gate  */
5280Sstevel@tonic-gate static void
5290Sstevel@tonic-gate handle_acl(char *name, o_mode_t group_clear_bits, o_mode_t group_set_bits)
5300Sstevel@tonic-gate {
5310Sstevel@tonic-gate 	int aclcnt, n;
5320Sstevel@tonic-gate 	aclent_t *aclp, *tp;
5330Sstevel@tonic-gate 	o_mode_t newperm;
534789Sahrens 	/*
535789Sahrens 	 * if this file system support ace_t acl's
536789Sahrens 	 * then simply return since we don't have an
537789Sahrens 	 * acl mask to deal with
538789Sahrens 	 */
539789Sahrens 	if (pathconf(name, _PC_ACL_ENABLED) == _ACL_ACE_ENABLED)
540789Sahrens 		return;
5410Sstevel@tonic-gate 	if ((aclcnt = acl(name, GETACLCNT, 0, NULL)) <= MIN_ACL_ENTRIES)
5420Sstevel@tonic-gate 		return;	/* it's just a trivial acl; no need to change it */
5430Sstevel@tonic-gate 	if ((aclp = (aclent_t *)malloc((sizeof (aclent_t)) * aclcnt))
5440Sstevel@tonic-gate 	    == NULL) {
5450Sstevel@tonic-gate 		perror("chmod");
5460Sstevel@tonic-gate 		exit(2);
5470Sstevel@tonic-gate 	}
5480Sstevel@tonic-gate 
5490Sstevel@tonic-gate 	if (acl(name, GETACL, aclcnt, aclp) < 0) {
5500Sstevel@tonic-gate 		free(aclp);
5510Sstevel@tonic-gate 		(void) fprintf(stderr, "chmod: ");
5520Sstevel@tonic-gate 		perror(name);
5530Sstevel@tonic-gate 		return;
5540Sstevel@tonic-gate 	}
5550Sstevel@tonic-gate 	for (tp = aclp, n = aclcnt; n--; tp++) {
5560Sstevel@tonic-gate 		if (tp->a_type == GROUP_OBJ) {
5570Sstevel@tonic-gate 			newperm = tp->a_perm;
5580Sstevel@tonic-gate 			if (group_clear_bits != 0)
5590Sstevel@tonic-gate 				newperm &= ~group_clear_bits;
5600Sstevel@tonic-gate 			if (group_set_bits != 0)
5610Sstevel@tonic-gate 				newperm |= group_set_bits;
5620Sstevel@tonic-gate 			if (newperm != tp->a_perm) {
5630Sstevel@tonic-gate 				tp->a_perm = newperm;
5640Sstevel@tonic-gate 				if (acl(name, SETACL, aclcnt, aclp)
5650Sstevel@tonic-gate 				    < 0) {
5660Sstevel@tonic-gate 					(void) fprintf(stderr, "chmod: ");
5670Sstevel@tonic-gate 					perror(name);
5680Sstevel@tonic-gate 				}
5690Sstevel@tonic-gate 			}
5700Sstevel@tonic-gate 			break;
5710Sstevel@tonic-gate 		}
5720Sstevel@tonic-gate 	}
5730Sstevel@tonic-gate 	free(aclp);
5740Sstevel@tonic-gate }
575789Sahrens 
576789Sahrens static int
577789Sahrens doacl(char *file, struct stat *st, acl_args_t *acl_args)
578789Sahrens {
579789Sahrens 	acl_t *aclp;
580789Sahrens 	acl_t *set_aclp;
581789Sahrens 	int error = 0;
582789Sahrens 	void *to, *from;
583789Sahrens 	int len;
584789Sahrens 	int isdir;
585789Sahrens 	isdir = S_ISDIR(st->st_mode);
586789Sahrens 
587789Sahrens 	error = acl_get(file, 0, &aclp);
588789Sahrens 
589789Sahrens 	if (error != 0) {
590789Sahrens 		errmsg(1, 1, "%s\n", acl_strerror(error));
591789Sahrens 		return (1);
592789Sahrens 	}
593789Sahrens 	switch (acl_args->acl_action) {
594789Sahrens 	case ACL_ADD:
595789Sahrens 		if ((error = acl_addentries(aclp,
596789Sahrens 			acl_args->acl_aclp, acl_args->acl_slot)) != 0) {
597789Sahrens 				errmsg(1, 1, "%s\n", acl_strerror(error));
598789Sahrens 				acl_free(aclp);
599789Sahrens 				return (1);
600789Sahrens 		}
601789Sahrens 		set_aclp = aclp;
602789Sahrens 		break;
603789Sahrens 	case ACL_SLOT_DELETE:
604789Sahrens 		if (acl_args->acl_slot + 1 > aclp->acl_cnt) {
605789Sahrens 			errmsg(1, 1,
606789Sahrens 			    gettext("Invalid slot specified for removal\n"));
607789Sahrens 			acl_free(aclp);
608789Sahrens 			return (1);
609789Sahrens 		}
610789Sahrens 
611789Sahrens 		if (acl_args->acl_slot == 0 && aclp->acl_cnt == 1) {
612789Sahrens 			errmsg(1, 1,
613789Sahrens 			    gettext("Can't remove all ACL "
614789Sahrens 			    "entries from a file\n"));
615789Sahrens 			acl_free(aclp);
616789Sahrens 			return (1);
617789Sahrens 		}
618789Sahrens 
619789Sahrens 		/*
620789Sahrens 		 * remove a single entry
621789Sahrens 		 *
622789Sahrens 		 * if last entry just adjust acl_cnt
623789Sahrens 		 */
624789Sahrens 
625789Sahrens 		if ((acl_args->acl_slot + 1) == aclp->acl_cnt)
626789Sahrens 			aclp->acl_cnt--;
627789Sahrens 		else {
628789Sahrens 			to = (char *)aclp->acl_aclp +
629789Sahrens 			    (acl_args->acl_slot * aclp->acl_entry_size);
630789Sahrens 			from = (char *)to + aclp->acl_entry_size;
631789Sahrens 			len = (aclp->acl_cnt - acl_args->acl_slot - 1) *
632789Sahrens 			    aclp->acl_entry_size;
633789Sahrens 			(void) memmove(to, from, len);
634789Sahrens 			aclp->acl_cnt--;
635789Sahrens 		}
636789Sahrens 		set_aclp = aclp;
637789Sahrens 		break;
638789Sahrens 
639789Sahrens 	case ACL_DELETE:
640789Sahrens 		if ((error = acl_removeentries(aclp, acl_args->acl_aclp,
641789Sahrens 		    acl_args->acl_slot, ACL_REMOVE_ALL)) != 0) {
642789Sahrens 			errmsg(1, 1, "%s\n", acl_strerror(error));
643789Sahrens 			acl_free(aclp);
644789Sahrens 			return (1);
645789Sahrens 		}
646789Sahrens 
647789Sahrens 		if (aclp->acl_cnt == 0) {
648789Sahrens 			errmsg(1, 1,
649789Sahrens 			    gettext("Can't remove all ACL "
650789Sahrens 			    "entries from a file\n"));
651789Sahrens 			acl_free(aclp);
652789Sahrens 			return (1);
653789Sahrens 		}
654789Sahrens 
655789Sahrens 		set_aclp = aclp;
656789Sahrens 		break;
657789Sahrens 	case ACL_REPLACE:
658789Sahrens 		if (acl_args->acl_slot >= 0)  {
659789Sahrens 			error = acl_modifyentries(aclp, acl_args->acl_aclp,
660789Sahrens 			    acl_args->acl_slot);
661789Sahrens 			if (error) {
662789Sahrens 				errmsg(1, 1, "%s\n", acl_strerror(error));
663789Sahrens 				acl_free(aclp);
664789Sahrens 				return (1);
665789Sahrens 			}
666789Sahrens 			set_aclp = aclp;
667789Sahrens 		} else {
668789Sahrens 			set_aclp = acl_args->acl_aclp;
669789Sahrens 		}
670789Sahrens 		break;
671789Sahrens 	case ACL_STRIP:
672789Sahrens 		error = acl_strip(file, st->st_uid, st->st_gid, st->st_mode);
673789Sahrens 		if (error) {
674789Sahrens 			errmsg(1, 1, "%s\n", acl_strerror(error));
675789Sahrens 			return (1);
676789Sahrens 		}
677789Sahrens 		acl_free(aclp);
678789Sahrens 		return (0);
679789Sahrens 		/*NOTREACHED*/
680789Sahrens 	default:
681789Sahrens 		errmsg(1, 0, gettext("Unknown ACL action requested\n"));
682789Sahrens 		return (1);
683789Sahrens 		break;
684789Sahrens 	}
685789Sahrens 	error = acl_check(set_aclp, isdir);
686789Sahrens 
687789Sahrens 	if (error) {
688789Sahrens 		errmsg(1, 0, "%s\n%s", acl_strerror(error),
689789Sahrens 		    gettext("See chmod(1) for more information on "
690789Sahrens 		    "valid ACL syntax\n"));
691789Sahrens 		return (1);
692789Sahrens 	}
693789Sahrens 	if ((error = acl_set(file, set_aclp)) != 0) {
694789Sahrens 			errmsg(1, 0, gettext("Failed to set ACL: %s\n"),
695789Sahrens 			    acl_strerror(error));
696789Sahrens 			acl_free(aclp);
697789Sahrens 			return (1);
698789Sahrens 	}
699789Sahrens 	acl_free(aclp);
700789Sahrens 	return (0);
701789Sahrens }
702