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 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 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