test.c revision 99110
1/* $NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $ */ 2 3/* 4 * test(1); version 7-like -- author Erik Baalbergen 5 * modified by Eric Gisin to be used as built-in. 6 * modified by Arnold Robbins to add SVR3 compatibility 7 * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). 8 * modified by J.T. Conklin for NetBSD. 9 * 10 * This program is in the Public Domain. 11 */ 12 13#include <sys/cdefs.h> 14__FBSDID("$FreeBSD: head/bin/test/test.c 99110 2002-06-30 05:15:05Z obrien $"); 15 16#include <sys/types.h> 17#include <sys/stat.h> 18 19#include <ctype.h> 20#include <err.h> 21#include <errno.h> 22#include <inttypes.h> 23#include <limits.h> 24#include <stdarg.h> 25#include <stdio.h> 26#include <stdlib.h> 27#include <string.h> 28#include <unistd.h> 29 30#ifdef SHELL 31#define main testcmd 32#include "bltin/bltin.h" 33#else 34#include <locale.h> 35 36static void error(const char *, ...) __dead2 __printf0like(1, 2); 37 38static void 39error(const char *msg, ...) 40{ 41 va_list ap; 42 va_start(ap, msg); 43 verrx(2, msg, ap); 44 /*NOTREACHED*/ 45 va_end(ap); 46} 47#endif 48 49/* test(1) accepts the following grammar: 50 oexpr ::= aexpr | aexpr "-o" oexpr ; 51 aexpr ::= nexpr | nexpr "-a" aexpr ; 52 nexpr ::= primary | "!" primary 53 primary ::= unary-operator operand 54 | operand binary-operator operand 55 | operand 56 | "(" oexpr ")" 57 ; 58 unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| 59 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; 60 61 binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| 62 "-nt"|"-ot"|"-ef"; 63 operand ::= <any legal UNIX file name> 64*/ 65 66enum token { 67 EOI, 68 FILRD, 69 FILWR, 70 FILEX, 71 FILEXIST, 72 FILREG, 73 FILDIR, 74 FILCDEV, 75 FILBDEV, 76 FILFIFO, 77 FILSOCK, 78 FILSYM, 79 FILGZ, 80 FILTT, 81 FILSUID, 82 FILSGID, 83 FILSTCK, 84 FILNT, 85 FILOT, 86 FILEQ, 87 FILUID, 88 FILGID, 89 STREZ, 90 STRNZ, 91 STREQ, 92 STRNE, 93 STRLT, 94 STRGT, 95 INTEQ, 96 INTNE, 97 INTGE, 98 INTGT, 99 INTLE, 100 INTLT, 101 UNOT, 102 BAND, 103 BOR, 104 LPAREN, 105 RPAREN, 106 OPERAND 107}; 108 109enum token_types { 110 UNOP, 111 BINOP, 112 BUNOP, 113 BBINOP, 114 PAREN 115}; 116 117struct t_op { 118 const char *op_text; 119 short op_num, op_type; 120} const ops [] = { 121 {"-r", FILRD, UNOP}, 122 {"-w", FILWR, UNOP}, 123 {"-x", FILEX, UNOP}, 124 {"-e", FILEXIST,UNOP}, 125 {"-f", FILREG, UNOP}, 126 {"-d", FILDIR, UNOP}, 127 {"-c", FILCDEV,UNOP}, 128 {"-b", FILBDEV,UNOP}, 129 {"-p", FILFIFO,UNOP}, 130 {"-u", FILSUID,UNOP}, 131 {"-g", FILSGID,UNOP}, 132 {"-k", FILSTCK,UNOP}, 133 {"-s", FILGZ, UNOP}, 134 {"-t", FILTT, UNOP}, 135 {"-z", STREZ, UNOP}, 136 {"-n", STRNZ, UNOP}, 137 {"-h", FILSYM, UNOP}, /* for backwards compat */ 138 {"-O", FILUID, UNOP}, 139 {"-G", FILGID, UNOP}, 140 {"-L", FILSYM, UNOP}, 141 {"-S", FILSOCK,UNOP}, 142 {"=", STREQ, BINOP}, 143 {"!=", STRNE, BINOP}, 144 {"<", STRLT, BINOP}, 145 {">", STRGT, BINOP}, 146 {"-eq", INTEQ, BINOP}, 147 {"-ne", INTNE, BINOP}, 148 {"-ge", INTGE, BINOP}, 149 {"-gt", INTGT, BINOP}, 150 {"-le", INTLE, BINOP}, 151 {"-lt", INTLT, BINOP}, 152 {"-nt", FILNT, BINOP}, 153 {"-ot", FILOT, BINOP}, 154 {"-ef", FILEQ, BINOP}, 155 {"!", UNOT, BUNOP}, 156 {"-a", BAND, BBINOP}, 157 {"-o", BOR, BBINOP}, 158 {"(", LPAREN, PAREN}, 159 {")", RPAREN, PAREN}, 160 {0, 0, 0} 161}; 162 163struct t_op const *t_wp_op; 164char **t_wp; 165 166static int aexpr(enum token); 167static int binop(void); 168static int equalf(const char *, const char *); 169static int filstat(char *, enum token); 170static int getn(const char *); 171static intmax_t getq(const char *); 172static int intcmp(const char *, const char *); 173static int isoperand(void); 174static int newerf(const char *, const char *); 175static int nexpr(enum token); 176static int oexpr(enum token); 177static int olderf(const char *, const char *); 178static int primary(enum token); 179static void syntax(const char *, const char *); 180static enum token t_lex(char *); 181 182int 183main(int argc, char **argv) 184{ 185 int i, res; 186 char *p; 187 char **nargv; 188 189 /* 190 * XXX copy the whole contents of argv to a newly allocated 191 * space with two extra cells filled with NULL's - this source 192 * code totally depends on their presence. 193 */ 194 if ((nargv = (char **)malloc((argc + 2) * sizeof(char *))) == NULL) 195 error("Out of space"); 196 197 for (i = 0; i < argc; i++) 198 nargv[i] = argv[i]; 199 200 nargv[i] = nargv[i + 1] = NULL; 201 argv = nargv; 202 203 if ((p = rindex(argv[0], '/')) == NULL) 204 p = argv[0]; 205 else 206 p++; 207 if (strcmp(p, "[") == 0) { 208 if (strcmp(argv[--argc], "]") != 0) 209 error("missing ]"); 210 argv[argc] = NULL; 211 } 212 213#ifndef SHELL 214 (void)setlocale(LC_CTYPE, ""); 215#endif 216 t_wp = &argv[1]; 217 res = !oexpr(t_lex(*t_wp)); 218 219 if (*t_wp != NULL && *++t_wp != NULL) 220 syntax(*t_wp, "unexpected operator"); 221 222 return res; 223} 224 225static void 226syntax(const char *op, const char *msg) 227{ 228 229 if (op && *op) 230 error("%s: %s", op, msg); 231 else 232 error("%s", msg); 233} 234 235static int 236oexpr(enum token n) 237{ 238 int res; 239 240 res = aexpr(n); 241 if (t_lex(*++t_wp) == BOR) 242 return oexpr(t_lex(*++t_wp)) || res; 243 t_wp--; 244 return res; 245} 246 247static int 248aexpr(enum token n) 249{ 250 int res; 251 252 res = nexpr(n); 253 if (t_lex(*++t_wp) == BAND) 254 return aexpr(t_lex(*++t_wp)) && res; 255 t_wp--; 256 return res; 257} 258 259static int 260nexpr(enum token n) 261{ 262 if (n == UNOT) 263 return !nexpr(t_lex(*++t_wp)); 264 return primary(n); 265} 266 267static int 268primary(enum token n) 269{ 270 enum token nn; 271 int res; 272 273 if (n == EOI) 274 return 0; /* missing expression */ 275 if (n == LPAREN) { 276 if ((nn = t_lex(*++t_wp)) == RPAREN) 277 return 0; /* missing expression */ 278 res = oexpr(nn); 279 if (t_lex(*++t_wp) != RPAREN) 280 syntax(NULL, "closing paren expected"); 281 return res; 282 } 283 if (t_wp_op && t_wp_op->op_type == UNOP) { 284 /* unary expression */ 285 if (*++t_wp == NULL) 286 syntax(t_wp_op->op_text, "argument expected"); 287 switch (n) { 288 case STREZ: 289 return strlen(*t_wp) == 0; 290 case STRNZ: 291 return strlen(*t_wp) != 0; 292 case FILTT: 293 return isatty(getn(*t_wp)); 294 default: 295 return filstat(*t_wp, n); 296 } 297 } 298 299 if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { 300 return binop(); 301 } 302 303 return strlen(*t_wp) > 0; 304} 305 306static int 307binop(void) 308{ 309 const char *opnd1, *opnd2; 310 struct t_op const *op; 311 312 opnd1 = *t_wp; 313 (void) t_lex(*++t_wp); 314 op = t_wp_op; 315 316 if ((opnd2 = *++t_wp) == NULL) 317 syntax(op->op_text, "argument expected"); 318 319 switch (op->op_num) { 320 case STREQ: 321 return strcmp(opnd1, opnd2) == 0; 322 case STRNE: 323 return strcmp(opnd1, opnd2) != 0; 324 case STRLT: 325 return strcmp(opnd1, opnd2) < 0; 326 case STRGT: 327 return strcmp(opnd1, opnd2) > 0; 328 case INTEQ: 329 return intcmp(opnd1, opnd2) == 0; 330 case INTNE: 331 return intcmp(opnd1, opnd2) != 0; 332 case INTGE: 333 return intcmp(opnd1, opnd2) >= 0; 334 case INTGT: 335 return intcmp(opnd1, opnd2) > 0; 336 case INTLE: 337 return intcmp(opnd1, opnd2) <= 0; 338 case INTLT: 339 return intcmp(opnd1, opnd2) < 0; 340 case FILNT: 341 return newerf (opnd1, opnd2); 342 case FILOT: 343 return olderf (opnd1, opnd2); 344 case FILEQ: 345 return equalf (opnd1, opnd2); 346 default: 347 abort(); 348 /* NOTREACHED */ 349 } 350} 351 352static int 353filstat(char *nm, enum token mode) 354{ 355 struct stat s; 356 357 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) 358 return 0; 359 360 switch (mode) { 361 case FILRD: 362 return (eaccess(nm, R_OK) == 0); 363 case FILWR: 364 return (eaccess(nm, W_OK) == 0); 365 case FILEX: 366 /* XXX work around eaccess(2) false positives for superuser */ 367 if (eaccess(nm, X_OK) != 0) 368 return 0; 369 if (S_ISDIR(s.st_mode) || geteuid() != 0) 370 return 1; 371 return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; 372 case FILEXIST: 373 return (eaccess(nm, F_OK) == 0); 374 case FILREG: 375 return S_ISREG(s.st_mode); 376 case FILDIR: 377 return S_ISDIR(s.st_mode); 378 case FILCDEV: 379 return S_ISCHR(s.st_mode); 380 case FILBDEV: 381 return S_ISBLK(s.st_mode); 382 case FILFIFO: 383 return S_ISFIFO(s.st_mode); 384 case FILSOCK: 385 return S_ISSOCK(s.st_mode); 386 case FILSYM: 387 return S_ISLNK(s.st_mode); 388 case FILSUID: 389 return (s.st_mode & S_ISUID) != 0; 390 case FILSGID: 391 return (s.st_mode & S_ISGID) != 0; 392 case FILSTCK: 393 return (s.st_mode & S_ISVTX) != 0; 394 case FILGZ: 395 return s.st_size > (off_t)0; 396 case FILUID: 397 return s.st_uid == geteuid(); 398 case FILGID: 399 return s.st_gid == getegid(); 400 default: 401 return 1; 402 } 403} 404 405static enum token 406t_lex(char *s) 407{ 408 struct t_op const *op = ops; 409 410 if (s == 0) { 411 t_wp_op = NULL; 412 return EOI; 413 } 414 while (op->op_text) { 415 if (strcmp(s, op->op_text) == 0) { 416 if ((op->op_type == UNOP && isoperand()) || 417 (op->op_num == LPAREN && *(t_wp+1) == 0)) 418 break; 419 t_wp_op = op; 420 return op->op_num; 421 } 422 op++; 423 } 424 t_wp_op = NULL; 425 return OPERAND; 426} 427 428static int 429isoperand(void) 430{ 431 struct t_op const *op = ops; 432 char *s; 433 char *t; 434 435 if ((s = *(t_wp+1)) == 0) 436 return 1; 437 if ((t = *(t_wp+2)) == 0) 438 return 0; 439 while (op->op_text) { 440 if (strcmp(s, op->op_text) == 0) 441 return op->op_type == BINOP && 442 (t[0] != ')' || t[1] != '\0'); 443 op++; 444 } 445 return 0; 446} 447 448/* atoi with error detection */ 449static int 450getn(const char *s) 451{ 452 char *p; 453 long r; 454 455 errno = 0; 456 r = strtol(s, &p, 10); 457 458 if (s == p) 459 error("%s: bad number", s); 460 461 if (errno != 0) 462 error((errno == EINVAL) ? "%s: bad number" : 463 "%s: out of range", s); 464 465 while (isspace((unsigned char)*p)) 466 p++; 467 468 if (*p) 469 error("%s: bad number", s); 470 471 return (int) r; 472} 473 474/* atoi with error detection and 64 bit range */ 475static intmax_t 476getq(const char *s) 477{ 478 char *p; 479 intmax_t r; 480 481 errno = 0; 482 r = strtoimax(s, &p, 10); 483 484 if (s == p) 485 error("%s: bad number", s); 486 487 if (errno != 0) 488 error((errno == EINVAL) ? "%s: bad number" : 489 "%s: out of range", s); 490 491 while (isspace((unsigned char)*p)) 492 p++; 493 494 if (*p) 495 error("%s: bad number", s); 496 497 return r; 498} 499 500static int 501intcmp (const char *s1, const char *s2) 502{ 503 intmax_t q1, q2; 504 505 506 q1 = getq(s1); 507 q2 = getq(s2); 508 509 if (q1 > q2) 510 return 1; 511 512 if (q1 < q2) 513 return -1; 514 515 return 0; 516} 517 518static int 519newerf (const char *f1, const char *f2) 520{ 521 struct stat b1, b2; 522 523 return (stat (f1, &b1) == 0 && 524 stat (f2, &b2) == 0 && 525 b1.st_mtime > b2.st_mtime); 526} 527 528static int 529olderf (const char *f1, const char *f2) 530{ 531 struct stat b1, b2; 532 533 return (stat (f1, &b1) == 0 && 534 stat (f2, &b2) == 0 && 535 b1.st_mtime < b2.st_mtime); 536} 537 538static int 539equalf (const char *f1, const char *f2) 540{ 541 struct stat b1, b2; 542 543 return (stat (f1, &b1) == 0 && 544 stat (f2, &b2) == 0 && 545 b1.st_dev == b2.st_dev && 546 b1.st_ino == b2.st_ino); 547} 548