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