|
|
1.1 ! root 1: /* ! 2: * cmd/test.c ! 3: * 5/4/93 ! 4: * Set status based on specified conditions, mostly related to files. ! 5: * Used for control flow in shell scripts. ! 6: * Cf. POSIX P1003.2/D11.2 section 4.62; extensions marked !POSIX below. ! 7: * Usage: see usage() below. ! 8: * Exit status: ! 9: * 0 expression is true ! 10: * 1 expression is false or absent ! 11: * 2 syntax error or other error ! 12: * This used to be a yacc grammar test.y, but the grammar is not really LR(1); ! 13: * e.g. "-ne" can be used either as an ordinary argument or as a string, ! 14: * which causes great complication in yylex()/yyparse() interaction. ! 15: * This version uses ad hoc recursive parse instead. ! 16: */ ! 17: ! 18: #include <stdio.h> ! 19: #include <stdlib.h> ! 20: #include <access.h> ! 21: #include <sys/stat.h> ! 22: ! 23: #define ERROR 2 /* error exit status */ ! 24: #define FALSE "" /* false (empty) arg */ ! 25: #define TRUE "T" /* true (nonempty) arg */ ! 26: #define equal(s1, s2) (strcmp((s1), (s2)) == 0) ! 27: ! 28: /* Primary operators. */ ! 29: typedef struct prim { ! 30: char *p_name; /* operator name */ ! 31: int (*p_fn)(); /* function */ ! 32: } PRIM; ! 33: ! 34: /* Forward function definitions. */ ! 35: int (*is_binary)(); ! 36: int (*is_unary)(); ! 37: int rparen(); ! 38: int test(); ! 39: int testsub(); ! 40: void usage(); ! 41: ! 42: /* Elementary test functions. */ ! 43: int x_b(); ! 44: int x_c(); ! 45: int x_d(); ! 46: int x_e(); ! 47: int x_ef(); ! 48: int x_eq(); ! 49: int x_f(); ! 50: int x_g(); ! 51: int x_ge(); ! 52: int x_gt(); ! 53: int x_k(); ! 54: int x_L(); ! 55: int x_le(); ! 56: int x_lt(); ! 57: int x_n(); ! 58: int x_ne(); ! 59: int x_nt(); ! 60: int x_ot(); ! 61: int x_p(); ! 62: int x_r(); ! 63: int x_s(); ! 64: int x_str_eq(); ! 65: int x_str_gt(); ! 66: int x_str_lt(); ! 67: int x_str_ne(); ! 68: int x_t(); ! 69: int x_u(); ! 70: int x_w(); ! 71: int x_x(); ! 72: int x_z(); ! 73: ! 74: /* Globals. */ ! 75: struct stat sb, sb2; /* stat() buffers */ ! 76: ! 77: /* Unary primaries, not including "!"; implicit leading '-'. */ ! 78: PRIM uprims[] = { ! 79: "b", x_b, ! 80: "c", x_c, ! 81: "d", x_d, ! 82: "e", x_e, ! 83: "f", x_f, /* POSIX semantics, not BSD */ ! 84: "g", x_g, ! 85: "k", x_k, /* !POSIX */ ! 86: "L", x_L, /* !POSIX */ ! 87: "n", x_n, ! 88: "p", x_p, ! 89: "r", x_r, ! 90: "s", x_s, ! 91: "t", x_t, /* POSIX semantics, arg not optional */ ! 92: "u", x_u, ! 93: "w", x_w, ! 94: "x", x_x, ! 95: "z", x_z ! 96: }; ! 97: #define NUPRIM (sizeof(uprims)/sizeof(uprims[0])) ! 98: ! 99: /* Binary primaries, not including string binaries (below), "-a", "-o"; implicit leading '-'. */ ! 100: PRIM bprims[] = { ! 101: "ef", x_ef, /* !POSIX */ ! 102: "eq", x_eq, ! 103: "ge", x_ge, ! 104: "gt", x_gt, ! 105: "le", x_le, ! 106: "lt", x_lt, ! 107: "ne", x_ne, ! 108: "nt", x_nt, /* !POSIX */ ! 109: "ot", x_ot /* !POSIX */ ! 110: }; ! 111: #define NBPRIM (sizeof(bprims)/sizeof(bprims[0])) ! 112: ! 113: /* String binary primaries, no leading '-'. */ ! 114: PRIM sprims[] = { ! 115: "=", x_str_eq, ! 116: ">", x_str_gt, /* !POSIX */ ! 117: "<", x_str_lt, /* !POSIX */ ! 118: "!=", x_str_ne ! 119: }; ! 120: #define NSPRIM (sizeof(sprims)/sizeof(sprims[0])) ! 121: ! 122: main(argc, argv) register int argc; char *argv[]; ! 123: { ! 124: register int n; ! 125: ! 126: --argc; ! 127: if (equal(argv[0], "[")) { ! 128: if (!equal(argv[argc], "]")) { ! 129: fprintf(stderr, "[: missing ]\n"); ! 130: exit(ERROR); ! 131: } ! 132: argc--; ! 133: } ! 134: ++argv; ! 135: if ((n = test(argc, argv)) == -1) ! 136: usage(); /* syntax error */ ! 137: exit(!n); /* flip returned status */ ! 138: } ! 139: ! 140: /* Return function pointer if arg is a binary primary, else return NULL. */ ! 141: int ! 142: (*is_binary(s)) register char *s; ! 143: { ! 144: register PRIM *pp, *endp; ! 145: ! 146: if (*s != '-') { /* no leading '-', search string primaries */ ! 147: pp = sprims; ! 148: endp = &sprims[NSPRIM]; ! 149: } else { /* leading '-', search nonstring primaries */ ! 150: ++s; /* and skip leading '-' */ ! 151: pp = bprims; ! 152: endp = &bprims[NBPRIM]; ! 153: } ! 154: for ( ; pp < endp; pp++) ! 155: if (equal(pp->p_name, s)) ! 156: return pp->p_fn; ! 157: return NULL; ! 158: } ! 159: ! 160: /* Return function pointer if arg is a unary primary, else return NULL. */ ! 161: int ! 162: (*is_unary(s)) register char *s; ! 163: { ! 164: register PRIM *pp; ! 165: ! 166: if (*s++ != '-') ! 167: return NULL; ! 168: for (pp = uprims; pp < &uprims[NUPRIM]; pp++) ! 169: if (equal(pp->p_name, s)) ! 170: return pp->p_fn; ! 171: return NULL; ! 172: } ! 173: ! 174: /* ! 175: * argv[0] is '(', so find the matching ')' and return its index. ! 176: * Return -1 if not found. ! 177: */ ! 178: int ! 179: rparen(argc, argv) int argc; char *argv[]; ! 180: { ! 181: register int n, count; ! 182: ! 183: for (count = n = 1; n < argc; n++) { ! 184: if (equal(argv[n], "(")) ! 185: ++count; ! 186: else if (equal(argv[n], ")") && --count == 0) ! 187: return n; ! 188: } ! 189: return -1; ! 190: } ! 191: ! 192: /* ! 193: * Parse and evaluate the test expression. ! 194: * The order in which subexpressions are tried here determines the parsing. ! 195: * Handle parens, "-a", "-o", "!" directly here. ! 196: * Tricky stuff, this may need some tinkering. ! 197: * Return 1 if true, 0 if false, -1 if syntax error. ! 198: */ ! 199: int ! 200: test(argc, argv) register int argc; char *argv[]; ! 201: { ! 202: register int (*fnp)(); ! 203: register int n, i; ! 204: ! 205: #if DEBUG ! 206: printf("test(argc=%d, argv={ ", argc); ! 207: for (n = 0; n < argc; n++) ! 208: printf("%s ", argv[n]); ! 209: printf("})\n"); ! 210: #endif ! 211: if (argc == 0) ! 212: return 0; ! 213: if (argc == 1) ! 214: return argv[0][0] != 0; ! 215: /* We can assume argc >= 2 below here. */ ! 216: if (argc == 2 && (fnp = is_unary(argv[0])) != NULL) ! 217: return (*fnp)(argv[1]); ! 218: if (argc == 3 && (fnp = is_binary(argv[1])) != NULL) ! 219: return (*fnp)(argv[0], argv[2]); ! 220: if (argc <= 4 && equal(argv[0], "!")) { ! 221: if (argc == 2) ! 222: return argv[1][0] == 0; ! 223: if (argc == 3 && (fnp = is_unary(argv[1])) != NULL) ! 224: return !(*fnp)(argv[2]); ! 225: if (argc == 4 && (fnp = is_binary(argv[2])) != NULL) ! 226: return !(*fnp)(argv[1], argv[3]); ! 227: } ! 228: if (equal(argv[0], "(")) { ! 229: /* Find the matching ")" and evaluate the subexpression. */ ! 230: if ((n = rparen(argc, argv)) == -1) ! 231: return -1; ! 232: else if ((i = test(n - 1, &argv[1])) == -1) ! 233: return -1; ! 234: /* Replace the subexpression by TRUE or FALSE, evaluate the rest. */ ! 235: argv[n] = (i) ? TRUE : FALSE; ! 236: return test(argc - n, &argv[n]); ! 237: } ! 238: if ((n = testsub(argc, argv, "-o")) != -1) ! 239: return n; ! 240: if ((n = testsub(argc, argv, "-a")) != -1) ! 241: return n; ! 242: if (equal(argv[0], "!") && (n = test(argc-1, &argv[1])) != -1) ! 243: return !n; ! 244: #if DEBUG ! 245: printf("\ttest(): failed!\n"); ! 246: #endif ! 247: return -1; ! 248: } ! 249: ! 250: /* ! 251: * Try to split test expression involving "-a" or "-o" into subexpressions. ! 252: * The scan is r-to-l to make "-a" and "-o" left associative. ! 253: * Watch out for parens. ! 254: * Return 1 if true, 0 if false, -1 if syntax error. ! 255: */ ! 256: int ! 257: testsub(argc, argv, opt) int argc; char *argv[]; char *opt; ! 258: { ! 259: register int n, parens, n1, n2; ! 260: ! 261: for (parens = 0, n = argc-1; n > 0; n--) { ! 262: if (equal(argv[n], ")")) ! 263: ++parens; ! 264: else if (equal(argv[n], "(")) ! 265: --parens; ! 266: else if (equal(argv[n], opt) && parens == 0 && n < argc-1) { ! 267: /* Try subdividing at arg n. */ ! 268: if ((n1 = test(n, argv)) != -1 ! 269: && (n2 = test(argc-n-1, &argv[n+1])) != -1) { ! 270: #if DEBUG ! 271: printf("\ttestsub(): use %s at arg %d\n", opt, n); ! 272: #endif ! 273: return (equal(opt, "-o")) ? (n1 || n2) : (n1 && n2); ! 274: } ! 275: } ! 276: } ! 277: return -1; ! 278: } ! 279: ! 280: /* Print verbose usage message and die. */ ! 281: void ! 282: usage() ! 283: { ! 284: fprintf(stderr, ! 285: "test: test expression syntax error\n" ! 286: "Usage: test [ expression ]\n" ! 287: ); ! 288: #if !DEBUG ! 289: fprintf(stderr, ! 290: "Unary primaries:\n" ! 291: "\t-b file\t\tfile exists and is a block special file\n" ! 292: "\t-c file\t\tfile exists and is a character special file\n" ! 293: "\t-d file\t\tfile exists and is a directory\n" ! 294: "\t-e file\t\tfile exists\n" ! 295: "\t-f file\t\tfile exists and is a regular file\n" ! 296: "\t-g file\t\tfile exists and is setgid\n" ! 297: "\t-k file\t\tfile exists and has sticky bit set\t(not Posix)\n" ! 298: "\t-L file\t\tfile is a link\t\t\t\t(not Posix)\n" ! 299: "\t-n string\tstring length is nonzero\n" ! 300: "\t-p file\t\tfile exists and is a named pipe (FIFO)\n" ! 301: "\t-r file\t\tfile exists and is readable\n" ! 302: "\t-s file\t\tfile exists and has nonzero size\n" ! 303: "\t-t fd\t\tfd is the file descriptor of a terminal\n" ! 304: "\t-u file\t\tfile exists and is setuid\n" ! 305: "\t-w file\t\tfile exists and is writable\n" ! 306: "\t-x file\t\tfile exists and is executable\n" ! 307: "\t-z string\tstring length is zero\n" ! 308: "\tstring\t\tstring is not the empty string\n" ! 309: ); ! 310: fprintf(stderr, ! 311: "Binary primaries:\n" ! 312: "\ts1 = s2\t\tstrings s1 and s2 are identical\n" ! 313: "\ts1 != s2\tstrings s1 and s2 are not identical\n" ! 314: "\ts1 < s2\t\tstring s1 is less than s2\t\t(not Posix)\n" ! 315: "\ts1 > s2\t\tstring s1 is greater than s2\t\t(not Posix)\n" ! 316: "\tfile1 -ef file2\tfile1 and file2 are identical\t\t(not Posix)\n" ! 317: "\tn1 -eq n2\tnumbers n1 and n2 are equal\n" ! 318: "\tn1 -ge n2\tnumber n1 is greater than or equal to n2\n" ! 319: "\tn1 -gt n2\tnumber n1 is greater than n2\n" ! 320: "\tn1 -le n2\tnumber n1 is less than or equal to n2\n" ! 321: "\tn1 -lt n2\tnumber n1 is less than n2\n" ! 322: "\tn1 -ne n2\tnumbers n1 and n2 are not equal\n" ! 323: "\tfile1 -nt file2\tfile1 is newer than file2\t\t(not Posix)\n" ! 324: "\tfile1 -ot file2\tfile1 is older than file2\t\t(not Posix)\n" ! 325: ); ! 326: fprintf(stderr, ! 327: "Expression grouping:\n" ! 328: "\t! exp\t\texp is false\n" ! 329: "\texp1 -a exp2\texp1 and exp2 are true\t\t\t(not Posix)\n" ! 330: "\texp1 -o exp2\texp1 or exp2 is true\t\t\t(not Posix)\n" ! 331: "\t( exp )\t\tparentheses for grouping\t\t(not Posix)\n" ! 332: ); ! 333: #endif ! 334: exit(ERROR); ! 335: } ! 336: ! 337: /* Elementary test routines; each returns 1 if true, 0 if false. */ ! 338: /* File exists and is a block special file. */ ! 339: int ! 340: x_b(s) char *s; ! 341: { ! 342: return stat(s, &sb)>=0 && (sb.st_mode&S_IFMT)==S_IFBLK; ! 343: } ! 344: ! 345: /* File exists and is a character special file. */ ! 346: int ! 347: x_c(s) char *s; ! 348: { ! 349: return stat(s, &sb)>=0 && (sb.st_mode&S_IFMT)==S_IFCHR; ! 350: } ! 351: ! 352: /* File exists and is a directory. */ ! 353: int ! 354: x_d(s) char *s; ! 355: { ! 356: return stat(s, &sb)>=0 && (sb.st_mode&S_IFMT)==S_IFDIR; ! 357: } ! 358: ! 359: /* File exists. */ ! 360: int ! 361: x_e(s) char *s; ! 362: { ! 363: return stat(s, &sb)>=0; ! 364: } ! 365: ! 366: /* Two files are the same. */ ! 367: int ! 368: x_ef(s1, s2) char *s1, *s2; ! 369: { ! 370: return stat(s1, &sb)>=0 ! 371: && stat(s2, &sb2)>=0 ! 372: && sb.st_dev==sb2.st_dev && sb.st_ino==sb2.st_ino; ! 373: } ! 374: ! 375: /* Two numbers are equal. */ ! 376: int ! 377: x_eq(s1, s2) char *s1, *s2; ! 378: { ! 379: return atol(s1) == atol(s2); ! 380: } ! 381: ! 382: /* File exists and is an ordinary file. */ ! 383: /* This is POSIX semantics; BSD uses "file exists and is not a directory." */ ! 384: int ! 385: x_f(s) char *s; ! 386: { ! 387: return stat(s, &sb)>=0 && (sb.st_mode&S_IFMT)==S_IFREG; ! 388: } ! 389: ! 390: /* File exists and is setgid. */ ! 391: int ! 392: x_g(s) char *s; ! 393: { ! 394: return stat(s, &sb)>=0 && (sb.st_mode&S_ISGID)!=0; ! 395: } ! 396: ! 397: /* First number is greater than or equal to the second. */ ! 398: int ! 399: x_ge(s1, s2) char *s1, *s2; ! 400: { ! 401: return atol(s1) >= atol(s2); ! 402: } ! 403: ! 404: /* First number is greater than the second. */ ! 405: int ! 406: x_gt(s1, s2) char *s1, *s2; ! 407: { ! 408: return atol(s1) > atol(s2); ! 409: } ! 410: ! 411: /* File exists and has sticky bit set. */ ! 412: int ! 413: x_k(s) char *s; ! 414: { ! 415: return stat(s, &sb)>=0 && (sb.st_mode&S_ISVTX)!=0; ! 416: } ! 417: ! 418: /* File is a link. */ ! 419: int ! 420: x_L(s) char *s; ! 421: { ! 422: return stat(s, &sb)>=0 && sb.st_nlink>1; ! 423: } ! 424: ! 425: /* First number is less than or equal to the second. */ ! 426: int ! 427: x_le(s1, s2) char *s1, *s2; ! 428: { ! 429: return atol(s1) <= atol(s2); ! 430: } ! 431: ! 432: /* First number is less than the second. */ ! 433: int ! 434: x_lt(s1, s2) char *s1, *s2; ! 435: { ! 436: return atol(s1) < atol(s2); ! 437: } ! 438: ! 439: /* String length is non-zero. */ ! 440: int ! 441: x_n(s) char *s; ! 442: { ! 443: return s[0] != '\0'; ! 444: } ! 445: ! 446: /* Two numbers are not equal. */ ! 447: int ! 448: x_ne(s1, s2) char *s1, *s2; ! 449: { ! 450: return atol(s1) != atol(s2); ! 451: } ! 452: ! 453: /* First file is newer than the second. */ ! 454: int ! 455: x_nt(s1, s2) char *s1, *s2; ! 456: { ! 457: return stat(s1, &sb)>=0 ! 458: && stat(s2, &sb2)>=0 ! 459: && sb.st_mtime>sb2.st_mtime; ! 460: } ! 461: ! 462: /* First file is older than the second. */ ! 463: int ! 464: x_ot(s1, s2) char *s1, *s2; ! 465: { ! 466: return stat(s1, &sb)>=0 ! 467: && stat(s2, &sb2)>=0 ! 468: && sb.st_mtime<sb2.st_mtime; ! 469: } ! 470: ! 471: /* File exists and is a named pipe. */ ! 472: int ! 473: x_p(s) char *s; ! 474: { ! 475: return stat(s, &sb)>=0 && (sb.st_mode&S_IFMT)==S_IFPIP; ! 476: } ! 477: ! 478: /* File exists and is readable. */ ! 479: int ! 480: x_r(s) char *s; ! 481: { ! 482: return access(s, AREAD) >= 0; ! 483: } ! 484: ! 485: /* File exists and has a non-zero size. */ ! 486: int ! 487: x_s(s) char *s; ! 488: { ! 489: return stat(s, &sb)>=0 && sb.st_size>0; ! 490: } ! 491: ! 492: /* Two strings are lexicographically equal. */ ! 493: int ! 494: x_str_eq(s1, s2) char *s1, *s2; ! 495: { ! 496: return equal(s1, s2); ! 497: } ! 498: ! 499: /* First string is lexicographically less than second. */ ! 500: int ! 501: x_str_lt(s1, s2) char *s1, *s2; ! 502: { ! 503: return strcmp(s1, s2) < 0; ! 504: } ! 505: ! 506: /* First string is lexicographically greater than second. */ ! 507: int ! 508: x_str_gt(s1, s2) char *s1, *s2; ! 509: { ! 510: return strcmp(s1, s2) > 0; ! 511: } ! 512: ! 513: /* Two strings are lexicographically unequal. */ ! 514: int ! 515: x_str_ne(s1, s2) char *s1, *s2; ! 516: { ! 517: return !equal(s1, s2); ! 518: } ! 519: ! 520: /* File descriptor is associated with a terminal. */ ! 521: /* Posix says the fd in "-t fd" arg is required, not optional. */ ! 522: int ! 523: x_t(s) char *s; ! 524: { ! 525: return isatty(atoi(s)); ! 526: } ! 527: ! 528: /* File exists and is setuid. */ ! 529: int ! 530: x_u(s) char *s; ! 531: { ! 532: return stat(s, &sb)>=0 && (sb.st_mode&S_ISUID)!=0; ! 533: } ! 534: ! 535: /* File exists and is writeable. */ ! 536: int ! 537: x_w(s) char *s; ! 538: { ! 539: return access(s, AWRITE) >= 0; ! 540: } ! 541: ! 542: /* File exists and is executable. */ ! 543: int ! 544: x_x(s) char *s; ! 545: { ! 546: return access(s, AEXEC) >= 0; ! 547: } ! 548: ! 549: /* String length is zero. */ ! 550: int ! 551: x_z(s) char *s; ! 552: { ! 553: return s[0] == '\0'; ! 554: } ! 555: ! 556: /* end of cmd/test.c */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.