1da2e3ebdSchin /***********************************************************************
2da2e3ebdSchin *                                                                      *
3da2e3ebdSchin *               This software is part of the ast package               *
4b30d1939SAndy Fiddaman *          Copyright (c) 1992-2012 AT&T Intellectual Property          *
5da2e3ebdSchin *                      and is licensed under the                       *
6b30d1939SAndy Fiddaman *                 Eclipse Public License, Version 1.0                  *
77c2fbfb3SApril Chin *                    by AT&T Intellectual Property                     *
8da2e3ebdSchin *                                                                      *
9da2e3ebdSchin *                A copy of the License is available at                 *
10b30d1939SAndy Fiddaman *          http://www.eclipse.org/org/documents/epl-v10.html           *
11b30d1939SAndy Fiddaman *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12da2e3ebdSchin *                                                                      *
13da2e3ebdSchin *              Information and Software Systems Research               *
14da2e3ebdSchin *                            AT&T Research                             *
15da2e3ebdSchin *                           Florham Park NJ                            *
16da2e3ebdSchin *                                                                      *
17da2e3ebdSchin *                 Glenn Fowler <gsf@research.att.com>                  *
18da2e3ebdSchin *                  David Korn <dgk@research.att.com>                   *
19da2e3ebdSchin *                                                                      *
20da2e3ebdSchin ***********************************************************************/
21da2e3ebdSchin #pragma prototyped
22da2e3ebdSchin /*
23da2e3ebdSchin  * David Korn
24da2e3ebdSchin  * Glenn Fowler
25da2e3ebdSchin  * AT&T Research
26da2e3ebdSchin  *
27da2e3ebdSchin  * chgrp+chown
28da2e3ebdSchin  */
29da2e3ebdSchin 
30da2e3ebdSchin static const char usage_1[] =
31b30d1939SAndy Fiddaman "[-?@(#)$Id: chgrp (AT&T Research) 2012-04-20 $\n]"
32da2e3ebdSchin USAGE_LICENSE
33da2e3ebdSchin ;
34da2e3ebdSchin 
35da2e3ebdSchin static const char usage_grp_1[] =
36da2e3ebdSchin "[+NAME?chgrp - change the group ownership of files]"
37da2e3ebdSchin "[+DESCRIPTION?\bchgrp\b changes the group ownership of each file"
38da2e3ebdSchin "	to \agroup\a, which can be either a group name or a numeric"
39da2e3ebdSchin "	group id. The user ownership of each file may also be changed to"
40da2e3ebdSchin "	\auser\a by prepending \auser\a\b:\b to the group name.]"
41da2e3ebdSchin ;
42da2e3ebdSchin 
43da2e3ebdSchin static const char usage_own_1[] =
44da2e3ebdSchin "[+NAME?chown - change the ownership of files]"
45da2e3ebdSchin "[+DESCRIPTION?\bchown\b changes the ownership of each file"
46da2e3ebdSchin "	to \auser\a, which can be either a user name or a numeric"
47da2e3ebdSchin "	user id. The group ownership of each file may also be changed to"
48da2e3ebdSchin "	\auser\a by appending \b:\b\agroup\a to the user name.]"
49da2e3ebdSchin ;
50da2e3ebdSchin 
51da2e3ebdSchin static const char usage_2[] =
527c2fbfb3SApril Chin "[b:before?Only change files with \bctime\b before (less than) the "
537c2fbfb3SApril Chin     "\bmtime\b of \afile\a.]:[file]"
54da2e3ebdSchin "[c:changes?Describe only files whose ownership actually changes.]"
55da2e3ebdSchin "[f:quiet|silent?Do not report files whose ownership fails to change.]"
56b30d1939SAndy Fiddaman "[h|l:symlink?Change the ownership of symbolic links on systems that "
57b30d1939SAndy Fiddaman     "support \blchown\b(2). Implies \b--physical\b.]"
587c2fbfb3SApril Chin "[m:map?The first operand is interpreted as a file that contains a map "
597c2fbfb3SApril Chin     "of space separated \afrom_uid:from_gid to_uid:to_gid\a pairs. The "
607c2fbfb3SApril Chin     "\auid\a or \agid\a part of each pair may be omitted to mean any \auid\a "
617c2fbfb3SApril Chin     "or \agid\a. Ownership of files matching the \afrom\a part of any pair "
627c2fbfb3SApril Chin     "is changed to the corresponding \ato\a part of the pair. The matching "
637c2fbfb3SApril Chin     "for each file operand is in the order \auid\a:\agid\a, \auid\a:, "
647c2fbfb3SApril Chin     ":\agid\a. For a given file, once a \auid\a or \agid\a mapping is "
657c2fbfb3SApril Chin     "determined it is not overridden by any subsequent match. Unmatched "
667c2fbfb3SApril Chin     "files are silently ignored.]"
67da2e3ebdSchin "[n:show?Show actions but don't execute.]"
68b30d1939SAndy Fiddaman "[N:numeric?By default numeric user and group id operands are first "
69b30d1939SAndy Fiddaman     "interpreted as names; if no name exists then they are interpreted as "
70b30d1939SAndy Fiddaman     "explicit numeric ids. \b--numeric\b interprets numeric id operands as "
71b30d1939SAndy Fiddaman     "numeric ids.]"
727c2fbfb3SApril Chin "[r:reference?Omit the explicit ownership operand and use the ownership "
737c2fbfb3SApril Chin     "of \afile\a instead.]:[file]"
747c2fbfb3SApril Chin "[u:unmapped?Print a diagnostic for each file for which either the "
757c2fbfb3SApril Chin     "\auid\a or \agid\a or both were not mapped.]"
76da2e3ebdSchin "[v:verbose?Describe changed permissions of all files.]"
777c2fbfb3SApril Chin "[H:metaphysical?Follow symbolic links for command arguments; otherwise "
787c2fbfb3SApril Chin     "don't follow symbolic links when traversing directories.]"
79da2e3ebdSchin "[L:logical|follow?Follow symbolic links when traversing directories.]"
807c2fbfb3SApril Chin "[P:physical|nofollow?Don't follow symbolic links when traversing "
817c2fbfb3SApril Chin     "directories.]"
827c2fbfb3SApril Chin "[R:recursive?Recursively change ownership of directories and their "
837c2fbfb3SApril Chin     "contents.]"
84da2e3ebdSchin "[X:test?Canonicalize output for testing.]"
85da2e3ebdSchin 
86da2e3ebdSchin "\n"
87da2e3ebdSchin "\n"
88da2e3ebdSchin ;
89da2e3ebdSchin 
90da2e3ebdSchin static const char usage_3[] =
91da2e3ebdSchin " file ...\n"
92da2e3ebdSchin "\n"
93da2e3ebdSchin "[+EXIT STATUS?]{"
94da2e3ebdSchin 	"[+0?All files changed successfully.]"
95da2e3ebdSchin 	"[+>0?Unable to change ownership of one or more files.]"
96da2e3ebdSchin "}"
97b30d1939SAndy Fiddaman "[+SEE ALSO?\bchmod\b(1), \bchown\b(2), \btw\b(1), \bgetconf\b(1), \bls\b(1)]"
98da2e3ebdSchin ;
99da2e3ebdSchin 
100da2e3ebdSchin #if defined(__STDPP__directive) && defined(__STDPP__hide)
101da2e3ebdSchin __STDPP__directive pragma pp:hide lchown
102da2e3ebdSchin #else
103da2e3ebdSchin #define lchown		______lchown
104da2e3ebdSchin #endif
105da2e3ebdSchin 
106da2e3ebdSchin #include <cmd.h>
107da2e3ebdSchin #include <cdt.h>
108da2e3ebdSchin #include <ls.h>
109da2e3ebdSchin #include <ctype.h>
1103e14f97fSRoger A. Faulkner #include <fts_fix.h>
111da2e3ebdSchin 
112b30d1939SAndy Fiddaman #ifndef ENOSYS
113b30d1939SAndy Fiddaman #define ENOSYS	EINVAL
114b30d1939SAndy Fiddaman #endif
115b30d1939SAndy Fiddaman 
116da2e3ebdSchin #include "FEATURE/symlink"
117da2e3ebdSchin 
118da2e3ebdSchin #if defined(__STDPP__directive) && defined(__STDPP__hide)
119da2e3ebdSchin __STDPP__directive pragma pp:nohide lchown
120da2e3ebdSchin #else
121da2e3ebdSchin #undef	lchown
122da2e3ebdSchin #endif
123da2e3ebdSchin 
1247c2fbfb3SApril Chin typedef struct Key_s			/* uid/gid key			*/
1257c2fbfb3SApril Chin {
1267c2fbfb3SApril Chin 	int		uid;		/* uid				*/
1277c2fbfb3SApril Chin 	int		gid;		/* gid				*/
1287c2fbfb3SApril Chin } Key_t;
1297c2fbfb3SApril Chin 
1307c2fbfb3SApril Chin typedef struct Map_s			/* uid/gid map			*/
131da2e3ebdSchin {
132da2e3ebdSchin 	Dtlink_t	link;		/* dictionary link		*/
1337c2fbfb3SApril Chin 	Key_t		key;		/* key				*/
1347c2fbfb3SApril Chin 	Key_t		to;		/* map to these			*/
135da2e3ebdSchin } Map_t;
136da2e3ebdSchin 
137*4162633aSAndy Fiddaman /*
138*4162633aSAndy Fiddaman  * libast's struid() has the peculiar feature that it returns -1 in response
139*4162633aSAndy Fiddaman  * to not finding a UID for a name on the first call, and -2 for subsequent
140*4162633aSAndy Fiddaman  * calls for the same string.
141*4162633aSAndy Fiddaman  */
142da2e3ebdSchin #define NOID		(-1)
143*4162633aSAndy Fiddaman #define STILLNOID	(-2)
144da2e3ebdSchin 
145b30d1939SAndy Fiddaman #define OPT_CHOWN	0x0001		/* chown			*/
146b30d1939SAndy Fiddaman #define OPT_FORCE	0x0002		/* ignore errors		*/
147b30d1939SAndy Fiddaman #define OPT_GID		0x0004		/* have gid			*/
148b30d1939SAndy Fiddaman #define OPT_LCHOWN	0x0008		/* lchown			*/
149b30d1939SAndy Fiddaman #define OPT_NUMERIC	0x0010		/* favor numeric ids		*/
150b30d1939SAndy Fiddaman #define OPT_SHOW	0x0020		/* show but don't do		*/
151b30d1939SAndy Fiddaman #define OPT_TEST	0x0040		/* canonicalize output		*/
152b30d1939SAndy Fiddaman #define OPT_UID		0x0080		/* have uid			*/
153b30d1939SAndy Fiddaman #define OPT_UNMAPPED	0x0100		/* unmapped file diagnostic	*/
154b30d1939SAndy Fiddaman #define OPT_VERBOSE	0x0200		/* have uid			*/
155da2e3ebdSchin 
156da2e3ebdSchin extern int	lchown(const char*, uid_t, gid_t);
157da2e3ebdSchin 
158da2e3ebdSchin /*
159da2e3ebdSchin  * parse uid and gid from s
160da2e3ebdSchin  */
161da2e3ebdSchin 
162da2e3ebdSchin static void
getids(register char * s,char ** e,Key_t * key,int options)1637c2fbfb3SApril Chin getids(register char* s, char** e, Key_t* key, int options)
164da2e3ebdSchin {
165da2e3ebdSchin 	register char*	t;
166da2e3ebdSchin 	register int	n;
167b30d1939SAndy Fiddaman 	register int	m;
168da2e3ebdSchin 	char*		z;
169da2e3ebdSchin 	char		buf[64];
170da2e3ebdSchin 
1717c2fbfb3SApril Chin 	key->uid = key->gid = NOID;
172da2e3ebdSchin 	while (isspace(*s))
173da2e3ebdSchin 		s++;
174da2e3ebdSchin 	for (t = s; (n = *t) && n != ':' && n != '.' && !isspace(n); t++);
175da2e3ebdSchin 	if (n)
176da2e3ebdSchin 	{
177da2e3ebdSchin 		options |= OPT_CHOWN;
178da2e3ebdSchin 		if ((n = t++ - s) >= sizeof(buf))
179da2e3ebdSchin 			n = sizeof(buf) - 1;
180da2e3ebdSchin 		*((s = (char*)memcpy(buf, s, n)) + n) = 0;
181da2e3ebdSchin 	}
182da2e3ebdSchin 	if (options & OPT_CHOWN)
183da2e3ebdSchin 	{
184da2e3ebdSchin 		if (*s)
185da2e3ebdSchin 		{
186b30d1939SAndy Fiddaman 			n = (int)strtol(s, &z, 0);
187b30d1939SAndy Fiddaman 			if (*z || !(options & OPT_NUMERIC))
188da2e3ebdSchin 			{
189*4162633aSAndy Fiddaman 				if ((m = struid(s)) != NOID && m != STILLNOID)
190b30d1939SAndy Fiddaman 					n = m;
191b30d1939SAndy Fiddaman 				else if (*z)
192da2e3ebdSchin 					error(ERROR_exit(1), "%s: unknown user", s);
193da2e3ebdSchin 			}
1947c2fbfb3SApril Chin 			key->uid = n;
195da2e3ebdSchin 		}
196da2e3ebdSchin 		for (s = t; (n = *t) && !isspace(n); t++);
197da2e3ebdSchin 		if (n)
198da2e3ebdSchin 		{
199da2e3ebdSchin 			if ((n = t++ - s) >= sizeof(buf))
200da2e3ebdSchin 				n = sizeof(buf) - 1;
201da2e3ebdSchin 			*((s = (char*)memcpy(buf, s, n)) + n) = 0;
202da2e3ebdSchin 		}
203da2e3ebdSchin 	}
204da2e3ebdSchin 	if (*s)
205da2e3ebdSchin 	{
206b30d1939SAndy Fiddaman 		n = (int)strtol(s, &z, 0);
207b30d1939SAndy Fiddaman 		if (*z || !(options & OPT_NUMERIC))
208da2e3ebdSchin 		{
209*4162633aSAndy Fiddaman 			if ((m = strgid(s)) != NOID && m != STILLNOID)
210b30d1939SAndy Fiddaman 				n = m;
211b30d1939SAndy Fiddaman 			else if (*z)
212da2e3ebdSchin 				error(ERROR_exit(1), "%s: unknown group", s);
213da2e3ebdSchin 		}
2147c2fbfb3SApril Chin 		key->gid = n;
215da2e3ebdSchin 	}
216da2e3ebdSchin 	if (e)
217da2e3ebdSchin 		*e = t;
218da2e3ebdSchin }
219da2e3ebdSchin 
220b30d1939SAndy Fiddaman /*
221b30d1939SAndy Fiddaman  * NOTE: we only use the native lchown() on symlinks just in case
222b30d1939SAndy Fiddaman  *	 the implementation is a feckless stub
223b30d1939SAndy Fiddaman  */
224b30d1939SAndy Fiddaman 
225da2e3ebdSchin int
b_chgrp(int argc,char ** argv,Shbltin_t * context)226b30d1939SAndy Fiddaman b_chgrp(int argc, char** argv, Shbltin_t* context)
227da2e3ebdSchin {
228da2e3ebdSchin 	register int	options = 0;
229da2e3ebdSchin 	register char*	s;
230da2e3ebdSchin 	register Map_t*	m;
231da2e3ebdSchin 	register FTS*	fts;
232da2e3ebdSchin 	register FTSENT*ent;
2337c2fbfb3SApril Chin 	register int	i;
234da2e3ebdSchin 	Dt_t*		map = 0;
23534f9b3eeSRoland Mainz 	int		logical = 1;
236da2e3ebdSchin 	int		flags;
237da2e3ebdSchin 	int		uid;
238da2e3ebdSchin 	int		gid;
239da2e3ebdSchin 	char*		op;
240da2e3ebdSchin 	char*		usage;
2417c2fbfb3SApril Chin 	char*		t;
242da2e3ebdSchin 	Sfio_t*		sp;
2437c2fbfb3SApril Chin 	unsigned long	before;
244da2e3ebdSchin 	Dtdisc_t	mapdisc;
2457c2fbfb3SApril Chin 	Key_t		keys[3];
2467c2fbfb3SApril Chin 	Key_t		key;
247da2e3ebdSchin 	struct stat	st;
248da2e3ebdSchin 	int		(*chownf)(const char*, uid_t, gid_t);
249da2e3ebdSchin 
250da2e3ebdSchin 	cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
251b30d1939SAndy Fiddaman 	flags = fts_flags() | FTS_META | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR;
2527c2fbfb3SApril Chin 	before = ~0;
253da2e3ebdSchin 	if (!(sp = sfstropen()))
254da2e3ebdSchin 		error(ERROR_SYSTEM|3, "out of space");
255da2e3ebdSchin 	sfputr(sp, usage_1, -1);
256da2e3ebdSchin 	if (error_info.id[2] == 'g')
257da2e3ebdSchin 		sfputr(sp, usage_grp_1, -1);
258da2e3ebdSchin 	else
259da2e3ebdSchin 	{
260da2e3ebdSchin 		sfputr(sp, usage_own_1, -1);
261da2e3ebdSchin 		options |= OPT_CHOWN;
262da2e3ebdSchin 	}
263da2e3ebdSchin 	sfputr(sp, usage_2, -1);
264da2e3ebdSchin 	if (options & OPT_CHOWN)
265da2e3ebdSchin 		sfputr(sp, ERROR_translate(0, 0, 0, "[owner[:group]]"), -1);
266da2e3ebdSchin 	else
267da2e3ebdSchin 		sfputr(sp, ERROR_translate(0, 0, 0, "[[owner:]group]"), -1);
268da2e3ebdSchin 	sfputr(sp, usage_3, -1);
269da2e3ebdSchin 	if (!(usage = sfstruse(sp)))
270da2e3ebdSchin 		error(ERROR_SYSTEM|3, "out of space");
271da2e3ebdSchin 	for (;;)
272da2e3ebdSchin 	{
273da2e3ebdSchin 		switch (optget(argv, usage))
274da2e3ebdSchin 		{
2757c2fbfb3SApril Chin 		case 'b':
2767c2fbfb3SApril Chin 			if (stat(opt_info.arg, &st))
2777c2fbfb3SApril Chin 				error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
2787c2fbfb3SApril Chin 			before = st.st_mtime;
2797c2fbfb3SApril Chin 			continue;
280da2e3ebdSchin 		case 'c':
281da2e3ebdSchin 		case 'v':
282da2e3ebdSchin 			options |= OPT_VERBOSE;
283da2e3ebdSchin 			continue;
284da2e3ebdSchin 		case 'f':
285da2e3ebdSchin 			options |= OPT_FORCE;
286da2e3ebdSchin 			continue;
287b30d1939SAndy Fiddaman 		case 'h':
288da2e3ebdSchin 			options |= OPT_LCHOWN;
289da2e3ebdSchin 			continue;
290da2e3ebdSchin 		case 'm':
291da2e3ebdSchin 			memset(&mapdisc, 0, sizeof(mapdisc));
2927c2fbfb3SApril Chin 			mapdisc.key = offsetof(Map_t, key);
2937c2fbfb3SApril Chin 			mapdisc.size = sizeof(Key_t);
294b30d1939SAndy Fiddaman 			if (!(map = dtopen(&mapdisc, Dtset)))
295da2e3ebdSchin 				error(ERROR_exit(1), "out of space [id map]");
296da2e3ebdSchin 			continue;
297da2e3ebdSchin 		case 'n':
298da2e3ebdSchin 			options |= OPT_SHOW;
299da2e3ebdSchin 			continue;
300b30d1939SAndy Fiddaman 		case 'N':
301b30d1939SAndy Fiddaman 			options |= OPT_NUMERIC;
302b30d1939SAndy Fiddaman 			continue;
303da2e3ebdSchin 		case 'r':
304da2e3ebdSchin 			if (stat(opt_info.arg, &st))
305da2e3ebdSchin 				error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
306da2e3ebdSchin 			uid = st.st_uid;
307da2e3ebdSchin 			gid = st.st_gid;
308da2e3ebdSchin 			options |= OPT_UID|OPT_GID;
309da2e3ebdSchin 			continue;
3107c2fbfb3SApril Chin 		case 'u':
3117c2fbfb3SApril Chin 			options |= OPT_UNMAPPED;
3127c2fbfb3SApril Chin 			continue;
313da2e3ebdSchin 		case 'H':
314da2e3ebdSchin 			flags |= FTS_META|FTS_PHYSICAL;
31534f9b3eeSRoland Mainz 			logical = 0;
316da2e3ebdSchin 			continue;
317da2e3ebdSchin 		case 'L':
318da2e3ebdSchin 			flags &= ~(FTS_META|FTS_PHYSICAL);
31934f9b3eeSRoland Mainz 			logical = 0;
320da2e3ebdSchin 			continue;
321da2e3ebdSchin 		case 'P':
322da2e3ebdSchin 			flags &= ~FTS_META;
323da2e3ebdSchin 			flags |= FTS_PHYSICAL;
32434f9b3eeSRoland Mainz 			logical = 0;
325da2e3ebdSchin 			continue;
326da2e3ebdSchin 		case 'R':
327da2e3ebdSchin 			flags &= ~FTS_TOP;
32834f9b3eeSRoland Mainz 			logical = 0;
329da2e3ebdSchin 			continue;
330da2e3ebdSchin 		case 'X':
331da2e3ebdSchin 			options |= OPT_TEST;
332da2e3ebdSchin 			continue;
333da2e3ebdSchin 		case ':':
334da2e3ebdSchin 			error(2, "%s", opt_info.arg);
335da2e3ebdSchin 			continue;
336da2e3ebdSchin 		case '?':
337da2e3ebdSchin 			error(ERROR_usage(2), "%s", opt_info.arg);
338da2e3ebdSchin 			break;
339da2e3ebdSchin 		}
340da2e3ebdSchin 		break;
341da2e3ebdSchin 	}
342da2e3ebdSchin 	argv += opt_info.index;
343da2e3ebdSchin 	argc -= opt_info.index;
344da2e3ebdSchin 	if (error_info.errors || argc < 2)
345da2e3ebdSchin 		error(ERROR_usage(2), "%s", optusage(NiL));
346da2e3ebdSchin 	s = *argv;
347b30d1939SAndy Fiddaman 	if (options & OPT_LCHOWN)
348b30d1939SAndy Fiddaman 	{
349b30d1939SAndy Fiddaman 		flags &= ~FTS_META;
350b30d1939SAndy Fiddaman 		flags |= FTS_PHYSICAL;
351b30d1939SAndy Fiddaman 		logical = 0;
352b30d1939SAndy Fiddaman 	}
35334f9b3eeSRoland Mainz 	if (logical)
35434f9b3eeSRoland Mainz 		flags &= ~(FTS_META|FTS_PHYSICAL);
355da2e3ebdSchin 	if (map)
356da2e3ebdSchin 	{
357da2e3ebdSchin 		if (streq(s, "-"))
358da2e3ebdSchin 			sp = sfstdin;
359da2e3ebdSchin 		else if (!(sp = sfopen(NiL, s, "r")))
360da2e3ebdSchin 			error(ERROR_exit(1), "%s: cannot read", s);
361da2e3ebdSchin 		while (s = sfgetr(sp, '\n', 1))
362da2e3ebdSchin 		{
3637c2fbfb3SApril Chin 			getids(s, &t, &key, options);
3647c2fbfb3SApril Chin 			if (!(m = (Map_t*)dtmatch(map, &key)))
365da2e3ebdSchin 			{
3667c2fbfb3SApril Chin 				if (!(m = (Map_t*)stakalloc(sizeof(Map_t))))
367da2e3ebdSchin 					error(ERROR_exit(1), "out of space [id dictionary]");
3687c2fbfb3SApril Chin 				m->key = key;
3697c2fbfb3SApril Chin 				m->to.uid = m->to.gid = NOID;
3707c2fbfb3SApril Chin 				dtinsert(map, m);
371da2e3ebdSchin 			}
3727c2fbfb3SApril Chin 			getids(t, NiL, &m->to, options);
373da2e3ebdSchin 		}
374da2e3ebdSchin 		if (sp != sfstdin)
375da2e3ebdSchin 			sfclose(sp);
3767c2fbfb3SApril Chin 		keys[1].gid = keys[2].uid = NOID;
377da2e3ebdSchin 	}
378da2e3ebdSchin 	else if (!(options & (OPT_UID|OPT_GID)))
379da2e3ebdSchin 	{
3807c2fbfb3SApril Chin 		getids(s, NiL, &key, options);
3817c2fbfb3SApril Chin 		if ((uid = key.uid) != NOID)
382da2e3ebdSchin 			options |= OPT_UID;
3837c2fbfb3SApril Chin 		if ((gid = key.gid) != NOID)
384da2e3ebdSchin 			options |= OPT_GID;
385da2e3ebdSchin 	}
386da2e3ebdSchin 	switch (options & (OPT_UID|OPT_GID))
387da2e3ebdSchin 	{
388da2e3ebdSchin 	case OPT_UID:
389da2e3ebdSchin 		s = ERROR_translate(0, 0, 0, " owner");
390da2e3ebdSchin 		break;
391da2e3ebdSchin 	case OPT_GID:
392da2e3ebdSchin 		s = ERROR_translate(0, 0, 0, " group");
393da2e3ebdSchin 		break;
394da2e3ebdSchin 	case OPT_UID|OPT_GID:
395da2e3ebdSchin 		s = ERROR_translate(0, 0, 0, " owner and group");
396da2e3ebdSchin 		break;
397da2e3ebdSchin 	default:
398da2e3ebdSchin 		s = "";
399da2e3ebdSchin 		break;
400da2e3ebdSchin 	}
401da2e3ebdSchin 	if (!(fts = fts_open(argv + 1, flags, NiL)))
402da2e3ebdSchin 		error(ERROR_system(1), "%s: not found", argv[1]);
4037c2fbfb3SApril Chin 	while (!sh_checksig(context) && (ent = fts_read(fts)))
404da2e3ebdSchin 		switch (ent->fts_info)
405da2e3ebdSchin 		{
406da2e3ebdSchin 		case FTS_SL:
407da2e3ebdSchin 		case FTS_SLNONE:
408b30d1939SAndy Fiddaman 			if (options & OPT_LCHOWN)
409b30d1939SAndy Fiddaman 			{
410b30d1939SAndy Fiddaman #if _lib_lchown
411b30d1939SAndy Fiddaman 				chownf = lchown;
412b30d1939SAndy Fiddaman 				op = "lchown";
413b30d1939SAndy Fiddaman 				goto commit;
414b30d1939SAndy Fiddaman #else
415b30d1939SAndy Fiddaman 				if (!(options & OPT_FORCE))
416b30d1939SAndy Fiddaman 				{
417b30d1939SAndy Fiddaman 					errno = ENOSYS;
418b30d1939SAndy Fiddaman 					error(ERROR_system(0), "%s: cannot change symlink owner/group", ent->fts_path);
419b30d1939SAndy Fiddaman 				}
420b30d1939SAndy Fiddaman #endif
421b30d1939SAndy Fiddaman 			}
422b30d1939SAndy Fiddaman 			break;
423b30d1939SAndy Fiddaman 		case FTS_F:
424b30d1939SAndy Fiddaman 		case FTS_D:
425da2e3ebdSchin 		anyway:
426b30d1939SAndy Fiddaman 			chownf = chown;
427b30d1939SAndy Fiddaman 			op = "chown";
428b30d1939SAndy Fiddaman 		commit:
4297c2fbfb3SApril Chin 			if ((unsigned long)ent->fts_statp->st_ctime >= before)
4307c2fbfb3SApril Chin 				break;
431da2e3ebdSchin 			if (map)
432da2e3ebdSchin 			{
433da2e3ebdSchin 				options &= ~(OPT_UID|OPT_GID);
4347c2fbfb3SApril Chin 				uid = gid = NOID;
4357c2fbfb3SApril Chin 				keys[0].uid = keys[1].uid = ent->fts_statp->st_uid;
4367c2fbfb3SApril Chin 				keys[0].gid = keys[2].gid = ent->fts_statp->st_gid;
4377c2fbfb3SApril Chin 				i = 0;
4387c2fbfb3SApril Chin 				do
439da2e3ebdSchin 				{
4407c2fbfb3SApril Chin 					if (m = (Map_t*)dtmatch(map, &keys[i]))
4417c2fbfb3SApril Chin 					{
4427c2fbfb3SApril Chin 						if (uid == NOID && m->to.uid != NOID)
4437c2fbfb3SApril Chin 						{
4447c2fbfb3SApril Chin 							uid = m->to.uid;
4457c2fbfb3SApril Chin 							options |= OPT_UID;
4467c2fbfb3SApril Chin 						}
4477c2fbfb3SApril Chin 						if (gid == NOID && m->to.gid != NOID)
4487c2fbfb3SApril Chin 						{
4497c2fbfb3SApril Chin 							gid = m->to.gid;
4507c2fbfb3SApril Chin 							options |= OPT_GID;
4517c2fbfb3SApril Chin 						}
4527c2fbfb3SApril Chin 					}
4537c2fbfb3SApril Chin 				} while (++i < elementsof(keys) && (uid == NOID || gid == NOID));
454da2e3ebdSchin 			}
455da2e3ebdSchin 			else
456da2e3ebdSchin 			{
457da2e3ebdSchin 				if (!(options & OPT_UID))
458da2e3ebdSchin 					uid = ent->fts_statp->st_uid;
459da2e3ebdSchin 				if (!(options & OPT_GID))
460da2e3ebdSchin 					gid = ent->fts_statp->st_gid;
461da2e3ebdSchin 			}
4627c2fbfb3SApril Chin 			if ((options & OPT_UNMAPPED) && (uid == NOID || gid == NOID))
4637c2fbfb3SApril Chin 			{
4647c2fbfb3SApril Chin 				if (uid == NOID && gid == NOID)
4657c2fbfb3SApril Chin 					error(ERROR_warn(0), "%s: uid and gid not mapped", ent->fts_path);
4667c2fbfb3SApril Chin 				else if (uid == NOID)
4677c2fbfb3SApril Chin 					error(ERROR_warn(0), "%s: uid not mapped", ent->fts_path);
4687c2fbfb3SApril Chin 				else
4697c2fbfb3SApril Chin 					error(ERROR_warn(0), "%s: gid not mapped", ent->fts_path);
4707c2fbfb3SApril Chin 			}
4717c2fbfb3SApril Chin 			if (uid != ent->fts_statp->st_uid && uid != NOID || gid != ent->fts_statp->st_gid && gid != NOID)
472da2e3ebdSchin 			{
473da2e3ebdSchin 				if (options & (OPT_SHOW|OPT_VERBOSE))
474da2e3ebdSchin 				{
475da2e3ebdSchin 					if (options & OPT_TEST)
476da2e3ebdSchin 					{
477da2e3ebdSchin 						ent->fts_statp->st_uid = 0;
478da2e3ebdSchin 						ent->fts_statp->st_gid = 0;
479da2e3ebdSchin 					}
4807c2fbfb3SApril Chin 					sfprintf(sfstdout, "%s uid:%05d->%05d gid:%05d->%05d %s\n", op, ent->fts_statp->st_uid, uid, ent->fts_statp->st_gid, gid, ent->fts_path);
481da2e3ebdSchin 				}
482da2e3ebdSchin 				if (!(options & OPT_SHOW) && (*chownf)(ent->fts_accpath, uid, gid) && !(options & OPT_FORCE))
483b30d1939SAndy Fiddaman 					error(ERROR_system(0), "%s: cannot change%s", ent->fts_path, s);
484da2e3ebdSchin 			}
485da2e3ebdSchin 			break;
486da2e3ebdSchin 		case FTS_DC:
487da2e3ebdSchin 			if (!(options & OPT_FORCE))
488b30d1939SAndy Fiddaman 				error(ERROR_warn(0), "%s: directory causes cycle", ent->fts_path);
489da2e3ebdSchin 			break;
490da2e3ebdSchin 		case FTS_DNR:
491da2e3ebdSchin 			if (!(options & OPT_FORCE))
492b30d1939SAndy Fiddaman 				error(ERROR_system(0), "%s: cannot read directory", ent->fts_path);
493da2e3ebdSchin 			goto anyway;
494da2e3ebdSchin 		case FTS_DNX:
495da2e3ebdSchin 			if (!(options & OPT_FORCE))
496b30d1939SAndy Fiddaman 				error(ERROR_system(0), "%s: cannot search directory", ent->fts_path);
497da2e3ebdSchin 			goto anyway;
498da2e3ebdSchin 		case FTS_NS:
499da2e3ebdSchin 			if (!(options & OPT_FORCE))
500b30d1939SAndy Fiddaman 				error(ERROR_system(0), "%s: not found", ent->fts_path);
501da2e3ebdSchin 			break;
502da2e3ebdSchin 		}
503da2e3ebdSchin 	fts_close(fts);
504da2e3ebdSchin 	if (map)
505da2e3ebdSchin 		dtclose(map);
506da2e3ebdSchin 	return error_info.errors != 0;
507da2e3ebdSchin }
508