1N/A/***********************************************************************
1N/A* *
1N/A* This software is part of the ast package *
1N/A* Copyright (c) 1992-2011 AT&T Intellectual Property *
1N/A* and is licensed under the *
1N/A* Common Public License, Version 1.0 *
1N/A* by AT&T Intellectual Property *
1N/A* *
1N/A* A copy of the License is available at *
1N/A* http://www.opensource.org/licenses/cpl1.0.txt *
1N/A* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
1N/A* *
1N/A* Information and Software Systems Research *
1N/A* AT&T Research *
1N/A* Florham Park NJ *
1N/A* *
1N/A* Glenn Fowler <gsf@research.att.com> *
1N/A* David Korn <dgk@research.att.com> *
1N/A* *
1N/A***********************************************************************/
1N/A#pragma prototyped
1N/A/*
1N/A * David Korn
1N/A * Glenn Fowler
1N/A * AT&T Research
1N/A *
1N/A * chgrp+chown
1N/A */
1N/A
1N/Astatic const char usage_1[] =
1N/A"[-?@(#)$Id: chgrp (AT&T Research) 2011-01-03 $\n]"
1N/AUSAGE_LICENSE
1N/A;
1N/A
1N/Astatic const char usage_grp_1[] =
1N/A"[+NAME?chgrp - change the group ownership of files]"
1N/A"[+DESCRIPTION?\bchgrp\b changes the group ownership of each file"
1N/A" to \agroup\a, which can be either a group name or a numeric"
1N/A" group id. The user ownership of each file may also be changed to"
1N/A" \auser\a by prepending \auser\a\b:\b to the group name.]"
1N/A;
1N/A
1N/Astatic const char usage_own_1[] =
1N/A"[+NAME?chown - change the ownership of files]"
1N/A"[+DESCRIPTION?\bchown\b changes the ownership of each file"
1N/A" to \auser\a, which can be either a user name or a numeric"
1N/A" user id. The group ownership of each file may also be changed to"
1N/A" \auser\a by appending \b:\b\agroup\a to the user name.]"
1N/A;
1N/A
1N/Astatic const char usage_2[] =
1N/A"[b:before?Only change files with \bctime\b before (less than) the "
1N/A "\bmtime\b of \afile\a.]:[file]"
1N/A"[c:changes?Describe only files whose ownership actually changes.]"
1N/A"[f:quiet|silent?Do not report files whose ownership fails to change.]"
1N/A"[l|h:symlink?Change the ownership of symbolic links on systems that "
1N/A "support this. Implies \b--physical\b.]"
1N/A"[m:map?The first operand is interpreted as a file that contains a map "
1N/A "of space separated \afrom_uid:from_gid to_uid:to_gid\a pairs. The "
1N/A "\auid\a or \agid\a part of each pair may be omitted to mean any \auid\a "
1N/A "or \agid\a. Ownership of files matching the \afrom\a part of any pair "
1N/A "is changed to the corresponding \ato\a part of the pair. The matching "
1N/A "for each file operand is in the order \auid\a:\agid\a, \auid\a:, "
1N/A ":\agid\a. For a given file, once a \auid\a or \agid\a mapping is "
1N/A "determined it is not overridden by any subsequent match. Unmatched "
1N/A "files are silently ignored.]"
1N/A"[n:show?Show actions but don't execute.]"
1N/A"[N:numeric?By default numeric user and group id operands are first "
1N/A "interpreted as names; if no name exists then they are interpreted as "
1N/A "explicit numeric ids. \b--numeric\b interprets numeric id operands as "
1N/A "numeric ids.]"
1N/A"[r:reference?Omit the explicit ownership operand and use the ownership "
1N/A "of \afile\a instead.]:[file]"
1N/A"[u:unmapped?Print a diagnostic for each file for which either the "
1N/A "\auid\a or \agid\a or both were not mapped.]"
1N/A"[v:verbose?Describe changed permissions of all files.]"
1N/A"[H:metaphysical?Follow symbolic links for command arguments; otherwise "
1N/A "don't follow symbolic links when traversing directories.]"
1N/A"[L:logical|follow?Follow symbolic links when traversing directories.]"
1N/A"[P:physical|nofollow?Don't follow symbolic links when traversing "
1N/A "directories.]"
1N/A"[R:recursive?Recursively change ownership of directories and their "
1N/A "contents.]"
1N/A"[X:test?Canonicalize output for testing.]"
1N/A
1N/A"\n"
1N/A"\n"
1N/A;
1N/A
1N/Astatic const char usage_3[] =
1N/A" file ...\n"
1N/A"\n"
1N/A"[+EXIT STATUS?]{"
1N/A "[+0?All files changed successfully.]"
1N/A "[+>0?Unable to change ownership of one or more files.]"
1N/A"}"
1N/A"[+SEE ALSO?\bchmod\b(1), \btw\b(1), \bgetconf\b(1), \bls\b(1)]"
1N/A;
1N/A
1N/A#if defined(__STDPP__directive) && defined(__STDPP__hide)
1N/A__STDPP__directive pragma pp:hide lchown
1N/A#else
1N/A#define lchown ______lchown
1N/A#endif
1N/A
1N/A#include <cmd.h>
1N/A#include <cdt.h>
1N/A#include <ls.h>
1N/A#include <ctype.h>
1N/A#include <fts_fix.h>
1N/A
1N/A#include "FEATURE/symlink"
1N/A
1N/A#if defined(__STDPP__directive) && defined(__STDPP__hide)
1N/A__STDPP__directive pragma pp:nohide lchown
1N/A#else
1N/A#undef lchown
1N/A#endif
1N/A
1N/Atypedef struct Key_s /* uid/gid key */
1N/A{
1N/A int uid; /* uid */
1N/A int gid; /* gid */
1N/A} Key_t;
1N/A
1N/Atypedef struct Map_s /* uid/gid map */
1N/A{
1N/A Dtlink_t link; /* dictionary link */
1N/A Key_t key; /* key */
1N/A Key_t to; /* map to these */
1N/A} Map_t;
1N/A
1N/A#define NOID (-1)
1N/A
1N/A#define OPT_CHOWN 0x0001 /* chown */
1N/A#define OPT_FORCE 0x0002 /* ignore errors */
1N/A#define OPT_GID 0x0004 /* have gid */
1N/A#define OPT_LCHOWN 0x0008 /* lchown */
1N/A#define OPT_NUMERIC 0x0010 /* favor numeric ids */
1N/A#define OPT_SHOW 0x0020 /* show but don't do */
1N/A#define OPT_TEST 0x0040 /* canonicalize output */
1N/A#define OPT_UID 0x0080 /* have uid */
1N/A#define OPT_UNMAPPED 0x0100 /* unmapped file diagnostic */
1N/A#define OPT_VERBOSE 0x0200 /* have uid */
1N/A
1N/Aextern int lchown(const char*, uid_t, gid_t);
1N/A
1N/A#if !_lib_lchown
1N/A
1N/A#ifndef ENOSYS
1N/A#define ENOSYS EINVAL
1N/A#endif
1N/A
1N/Aint
1N/Alchown(const char* path, uid_t uid, gid_t gid)
1N/A{
1N/A return ENOSYS;
1N/A}
1N/A
1N/A#endif /* _lib_chown */
1N/A
1N/A/*
1N/A * parse uid and gid from s
1N/A */
1N/A
1N/Astatic void
1N/Agetids(register char* s, char** e, Key_t* key, int options)
1N/A{
1N/A register char* t;
1N/A register int n;
1N/A register int m;
1N/A char* z;
1N/A char buf[64];
1N/A
1N/A key->uid = key->gid = NOID;
1N/A while (isspace(*s))
1N/A s++;
1N/A for (t = s; (n = *t) && n != ':' && n != '.' && !isspace(n); t++);
1N/A if (n)
1N/A {
1N/A options |= OPT_CHOWN;
1N/A if ((n = t++ - s) >= sizeof(buf))
1N/A n = sizeof(buf) - 1;
1N/A *((s = (char*)memcpy(buf, s, n)) + n) = 0;
1N/A }
1N/A if (options & OPT_CHOWN)
1N/A {
1N/A if (*s)
1N/A {
1N/A n = (int)strtol(s, &z, 0);
1N/A if (*z || !(options & OPT_NUMERIC))
1N/A {
1N/A if ((m = struid(s)) != NOID)
1N/A n = m;
1N/A else if (*z)
1N/A error(ERROR_exit(1), "%s: unknown user", s);
1N/A }
1N/A key->uid = n;
1N/A }
1N/A for (s = t; (n = *t) && !isspace(n); t++);
1N/A if (n)
1N/A {
1N/A if ((n = t++ - s) >= sizeof(buf))
1N/A n = sizeof(buf) - 1;
1N/A *((s = (char*)memcpy(buf, s, n)) + n) = 0;
1N/A }
1N/A }
1N/A if (*s)
1N/A {
1N/A n = (int)strtol(s, &z, 0);
1N/A if (*z || !(options & OPT_NUMERIC))
1N/A {
1N/A if ((m = strgid(s)) != NOID)
1N/A n = m;
1N/A else if (*z)
1N/A error(ERROR_exit(1), "%s: unknown group", s);
1N/A }
1N/A key->gid = n;
1N/A }
1N/A if (e)
1N/A *e = t;
1N/A}
1N/A
1N/Aint
1N/Ab_chgrp(int argc, char** argv, void* context)
1N/A{
1N/A register int options = 0;
1N/A register char* s;
1N/A register Map_t* m;
1N/A register FTS* fts;
1N/A register FTSENT*ent;
1N/A register int i;
1N/A Dt_t* map = 0;
1N/A int logical = 1;
1N/A int flags;
1N/A int uid;
1N/A int gid;
1N/A char* op;
1N/A char* usage;
1N/A char* t;
1N/A Sfio_t* sp;
1N/A unsigned long before;
1N/A Dtdisc_t mapdisc;
1N/A Key_t keys[3];
1N/A Key_t key;
1N/A struct stat st;
1N/A int (*chownf)(const char*, uid_t, gid_t);
1N/A
1N/A cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
1N/A flags = fts_flags() | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR;
1N/A before = ~0;
1N/A if (!(sp = sfstropen()))
1N/A error(ERROR_SYSTEM|3, "out of space");
1N/A sfputr(sp, usage_1, -1);
1N/A if (error_info.id[2] == 'g')
1N/A sfputr(sp, usage_grp_1, -1);
1N/A else
1N/A {
1N/A sfputr(sp, usage_own_1, -1);
1N/A options |= OPT_CHOWN;
1N/A }
1N/A sfputr(sp, usage_2, -1);
1N/A if (options & OPT_CHOWN)
1N/A sfputr(sp, ERROR_translate(0, 0, 0, "[owner[:group]]"), -1);
1N/A else
1N/A sfputr(sp, ERROR_translate(0, 0, 0, "[[owner:]group]"), -1);
1N/A sfputr(sp, usage_3, -1);
1N/A if (!(usage = sfstruse(sp)))
1N/A error(ERROR_SYSTEM|3, "out of space");
1N/A for (;;)
1N/A {
1N/A switch (optget(argv, usage))
1N/A {
1N/A case 'b':
1N/A if (stat(opt_info.arg, &st))
1N/A error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
1N/A before = st.st_mtime;
1N/A continue;
1N/A case 'c':
1N/A case 'v':
1N/A options |= OPT_VERBOSE;
1N/A continue;
1N/A case 'f':
1N/A options |= OPT_FORCE;
1N/A continue;
1N/A case 'l':
1N/A options |= OPT_LCHOWN;
1N/A continue;
1N/A case 'm':
1N/A memset(&mapdisc, 0, sizeof(mapdisc));
1N/A mapdisc.key = offsetof(Map_t, key);
1N/A mapdisc.size = sizeof(Key_t);
1N/A if (!(map = dtopen(&mapdisc, Dthash)))
1N/A error(ERROR_exit(1), "out of space [id map]");
1N/A continue;
1N/A case 'n':
1N/A options |= OPT_SHOW;
1N/A continue;
1N/A case 'N':
1N/A options |= OPT_NUMERIC;
1N/A continue;
1N/A case 'r':
1N/A if (stat(opt_info.arg, &st))
1N/A error(ERROR_exit(1), "%s: cannot stat", opt_info.arg);
1N/A uid = st.st_uid;
1N/A gid = st.st_gid;
1N/A options |= OPT_UID|OPT_GID;
1N/A continue;
1N/A case 'u':
1N/A options |= OPT_UNMAPPED;
1N/A continue;
1N/A case 'H':
1N/A flags |= FTS_META|FTS_PHYSICAL;
1N/A logical = 0;
1N/A continue;
1N/A case 'L':
1N/A flags &= ~(FTS_META|FTS_PHYSICAL);
1N/A logical = 0;
1N/A continue;
1N/A case 'P':
1N/A flags &= ~FTS_META;
1N/A flags |= FTS_PHYSICAL;
1N/A logical = 0;
1N/A continue;
1N/A case 'R':
1N/A flags &= ~FTS_TOP;
1N/A logical = 0;
1N/A continue;
1N/A case 'X':
1N/A options |= OPT_TEST;
1N/A continue;
1N/A case ':':
1N/A error(2, "%s", opt_info.arg);
1N/A continue;
1N/A case '?':
1N/A error(ERROR_usage(2), "%s", opt_info.arg);
1N/A break;
1N/A }
1N/A break;
1N/A }
1N/A argv += opt_info.index;
1N/A argc -= opt_info.index;
1N/A if (error_info.errors || argc < 2)
1N/A error(ERROR_usage(2), "%s", optusage(NiL));
1N/A s = *argv;
1N/A if (options & OPT_LCHOWN)
1N/A {
1N/A flags &= ~FTS_META;
1N/A flags |= FTS_PHYSICAL;
1N/A logical = 0;
1N/A }
1N/A if (logical)
1N/A flags &= ~(FTS_META|FTS_PHYSICAL);
1N/A if (map)
1N/A {
1N/A if (streq(s, "-"))
1N/A sp = sfstdin;
1N/A else if (!(sp = sfopen(NiL, s, "r")))
1N/A error(ERROR_exit(1), "%s: cannot read", s);
1N/A while (s = sfgetr(sp, '\n', 1))
1N/A {
1N/A getids(s, &t, &key, options);
1N/A if (!(m = (Map_t*)dtmatch(map, &key)))
1N/A {
1N/A if (!(m = (Map_t*)stakalloc(sizeof(Map_t))))
1N/A error(ERROR_exit(1), "out of space [id dictionary]");
1N/A m->key = key;
1N/A m->to.uid = m->to.gid = NOID;
1N/A dtinsert(map, m);
1N/A }
1N/A getids(t, NiL, &m->to, options);
1N/A }
1N/A if (sp != sfstdin)
1N/A sfclose(sp);
1N/A keys[1].gid = keys[2].uid = NOID;
1N/A }
1N/A else if (!(options & (OPT_UID|OPT_GID)))
1N/A {
1N/A getids(s, NiL, &key, options);
1N/A if ((uid = key.uid) != NOID)
1N/A options |= OPT_UID;
1N/A if ((gid = key.gid) != NOID)
1N/A options |= OPT_GID;
1N/A }
1N/A switch (options & (OPT_UID|OPT_GID))
1N/A {
1N/A case OPT_UID:
1N/A s = ERROR_translate(0, 0, 0, " owner");
1N/A break;
1N/A case OPT_GID:
1N/A s = ERROR_translate(0, 0, 0, " group");
1N/A break;
1N/A case OPT_UID|OPT_GID:
1N/A s = ERROR_translate(0, 0, 0, " owner and group");
1N/A break;
1N/A default:
1N/A s = "";
1N/A break;
1N/A }
1N/A if (!(fts = fts_open(argv + 1, flags, NiL)))
1N/A error(ERROR_system(1), "%s: not found", argv[1]);
1N/A while (!sh_checksig(context) && (ent = fts_read(fts)))
1N/A switch (ent->fts_info)
1N/A {
1N/A case FTS_F:
1N/A case FTS_D:
1N/A case FTS_SL:
1N/A case FTS_SLNONE:
1N/A anyway:
1N/A if ((unsigned long)ent->fts_statp->st_ctime >= before)
1N/A break;
1N/A if (map)
1N/A {
1N/A options &= ~(OPT_UID|OPT_GID);
1N/A uid = gid = NOID;
1N/A keys[0].uid = keys[1].uid = ent->fts_statp->st_uid;
1N/A keys[0].gid = keys[2].gid = ent->fts_statp->st_gid;
1N/A i = 0;
1N/A do
1N/A {
1N/A if (m = (Map_t*)dtmatch(map, &keys[i]))
1N/A {
1N/A if (uid == NOID && m->to.uid != NOID)
1N/A {
1N/A uid = m->to.uid;
1N/A options |= OPT_UID;
1N/A }
1N/A if (gid == NOID && m->to.gid != NOID)
1N/A {
1N/A gid = m->to.gid;
1N/A options |= OPT_GID;
1N/A }
1N/A }
1N/A } while (++i < elementsof(keys) && (uid == NOID || gid == NOID));
1N/A }
1N/A else
1N/A {
1N/A if (!(options & OPT_UID))
1N/A uid = ent->fts_statp->st_uid;
1N/A if (!(options & OPT_GID))
1N/A gid = ent->fts_statp->st_gid;
1N/A }
1N/A if ((options & OPT_UNMAPPED) && (uid == NOID || gid == NOID))
1N/A {
1N/A if (uid == NOID && gid == NOID)
1N/A error(ERROR_warn(0), "%s: uid and gid not mapped", ent->fts_path);
1N/A else if (uid == NOID)
1N/A error(ERROR_warn(0), "%s: uid not mapped", ent->fts_path);
1N/A else
1N/A error(ERROR_warn(0), "%s: gid not mapped", ent->fts_path);
1N/A }
1N/A if (uid != ent->fts_statp->st_uid && uid != NOID || gid != ent->fts_statp->st_gid && gid != NOID)
1N/A {
1N/A if ((ent->fts_info & FTS_SL) && (flags & FTS_PHYSICAL) && (options & OPT_LCHOWN))
1N/A {
1N/A op = "lchown";
1N/A chownf = lchown;
1N/A }
1N/A else
1N/A {
1N/A op = "chown";
1N/A chownf = chown;
1N/A }
1N/A if (options & (OPT_SHOW|OPT_VERBOSE))
1N/A {
1N/A if (options & OPT_TEST)
1N/A {
1N/A ent->fts_statp->st_uid = 0;
1N/A ent->fts_statp->st_gid = 0;
1N/A }
1N/A 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);
1N/A }
1N/A if (!(options & OPT_SHOW) && (*chownf)(ent->fts_accpath, uid, gid) && !(options & OPT_FORCE))
1N/A error(ERROR_system(0), "%s: cannot change%s", ent->fts_path, s);
1N/A }
1N/A break;
1N/A case FTS_DC:
1N/A if (!(options & OPT_FORCE))
1N/A error(ERROR_warn(0), "%s: directory causes cycle", ent->fts_path);
1N/A break;
1N/A case FTS_DNR:
1N/A if (!(options & OPT_FORCE))
1N/A error(ERROR_system(0), "%s: cannot read directory", ent->fts_path);
1N/A goto anyway;
1N/A case FTS_DNX:
1N/A if (!(options & OPT_FORCE))
1N/A error(ERROR_system(0), "%s: cannot search directory", ent->fts_path);
1N/A goto anyway;
1N/A case FTS_NS:
1N/A if (!(options & OPT_FORCE))
1N/A error(ERROR_system(0), "%s: not found", ent->fts_path);
1N/A break;
1N/A }
1N/A fts_close(fts);
1N/A if (map)
1N/A dtclose(map);
1N/A return error_info.errors != 0;
1N/A}