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