1 /* $OpenBSD: test.c,v 1.16 2016/01/13 13:13:04 gsoares 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 extern char *__progname; 159 int res; 160 161 if (pledge("stdio rpath", NULL) == -1) 162 err(2, "pledge"); 163 164 if (strcmp(__progname, "[") == 0) { 165 if (strcmp(argv[--argc], "]")) 166 errx(2, "missing ]"); 167 argv[argc] = NULL; 168 } 169 170 /* Implement special cases from POSIX.2, section 4.62.4 */ 171 switch (argc) { 172 case 1: 173 return 1; 174 case 2: 175 return (*argv[1] == '\0'); 176 case 3: 177 if (argv[1][0] == '!' && argv[1][1] == '\0') { 178 return !(*argv[2] == '\0'); 179 } 180 break; 181 case 4: 182 if (argv[1][0] != '!' || argv[1][1] != '\0') { 183 if (t_lex(argv[2]), 184 t_wp_op && t_wp_op->op_type == BINOP) { 185 t_wp = &argv[1]; 186 return (binop() == 0); 187 } 188 } 189 break; 190 case 5: 191 if (argv[1][0] == '!' && argv[1][1] == '\0') { 192 if (t_lex(argv[3]), 193 t_wp_op && t_wp_op->op_type == BINOP) { 194 t_wp = &argv[2]; 195 return !(binop() == 0); 196 } 197 } 198 break; 199 } 200 201 t_wp = &argv[1]; 202 res = !oexpr(t_lex(*t_wp)); 203 204 if (*t_wp != NULL && *++t_wp != NULL) 205 syntax(*t_wp, "unknown operand"); 206 207 return res; 208 } 209 210 static __dead void 211 syntax(const char *op, char *msg) 212 { 213 if (op && *op) 214 errx(2, "%s: %s", op, msg); 215 else 216 errx(2, "%s", msg); 217 } 218 219 static int 220 oexpr(enum token n) 221 { 222 int res; 223 224 res = aexpr(n); 225 if (t_lex(*++t_wp) == BOR) 226 return oexpr(t_lex(*++t_wp)) || res; 227 t_wp--; 228 return res; 229 } 230 231 static int 232 aexpr(enum token n) 233 { 234 int res; 235 236 res = nexpr(n); 237 if (t_lex(*++t_wp) == BAND) 238 return aexpr(t_lex(*++t_wp)) && res; 239 t_wp--; 240 return res; 241 } 242 243 static int 244 nexpr(enum token n) 245 { 246 if (n == UNOT) 247 return !nexpr(t_lex(*++t_wp)); 248 return primary(n); 249 } 250 251 static int 252 primary(enum token n) 253 { 254 int res; 255 256 if (n == EOI) 257 syntax(NULL, "argument expected"); 258 if (n == LPAREN) { 259 res = oexpr(t_lex(*++t_wp)); 260 if (t_lex(*++t_wp) != RPAREN) 261 syntax(NULL, "closing paren expected"); 262 return res; 263 } 264 /* 265 * We need this, if not binary operations with more than 4 266 * arguments will always fall into unary. 267 */ 268 if(t_lex_type(t_wp[1]) == BINOP) { 269 t_lex(t_wp[1]); 270 if (t_wp_op && t_wp_op->op_type == BINOP) 271 return binop(); 272 } 273 274 if (t_wp_op && t_wp_op->op_type == UNOP) { 275 /* unary expression */ 276 if (*++t_wp == NULL) 277 syntax(t_wp_op->op_text, "argument expected"); 278 switch (n) { 279 case STREZ: 280 return strlen(*t_wp) == 0; 281 case STRNZ: 282 return strlen(*t_wp) != 0; 283 case FILTT: 284 return isatty(getn(*t_wp)); 285 default: 286 return filstat(*t_wp, n); 287 } 288 } 289 290 return strlen(*t_wp) > 0; 291 } 292 293 static int 294 binop(void) 295 { 296 const char *opnd1, *opnd2; 297 struct t_op const *op; 298 299 opnd1 = *t_wp; 300 (void) t_lex(*++t_wp); 301 op = t_wp_op; 302 303 if ((opnd2 = *++t_wp) == NULL) 304 syntax(op->op_text, "argument expected"); 305 306 switch (op->op_num) { 307 case STREQ: 308 return strcmp(opnd1, opnd2) == 0; 309 case STRNE: 310 return strcmp(opnd1, opnd2) != 0; 311 case STRLT: 312 return strcmp(opnd1, opnd2) < 0; 313 case STRGT: 314 return strcmp(opnd1, opnd2) > 0; 315 case INTEQ: 316 return getn(opnd1) == getn(opnd2); 317 case INTNE: 318 return getn(opnd1) != getn(opnd2); 319 case INTGE: 320 return getn(opnd1) >= getn(opnd2); 321 case INTGT: 322 return getn(opnd1) > getn(opnd2); 323 case INTLE: 324 return getn(opnd1) <= getn(opnd2); 325 case INTLT: 326 return getn(opnd1) < getn(opnd2); 327 case FILNT: 328 return newerf(opnd1, opnd2); 329 case FILOT: 330 return olderf(opnd1, opnd2); 331 case FILEQ: 332 return equalf(opnd1, opnd2); 333 } 334 /* NOTREACHED */ 335 } 336 337 static enum token 338 t_lex_type(char *s) 339 { 340 struct t_op const *op = ops; 341 342 if (s == NULL) 343 return -1; 344 345 while (op->op_text) { 346 if (strcmp(s, op->op_text) == 0) 347 return op->op_type; 348 op++; 349 } 350 return -1; 351 } 352 353 static int 354 filstat(char *nm, enum token mode) 355 { 356 struct stat s; 357 mode_t i; 358 359 if (mode == FILSYM) { 360 #ifdef S_IFLNK 361 if (lstat(nm, &s) == 0) { 362 i = S_IFLNK; 363 goto filetype; 364 } 365 #endif 366 return 0; 367 } 368 369 if (stat(nm, &s) != 0) 370 return 0; 371 372 switch (mode) { 373 case FILRD: 374 return access(nm, R_OK) == 0; 375 case FILWR: 376 return access(nm, W_OK) == 0; 377 case FILEX: 378 return access(nm, X_OK) == 0; 379 case FILEXIST: 380 return access(nm, F_OK) == 0; 381 case FILREG: 382 i = S_IFREG; 383 goto filetype; 384 case FILDIR: 385 i = S_IFDIR; 386 goto filetype; 387 case FILCDEV: 388 i = S_IFCHR; 389 goto filetype; 390 case FILBDEV: 391 i = S_IFBLK; 392 goto filetype; 393 case FILFIFO: 394 #ifdef S_IFIFO 395 i = S_IFIFO; 396 goto filetype; 397 #else 398 return 0; 399 #endif 400 case FILSOCK: 401 #ifdef S_IFSOCK 402 i = S_IFSOCK; 403 goto filetype; 404 #else 405 return 0; 406 #endif 407 case FILSUID: 408 i = S_ISUID; 409 goto filebit; 410 case FILSGID: 411 i = S_ISGID; 412 goto filebit; 413 case FILSTCK: 414 i = S_ISVTX; 415 goto filebit; 416 case FILGZ: 417 return s.st_size > 0L; 418 case FILUID: 419 return s.st_uid == geteuid(); 420 case FILGID: 421 return s.st_gid == getegid(); 422 default: 423 return 1; 424 } 425 426 filetype: 427 return ((s.st_mode & S_IFMT) == i); 428 429 filebit: 430 return ((s.st_mode & i) != 0); 431 } 432 433 static enum token 434 t_lex(char *s) 435 { 436 struct t_op const *op = ops; 437 438 if (s == 0) { 439 t_wp_op = NULL; 440 return EOI; 441 } 442 while (op->op_text) { 443 if (strcmp(s, op->op_text) == 0) { 444 t_wp_op = op; 445 return op->op_num; 446 } 447 op++; 448 } 449 t_wp_op = NULL; 450 return OPERAND; 451 } 452 453 /* atoi with error detection */ 454 static int 455 getn(const char *s) 456 { 457 char *p; 458 long r; 459 460 errno = 0; 461 r = strtol(s, &p, 10); 462 463 if (errno != 0) 464 errx(2, "%s: out of range", s); 465 466 while (isspace((unsigned char)*p)) 467 p++; 468 469 if (*p) 470 errx(2, "%s: bad number", s); 471 472 return (int) r; 473 } 474 475 static int 476 newerf(const char *f1, const char *f2) 477 { 478 struct stat b1, b2; 479 480 return (stat(f1, &b1) == 0 && 481 stat(f2, &b2) == 0 && 482 b1.st_mtime > b2.st_mtime); 483 } 484 485 static int 486 olderf(const char *f1, const char *f2) 487 { 488 struct stat b1, b2; 489 490 return (stat(f1, &b1) == 0 && 491 stat(f2, &b2) == 0 && 492 b1.st_mtime < b2.st_mtime); 493 } 494 495 static int 496 equalf(const char *f1, const char *f2) 497 { 498 struct stat b1, b2; 499 500 return (stat(f1, &b1) == 0 && 501 stat(f2, &b2) == 0 && 502 b1.st_dev == b2.st_dev && 503 b1.st_ino == b2.st_ino); 504 } 505