test.c revision 4170
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: test.c,v 1.7 1994/11/05 17:31:23 ache Exp $ 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: 339exist: 340 if (fs == NULL || fs->rcode == -1) 341 goto false; 342 else 343 goto true; 344 case ISREAD: 345 if (geteuid() == 0) 346 goto exist; 347 i = S_IROTH; 348 goto permission; 349 case ISWRITE: 350 if (geteuid() != 0) 351 i = S_IWOTH; 352 else { 353 i = S_IWOTH|S_IWGRP|S_IWUSR; 354 goto filebit; 355 } 356 goto permission; 357 case ISEXEC: 358 if (geteuid() != 0) { 359 i = S_IXOTH; 360permission: if (fs->stat.st_uid == geteuid()) 361 i <<= 6; 362 else if (fs->stat.st_gid == getegid()) 363 i <<= 3; 364 } else 365 i = S_IXOTH|S_IXGRP|S_IXUSR; 366 goto filebit; /* true if (stat.st_mode & i) != 0 */ 367 case ISFILE: 368 i = S_IFREG; 369 goto filetype; 370 case ISDIR: 371 i = S_IFDIR; 372 goto filetype; 373 case ISCHAR: 374 i = S_IFCHR; 375 goto filetype; 376 case ISBLOCK: 377 i = S_IFBLK; 378 goto filetype; 379 case ISSYMLINK: 380 i = S_IFLNK; 381 (void)lstat(sp->u.string, &fs->stat); 382 goto filetype; 383 case ISFIFO: 384 i = S_IFIFO; 385 goto filetype; 386filetype: if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0) 387true: sp->u.num = 1; 388 else 389false: sp->u.num = 0; 390 sp->type = BOOLEAN; 391 break; 392 case ISSETUID: 393 i = S_ISUID; 394 goto filebit; 395 case ISSETGID: 396 i = S_ISGID; 397 goto filebit; 398 case ISSTICKY: 399 i = S_ISVTX; 400filebit: if (fs->stat.st_mode & i && fs->rcode >= 0) 401 goto true; 402 goto false; 403 case ISSIZE: 404 sp->u.num = fs->rcode >= 0 ? fs->stat.st_size : 0L; 405 sp->type = INTEGER; 406 break; 407 case ISTTY: 408 sp->u.num = isatty(sp->u.num); 409 sp->type = BOOLEAN; 410 break; 411 case NULSTR: 412 if (sp->u.string[0] == '\0') 413 goto true; 414 goto false; 415 case STRLEN: 416 sp->u.num = strlen(sp->u.string); 417 sp->type = INTEGER; 418 break; 419 case OR1: 420 case AND1: 421 /* 422 * These operators are mostly handled by the parser. If we 423 * get here it means that both operands were evaluated, so 424 * the value is the value of the second operand. 425 */ 426 *sp = *(sp + 1); 427 break; 428 case STREQ: 429 case STRNE: 430 i = 0; 431 if (!strcmp(sp->u.string, (sp + 1)->u.string)) 432 i++; 433 if (op == STRNE) 434 i = 1 - i; 435 sp->u.num = i; 436 sp->type = BOOLEAN; 437 break; 438 case EQ: 439 if (sp->u.num == (sp + 1)->u.num) 440 goto true; 441 goto false; 442 case NE: 443 if (sp->u.num != (sp + 1)->u.num) 444 goto true; 445 goto false; 446 case GT: 447 if (sp->u.num > (sp + 1)->u.num) 448 goto true; 449 goto false; 450 case LT: 451 if (sp->u.num < (sp + 1)->u.num) 452 goto true; 453 goto false; 454 case LE: 455 if (sp->u.num <= (sp + 1)->u.num) 456 goto true; 457 goto false; 458 case GE: 459 if (sp->u.num >= (sp + 1)->u.num) 460 goto true; 461 goto false; 462 463 } 464} 465 466static int 467lookup_op(name, table) 468 char *name; 469 const char *const * table; 470{ 471 const char *const * tp; 472 const char *p; 473 char c; 474 475 c = name[1]; 476 for (tp = table; (p = *tp) != NULL; tp++) 477 if (p[1] == c && !strcmp(p, name)) 478 return (tp - table); 479 return (-1); 480} 481 482static int 483posix_unary_op(argv) 484 char **argv; 485{ 486 struct filestat fs; 487 struct value valp; 488 int op, c; 489 char *opname; 490 491 opname = *argv; 492 if ((op = lookup_op(opname, unary_op)) < 0) 493 return (-1); 494 c = op_argflag[op]; 495 opname = argv[1]; 496 valp.u.string = opname; 497 if (c == OP_FILE) { 498 fs.name = opname; 499 fs.rcode = stat(opname, &fs.stat); 500 } else if (c != OP_STRING) 501 return (-1); 502 503 expr_operator(op, &valp, &fs); 504 return (valp.u.num == 0); 505} 506 507static int 508posix_binary_op(argv) 509 char **argv; 510{ 511 struct value v[2]; 512 int op, c; 513 char *opname; 514 515 opname = argv[1]; 516 if ((op = lookup_op(opname, binary_op)) < 0) 517 return (-1); 518 op += FIRST_BINARY_OP; 519 c = op_argflag[op]; 520 521 if (c == OP_INT) { 522 get_int(argv[0], &v[0].u.num); 523 get_int(argv[2], &v[1].u.num); 524 } else { 525 v[0].u.string = argv[0]; 526 v[1].u.string = argv[2]; 527 } 528 expr_operator(op, v, NULL); 529 return (v[0].u.num == 0); 530} 531 532/* 533 * Integer type checking. 534 */ 535static void 536get_int(v, lp) 537 char *v; 538 long *lp; 539{ 540 long val; 541 char *ep; 542 543 for (; *v && isspace(*v); ++v); 544 545 if(!*v) { 546 *lp = 0; 547 return; 548 } 549 550 if (isdigit(*v) || ((*v == '-' || *v == '+') && isdigit(*(v+1)))) { 551 errno = 0; 552 val = strtol(v, &ep, 10); 553 if (*ep != '\0') 554 errx(2, "%s: trailing non-numeric characters", v); 555 if (errno == ERANGE) { 556 if (val == LONG_MIN) 557 errx(2, "%s: underflow", v); 558 if (val == LONG_MAX) 559 errx(2, "%s: overflow", v); 560 } 561 *lp = val; 562 return; 563 } 564 errx(2, "%s: expected integer", v); 565} 566 567static void 568syntax() 569{ 570 571 err(2, "syntax error"); 572} 573 574static void 575overflow() 576{ 577 578 err(2, "expression is too complex"); 579} 580