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