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