1/* 2 * stat.c - stat builtin interface to system call 3 * 4 * This file is part of zsh, the Z shell. 5 * 6 * Copyright (c) 1996-1997 Peter Stephenson 7 * All rights reserved. 8 * 9 * Permission is hereby granted, without written agreement and without 10 * license or royalty fees, to use, copy, modify, and distribute this 11 * software and to distribute modified versions of this software for any 12 * purpose, provided that the above copyright notice and the following 13 * two paragraphs appear in all copies of this software. 14 * 15 * In no event shall Peter Stephenson or the Zsh Development Group be liable 16 * to any party for direct, indirect, special, incidental, or consequential 17 * damages arising out of the use of this software and its documentation, 18 * even if Peter Stephenson and the Zsh Development Group have been advised of 19 * the possibility of such damage. 20 * 21 * Peter Stephenson and the Zsh Development Group specifically disclaim any 22 * warranties, including, but not limited to, the implied warranties of 23 * merchantability and fitness for a particular purpose. The software 24 * provided hereunder is on an "as is" basis, and Peter Stephenson and the 25 * Zsh Development Group have no obligation to provide maintenance, 26 * support, updates, enhancements, or modifications. 27 * 28 */ 29 30#include "stat.mdh" 31#include "stat.pro" 32 33enum statnum { ST_DEV, ST_INO, ST_MODE, ST_NLINK, ST_UID, ST_GID, 34 ST_RDEV, ST_SIZE, ST_ATIM, ST_MTIM, ST_CTIM, 35 ST_BLKSIZE, ST_BLOCKS, ST_READLINK, ST_COUNT }; 36enum statflags { STF_NAME = 1, STF_FILE = 2, STF_STRING = 4, STF_RAW = 8, 37 STF_PICK = 16, STF_ARRAY = 32, STF_GMT = 64, 38 STF_HASH = 128, STF_OCTAL = 256 }; 39static char *statelts[] = { "device", "inode", "mode", "nlink", 40 "uid", "gid", "rdev", "size", "atime", 41 "mtime", "ctime", "blksize", "blocks", 42 "link", NULL }; 43#define HNAMEKEY "name" 44 45/**/ 46static void 47statmodeprint(mode_t mode, char *outbuf, int flags) 48{ 49 if (flags & STF_RAW) { 50 sprintf(outbuf, (flags & STF_OCTAL) ? "0%lo" : "%lu", 51 (unsigned long)mode); 52 if (flags & STF_STRING) 53 strcat(outbuf, " ("); 54 } 55 if (flags & STF_STRING) { 56 static const char *modes = "?rwxrwxrwx"; 57#ifdef __CYGWIN__ 58 static mode_t mflags[9] = { 0 }; 59#else 60 static const mode_t mflags[9] = { 61 S_IRUSR, S_IWUSR, S_IXUSR, 62 S_IRGRP, S_IWGRP, S_IXGRP, 63 S_IROTH, S_IWOTH, S_IXOTH 64 }; 65#endif 66 const mode_t *mfp = mflags; 67 char pm[11]; 68 int i; 69 70#ifdef __CYGWIN__ 71 if (mflags[0] == 0) { 72 mflags[0] = S_IRUSR; 73 mflags[1] = S_IWUSR; 74 mflags[2] = S_IXUSR; 75 mflags[3] = S_IRGRP; 76 mflags[4] = S_IWGRP; 77 mflags[5] = S_IXGRP; 78 mflags[6] = S_IROTH; 79 mflags[7] = S_IWOTH; 80 mflags[8] = S_IXOTH; 81 } 82#endif 83 84 if (S_ISBLK(mode)) 85 *pm = 'b'; 86 else if (S_ISCHR(mode)) 87 *pm = 'c'; 88 else if (S_ISDIR(mode)) 89 *pm = 'd'; 90 else if (S_ISDOOR(mode)) 91 *pm = 'D'; 92 else if (S_ISFIFO(mode)) 93 *pm = 'p'; 94 else if (S_ISLNK(mode)) 95 *pm = 'l'; 96 else if (S_ISMPC(mode)) 97 *pm = 'm'; 98 else if (S_ISNWK(mode)) 99 *pm = 'n'; 100 else if (S_ISOFD(mode)) 101 *pm = 'M'; 102 else if (S_ISOFL(mode)) 103 *pm = 'M'; 104 else if (S_ISREG(mode)) 105 *pm = '-'; 106 else if (S_ISSOCK(mode)) 107 *pm = 's'; 108 else 109 *pm = '?'; 110 111 for (i = 1; i <= 9; i++) 112 pm[i] = (mode & *mfp++) ? modes[i] : '-'; 113 pm[10] = '\0'; 114 115 if (mode & S_ISUID) 116 pm[3] = (mode & S_IXUSR) ? 's' : 'S'; 117 if (mode & S_ISGID) 118 pm[6] = (mode & S_IXGRP) ? 's' : 'S'; 119 if (mode & S_ISVTX) 120 pm[9] = (mode & S_IXOTH) ? 't' : 'T'; 121 122 pm[10] = 0; 123 strcat(outbuf, pm); 124 if (flags & STF_RAW) 125 strcat(outbuf, ")"); 126 } 127} 128 129 130/**/ 131static void 132statuidprint(uid_t uid, char *outbuf, int flags) 133{ 134 if (flags & STF_RAW) { 135 sprintf(outbuf, "%lu", (unsigned long)uid); 136 if (flags & STF_STRING) 137 strcat(outbuf, " ("); 138 } 139 if (flags & STF_STRING) { 140#ifdef HAVE_GETPWUID 141 struct passwd *pwd; 142 pwd = getpwuid(uid); 143 if (pwd) 144 strcat(outbuf, pwd->pw_name); 145 else 146#endif /* !HAVE_GETPWUID */ 147 { 148 char *optr; 149 for (optr = outbuf; *optr; optr++) 150 ; 151 sprintf(optr, "%lu", (unsigned long)uid); 152 } 153 if (flags & STF_RAW) 154 strcat(outbuf, ")"); 155 } 156} 157 158 159/**/ 160static void 161statgidprint(gid_t gid, char *outbuf, int flags) 162{ 163 if (flags & STF_RAW) { 164 sprintf(outbuf, "%lu", (unsigned long)gid); 165 if (flags & STF_STRING) 166 strcat(outbuf, " ("); 167 } 168 if (flags & STF_STRING) { 169#ifdef USE_GETGRGID 170 struct group *gr; 171 gr = getgrgid(gid); 172 if (gr) 173 strcat(outbuf, gr->gr_name); 174 else 175#endif /* !USE_GETGRGID */ 176 { 177 char *optr; 178 for (optr = outbuf; *optr; optr++) 179 ; 180 sprintf(optr, "%lu", (unsigned long)gid); 181 } 182 if (flags & STF_RAW) 183 strcat(outbuf, ")"); 184 } 185} 186 187static char *timefmt; 188 189/**/ 190static void 191stattimeprint(time_t tim, char *outbuf, int flags) 192{ 193 if (flags & STF_RAW) { 194 sprintf(outbuf, "%ld", (unsigned long)tim); 195 if (flags & STF_STRING) 196 strcat(outbuf, " ("); 197 } 198 if (flags & STF_STRING) { 199 char *oend = outbuf + strlen(outbuf); 200 ztrftime(oend, 40, timefmt, (flags & STF_GMT) ? gmtime(&tim) : 201 localtime(&tim)); 202 if (flags & STF_RAW) 203 strcat(oend, ")"); 204 } 205} 206 207 208/**/ 209static void 210statulprint(unsigned long num, char *outbuf) 211{ 212 sprintf(outbuf, "%lu", num); 213} 214 215 216/**/ 217static void 218statlinkprint(struct stat *sbuf, char *outbuf, char *fname) 219{ 220 int num; 221 222 /* fname is NULL if we are looking at an fd */ 223 if (fname && S_ISLNK(sbuf->st_mode) && 224 (num = readlink(fname, outbuf, PATH_MAX)) > 0) { 225 /* readlink doesn't terminate the buffer itself */ 226 outbuf[num] = '\0'; 227 } 228} 229 230 231/**/ 232static void 233statprint(struct stat *sbuf, char *outbuf, char *fname, int iwhich, int flags) 234{ 235 char *optr = outbuf; 236 237 if (flags & STF_NAME) { 238 sprintf(outbuf, (flags & (STF_PICK|STF_ARRAY)) ? 239 "%s " : "%-8s", statelts[iwhich]); 240 optr += strlen(outbuf); 241 } 242 *optr = '\0'; 243 244 /* cast values to unsigned long as safest bet */ 245 switch (iwhich) { 246 case ST_DEV: 247 statulprint((unsigned long)sbuf->st_dev, optr); 248 break; 249 250 case ST_INO: 251#ifdef INO_T_IS_64_BIT 252 convbase(optr, sbuf->st_ino, 0); 253#else 254 DPUTS(sizeof(sbuf->st_ino) > sizeof(unsigned long), 255 "Shell compiled with wrong ino_t size"); 256 statulprint((unsigned long)sbuf->st_ino, optr); 257#endif 258 break; 259 260 case ST_MODE: 261 statmodeprint(sbuf->st_mode, optr, flags); 262 break; 263 264 case ST_NLINK: 265 statulprint((unsigned long)sbuf->st_nlink, optr); 266 break; 267 268 case ST_UID: 269 statuidprint(sbuf->st_uid, optr, flags); 270 break; 271 272 case ST_GID: 273 statgidprint(sbuf->st_gid, optr, flags); 274 break; 275 276 case ST_RDEV: 277 statulprint((unsigned long)sbuf->st_rdev, optr); 278 break; 279 280 case ST_SIZE: 281#ifdef OFF_T_IS_64_BIT 282 convbase(optr, sbuf->st_size, 0); 283#else 284 DPUTS(sizeof(sbuf->st_size) > sizeof(unsigned long), 285 "Shell compiled with wrong off_t size"); 286 statulprint((unsigned long)sbuf->st_size, optr); 287#endif 288 break; 289 290 case ST_ATIM: 291 stattimeprint(sbuf->st_atime, optr, flags); 292 break; 293 294 case ST_MTIM: 295 stattimeprint(sbuf->st_mtime, optr, flags); 296 break; 297 298 case ST_CTIM: 299 stattimeprint(sbuf->st_ctime, optr, flags); 300 break; 301 302 case ST_BLKSIZE: 303 statulprint((unsigned long)sbuf->st_blksize, optr); 304 break; 305 306 case ST_BLOCKS: 307 statulprint((unsigned long)sbuf->st_blocks, optr); 308 break; 309 310 case ST_READLINK: 311 statlinkprint(sbuf, optr, fname); 312 break; 313 314 case ST_COUNT: /* keep some compilers happy */ 315 break; 316 } 317} 318 319 320/* 321 * 322 * Options: 323 * -f fd: stat fd instead of file 324 * -g: use GMT rather than local time for time strings (forces -s on). 325 * -n: always print file name of file being statted 326 * -N: never print file name 327 * -l: list stat types 328 * -L: do lstat (else links are implicitly dereferenced by stat) 329 * -t: always print name of stat type 330 * -T: never print name of stat type 331 * -r: print raw alongside string data 332 * -s: string, print mode, times, uid, gid as appropriate strings: 333 * harmless but unnecessary when combined with -r. 334 * -A array: assign results to given array, one stat result per element. 335 * File names and type names are only added if explicitly requested: 336 * file names are returned as a separate array element, type names as 337 * prefix to element. Note the formatting deliberately contains 338 * fewer frills when -A is used. 339 * -H hash: as for -A array, but returns a hash with the keys being those 340 * from stat -l 341 * -F fmt: specify a $TIME-like format for printing times; the default 342 * is the (CTIME-like) "%a %b %e %k:%M:%S %Z %Y". This option implies 343 * -s as it is not useful for numerical times. 344 * 345 * +type selects just element type of stat buffer (-l gives list): 346 * type can be shortened to unambiguous string. only one type is 347 * allowed. The extra type, +link, reads the target of a symbolic 348 * link; it is empty if the stat was not an lstat or if 349 * a file descriptor was stat'd, if the stat'd file is 350 * not a symbolic link, or if symbolic links are not 351 * supported. If +link is explicitly requested, the -L (lstat) 352 * option is set automatically. 353 */ 354/**/ 355static int 356bin_stat(char *name, char **args, Options ops, UNUSED(int func)) 357{ 358 char **aptr, *arrnam = NULL, **array = NULL, **arrptr = NULL; 359 char *hashnam = NULL, **hash = NULL, **hashptr = NULL; 360 int len, iwhich = -1, ret = 0, flags = 0, arrsize = 0, fd = 0; 361 struct stat statbuf; 362 int found = 0, nargs; 363 364 timefmt = "%a %b %e %k:%M:%S %Z %Y"; 365 366 for (; *args && (**args == '+' || **args == '-'); args++) { 367 char *arg = *args+1; 368 if (!*arg || *arg == '-' || *arg == '+') { 369 args++; 370 break; 371 } 372 373 if (**args == '+') { 374 if (found) 375 break; 376 len = strlen(arg); 377 for (aptr = statelts; *aptr; aptr++) 378 if (!strncmp(*aptr, arg, len)) { 379 found++; 380 iwhich = aptr - statelts; 381 } 382 if (found > 1) { 383 zwarnnam(name, "%s: ambiguous stat element", arg); 384 return 1; 385 } else if (found == 0) { 386 zwarnnam(name, "%s: no such stat element", arg); 387 return 1; 388 } 389 /* if name of link requested, turn on lstat */ 390 if (iwhich == ST_READLINK) 391 ops->ind['L'] = 1; 392 flags |= STF_PICK; 393 } else { 394 for (; *arg; arg++) { 395 if (strchr("glLnNorstT", *arg)) 396 ops->ind[STOUC(*arg)] = 1; 397 else if (*arg == 'A') { 398 if (arg[1]) { 399 arrnam = arg+1; 400 } else if (!(arrnam = *++args)) { 401 zwarnnam(name, "missing parameter name"); 402 return 1; 403 } 404 flags |= STF_ARRAY; 405 break; 406 } else if (*arg == 'H') { 407 if (arg[1]) { 408 hashnam = arg+1; 409 } else if (!(hashnam = *++args)) { 410 zwarnnam(name, "missing parameter name"); 411 return 1; 412 } 413 flags |= STF_HASH; 414 break; 415 } else if (*arg == 'f') { 416 char *sfd; 417 ops->ind['f'] = 1; 418 if (arg[1]) { 419 sfd = arg+1; 420 } else if (!(sfd = *++args)) { 421 zwarnnam(name, "missing file descriptor"); 422 return 1; 423 } 424 fd = zstrtol(sfd, &sfd, 10); 425 if (*sfd) { 426 zwarnnam(name, "bad file descriptor"); 427 return 1; 428 } 429 break; 430 } else if (*arg == 'F') { 431 if (arg[1]) { 432 timefmt = arg+1; 433 } else if (!(timefmt = *++args)) { 434 zwarnnam(name, "missing time format"); 435 return 1; 436 } 437 /* force string format in order to use time format */ 438 ops->ind['s'] = 1; 439 break; 440 } else { 441 zwarnnam(name, "bad option: -%c", *arg); 442 return 1; 443 } 444 } 445 } 446 } 447 448 if ((flags & STF_ARRAY) && (flags & STF_HASH)) { 449 /* We don't implement setting multiple variables at once */ 450 zwarnnam(name, "both array and hash requested"); 451 return 1; 452 /* Alternate method would be to make -H blank arrnam etc etc * 453 * and so get 'silent loss' of earlier choice, which would * 454 * be similar to stat -A foo -A bar filename */ 455 } 456 457 if (OPT_ISSET(ops,'l')) { 458 /* list types and return: can also list to array */ 459 if (arrnam) { 460 arrptr = array = (char **)zalloc((ST_COUNT+1)*sizeof(char *)); 461 array[ST_COUNT] = NULL; 462 } 463 for (aptr = statelts; *aptr; aptr++) { 464 if (arrnam) { 465 *arrptr++ = ztrdup(*aptr); 466 } else { 467 printf("%s", *aptr); 468 if (aptr[1]) 469 putchar(' '); 470 } 471 } 472 if (arrnam) { 473 setaparam(arrnam, array); 474 if (errflag) 475 return 1; 476 } else 477 putchar('\n'); 478 return 0; 479 } 480 481 if (!*args && !OPT_ISSET(ops,'f')) { 482 zwarnnam(name, "no files given"); 483 return 1; 484 } else if (*args && OPT_ISSET(ops,'f')) { 485 zwarnnam(name, "no files allowed with -f"); 486 return 1; 487 } 488 489 nargs = 0; 490 if (OPT_ISSET(ops,'f')) 491 nargs = 1; 492 else 493 for (aptr = args; *aptr; aptr++) 494 nargs++; 495 496 if (OPT_ISSET(ops,'g')) { 497 flags |= STF_GMT; 498 ops->ind['s'] = 1; 499 } 500 if (OPT_ISSET(ops,'s') || OPT_ISSET(ops,'r')) 501 flags |= STF_STRING; 502 if (OPT_ISSET(ops,'r') || !OPT_ISSET(ops,'s')) 503 flags |= STF_RAW; 504 if (OPT_ISSET(ops,'n')) 505 flags |= STF_FILE; 506 if (OPT_ISSET(ops,'o')) 507 flags |= STF_OCTAL; 508 if (OPT_ISSET(ops,'t')) 509 flags |= STF_NAME; 510 511 if (!(arrnam || hashnam)) { 512 if (nargs > 1) 513 flags |= STF_FILE; 514 if (!(flags & STF_PICK)) 515 flags |= STF_NAME; 516 } 517 518 if (OPT_ISSET(ops,'N') || OPT_ISSET(ops,'f')) 519 flags &= ~STF_FILE; 520 if (OPT_ISSET(ops,'T') || OPT_ISSET(ops,'H')) 521 flags &= ~STF_NAME; 522 523 if (hashnam) { 524 if (nargs > 1) { 525 zwarnnam(name, "only one file allowed with -H"); 526 return 1; 527 } 528 arrsize = (flags & STF_PICK) ? 1 : ST_COUNT; 529 if (flags & STF_FILE) 530 arrsize++; 531 hashptr = hash = (char **)zshcalloc((arrsize+1)*2*sizeof(char *)); 532 } 533 534 if (arrnam) { 535 arrsize = (flags & STF_PICK) ? 1 : ST_COUNT; 536 if (flags & STF_FILE) 537 arrsize++; 538 arrsize *= nargs; 539 arrptr = array = (char **)zshcalloc((arrsize+1)*sizeof(char *)); 540 } 541 542 for (; OPT_ISSET(ops,'f') || *args; args++) { 543 char outbuf[PATH_MAX + 9]; /* "link " + link name + NULL */ 544 int rval = OPT_ISSET(ops,'f') ? fstat(fd, &statbuf) : 545 OPT_ISSET(ops,'L') ? lstat(unmeta(*args), &statbuf) : 546 stat(unmeta(*args), &statbuf); 547 if (rval) { 548 if (OPT_ISSET(ops,'f')) 549 sprintf(outbuf, "%d", fd); 550 zwarnnam(name, "%s: %e", OPT_ISSET(ops,'f') ? outbuf : *args, 551 errno); 552 ret = 1; 553 if (OPT_ISSET(ops,'f') || arrnam) 554 break; 555 else 556 continue; 557 } 558 559 if (flags & STF_FILE) { 560 if (arrnam) 561 *arrptr++ = ztrdup(*args); 562 else if (hashnam) { 563 *hashptr++ = ztrdup(HNAMEKEY); 564 *hashptr++ = ztrdup(*args); 565 } else 566 printf("%s%s", *args, (flags & STF_PICK) ? " " : ":\n"); 567 } 568 if (iwhich > -1) { 569 statprint(&statbuf, outbuf, *args, iwhich, flags); 570 if (arrnam) 571 *arrptr++ = metafy(outbuf, -1, META_DUP); 572 else if (hashnam) { 573 /* STF_NAME explicitly turned off for ops.ind['H'] above */ 574 *hashptr++ = ztrdup(statelts[iwhich]); 575 *hashptr++ = metafy(outbuf, -1, META_DUP); 576 } else 577 printf("%s\n", outbuf); 578 } else { 579 int i; 580 for (i = 0; i < ST_COUNT; i++) { 581 statprint(&statbuf, outbuf, *args, i, flags); 582 if (arrnam) 583 *arrptr++= metafy(outbuf, -1, META_DUP); 584 else if (hashnam) { 585 /* STF_NAME explicitly turned off for ops.ind['H'] above */ 586 *hashptr++ = ztrdup(statelts[i]); 587 *hashptr++ = metafy(outbuf, -1, META_DUP); 588 } else 589 printf("%s\n", outbuf); 590 } 591 } 592 if (OPT_ISSET(ops,'f')) 593 break; 594 595 if (!arrnam && !hashnam && args[1] && !(flags & STF_PICK)) 596 putchar('\n'); 597 } 598 599 if (arrnam) { 600 if (ret) 601 freearray(array); 602 else { 603 setaparam(arrnam, array); 604 if (errflag) 605 return 1; 606 } 607 } 608 609 if (hashnam) { 610 if (ret) 611 freearray(hash); 612 else { 613 sethparam(hashnam, hash); 614 if (errflag) 615 return 1; 616 } 617 } 618 619 return ret; 620} 621 622static struct builtin bintab[] = { 623 BUILTIN("stat", 0, bin_stat, 0, -1, 0, NULL, NULL), 624 BUILTIN("zstat", 0, bin_stat, 0, -1, 0, NULL, NULL), 625}; 626 627static struct features module_features = { 628 bintab, sizeof(bintab)/sizeof(*bintab), 629 NULL, 0, 630 NULL, 0, 631 NULL, 0, 632 0 633}; 634 635/**/ 636int 637setup_(UNUSED(Module m)) 638{ 639 return 0; 640} 641 642/**/ 643int 644features_(Module m, char ***features) 645{ 646 *features = featuresarray(m, &module_features); 647 return 0; 648} 649 650/**/ 651int 652enables_(Module m, int **enables) 653{ 654 return handlefeatures(m, &module_features, enables); 655} 656 657/**/ 658int 659boot_(Module m) 660{ 661 return 0; 662} 663 664/**/ 665int 666cleanup_(Module m) 667{ 668 return setfeatureenables(m, &module_features, NULL); 669} 670 671/**/ 672int 673finish_(UNUSED(Module m)) 674{ 675 return 0; 676} 677