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