xref: /titanic_41/usr/src/lib/libcmd/common/chmod.c (revision 2a8d6eba033e4713ab12b61178f0513f1f075482)
1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1992-2008 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                  Common Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *            http://www.opensource.org/licenses/cpl1.0.txt             *
11 *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                 Glenn Fowler <gsf@research.att.com>                  *
18 *                  David Korn <dgk@research.att.com>                   *
19 *                                                                      *
20 ***********************************************************************/
21 #pragma prototyped
22 /*
23  * David Korn
24  * Glenn Fowler
25  * AT&T Research
26  *
27  * chmod
28  */
29 
30 static const char usage[] =
31 "[-?\n@(#)$Id: chmod (AT&T Research) 2007-09-10 $\n]"
32 USAGE_LICENSE
33 "[+NAME?chmod - change the access permissions of files]"
34 "[+DESCRIPTION?\bchmod\b changes the permission of each file "
35 	"according to mode, which can be either a symbolic representation "
36 	"of changes to make, or an octal number representing the bit "
37 	"pattern for the new permissions.]"
38 "[+?Symbolic mode strings consist of one or more comma separated list "
39 	"of operations that can be perfomed on the mode. Each operation is of "
40 	"the form \auser\a \aop\a \aperm\a where \auser\a is zero or more of "
41 	"the following letters:]{"
42 	"[+u?User permission bits.]"
43 	"[+g?Group permission bits.]"
44 	"[+o?Other permission bits.]"
45 	"[+a?All permission bits. This is the default if none are specified.]"
46 	"}"
47 "[+?The \aperm\a portion consists of zero or more of the following letters:]{"
48 	"[+r?Read permission.]"
49 	"[+s?Setuid when \bu\b is selected for \awho\a and setgid when \bg\b "
50 		"is selected for \awho\a.]"
51 	"[+w?Write permission.]"
52 	"[+x?Execute permission for files, search permission for directories.]"
53 	"[+X?Same as \bx\b except that it is ignored for files that do not "
54 		"already have at least one \bx\b bit set.]"
55 	"[+l?Exclusive lock bit on systems that support it. Group execute "
56 		"must be off.]"
57 	"[+t?Sticky bit on systems that support it.]"
58 	"}"
59 "[+?The \aop\a portion consists of one or more of the following characters:]{"
60 	"[++?Cause the permission selected to be added to the existing "
61 		"permissions. | is equivalent to +.]"
62 	"[+-?Cause the permission selected to be removed to the existing "
63 		"permissions.]"
64 	"[+=?Cause the permission to be set to the given permissions.]"
65 	"[+&?Cause the permission selected to be \aand\aed with the existing "
66 		"permissions.]"
67 	"[+^?Cause the permission selected to be propagated to more "
68 		"restrictive groups.]"
69 	"}"
70 "[+?Symbolic modes with the \auser\a portion omitted are subject to "
71 	"\bumask\b(2) settings unless the \b=\b \aop\a or the "
72 	"\b--ignore-umask\b option is specified.]"
73 "[+?A numeric mode is from one to four octal digits (0-7), "
74 	"derived by adding up the bits with values 4, 2, and 1. "
75 	"Any omitted digits are assumed to be leading zeros. The "
76 	"first digit selects the set user ID (4) and set group ID "
77 	"(2) and save text image (1) attributes. The second digit "
78 	"selects permissions for the user who owns the file: read "
79 	"(4), write (2), and execute (1); the third selects permissions"
80 	"for other users in the file's group, with the same values; "
81 	"and the fourth for other users not in the file's group, with "
82 	"the same values.]"
83 
84 "[+?For symbolic links, by default, \bchmod\b changes the mode on the file "
85 	"referenced by the symbolic link, not on the symbolic link itself. "
86 	"The \b-h\b options can be specified to change the mode of the link. "
87 	"When traversing directories with \b-R\b, \bchmod\b either follows "
88 	"symbolic links or does not follow symbolic links, based on the "
89 	"options \b-H\b, \b-L\b, and \b-P\b. The configuration parameter "
90 	"\bPATH_RESOLVE\b determines the default behavior if none of these "
91 	"options is specified.]"
92 
93 "[+?When the \b-c\b or \b-v\b options are specified, change notifications "
94 	"are written to standard output using the format, "
95 	"\b%s: mode changed to %0.4o (%s)\b, with arguments of the "
96 	"pathname, the numeric mode, and the resulting permission bits as "
97 	"would be displayed by the \bls\b command.]"
98 
99 "[+?For backwards compatibility, if an invalid option is given that is a valid "
100 	"symbolic mode specification, \bchmod\b treats this as a mode "
101 	"specification rather than as an option specification.]"
102 
103 "[H:metaphysical?Follow symbolic links for command arguments; otherwise don't "
104 	"follow symbolic links when traversing directories.]"
105 "[L:logical|follow?Follow symbolic links when traversing directories.]"
106 "[P:physical|nofollow?Don't follow symbolic links when traversing directories.]"
107 "[R:recursive?Change the mode for files in subdirectories recursively.]"
108 "[c:changes?Describe only files whose permission actually change.]"
109 "[f:quiet|silent?Do not report files whose permissioins fail to change.]"
110 "[h:symlink?Change the mode of the symbolic links on systems that "
111 	"support this.]"
112 "[i:ignore-umask?Ignore the \bumask\b(2) value in symbolic mode "
113 	"expressions. This is probably how you expect \bchmod\b to work.]"
114 "[n:show?Show actions but do not change any file modes.]"
115 "[F:reference?Omit the \amode\a operand and use the mode of \afile\a "
116 	"instead.]:[file]"
117 "[v:verbose?Describe changed permissions of all files.]"
118 "\n"
119 "\nmode file ...\n"
120 "\n"
121 "[+EXIT STATUS?]{"
122 	"[+0?All files changed successfully.]"
123 	"[+>0?Unable to change mode of one or more files.]"
124 "}"
125 "[+SEE ALSO?\bchgrp\b(1), \bchown\b(1), \btw\b(1), \bgetconf\b(1), \bls\b(1), "
126 	"\bumask\b(2)]"
127 ;
128 
129 
130 #if defined(__STDPP__directive) && defined(__STDPP__hide)
131 __STDPP__directive pragma pp:hide lchmod
132 #else
133 #define lchmod		______lchmod
134 #endif
135 
136 #include <cmd.h>
137 #include <ls.h>
138 #include <fts.h>
139 
140 #include "FEATURE/symlink"
141 
142 #if defined(__STDPP__directive) && defined(__STDPP__hide)
143 __STDPP__directive pragma pp:nohide lchmod
144 #else
145 #undef	lchmod
146 #endif
147 
148 extern int	lchmod(const char*, mode_t);
149 
150 int
151 b_chmod(int argc, char** argv, void* context)
152 {
153 	register int	mode;
154 	register int	force = 0;
155 	register int	flags;
156 	register char*	amode = 0;
157 	register FTS*	fts;
158 	register FTSENT*ent;
159 	char*		last;
160 	int		(*chmodf)(const char*, mode_t);
161 	int		notify = 0;
162 	int		ignore = 0;
163 	int		show = 0;
164 #if _lib_lchmod
165 	int		chlink = 0;
166 #endif
167 	struct stat	st;
168 
169 	cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
170 	flags = fts_flags() | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR;
171 
172 	/*
173 	 * NOTE: we diverge from the normal optget boilerplate
174 	 *	 to allow `chmod -x etc' to fall through
175 	 */
176 
177 	for (;;)
178 	{
179 		switch (optget(argv, usage))
180 		{
181 		case 'c':
182 			notify = 1;
183 			continue;
184 		case 'f':
185 			force = 1;
186 			continue;
187 		case 'h':
188 #if _lib_lchmod
189 			chlink = 1;
190 #endif
191 			continue;
192 		case 'i':
193 			ignore = 1;
194 			continue;
195 		case 'n':
196 			show = 1;
197 			continue;
198 		case 'v':
199 			notify = 2;
200 			continue;
201 		case 'F':
202 			if (stat(opt_info.arg, &st))
203 				error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
204 			mode = st.st_mode;
205 			amode = "";
206 			continue;
207 		case 'H':
208 			flags |= FTS_META|FTS_PHYSICAL;
209 			continue;
210 		case 'L':
211 			flags &= ~(FTS_META|FTS_PHYSICAL);
212 			continue;
213 		case 'P':
214 			flags &= ~FTS_META;
215 			flags |= FTS_PHYSICAL;
216 			continue;
217 		case 'R':
218 			flags &= ~FTS_TOP;
219 			continue;
220 		case '?':
221 			error(ERROR_usage(2), "%s", opt_info.arg);
222 			break;
223 		}
224 		break;
225 	}
226 	argv += opt_info.index;
227 	if (error_info.errors || !*argv || !amode && !*(argv + 1))
228 		error(ERROR_usage(2), "%s", optusage(NiL));
229 	if (ignore)
230 		ignore = umask(0);
231 	if (amode)
232 		amode = 0;
233 	else
234 	{
235 		amode = *argv++;
236 		mode = strperm(amode, &last, 0);
237 		if (*last)
238 		{
239 			if (ignore)
240 				umask(ignore);
241 			error(ERROR_exit(1), "%s: invalid mode", amode);
242 		}
243 	}
244 	chmodf =
245 #if _lib_lchmod
246 		chlink ? lchmod :
247 #endif
248 		chmod;
249 	if (!(fts = fts_open(argv, flags, NiL)))
250 	{
251 		if (ignore)
252 			umask(ignore);
253 		error(ERROR_system(1), "%s: not found", *argv);
254 	}
255 	while (!sh_checksig(context) && (ent = fts_read(fts)))
256 		switch (ent->fts_info)
257 		{
258 		case FTS_SL:
259 			if (chmodf == chmod)
260 			{
261 				if (!(flags & FTS_PHYSICAL) || (flags & FTS_META) && ent->fts_level == 1)
262 					fts_set(NiL, ent, FTS_FOLLOW);
263 				break;
264 			}
265 			/*FALLTHROUGH*/
266 		case FTS_F:
267 		case FTS_D:
268 		case FTS_SLNONE:
269 		anyway:
270 			if (amode)
271 				mode = strperm(amode, &last, ent->fts_statp->st_mode);
272 			if (show || (*chmodf)(ent->fts_accpath, mode) >= 0)
273 			{
274 				if (notify == 2 || notify == 1 && (mode&S_IPERM) != (ent->fts_statp->st_mode&S_IPERM))
275 					sfprintf(sfstdout, "%s: mode changed to %0.4o (%s)\n", ent->fts_path, mode, fmtmode(mode, 1)+1);
276 			}
277 			else if (!force)
278 				error(ERROR_system(0), "%s: cannot change mode", ent->fts_accpath);
279 			break;
280 		case FTS_DC:
281 			if (!force)
282 				error(ERROR_warn(0), "%s: directory causes cycle", ent->fts_accpath);
283 			break;
284 		case FTS_DNR:
285 			if (!force)
286 				error(ERROR_system(0), "%s: cannot read directory", ent->fts_accpath);
287 			goto anyway;
288 		case FTS_DNX:
289 			if (!force)
290 				error(ERROR_system(0), "%s: cannot search directory", ent->fts_accpath);
291 			goto anyway;
292 		case FTS_NS:
293 			if (!force)
294 				error(ERROR_system(0), "%s: not found", ent->fts_accpath);
295 			break;
296 		}
297 	fts_close(fts);
298 	if (ignore)
299 		umask(ignore);
300 	return error_info.errors != 0;
301 }
302