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