xref: /titanic_52/usr/src/cmd/chmod/chmod.c (revision 401f278d817f55ca385f31d9e40db6deac6220e8)
17c478bd9Sstevel@tonic-gate /*
27c478bd9Sstevel@tonic-gate  * CDDL HEADER START
37c478bd9Sstevel@tonic-gate  *
47c478bd9Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5c4518760Scasper  * Common Development and Distribution License (the "License").
6c4518760Scasper  * You may not use this file except in compliance with the License.
77c478bd9Sstevel@tonic-gate  *
87c478bd9Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
97c478bd9Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
107c478bd9Sstevel@tonic-gate  * See the License for the specific language governing permissions
117c478bd9Sstevel@tonic-gate  * and limitations under the License.
127c478bd9Sstevel@tonic-gate  *
137c478bd9Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
147c478bd9Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
157c478bd9Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
167c478bd9Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
177c478bd9Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
187c478bd9Sstevel@tonic-gate  *
197c478bd9Sstevel@tonic-gate  * CDDL HEADER END
207c478bd9Sstevel@tonic-gate  */
217c478bd9Sstevel@tonic-gate /*
22*401f278dSas145665  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
237c478bd9Sstevel@tonic-gate  * Use is subject to license terms.
247c478bd9Sstevel@tonic-gate  */
257c478bd9Sstevel@tonic-gate 
267c478bd9Sstevel@tonic-gate /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T			*/
277c478bd9Sstevel@tonic-gate /*	  All Rights Reserved						*/
287c478bd9Sstevel@tonic-gate /*									*/
297c478bd9Sstevel@tonic-gate 
307c478bd9Sstevel@tonic-gate /*
317c478bd9Sstevel@tonic-gate  * University Copyright- Copyright (c) 1982, 1986, 1988
327c478bd9Sstevel@tonic-gate  * The Regents of the University of California
337c478bd9Sstevel@tonic-gate  * All Rights Reserved
347c478bd9Sstevel@tonic-gate  *
357c478bd9Sstevel@tonic-gate  * University Acknowledgment- Portions of this document are derived from
367c478bd9Sstevel@tonic-gate  * software developed by the University of California, Berkeley, and its
377c478bd9Sstevel@tonic-gate  * contributors.
387c478bd9Sstevel@tonic-gate  */
397c478bd9Sstevel@tonic-gate 
407c478bd9Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
417c478bd9Sstevel@tonic-gate 
427c478bd9Sstevel@tonic-gate /*
437c478bd9Sstevel@tonic-gate  * chmod option mode files
447c478bd9Sstevel@tonic-gate  * where
457c478bd9Sstevel@tonic-gate  *	mode is [ugoa][+-=][rwxXlstugo] or an octal number
46fa9e4066Sahrens  *	mode is [<+|->A[# <number] ]<aclspec>
477c478bd9Sstevel@tonic-gate  *	option is -R and -f
487c478bd9Sstevel@tonic-gate  */
497c478bd9Sstevel@tonic-gate 
507c478bd9Sstevel@tonic-gate /*
517c478bd9Sstevel@tonic-gate  *  Note that many convolutions are necessary
527c478bd9Sstevel@tonic-gate  *  due to the re-use of bits between locking
537c478bd9Sstevel@tonic-gate  *  and setgid
547c478bd9Sstevel@tonic-gate  */
557c478bd9Sstevel@tonic-gate 
567c478bd9Sstevel@tonic-gate #include <unistd.h>
577c478bd9Sstevel@tonic-gate #include <stdlib.h>
587c478bd9Sstevel@tonic-gate #include <stdio.h>
597c478bd9Sstevel@tonic-gate #include <sys/types.h>
607c478bd9Sstevel@tonic-gate #include <sys/stat.h>
617c478bd9Sstevel@tonic-gate #include <dirent.h>
627c478bd9Sstevel@tonic-gate #include <locale.h>
637c478bd9Sstevel@tonic-gate #include <string.h>	/* strerror() */
647c478bd9Sstevel@tonic-gate #include <stdarg.h>
657c478bd9Sstevel@tonic-gate #include <limits.h>
66fa9e4066Sahrens #include <ctype.h>
677c478bd9Sstevel@tonic-gate #include <errno.h>
687c478bd9Sstevel@tonic-gate #include <sys/acl.h>
69fa9e4066Sahrens #include <aclutils.h>
707c478bd9Sstevel@tonic-gate 
717c478bd9Sstevel@tonic-gate static int	rflag;
727c478bd9Sstevel@tonic-gate static int	fflag;
737c478bd9Sstevel@tonic-gate 
747c478bd9Sstevel@tonic-gate extern int	optind;
757c478bd9Sstevel@tonic-gate extern int	errno;
767c478bd9Sstevel@tonic-gate 
777c478bd9Sstevel@tonic-gate static int	mac;		/* Alternate to argc (for parseargs) */
787c478bd9Sstevel@tonic-gate static char	**mav;		/* Alternate to argv (for parseargs) */
797c478bd9Sstevel@tonic-gate 
807c478bd9Sstevel@tonic-gate static char	*ms;		/* Points to the mode argument */
817c478bd9Sstevel@tonic-gate 
82fa9e4066Sahrens #define	ACL_ADD		1
83fa9e4066Sahrens #define	ACL_DELETE	2
84fa9e4066Sahrens #define	ACL_SLOT_DELETE 3
85fa9e4066Sahrens #define	ACL_REPLACE	4
86fa9e4066Sahrens #define	ACL_STRIP	5
87fa9e4066Sahrens 
88fa9e4066Sahrens typedef struct acl_args {
89fa9e4066Sahrens 	acl_t	*acl_aclp;
90fa9e4066Sahrens 	int	acl_slot;
91fa9e4066Sahrens 	int	acl_action;
92fa9e4066Sahrens } acl_args_t;
93fa9e4066Sahrens 
947c478bd9Sstevel@tonic-gate extern mode_t
957c478bd9Sstevel@tonic-gate newmode_common(char *ms, mode_t new_mode, mode_t umsk, char *file, char *path,
967c478bd9Sstevel@tonic-gate 	o_mode_t *group_clear_bits, o_mode_t *group_set_bits);
977c478bd9Sstevel@tonic-gate 
987c478bd9Sstevel@tonic-gate static int
99fa9e4066Sahrens dochmod(char *name, char *path, mode_t umsk, acl_args_t *aclp),
100fa9e4066Sahrens chmodr(char *dir, char *path, mode_t mode, mode_t umsk, acl_args_t *aclp);
101fa9e4066Sahrens static int doacl(char *file, struct stat *st, acl_args_t *aclp);
1027c478bd9Sstevel@tonic-gate 
1037c478bd9Sstevel@tonic-gate static void handle_acl(char *name, o_mode_t group_clear_bits,
1047c478bd9Sstevel@tonic-gate     o_mode_t group_set_bits);
1057c478bd9Sstevel@tonic-gate 
106fa9e4066Sahrens static void usage(void);
1077c478bd9Sstevel@tonic-gate 
108fa9e4066Sahrens void errmsg(int severity, int code, char *format, ...);
1097c478bd9Sstevel@tonic-gate 
110fa9e4066Sahrens static void parseargs(int ac, char *av[]);
111fa9e4066Sahrens 
112fa9e4066Sahrens int
113fa9e4066Sahrens parse_acl_args(char *arg, acl_args_t **acl_args);
1147c478bd9Sstevel@tonic-gate 
1157c478bd9Sstevel@tonic-gate int
1167c478bd9Sstevel@tonic-gate main(int argc, char *argv[])
1177c478bd9Sstevel@tonic-gate {
1187c478bd9Sstevel@tonic-gate 	int i, c;
1197c478bd9Sstevel@tonic-gate 	int status = 0;
1207c478bd9Sstevel@tonic-gate 	mode_t umsk;
121fa9e4066Sahrens 	acl_args_t *acl_args = NULL;
1227c478bd9Sstevel@tonic-gate 
1237c478bd9Sstevel@tonic-gate 	(void) setlocale(LC_ALL, "");
1247c478bd9Sstevel@tonic-gate #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
1257c478bd9Sstevel@tonic-gate #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
1267c478bd9Sstevel@tonic-gate #endif
1277c478bd9Sstevel@tonic-gate 	(void) textdomain(TEXT_DOMAIN);
1287c478bd9Sstevel@tonic-gate 
1297c478bd9Sstevel@tonic-gate 	parseargs(argc, argv);
1307c478bd9Sstevel@tonic-gate 
1317c478bd9Sstevel@tonic-gate 	while ((c = getopt(mac, mav, "Rf")) != EOF) {
1327c478bd9Sstevel@tonic-gate 		switch (c) {
1337c478bd9Sstevel@tonic-gate 		case 'R':
1347c478bd9Sstevel@tonic-gate 			rflag++;
1357c478bd9Sstevel@tonic-gate 			break;
1367c478bd9Sstevel@tonic-gate 		case 'f':
1377c478bd9Sstevel@tonic-gate 			fflag++;
1387c478bd9Sstevel@tonic-gate 			break;
1397c478bd9Sstevel@tonic-gate 		case '?':
1407c478bd9Sstevel@tonic-gate 			usage();
1417c478bd9Sstevel@tonic-gate 			exit(2);
1427c478bd9Sstevel@tonic-gate 		}
1437c478bd9Sstevel@tonic-gate 	}
1447c478bd9Sstevel@tonic-gate 
1457c478bd9Sstevel@tonic-gate 	/*
1467c478bd9Sstevel@tonic-gate 	 * Check for sufficient arguments
1477c478bd9Sstevel@tonic-gate 	 * or a usage error.
1487c478bd9Sstevel@tonic-gate 	 */
1497c478bd9Sstevel@tonic-gate 
1507c478bd9Sstevel@tonic-gate 	mac -= optind;
1517c478bd9Sstevel@tonic-gate 	mav += optind;
152fa9e4066Sahrens 	if (mac >= 2 && (mav[0][0] == 'A')) {
153fa9e4066Sahrens 		if (parse_acl_args(*mav, &acl_args)) {
154fa9e4066Sahrens 			usage();
155fa9e4066Sahrens 			exit(2);
156fa9e4066Sahrens 		}
157fa9e4066Sahrens 	} else {
1587c478bd9Sstevel@tonic-gate 		if (mac < 2) {
1597c478bd9Sstevel@tonic-gate 			usage();
1607c478bd9Sstevel@tonic-gate 			exit(2);
1617c478bd9Sstevel@tonic-gate 		}
162fa9e4066Sahrens 	}
1637c478bd9Sstevel@tonic-gate 
1647c478bd9Sstevel@tonic-gate 	ms = mav[0];
1657c478bd9Sstevel@tonic-gate 
1667c478bd9Sstevel@tonic-gate 	umsk = umask(0);
1677c478bd9Sstevel@tonic-gate 	(void) umask(umsk);
1687c478bd9Sstevel@tonic-gate 
169fa9e4066Sahrens 	for (i = 1; i < mac; i++) {
170fa9e4066Sahrens 		status += dochmod(mav[i], mav[i], umsk, acl_args);
171fa9e4066Sahrens 	}
1727c478bd9Sstevel@tonic-gate 
1737c478bd9Sstevel@tonic-gate 	return (fflag ? 0 : status);
1747c478bd9Sstevel@tonic-gate }
1757c478bd9Sstevel@tonic-gate 
1767c478bd9Sstevel@tonic-gate static int
177fa9e4066Sahrens dochmod(char *name, char *path, mode_t umsk, acl_args_t *aclp)
1787c478bd9Sstevel@tonic-gate {
1797c478bd9Sstevel@tonic-gate 	static struct stat st;
1807c478bd9Sstevel@tonic-gate 	int linkflg = 0;
1817c478bd9Sstevel@tonic-gate 	o_mode_t	group_clear_bits, group_set_bits;
1827c478bd9Sstevel@tonic-gate 
1837c478bd9Sstevel@tonic-gate 	if (lstat(name, &st) < 0) {
1847c478bd9Sstevel@tonic-gate 		errmsg(2, 0, gettext("can't access %s\n"), path);
1857c478bd9Sstevel@tonic-gate 		return (1);
1867c478bd9Sstevel@tonic-gate 	}
1877c478bd9Sstevel@tonic-gate 
1887c478bd9Sstevel@tonic-gate 	if ((st.st_mode & S_IFMT) == S_IFLNK) {
1897c478bd9Sstevel@tonic-gate 		linkflg = 1;
1907c478bd9Sstevel@tonic-gate 		if (stat(name, &st) < 0) {
1917c478bd9Sstevel@tonic-gate 			errmsg(2, 0, gettext("can't access %s\n"), path);
1927c478bd9Sstevel@tonic-gate 			return (1);
1937c478bd9Sstevel@tonic-gate 		}
1947c478bd9Sstevel@tonic-gate 	}
1957c478bd9Sstevel@tonic-gate 
1967c478bd9Sstevel@tonic-gate 	/* Do not recurse if directory is object of symbolic link */
1977c478bd9Sstevel@tonic-gate 	if (rflag && ((st.st_mode & S_IFMT) == S_IFDIR) && !linkflg)
198fa9e4066Sahrens 		return (chmodr(name, path, st.st_mode, umsk, aclp));
1997c478bd9Sstevel@tonic-gate 
200fa9e4066Sahrens 	if (aclp) {
201fa9e4066Sahrens 		return (doacl(name, &st, aclp));
202fa9e4066Sahrens 	} else if (chmod(name, newmode_common(ms, st.st_mode, umsk, name, path,
2037c478bd9Sstevel@tonic-gate 	    &group_clear_bits, &group_set_bits)) == -1) {
2047c478bd9Sstevel@tonic-gate 		errmsg(2, 0, gettext("can't change %s\n"), path);
2057c478bd9Sstevel@tonic-gate 		return (1);
2067c478bd9Sstevel@tonic-gate 	}
2077c478bd9Sstevel@tonic-gate 
2087c478bd9Sstevel@tonic-gate 	/*
2097c478bd9Sstevel@tonic-gate 	 * If the group permissions of the file are being modified,
2107c478bd9Sstevel@tonic-gate 	 * make sure that the file's ACL (if it has one) is
2117c478bd9Sstevel@tonic-gate 	 * modified also, since chmod is supposed to apply group
2127c478bd9Sstevel@tonic-gate 	 * permissions changes to both the acl mask and the
2137c478bd9Sstevel@tonic-gate 	 * general group permissions.
2147c478bd9Sstevel@tonic-gate 	 */
2157c478bd9Sstevel@tonic-gate 	if (group_clear_bits || group_set_bits)
2167c478bd9Sstevel@tonic-gate 		handle_acl(name, group_clear_bits, group_set_bits);
2177c478bd9Sstevel@tonic-gate 
2187c478bd9Sstevel@tonic-gate 	return (0);
2197c478bd9Sstevel@tonic-gate }
2207c478bd9Sstevel@tonic-gate 
2217c478bd9Sstevel@tonic-gate 
2227c478bd9Sstevel@tonic-gate static int
223fa9e4066Sahrens chmodr(char *dir, char *path,  mode_t mode, mode_t umsk, acl_args_t *aclp)
2247c478bd9Sstevel@tonic-gate {
2257c478bd9Sstevel@tonic-gate 
2267c478bd9Sstevel@tonic-gate 	DIR *dirp;
2277c478bd9Sstevel@tonic-gate 	struct dirent *dp;
2287c478bd9Sstevel@tonic-gate 	char savedir[PATH_MAX];			/* dir name to restore */
2297c478bd9Sstevel@tonic-gate 	char currdir[PATH_MAX+1];		/* current dir name + '/' */
2307c478bd9Sstevel@tonic-gate 	char parentdir[PATH_MAX+1];		/* parent dir name  + '/' */
2317c478bd9Sstevel@tonic-gate 	int ecode;
232fa9e4066Sahrens 	struct stat st;
2337c478bd9Sstevel@tonic-gate 	o_mode_t	group_clear_bits, group_set_bits;
2347c478bd9Sstevel@tonic-gate 
2357c478bd9Sstevel@tonic-gate 	if (getcwd(savedir, PATH_MAX) == 0)
2367c478bd9Sstevel@tonic-gate 		errmsg(2, 255, gettext("chmod: could not getcwd %s\n"),
2377c478bd9Sstevel@tonic-gate 		    savedir);
2387c478bd9Sstevel@tonic-gate 
2397c478bd9Sstevel@tonic-gate 	/*
2407c478bd9Sstevel@tonic-gate 	 * Change what we are given before doing it's contents
2417c478bd9Sstevel@tonic-gate 	 */
242fa9e4066Sahrens 	if (aclp) {
243fa9e4066Sahrens 		if (lstat(dir, &st) < 0) {
244fa9e4066Sahrens 			errmsg(2, 0, gettext("can't access %s\n"), path);
245fa9e4066Sahrens 			return (1);
246fa9e4066Sahrens 		}
247fa9e4066Sahrens 		if (doacl(dir, &st, aclp) != 0)
248fa9e4066Sahrens 			return (1);
249fa9e4066Sahrens 	} else if (chmod(dir, newmode_common(ms, mode, umsk, dir, path,
2507c478bd9Sstevel@tonic-gate 	    &group_clear_bits, &group_set_bits)) < 0) {
2517c478bd9Sstevel@tonic-gate 		errmsg(2, 0, gettext("can't change %s\n"), path);
2527c478bd9Sstevel@tonic-gate 		return (1);
2537c478bd9Sstevel@tonic-gate 	}
2547c478bd9Sstevel@tonic-gate 
2557c478bd9Sstevel@tonic-gate 	/*
2567c478bd9Sstevel@tonic-gate 	 * If the group permissions of the file are being modified,
2577c478bd9Sstevel@tonic-gate 	 * make sure that the file's ACL (if it has one) is
2587c478bd9Sstevel@tonic-gate 	 * modified also, since chmod is supposed to apply group
2597c478bd9Sstevel@tonic-gate 	 * permissions changes to both the acl mask and the
2607c478bd9Sstevel@tonic-gate 	 * general group permissions.
2617c478bd9Sstevel@tonic-gate 	 */
262fa9e4066Sahrens 
263fa9e4066Sahrens 	if (aclp == NULL) { /* only necessary when not setting ACL */
2647c478bd9Sstevel@tonic-gate 		if (group_clear_bits || group_set_bits)
2657c478bd9Sstevel@tonic-gate 			handle_acl(dir, group_clear_bits, group_set_bits);
266fa9e4066Sahrens 	}
2677c478bd9Sstevel@tonic-gate 
2687c478bd9Sstevel@tonic-gate 	if (chdir(dir) < 0) {
2697c478bd9Sstevel@tonic-gate 		errmsg(2, 0, "%s/%s: %s\n", savedir, dir, strerror(errno));
2707c478bd9Sstevel@tonic-gate 		return (1);
2717c478bd9Sstevel@tonic-gate 	}
2727c478bd9Sstevel@tonic-gate 	if ((dirp = opendir(".")) == NULL) {
2737c478bd9Sstevel@tonic-gate 		errmsg(2, 0, "%s\n", strerror(errno));
2747c478bd9Sstevel@tonic-gate 		return (1);
2757c478bd9Sstevel@tonic-gate 	}
2767c478bd9Sstevel@tonic-gate 	ecode = 0;
2777c478bd9Sstevel@tonic-gate 
2787c478bd9Sstevel@tonic-gate 	/*
2797c478bd9Sstevel@tonic-gate 	 * Save parent directory path before recursive chmod.
2807c478bd9Sstevel@tonic-gate 	 * We'll need this for error printing purposes. Add
2817c478bd9Sstevel@tonic-gate 	 * a trailing '/' to the path except in the case where
2827c478bd9Sstevel@tonic-gate 	 * the path is just '/'
2837c478bd9Sstevel@tonic-gate 	 */
2847c478bd9Sstevel@tonic-gate 
285*401f278dSas145665 	if (strlcpy(parentdir, path, PATH_MAX + 1) >= PATH_MAX + 1) {
286*401f278dSas145665 		errmsg(2, 0, gettext("directory path name too long: %s\n"),
287*401f278dSas145665 		    path);
288*401f278dSas145665 		return (1);
289*401f278dSas145665 	}
2907c478bd9Sstevel@tonic-gate 	if (strcmp(path, "/") != 0)
291*401f278dSas145665 		if (strlcat(parentdir, "/", PATH_MAX + 1) >= PATH_MAX + 1) {
292*401f278dSas145665 			errmsg(2, 0,
293*401f278dSas145665 			    gettext("directory path name too long: %s/\n"),
294*401f278dSas145665 			    parentdir);
295*401f278dSas145665 			return (1);
296*401f278dSas145665 		}
297*401f278dSas145665 
2987c478bd9Sstevel@tonic-gate 
2997c478bd9Sstevel@tonic-gate 	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp))  {
300*401f278dSas145665 
301c4518760Scasper 		if (strcmp(dp->d_name, ".") == 0 ||	/* skip . and .. */
302c4518760Scasper 		    strcmp(dp->d_name, "..") == 0) {
303c4518760Scasper 			continue;
304c4518760Scasper 		}
305*401f278dSas145665 		if (strlcpy(currdir, parentdir, PATH_MAX + 1) >= PATH_MAX + 1) {
306*401f278dSas145665 			errmsg(2, 0,
307*401f278dSas145665 			    gettext("directory path name too long: %s\n"),
308*401f278dSas145665 			    parentdir);
309*401f278dSas145665 			return (1);
310*401f278dSas145665 		}
311*401f278dSas145665 		if (strlcat(currdir, dp->d_name, PATH_MAX + 1)
312*401f278dSas145665 		    >= PATH_MAX + 1) {
313*401f278dSas145665 			errmsg(2, 0,
314*401f278dSas145665 			    gettext("directory path name too long: %s%s\n"),
315*401f278dSas145665 			    currdir, dp->d_name);
316*401f278dSas145665 			return (1);
317*401f278dSas145665 		}
318fa9e4066Sahrens 		ecode += dochmod(dp->d_name, currdir, umsk, aclp);
3197c478bd9Sstevel@tonic-gate 	}
3207c478bd9Sstevel@tonic-gate 	(void) closedir(dirp);
3217c478bd9Sstevel@tonic-gate 	if (chdir(savedir) < 0) {
3227c478bd9Sstevel@tonic-gate 		errmsg(2, 255, gettext("can't change back to %s\n"), savedir);
3237c478bd9Sstevel@tonic-gate 	}
3247c478bd9Sstevel@tonic-gate 	return (ecode ? 1 : 0);
3257c478bd9Sstevel@tonic-gate }
3267c478bd9Sstevel@tonic-gate 
3277c478bd9Sstevel@tonic-gate /* PRINTFLIKE3 */
3287c478bd9Sstevel@tonic-gate void
3297c478bd9Sstevel@tonic-gate errmsg(int severity, int code, char *format, ...)
3307c478bd9Sstevel@tonic-gate {
3317c478bd9Sstevel@tonic-gate 	va_list ap;
3327c478bd9Sstevel@tonic-gate 	static char *msg[] = {
3337c478bd9Sstevel@tonic-gate 	"",
3347c478bd9Sstevel@tonic-gate 	"ERROR",
3357c478bd9Sstevel@tonic-gate 	"WARNING",
3367c478bd9Sstevel@tonic-gate 	""
3377c478bd9Sstevel@tonic-gate 	};
3387c478bd9Sstevel@tonic-gate 
3397c478bd9Sstevel@tonic-gate 	va_start(ap, format);
3407c478bd9Sstevel@tonic-gate 
3417c478bd9Sstevel@tonic-gate 	/*
3427c478bd9Sstevel@tonic-gate 	 * Always print error message if this is a fatal error (code == 0);
3437c478bd9Sstevel@tonic-gate 	 * otherwise, print message if fflag == 0 (no -f option specified)
3447c478bd9Sstevel@tonic-gate 	 */
3457c478bd9Sstevel@tonic-gate 	if (!fflag || (code != 0)) {
3467c478bd9Sstevel@tonic-gate 		(void) fprintf(stderr,
3477c478bd9Sstevel@tonic-gate 			"chmod: %s: ", gettext(msg[severity]));
3487c478bd9Sstevel@tonic-gate 		(void) vfprintf(stderr, format, ap);
3497c478bd9Sstevel@tonic-gate 	}
3507c478bd9Sstevel@tonic-gate 
3517c478bd9Sstevel@tonic-gate 	va_end(ap);
3527c478bd9Sstevel@tonic-gate 
3537c478bd9Sstevel@tonic-gate 	if (code != 0)
3547c478bd9Sstevel@tonic-gate 		exit(fflag ? 0 : code);
3557c478bd9Sstevel@tonic-gate }
3567c478bd9Sstevel@tonic-gate 
3577c478bd9Sstevel@tonic-gate static void
3587c478bd9Sstevel@tonic-gate usage(void)
3597c478bd9Sstevel@tonic-gate {
3607c478bd9Sstevel@tonic-gate 	(void) fprintf(stderr, gettext(
3617c478bd9Sstevel@tonic-gate 	    "usage:\tchmod [-fR] <absolute-mode> file ...\n"));
3627c478bd9Sstevel@tonic-gate 
3637c478bd9Sstevel@tonic-gate 	(void) fprintf(stderr, gettext(
364fa9e4066Sahrens 	    "\tchmod [-fR] <ACL-operation> file ...\n"));
365fa9e4066Sahrens 
366fa9e4066Sahrens 	(void) fprintf(stderr, gettext(
3677c478bd9Sstevel@tonic-gate 	    "\tchmod [-fR] <symbolic-mode-list> file ...\n"));
3687c478bd9Sstevel@tonic-gate 
369fa9e4066Sahrens 
3707c478bd9Sstevel@tonic-gate 	(void) fprintf(stderr, gettext(
3717c478bd9Sstevel@tonic-gate 	    "where \t<symbolic-mode-list> is a comma-separated list of\n"));
3727c478bd9Sstevel@tonic-gate 
3737c478bd9Sstevel@tonic-gate 	(void) fprintf(stderr, gettext(
3747c478bd9Sstevel@tonic-gate 	    "\t[ugoa]{+|-|=}[rwxXlstugo]\n"));
375fa9e4066Sahrens 
376fa9e4066Sahrens 	(void) fprintf(stderr, gettext(
377fa9e4066Sahrens 	    "where \t<ACL-operation> is one of the following\n"));
378fa9e4066Sahrens 	(void) fprintf(stderr, gettext("\tA-<acl_specification>\n"));
379fa9e4066Sahrens 	(void) fprintf(stderr, gettext("\tA[number]-\n"));
380fa9e4066Sahrens 	(void) fprintf(stderr, gettext(
381fa9e4066Sahrens 	    "\tA[number]{+|=}<acl_specification>\n"));
382fa9e4066Sahrens 	(void) fprintf(stderr, gettext(
383fa9e4066Sahrens 	    "where \t<acl-specification> is a comma-separated list of ACEs\n"));
3847c478bd9Sstevel@tonic-gate }
3857c478bd9Sstevel@tonic-gate 
3867c478bd9Sstevel@tonic-gate /*
3877c478bd9Sstevel@tonic-gate  *  parseargs - generate getopt-friendly argument list for backwards
3887c478bd9Sstevel@tonic-gate  *		compatibility with earlier Solaris usage (eg, chmod -w
3897c478bd9Sstevel@tonic-gate  *		foo).
3907c478bd9Sstevel@tonic-gate  *
3917c478bd9Sstevel@tonic-gate  *  assumes the existence of a static set of alternates to argc and argv,
3927c478bd9Sstevel@tonic-gate  *  (namely, mac, and mav[]).
3937c478bd9Sstevel@tonic-gate  *
3947c478bd9Sstevel@tonic-gate  */
3957c478bd9Sstevel@tonic-gate 
3967c478bd9Sstevel@tonic-gate static void
3977c478bd9Sstevel@tonic-gate parseargs(int ac, char *av[])
3987c478bd9Sstevel@tonic-gate {
3997c478bd9Sstevel@tonic-gate 	int i;			/* current argument			*/
4007c478bd9Sstevel@tonic-gate 	int fflag;		/* arg list contains "--"		*/
4017c478bd9Sstevel@tonic-gate 	size_t mav_num;		/* number of entries in mav[]		*/
4027c478bd9Sstevel@tonic-gate 
4037c478bd9Sstevel@tonic-gate 	/*
4047c478bd9Sstevel@tonic-gate 	 * We add an extra argument slot, in case we need to jam a "--"
4057c478bd9Sstevel@tonic-gate 	 * argument into the list.
4067c478bd9Sstevel@tonic-gate 	 */
4077c478bd9Sstevel@tonic-gate 
4087c478bd9Sstevel@tonic-gate 	mav_num = (size_t)ac+2;
4097c478bd9Sstevel@tonic-gate 
4107c478bd9Sstevel@tonic-gate 	if ((mav = calloc(mav_num, sizeof (char *))) == NULL) {
4117c478bd9Sstevel@tonic-gate 		perror("chmod");
4127c478bd9Sstevel@tonic-gate 		exit(2);
4137c478bd9Sstevel@tonic-gate 	}
4147c478bd9Sstevel@tonic-gate 
4157c478bd9Sstevel@tonic-gate 	/* scan for the use of "--" in the argument list */
4167c478bd9Sstevel@tonic-gate 
4177c478bd9Sstevel@tonic-gate 	for (fflag = i = 0; i < ac; i ++) {
4187c478bd9Sstevel@tonic-gate 		if (strcmp(av[i], "--") == 0)
4197c478bd9Sstevel@tonic-gate 		    fflag = 1;
4207c478bd9Sstevel@tonic-gate 	}
4217c478bd9Sstevel@tonic-gate 
4227c478bd9Sstevel@tonic-gate 	/* process the arguments */
4237c478bd9Sstevel@tonic-gate 
4247c478bd9Sstevel@tonic-gate 	for (i = mac = 0;
4257c478bd9Sstevel@tonic-gate 	    (av[i] != (char *)NULL) && (av[i][0] != (char)NULL);
4267c478bd9Sstevel@tonic-gate 	    i++) {
4277c478bd9Sstevel@tonic-gate 		if (!fflag && av[i][0] == '-') {
4287c478bd9Sstevel@tonic-gate 			/*
4297c478bd9Sstevel@tonic-gate 			 *  If there is not already a "--" argument specified,
4307c478bd9Sstevel@tonic-gate 			 *  and the argument starts with '-' but does not
4317c478bd9Sstevel@tonic-gate 			 *  contain any of the official option letters, then it
4327c478bd9Sstevel@tonic-gate 			 *  is probably a mode argument beginning with '-'.
4337c478bd9Sstevel@tonic-gate 			 *  Force a "--" into the argument stream in front of
4347c478bd9Sstevel@tonic-gate 			 *  it.
4357c478bd9Sstevel@tonic-gate 			 */
4367c478bd9Sstevel@tonic-gate 
4377c478bd9Sstevel@tonic-gate 			if ((strchr(av[i], 'R') == NULL &&
4387c478bd9Sstevel@tonic-gate 			    strchr(av[i], 'f') == NULL)) {
4397c478bd9Sstevel@tonic-gate 				mav[mac++] = strdup("--");
4407c478bd9Sstevel@tonic-gate 			}
4417c478bd9Sstevel@tonic-gate 		}
4427c478bd9Sstevel@tonic-gate 
4437c478bd9Sstevel@tonic-gate 		mav[mac++] = strdup(av[i]);
4447c478bd9Sstevel@tonic-gate 	}
4457c478bd9Sstevel@tonic-gate 
4467c478bd9Sstevel@tonic-gate 	mav[mac] = (char *)NULL;
4477c478bd9Sstevel@tonic-gate }
4487c478bd9Sstevel@tonic-gate 
449fa9e4066Sahrens int
450fa9e4066Sahrens parse_acl_args(char *arg, acl_args_t **acl_args)
451fa9e4066Sahrens {
452fa9e4066Sahrens 	acl_t *new_acl = NULL;
453fa9e4066Sahrens 	int slot;
454fa9e4066Sahrens 	int len;
455fa9e4066Sahrens 	int action;
456fa9e4066Sahrens 	acl_args_t *new_acl_args;
457fa9e4066Sahrens 	char *acl_spec = NULL;
458fa9e4066Sahrens 	char *end;
459fa9e4066Sahrens 
460fa9e4066Sahrens 	if (arg[0] != 'A')
461fa9e4066Sahrens 		return (1);
462fa9e4066Sahrens 
463fa9e4066Sahrens 	slot = strtol(&arg[1], &end, 10);
464fa9e4066Sahrens 
465fa9e4066Sahrens 	len = strlen(arg);
466fa9e4066Sahrens 	switch (*end) {
467fa9e4066Sahrens 	case '+':
468fa9e4066Sahrens 		action = ACL_ADD;
469fa9e4066Sahrens 		acl_spec = ++end;
470fa9e4066Sahrens 		break;
471fa9e4066Sahrens 	case '-':
472fa9e4066Sahrens 		if (len == 2 && arg[0] == 'A' && arg[1] == '-')
473fa9e4066Sahrens 			action = ACL_STRIP;
474fa9e4066Sahrens 		else
475fa9e4066Sahrens 			action = ACL_DELETE;
476fa9e4066Sahrens 		if (action != ACL_STRIP) {
477fa9e4066Sahrens 			acl_spec = ++end;
478fa9e4066Sahrens 			if (acl_spec[0] == '\0') {
479fa9e4066Sahrens 				action = ACL_SLOT_DELETE;
480fa9e4066Sahrens 				acl_spec = NULL;
481fa9e4066Sahrens 			} else if (arg[1] != '-')
482fa9e4066Sahrens 				return (1);
483fa9e4066Sahrens 		}
484fa9e4066Sahrens 		break;
485fa9e4066Sahrens 	case '=':
48655601ddbSmarks 		/*
48755601ddbSmarks 		 * Was slot specified?
48855601ddbSmarks 		 */
48955601ddbSmarks 		if (arg[1] == '=')
49055601ddbSmarks 			slot = -1;
491fa9e4066Sahrens 		action = ACL_REPLACE;
492fa9e4066Sahrens 		acl_spec = ++end;
493fa9e4066Sahrens 		break;
494fa9e4066Sahrens 	default:
495fa9e4066Sahrens 		return (1);
496fa9e4066Sahrens 	}
497fa9e4066Sahrens 
498fa9e4066Sahrens 	if ((action == ACL_REPLACE || action == ACL_ADD) && acl_spec[0] == '\0')
499fa9e4066Sahrens 		return (1);
500fa9e4066Sahrens 
501fa9e4066Sahrens 	if (acl_spec) {
5025a5eeccaSmarks 		if (acl_parse(acl_spec, &new_acl)) {
5035a5eeccaSmarks 			exit(1);
504fa9e4066Sahrens 		}
505fa9e4066Sahrens 	}
506fa9e4066Sahrens 
507fa9e4066Sahrens 	new_acl_args = malloc(sizeof (acl_args_t));
508fa9e4066Sahrens 	if (new_acl_args == NULL)
509fa9e4066Sahrens 		return (1);
510fa9e4066Sahrens 
511fa9e4066Sahrens 	new_acl_args->acl_aclp = new_acl;
512fa9e4066Sahrens 	new_acl_args->acl_slot = slot;
513fa9e4066Sahrens 	new_acl_args->acl_action = action;
514fa9e4066Sahrens 
515fa9e4066Sahrens 	*acl_args = new_acl_args;
516fa9e4066Sahrens 
517fa9e4066Sahrens 	return (0);
518fa9e4066Sahrens }
519fa9e4066Sahrens 
5207c478bd9Sstevel@tonic-gate /*
5217c478bd9Sstevel@tonic-gate  * This function is called whenever the group permissions of a file
5227c478bd9Sstevel@tonic-gate  * is being modified.  According to the chmod(1) manpage, any
5237c478bd9Sstevel@tonic-gate  * change made to the group permissions must be applied to both
5247c478bd9Sstevel@tonic-gate  * the acl mask and the acl's GROUP_OBJ.  The chmod(2) already
5257c478bd9Sstevel@tonic-gate  * set the mask, so this routine needs to make the same change
5267c478bd9Sstevel@tonic-gate  * to the GROUP_OBJ.
5277c478bd9Sstevel@tonic-gate  */
5287c478bd9Sstevel@tonic-gate static void
5297c478bd9Sstevel@tonic-gate handle_acl(char *name, o_mode_t group_clear_bits, o_mode_t group_set_bits)
5307c478bd9Sstevel@tonic-gate {
5317c478bd9Sstevel@tonic-gate 	int aclcnt, n;
5327c478bd9Sstevel@tonic-gate 	aclent_t *aclp, *tp;
5337c478bd9Sstevel@tonic-gate 	o_mode_t newperm;
534fa9e4066Sahrens 	/*
535fa9e4066Sahrens 	 * if this file system support ace_t acl's
536fa9e4066Sahrens 	 * then simply return since we don't have an
537fa9e4066Sahrens 	 * acl mask to deal with
538fa9e4066Sahrens 	 */
539fa9e4066Sahrens 	if (pathconf(name, _PC_ACL_ENABLED) == _ACL_ACE_ENABLED)
540fa9e4066Sahrens 		return;
5417c478bd9Sstevel@tonic-gate 	if ((aclcnt = acl(name, GETACLCNT, 0, NULL)) <= MIN_ACL_ENTRIES)
5427c478bd9Sstevel@tonic-gate 		return;	/* it's just a trivial acl; no need to change it */
5437c478bd9Sstevel@tonic-gate 	if ((aclp = (aclent_t *)malloc((sizeof (aclent_t)) * aclcnt))
5447c478bd9Sstevel@tonic-gate 	    == NULL) {
5457c478bd9Sstevel@tonic-gate 		perror("chmod");
5467c478bd9Sstevel@tonic-gate 		exit(2);
5477c478bd9Sstevel@tonic-gate 	}
5487c478bd9Sstevel@tonic-gate 
5497c478bd9Sstevel@tonic-gate 	if (acl(name, GETACL, aclcnt, aclp) < 0) {
5507c478bd9Sstevel@tonic-gate 		free(aclp);
5517c478bd9Sstevel@tonic-gate 		(void) fprintf(stderr, "chmod: ");
5527c478bd9Sstevel@tonic-gate 		perror(name);
5537c478bd9Sstevel@tonic-gate 		return;
5547c478bd9Sstevel@tonic-gate 	}
5557c478bd9Sstevel@tonic-gate 	for (tp = aclp, n = aclcnt; n--; tp++) {
5567c478bd9Sstevel@tonic-gate 		if (tp->a_type == GROUP_OBJ) {
5577c478bd9Sstevel@tonic-gate 			newperm = tp->a_perm;
5587c478bd9Sstevel@tonic-gate 			if (group_clear_bits != 0)
5597c478bd9Sstevel@tonic-gate 				newperm &= ~group_clear_bits;
5607c478bd9Sstevel@tonic-gate 			if (group_set_bits != 0)
5617c478bd9Sstevel@tonic-gate 				newperm |= group_set_bits;
5627c478bd9Sstevel@tonic-gate 			if (newperm != tp->a_perm) {
5637c478bd9Sstevel@tonic-gate 				tp->a_perm = newperm;
5647c478bd9Sstevel@tonic-gate 				if (acl(name, SETACL, aclcnt, aclp)
5657c478bd9Sstevel@tonic-gate 				    < 0) {
5667c478bd9Sstevel@tonic-gate 					(void) fprintf(stderr, "chmod: ");
5677c478bd9Sstevel@tonic-gate 					perror(name);
5687c478bd9Sstevel@tonic-gate 				}
5697c478bd9Sstevel@tonic-gate 			}
5707c478bd9Sstevel@tonic-gate 			break;
5717c478bd9Sstevel@tonic-gate 		}
5727c478bd9Sstevel@tonic-gate 	}
5737c478bd9Sstevel@tonic-gate 	free(aclp);
5747c478bd9Sstevel@tonic-gate }
575fa9e4066Sahrens 
576fa9e4066Sahrens static int
577fa9e4066Sahrens doacl(char *file, struct stat *st, acl_args_t *acl_args)
578fa9e4066Sahrens {
579fa9e4066Sahrens 	acl_t *aclp;
580fa9e4066Sahrens 	acl_t *set_aclp;
581fa9e4066Sahrens 	int error = 0;
582fa9e4066Sahrens 	void *to, *from;
583fa9e4066Sahrens 	int len;
584fa9e4066Sahrens 	int isdir;
585fa9e4066Sahrens 	isdir = S_ISDIR(st->st_mode);
586fa9e4066Sahrens 
587fa9e4066Sahrens 	error = acl_get(file, 0, &aclp);
588fa9e4066Sahrens 
589fa9e4066Sahrens 	if (error != 0) {
590fa9e4066Sahrens 		errmsg(1, 1, "%s\n", acl_strerror(error));
591fa9e4066Sahrens 		return (1);
592fa9e4066Sahrens 	}
593fa9e4066Sahrens 	switch (acl_args->acl_action) {
594fa9e4066Sahrens 	case ACL_ADD:
595fa9e4066Sahrens 		if ((error = acl_addentries(aclp,
596fa9e4066Sahrens 			acl_args->acl_aclp, acl_args->acl_slot)) != 0) {
597fa9e4066Sahrens 				errmsg(1, 1, "%s\n", acl_strerror(error));
598fa9e4066Sahrens 				acl_free(aclp);
599fa9e4066Sahrens 				return (1);
600fa9e4066Sahrens 		}
601fa9e4066Sahrens 		set_aclp = aclp;
602fa9e4066Sahrens 		break;
603fa9e4066Sahrens 	case ACL_SLOT_DELETE:
604fa9e4066Sahrens 		if (acl_args->acl_slot + 1 > aclp->acl_cnt) {
605fa9e4066Sahrens 			errmsg(1, 1,
606fa9e4066Sahrens 			    gettext("Invalid slot specified for removal\n"));
607fa9e4066Sahrens 			acl_free(aclp);
608fa9e4066Sahrens 			return (1);
609fa9e4066Sahrens 		}
610fa9e4066Sahrens 
611fa9e4066Sahrens 		if (acl_args->acl_slot == 0 && aclp->acl_cnt == 1) {
612fa9e4066Sahrens 			errmsg(1, 1,
613fa9e4066Sahrens 			    gettext("Can't remove all ACL "
614fa9e4066Sahrens 			    "entries from a file\n"));
615fa9e4066Sahrens 			acl_free(aclp);
616fa9e4066Sahrens 			return (1);
617fa9e4066Sahrens 		}
618fa9e4066Sahrens 
619fa9e4066Sahrens 		/*
620fa9e4066Sahrens 		 * remove a single entry
621fa9e4066Sahrens 		 *
622fa9e4066Sahrens 		 * if last entry just adjust acl_cnt
623fa9e4066Sahrens 		 */
624fa9e4066Sahrens 
625fa9e4066Sahrens 		if ((acl_args->acl_slot + 1) == aclp->acl_cnt)
626fa9e4066Sahrens 			aclp->acl_cnt--;
627fa9e4066Sahrens 		else {
628fa9e4066Sahrens 			to = (char *)aclp->acl_aclp +
629fa9e4066Sahrens 			    (acl_args->acl_slot * aclp->acl_entry_size);
630fa9e4066Sahrens 			from = (char *)to + aclp->acl_entry_size;
631fa9e4066Sahrens 			len = (aclp->acl_cnt - acl_args->acl_slot - 1) *
632fa9e4066Sahrens 			    aclp->acl_entry_size;
633fa9e4066Sahrens 			(void) memmove(to, from, len);
634fa9e4066Sahrens 			aclp->acl_cnt--;
635fa9e4066Sahrens 		}
636fa9e4066Sahrens 		set_aclp = aclp;
637fa9e4066Sahrens 		break;
638fa9e4066Sahrens 
639fa9e4066Sahrens 	case ACL_DELETE:
640fa9e4066Sahrens 		if ((error = acl_removeentries(aclp, acl_args->acl_aclp,
641fa9e4066Sahrens 		    acl_args->acl_slot, ACL_REMOVE_ALL)) != 0) {
642fa9e4066Sahrens 			errmsg(1, 1, "%s\n", acl_strerror(error));
643fa9e4066Sahrens 			acl_free(aclp);
644fa9e4066Sahrens 			return (1);
645fa9e4066Sahrens 		}
646fa9e4066Sahrens 
647fa9e4066Sahrens 		if (aclp->acl_cnt == 0) {
648fa9e4066Sahrens 			errmsg(1, 1,
649fa9e4066Sahrens 			    gettext("Can't remove all ACL "
650fa9e4066Sahrens 			    "entries from a file\n"));
651fa9e4066Sahrens 			acl_free(aclp);
652fa9e4066Sahrens 			return (1);
653fa9e4066Sahrens 		}
654fa9e4066Sahrens 
655fa9e4066Sahrens 		set_aclp = aclp;
656fa9e4066Sahrens 		break;
657fa9e4066Sahrens 	case ACL_REPLACE:
658fa9e4066Sahrens 		if (acl_args->acl_slot >= 0)  {
659fa9e4066Sahrens 			error = acl_modifyentries(aclp, acl_args->acl_aclp,
660fa9e4066Sahrens 			    acl_args->acl_slot);
661fa9e4066Sahrens 			if (error) {
662fa9e4066Sahrens 				errmsg(1, 1, "%s\n", acl_strerror(error));
663fa9e4066Sahrens 				acl_free(aclp);
664fa9e4066Sahrens 				return (1);
665fa9e4066Sahrens 			}
666fa9e4066Sahrens 			set_aclp = aclp;
667fa9e4066Sahrens 		} else {
668fa9e4066Sahrens 			set_aclp = acl_args->acl_aclp;
669fa9e4066Sahrens 		}
670fa9e4066Sahrens 		break;
671fa9e4066Sahrens 	case ACL_STRIP:
672fa9e4066Sahrens 		error = acl_strip(file, st->st_uid, st->st_gid, st->st_mode);
673fa9e4066Sahrens 		if (error) {
674fa9e4066Sahrens 			errmsg(1, 1, "%s\n", acl_strerror(error));
675fa9e4066Sahrens 			return (1);
676fa9e4066Sahrens 		}
677fa9e4066Sahrens 		acl_free(aclp);
678fa9e4066Sahrens 		return (0);
679fa9e4066Sahrens 		/*NOTREACHED*/
680fa9e4066Sahrens 	default:
681fa9e4066Sahrens 		errmsg(1, 0, gettext("Unknown ACL action requested\n"));
682fa9e4066Sahrens 		return (1);
683fa9e4066Sahrens 		break;
684fa9e4066Sahrens 	}
685fa9e4066Sahrens 	error = acl_check(set_aclp, isdir);
686fa9e4066Sahrens 
687fa9e4066Sahrens 	if (error) {
688fa9e4066Sahrens 		errmsg(1, 0, "%s\n%s", acl_strerror(error),
689fa9e4066Sahrens 		    gettext("See chmod(1) for more information on "
690fa9e4066Sahrens 		    "valid ACL syntax\n"));
691fa9e4066Sahrens 		return (1);
692fa9e4066Sahrens 	}
693fa9e4066Sahrens 	if ((error = acl_set(file, set_aclp)) != 0) {
694fa9e4066Sahrens 			errmsg(1, 0, gettext("Failed to set ACL: %s\n"),
695fa9e4066Sahrens 			    acl_strerror(error));
696fa9e4066Sahrens 			acl_free(aclp);
697fa9e4066Sahrens 			return (1);
698fa9e4066Sahrens 	}
699fa9e4066Sahrens 	acl_free(aclp);
700fa9e4066Sahrens 	return (0);
701fa9e4066Sahrens }
702