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  * chgrp+chown
28  */
29 
30 static const char usage_1[] =
31 "[-?@(#)$Id: chgrp (AT&T Research) 2012-04-20 $\n]"
32 USAGE_LICENSE
33 ;
34 
35 static const char usage_grp_1[] =
36 "[+NAME?chgrp - change the group ownership of files]"
37 "[+DESCRIPTION?\bchgrp\b changes the group ownership of each file"
38 "	to \agroup\a, which can be either a group name or a numeric"
39 "	group id. The user ownership of each file may also be changed to"
40 "	\auser\a by prepending \auser\a\b:\b to the group name.]"
41 ;
42 
43 static const char usage_own_1[] =
44 "[+NAME?chown - change the ownership of files]"
45 "[+DESCRIPTION?\bchown\b changes the ownership of each file"
46 "	to \auser\a, which can be either a user name or a numeric"
47 "	user id. The group ownership of each file may also be changed to"
48 "	\auser\a by appending \b:\b\agroup\a to the user name.]"
49 ;
50 
51 static const char usage_2[] =
52 "[b:before?Only change files with \bctime\b before (less than) the "
53     "\bmtime\b of \afile\a.]:[file]"
54 "[c:changes?Describe only files whose ownership actually changes.]"
55 "[f:quiet|silent?Do not report files whose ownership fails to change.]"
56 "[h|l:symlink?Change the ownership of symbolic links on systems that "
57     "support \blchown\b(2). Implies \b--physical\b.]"
58 "[m:map?The first operand is interpreted as a file that contains a map "
59     "of space separated \afrom_uid:from_gid to_uid:to_gid\a pairs. The "
60     "\auid\a or \agid\a part of each pair may be omitted to mean any \auid\a "
61     "or \agid\a. Ownership of files matching the \afrom\a part of any pair "
62     "is changed to the corresponding \ato\a part of the pair. The matching "
63     "for each file operand is in the order \auid\a:\agid\a, \auid\a:, "
64     ":\agid\a. For a given file, once a \auid\a or \agid\a mapping is "
65     "determined it is not overridden by any subsequent match. Unmatched "
66     "files are silently ignored.]"
67 "[n:show?Show actions but don't execute.]"
68 "[N:numeric?By default numeric user and group id operands are first "
69     "interpreted as names; if no name exists then they are interpreted as "
70     "explicit numeric ids. \b--numeric\b interprets numeric id operands as "
71     "numeric ids.]"
72 "[r:reference?Omit the explicit ownership operand and use the ownership "
73     "of \afile\a instead.]:[file]"
74 "[u:unmapped?Print a diagnostic for each file for which either the "
75     "\auid\a or \agid\a or both were not mapped.]"
76 "[v:verbose?Describe changed permissions of all files.]"
77 "[H:metaphysical?Follow symbolic links for command arguments; otherwise "
78     "don't follow symbolic links when traversing directories.]"
79 "[L:logical|follow?Follow symbolic links when traversing directories.]"
80 "[P:physical|nofollow?Don't follow symbolic links when traversing "
81     "directories.]"
82 "[R:recursive?Recursively change ownership of directories and their "
83     "contents.]"
84 "[X:test?Canonicalize output for testing.]"
85 
86 "\n"
87 "\n"
88 ;
89 
90 static const char usage_3[] =
91 " file ...\n"
92 "\n"
93 "[+EXIT STATUS?]{"
94 	"[+0?All files changed successfully.]"
95 	"[+>0?Unable to change ownership of one or more files.]"
96 "}"
97 "[+SEE ALSO?\bchmod\b(1), \bchown\b(2), \btw\b(1), \bgetconf\b(1), \bls\b(1)]"
98 ;
99 
100 #if defined(__STDPP__directive) && defined(__STDPP__hide)
101 __STDPP__directive pragma pp:hide lchown
102 #else
103 #define lchown		______lchown
104 #endif
105 
106 #include <cmd.h>
107 #include <cdt.h>
108 #include <ls.h>
109 #include <ctype.h>
110 #include <fts_fix.h>
111 
112 #ifndef ENOSYS
113 #define ENOSYS	EINVAL
114 #endif
115 
116 #include "FEATURE/symlink"
117 
118 #if defined(__STDPP__directive) && defined(__STDPP__hide)
119 __STDPP__directive pragma pp:nohide lchown
120 #else
121 #undef	lchown
122 #endif
123 
124 typedef struct Key_s			/* uid/gid key			*/
125 {
126 	int		uid;		/* uid				*/
127 	int		gid;		/* gid				*/
128 } Key_t;
129 
130 typedef struct Map_s			/* uid/gid map			*/
131 {
132 	Dtlink_t	link;		/* dictionary link		*/
133 	Key_t		key;		/* key				*/
134 	Key_t		to;		/* map to these			*/
135 } Map_t;
136 
137 /*
138  * libast's struid() has the peculiar feature that it returns -1 in response
139  * to not finding a UID for a name on the first call, and -2 for subsequent
140  * calls for the same string.
141  */
142 #define NOID		(-1)
143 #define STILLNOID	(-2)
144 
145 #define OPT_CHOWN	0x0001		/* chown			*/
146 #define OPT_FORCE	0x0002		/* ignore errors		*/
147 #define OPT_GID		0x0004		/* have gid			*/
148 #define OPT_LCHOWN	0x0008		/* lchown			*/
149 #define OPT_NUMERIC	0x0010		/* favor numeric ids		*/
150 #define OPT_SHOW	0x0020		/* show but don't do		*/
151 #define OPT_TEST	0x0040		/* canonicalize output		*/
152 #define OPT_UID		0x0080		/* have uid			*/
153 #define OPT_UNMAPPED	0x0100		/* unmapped file diagnostic	*/
154 #define OPT_VERBOSE	0x0200		/* have uid			*/
155 
156 extern int	lchown(const char*, uid_t, gid_t);
157 
158 /*
159  * parse uid and gid from s
160  */
161 
162 static void
getids(register char * s,char ** e,Key_t * key,int options)163 getids(register char* s, char** e, Key_t* key, int options)
164 {
165 	register char*	t;
166 	register int	n;
167 	register int	m;
168 	char*		z;
169 	char		buf[64];
170 
171 	key->uid = key->gid = NOID;
172 	while (isspace(*s))
173 		s++;
174 	for (t = s; (n = *t) && n != ':' && n != '.' && !isspace(n); t++);
175 	if (n)
176 	{
177 		options |= OPT_CHOWN;
178 		if ((n = t++ - s) >= sizeof(buf))
179 			n = sizeof(buf) - 1;
180 		*((s = (char*)memcpy(buf, s, n)) + n) = 0;
181 	}
182 	if (options & OPT_CHOWN)
183 	{
184 		if (*s)
185 		{
186 			n = (int)strtol(s, &z, 0);
187 			if (*z || !(options & OPT_NUMERIC))
188 			{
189 				if ((m = struid(s)) != NOID && m != STILLNOID)
190 					n = m;
191 				else if (*z)
192 					error(ERROR_exit(1), "%s: unknown user", s);
193 			}
194 			key->uid = n;
195 		}
196 		for (s = t; (n = *t) && !isspace(n); t++);
197 		if (n)
198 		{
199 			if ((n = t++ - s) >= sizeof(buf))
200 				n = sizeof(buf) - 1;
201 			*((s = (char*)memcpy(buf, s, n)) + n) = 0;
202 		}
203 	}
204 	if (*s)
205 	{
206 		n = (int)strtol(s, &z, 0);
207 		if (*z || !(options & OPT_NUMERIC))
208 		{
209 			if ((m = strgid(s)) != NOID && m != STILLNOID)
210 				n = m;
211 			else if (*z)
212 				error(ERROR_exit(1), "%s: unknown group", s);
213 		}
214 		key->gid = n;
215 	}
216 	if (e)
217 		*e = t;
218 }
219 
220 /*
221  * NOTE: we only use the native lchown() on symlinks just in case
222  *	 the implementation is a feckless stub
223  */
224 
225 int
b_chgrp(int argc,char ** argv,Shbltin_t * context)226 b_chgrp(int argc, char** argv, Shbltin_t* context)
227 {
228 	register int	options = 0;
229 	register char*	s;
230 	register Map_t*	m;
231 	register FTS*	fts;
232 	register FTSENT*ent;
233 	register int	i;
234 	Dt_t*		map = 0;
235 	int		logical = 1;
236 	int		flags;
237 	int		uid;
238 	int		gid;
239 	char*		op;
240 	char*		usage;
241 	char*		t;
242 	Sfio_t*		sp;
243 	unsigned long	before;
244 	Dtdisc_t	mapdisc;
245 	Key_t		keys[3];
246 	Key_t		key;
247 	struct stat	st;
248 	int		(*chownf)(const char*, uid_t, gid_t);
249 
250 	cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
251 	flags = fts_flags() | FTS_META | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR;
252 	before = ~0;
253 	if (!(sp = sfstropen()))
254 		error(ERROR_SYSTEM|3, "out of space");
255 	sfputr(sp, usage_1, -1);
256 	if (error_info.id[2] == 'g')
257 		sfputr(sp, usage_grp_1, -1);
258 	else
259 	{
260 		sfputr(sp, usage_own_1, -1);
261 		options |= OPT_CHOWN;
262 	}
263 	sfputr(sp, usage_2, -1);
264 	if (options & OPT_CHOWN)
265 		sfputr(sp, ERROR_translate(0, 0, 0, "[owner[:group]]"), -1);
266 	else
267 		sfputr(sp, ERROR_translate(0, 0, 0, "[[owner:]group]"), -1);
268 	sfputr(sp, usage_3, -1);
269 	if (!(usage = sfstruse(sp)))
270 		error(ERROR_SYSTEM|3, "out of space");
271 	for (;;)
272 	{
273 		switch (optget(argv, usage))
274 		{
275 		case 'b':
276 			if (stat(opt_info.arg, &st))
277 				error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
278 			before = st.st_mtime;
279 			continue;
280 		case 'c':
281 		case 'v':
282 			options |= OPT_VERBOSE;
283 			continue;
284 		case 'f':
285 			options |= OPT_FORCE;
286 			continue;
287 		case 'h':
288 			options |= OPT_LCHOWN;
289 			continue;
290 		case 'm':
291 			memset(&mapdisc, 0, sizeof(mapdisc));
292 			mapdisc.key = offsetof(Map_t, key);
293 			mapdisc.size = sizeof(Key_t);
294 			if (!(map = dtopen(&mapdisc, Dtset)))
295 				error(ERROR_exit(1), "out of space [id map]");
296 			continue;
297 		case 'n':
298 			options |= OPT_SHOW;
299 			continue;
300 		case 'N':
301 			options |= OPT_NUMERIC;
302 			continue;
303 		case 'r':
304 			if (stat(opt_info.arg, &st))
305 				error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
306 			uid = st.st_uid;
307 			gid = st.st_gid;
308 			options |= OPT_UID|OPT_GID;
309 			continue;
310 		case 'u':
311 			options |= OPT_UNMAPPED;
312 			continue;
313 		case 'H':
314 			flags |= FTS_META|FTS_PHYSICAL;
315 			logical = 0;
316 			continue;
317 		case 'L':
318 			flags &= ~(FTS_META|FTS_PHYSICAL);
319 			logical = 0;
320 			continue;
321 		case 'P':
322 			flags &= ~FTS_META;
323 			flags |= FTS_PHYSICAL;
324 			logical = 0;
325 			continue;
326 		case 'R':
327 			flags &= ~FTS_TOP;
328 			logical = 0;
329 			continue;
330 		case 'X':
331 			options |= OPT_TEST;
332 			continue;
333 		case ':':
334 			error(2, "%s", opt_info.arg);
335 			continue;
336 		case '?':
337 			error(ERROR_usage(2), "%s", opt_info.arg);
338 			break;
339 		}
340 		break;
341 	}
342 	argv += opt_info.index;
343 	argc -= opt_info.index;
344 	if (error_info.errors || argc < 2)
345 		error(ERROR_usage(2), "%s", optusage(NiL));
346 	s = *argv;
347 	if (options & OPT_LCHOWN)
348 	{
349 		flags &= ~FTS_META;
350 		flags |= FTS_PHYSICAL;
351 		logical = 0;
352 	}
353 	if (logical)
354 		flags &= ~(FTS_META|FTS_PHYSICAL);
355 	if (map)
356 	{
357 		if (streq(s, "-"))
358 			sp = sfstdin;
359 		else if (!(sp = sfopen(NiL, s, "r")))
360 			error(ERROR_exit(1), "%s: cannot read", s);
361 		while (s = sfgetr(sp, '\n', 1))
362 		{
363 			getids(s, &t, &key, options);
364 			if (!(m = (Map_t*)dtmatch(map, &key)))
365 			{
366 				if (!(m = (Map_t*)stakalloc(sizeof(Map_t))))
367 					error(ERROR_exit(1), "out of space [id dictionary]");
368 				m->key = key;
369 				m->to.uid = m->to.gid = NOID;
370 				dtinsert(map, m);
371 			}
372 			getids(t, NiL, &m->to, options);
373 		}
374 		if (sp != sfstdin)
375 			sfclose(sp);
376 		keys[1].gid = keys[2].uid = NOID;
377 	}
378 	else if (!(options & (OPT_UID|OPT_GID)))
379 	{
380 		getids(s, NiL, &key, options);
381 		if ((uid = key.uid) != NOID)
382 			options |= OPT_UID;
383 		if ((gid = key.gid) != NOID)
384 			options |= OPT_GID;
385 	}
386 	switch (options & (OPT_UID|OPT_GID))
387 	{
388 	case OPT_UID:
389 		s = ERROR_translate(0, 0, 0, " owner");
390 		break;
391 	case OPT_GID:
392 		s = ERROR_translate(0, 0, 0, " group");
393 		break;
394 	case OPT_UID|OPT_GID:
395 		s = ERROR_translate(0, 0, 0, " owner and group");
396 		break;
397 	default:
398 		s = "";
399 		break;
400 	}
401 	if (!(fts = fts_open(argv + 1, flags, NiL)))
402 		error(ERROR_system(1), "%s: not found", argv[1]);
403 	while (!sh_checksig(context) && (ent = fts_read(fts)))
404 		switch (ent->fts_info)
405 		{
406 		case FTS_SL:
407 		case FTS_SLNONE:
408 			if (options & OPT_LCHOWN)
409 			{
410 #if _lib_lchown
411 				chownf = lchown;
412 				op = "lchown";
413 				goto commit;
414 #else
415 				if (!(options & OPT_FORCE))
416 				{
417 					errno = ENOSYS;
418 					error(ERROR_system(0), "%s: cannot change symlink owner/group", ent->fts_path);
419 				}
420 #endif
421 			}
422 			break;
423 		case FTS_F:
424 		case FTS_D:
425 		anyway:
426 			chownf = chown;
427 			op = "chown";
428 		commit:
429 			if ((unsigned long)ent->fts_statp->st_ctime >= before)
430 				break;
431 			if (map)
432 			{
433 				options &= ~(OPT_UID|OPT_GID);
434 				uid = gid = NOID;
435 				keys[0].uid = keys[1].uid = ent->fts_statp->st_uid;
436 				keys[0].gid = keys[2].gid = ent->fts_statp->st_gid;
437 				i = 0;
438 				do
439 				{
440 					if (m = (Map_t*)dtmatch(map, &keys[i]))
441 					{
442 						if (uid == NOID && m->to.uid != NOID)
443 						{
444 							uid = m->to.uid;
445 							options |= OPT_UID;
446 						}
447 						if (gid == NOID && m->to.gid != NOID)
448 						{
449 							gid = m->to.gid;
450 							options |= OPT_GID;
451 						}
452 					}
453 				} while (++i < elementsof(keys) && (uid == NOID || gid == NOID));
454 			}
455 			else
456 			{
457 				if (!(options & OPT_UID))
458 					uid = ent->fts_statp->st_uid;
459 				if (!(options & OPT_GID))
460 					gid = ent->fts_statp->st_gid;
461 			}
462 			if ((options & OPT_UNMAPPED) && (uid == NOID || gid == NOID))
463 			{
464 				if (uid == NOID && gid == NOID)
465 					error(ERROR_warn(0), "%s: uid and gid not mapped", ent->fts_path);
466 				else if (uid == NOID)
467 					error(ERROR_warn(0), "%s: uid not mapped", ent->fts_path);
468 				else
469 					error(ERROR_warn(0), "%s: gid not mapped", ent->fts_path);
470 			}
471 			if (uid != ent->fts_statp->st_uid && uid != NOID || gid != ent->fts_statp->st_gid && gid != NOID)
472 			{
473 				if (options & (OPT_SHOW|OPT_VERBOSE))
474 				{
475 					if (options & OPT_TEST)
476 					{
477 						ent->fts_statp->st_uid = 0;
478 						ent->fts_statp->st_gid = 0;
479 					}
480 					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);
481 				}
482 				if (!(options & OPT_SHOW) && (*chownf)(ent->fts_accpath, uid, gid) && !(options & OPT_FORCE))
483 					error(ERROR_system(0), "%s: cannot change%s", ent->fts_path, s);
484 			}
485 			break;
486 		case FTS_DC:
487 			if (!(options & OPT_FORCE))
488 				error(ERROR_warn(0), "%s: directory causes cycle", ent->fts_path);
489 			break;
490 		case FTS_DNR:
491 			if (!(options & OPT_FORCE))
492 				error(ERROR_system(0), "%s: cannot read directory", ent->fts_path);
493 			goto anyway;
494 		case FTS_DNX:
495 			if (!(options & OPT_FORCE))
496 				error(ERROR_system(0), "%s: cannot search directory", ent->fts_path);
497 			goto anyway;
498 		case FTS_NS:
499 			if (!(options & OPT_FORCE))
500 				error(ERROR_system(0), "%s: not found", ent->fts_path);
501 			break;
502 		}
503 	fts_close(fts);
504 	if (map)
505 		dtclose(map);
506 	return error_info.errors != 0;
507 }
508