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