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