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