1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1992-2010 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 * chgrp+chown
28 */
29
30static const char usage_1[] =
31"[-?@(#)$Id: chgrp (AT&T Research) 2009-07-02 $\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"[l|h:symlink?Change the ownership of the symbolic links on systems that "
57    "support this.]"
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"[r:reference?Omit the explicit ownership operand and use the ownership "
69    "of \afile\a instead.]:[file]"
70"[u:unmapped?Print a diagnostic for each file for which either the "
71    "\auid\a or \agid\a or both were not mapped.]"
72"[v:verbose?Describe changed permissions of all files.]"
73"[H:metaphysical?Follow symbolic links for command arguments; otherwise "
74    "don't follow symbolic links when traversing directories.]"
75"[L:logical|follow?Follow symbolic links when traversing directories.]"
76"[P:physical|nofollow?Don't follow symbolic links when traversing "
77    "directories.]"
78"[R:recursive?Recursively change ownership of directories and their "
79    "contents.]"
80"[X:test?Canonicalize output for testing.]"
81
82"\n"
83"\n"
84;
85
86static const char usage_3[] =
87" file ...\n"
88"\n"
89"[+EXIT STATUS?]{"
90	"[+0?All files changed successfully.]"
91	"[+>0?Unable to change ownership of one or more files.]"
92"}"
93"[+SEE ALSO?\bchmod\b(1), \btw\b(1), \bgetconf\b(1), \bls\b(1)]"
94;
95
96#if defined(__STDPP__directive) && defined(__STDPP__hide)
97__STDPP__directive pragma pp:hide lchown
98#else
99#define lchown		______lchown
100#endif
101
102#include <cmd.h>
103#include <cdt.h>
104#include <ls.h>
105#include <ctype.h>
106#include <fts_fix.h>
107
108#include "FEATURE/symlink"
109
110#if defined(__STDPP__directive) && defined(__STDPP__hide)
111__STDPP__directive pragma pp:nohide lchown
112#else
113#undef	lchown
114#endif
115
116typedef struct Key_s			/* uid/gid key			*/
117{
118	int		uid;		/* uid				*/
119	int		gid;		/* gid				*/
120} Key_t;
121
122typedef struct Map_s			/* uid/gid map			*/
123{
124	Dtlink_t	link;		/* dictionary link		*/
125	Key_t		key;		/* key				*/
126	Key_t		to;		/* map to these			*/
127} Map_t;
128
129#define NOID		(-1)
130
131#define OPT_CHOWN	(1<<0)		/* chown			*/
132#define OPT_FORCE	(1<<1)		/* ignore errors		*/
133#define OPT_GID		(1<<2)		/* have gid			*/
134#define OPT_LCHOWN	(1<<3)		/* lchown			*/
135#define OPT_SHOW	(1<<4)		/* show but don't do		*/
136#define OPT_TEST	(1<<5)		/* canonicalize output		*/
137#define OPT_UID		(1<<6)		/* have uid			*/
138#define OPT_UNMAPPED	(1<<7)		/* unmapped file diagnostic	*/
139#define OPT_VERBOSE	(1<<8)		/* have uid			*/
140
141extern int	lchown(const char*, uid_t, gid_t);
142
143#if !_lib_lchown
144
145#ifndef ENOSYS
146#define ENOSYS	EINVAL
147#endif
148
149int
150lchown(const char* path, uid_t uid, gid_t gid)
151{
152	return ENOSYS;
153}
154
155#endif /* _lib_chown */
156
157/*
158 * parse uid and gid from s
159 */
160
161static void
162getids(register char* s, char** e, Key_t* key, int options)
163{
164	register char*	t;
165	register int	n;
166	char*		z;
167	char		buf[64];
168
169	key->uid = key->gid = NOID;
170	while (isspace(*s))
171		s++;
172	for (t = s; (n = *t) && n != ':' && n != '.' && !isspace(n); t++);
173	if (n)
174	{
175		options |= OPT_CHOWN;
176		if ((n = t++ - s) >= sizeof(buf))
177			n = sizeof(buf) - 1;
178		*((s = (char*)memcpy(buf, s, n)) + n) = 0;
179	}
180	if (options & OPT_CHOWN)
181	{
182		if (*s)
183		{
184			if ((n = struid(s)) == NOID)
185			{
186				n = (int)strtol(s, &z, 0);
187				if (*z)
188					error(ERROR_exit(1), "%s: unknown user", s);
189			}
190			key->uid = n;
191		}
192		for (s = t; (n = *t) && !isspace(n); t++);
193		if (n)
194		{
195			if ((n = t++ - s) >= sizeof(buf))
196				n = sizeof(buf) - 1;
197			*((s = (char*)memcpy(buf, s, n)) + n) = 0;
198		}
199	}
200	if (*s)
201	{
202		if ((n = strgid(s)) == NOID)
203		{
204			n = (int)strtol(s, &z, 0);
205			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
214int
215b_chgrp(int argc, char** argv, void* context)
216{
217	register int	options = 0;
218	register char*	s;
219	register Map_t*	m;
220	register FTS*	fts;
221	register FTSENT*ent;
222	register int	i;
223	Dt_t*		map = 0;
224	int		logical = 1;
225	int		flags;
226	int		uid;
227	int		gid;
228	char*		op;
229	char*		usage;
230	char*		t;
231	Sfio_t*		sp;
232	unsigned long	before;
233	Dtdisc_t	mapdisc;
234	Key_t		keys[3];
235	Key_t		key;
236	struct stat	st;
237	int		(*chownf)(const char*, uid_t, gid_t);
238
239	cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
240	flags = fts_flags() | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR;
241	before = ~0;
242	if (!(sp = sfstropen()))
243		error(ERROR_SYSTEM|3, "out of space");
244	sfputr(sp, usage_1, -1);
245	if (error_info.id[2] == 'g')
246		sfputr(sp, usage_grp_1, -1);
247	else
248	{
249		sfputr(sp, usage_own_1, -1);
250		options |= OPT_CHOWN;
251	}
252	sfputr(sp, usage_2, -1);
253	if (options & OPT_CHOWN)
254		sfputr(sp, ERROR_translate(0, 0, 0, "[owner[:group]]"), -1);
255	else
256		sfputr(sp, ERROR_translate(0, 0, 0, "[[owner:]group]"), -1);
257	sfputr(sp, usage_3, -1);
258	if (!(usage = sfstruse(sp)))
259		error(ERROR_SYSTEM|3, "out of space");
260	for (;;)
261	{
262		switch (optget(argv, usage))
263		{
264		case 'b':
265			if (stat(opt_info.arg, &st))
266				error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
267			before = st.st_mtime;
268			continue;
269		case 'c':
270		case 'v':
271			options |= OPT_VERBOSE;
272			continue;
273		case 'f':
274			options |= OPT_FORCE;
275			continue;
276		case 'l':
277			options |= OPT_LCHOWN;
278			continue;
279		case 'm':
280			memset(&mapdisc, 0, sizeof(mapdisc));
281			mapdisc.key = offsetof(Map_t, key);
282			mapdisc.size = sizeof(Key_t);
283			if (!(map = dtopen(&mapdisc, Dthash)))
284				error(ERROR_exit(1), "out of space [id map]");
285			continue;
286		case 'n':
287			options |= OPT_SHOW;
288			continue;
289		case 'r':
290			if (stat(opt_info.arg, &st))
291				error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
292			uid = st.st_uid;
293			gid = st.st_gid;
294			options |= OPT_UID|OPT_GID;
295			continue;
296		case 'u':
297			options |= OPT_UNMAPPED;
298			continue;
299		case 'H':
300			flags |= FTS_META|FTS_PHYSICAL;
301			logical = 0;
302			continue;
303		case 'L':
304			flags &= ~(FTS_META|FTS_PHYSICAL);
305			logical = 0;
306			continue;
307		case 'P':
308			flags &= ~FTS_META;
309			flags |= FTS_PHYSICAL;
310			logical = 0;
311			continue;
312		case 'R':
313			flags &= ~FTS_TOP;
314			logical = 0;
315			continue;
316		case 'X':
317			options |= OPT_TEST;
318			continue;
319		case ':':
320			error(2, "%s", opt_info.arg);
321			continue;
322		case '?':
323			error(ERROR_usage(2), "%s", opt_info.arg);
324			break;
325		}
326		break;
327	}
328	argv += opt_info.index;
329	argc -= opt_info.index;
330	if (error_info.errors || argc < 2)
331		error(ERROR_usage(2), "%s", optusage(NiL));
332	s = *argv;
333	if (logical)
334		flags &= ~(FTS_META|FTS_PHYSICAL);
335	if (map)
336	{
337		if (streq(s, "-"))
338			sp = sfstdin;
339		else if (!(sp = sfopen(NiL, s, "r")))
340			error(ERROR_exit(1), "%s: cannot read", s);
341		while (s = sfgetr(sp, '\n', 1))
342		{
343			getids(s, &t, &key, options);
344			if (!(m = (Map_t*)dtmatch(map, &key)))
345			{
346				if (!(m = (Map_t*)stakalloc(sizeof(Map_t))))
347					error(ERROR_exit(1), "out of space [id dictionary]");
348				m->key = key;
349				m->to.uid = m->to.gid = NOID;
350				dtinsert(map, m);
351			}
352			getids(t, NiL, &m->to, options);
353		}
354		if (sp != sfstdin)
355			sfclose(sp);
356		keys[1].gid = keys[2].uid = NOID;
357	}
358	else if (!(options & (OPT_UID|OPT_GID)))
359	{
360		getids(s, NiL, &key, options);
361		if ((uid = key.uid) != NOID)
362			options |= OPT_UID;
363		if ((gid = key.gid) != NOID)
364			options |= OPT_GID;
365	}
366	switch (options & (OPT_UID|OPT_GID))
367	{
368	case OPT_UID:
369		s = ERROR_translate(0, 0, 0, " owner");
370		break;
371	case OPT_GID:
372		s = ERROR_translate(0, 0, 0, " group");
373		break;
374	case OPT_UID|OPT_GID:
375		s = ERROR_translate(0, 0, 0, " owner and group");
376		break;
377	default:
378		s = "";
379		break;
380	}
381	if (!(fts = fts_open(argv + 1, flags, NiL)))
382		error(ERROR_system(1), "%s: not found", argv[1]);
383	while (!sh_checksig(context) && (ent = fts_read(fts)))
384		switch (ent->fts_info)
385		{
386		case FTS_F:
387		case FTS_D:
388		case FTS_SL:
389		case FTS_SLNONE:
390		anyway:
391			if ((unsigned long)ent->fts_statp->st_ctime >= before)
392				break;
393			if (map)
394			{
395				options &= ~(OPT_UID|OPT_GID);
396				uid = gid = NOID;
397				keys[0].uid = keys[1].uid = ent->fts_statp->st_uid;
398				keys[0].gid = keys[2].gid = ent->fts_statp->st_gid;
399				i = 0;
400				do
401				{
402					if (m = (Map_t*)dtmatch(map, &keys[i]))
403					{
404						if (uid == NOID && m->to.uid != NOID)
405						{
406							uid = m->to.uid;
407							options |= OPT_UID;
408						}
409						if (gid == NOID && m->to.gid != NOID)
410						{
411							gid = m->to.gid;
412							options |= OPT_GID;
413						}
414					}
415				} while (++i < elementsof(keys) && (uid == NOID || gid == NOID));
416			}
417			else
418			{
419				if (!(options & OPT_UID))
420					uid = ent->fts_statp->st_uid;
421				if (!(options & OPT_GID))
422					gid = ent->fts_statp->st_gid;
423			}
424			if ((options & OPT_UNMAPPED) && (uid == NOID || gid == NOID))
425			{
426				if (uid == NOID && gid == NOID)
427					error(ERROR_warn(0), "%s: uid and gid not mapped", ent->fts_path);
428				else if (uid == NOID)
429					error(ERROR_warn(0), "%s: uid not mapped", ent->fts_path);
430				else
431					error(ERROR_warn(0), "%s: gid not mapped", ent->fts_path);
432			}
433			if (uid != ent->fts_statp->st_uid && uid != NOID || gid != ent->fts_statp->st_gid && gid != NOID)
434			{
435				if ((ent->fts_info & FTS_SL) && (flags & FTS_PHYSICAL) && (options & OPT_LCHOWN))
436				{
437					op = "lchown";
438					chownf = lchown;
439				}
440				else
441				{
442					op = "chown";
443					chownf = chown;
444				}
445				if (options & (OPT_SHOW|OPT_VERBOSE))
446				{
447					if (options & OPT_TEST)
448					{
449						ent->fts_statp->st_uid = 0;
450						ent->fts_statp->st_gid = 0;
451					}
452					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);
453				}
454				if (!(options & OPT_SHOW) && (*chownf)(ent->fts_accpath, uid, gid) && !(options & OPT_FORCE))
455					error(ERROR_system(0), "%s: cannot change%s", ent->fts_accpath, s);
456			}
457			break;
458		case FTS_DC:
459			if (!(options & OPT_FORCE))
460				error(ERROR_warn(0), "%s: directory causes cycle", ent->fts_accpath);
461			break;
462		case FTS_DNR:
463			if (!(options & OPT_FORCE))
464				error(ERROR_system(0), "%s: cannot read directory", ent->fts_accpath);
465			goto anyway;
466		case FTS_DNX:
467			if (!(options & OPT_FORCE))
468				error(ERROR_system(0), "%s: cannot search directory", ent->fts_accpath);
469			goto anyway;
470		case FTS_NS:
471			if (!(options & OPT_FORCE))
472				error(ERROR_system(0), "%s: not found", ent->fts_accpath);
473			break;
474		}
475	fts_close(fts);
476	if (map)
477		dtclose(map);
478	return error_info.errors != 0;
479}
480