xref: /titanic_44/usr/src/cmd/chmod/chmod.c (revision b7f45089ccbe01bab3d7c7377b49d80d2ae18a69)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T			*/
28 /*	  All Rights Reserved						*/
29 /*									*/
30 
31 /*
32  * University Copyright- Copyright (c) 1982, 1986, 1988
33  * The Regents of the University of California
34  * All Rights Reserved
35  *
36  * University Acknowledgment- Portions of this document are derived from
37  * software developed by the University of California, Berkeley, and its
38  * contributors.
39  */
40 
41 #pragma ident	"%Z%%M%	%I%	%E% SMI"
42 
43 /*
44  * chmod option mode files
45  * where
46  *	mode is [ugoa][+-=][rwxXlstugo] or an octal number
47  *	option is -R and -f
48  */
49 
50 /*
51  *  Note that many convolutions are necessary
52  *  due to the re-use of bits between locking
53  *  and setgid
54  */
55 
56 #include <unistd.h>
57 #include <stdlib.h>
58 #include <stdio.h>
59 #include <sys/types.h>
60 #include <sys/stat.h>
61 #include <dirent.h>
62 #include <locale.h>
63 #include <string.h>	/* strerror() */
64 #include <stdarg.h>
65 #include <limits.h>
66 #include <errno.h>
67 #include <sys/acl.h>
68 
69 static int	rflag;
70 static int	fflag;
71 
72 extern int	optind;
73 extern int	errno;
74 
75 static int	mac;		/* Alternate to argc (for parseargs) */
76 static char	**mav;		/* Alternate to argv (for parseargs) */
77 
78 static char	*ms;		/* Points to the mode argument */
79 
80 extern mode_t
81 newmode_common(char *ms, mode_t new_mode, mode_t umsk, char *file, char *path,
82 	o_mode_t *group_clear_bits, o_mode_t *group_set_bits);
83 
84 static int
85 dochmod(char *name, char *path, mode_t umsk),
86 chmodr(char *dir, char *path, mode_t mode, mode_t umsk);
87 
88 static void handle_acl(char *name, o_mode_t group_clear_bits,
89 	o_mode_t group_set_bits);
90 
91 static void
92 usage(void);
93 
94 void
95 errmsg(int severity, int code, char *format, ...);
96 
97 static void
98 parseargs(int ac, char *av[]);
99 
100 int
101 main(int argc, char *argv[])
102 {
103 	int i, c;
104 	int status = 0;
105 	mode_t umsk;
106 
107 	(void) setlocale(LC_ALL, "");
108 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
109 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
110 #endif
111 	(void) textdomain(TEXT_DOMAIN);
112 
113 	parseargs(argc, argv);
114 
115 	while ((c = getopt(mac, mav, "Rf")) != EOF) {
116 		switch (c) {
117 		case 'R':
118 			rflag++;
119 			break;
120 		case 'f':
121 			fflag++;
122 			break;
123 		case '?':
124 			usage();
125 			exit(2);
126 		}
127 	}
128 
129 	/*
130 	 * Check for sufficient arguments
131 	 * or a usage error.
132 	 */
133 
134 	mac -= optind;
135 	mav += optind;
136 
137 	if (mac < 2) {
138 		usage();
139 		exit(2);
140 	}
141 
142 	ms = mav[0];
143 
144 	umsk = umask(0);
145 	(void) umask(umsk);
146 
147 	for (i = 1; i < mac; i++)
148 		status += dochmod(mav[i], mav[i], umsk);
149 
150 	return (fflag ? 0 : status);
151 }
152 
153 static int
154 dochmod(char *name, char *path, mode_t umsk)
155 {
156 	static struct stat st;
157 	int linkflg = 0;
158 	o_mode_t	group_clear_bits, group_set_bits;
159 
160 	if (lstat(name, &st) < 0) {
161 		errmsg(2, 0, gettext("can't access %s\n"), path);
162 		return (1);
163 	}
164 
165 	if ((st.st_mode & S_IFMT) == S_IFLNK) {
166 		linkflg = 1;
167 		if (stat(name, &st) < 0) {
168 			errmsg(2, 0, gettext("can't access %s\n"), path);
169 			return (1);
170 		}
171 	}
172 
173 	/* Do not recurse if directory is object of symbolic link */
174 	if (rflag && ((st.st_mode & S_IFMT) == S_IFDIR) && !linkflg)
175 		return (chmodr(name, path, st.st_mode, umsk));
176 
177 	if (chmod(name, newmode_common(ms, st.st_mode, umsk, name, path,
178 	    &group_clear_bits, &group_set_bits)) == -1) {
179 		errmsg(2, 0, gettext("can't change %s\n"), path);
180 		return (1);
181 	}
182 
183 	/*
184 	 * If the group permissions of the file are being modified,
185 	 * make sure that the file's ACL (if it has one) is
186 	 * modified also, since chmod is supposed to apply group
187 	 * permissions changes to both the acl mask and the
188 	 * general group permissions.
189 	 */
190 	if (group_clear_bits || group_set_bits)
191 		handle_acl(name, group_clear_bits, group_set_bits);
192 
193 	return (0);
194 }
195 
196 
197 static int
198 chmodr(char *dir, char *path,  mode_t mode, mode_t umsk)
199 {
200 
201 	DIR *dirp;
202 	struct dirent *dp;
203 	char savedir[PATH_MAX];			/* dir name to restore */
204 	char currdir[PATH_MAX+1];		/* current dir name + '/' */
205 	char parentdir[PATH_MAX+1];		/* parent dir name  + '/' */
206 	int ecode;
207 	o_mode_t	group_clear_bits, group_set_bits;
208 
209 	if (getcwd(savedir, PATH_MAX) == 0)
210 		errmsg(2, 255, gettext("chmod: could not getcwd %s\n"),
211 		    savedir);
212 
213 	/*
214 	 * Change what we are given before doing it's contents
215 	 */
216 	if (chmod(dir, newmode_common(ms, mode, umsk, dir, path,
217 	    &group_clear_bits, &group_set_bits)) < 0) {
218 		errmsg(2, 0, gettext("can't change %s\n"), path);
219 		return (1);
220 	}
221 
222 	/*
223 	 * If the group permissions of the file are being modified,
224 	 * make sure that the file's ACL (if it has one) is
225 	 * modified also, since chmod is supposed to apply group
226 	 * permissions changes to both the acl mask and the
227 	 * general group permissions.
228 	 */
229 	if (group_clear_bits || group_set_bits)
230 		handle_acl(dir, group_clear_bits, group_set_bits);
231 
232 	if (chdir(dir) < 0) {
233 		errmsg(2, 0, "%s/%s: %s\n", savedir, dir, strerror(errno));
234 		return (1);
235 	}
236 	if ((dirp = opendir(".")) == NULL) {
237 		errmsg(2, 0, "%s\n", strerror(errno));
238 		return (1);
239 	}
240 	dp = readdir(dirp);
241 	dp = readdir(dirp); /* read "." and ".." */
242 	ecode = 0;
243 
244 	/*
245 	 * Save parent directory path before recursive chmod.
246 	 * We'll need this for error printing purposes. Add
247 	 * a trailing '/' to the path except in the case where
248 	 * the path is just '/'
249 	 */
250 
251 	(void) strcpy(parentdir, path);
252 	if (strcmp(path, "/") != 0)
253 		(void) strcat(parentdir, "/");
254 
255 	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp))  {
256 		(void) strcpy(currdir, parentdir);
257 		(void) strcat(currdir, dp->d_name);
258 		ecode += dochmod(dp->d_name, currdir, umsk);
259 	}
260 	(void) closedir(dirp);
261 	if (chdir(savedir) < 0) {
262 		errmsg(2, 255, gettext("can't change back to %s\n"), savedir);
263 	}
264 	return (ecode ? 1 : 0);
265 }
266 
267 /* PRINTFLIKE3 */
268 void
269 errmsg(int severity, int code, char *format, ...)
270 {
271 	va_list ap;
272 	static char *msg[] = {
273 	"",
274 	"ERROR",
275 	"WARNING",
276 	""
277 	};
278 
279 	va_start(ap, format);
280 
281 	/*
282 	 * Always print error message if this is a fatal error (code == 0);
283 	 * otherwise, print message if fflag == 0 (no -f option specified)
284 	 */
285 	if (!fflag || (code != 0)) {
286 		(void) fprintf(stderr,
287 			"chmod: %s: ", gettext(msg[severity]));
288 		(void) vfprintf(stderr, format, ap);
289 	}
290 
291 	va_end(ap);
292 
293 	if (code != 0)
294 		exit(fflag ? 0 : code);
295 }
296 
297 static void
298 usage(void)
299 {
300 	(void) fprintf(stderr, gettext(
301 	    "usage:\tchmod [-fR] <absolute-mode> file ...\n"));
302 
303 	(void) fprintf(stderr, gettext(
304 	    "\tchmod [-fR] <symbolic-mode-list> file ...\n"));
305 
306 	(void) fprintf(stderr, gettext(
307 	    "where \t<symbolic-mode-list> is a comma-separated list of\n"));
308 
309 	(void) fprintf(stderr, gettext(
310 	    "\t[ugoa]{+|-|=}[rwxXlstugo]\n"));
311 }
312 
313 /*
314  *  parseargs - generate getopt-friendly argument list for backwards
315  *		compatibility with earlier Solaris usage (eg, chmod -w
316  *		foo).
317  *
318  *  assumes the existence of a static set of alternates to argc and argv,
319  *  (namely, mac, and mav[]).
320  *
321  */
322 
323 static void
324 parseargs(int ac, char *av[])
325 {
326 	int i;			/* current argument			*/
327 	int fflag;		/* arg list contains "--"		*/
328 	size_t mav_num;		/* number of entries in mav[]		*/
329 
330 	/*
331 	 * We add an extra argument slot, in case we need to jam a "--"
332 	 * argument into the list.
333 	 */
334 
335 	mav_num = (size_t)ac+2;
336 
337 	if ((mav = calloc(mav_num, sizeof (char *))) == NULL) {
338 		perror("chmod");
339 		exit(2);
340 	}
341 
342 	/* scan for the use of "--" in the argument list */
343 
344 	for (fflag = i = 0; i < ac; i ++) {
345 		if (strcmp(av[i], "--") == 0)
346 		    fflag = 1;
347 	}
348 
349 	/* process the arguments */
350 
351 	for (i = mac = 0;
352 	    (av[i] != (char *)NULL) && (av[i][0] != (char)NULL);
353 	    i++) {
354 		if (!fflag && av[i][0] == '-') {
355 			/*
356 			 *  If there is not already a "--" argument specified,
357 			 *  and the argument starts with '-' but does not
358 			 *  contain any of the official option letters, then it
359 			 *  is probably a mode argument beginning with '-'.
360 			 *  Force a "--" into the argument stream in front of
361 			 *  it.
362 			 */
363 
364 			if ((strchr(av[i], 'R') == NULL &&
365 			    strchr(av[i], 'f') == NULL)) {
366 				mav[mac++] = strdup("--");
367 			}
368 		}
369 
370 		mav[mac++] = strdup(av[i]);
371 	}
372 
373 	mav[mac] = (char *)NULL;
374 }
375 
376 /*
377  * This function is called whenever the group permissions of a file
378  * is being modified.  According to the chmod(1) manpage, any
379  * change made to the group permissions must be applied to both
380  * the acl mask and the acl's GROUP_OBJ.  The chmod(2) already
381  * set the mask, so this routine needs to make the same change
382  * to the GROUP_OBJ.
383  */
384 static void
385 handle_acl(char *name, o_mode_t group_clear_bits, o_mode_t group_set_bits)
386 {
387 	int aclcnt, n;
388 	aclent_t *aclp, *tp;
389 	o_mode_t newperm;
390 
391 	if ((aclcnt = acl(name, GETACLCNT, 0, NULL)) <= MIN_ACL_ENTRIES)
392 		return;	/* it's just a trivial acl; no need to change it */
393 
394 	if ((aclp = (aclent_t *)malloc((sizeof (aclent_t)) * aclcnt))
395 	    == NULL) {
396 		perror("chmod");
397 		exit(2);
398 	}
399 
400 	if (acl(name, GETACL, aclcnt, aclp) < 0) {
401 		free(aclp);
402 		(void) fprintf(stderr, "chmod: ");
403 		perror(name);
404 		return;
405 	}
406 
407 	for (tp = aclp, n = aclcnt; n--; tp++) {
408 		if (tp->a_type == GROUP_OBJ) {
409 			newperm = tp->a_perm;
410 			if (group_clear_bits != 0)
411 				newperm &= ~group_clear_bits;
412 			if (group_set_bits != 0)
413 				newperm |= group_set_bits;
414 			if (newperm != tp->a_perm) {
415 				tp->a_perm = newperm;
416 				if (acl(name, SETACL, aclcnt, aclp)
417 				    < 0) {
418 					(void) fprintf(stderr, "chmod: ");
419 					perror(name);
420 				}
421 			}
422 			break;
423 		}
424 	}
425 	free(aclp);
426 }
427