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
30static const char usage_1[] =
31"[-?@(#)$Id: chgrp (AT&T Research) 2012-04-20 $\n]"
32USAGE_LICENSE
33;
34
35static 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
43static 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
51static 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
90static 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
124typedef struct Key_s			/* uid/gid key			*/
125{
126	int		uid;		/* uid				*/
127	int		gid;		/* gid				*/
128} Key_t;
129
130typedef 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#define NOID		(-1)
138
139#define OPT_CHOWN	0x0001		/* chown			*/
140#define OPT_FORCE	0x0002		/* ignore errors		*/
141#define OPT_GID		0x0004		/* have gid			*/
142#define OPT_LCHOWN	0x0008		/* lchown			*/
143#define OPT_NUMERIC	0x0010		/* favor numeric ids		*/
144#define OPT_SHOW	0x0020		/* show but don't do		*/
145#define OPT_TEST	0x0040		/* canonicalize output		*/
146#define OPT_UID		0x0080		/* have uid			*/
147#define OPT_UNMAPPED	0x0100		/* unmapped file diagnostic	*/
148#define OPT_VERBOSE	0x0200		/* have uid			*/
149
150extern int	lchown(const char*, uid_t, gid_t);
151
152/*
153 * parse uid and gid from s
154 */
155
156static void
157getids(register char* s, char** e, Key_t* key, int options)
158{
159	register char*	t;
160	register int	n;
161	register int	m;
162	char*		z;
163	char		buf[64];
164
165	key->uid = key->gid = NOID;
166	while (isspace(*s))
167		s++;
168	for (t = s; (n = *t) && n != ':' && n != '.' && !isspace(n); t++);
169	if (n)
170	{
171		options |= OPT_CHOWN;
172		if ((n = t++ - s) >= sizeof(buf))
173			n = sizeof(buf) - 1;
174		*((s = (char*)memcpy(buf, s, n)) + n) = 0;
175	}
176	if (options & OPT_CHOWN)
177	{
178		if (*s)
179		{
180			n = (int)strtol(s, &z, 0);
181			if (*z || !(options & OPT_NUMERIC))
182			{
183				if ((m = struid(s)) != NOID)
184					n = m;
185				else if (*z)
186					error(ERROR_exit(1), "%s: unknown user", s);
187			}
188			key->uid = n;
189		}
190		for (s = t; (n = *t) && !isspace(n); t++);
191		if (n)
192		{
193			if ((n = t++ - s) >= sizeof(buf))
194				n = sizeof(buf) - 1;
195			*((s = (char*)memcpy(buf, s, n)) + n) = 0;
196		}
197	}
198	if (*s)
199	{
200		n = (int)strtol(s, &z, 0);
201		if (*z || !(options & OPT_NUMERIC))
202		{
203			if ((m = strgid(s)) != NOID)
204				n = m;
205			else if (*z)
206				error(ERROR_exit(1), "%s: unknown group", s);
207		}
208		key->gid = n;
209	}
210	if (e)
211		*e = t;
212}
213
214/*
215 * NOTE: we only use the native lchown() on symlinks just in case
216 *	 the implementation is a feckless stub
217 */
218
219int
220b_chgrp(int argc, char** argv, Shbltin_t* context)
221{
222	register int	options = 0;
223	register char*	s;
224	register Map_t*	m;
225	register FTS*	fts;
226	register FTSENT*ent;
227	register int	i;
228	Dt_t*		map = 0;
229	int		logical = 1;
230	int		flags;
231	int		uid;
232	int		gid;
233	char*		op;
234	char*		usage;
235	char*		t;
236	Sfio_t*		sp;
237	unsigned long	before;
238	Dtdisc_t	mapdisc;
239	Key_t		keys[3];
240	Key_t		key;
241	struct stat	st;
242	int		(*chownf)(const char*, uid_t, gid_t);
243
244	cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
245	flags = fts_flags() | FTS_META | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR;
246	before = ~0;
247	if (!(sp = sfstropen()))
248		error(ERROR_SYSTEM|3, "out of space");
249	sfputr(sp, usage_1, -1);
250	if (error_info.id[2] == 'g')
251		sfputr(sp, usage_grp_1, -1);
252	else
253	{
254		sfputr(sp, usage_own_1, -1);
255		options |= OPT_CHOWN;
256	}
257	sfputr(sp, usage_2, -1);
258	if (options & OPT_CHOWN)
259		sfputr(sp, ERROR_translate(0, 0, 0, "[owner[:group]]"), -1);
260	else
261		sfputr(sp, ERROR_translate(0, 0, 0, "[[owner:]group]"), -1);
262	sfputr(sp, usage_3, -1);
263	if (!(usage = sfstruse(sp)))
264		error(ERROR_SYSTEM|3, "out of space");
265	for (;;)
266	{
267		switch (optget(argv, usage))
268		{
269		case 'b':
270			if (stat(opt_info.arg, &st))
271				error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
272			before = st.st_mtime;
273			continue;
274		case 'c':
275		case 'v':
276			options |= OPT_VERBOSE;
277			continue;
278		case 'f':
279			options |= OPT_FORCE;
280			continue;
281		case 'h':
282			options |= OPT_LCHOWN;
283			continue;
284		case 'm':
285			memset(&mapdisc, 0, sizeof(mapdisc));
286			mapdisc.key = offsetof(Map_t, key);
287			mapdisc.size = sizeof(Key_t);
288			if (!(map = dtopen(&mapdisc, Dtset)))
289				error(ERROR_exit(1), "out of space [id map]");
290			continue;
291		case 'n':
292			options |= OPT_SHOW;
293			continue;
294		case 'N':
295			options |= OPT_NUMERIC;
296			continue;
297		case 'r':
298			if (stat(opt_info.arg, &st))
299				error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
300			uid = st.st_uid;
301			gid = st.st_gid;
302			options |= OPT_UID|OPT_GID;
303			continue;
304		case 'u':
305			options |= OPT_UNMAPPED;
306			continue;
307		case 'H':
308			flags |= FTS_META|FTS_PHYSICAL;
309			logical = 0;
310			continue;
311		case 'L':
312			flags &= ~(FTS_META|FTS_PHYSICAL);
313			logical = 0;
314			continue;
315		case 'P':
316			flags &= ~FTS_META;
317			flags |= FTS_PHYSICAL;
318			logical = 0;
319			continue;
320		case 'R':
321			flags &= ~FTS_TOP;
322			logical = 0;
323			continue;
324		case 'X':
325			options |= OPT_TEST;
326			continue;
327		case ':':
328			error(2, "%s", opt_info.arg);
329			continue;
330		case '?':
331			error(ERROR_usage(2), "%s", opt_info.arg);
332			break;
333		}
334		break;
335	}
336	argv += opt_info.index;
337	argc -= opt_info.index;
338	if (error_info.errors || argc < 2)
339		error(ERROR_usage(2), "%s", optusage(NiL));
340	s = *argv;
341	if (options & OPT_LCHOWN)
342	{
343		flags &= ~FTS_META;
344		flags |= FTS_PHYSICAL;
345		logical = 0;
346	}
347	if (logical)
348		flags &= ~(FTS_META|FTS_PHYSICAL);
349	if (map)
350	{
351		if (streq(s, "-"))
352			sp = sfstdin;
353		else if (!(sp = sfopen(NiL, s, "r")))
354			error(ERROR_exit(1), "%s: cannot read", s);
355		while (s = sfgetr(sp, '\n', 1))
356		{
357			getids(s, &t, &key, options);
358			if (!(m = (Map_t*)dtmatch(map, &key)))
359			{
360				if (!(m = (Map_t*)stakalloc(sizeof(Map_t))))
361					error(ERROR_exit(1), "out of space [id dictionary]");
362				m->key = key;
363				m->to.uid = m->to.gid = NOID;
364				dtinsert(map, m);
365			}
366			getids(t, NiL, &m->to, options);
367		}
368		if (sp != sfstdin)
369			sfclose(sp);
370		keys[1].gid = keys[2].uid = NOID;
371	}
372	else if (!(options & (OPT_UID|OPT_GID)))
373	{
374		getids(s, NiL, &key, options);
375		if ((uid = key.uid) != NOID)
376			options |= OPT_UID;
377		if ((gid = key.gid) != NOID)
378			options |= OPT_GID;
379	}
380	switch (options & (OPT_UID|OPT_GID))
381	{
382	case OPT_UID:
383		s = ERROR_translate(0, 0, 0, " owner");
384		break;
385	case OPT_GID:
386		s = ERROR_translate(0, 0, 0, " group");
387		break;
388	case OPT_UID|OPT_GID:
389		s = ERROR_translate(0, 0, 0, " owner and group");
390		break;
391	default:
392		s = "";
393		break;
394	}
395	if (!(fts = fts_open(argv + 1, flags, NiL)))
396		error(ERROR_system(1), "%s: not found", argv[1]);
397	while (!sh_checksig(context) && (ent = fts_read(fts)))
398		switch (ent->fts_info)
399		{
400		case FTS_SL:
401		case FTS_SLNONE:
402			if (options & OPT_LCHOWN)
403			{
404#if _lib_lchown
405				chownf = lchown;
406				op = "lchown";
407				goto commit;
408#else
409				if (!(options & OPT_FORCE))
410				{
411					errno = ENOSYS;
412					error(ERROR_system(0), "%s: cannot change symlink owner/group", ent->fts_path);
413				}
414#endif
415			}
416			break;
417		case FTS_F:
418		case FTS_D:
419		anyway:
420			chownf = chown;
421			op = "chown";
422		commit:
423			if ((unsigned long)ent->fts_statp->st_ctime >= before)
424				break;
425			if (map)
426			{
427				options &= ~(OPT_UID|OPT_GID);
428				uid = gid = NOID;
429				keys[0].uid = keys[1].uid = ent->fts_statp->st_uid;
430				keys[0].gid = keys[2].gid = ent->fts_statp->st_gid;
431				i = 0;
432				do
433				{
434					if (m = (Map_t*)dtmatch(map, &keys[i]))
435					{
436						if (uid == NOID && m->to.uid != NOID)
437						{
438							uid = m->to.uid;
439							options |= OPT_UID;
440						}
441						if (gid == NOID && m->to.gid != NOID)
442						{
443							gid = m->to.gid;
444							options |= OPT_GID;
445						}
446					}
447				} while (++i < elementsof(keys) && (uid == NOID || gid == NOID));
448			}
449			else
450			{
451				if (!(options & OPT_UID))
452					uid = ent->fts_statp->st_uid;
453				if (!(options & OPT_GID))
454					gid = ent->fts_statp->st_gid;
455			}
456			if ((options & OPT_UNMAPPED) && (uid == NOID || gid == NOID))
457			{
458				if (uid == NOID && gid == NOID)
459					error(ERROR_warn(0), "%s: uid and gid not mapped", ent->fts_path);
460				else if (uid == NOID)
461					error(ERROR_warn(0), "%s: uid not mapped", ent->fts_path);
462				else
463					error(ERROR_warn(0), "%s: gid not mapped", ent->fts_path);
464			}
465			if (uid != ent->fts_statp->st_uid && uid != NOID || gid != ent->fts_statp->st_gid && gid != NOID)
466			{
467				if (options & (OPT_SHOW|OPT_VERBOSE))
468				{
469					if (options & OPT_TEST)
470					{
471						ent->fts_statp->st_uid = 0;
472						ent->fts_statp->st_gid = 0;
473					}
474					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);
475				}
476				if (!(options & OPT_SHOW) && (*chownf)(ent->fts_accpath, uid, gid) && !(options & OPT_FORCE))
477					error(ERROR_system(0), "%s: cannot change%s", ent->fts_path, s);
478			}
479			break;
480		case FTS_DC:
481			if (!(options & OPT_FORCE))
482				error(ERROR_warn(0), "%s: directory causes cycle", ent->fts_path);
483			break;
484		case FTS_DNR:
485			if (!(options & OPT_FORCE))
486				error(ERROR_system(0), "%s: cannot read directory", ent->fts_path);
487			goto anyway;
488		case FTS_DNX:
489			if (!(options & OPT_FORCE))
490				error(ERROR_system(0), "%s: cannot search directory", ent->fts_path);
491			goto anyway;
492		case FTS_NS:
493			if (!(options & OPT_FORCE))
494				error(ERROR_system(0), "%s: not found", ent->fts_path);
495			break;
496		}
497	fts_close(fts);
498	if (map)
499		dtclose(map);
500	return error_info.errors != 0;
501}
502