1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1992-2011 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
30static const char usage[] =
31"[-?\n@(#)$Id: chmod (AT&T Research) 2010-07-28 $\n]"
32USAGE_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_fix.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
148extern int	lchmod(const char*, mode_t);
149
150int
151b_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		logical = 1;
162	int		notify = 0;
163	int		ignore = 0;
164	int		show = 0;
165#if _lib_lchmod
166	int		chlink = 0;
167#endif
168	struct stat	st;
169
170	cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
171	flags = fts_flags() | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR;
172
173	/*
174	 * NOTE: we diverge from the normal optget boilerplate
175	 *	 to allow `chmod -x etc' to fall through
176	 */
177
178	for (;;)
179	{
180		switch (optget(argv, usage))
181		{
182		case 'c':
183			notify = 1;
184			continue;
185		case 'f':
186			force = 1;
187			continue;
188		case 'h':
189#if _lib_lchmod
190			chlink = 1;
191#endif
192			continue;
193		case 'i':
194			ignore = 1;
195			continue;
196		case 'n':
197			show = 1;
198			continue;
199		case 'v':
200			notify = 2;
201			continue;
202		case 'F':
203			if (stat(opt_info.arg, &st))
204				error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
205			mode = st.st_mode;
206			amode = "";
207			continue;
208		case 'H':
209			flags |= FTS_META|FTS_PHYSICAL;
210			logical = 0;
211			continue;
212		case 'L':
213			flags &= ~(FTS_META|FTS_PHYSICAL);
214			logical = 0;
215			continue;
216		case 'P':
217			flags &= ~FTS_META;
218			flags |= FTS_PHYSICAL;
219			logical = 0;
220			continue;
221		case 'R':
222			flags &= ~FTS_TOP;
223			logical = 0;
224			continue;
225		case '?':
226			error(ERROR_usage(2), "%s", opt_info.arg);
227			break;
228		}
229		break;
230	}
231	argv += opt_info.index;
232	if (error_info.errors || !*argv || !amode && !*(argv + 1))
233		error(ERROR_usage(2), "%s", optusage(NiL));
234	if (logical)
235		flags &= ~(FTS_META|FTS_PHYSICAL);
236	if (ignore)
237		ignore = umask(0);
238	if (amode)
239		amode = 0;
240	else
241	{
242		amode = *argv++;
243		mode = strperm(amode, &last, 0);
244		if (*last)
245		{
246			if (ignore)
247				umask(ignore);
248			error(ERROR_exit(1), "%s: invalid mode", amode);
249		}
250	}
251	chmodf =
252#if _lib_lchmod
253		chlink ? lchmod :
254#endif
255		chmod;
256	if (!(fts = fts_open(argv, flags, NiL)))
257	{
258		if (ignore)
259			umask(ignore);
260		error(ERROR_system(1), "%s: not found", *argv);
261	}
262	while (!sh_checksig(context) && (ent = fts_read(fts)))
263		switch (ent->fts_info)
264		{
265		case FTS_SL:
266			if (chmodf == chmod)
267			{
268				if (!(flags & FTS_PHYSICAL) || (flags & FTS_META) && ent->fts_level == 1)
269					fts_set(NiL, ent, FTS_FOLLOW);
270				break;
271			}
272			/*FALLTHROUGH*/
273		case FTS_F:
274		case FTS_D:
275		case FTS_SLNONE:
276		anyway:
277			if (amode)
278				mode = strperm(amode, &last, ent->fts_statp->st_mode);
279			if (show || (*chmodf)(ent->fts_accpath, mode) >= 0)
280			{
281				if (notify == 2 || notify == 1 && (mode&S_IPERM) != (ent->fts_statp->st_mode&S_IPERM))
282					sfprintf(sfstdout, "%s: mode changed to %0.4o (%s)\n", ent->fts_path, mode, fmtmode(mode, 1)+1);
283			}
284			else if (!force)
285				error(ERROR_system(0), "%s: cannot change mode", ent->fts_path);
286			break;
287		case FTS_DC:
288			if (!force)
289				error(ERROR_warn(0), "%s: directory causes cycle", ent->fts_path);
290			break;
291		case FTS_DNR:
292			if (!force)
293				error(ERROR_system(0), "%s: cannot read directory", ent->fts_path);
294			goto anyway;
295		case FTS_DNX:
296			if (!force)
297				error(ERROR_system(0), "%s: cannot search directory", ent->fts_path);
298			goto anyway;
299		case FTS_NS:
300			if (!force)
301				error(ERROR_system(0), "%s: not found", ent->fts_path);
302			break;
303		}
304	fts_close(fts);
305	if (ignore)
306		umask(ignore);
307	return error_info.errors != 0;
308}
309