1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1992-2012 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Eclipse Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
8 * *
9 * A copy of the License is available at *
10 * http://www.eclipse.org/org/documents/epl-v10.html *
11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) *
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) 2012-04-20 $\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|l:symlink?Change the mode of symbolic links on systems that "
111 "support \blchmod\b(2). Implies \b--physical\b.]"
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), \blchmod\b(1), \btw\b(1), \bgetconf\b(1), "
126 "\bls\b(1), \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_fix.h>
139
140 #ifndef ENOSYS
141 #define ENOSYS EINVAL
142 #endif
143
144 #include "FEATURE/symlink"
145
146 #if defined(__STDPP__directive) && defined(__STDPP__hide)
147 __STDPP__directive pragma pp:nohide lchmod
148 #else
149 #undef lchmod
150 #endif
151
152 extern int lchmod(const char*, mode_t);
153
154 /*
155 * NOTE: we only use the native lchmod() on symlinks just in case
156 * the implementation is a feckless stub
157 */
158
159 int
b_chmod(int argc,char ** argv,Shbltin_t * context)160 b_chmod(int argc, char** argv, Shbltin_t* context)
161 {
162 register int mode;
163 register int force = 0;
164 register int flags;
165 register char* amode = 0;
166 register FTS* fts;
167 register FTSENT*ent;
168 char* last;
169 int (*chmodf)(const char*, mode_t);
170 int logical = 1;
171 int notify = 0;
172 int ignore = 0;
173 int show = 0;
174 int chlink = 0;
175 struct stat st;
176
177 cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
178 flags = fts_flags() | FTS_META | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR;
179
180 /*
181 * NOTE: we diverge from the normal optget boilerplate
182 * to allow `chmod -x etc' to fall through
183 */
184
185 for (;;)
186 {
187 switch (optget(argv, usage))
188 {
189 case 'c':
190 notify = 1;
191 continue;
192 case 'f':
193 force = 1;
194 continue;
195 case 'h':
196 chlink = 1;
197 continue;
198 case 'i':
199 ignore = 1;
200 continue;
201 case 'n':
202 show = 1;
203 continue;
204 case 'v':
205 notify = 2;
206 continue;
207 case 'F':
208 if (stat(opt_info.arg, &st))
209 error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
210 mode = st.st_mode;
211 amode = "";
212 continue;
213 case 'H':
214 flags |= FTS_META|FTS_PHYSICAL;
215 logical = 0;
216 continue;
217 case 'L':
218 flags &= ~(FTS_META|FTS_PHYSICAL);
219 logical = 0;
220 continue;
221 case 'P':
222 flags &= ~FTS_META;
223 flags |= FTS_PHYSICAL;
224 logical = 0;
225 continue;
226 case 'R':
227 flags &= ~FTS_TOP;
228 logical = 0;
229 continue;
230 case '?':
231 error(ERROR_usage(2), "%s", opt_info.arg);
232 break;
233 }
234 break;
235 }
236 argv += opt_info.index;
237 if (error_info.errors || !*argv || !amode && !*(argv + 1))
238 error(ERROR_usage(2), "%s", optusage(NiL));
239 if (chlink)
240 {
241 flags &= ~FTS_META;
242 flags |= FTS_PHYSICAL;
243 logical = 0;
244 }
245 if (logical)
246 flags &= ~(FTS_META|FTS_PHYSICAL);
247 if (ignore)
248 ignore = umask(0);
249 if (amode)
250 amode = 0;
251 else
252 {
253 amode = *argv++;
254 mode = strperm(amode, &last, 0);
255 if (*last)
256 {
257 if (ignore)
258 umask(ignore);
259 error(ERROR_exit(1), "%s: invalid mode", amode);
260 }
261 }
262 if (!(fts = fts_open(argv, flags, NiL)))
263 {
264 if (ignore)
265 umask(ignore);
266 error(ERROR_system(1), "%s: not found", *argv);
267 }
268 while (!sh_checksig(context) && (ent = fts_read(fts)))
269 switch (ent->fts_info)
270 {
271 case FTS_SL:
272 case FTS_SLNONE:
273 if (chlink)
274 {
275 #if _lib_lchmod
276 chmodf = lchmod;
277 goto commit;
278 #else
279 if (!force)
280 {
281 errno = ENOSYS;
282 error(ERROR_system(0), "%s: cannot change symlink mode", ent->fts_path);
283 }
284 #endif
285 }
286 break;
287 case FTS_F:
288 case FTS_D:
289 anyway:
290 chmodf = chmod;
291 #if _lib_lchmod
292 commit:
293 #endif
294 if (amode)
295 mode = strperm(amode, &last, ent->fts_statp->st_mode);
296 if (show || (*chmodf)(ent->fts_accpath, mode) >= 0)
297 {
298 if (notify == 2 || notify == 1 && (mode&S_IPERM) != (ent->fts_statp->st_mode&S_IPERM))
299 sfprintf(sfstdout, "%s: mode changed to %0.4o (%s)\n", ent->fts_path, mode, fmtmode(mode, 1)+1);
300 }
301 else if (!force)
302 error(ERROR_system(0), "%s: cannot change mode", ent->fts_path);
303 break;
304 case FTS_DC:
305 if (!force)
306 error(ERROR_warn(0), "%s: directory causes cycle", ent->fts_path);
307 break;
308 case FTS_DNR:
309 if (!force)
310 error(ERROR_system(0), "%s: cannot read directory", ent->fts_path);
311 goto anyway;
312 case FTS_DNX:
313 if (!force)
314 error(ERROR_system(0), "%s: cannot search directory", ent->fts_path);
315 goto anyway;
316 case FTS_NS:
317 if (!force)
318 error(ERROR_system(0), "%s: not found", ent->fts_path);
319 break;
320 }
321 fts_close(fts);
322 if (ignore)
323 umask(ignore);
324 return error_info.errors != 0;
325 }
326