1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1992-2007 AT&T Knowledge Ventures * 5 * and is licensed under the * 6 * Common Public License, Version 1.0 * 7 * by AT&T Knowledge Ventures * 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) 2006-10-11 $\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 "[c:changes?Describe only files whose ownership actually changes.]" 53 "[f:quiet|silent?Do not report files whose ownership fails to change.]" 54 "[l|h:symlink?Change the ownership of the symbolic links on systems that" 55 " support this.]" 56 "[m:map?The first operand is interpreted as a file that contains a map" 57 " of \afrom_uid:from_gid to_uid:to_gid\a pairs. Ownership of files" 58 " matching the \afrom\a part of any pair is changed to the corresponding" 59 " \ato\a part of the pair. The process stops at the first match for" 60 " each file. Unmatched files are silently ignored.]" 61 "[n:show?Show actions but don't execute.]" 62 "[r:reference?Omit the explicit ownership operand and use the ownership of" 63 " \afile\a instead.]:[file]" 64 "[v:verbose?Describe changed permissions of all files.]" 65 "[H:metaphysical?Follow symbolic links for command arguments; otherwise don't" 66 " follow symbolic links when traversing directories.]" 67 "[L:logical|follow?Follow symbolic links when traversing directories.]" 68 "[P:physical|nofollow?Don't follow symbolic links when traversing directories.]" 69 "[R:recursive?Recursively change ownership of directories and their contents.]" 70 "[X:test?Canonicalize output for testing.]" 71 72 "\n" 73 "\n" 74 ; 75 76 static const char usage_3[] = 77 " file ...\n" 78 "\n" 79 "[+EXIT STATUS?]{" 80 "[+0?All files changed successfully.]" 81 "[+>0?Unable to change ownership of one or more files.]" 82 "}" 83 "[+SEE ALSO?\bchmod\b(1), \btw\b(1), \bgetconf\b(1), \bls\b(1)]" 84 ; 85 86 #if defined(__STDPP__directive) && defined(__STDPP__hide) 87 __STDPP__directive pragma pp:hide lchown 88 #else 89 #define lchown ______lchown 90 #endif 91 92 #include <cmd.h> 93 #include <cdt.h> 94 #include <ls.h> 95 #include <ctype.h> 96 #include <fts.h> 97 98 #include "FEATURE/symlink" 99 100 #if defined(__STDPP__directive) && defined(__STDPP__hide) 101 __STDPP__directive pragma pp:nohide lchown 102 #else 103 #undef lchown 104 #endif 105 106 typedef struct /* uid/gid map */ 107 { 108 Dtlink_t link; /* dictionary link */ 109 int id; /* id */ 110 int uid; /* id maps to this uid */ 111 int gid; /* id maps to this gid */ 112 } Map_t; 113 114 #define NOID (-1) 115 116 #define OPT_CHOWN (1<<0) /* chown */ 117 #define OPT_FORCE (1<<1) /* ignore errors */ 118 #define OPT_GID (1<<2) /* have gid */ 119 #define OPT_LCHOWN (1<<3) /* lchown */ 120 #define OPT_SHOW (1<<4) /* show but don't do */ 121 #define OPT_TEST (1<<5) /* canonicalize output */ 122 #define OPT_UID (1<<6) /* have uid */ 123 #define OPT_VERBOSE (1<<7) /* have uid */ 124 125 extern int lchown(const char*, uid_t, gid_t); 126 127 #if !_lib_lchown 128 129 #ifndef ENOSYS 130 #define ENOSYS EINVAL 131 #endif 132 133 int 134 lchown(const char* path, uid_t uid, gid_t gid) 135 { 136 return ENOSYS; 137 } 138 139 #endif /* _lib_chown */ 140 141 /* 142 * parse uid and gid from s 143 */ 144 145 static void 146 getids(register char* s, char** e, int* uid, int* gid, int options) 147 { 148 register char* t; 149 register int n; 150 char* z; 151 char buf[64]; 152 153 *uid = *gid = NOID; 154 while (isspace(*s)) 155 s++; 156 for (t = s; (n = *t) && n != ':' && n != '.' && !isspace(n); t++); 157 if (n) 158 { 159 options |= OPT_CHOWN; 160 if ((n = t++ - s) >= sizeof(buf)) 161 n = sizeof(buf) - 1; 162 *((s = (char*)memcpy(buf, s, n)) + n) = 0; 163 while (isspace(*t)) 164 t++; 165 } 166 if (options & OPT_CHOWN) 167 { 168 if (*s) 169 { 170 if ((n = struid(s)) == NOID) 171 { 172 n = (int)strtol(s, &z, 0); 173 if (*z) 174 error(ERROR_exit(1), "%s: unknown user", s); 175 } 176 *uid = n; 177 } 178 for (s = t; (n = *t) && !isspace(n); t++); 179 if (n) 180 { 181 if ((n = t++ - s) >= sizeof(buf)) 182 n = sizeof(buf) - 1; 183 *((s = (char*)memcpy(buf, s, n)) + n) = 0; 184 } 185 } 186 if (*s) 187 { 188 if ((n = strgid(s)) == NOID) 189 { 190 n = (int)strtol(s, &z, 0); 191 if (*z) 192 error(ERROR_exit(1), "%s: unknown group", s); 193 } 194 *gid = n; 195 } 196 if (e) 197 *e = t; 198 } 199 200 int 201 b_chgrp(int argc, char** argv, void* context) 202 { 203 register int options = 0; 204 register char* s; 205 register Map_t* m; 206 register FTS* fts; 207 register FTSENT*ent; 208 Dt_t* map = 0; 209 int flags; 210 int uid; 211 int gid; 212 char* op; 213 char* usage; 214 Sfio_t* sp; 215 Dtdisc_t mapdisc; 216 struct stat st; 217 int (*chownf)(const char*, uid_t, gid_t); 218 219 cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY); 220 flags = fts_flags() | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR; 221 if (!(sp = sfstropen())) 222 error(ERROR_SYSTEM|3, "out of space"); 223 sfputr(sp, usage_1, -1); 224 if (error_info.id[2] == 'g') 225 sfputr(sp, usage_grp_1, -1); 226 else 227 { 228 sfputr(sp, usage_own_1, -1); 229 options |= OPT_CHOWN; 230 } 231 sfputr(sp, usage_2, -1); 232 if (options & OPT_CHOWN) 233 sfputr(sp, ERROR_translate(0, 0, 0, "[owner[:group]]"), -1); 234 else 235 sfputr(sp, ERROR_translate(0, 0, 0, "[[owner:]group]"), -1); 236 sfputr(sp, usage_3, -1); 237 if (!(usage = sfstruse(sp))) 238 error(ERROR_SYSTEM|3, "out of space"); 239 for (;;) 240 { 241 switch (optget(argv, usage)) 242 { 243 case 'c': 244 case 'v': 245 options |= OPT_VERBOSE; 246 continue; 247 case 'f': 248 options |= OPT_FORCE; 249 continue; 250 case 'l': 251 options |= OPT_LCHOWN; 252 continue; 253 case 'm': 254 memset(&mapdisc, 0, sizeof(mapdisc)); 255 mapdisc.key = offsetof(Map_t, id); 256 mapdisc.size = sizeof(int); 257 if (!(map = dtopen(&mapdisc, Dthash))) 258 error(ERROR_exit(1), "out of space [id map]"); 259 continue; 260 case 'n': 261 options |= OPT_SHOW; 262 continue; 263 case 'r': 264 if (stat(opt_info.arg, &st)) 265 error(ERROR_exit(1), "%s: cannot stat", opt_info.arg); 266 uid = st.st_uid; 267 gid = st.st_gid; 268 options |= OPT_UID|OPT_GID; 269 continue; 270 case 'H': 271 flags |= FTS_META|FTS_PHYSICAL; 272 continue; 273 case 'L': 274 flags &= ~(FTS_META|FTS_PHYSICAL); 275 continue; 276 case 'P': 277 flags &= ~FTS_META; 278 flags |= FTS_PHYSICAL; 279 continue; 280 case 'R': 281 flags &= ~FTS_TOP; 282 continue; 283 case 'X': 284 options |= OPT_TEST; 285 continue; 286 case ':': 287 error(2, "%s", opt_info.arg); 288 continue; 289 case '?': 290 error(ERROR_usage(2), "%s", opt_info.arg); 291 break; 292 } 293 break; 294 } 295 argv += opt_info.index; 296 argc -= opt_info.index; 297 if (error_info.errors || argc < 2) 298 error(ERROR_usage(2), "%s", optusage(NiL)); 299 s = *argv; 300 if (map) 301 { 302 char* t; 303 int nuid; 304 int ngid; 305 306 if (streq(s, "-")) 307 sp = sfstdin; 308 else if (!(sp = sfopen(NiL, s, "r"))) 309 error(ERROR_exit(1), "%s: cannot read", s); 310 while (s = sfgetr(sp, '\n', 1)) 311 { 312 getids(s, &t, &uid, &gid, options); 313 getids(t, NiL, &nuid, &ngid, options); 314 if (uid != NOID) 315 { 316 if (m = (Map_t*)dtmatch(map, &uid)) 317 { 318 m->uid = nuid; 319 if (m->gid == NOID) 320 m->gid = ngid; 321 } 322 else if (m = (Map_t*)stakalloc(sizeof(Map_t))) 323 { 324 m->id = uid; 325 m->uid = nuid; 326 m->gid = ngid; 327 dtinsert(map, m); 328 } 329 else 330 error(ERROR_exit(1), "out of space [id dictionary]"); 331 } 332 if (gid != NOID) 333 { 334 if (gid == uid || (m = (Map_t*)dtmatch(map, &gid))) 335 m->gid = ngid; 336 else if (m = (Map_t*)stakalloc(sizeof(Map_t))) 337 { 338 m->id = gid; 339 m->uid = NOID; 340 m->gid = ngid; 341 dtinsert(map, m); 342 } 343 else 344 error(ERROR_exit(1), "out of space [id dictionary]"); 345 } 346 } 347 if (sp != sfstdin) 348 sfclose(sp); 349 } 350 else if (!(options & (OPT_UID|OPT_GID))) 351 { 352 getids(s, NiL, &uid, &gid, options); 353 if (uid != NOID) 354 options |= OPT_UID; 355 if (gid != NOID) 356 options |= OPT_GID; 357 } 358 switch (options & (OPT_UID|OPT_GID)) 359 { 360 case OPT_UID: 361 s = ERROR_translate(0, 0, 0, " owner"); 362 break; 363 case OPT_GID: 364 s = ERROR_translate(0, 0, 0, " group"); 365 break; 366 case OPT_UID|OPT_GID: 367 s = ERROR_translate(0, 0, 0, " owner and group"); 368 break; 369 default: 370 s = ""; 371 break; 372 } 373 if (!(fts = fts_open(argv + 1, flags, NiL))) 374 error(ERROR_system(1), "%s: not found", argv[1]); 375 while (!cmdquit() && (ent = fts_read(fts))) 376 switch (ent->fts_info) 377 { 378 case FTS_F: 379 case FTS_D: 380 case FTS_SL: 381 case FTS_SLNONE: 382 anyway: 383 if (map) 384 { 385 options &= ~(OPT_UID|OPT_GID); 386 uid = ent->fts_statp->st_uid; 387 gid = ent->fts_statp->st_gid; 388 if ((m = (Map_t*)dtmatch(map, &uid)) && m->uid != NOID) 389 { 390 uid = m->uid; 391 options |= OPT_UID; 392 } 393 if (gid != uid) 394 m = (Map_t*)dtmatch(map, &gid); 395 if (m && m->gid != NOID) 396 { 397 gid = m->gid; 398 options |= OPT_GID; 399 } 400 } 401 else 402 { 403 if (!(options & OPT_UID)) 404 uid = ent->fts_statp->st_uid; 405 if (!(options & OPT_GID)) 406 gid = ent->fts_statp->st_gid; 407 } 408 if (uid != ent->fts_statp->st_uid || gid != ent->fts_statp->st_gid) 409 { 410 if ((ent->fts_info & FTS_SL) && (flags & FTS_PHYSICAL) && (options & OPT_LCHOWN)) 411 { 412 op = "lchown"; 413 chownf = lchown; 414 } 415 else 416 { 417 op = "chown"; 418 chownf = chown; 419 } 420 if (options & (OPT_SHOW|OPT_VERBOSE)) 421 { 422 if (options & OPT_TEST) 423 { 424 ent->fts_statp->st_uid = 0; 425 ent->fts_statp->st_gid = 0; 426 } 427 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_accpath); 428 } 429 if (!(options & OPT_SHOW) && (*chownf)(ent->fts_accpath, uid, gid) && !(options & OPT_FORCE)) 430 error(ERROR_system(0), "%s: cannot change%s", ent->fts_accpath, s); 431 } 432 break; 433 case FTS_DC: 434 if (!(options & OPT_FORCE)) 435 error(ERROR_warn(0), "%s: directory causes cycle", ent->fts_accpath); 436 break; 437 case FTS_DNR: 438 if (!(options & OPT_FORCE)) 439 error(ERROR_system(0), "%s: cannot read directory", ent->fts_accpath); 440 goto anyway; 441 case FTS_DNX: 442 if (!(options & OPT_FORCE)) 443 error(ERROR_system(0), "%s: cannot search directory", ent->fts_accpath); 444 goto anyway; 445 case FTS_NS: 446 if (!(options & OPT_FORCE)) 447 error(ERROR_system(0), "%s: not found", ent->fts_accpath); 448 break; 449 } 450 fts_close(fts); 451 if (map) 452 dtclose(map); 453 return error_info.errors != 0; 454 } 455