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