1 /* $NetBSD: test.c,v 1.22 2000/04/09 23:24:59 christos 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 #ifndef lint 15 __RCSID("$NetBSD: test.c,v 1.22 2000/04/09 23:24:59 christos Exp $"); 16 #endif 17 18 #include <sys/types.h> 19 #include <sys/stat.h> 20 #include <unistd.h> 21 #include <ctype.h> 22 #include <errno.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <err.h> 27 #ifdef __STDC__ 28 #include <stdarg.h> 29 #else 30 #include <varargs.h> 31 #endif 32 33 /* test(1) accepts the following grammar: 34 oexpr ::= aexpr | aexpr "-o" oexpr ; 35 aexpr ::= nexpr | nexpr "-a" aexpr ; 36 nexpr ::= primary | "!" primary 37 primary ::= unary-operator operand 38 | operand binary-operator operand 39 | operand 40 | "(" oexpr ")" 41 ; 42 unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| 43 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; 44 45 binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| 46 "-nt"|"-ot"|"-ef"; 47 operand ::= <any legal UNIX file name> 48 */ 49 50 enum token { 51 EOI, 52 FILRD, 53 FILWR, 54 FILEX, 55 FILEXIST, 56 FILREG, 57 FILDIR, 58 FILCDEV, 59 FILBDEV, 60 FILFIFO, 61 FILSOCK, 62 FILSYM, 63 FILGZ, 64 FILTT, 65 FILSUID, 66 FILSGID, 67 FILSTCK, 68 FILNT, 69 FILOT, 70 FILEQ, 71 FILUID, 72 FILGID, 73 STREZ, 74 STRNZ, 75 STREQ, 76 STRNE, 77 STRLT, 78 STRGT, 79 INTEQ, 80 INTNE, 81 INTGE, 82 INTGT, 83 INTLE, 84 INTLT, 85 UNOT, 86 BAND, 87 BOR, 88 LPAREN, 89 RPAREN, 90 OPERAND 91 }; 92 93 enum token_types { 94 UNOP, 95 BINOP, 96 BUNOP, 97 BBINOP, 98 PAREN 99 }; 100 101 static struct t_op { 102 const char *op_text; 103 short op_num, op_type; 104 } const ops [] = { 105 {"-r", FILRD, UNOP}, 106 {"-w", FILWR, UNOP}, 107 {"-x", FILEX, UNOP}, 108 {"-e", FILEXIST,UNOP}, 109 {"-f", FILREG, UNOP}, 110 {"-d", FILDIR, UNOP}, 111 {"-c", FILCDEV,UNOP}, 112 {"-b", FILBDEV,UNOP}, 113 {"-p", FILFIFO,UNOP}, 114 {"-u", FILSUID,UNOP}, 115 {"-g", FILSGID,UNOP}, 116 {"-k", FILSTCK,UNOP}, 117 {"-s", FILGZ, UNOP}, 118 {"-t", FILTT, UNOP}, 119 {"-z", STREZ, UNOP}, 120 {"-n", STRNZ, UNOP}, 121 {"-h", FILSYM, UNOP}, /* for backwards compat */ 122 {"-O", FILUID, UNOP}, 123 {"-G", FILGID, UNOP}, 124 {"-L", FILSYM, UNOP}, 125 {"-S", FILSOCK,UNOP}, 126 {"=", STREQ, BINOP}, 127 {"!=", STRNE, BINOP}, 128 {"<", STRLT, BINOP}, 129 {">", STRGT, BINOP}, 130 {"-eq", INTEQ, BINOP}, 131 {"-ne", INTNE, BINOP}, 132 {"-ge", INTGE, BINOP}, 133 {"-gt", INTGT, BINOP}, 134 {"-le", INTLE, BINOP}, 135 {"-lt", INTLT, BINOP}, 136 {"-nt", FILNT, BINOP}, 137 {"-ot", FILOT, BINOP}, 138 {"-ef", FILEQ, BINOP}, 139 {"!", UNOT, BUNOP}, 140 {"-a", BAND, BBINOP}, 141 {"-o", BOR, BBINOP}, 142 {"(", LPAREN, PAREN}, 143 {")", RPAREN, PAREN}, 144 {0, 0, 0} 145 }; 146 147 static char **t_wp; 148 static struct t_op const *t_wp_op; 149 150 static void syntax __P((const char *, const char *)); 151 static int oexpr __P((enum token)); 152 static int aexpr __P((enum token)); 153 static int nexpr __P((enum token)); 154 static int primary __P((enum token)); 155 static int binop __P((void)); 156 static int filstat __P((char *, enum token)); 157 static enum token t_lex __P((char *)); 158 static int isoperand __P((void)); 159 static int getn __P((const char *)); 160 static int newerf __P((const char *, const char *)); 161 static int olderf __P((const char *, const char *)); 162 static int equalf __P((const char *, const char *)); 163 164 #if defined(SHELL) 165 extern void error __P((const char *, ...)) __attribute__((__noreturn__)); 166 #else 167 static void error __P((const char *, ...)) __attribute__((__noreturn__)); 168 169 static void 170 #ifdef __STDC__ 171 error(const char *msg, ...) 172 #else 173 error(va_alist) 174 va_dcl 175 #endif 176 { 177 va_list ap; 178 #ifndef __STDC__ 179 const char *msg; 180 181 va_start(ap); 182 msg = va_arg(ap, const char *); 183 #else 184 va_start(ap, msg); 185 #endif 186 verrx(2, msg, ap); 187 /*NOTREACHED*/ 188 va_end(ap); 189 } 190 #endif 191 192 #ifdef SHELL 193 int testcmd __P((int, char **)); 194 195 int 196 testcmd(argc, argv) 197 int argc; 198 char **argv; 199 #else 200 int main __P((int, char **)); 201 202 int 203 main(argc, argv) 204 int argc; 205 char **argv; 206 #endif 207 { 208 int res; 209 210 211 if (strcmp(argv[0], "[") == 0) { 212 if (strcmp(argv[--argc], "]")) 213 error("missing ]"); 214 argv[argc] = NULL; 215 } 216 217 if (argc < 2) 218 return 1; 219 220 t_wp = &argv[1]; 221 res = !oexpr(t_lex(*t_wp)); 222 223 if (*t_wp != NULL && *++t_wp != NULL) 224 syntax(*t_wp, "unexpected operator"); 225 226 return res; 227 } 228 229 static void 230 syntax(op, msg) 231 const char *op; 232 const char *msg; 233 { 234 if (op && *op) 235 error("%s: %s", op, msg); 236 else 237 error("%s", msg); 238 } 239 240 static int 241 oexpr(n) 242 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 253 static int 254 aexpr(n) 255 enum token n; 256 { 257 int res; 258 259 res = nexpr(n); 260 if (t_lex(*++t_wp) == BAND) 261 return aexpr(t_lex(*++t_wp)) && res; 262 t_wp--; 263 return res; 264 } 265 266 static int 267 nexpr(n) 268 enum token n; /* token */ 269 { 270 if (n == UNOT) 271 return !nexpr(t_lex(*++t_wp)); 272 return primary(n); 273 } 274 275 static int 276 primary(n) 277 enum token n; 278 { 279 enum token nn; 280 int res; 281 282 if (n == EOI) 283 return 0; /* missing expression */ 284 if (n == LPAREN) { 285 if ((nn = t_lex(*++t_wp)) == RPAREN) 286 return 0; /* missing expression */ 287 res = oexpr(nn); 288 if (t_lex(*++t_wp) != RPAREN) 289 syntax(NULL, "closing paren expected"); 290 return res; 291 } 292 if (t_wp_op && t_wp_op->op_type == UNOP) { 293 /* unary expression */ 294 if (*++t_wp == NULL) 295 syntax(t_wp_op->op_text, "argument expected"); 296 switch (n) { 297 case STREZ: 298 return strlen(*t_wp) == 0; 299 case STRNZ: 300 return strlen(*t_wp) != 0; 301 case FILTT: 302 return isatty(getn(*t_wp)); 303 default: 304 return filstat(*t_wp, n); 305 } 306 } 307 308 if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { 309 return binop(); 310 } 311 312 return strlen(*t_wp) > 0; 313 } 314 315 static int 316 binop() 317 { 318 const char *opnd1, *opnd2; 319 struct t_op const *op; 320 321 opnd1 = *t_wp; 322 (void) t_lex(*++t_wp); 323 op = t_wp_op; 324 325 if ((opnd2 = *++t_wp) == (char *)0) 326 syntax(op->op_text, "argument expected"); 327 328 switch (op->op_num) { 329 case STREQ: 330 return strcmp(opnd1, opnd2) == 0; 331 case STRNE: 332 return strcmp(opnd1, opnd2) != 0; 333 case STRLT: 334 return strcmp(opnd1, opnd2) < 0; 335 case STRGT: 336 return strcmp(opnd1, opnd2) > 0; 337 case INTEQ: 338 return getn(opnd1) == getn(opnd2); 339 case INTNE: 340 return getn(opnd1) != getn(opnd2); 341 case INTGE: 342 return getn(opnd1) >= getn(opnd2); 343 case INTGT: 344 return getn(opnd1) > getn(opnd2); 345 case INTLE: 346 return getn(opnd1) <= getn(opnd2); 347 case INTLT: 348 return getn(opnd1) < getn(opnd2); 349 case FILNT: 350 return newerf (opnd1, opnd2); 351 case FILOT: 352 return olderf (opnd1, opnd2); 353 case FILEQ: 354 return equalf (opnd1, opnd2); 355 default: 356 abort(); 357 /* NOTREACHED */ 358 } 359 } 360 361 static int 362 filstat(nm, mode) 363 char *nm; 364 enum token mode; 365 { 366 struct stat s; 367 368 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) 369 return 0; 370 371 switch (mode) { 372 case FILRD: 373 return access(nm, R_OK) == 0; 374 case FILWR: 375 return access(nm, W_OK) == 0; 376 case FILEX: 377 return access(nm, X_OK) == 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 411 static enum token 412 t_lex(s) 413 char *s; 414 { 415 struct t_op const *op = ops; 416 417 if (s == 0) { 418 t_wp_op = (struct t_op *)0; 419 return EOI; 420 } 421 while (op->op_text) { 422 if (strcmp(s, op->op_text) == 0) { 423 if ((op->op_type == UNOP && isoperand()) || 424 (op->op_num == LPAREN && *(t_wp+1) == 0)) 425 break; 426 t_wp_op = op; 427 return op->op_num; 428 } 429 op++; 430 } 431 t_wp_op = (struct t_op *)0; 432 return OPERAND; 433 } 434 435 static int 436 isoperand() 437 { 438 struct t_op const *op = ops; 439 char *s; 440 char *t; 441 442 if ((s = *(t_wp+1)) == 0) 443 return 1; 444 if ((t = *(t_wp+2)) == 0) 445 return 0; 446 while (op->op_text) { 447 if (strcmp(s, op->op_text) == 0) 448 return op->op_type == BINOP && 449 (t[0] != ')' || t[1] != '\0'); 450 op++; 451 } 452 return 0; 453 } 454 455 /* atoi with error detection */ 456 static int 457 getn(s) 458 const char *s; 459 { 460 char *p; 461 long r; 462 463 errno = 0; 464 r = strtol(s, &p, 10); 465 466 if (errno != 0) 467 error("%s: out of range", s); 468 469 while (isspace((unsigned char)*p)) 470 p++; 471 472 if (*p) 473 error("%s: bad number", s); 474 475 return (int) r; 476 } 477 478 static int 479 newerf (f1, f2) 480 const char *f1, *f2; 481 { 482 struct stat b1, b2; 483 484 return (stat (f1, &b1) == 0 && 485 stat (f2, &b2) == 0 && 486 b1.st_mtime > b2.st_mtime); 487 } 488 489 static int 490 olderf (f1, f2) 491 const char *f1, *f2; 492 { 493 struct stat b1, b2; 494 495 return (stat (f1, &b1) == 0 && 496 stat (f2, &b2) == 0 && 497 b1.st_mtime < b2.st_mtime); 498 } 499 500 static int 501 equalf (f1, f2) 502 const char *f1, *f2; 503 { 504 struct stat b1, b2; 505 506 return (stat (f1, &b1) == 0 && 507 stat (f2, &b2) == 0 && 508 b1.st_dev == b2.st_dev && 509 b1.st_ino == b2.st_ino); 510 } 511