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