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