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 #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
150 extern int lchown(const char*, uid_t, gid_t);
151
152 /*
153 * parse uid and gid from s
154 */
155
156 static void
getids(register char * s,char ** e,Key_t * key,int options)157 getids(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
219 int
b_chgrp(int argc,char ** argv,Shbltin_t * context)220 b_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