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
lchown(const char * path,uid_t uid,gid_t gid)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
getids(register char * s,char ** e,Key_t * key,int options)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
b_chgrp(int argc,char ** argv,void * context)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