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