test.c revision 101923
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 101923 2002-08-15 14:53:20Z maxim $"); 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; 164int nargc; 165char **t_wp; 166 167static int aexpr(enum token); 168static int binop(void); 169static int equalf(const char *, const char *); 170static int filstat(char *, enum token); 171static int getn(const char *); 172static intmax_t getq(const char *); 173static int intcmp(const char *, const char *); 174static int isoperand(void); 175static int newerf(const char *, const char *); 176static int nexpr(enum token); 177static int oexpr(enum token); 178static int olderf(const char *, const char *); 179static int primary(enum token); 180static void syntax(const char *, const char *); 181static enum token t_lex(char *); 182 183int 184main(int argc, char **argv) 185{ 186 int res; 187 char *p; 188 189 if ((p = rindex(argv[0], '/')) == NULL) 190 p = argv[0]; 191 else 192 p++; 193 if (strcmp(p, "[") == 0) { 194 if (strcmp(argv[--argc], "]") != 0) 195 error("missing ]"); 196 argv[argc] = NULL; 197 } 198 199 /* no expression => false */ 200 if (--argc <= 0) 201 return 1; 202 203#ifndef SHELL 204 (void)setlocale(LC_CTYPE, ""); 205#endif 206 nargc = argc; 207 t_wp = &argv[1]; 208 res = !oexpr(t_lex(*t_wp)); 209 210 if (--nargc > 0) 211 syntax(*t_wp, "unexpected operator"); 212 213 return res; 214} 215 216static void 217syntax(const char *op, const char *msg) 218{ 219 220 if (op && *op) 221 error("%s: %s", op, msg); 222 else 223 error("%s", msg); 224} 225 226static int 227oexpr(enum token n) 228{ 229 int res; 230 231 res = aexpr(n); 232 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR) 233 return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) || 234 res; 235 t_wp--; 236 nargc++; 237 return res; 238} 239 240static int 241aexpr(enum token n) 242{ 243 int res; 244 245 res = nexpr(n); 246 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND) 247 return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) && 248 res; 249 t_wp--; 250 nargc++; 251 return res; 252} 253 254static int 255nexpr(enum token n) 256{ 257 if (n == UNOT) 258 return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)); 259 return primary(n); 260} 261 262static int 263primary(enum token n) 264{ 265 enum token nn; 266 int res; 267 268 if (n == EOI) 269 return 0; /* missing expression */ 270 if (n == LPAREN) { 271 if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == 272 RPAREN) 273 return 0; /* missing expression */ 274 res = oexpr(nn); 275 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN) 276 syntax(NULL, "closing paren expected"); 277 return res; 278 } 279 if (t_wp_op && t_wp_op->op_type == UNOP) { 280 /* unary expression */ 281 if (--nargc == 0) 282 syntax(t_wp_op->op_text, "argument expected"); 283 switch (n) { 284 case STREZ: 285 return strlen(*++t_wp) == 0; 286 case STRNZ: 287 return strlen(*++t_wp) != 0; 288 case FILTT: 289 return isatty(getn(*++t_wp)); 290 default: 291 return filstat(*++t_wp, n); 292 } 293 } 294 295 if (t_lex(nargc > 0 ? t_wp[1] : NULL), t_wp_op && t_wp_op->op_type == 296 BINOP) { 297 return binop(); 298 } 299 300 return strlen(*t_wp) > 0; 301} 302 303static int 304binop(void) 305{ 306 const char *opnd1, *opnd2; 307 struct t_op const *op; 308 309 opnd1 = *t_wp; 310 (void) t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL); 311 op = t_wp_op; 312 313 if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL) 314 syntax(op->op_text, "argument expected"); 315 316 switch (op->op_num) { 317 case STREQ: 318 return strcmp(opnd1, opnd2) == 0; 319 case STRNE: 320 return strcmp(opnd1, opnd2) != 0; 321 case STRLT: 322 return strcmp(opnd1, opnd2) < 0; 323 case STRGT: 324 return strcmp(opnd1, opnd2) > 0; 325 case INTEQ: 326 return intcmp(opnd1, opnd2) == 0; 327 case INTNE: 328 return intcmp(opnd1, opnd2) != 0; 329 case INTGE: 330 return intcmp(opnd1, opnd2) >= 0; 331 case INTGT: 332 return intcmp(opnd1, opnd2) > 0; 333 case INTLE: 334 return intcmp(opnd1, opnd2) <= 0; 335 case INTLT: 336 return intcmp(opnd1, opnd2) < 0; 337 case FILNT: 338 return newerf (opnd1, opnd2); 339 case FILOT: 340 return olderf (opnd1, opnd2); 341 case FILEQ: 342 return equalf (opnd1, opnd2); 343 default: 344 abort(); 345 /* NOTREACHED */ 346 } 347} 348 349static int 350filstat(char *nm, enum token mode) 351{ 352 struct stat s; 353 354 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) 355 return 0; 356 357 switch (mode) { 358 case FILRD: 359 return (eaccess(nm, R_OK) == 0); 360 case FILWR: 361 return (eaccess(nm, W_OK) == 0); 362 case FILEX: 363 /* XXX work around eaccess(2) false positives for superuser */ 364 if (eaccess(nm, X_OK) != 0) 365 return 0; 366 if (S_ISDIR(s.st_mode) || geteuid() != 0) 367 return 1; 368 return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; 369 case FILEXIST: 370 return (eaccess(nm, F_OK) == 0); 371 case FILREG: 372 return S_ISREG(s.st_mode); 373 case FILDIR: 374 return S_ISDIR(s.st_mode); 375 case FILCDEV: 376 return S_ISCHR(s.st_mode); 377 case FILBDEV: 378 return S_ISBLK(s.st_mode); 379 case FILFIFO: 380 return S_ISFIFO(s.st_mode); 381 case FILSOCK: 382 return S_ISSOCK(s.st_mode); 383 case FILSYM: 384 return S_ISLNK(s.st_mode); 385 case FILSUID: 386 return (s.st_mode & S_ISUID) != 0; 387 case FILSGID: 388 return (s.st_mode & S_ISGID) != 0; 389 case FILSTCK: 390 return (s.st_mode & S_ISVTX) != 0; 391 case FILGZ: 392 return s.st_size > (off_t)0; 393 case FILUID: 394 return s.st_uid == geteuid(); 395 case FILGID: 396 return s.st_gid == getegid(); 397 default: 398 return 1; 399 } 400} 401 402static enum token 403t_lex(char *s) 404{ 405 struct t_op const *op = ops; 406 407 if (s == 0) { 408 t_wp_op = NULL; 409 return EOI; 410 } 411 while (op->op_text) { 412 if (strcmp(s, op->op_text) == 0) { 413 if ((op->op_type == UNOP && isoperand()) || 414 (op->op_num == LPAREN && nargc == 1)) 415 break; 416 t_wp_op = op; 417 return op->op_num; 418 } 419 op++; 420 } 421 t_wp_op = NULL; 422 return OPERAND; 423} 424 425static int 426isoperand(void) 427{ 428 struct t_op const *op = ops; 429 char *s; 430 char *t; 431 432 if (nargc == 1) 433 return 1; 434 if (nargc == 2) 435 return 0; 436 s = *(t_wp + 1); 437 t = *(t_wp + 2); 438 while (op->op_text) { 439 if (strcmp(s, op->op_text) == 0) 440 return op->op_type == BINOP && 441 (t[0] != ')' || t[1] != '\0'); 442 op++; 443 } 444 return 0; 445} 446 447/* atoi with error detection */ 448static int 449getn(const char *s) 450{ 451 char *p; 452 long r; 453 454 errno = 0; 455 r = strtol(s, &p, 10); 456 457 if (s == p) 458 error("%s: bad number", s); 459 460 if (errno != 0) 461 error((errno == EINVAL) ? "%s: bad number" : 462 "%s: out of range", s); 463 464 while (isspace((unsigned char)*p)) 465 p++; 466 467 if (*p) 468 error("%s: bad number", s); 469 470 return (int) r; 471} 472 473/* atoi with error detection and 64 bit range */ 474static intmax_t 475getq(const char *s) 476{ 477 char *p; 478 intmax_t r; 479 480 errno = 0; 481 r = strtoimax(s, &p, 10); 482 483 if (s == p) 484 error("%s: bad number", s); 485 486 if (errno != 0) 487 error((errno == EINVAL) ? "%s: bad number" : 488 "%s: out of range", s); 489 490 while (isspace((unsigned char)*p)) 491 p++; 492 493 if (*p) 494 error("%s: bad number", s); 495 496 return r; 497} 498 499static int 500intcmp (const char *s1, const char *s2) 501{ 502 intmax_t q1, q2; 503 504 505 q1 = getq(s1); 506 q2 = getq(s2); 507 508 if (q1 > q2) 509 return 1; 510 511 if (q1 < q2) 512 return -1; 513 514 return 0; 515} 516 517static int 518newerf (const char *f1, const char *f2) 519{ 520 struct stat b1, b2; 521 522 if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) 523 return 0; 524 525 if (b1.st_mtimespec.tv_sec > b2.st_mtimespec.tv_sec) 526 return 1; 527 if (b1.st_mtimespec.tv_sec < b2.st_mtimespec.tv_sec) 528 return 0; 529 530 return (b1.st_mtimespec.tv_nsec > b2.st_mtimespec.tv_nsec); 531} 532 533static int 534olderf (const char *f1, const char *f2) 535{ 536 return (newerf(f2, f1)); 537} 538 539static int 540equalf (const char *f1, const char *f2) 541{ 542 struct stat b1, b2; 543 544 return (stat (f1, &b1) == 0 && 545 stat (f2, &b2) == 0 && 546 b1.st_dev == b2.st_dev && 547 b1.st_ino == b2.st_ino); 548} 549