xref: /titanic_44/usr/src/lib/libcmd/common/chgrp.c (revision f80ce222af313ec87dc070b2219b29c81c5e5d8c)
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 
30 static const char usage_1[] =
31 "[-?@(#)$Id: chgrp (AT&T Research) 2009-07-02 $\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 "[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 
86 static 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 
116 typedef struct Key_s			/* uid/gid key			*/
117 {
118 	int		uid;		/* uid				*/
119 	int		gid;		/* gid				*/
120 } Key_t;
121 
122 typedef 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 
141 extern int	lchown(const char*, uid_t, gid_t);
142 
143 #if !_lib_lchown
144 
145 #ifndef ENOSYS
146 #define ENOSYS	EINVAL
147 #endif
148 
149 int
150 lchown(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 
161 static void
162 getids(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 
214 int
215 b_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