test.c revision 3044
1/*- 2 * Copyright (c) 1992, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Kenneth Almquist. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Berkeley and its contributors. 20 * 4. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 * 36 * $Id$ 37 */ 38 39#ifndef lint 40static char copyright[] = 41"@(#) Copyright (c) 1992, 1993, 1994\n\ 42 The Regents of the University of California. All rights reserved.\n"; 43#endif /* not lint */ 44 45#ifndef lint 46static char sccsid[] = "@(#)test.c 8.3 (Berkeley) 4/2/94"; 47#endif /* not lint */ 48 49#include <sys/types.h> 50#include <sys/stat.h> 51 52#include <ctype.h> 53#include <err.h> 54#include <errno.h> 55#include <limits.h> 56#include <stdio.h> 57#include <stdlib.h> 58#include <string.h> 59#include <unistd.h> 60 61#include "operators.h" 62 63#define STACKSIZE 12 64#define NESTINCR 16 65 66/* data types */ 67#define STRING 0 68#define INTEGER 1 69#define BOOLEAN 2 70 71#define IS_BANG(s) (s[0] == '!' && s[1] == '\0') 72 73/* 74 * This structure hold a value. The type keyword specifies the type of 75 * the value, and the union u holds the value. The value of a boolean 76 * is stored in u.num (1 = TRUE, 0 = FALSE). 77 */ 78struct value { 79 int type; 80 union { 81 char *string; 82 long num; 83 } u; 84}; 85 86struct operator { 87 short op; /* Which operator. */ 88 short pri; /* Priority of operator. */ 89}; 90 91struct filestat { 92 char *name; /* Name of file. */ 93 int rcode; /* Return code from stat. */ 94 struct stat stat; /* Status info on file. */ 95}; 96 97static int expr_is_false __P((struct value *)); 98static void expr_operator __P((int, struct value *, struct filestat *)); 99static void get_int __P((char *, long *)); 100static int lookup_op __P((char *, const char *const *)); 101static void overflow __P((void)); 102static int posix_binary_op __P((char **)); 103static int posix_unary_op __P((char **)); 104static void syntax __P((void)); 105 106int 107main(argc, argv) 108 int argc; 109 char *argv[]; 110{ 111 struct operator opstack[STACKSIZE]; 112 struct operator *opsp; 113 struct value valstack[STACKSIZE + 1]; 114 struct value *valsp; 115 struct filestat fs; 116 char c, **ap, *opname, *p; 117 int binary, nest, op, pri, ret_val, skipping; 118 119 if ((p = argv[0]) == NULL) 120 errx(2, "test: argc is zero"); 121 122 if (*p != '\0' && p[strlen(p) - 1] == '[') { 123 if (strcmp(argv[--argc], "]")) 124 errx(2, "missing ]"); 125 argv[argc] = NULL; 126 } 127 ap = argv + 1; 128 fs.name = NULL; 129 130 /* 131 * Test(1) implements an inherently ambiguous grammer. In order to 132 * assure some degree of consistency, we special case the POSIX 1003.2 133 * requirements to assure correct evaluation for POSIX scripts. The 134 * following special cases comply with POSIX P1003.2/D11.2 Section 135 * 4.62.4. 136 */ 137 switch(argc - 1) { 138 case 0: /* % test */ 139 return (1); 140 break; 141 case 1: /* % test arg */ 142 return (argv[1] == NULL || *argv[1] == '\0') ? 1 : 0; 143 break; 144 case 2: /* % test op arg */ 145 opname = argv[1]; 146 if (IS_BANG(opname)) 147 return (*argv[2] == '\0') ? 0 : 1; 148 else { 149 ret_val = posix_unary_op(&argv[1]); 150 if (ret_val >= 0) 151 return (ret_val); 152 } 153 break; 154 case 3: /* % test arg1 op arg2 */ 155 if (IS_BANG(argv[1])) { 156 ret_val = posix_unary_op(&argv[1]); 157 if (ret_val >= 0) 158 return (!ret_val); 159 } else { 160 ret_val = posix_binary_op(&argv[1]); 161 if (ret_val >= 0) 162 return (ret_val); 163 } 164 break; 165 case 4: /* % test ! arg1 op arg2 */ 166 if (IS_BANG(argv[1]) && lookup_op(argv[3], andor_op) < 0 ) { 167 ret_val = posix_binary_op(&argv[2]); 168 if (ret_val >= 0) 169 return (!ret_val); 170 } 171 break; 172 default: 173 break; 174 } 175 176 /* 177 * We use operator precedence parsing, evaluating the expression as 178 * we parse it. Parentheses are handled by bumping up the priority 179 * of operators using the variable "nest." We use the variable 180 * "skipping" to turn off evaluation temporarily for the short 181 * circuit boolean operators. (It is important do the short circuit 182 * evaluation because under NFS a stat operation can take infinitely 183 * long.) 184 */ 185 opsp = opstack + STACKSIZE; 186 valsp = valstack; 187 nest = skipping = 0; 188 if (*ap == NULL) { 189 valstack[0].type = BOOLEAN; 190 valstack[0].u.num = 0; 191 goto done; 192 } 193 for (;;) { 194 opname = *ap++; 195 if (opname == NULL) 196 syntax(); 197 if (opname[0] == '(' && opname[1] == '\0') { 198 nest += NESTINCR; 199 continue; 200 } else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) { 201 if (opsp == &opstack[0]) 202 overflow(); 203 --opsp; 204 opsp->op = op; 205 opsp->pri = op_priority[op] + nest; 206 continue; 207 } else { 208 valsp->type = STRING; 209 valsp->u.string = opname; 210 valsp++; 211 } 212 for (;;) { 213 opname = *ap++; 214 if (opname == NULL) { 215 if (nest != 0) 216 syntax(); 217 pri = 0; 218 break; 219 } 220 if (opname[0] != ')' || opname[1] != '\0') { 221 if ((op = lookup_op(opname, binary_op)) < 0) 222 syntax(); 223 op += FIRST_BINARY_OP; 224 pri = op_priority[op] + nest; 225 break; 226 } 227 if ((nest -= NESTINCR) < 0) 228 syntax(); 229 } 230 while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) { 231 binary = opsp->op; 232 for (;;) { 233 valsp--; 234 c = op_argflag[opsp->op]; 235 if (c == OP_INT) { 236 if (valsp->type == STRING) 237 get_int(valsp->u.string, 238 &valsp->u.num); 239 valsp->type = INTEGER; 240 } else if (c >= OP_STRING) { 241 /* OP_STRING or OP_FILE */ 242 if (valsp->type == INTEGER) { 243 if ((p = malloc(32)) == NULL) 244 err(2, NULL); 245#ifdef SHELL 246 fmtstr(p, 32, "%d", 247 valsp->u.num); 248#else 249 (void)sprintf(p, 250 "%d", valsp->u.num); 251#endif 252 valsp->u.string = p; 253 } else if (valsp->type == BOOLEAN) { 254 if (valsp->u.num) 255 valsp->u.string = 256 "true"; 257 else 258 valsp->u.string = ""; 259 } 260 valsp->type = STRING; 261 if (c == OP_FILE && (fs.name == NULL || 262 strcmp(fs.name, valsp->u.string))) { 263 fs.name = valsp->u.string; 264 fs.rcode = 265 stat(valsp->u.string, 266 &fs.stat); 267 } 268 } 269 if (binary < FIRST_BINARY_OP) 270 break; 271 binary = 0; 272 } 273 if (!skipping) 274 expr_operator(opsp->op, valsp, &fs); 275 else if (opsp->op == AND1 || opsp->op == OR1) 276 skipping--; 277 valsp++; /* push value */ 278 opsp++; /* pop operator */ 279 } 280 if (opname == NULL) 281 break; 282 if (opsp == &opstack[0]) 283 overflow(); 284 if (op == AND1 || op == AND2) { 285 op = AND1; 286 if (skipping || expr_is_false(valsp - 1)) 287 skipping++; 288 } 289 if (op == OR1 || op == OR2) { 290 op = OR1; 291 if (skipping || !expr_is_false(valsp - 1)) 292 skipping++; 293 } 294 opsp--; 295 opsp->op = op; 296 opsp->pri = pri; 297 } 298done: return (expr_is_false(&valstack[0])); 299} 300 301static int 302expr_is_false(val) 303 struct value *val; 304{ 305 306 if (val->type == STRING) { 307 if (val->u.string[0] == '\0') 308 return (1); 309 } else { /* INTEGER or BOOLEAN */ 310 if (val->u.num == 0) 311 return (1); 312 } 313 return (0); 314} 315 316 317/* 318 * Execute an operator. Op is the operator. Sp is the stack pointer; 319 * sp[0] refers to the first operand, sp[1] refers to the second operand 320 * (if any), and the result is placed in sp[0]. The operands are converted 321 * to the type expected by the operator before expr_operator is called. 322 * Fs is a pointer to a structure which holds the value of the last call 323 * to stat, to avoid repeated stat calls on the same file. 324 */ 325static void 326expr_operator(op, sp, fs) 327 int op; 328 struct value *sp; 329 struct filestat *fs; 330{ 331 int i; 332 333 switch (op) { 334 case NOT: 335 sp->u.num = expr_is_false(sp); 336 sp->type = BOOLEAN; 337 break; 338 case ISEXIST: 339 if (fs == NULL || fs->rcode == -1) 340 goto false; 341 else 342 goto true; 343 case ISREAD: 344 i = S_IROTH; 345 goto permission; 346 case ISWRITE: 347 i = S_IWOTH; 348 goto permission; 349 case ISEXEC: 350 i = S_IXOTH; 351permission: if (fs->stat.st_uid == geteuid()) 352 i <<= 6; 353 else if (fs->stat.st_gid == getegid()) 354 i <<= 3; 355 goto filebit; /* true if (stat.st_mode & i) != 0 */ 356 case ISFILE: 357 i = S_IFREG; 358 goto filetype; 359 case ISDIR: 360 i = S_IFDIR; 361 goto filetype; 362 case ISCHAR: 363 i = S_IFCHR; 364 goto filetype; 365 case ISBLOCK: 366 i = S_IFBLK; 367 goto filetype; 368 case ISSYMLINK: 369 i = S_IFLNK; 370 (void)lstat(sp->u.string, &fs->stat); 371 goto filetype; 372 case ISFIFO: 373 i = S_IFIFO; 374 goto filetype; 375filetype: if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0) 376true: sp->u.num = 1; 377 else 378false: sp->u.num = 0; 379 sp->type = BOOLEAN; 380 break; 381 case ISSETUID: 382 i = S_ISUID; 383 goto filebit; 384 case ISSETGID: 385 i = S_ISGID; 386 goto filebit; 387 case ISSTICKY: 388 i = S_ISVTX; 389filebit: if (fs->stat.st_mode & i && fs->rcode >= 0) 390 goto true; 391 goto false; 392 case ISSIZE: 393 sp->u.num = fs->rcode >= 0 ? fs->stat.st_size : 0L; 394 sp->type = INTEGER; 395 break; 396 case ISTTY: 397 sp->u.num = isatty(sp->u.num); 398 sp->type = BOOLEAN; 399 break; 400 case NULSTR: 401 if (sp->u.string[0] == '\0') 402 goto true; 403 goto false; 404 case STRLEN: 405 sp->u.num = strlen(sp->u.string); 406 sp->type = INTEGER; 407 break; 408 case OR1: 409 case AND1: 410 /* 411 * These operators are mostly handled by the parser. If we 412 * get here it means that both operands were evaluated, so 413 * the value is the value of the second operand. 414 */ 415 *sp = *(sp + 1); 416 break; 417 case STREQ: 418 case STRNE: 419 i = 0; 420 if (!strcmp(sp->u.string, (sp + 1)->u.string)) 421 i++; 422 if (op == STRNE) 423 i = 1 - i; 424 sp->u.num = i; 425 sp->type = BOOLEAN; 426 break; 427 case EQ: 428 if (sp->u.num == (sp + 1)->u.num) 429 goto true; 430 goto false; 431 case NE: 432 if (sp->u.num != (sp + 1)->u.num) 433 goto true; 434 goto false; 435 case GT: 436 if (sp->u.num > (sp + 1)->u.num) 437 goto true; 438 goto false; 439 case LT: 440 if (sp->u.num < (sp + 1)->u.num) 441 goto true; 442 goto false; 443 case LE: 444 if (sp->u.num <= (sp + 1)->u.num) 445 goto true; 446 goto false; 447 case GE: 448 if (sp->u.num >= (sp + 1)->u.num) 449 goto true; 450 goto false; 451 452 } 453} 454 455static int 456lookup_op(name, table) 457 char *name; 458 const char *const * table; 459{ 460 const char *const * tp; 461 const char *p; 462 char c; 463 464 c = name[1]; 465 for (tp = table; (p = *tp) != NULL; tp++) 466 if (p[1] == c && !strcmp(p, name)) 467 return (tp - table); 468 return (-1); 469} 470 471static int 472posix_unary_op(argv) 473 char **argv; 474{ 475 struct filestat fs; 476 struct value valp; 477 int op, c; 478 char *opname; 479 480 opname = *argv; 481 if ((op = lookup_op(opname, unary_op)) < 0) 482 return (-1); 483 c = op_argflag[op]; 484 opname = argv[1]; 485 valp.u.string = opname; 486 if (c == OP_FILE) { 487 fs.name = opname; 488 fs.rcode = stat(opname, &fs.stat); 489 } else if (c != OP_STRING) 490 return (-1); 491 492 expr_operator(op, &valp, &fs); 493 return (valp.u.num == 0); 494} 495 496static int 497posix_binary_op(argv) 498 char **argv; 499{ 500 struct value v[2]; 501 int op, c; 502 char *opname; 503 504 opname = argv[1]; 505 if ((op = lookup_op(opname, binary_op)) < 0) 506 return (-1); 507 op += FIRST_BINARY_OP; 508 c = op_argflag[op]; 509 510 if (c == OP_INT) { 511 get_int(argv[0], &v[0].u.num); 512 get_int(argv[2], &v[1].u.num); 513 } else { 514 v[0].u.string = argv[0]; 515 v[1].u.string = argv[2]; 516 } 517 expr_operator(op, v, NULL); 518 return (v[0].u.num == 0); 519} 520 521/* 522 * Integer type checking. 523 */ 524static void 525get_int(v, lp) 526 char *v; 527 long *lp; 528{ 529 long val; 530 char *ep; 531 532 for (; *v && isspace(*v); ++v); 533 534 if(!*v) { 535 *lp = 0; 536 return; 537 } 538 539 if (isdigit(*v) || ((*v == '-' || *v == '+') && isdigit(*(v+1)))) { 540 errno = 0; 541 val = strtol(v, &ep, 10); 542 if (*ep != '\0') 543 errx(2, "%s: trailing non-numeric characters", v); 544 if (errno == ERANGE) { 545 if (val == LONG_MIN) 546 errx(2, "%s: underflow", v); 547 if (val == LONG_MAX) 548 errx(2, "%s: overflow", v); 549 } 550 *lp = val; 551 return; 552 } 553 errx(2, "%s: expected integer", v); 554} 555 556static void 557syntax() 558{ 559 560 err(2, "syntax error"); 561} 562 563static void 564overflow() 565{ 566 567 err(2, "expression is too complex"); 568} 569