test.c revision 100774
11573Srgrimes/* $NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $ */ 21573Srgrimes 31573Srgrimes/* 41573Srgrimes * test(1); version 7-like -- author Erik Baalbergen 51573Srgrimes * modified by Eric Gisin to be used as built-in. 61573Srgrimes * modified by Arnold Robbins to add SVR3 compatibility 71573Srgrimes * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). 81573Srgrimes * modified by J.T. Conklin for NetBSD. 91573Srgrimes * 101573Srgrimes * This program is in the Public Domain. 111573Srgrimes */ 121573Srgrimes 131573Srgrimes#include <sys/cdefs.h> 141573Srgrimes__FBSDID("$FreeBSD: head/bin/test/test.c 100774 2002-07-27 22:53:44Z dwmalone $"); 151573Srgrimes 161573Srgrimes#include <sys/types.h> 171573Srgrimes#include <sys/stat.h> 181573Srgrimes 191573Srgrimes#include <ctype.h> 201573Srgrimes#include <err.h> 211573Srgrimes#include <errno.h> 221573Srgrimes#include <inttypes.h> 231573Srgrimes#include <limits.h> 241573Srgrimes#include <stdarg.h> 251573Srgrimes#include <stdio.h> 261573Srgrimes#include <stdlib.h> 271573Srgrimes#include <string.h> 281573Srgrimes#include <unistd.h> 291573Srgrimes 301573Srgrimes#ifdef SHELL 311573Srgrimes#define main testcmd 321573Srgrimes#include "bltin/bltin.h" 331573Srgrimes#else 341573Srgrimes#include <locale.h> 351573Srgrimes 361573Srgrimesstatic void error(const char *, ...) __dead2 __printf0like(1, 2); 3790039Sobrien 3890039Sobrienstatic void 391573Srgrimeserror(const char *msg, ...) 4071579Sdeischen{ 411573Srgrimes va_list ap; 421573Srgrimes va_start(ap, msg); 431573Srgrimes verrx(2, msg, ap); 4471579Sdeischen /*NOTREACHED*/ 451573Srgrimes va_end(ap); 461573Srgrimes} 471573Srgrimes#endif 481573Srgrimes 491573Srgrimes/* test(1) accepts the following grammar: 501573Srgrimes oexpr ::= aexpr | aexpr "-o" oexpr ; 511573Srgrimes aexpr ::= nexpr | nexpr "-a" aexpr ; 521573Srgrimes nexpr ::= primary | "!" primary 531573Srgrimes primary ::= unary-operator operand 541573Srgrimes | operand binary-operator operand 551573Srgrimes | operand 561573Srgrimes | "(" oexpr ")" 571573Srgrimes ; 581573Srgrimes unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| 591573Srgrimes "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; 601573Srgrimes 611573Srgrimes binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| 621573Srgrimes "-nt"|"-ot"|"-ef"; 631573Srgrimes operand ::= <any legal UNIX file name> 641573Srgrimes*/ 651573Srgrimes 661573Srgrimesenum token { 6756698Sjasone EOI, 6871579Sdeischen FILRD, 6971579Sdeischen FILWR, 7071579Sdeischen FILEX, 711573Srgrimes FILEXIST, 7256698Sjasone FILREG, 731573Srgrimes FILDIR, 741573Srgrimes FILCDEV, 751573Srgrimes 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 free(nargv); 222 223 return res; 224} 225 226static void 227syntax(const char *op, const char *msg) 228{ 229 230 if (op && *op) 231 error("%s: %s", op, msg); 232 else 233 error("%s", msg); 234} 235 236static int 237oexpr(enum token n) 238{ 239 int res; 240 241 res = aexpr(n); 242 if (t_lex(*++t_wp) == BOR) 243 return oexpr(t_lex(*++t_wp)) || res; 244 t_wp--; 245 return res; 246} 247 248static int 249aexpr(enum token n) 250{ 251 int res; 252 253 res = nexpr(n); 254 if (t_lex(*++t_wp) == BAND) 255 return aexpr(t_lex(*++t_wp)) && res; 256 t_wp--; 257 return res; 258} 259 260static int 261nexpr(enum token n) 262{ 263 if (n == UNOT) 264 return !nexpr(t_lex(*++t_wp)); 265 return primary(n); 266} 267 268static int 269primary(enum token n) 270{ 271 enum token nn; 272 int res; 273 274 if (n == EOI) 275 return 0; /* missing expression */ 276 if (n == LPAREN) { 277 if ((nn = t_lex(*++t_wp)) == RPAREN) 278 return 0; /* missing expression */ 279 res = oexpr(nn); 280 if (t_lex(*++t_wp) != RPAREN) 281 syntax(NULL, "closing paren expected"); 282 return res; 283 } 284 if (t_wp_op && t_wp_op->op_type == UNOP) { 285 /* unary expression */ 286 if (*++t_wp == NULL) 287 syntax(t_wp_op->op_text, "argument expected"); 288 switch (n) { 289 case STREZ: 290 return strlen(*t_wp) == 0; 291 case STRNZ: 292 return strlen(*t_wp) != 0; 293 case FILTT: 294 return isatty(getn(*t_wp)); 295 default: 296 return filstat(*t_wp, n); 297 } 298 } 299 300 if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { 301 return binop(); 302 } 303 304 return strlen(*t_wp) > 0; 305} 306 307static int 308binop(void) 309{ 310 const char *opnd1, *opnd2; 311 struct t_op const *op; 312 313 opnd1 = *t_wp; 314 (void) t_lex(*++t_wp); 315 op = t_wp_op; 316 317 if ((opnd2 = *++t_wp) == NULL) 318 syntax(op->op_text, "argument expected"); 319 320 switch (op->op_num) { 321 case STREQ: 322 return strcmp(opnd1, opnd2) == 0; 323 case STRNE: 324 return strcmp(opnd1, opnd2) != 0; 325 case STRLT: 326 return strcmp(opnd1, opnd2) < 0; 327 case STRGT: 328 return strcmp(opnd1, opnd2) > 0; 329 case INTEQ: 330 return intcmp(opnd1, opnd2) == 0; 331 case INTNE: 332 return intcmp(opnd1, opnd2) != 0; 333 case INTGE: 334 return intcmp(opnd1, opnd2) >= 0; 335 case INTGT: 336 return intcmp(opnd1, opnd2) > 0; 337 case INTLE: 338 return intcmp(opnd1, opnd2) <= 0; 339 case INTLT: 340 return intcmp(opnd1, opnd2) < 0; 341 case FILNT: 342 return newerf (opnd1, opnd2); 343 case FILOT: 344 return olderf (opnd1, opnd2); 345 case FILEQ: 346 return equalf (opnd1, opnd2); 347 default: 348 abort(); 349 /* NOTREACHED */ 350 } 351} 352 353static int 354filstat(char *nm, enum token mode) 355{ 356 struct stat s; 357 358 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) 359 return 0; 360 361 switch (mode) { 362 case FILRD: 363 return (eaccess(nm, R_OK) == 0); 364 case FILWR: 365 return (eaccess(nm, W_OK) == 0); 366 case FILEX: 367 /* XXX work around eaccess(2) false positives for superuser */ 368 if (eaccess(nm, X_OK) != 0) 369 return 0; 370 if (S_ISDIR(s.st_mode) || geteuid() != 0) 371 return 1; 372 return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; 373 case FILEXIST: 374 return (eaccess(nm, F_OK) == 0); 375 case FILREG: 376 return S_ISREG(s.st_mode); 377 case FILDIR: 378 return S_ISDIR(s.st_mode); 379 case FILCDEV: 380 return S_ISCHR(s.st_mode); 381 case FILBDEV: 382 return S_ISBLK(s.st_mode); 383 case FILFIFO: 384 return S_ISFIFO(s.st_mode); 385 case FILSOCK: 386 return S_ISSOCK(s.st_mode); 387 case FILSYM: 388 return S_ISLNK(s.st_mode); 389 case FILSUID: 390 return (s.st_mode & S_ISUID) != 0; 391 case FILSGID: 392 return (s.st_mode & S_ISGID) != 0; 393 case FILSTCK: 394 return (s.st_mode & S_ISVTX) != 0; 395 case FILGZ: 396 return s.st_size > (off_t)0; 397 case FILUID: 398 return s.st_uid == geteuid(); 399 case FILGID: 400 return s.st_gid == getegid(); 401 default: 402 return 1; 403 } 404} 405 406static enum token 407t_lex(char *s) 408{ 409 struct t_op const *op = ops; 410 411 if (s == 0) { 412 t_wp_op = NULL; 413 return EOI; 414 } 415 while (op->op_text) { 416 if (strcmp(s, op->op_text) == 0) { 417 if ((op->op_type == UNOP && isoperand()) || 418 (op->op_num == LPAREN && *(t_wp+1) == 0)) 419 break; 420 t_wp_op = op; 421 return op->op_num; 422 } 423 op++; 424 } 425 t_wp_op = NULL; 426 return OPERAND; 427} 428 429static int 430isoperand(void) 431{ 432 struct t_op const *op = ops; 433 char *s; 434 char *t; 435 436 if ((s = *(t_wp+1)) == 0) 437 return 1; 438 if ((t = *(t_wp+2)) == 0) 439 return 0; 440 while (op->op_text) { 441 if (strcmp(s, op->op_text) == 0) 442 return op->op_type == BINOP && 443 (t[0] != ')' || t[1] != '\0'); 444 op++; 445 } 446 return 0; 447} 448 449/* atoi with error detection */ 450static int 451getn(const char *s) 452{ 453 char *p; 454 long r; 455 456 errno = 0; 457 r = strtol(s, &p, 10); 458 459 if (s == p) 460 error("%s: bad number", s); 461 462 if (errno != 0) 463 error((errno == EINVAL) ? "%s: bad number" : 464 "%s: out of range", s); 465 466 while (isspace((unsigned char)*p)) 467 p++; 468 469 if (*p) 470 error("%s: bad number", s); 471 472 return (int) r; 473} 474 475/* atoi with error detection and 64 bit range */ 476static intmax_t 477getq(const char *s) 478{ 479 char *p; 480 intmax_t r; 481 482 errno = 0; 483 r = strtoimax(s, &p, 10); 484 485 if (s == p) 486 error("%s: bad number", s); 487 488 if (errno != 0) 489 error((errno == EINVAL) ? "%s: bad number" : 490 "%s: out of range", s); 491 492 while (isspace((unsigned char)*p)) 493 p++; 494 495 if (*p) 496 error("%s: bad number", s); 497 498 return r; 499} 500 501static int 502intcmp (const char *s1, const char *s2) 503{ 504 intmax_t q1, q2; 505 506 507 q1 = getq(s1); 508 q2 = getq(s2); 509 510 if (q1 > q2) 511 return 1; 512 513 if (q1 < q2) 514 return -1; 515 516 return 0; 517} 518 519static int 520newerf (const char *f1, const char *f2) 521{ 522 struct stat b1, b2; 523 524 if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) 525 return 0; 526 527 if (b1.st_mtimespec.tv_sec > b2.st_mtimespec.tv_sec) 528 return 1; 529 if (b1.st_mtimespec.tv_sec < b2.st_mtimespec.tv_sec) 530 return 0; 531 532 return (b1.st_mtimespec.tv_nsec > b2.st_mtimespec.tv_nsec); 533} 534 535static int 536olderf (const char *f1, const char *f2) 537{ 538 return (newerf(f2, f1)); 539} 540 541static int 542equalf (const char *f1, const char *f2) 543{ 544 struct stat b1, b2; 545 546 return (stat (f1, &b1) == 0 && 547 stat (f2, &b2) == 0 && 548 b1.st_dev == b2.st_dev && 549 b1.st_ino == b2.st_ino); 550} 551