|
|
1.1 ! root 1: /* ! 2: * New, improved version of the "test" utility, hopefully P1003.2 compliant. ! 3: * ! 4: * "test" has represented a problem to implementors because of the possibility ! 5: * of aliasing between operators and operands; while it is possible to resolve ! 6: * this ambiguity with conventional tools, the resulting program is rather ! 7: * subtle and easily broken. ! 8: * ! 9: * The observation the underpins this implementation is that the "test" input ! 10: * is more accurately parsed /from the right/ than from the left. Because the ! 11: * last element of a form is always an operand rather than an operator, and ! 12: * because unary and binary primaries are lexically distinct, it is possible ! 13: * to create an unambiguous parse with a single right->left scan of the input. ! 14: * ! 15: * Parentheses are a unique problem, however. The easiest way to support them ! 16: * is to say that parentheses are matched only if they are not consumed by ! 17: * any operators, i.e. \( "a" \) would fail to match any operators and so the ! 18: * parenthesis would match. ! 19: */ ! 20: ! 21: #include <sys/compat.h> ! 22: #include <sys/stat.h> ! 23: #include <access.h> ! 24: #include <string.h> ! 25: #include <stdlib.h> ! 26: #include <unistd.h> ! 27: ! 28: enum { ! 29: SYNTAX_ERROR = 2 ! 30: }; ! 31: ! 32: ! 33: typedef int (* compare_p) PROTO ((long diff)); ! 34: typedef int (* file_binop_p) PROTO ((struct stat * left, ! 35: struct stat * right)); ! 36: typedef int (* log_binop_p) PROTO ((int left, int right)); ! 37: typedef int (* string_unop_p) PROTO ((char * arg)); ! 38: typedef int (* file_unop_p) PROTO ((struct stat * stat)); ! 39: ! 40: ! 41: #ifdef USE_PROTO ! 42: # define SELECT(ansi,knr) ansi ! 43: #else ! 44: # define SELECT(ansi,knr) knr ! 45: #endif ! 46: ! 47: /* ! 48: * Miscellaneous comparison operators. ! 49: */ ! 50: ! 51: #define CMP_OP(name) static int name SELECT ((long diff), (diff) long diff;) ! 52: ! 53: CMP_OP (cmp_eq) { ! 54: return diff != 0; ! 55: } ! 56: CMP_OP (cmp_neq) { ! 57: return diff == 0; ! 58: } ! 59: CMP_OP (cmp_lt) { ! 60: return diff >= 0; ! 61: } ! 62: CMP_OP (cmp_gt) { ! 63: return diff <= 0; ! 64: } ! 65: CMP_OP (cmp_le) { ! 66: return diff > 0; ! 67: } ! 68: CMP_OP (cmp_ge) { ! 69: return diff < 0; ! 70: } ! 71: ! 72: ! 73: /* ! 74: * Miscellaneous file operators. ! 75: */ ! 76: ! 77: #define FILE_BINOP(name) \ ! 78: static int name SELECT((struct stat * left, struct stat * right), \ ! 79: (left, right) struct stat * left; struct stat * right;) ! 80: ! 81: FILE_BINOP (file_eq) { ! 82: return left->st_dev != right->st_dev || left->st_ino != right->st_ino; ! 83: } ! 84: FILE_BINOP (file_newer) { ! 85: return left->st_mtime <= right->st_mtime; ! 86: } ! 87: FILE_BINOP (file_older) { ! 88: return left->st_mtime >= right->st_mtime; ! 89: } ! 90: ! 91: #define FILE_UNOP(name) static int name SELECT ((struct stat * stat), \ ! 92: (stat) struct stat * stat;) ! 93: ! 94: FILE_UNOP (file_exists) { ! 95: return 0; ! 96: } ! 97: FILE_UNOP (file_blockspecial) { ! 98: return (stat->st_mode & S_IFMT) != S_IFBLK; ! 99: } ! 100: FILE_UNOP (file_charspecial) { ! 101: return (stat->st_mode & S_IFMT) != S_IFCHR; ! 102: } ! 103: FILE_UNOP (file_directory) { ! 104: return (stat->st_mode & S_IFMT) != S_IFDIR; ! 105: } ! 106: FILE_UNOP (file_pipe) { ! 107: return (stat->st_mode & S_IFMT) != S_IFPIP; ! 108: } ! 109: FILE_UNOP (file_regular) { ! 110: return (stat->st_mode & S_IFMT) != S_IFREG; ! 111: } ! 112: FILE_UNOP (file_setgid) { ! 113: return (stat->st_mode & S_ISGID) == 0; ! 114: } ! 115: FILE_UNOP (file_setuid) { ! 116: return (stat->st_mode & S_ISUID) == 0; ! 117: } ! 118: FILE_UNOP (file_sticky) { ! 119: return (stat->st_mode & S_ISVTX) == 0; ! 120: } ! 121: FILE_UNOP (file_linked) { ! 122: #if 1 ! 123: return 1; /* Coherent has no symbolic links */ ! 124: #else ! 125: return stat->st_nlink == 0; ! 126: #endif ! 127: } ! 128: FILE_UNOP (file_nonempty) { ! 129: return stat->st_size == 0; ! 130: } ! 131: ! 132: /* ! 133: * Miscellaneous string operators. ! 134: */ ! 135: ! 136: #define STRING_UNOP(name) static int name SELECT ((char * str),\ ! 137: (str) char * str;) ! 138: ! 139: STRING_UNOP (string_empty) { ! 140: return str [0] != 0; ! 141: } ! 142: STRING_UNOP (string_nonempty) { ! 143: return str [0] == 0; ! 144: } ! 145: STRING_UNOP (strfile_istty) { ! 146: return ! isatty (atoi (str)); ! 147: } ! 148: STRING_UNOP (strfile_readable) { ! 149: return access (str, AREAD); ! 150: } ! 151: STRING_UNOP (strfile_writeable) { ! 152: return access (str, AWRITE); ! 153: } ! 154: STRING_UNOP (strfile_executable) { ! 155: return access (str, AEXEC); ! 156: } ! 157: ! 158: ! 159: /* ! 160: * Declare and fill in some tables of function names and pointers to the ! 161: * implementations. ! 162: */ ! 163: ! 164: struct op { ! 165: char * op_name; ! 166: VOID * op_func; ! 167: }; ! 168: #define OP(name,func) { name, (VOID *) func } ! 169: ! 170: struct op string_binop [] = { ! 171: OP ("=", cmp_eq), OP ("!=", cmp_neq), OP ("<", cmp_lt), ! 172: OP (">", cmp_gt), OP ("<=", cmp_le), OP (">=", cmp_ge) ! 173: }; ! 174: struct op arith_binop [] = { ! 175: OP ("-eq", cmp_eq), OP ("-ne", cmp_neq), OP ("-lt", cmp_lt), ! 176: OP ("-gt", cmp_gt), OP ("-le", cmp_le), OP ("-ge", cmp_ge) ! 177: }; ! 178: struct op file_binop [] = { ! 179: OP ("-ef", file_eq), OP ("-nt", file_newer), OP ("-ot", file_older) ! 180: }; ! 181: struct op string_unop [] = { ! 182: OP ("-z", string_empty), OP ("-n", string_nonempty), ! 183: OP ("-t", strfile_istty), OP ("-r", strfile_readable), ! 184: OP ("-w", strfile_writeable), OP ("-x", strfile_executable) ! 185: /* OP ("!", string_empty) */ ! 186: }; ! 187: struct op file_unop [] = { ! 188: OP ("-b", file_blockspecial), OP ("-c", file_charspecial), ! 189: OP ("-d", file_directory), OP ("-p", file_pipe), ! 190: OP ("-f", file_regular), OP ("-g", file_setgid), ! 191: OP ("-u", file_setuid), OP ("-K", file_sticky), ! 192: OP ("-L", file_linked), OP ("-s", file_nonempty), ! 193: OP ("-e", file_exists) ! 194: }; ! 195: ! 196: #define FIND_OP(table,str) find_op (sizeof (table) / sizeof (* table),\ ! 197: table, str) ! 198: #define is_string_binop(str) ((compare_p) FIND_OP (string_binop, str)) ! 199: #define is_arith_binop(str) ((compare_p) FIND_OP (arith_binop, str)) ! 200: #define is_file_binop(str) ((file_binop_p) FIND_OP (file_binop, str)) ! 201: #define is_string_unop(str) ((string_unop_p) FIND_OP (string_unop, str)) ! 202: #define is_file_unop(str) ((file_unop_p) FIND_OP (file_unop, str)) ! 203: ! 204: /* ! 205: * Generic operator table search routine. ! 206: */ ! 207: ! 208: #ifdef USE_PROTO ! 209: static VOID * find_op (int size, struct op * table, char * str) ! 210: #else ! 211: static VOID * ! 212: find_op (size, table, str) ! 213: int size; ! 214: struct op * table; ! 215: char * str; ! 216: #endif ! 217: { ! 218: do ! 219: if (strcmp (table->op_name, str) == 0) ! 220: return table->op_func; ! 221: while (table ++, -- size > 0); ! 222: ! 223: return NULL; ! 224: } ! 225: ! 226: ! 227: /* ! 228: * Convert an operand of a numeric expression into a number. The rules for ! 229: * how strict the conversion to integer is are not clear. ! 230: * ! 231: * We return 0 on success, non-zero on failure. ! 232: */ ! 233: ! 234: #define convert_number(str,nump) (* (nump) = atol (str), 0) ! 235: ! 236: #ifdef USE_PROTO ! 237: int (convert_number) (char * string, long * numberp) ! 238: #else ! 239: int ! 240: convert_number ARGS ((string, numberp)) ! 241: char * string; ! 242: long * numberp; ! 243: #endif ! 244: { ! 245: return convert_number (string, numberp); ! 246: } ! 247: ! 248: enum { ! 249: NO_PAREN, ! 250: IN_PAREN ! 251: }; ! 252: ! 253: int test_boolor PROTO ((int argc, char * argv [], ! 254: int * matchedp, int paren)); ! 255: ! 256: ! 257: /* ! 258: * Process a non-empty element of a test-expression (see test () below). ! 259: * The fundamental premise of this code is that argv [argc - 1] is an ! 260: * operand, and thus that argv [argc - 2] is an operator of some form. ! 261: */ ! 262: ! 263: #ifdef USE_PROTO ! 264: int test_primop (int argc, char * argv [], int * matchedp, int paren) ! 265: #else ! 266: int ! 267: test_primop (argc, argv, matchedp, paren) ! 268: int argc; ! 269: char * argv []; ! 270: int * matchedp; ! 271: int paren; ! 272: #endif ! 273: { ! 274: int result; ! 275: int operator_flag = 1; ! 276: ! 277: if (argc < 1) ! 278: return SYNTAX_ERROR; /* syntax error in subexpr */ ! 279: ! 280: /* ! 281: * Begin by looking for binary operator at argv [argc - 2]. ! 282: * String operators only match if we have 3 arguments, and the ! 283: * same is true of arithmetic operators. ! 284: */ ! 285: ! 286: if (argc >= 3) { ! 287: compare_p cmp; ! 288: file_binop_p file; ! 289: ! 290: * matchedp = 3; ! 291: ! 292: if ((cmp = is_string_binop (argv [argc - 2])) != NULL) { ! 293: result = (* cmp) (strcmp (argv [argc - 3], ! 294: argv [argc - 1])); ! 295: goto negate; ! 296: } ! 297: if ((cmp = is_arith_binop (argv [argc - 2])) != NULL) { ! 298: long left; ! 299: long right; ! 300: ! 301: if (convert_number (argv [argc - 3], & left) == 0 && ! 302: convert_number (argv [argc - 1], & right) == 0) { ! 303: result = (* cmp) (left - right); ! 304: goto negate; ! 305: } ! 306: ! 307: return SYNTAX_ERROR; ! 308: } ! 309: ! 310: if ((file = is_file_binop (argv [argc - 2])) != NULL) { ! 311: struct stat left; ! 312: struct stat right; ! 313: ! 314: if (stat (argv [argc - 3], & left) < 0 || ! 315: stat (argv [argc - 1], & right) < 0) ! 316: result = 1; ! 317: else ! 318: result = (* file) (& left, & right); ! 319: goto negate; ! 320: } ! 321: } ! 322: ! 323: if (argc >= 2) { ! 324: string_unop_p string; ! 325: file_unop_p file; ! 326: ! 327: * matchedp = 2; ! 328: ! 329: if ((string = is_string_unop (argv [argc - 2])) != NULL) { ! 330: result = (* string) (argv [argc - 1]); ! 331: goto negate; ! 332: } ! 333: if ((file = is_file_unop (argv [argc - 2])) != NULL) { ! 334: struct stat statbuf; ! 335: ! 336: if (stat (argv [argc - 1], & statbuf) < 0) ! 337: result = 1; ! 338: else ! 339: result = (* file) (& statbuf); ! 340: goto negate; ! 341: } ! 342: } ! 343: ! 344: not_really_operator: ! 345: operator_flag = 0; ! 346: ! 347: if (argc > 2 && strcmp (argv [argc - 1], ")") == 0 && ! 348: (result = test_boolor (argc - 1, argv, matchedp, ! 349: IN_PAREN)) != SYNTAX_ERROR && ! 350: argc - 1 > * matchedp && ! 351: strcmp (argv [argc - 2 - * matchedp], "(") == 0) ! 352: (* matchedp) += 2; ! 353: else { ! 354: * matchedp = 1; ! 355: result = argv [argc - 1][0] == 0; ! 356: } ! 357: ! 358: negate: ! 359: /* ! 360: * Try and extend the subexpression on the right by looking ! 361: * for negations. ! 362: */ ! 363: ! 364: while (argc > * matchedp) { ! 365: char * next = argv [argc - 1 - * matchedp]; ! 366: ! 367: if (strcmp (next, "!") == 0) { ! 368: (* matchedp) ++; ! 369: result = ! result; ! 370: continue; ! 371: } ! 372: ! 373: /* ! 374: * We now have encountered something on the left... if it is ! 375: * a conjunction or disjunction operator, we are OK, otherwise ! 376: * we have hit a syntax error. If we got here by matching an ! 377: * operator, we can try matching a parenthesis and/or string ! 378: * as a fallback. ! 379: */ ! 380: ! 381: if (strcmp (next, "-a") != 0 && strcmp (next, "-o") != 0 && ! 382: (paren != IN_PAREN || strcmp (next, "(") != 0) && ! 383: operator_flag) ! 384: goto not_really_operator; ! 385: ! 386: break; ! 387: } ! 388: ! 389: return result; ! 390: } ! 391: ! 392: ! 393: /* ! 394: * Process an optional sequence of boolean conjunctions. This is separated ! 395: * from disjunction because conjunction has higher "precedence". ! 396: */ ! 397: ! 398: #ifdef USE_PROTO ! 399: int test_booland (int argc, char * argv [], int * matchedp, int paren) ! 400: #else ! 401: int ! 402: test_booland (argc, argv, matchedp, paren) ! 403: int argc; ! 404: char * argv []; ! 405: int * matchedp; ! 406: int paren; ! 407: #endif ! 408: { ! 409: int right = 0; /* "true" */ ! 410: ! 411: * matchedp = 0; ! 412: ! 413: /* ! 414: * Match a sequence of the form: ! 415: * and_expr = and_expr AND prim_expr ! 416: * | prim_expr ; ! 417: * Note that this grammar matches the definition of "and" as left- ! 418: * associative, but in fact since conjunction is commutative there is ! 419: * no great magic to this and we right-associate instead. ! 420: */ ! 421: ! 422: for (;;) { ! 423: int left; ! 424: int matched; ! 425: ! 426: if ((left = test_primop (argc, argv, & matched, ! 427: paren)) == SYNTAX_ERROR) ! 428: return SYNTAX_ERROR; ! 429: ! 430: right = right || left; /* "and" */ ! 431: * matchedp += matched; ! 432: argc -= matched; ! 433: ! 434: if (argc < 2 || strcmp (argv [argc - 1], "-a") != 0) ! 435: return right; ! 436: ! 437: (* matchedp) ++; ! 438: argc --; ! 439: } ! 440: } ! 441: ! 442: ! 443: /* ! 444: * Process an optional sequence of boolean disjunctions. ! 445: */ ! 446: ! 447: #ifdef USE_PROTO ! 448: int test_boolor (int argc, char * argv [], int * matchedp, int paren) ! 449: #else ! 450: int ! 451: test_boolor (argc, argv, matchedp, paren) ! 452: int argc; ! 453: char * argv []; ! 454: int * matchedp; ! 455: int paren; ! 456: #endif ! 457: { ! 458: int right = 1; /* "false" */ ! 459: ! 460: * matchedp = 0; ! 461: ! 462: /* ! 463: * Match an expression of the form: ! 464: * or_expr = or_expr OR and_expr ! 465: * | and_expr ; ! 466: * Note that this grammar matches the definition of "or" as being ! 467: * left-associative, but since disjunction is commutative this does ! 468: * not really matter and we actually associate to the right. ! 469: */ ! 470: ! 471: for (;;) { ! 472: int left; ! 473: int matched; ! 474: ! 475: if ((left = test_booland (argc, argv, & matched, ! 476: paren)) == SYNTAX_ERROR) ! 477: return SYNTAX_ERROR; ! 478: ! 479: (* matchedp) += matched; ! 480: argc -= matched; ! 481: right = right && left; /* "or" */ ! 482: ! 483: if (argc < 2 || strcmp (argv [argc - 1], "-o") != 0) ! 484: return right; ! 485: ! 486: (* matchedp) ++; ! 487: argc --; ! 488: } ! 489: } ! 490: ! 491: ! 492: /* ! 493: * Process a test-expression. The "argc" argument specifies the number of ! 494: * elements in the argument-vector "argv", where each element is a string. ! 495: * Note that unlike main (), the first element of argv [] is a real ! 496: * argument. ! 497: */ ! 498: ! 499: #ifdef USE_PROTO ! 500: int test (int argc, char * argv []) ! 501: #else ! 502: int ! 503: test ARGS ((argc, argv)) ! 504: int argc; ! 505: char * argv []; ! 506: #endif ! 507: { ! 508: int matched; ! 509: int value; ! 510: ! 511: /* ! 512: * Special cases to allow some simple tests. All these are available ! 513: * other ways, but they are historically significant. ! 514: */ ! 515: ! 516: if (argc == 0) ! 517: return 1; /* false */ ! 518: ! 519: #if 0 ! 520: if (argc == 1) ! 521: return argv [0][0] == 0; /* => -n <arg> */ ! 522: #endif ! 523: ! 524: if ((value = test_boolor (argc, argv, & matched, ! 525: NO_PAREN)) == SYNTAX_ERROR || ! 526: matched != argc) ! 527: return SYNTAX_ERROR; ! 528: ! 529: return value; ! 530: } ! 531: ! 532: ! 533: #ifdef TEST ! 534: ! 535: #define WRITESTR(s) write (2, s, strlen (s)) ! 536: #define WRITECONST(s) write (2, s, sizeof (s)) ! 537: ! 538: #ifdef USE_PROTO ! 539: void error (char * prog, char * str) ! 540: #else ! 541: void ! 542: error (prog, str) ! 543: char * prog; ! 544: char * str; ! 545: #endif ! 546: { ! 547: WRITESTR (prog); ! 548: WRITECONST (":"); ! 549: WRITESTR (str); ! 550: WRITECONST ("\n"); ! 551: ! 552: WRITECONST ("Unary primaries:\n" ! 553: "\t-b file\t\tfile exists and is a block special file\n" ! 554: "\t-c file\t\tfile exists and is a character special file\n" ! 555: "\t-d file\t\tfile exists and is a directory\n" ! 556: "\t-e file\t\tfile exists\n" ! 557: "\t-f file\t\tfile exists and is a regular file\n" ! 558: "\t-g file\t\tfile exists and is setgid\n" ! 559: "\t-k file\t\tfile exists and has sticky bit set\t(not Posix)\n" ! 560: "\t-L file\t\tfile is a link\t\t\t\t(not Posix)\n" ! 561: "\t-n string\tstring length is nonzero\n" ! 562: "\t-p file\t\tfile exists and is a named pipe (FIFO)\n" ! 563: "\t-r file\t\tfile exists and is readable\n" ! 564: "\t-s file\t\tfile exists and has nonzero size\n" ! 565: "\t-t fd\t\tfd is the file descriptor of a terminal\n" ! 566: "\t-u file\t\tfile exists and is setuid\n" ! 567: "\t-w file\t\tfile exists and is writable\n" ! 568: "\t-x file\t\tfile exists and is executable\n" ! 569: "\t-z string\tstring length is zero\n" ! 570: "\tstring\t\tstring is not the empty string\n" ! 571: ); ! 572: WRITECONST ("Binary primaries:\n" ! 573: "\ts1 = s2\t\tstrings s1 and s2 are identical\n" ! 574: "\ts1 != s2\tstrings s1 and s2 are not identical\n" ! 575: "\ts1 < s2\t\tstring s1 is less than s2\t\t(not Posix)\n" ! 576: "\ts1 > s2\t\tstring s1 is greater than s2\t\t(not Posix)\n" ! 577: "\tfile1 -ef file2\tfile1 and file2 are identical\t\t(not Posix)\n" ! 578: "\tn1 -eq n2\tnumbers n1 and n2 are equal\n" ! 579: "\tn1 -ge n2\tnumber n1 is greater than or equal to n2\n" ! 580: "\tn1 -gt n2\tnumber n1 is greater than n2\n" ! 581: "\tn1 -le n2\tnumber n1 is less than or equal to n2\n" ! 582: "\tn1 -lt n2\tnumber n1 is less than n2\n" ! 583: "\tn1 -ne n2\tnumbers n1 and n2 are not equal\n" ! 584: "\tfile1 -nt file2\tfile1 is newer than file2\t\t(not Posix)\n" ! 585: "\tfile1 -ot file2\tfile1 is older than file2\t\t(not Posix)\n" ! 586: ); ! 587: WRITECONST ( ! 588: "Expression grouping:\n" ! 589: "\t! exp\t\texp is false\n" ! 590: "\texp1 -a exp2\texp1 and exp2 are true\t\t\t(not Posix)\n" ! 591: "\texp1 -o exp2\texp1 or exp2 is true\t\t\t(not Posix)\n" ! 592: "\t( exp )\t\tparentheses for grouping\t\t(not Posix)\n" ! 593: ); ! 594: ! 595: exit (2); ! 596: } ! 597: ! 598: ! 599: #ifdef USE_PROTO ! 600: int main (int argc, char * argv []) ! 601: #else ! 602: int ! 603: main (argc, argv) ! 604: int argc; ! 605: char * argv []; ! 606: #endif ! 607: { ! 608: if (strcmp (argv [0], "[") == 0) { ! 609: if (strcmp (argv [argc - 1], "]") != 0) ! 610: error (argv [0], "Missing ']'"); ! 611: argc --; ! 612: } ! 613: ! 614: if ((argc = test (argc - 1, argv + 1)) == 2) ! 615: error (argv [0], "syntax error"); ! 616: ! 617: return argc; ! 618: } ! 619: ! 620: #endif
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.