1 /***********************************************************************
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 *
9 * A copy of the License is available at *
10 * http://www.opensource.org/licenses/cpl1.0.txt *
11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
13 * Information and Software Systems Research *
17 * Glenn Fowler <gsf@research.att.com> *
18 * David Korn <dgk@research.att.com> *
20 ***********************************************************************/
30 static const char usage_1
[] =
31 "[-?@(#)$Id: chgrp (AT&T Research) 2009-07-02 $\n]"
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.]"
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.]"
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 "
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 "
78 "[R:recursive?Recursively change ownership of directories and their "
80 "[X:test?Canonicalize output for testing.]"
86 static const char usage_3
[] =
90 "[+0?All files changed successfully.]"
91 "[+>0?Unable to change ownership of one or more files.]"
93 "[+SEE ALSO?\bchmod\b(1), \btw\b(1), \bgetconf\b(1), \bls\b(1)]"
96 #if defined(__STDPP__directive) && defined(__STDPP__hide)
97 __STDPP__directive pragma pp
:hide lchown
99 #define lchown ______lchown
108 #include "FEATURE/symlink"
110 #if defined(__STDPP__directive) && defined(__STDPP__hide)
111 __STDPP__directive pragma pp
:nohide lchown
116 typedef struct Key_s
/* uid/gid key */
122 typedef struct Map_s
/* uid/gid map */
124 Dtlink_t link
; /* dictionary link */
126 Key_t to
; /* map to these */
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 */
141 extern int lchown(const char*, uid_t
, gid_t
);
146 #define ENOSYS EINVAL
150 lchown(const char* path
, uid_t uid
, gid_t gid
)
155 #endif /* _lib_chown */
158 * parse uid and gid from s
162 getids(register char* s
, char** e
, Key_t
* key
, int options
)
169 key
->uid
= key
->gid
= NOID
;
172 for (t
= s
; (n
= *t
) && n
!= ':' && n
!= '.' && !isspace(n
); t
++);
175 options
|= OPT_CHOWN
;
176 if ((n
= t
++ - s
) >= sizeof(buf
))
178 *((s
= (char*)memcpy(buf
, s
, n
)) + n
) = 0;
180 if (options
& OPT_CHOWN
)
184 if ((n
= struid(s
)) == NOID
)
186 n
= (int)strtol(s
, &z
, 0);
188 error(ERROR_exit(1), "%s: unknown user", s
);
192 for (s
= t
; (n
= *t
) && !isspace(n
); t
++);
195 if ((n
= t
++ - s
) >= sizeof(buf
))
197 *((s
= (char*)memcpy(buf
, s
, n
)) + n
) = 0;
202 if ((n
= strgid(s
)) == NOID
)
204 n
= (int)strtol(s
, &z
, 0);
206 error(ERROR_exit(1), "%s: unknown group", s
);
215 b_chgrp(int argc
, char** argv
, void* context
)
217 register int options
= 0;
232 unsigned long before
;
237 int (*chownf
)(const char*, uid_t
, gid_t
);
239 cmdinit(argc
, argv
, context
, ERROR_CATALOG
, ERROR_NOTIFY
);
240 flags
= fts_flags() | FTS_TOP
| FTS_NOPOSTORDER
| FTS_NOSEEDOTDIR
;
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);
249 sfputr(sp
, usage_own_1
, -1);
250 options
|= OPT_CHOWN
;
252 sfputr(sp
, usage_2
, -1);
253 if (options
& OPT_CHOWN
)
254 sfputr(sp
, ERROR_translate(0, 0, 0, "[owner[:group]]"), -1);
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");
262 switch (optget(argv
, usage
))
265 if (stat(opt_info
.arg
, &st
))
266 error(ERROR_exit(1), "%s: cannot stat", opt_info
.arg
);
267 before
= st
.st_mtime
;
271 options
|= OPT_VERBOSE
;
274 options
|= OPT_FORCE
;
277 options
|= OPT_LCHOWN
;
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]");
290 if (stat(opt_info
.arg
, &st
))
291 error(ERROR_exit(1), "%s: cannot stat", opt_info
.arg
);
294 options
|= OPT_UID
|OPT_GID
;
297 options
|= OPT_UNMAPPED
;
300 flags
|= FTS_META
|FTS_PHYSICAL
;
304 flags
&= ~(FTS_META
|FTS_PHYSICAL
);
309 flags
|= FTS_PHYSICAL
;
320 error(2, "%s", opt_info
.arg
);
323 error(ERROR_usage(2), "%s", opt_info
.arg
);
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
));
334 flags
&= ~(FTS_META
|FTS_PHYSICAL
);
339 else if (!(sp
= sfopen(NiL
, s
, "r")))
340 error(ERROR_exit(1), "%s: cannot read", s
);
341 while (s
= sfgetr(sp
, '\n', 1))
343 getids(s
, &t
, &key
, options
);
344 if (!(m
= (Map_t
*)dtmatch(map
, &key
)))
346 if (!(m
= (Map_t
*)stakalloc(sizeof(Map_t
))))
347 error(ERROR_exit(1), "out of space [id dictionary]");
349 m
->to
.uid
= m
->to
.gid
= NOID
;
352 getids(t
, NiL
, &m
->to
, options
);
356 keys
[1].gid
= keys
[2].uid
= NOID
;
358 else if (!(options
& (OPT_UID
|OPT_GID
)))
360 getids(s
, NiL
, &key
, options
);
361 if ((uid
= key
.uid
) != NOID
)
363 if ((gid
= key
.gid
) != NOID
)
366 switch (options
& (OPT_UID
|OPT_GID
))
369 s
= ERROR_translate(0, 0, 0, " owner");
372 s
= ERROR_translate(0, 0, 0, " group");
374 case OPT_UID
|OPT_GID
:
375 s
= ERROR_translate(0, 0, 0, " owner and group");
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
)
391 if ((unsigned long)ent
->fts_statp
->st_ctime
>= before
)
395 options
&= ~(OPT_UID
|OPT_GID
);
397 keys
[0].uid
= keys
[1].uid
= ent
->fts_statp
->st_uid
;
398 keys
[0].gid
= keys
[2].gid
= ent
->fts_statp
->st_gid
;
402 if (m
= (Map_t
*)dtmatch(map
, &keys
[i
]))
404 if (uid
== NOID
&& m
->to
.uid
!= NOID
)
409 if (gid
== NOID
&& m
->to
.gid
!= NOID
)
415 } while (++i
< elementsof(keys
) && (uid
== NOID
|| gid
== NOID
));
419 if (!(options
& OPT_UID
))
420 uid
= ent
->fts_statp
->st_uid
;
421 if (!(options
& OPT_GID
))
422 gid
= ent
->fts_statp
->st_gid
;
424 if ((options
& OPT_UNMAPPED
) && (uid
== NOID
|| gid
== NOID
))
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
);
431 error(ERROR_warn(0), "%s: gid not mapped", ent
->fts_path
);
433 if (uid
!= ent
->fts_statp
->st_uid
&& uid
!= NOID
|| gid
!= ent
->fts_statp
->st_gid
&& gid
!= NOID
)
435 if ((ent
->fts_info
& FTS_SL
) && (flags
& FTS_PHYSICAL
) && (options
& OPT_LCHOWN
))
445 if (options
& (OPT_SHOW
|OPT_VERBOSE
))
447 if (options
& OPT_TEST
)
449 ent
->fts_statp
->st_uid
= 0;
450 ent
->fts_statp
->st_gid
= 0;
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
);
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
);
459 if (!(options
& OPT_FORCE
))
460 error(ERROR_warn(0), "%s: directory causes cycle", ent
->fts_accpath
);
463 if (!(options
& OPT_FORCE
))
464 error(ERROR_system(0), "%s: cannot read directory", ent
->fts_accpath
);
467 if (!(options
& OPT_FORCE
))
468 error(ERROR_system(0), "%s: cannot search directory", ent
->fts_accpath
);
471 if (!(options
& OPT_FORCE
))
472 error(ERROR_system(0), "%s: not found", ent
->fts_accpath
);
478 return error_info
.errors
!= 0;