1 | /*
|
---|
2 | * The expr and test commands.
|
---|
3 | *
|
---|
4 | * Copyright (C) 1989 by Kenneth Almquist. All rights reserved.
|
---|
5 | * This file is part of ash, which is distributed under the terms specified
|
---|
6 | * by the Ash General Public License. See the file named LICENSE.
|
---|
7 | */
|
---|
8 |
|
---|
9 |
|
---|
10 | #define main exprcmd
|
---|
11 |
|
---|
12 | #include "bltin.h"
|
---|
13 | #include "operators.h"
|
---|
14 | #include <sys/types.h>
|
---|
15 | #include <sys/stat.h>
|
---|
16 |
|
---|
17 | #ifndef S_ISLNK
|
---|
18 | #define lstat stat
|
---|
19 | #define S_ISLNK(mode) (0)
|
---|
20 | #endif
|
---|
21 |
|
---|
22 | #define STACKSIZE 12
|
---|
23 | #define NESTINCR 16
|
---|
24 |
|
---|
25 | /* data types */
|
---|
26 | #define STRING 0
|
---|
27 | #define INTEGER 1
|
---|
28 | #define BOOLEAN 2
|
---|
29 |
|
---|
30 |
|
---|
31 | /*
|
---|
32 | * This structure hold a value. The type keyword specifies the type of
|
---|
33 | * the value, and the union u holds the value. The value of a boolean
|
---|
34 | * is stored in u.num (1 = TRUE, 0 = FALSE).
|
---|
35 | */
|
---|
36 |
|
---|
37 | struct value {
|
---|
38 | int type;
|
---|
39 | union {
|
---|
40 | char *string;
|
---|
41 | long num;
|
---|
42 | } u;
|
---|
43 | };
|
---|
44 |
|
---|
45 |
|
---|
46 | struct operator {
|
---|
47 | short op; /* which operator */
|
---|
48 | short pri; /* priority of operator */
|
---|
49 | };
|
---|
50 |
|
---|
51 |
|
---|
52 | struct filestat {
|
---|
53 | int op; /* OP_FILE or OP_LFILE */
|
---|
54 | char *name; /* name of file */
|
---|
55 | int rcode; /* return code from stat */
|
---|
56 | struct stat stat; /* status info on file */
|
---|
57 | };
|
---|
58 |
|
---|
59 |
|
---|
60 | extern char *match_begin[10]; /* matched string */
|
---|
61 | extern short match_length[10]; /* defined in regexp.c */
|
---|
62 | extern short number_parens; /* number of \( \) pairs */
|
---|
63 |
|
---|
64 |
|
---|
65 | #ifdef __STDC__
|
---|
66 | int expr_is_false(struct value *);
|
---|
67 | void expr_operator(int, struct value *, struct filestat *);
|
---|
68 | int lookup_op(char *, char *const*);
|
---|
69 | char *re_compile(char *); /* defined in regexp.c */
|
---|
70 | int re_match(char *, char *); /* defined in regexp.c */
|
---|
71 | long atol(const char *);
|
---|
72 | #else
|
---|
73 | int expr_is_false();
|
---|
74 | void expr_operator();
|
---|
75 | int lookup_op();
|
---|
76 | char *re_compile(); /* defined in regexp.c */
|
---|
77 | int re_match(); /* defined in regexp.c */
|
---|
78 | long atol();
|
---|
79 | #endif
|
---|
80 |
|
---|
81 |
|
---|
82 |
|
---|
83 | main(argc, argv) char **argv; {
|
---|
84 | char **ap;
|
---|
85 | char *opname;
|
---|
86 | char c;
|
---|
87 | char *p;
|
---|
88 | int print;
|
---|
89 | int nest; /* parenthises nesting */
|
---|
90 | int op;
|
---|
91 | int pri;
|
---|
92 | int skipping;
|
---|
93 | int binary;
|
---|
94 | struct operator opstack[STACKSIZE];
|
---|
95 | struct operator *opsp;
|
---|
96 | struct value valstack[STACKSIZE + 1];
|
---|
97 | struct value *valsp;
|
---|
98 | struct filestat fs;
|
---|
99 |
|
---|
100 | INITARGS(argv);
|
---|
101 | c = **argv;
|
---|
102 | print = 1;
|
---|
103 | if (c == 't')
|
---|
104 | print = 0;
|
---|
105 | else if (c == '[') {
|
---|
106 | if (! equal(argv[argc - 1], "]"))
|
---|
107 | error("missing ]");
|
---|
108 | argv[argc - 1] = NULL;
|
---|
109 | print = 0;
|
---|
110 | }
|
---|
111 | ap = argv + 1;
|
---|
112 | fs.name = NULL;
|
---|
113 |
|
---|
114 | /*
|
---|
115 | * We use operator precedence parsing, evaluating the expression
|
---|
116 | * as we parse it. Parentheses are handled by bumping up the
|
---|
117 | * priority of operators using the variable "nest." We use the
|
---|
118 | * variable "skipping" to turn off evaluation temporarily for the
|
---|
119 | * short circuit boolean operators. (It is important do the short
|
---|
120 | * circuit evaluation because under NFS a stat operation can take
|
---|
121 | * infinitely long.)
|
---|
122 | */
|
---|
123 |
|
---|
124 | nest = 0;
|
---|
125 | skipping = 0;
|
---|
126 | opsp = opstack + STACKSIZE;
|
---|
127 | valsp = valstack;
|
---|
128 | if (*ap == NULL) {
|
---|
129 | valstack[0].type = BOOLEAN;
|
---|
130 | valstack[0].u.num = 0;
|
---|
131 | goto done;
|
---|
132 | }
|
---|
133 | for (;;) {
|
---|
134 | opname = *ap++;
|
---|
135 | if (opname == NULL)
|
---|
136 | syntax: error("syntax error");
|
---|
137 | if (opname[0] == '(' && opname[1] == '\0') {
|
---|
138 | nest += NESTINCR;
|
---|
139 | continue;
|
---|
140 | } else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) {
|
---|
141 | if (opsp == &opstack[0])
|
---|
142 | overflow: error("Expression too complex");
|
---|
143 | --opsp;
|
---|
144 | opsp->op = op;
|
---|
145 | opsp->pri = op_priority[op] + nest;
|
---|
146 | continue;
|
---|
147 |
|
---|
148 | } else {
|
---|
149 | valsp->type = STRING;
|
---|
150 | valsp->u.string = opname;
|
---|
151 | valsp++;
|
---|
152 | }
|
---|
153 | for (;;) {
|
---|
154 | opname = *ap++;
|
---|
155 | if (opname == NULL) {
|
---|
156 | if (nest != 0)
|
---|
157 | goto syntax;
|
---|
158 | pri = 0;
|
---|
159 | break;
|
---|
160 | }
|
---|
161 | if (opname[0] != ')' || opname[1] != '\0') {
|
---|
162 | if ((op = lookup_op(opname, binary_op)) < 0)
|
---|
163 | goto syntax;
|
---|
164 | op += FIRST_BINARY_OP;
|
---|
165 | pri = op_priority[op] + nest;
|
---|
166 | break;
|
---|
167 | }
|
---|
168 | if ((nest -= NESTINCR) < 0)
|
---|
169 | goto syntax;
|
---|
170 | }
|
---|
171 | while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) {
|
---|
172 | binary = opsp->op;
|
---|
173 | for (;;) {
|
---|
174 | valsp--;
|
---|
175 | c = op_argflag[opsp->op];
|
---|
176 | if (c == OP_INT) {
|
---|
177 | if (valsp->type == STRING)
|
---|
178 | valsp->u.num = atol(valsp->u.string);
|
---|
179 | valsp->type = INTEGER;
|
---|
180 | } else if (c >= OP_STRING) { /* OP_STRING or OP_FILE */
|
---|
181 | if (valsp->type == INTEGER) {
|
---|
182 | p = stalloc(32);
|
---|
183 | #ifdef SHELL
|
---|
184 | fmtstr(p, 32, "%d", valsp->u.num);
|
---|
185 | #else
|
---|
186 | sprintf(p, "%d", valsp->u.num);
|
---|
187 | #endif
|
---|
188 | valsp->u.string = p;
|
---|
189 | } else if (valsp->type == BOOLEAN) {
|
---|
190 | if (valsp->u.num)
|
---|
191 | valsp->u.string = "true";
|
---|
192 | else
|
---|
193 | valsp->u.string = "";
|
---|
194 | }
|
---|
195 | valsp->type = STRING;
|
---|
196 | if (c >= OP_FILE
|
---|
197 | && (fs.op != c
|
---|
198 | || fs.name == NULL
|
---|
199 | || ! equal(fs.name, valsp->u.string))) {
|
---|
200 | fs.op = c;
|
---|
201 | fs.name = valsp->u.string;
|
---|
202 | if (c == OP_FILE) {
|
---|
203 | fs.rcode = stat(valsp->u.string,
|
---|
204 | &fs.stat);
|
---|
205 | } else {
|
---|
206 | fs.rcode = lstat(valsp->u.string,
|
---|
207 | &fs.stat);
|
---|
208 | }
|
---|
209 | }
|
---|
210 | }
|
---|
211 | if (binary < FIRST_BINARY_OP)
|
---|
212 | break;
|
---|
213 | binary = 0;
|
---|
214 | }
|
---|
215 | if (! skipping)
|
---|
216 | expr_operator(opsp->op, valsp, &fs);
|
---|
217 | else if (opsp->op == AND1 || opsp->op == OR1)
|
---|
218 | skipping--;
|
---|
219 | valsp++; /* push value */
|
---|
220 | opsp++; /* pop operator */
|
---|
221 | }
|
---|
222 | if (opname == NULL)
|
---|
223 | break;
|
---|
224 | if (opsp == &opstack[0])
|
---|
225 | goto overflow;
|
---|
226 | if (op == AND1 || op == AND2) {
|
---|
227 | op = AND1;
|
---|
228 | if (skipping || expr_is_false(valsp - 1))
|
---|
229 | skipping++;
|
---|
230 | }
|
---|
231 | if (op == OR1 || op == OR2) {
|
---|
232 | op = OR1;
|
---|
233 | if (skipping || ! expr_is_false(valsp - 1))
|
---|
234 | skipping++;
|
---|
235 | }
|
---|
236 | opsp--;
|
---|
237 | opsp->op = op;
|
---|
238 | opsp->pri = pri;
|
---|
239 | }
|
---|
240 | done:
|
---|
241 | if (print) {
|
---|
242 | if (valstack[0].type == STRING)
|
---|
243 | printf("%s\n", valstack[0].u.string);
|
---|
244 | else if (valstack[0].type == INTEGER)
|
---|
245 | printf("%ld\n", valstack[0].u.num);
|
---|
246 | else if (valstack[0].u.num != 0)
|
---|
247 | printf("true\n");
|
---|
248 | }
|
---|
249 | return expr_is_false(&valstack[0]);
|
---|
250 | }
|
---|
251 |
|
---|
252 |
|
---|
253 | int
|
---|
254 | expr_is_false(val)
|
---|
255 | struct value *val;
|
---|
256 | {
|
---|
257 | if (val->type == STRING) {
|
---|
258 | if (val->u.string[0] == '\0')
|
---|
259 | return 1;
|
---|
260 | } else { /* INTEGER or BOOLEAN */
|
---|
261 | if (val->u.num == 0)
|
---|
262 | return 1;
|
---|
263 | }
|
---|
264 | return 0;
|
---|
265 | }
|
---|
266 |
|
---|
267 |
|
---|
268 | /*
|
---|
269 | * Execute an operator. Op is the operator. Sp is the stack pointer;
|
---|
270 | * sp[0] refers to the first operand, sp[1] refers to the second operand
|
---|
271 | * (if any), and the result is placed in sp[0]. The operands are converted
|
---|
272 | * to the type expected by the operator before expr_operator is called.
|
---|
273 | * Fs is a pointer to a structure which holds the value of the last call
|
---|
274 | * to stat, to avoid repeated stat calls on the same file.
|
---|
275 | */
|
---|
276 |
|
---|
277 | void
|
---|
278 | expr_operator(op, sp, fs)
|
---|
279 | int op;
|
---|
280 | struct value *sp;
|
---|
281 | struct filestat *fs;
|
---|
282 | {
|
---|
283 | int i;
|
---|
284 | struct stat st1, st2;
|
---|
285 |
|
---|
286 | switch (op) {
|
---|
287 | case NOT:
|
---|
288 | sp->u.num = expr_is_false(sp);
|
---|
289 | sp->type = BOOLEAN;
|
---|
290 | break;
|
---|
291 | case ISREAD:
|
---|
292 | i = 04;
|
---|
293 | goto permission;
|
---|
294 | case ISWRITE:
|
---|
295 | i = 02;
|
---|
296 | goto permission;
|
---|
297 | case ISEXEC:
|
---|
298 | i = 01;
|
---|
299 | permission:
|
---|
300 | if (fs->stat.st_uid == geteuid())
|
---|
301 | i <<= 6;
|
---|
302 | else if (fs->stat.st_gid == getegid())
|
---|
303 | i <<= 3;
|
---|
304 | goto filebit; /* true if (stat.st_mode & i) != 0 */
|
---|
305 | case ISFILE:
|
---|
306 | i = S_IFREG;
|
---|
307 | goto filetype;
|
---|
308 | case ISDIR:
|
---|
309 | i = S_IFDIR;
|
---|
310 | goto filetype;
|
---|
311 | case ISCHAR:
|
---|
312 | i = S_IFCHR;
|
---|
313 | goto filetype;
|
---|
314 | case ISBLOCK:
|
---|
315 | i = S_IFBLK;
|
---|
316 | goto filetype;
|
---|
317 | case ISFIFO:
|
---|
318 | #ifdef S_IFIFO
|
---|
319 | i = S_IFIFO;
|
---|
320 | goto filetype;
|
---|
321 | #else
|
---|
322 | goto false;
|
---|
323 | #endif
|
---|
324 | filetype:
|
---|
325 | if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0) {
|
---|
326 | true:
|
---|
327 | sp->u.num = 1;
|
---|
328 | } else {
|
---|
329 | false:
|
---|
330 | sp->u.num = 0;
|
---|
331 | }
|
---|
332 | sp->type = BOOLEAN;
|
---|
333 | break;
|
---|
334 | case ISSETUID:
|
---|
335 | i = S_ISUID;
|
---|
336 | goto filebit;
|
---|
337 | case ISSETGID:
|
---|
338 | i = S_ISGID;
|
---|
339 | goto filebit;
|
---|
340 | case ISSTICKY:
|
---|
341 | i = S_ISVTX;
|
---|
342 | filebit:
|
---|
343 | if (fs->stat.st_mode & i && fs->rcode >= 0)
|
---|
344 | goto true;
|
---|
345 | goto false;
|
---|
346 | case ISSIZE:
|
---|
347 | sp->u.num = fs->rcode >= 0? fs->stat.st_size : 0L;
|
---|
348 | sp->type = INTEGER;
|
---|
349 | break;
|
---|
350 | case ISLINK1:
|
---|
351 | case ISLINK2:
|
---|
352 | if (S_ISLNK(fs->stat.st_mode) && fs->rcode >= 0)
|
---|
353 | goto true;
|
---|
354 | fs->op = OP_FILE; /* not a symlink, so expect a -d or so next */
|
---|
355 | goto false;
|
---|
356 | case NEWER:
|
---|
357 | if (stat(sp->u.string, &st1) != 0) {
|
---|
358 | sp->u.num = 0;
|
---|
359 | } else if (stat((sp + 1)->u.string, &st2) != 0) {
|
---|
360 | sp->u.num = 1;
|
---|
361 | } else {
|
---|
362 | sp->u.num = st1.st_mtime >= st2.st_mtime;
|
---|
363 | }
|
---|
364 | sp->type = INTEGER;
|
---|
365 | break;
|
---|
366 | case ISTTY:
|
---|
367 | sp->u.num = isatty(sp->u.num);
|
---|
368 | sp->type = BOOLEAN;
|
---|
369 | break;
|
---|
370 | case NULSTR:
|
---|
371 | if (sp->u.string[0] == '\0')
|
---|
372 | goto true;
|
---|
373 | goto false;
|
---|
374 | case STRLEN:
|
---|
375 | sp->u.num = strlen(sp->u.string);
|
---|
376 | sp->type = INTEGER;
|
---|
377 | break;
|
---|
378 | case OR1:
|
---|
379 | case AND1:
|
---|
380 | /*
|
---|
381 | * These operators are mostly handled by the parser. If we
|
---|
382 | * get here it means that both operands were evaluated, so
|
---|
383 | * the value is the value of the second operand.
|
---|
384 | */
|
---|
385 | *sp = *(sp + 1);
|
---|
386 | break;
|
---|
387 | case STREQ:
|
---|
388 | case STRNE:
|
---|
389 | i = 0;
|
---|
390 | if (equal(sp->u.string, (sp + 1)->u.string))
|
---|
391 | i++;
|
---|
392 | if (op == STRNE)
|
---|
393 | i = 1 - i;
|
---|
394 | sp->u.num = i;
|
---|
395 | sp->type = BOOLEAN;
|
---|
396 | break;
|
---|
397 | case EQ:
|
---|
398 | if (sp->u.num == (sp + 1)->u.num)
|
---|
399 | goto true;
|
---|
400 | goto false;
|
---|
401 | case NE:
|
---|
402 | if (sp->u.num != (sp + 1)->u.num)
|
---|
403 | goto true;
|
---|
404 | goto false;
|
---|
405 | case GT:
|
---|
406 | if (sp->u.num > (sp + 1)->u.num)
|
---|
407 | goto true;
|
---|
408 | goto false;
|
---|
409 | case LT:
|
---|
410 | if (sp->u.num < (sp + 1)->u.num)
|
---|
411 | goto true;
|
---|
412 | goto false;
|
---|
413 | case LE:
|
---|
414 | if (sp->u.num <= (sp + 1)->u.num)
|
---|
415 | goto true;
|
---|
416 | goto false;
|
---|
417 | case GE:
|
---|
418 | if (sp->u.num >= (sp + 1)->u.num)
|
---|
419 | goto true;
|
---|
420 | goto false;
|
---|
421 | case PLUS:
|
---|
422 | sp->u.num += (sp + 1)->u.num;
|
---|
423 | break;
|
---|
424 | case MINUS:
|
---|
425 | sp->u.num -= (sp + 1)->u.num;
|
---|
426 | break;
|
---|
427 | case TIMES:
|
---|
428 | sp->u.num *= (sp + 1)->u.num;
|
---|
429 | break;
|
---|
430 | case DIVIDE:
|
---|
431 | if ((sp + 1)->u.num == 0)
|
---|
432 | error("Division by zero");
|
---|
433 | sp->u.num /= (sp + 1)->u.num;
|
---|
434 | break;
|
---|
435 | case REM:
|
---|
436 | if ((sp + 1)->u.num == 0)
|
---|
437 | error("Division by zero");
|
---|
438 | sp->u.num %= (sp + 1)->u.num;
|
---|
439 | break;
|
---|
440 | case MATCHPAT:
|
---|
441 | {
|
---|
442 | char *pat;
|
---|
443 |
|
---|
444 | pat = re_compile((sp + 1)->u.string);
|
---|
445 | if (re_match(pat, sp->u.string)) {
|
---|
446 | if (number_parens > 0) {
|
---|
447 | sp->u.string = match_begin[1];
|
---|
448 | sp->u.string[match_length[1]] = '\0';
|
---|
449 | } else {
|
---|
450 | sp->u.num = match_length[0];
|
---|
451 | sp->type = INTEGER;
|
---|
452 | }
|
---|
453 | } else {
|
---|
454 | if (number_parens > 0) {
|
---|
455 | sp->u.string[0] = '\0';
|
---|
456 | } else {
|
---|
457 | sp->u.num = 0;
|
---|
458 | sp->type = INTEGER;
|
---|
459 | }
|
---|
460 | }
|
---|
461 | }
|
---|
462 | break;
|
---|
463 | }
|
---|
464 | }
|
---|
465 |
|
---|
466 |
|
---|
467 | int
|
---|
468 | lookup_op(name, table)
|
---|
469 | char *name;
|
---|
470 | char *const*table;
|
---|
471 | {
|
---|
472 | register char *const*tp;
|
---|
473 | register char const *p;
|
---|
474 | char c = name[1];
|
---|
475 |
|
---|
476 | for (tp = table ; (p = *tp) != NULL ; tp++) {
|
---|
477 | if (p[1] == c && equal(p, name))
|
---|
478 | return tp - table;
|
---|
479 | }
|
---|
480 | return -1;
|
---|
481 | }
|
---|