1 /* $OpenBSD: test.c,v 1.22 2024/08/15 06:27:24 guenther 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 <limits.h> 20 #include <stdio.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <err.h> 24 25 /* test(1) accepts the following grammar: 26 oexpr ::= aexpr | aexpr "-o" oexpr ; 27 aexpr ::= nexpr | nexpr "-a" aexpr ; 28 nexpr ::= primary | "!" primary 29 primary ::= unary-operator operand 30 | operand binary-operator operand 31 | operand 32 | "(" oexpr ")" 33 ; 34 unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| 35 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; 36 37 binary-operator ::= "="|"!="|"<"|">"|"-eq"|"-ne"|"-ge"|"-gt"| 38 "-le"|"-lt"|"-nt"|"-ot"|"-ef"; 39 operand ::= <any legal UNIX file name> 40 */ 41 42 enum token { 43 EOI, 44 FILRD, 45 FILWR, 46 FILEX, 47 FILEXIST, 48 FILREG, 49 FILDIR, 50 FILCDEV, 51 FILBDEV, 52 FILFIFO, 53 FILSOCK, 54 FILSYM, 55 FILGZ, 56 FILTT, 57 FILSUID, 58 FILSGID, 59 FILSTCK, 60 FILNT, 61 FILOT, 62 FILEQ, 63 FILUID, 64 FILGID, 65 STREZ, 66 STRNZ, 67 STREQ, 68 STRNE, 69 STRLT, 70 STRGT, 71 INTEQ, 72 INTNE, 73 INTGE, 74 INTGT, 75 INTLE, 76 INTLT, 77 UNOT, 78 BAND, 79 BOR, 80 LPAREN, 81 RPAREN, 82 OPERAND 83 }; 84 85 enum token_types { 86 UNOP, 87 BINOP, 88 BUNOP, 89 BBINOP, 90 PAREN 91 }; 92 93 struct t_op { 94 const char *op_text; 95 short op_num, op_type; 96 } const ops [] = { 97 {"-r", FILRD, UNOP}, 98 {"-w", FILWR, UNOP}, 99 {"-x", FILEX, UNOP}, 100 {"-e", FILEXIST,UNOP}, 101 {"-f", FILREG, UNOP}, 102 {"-d", FILDIR, UNOP}, 103 {"-c", FILCDEV,UNOP}, 104 {"-b", FILBDEV,UNOP}, 105 {"-p", FILFIFO,UNOP}, 106 {"-u", FILSUID,UNOP}, 107 {"-g", FILSGID,UNOP}, 108 {"-k", FILSTCK,UNOP}, 109 {"-s", FILGZ, UNOP}, 110 {"-t", FILTT, UNOP}, 111 {"-z", STREZ, UNOP}, 112 {"-n", STRNZ, UNOP}, 113 {"-h", FILSYM, UNOP}, 114 {"-O", FILUID, UNOP}, 115 {"-G", FILGID, UNOP}, 116 {"-L", FILSYM, UNOP}, 117 {"-S", FILSOCK,UNOP}, 118 {"=", STREQ, BINOP}, 119 {"!=", STRNE, BINOP}, 120 {"<", STRLT, BINOP}, 121 {">", STRGT, BINOP}, 122 {"-eq", INTEQ, BINOP}, 123 {"-ne", INTNE, BINOP}, 124 {"-ge", INTGE, BINOP}, 125 {"-gt", INTGT, BINOP}, 126 {"-le", INTLE, BINOP}, 127 {"-lt", INTLT, BINOP}, 128 {"-nt", FILNT, BINOP}, 129 {"-ot", FILOT, BINOP}, 130 {"-ef", FILEQ, BINOP}, 131 {"!", UNOT, BUNOP}, 132 {"-a", BAND, BBINOP}, 133 {"-o", BOR, BBINOP}, 134 {"(", LPAREN, PAREN}, 135 {")", RPAREN, PAREN}, 136 {0, 0, 0} 137 }; 138 139 char **t_wp; 140 struct t_op const *t_wp_op; 141 142 static enum token t_lex(char *); 143 static enum token_types t_lex_type(char *); 144 static int oexpr(enum token n); 145 static int aexpr(enum token n); 146 static int nexpr(enum token n); 147 static int binop(void); 148 static int primary(enum token n); 149 static const char *getnstr(const char *, int *, size_t *); 150 static int intcmp(const char *, const char *); 151 static int filstat(char *nm, enum token mode); 152 static int getn(const char *s); 153 static int newerf(const char *, const char *); 154 static int olderf(const char *, const char *); 155 static int equalf(const char *, const char *); 156 static __dead void syntax(const char *op, char *msg); 157 158 int 159 main(int argc, char *argv[]) 160 { 161 extern char *__progname; 162 int res; 163 164 if (pledge("stdio rpath", NULL) == -1) 165 err(2, "pledge"); 166 167 if (strcmp(__progname, "[") == 0) { 168 if (strcmp(argv[--argc], "]")) 169 errx(2, "missing ]"); 170 argv[argc] = NULL; 171 } 172 173 /* Implement special cases from POSIX.2, section 4.62.4 */ 174 switch (argc) { 175 case 1: 176 return 1; 177 case 2: 178 return (*argv[1] == '\0'); 179 case 3: 180 if (argv[1][0] == '!' && argv[1][1] == '\0') { 181 return !(*argv[2] == '\0'); 182 } 183 break; 184 case 4: 185 if (argv[1][0] != '!' || argv[1][1] != '\0') { 186 if (t_lex(argv[2]), 187 t_wp_op && t_wp_op->op_type == BINOP) { 188 t_wp = &argv[1]; 189 return (binop() == 0); 190 } 191 } 192 break; 193 case 5: 194 if (argv[1][0] == '!' && argv[1][1] == '\0') { 195 if (t_lex(argv[3]), 196 t_wp_op && t_wp_op->op_type == BINOP) { 197 t_wp = &argv[2]; 198 return !(binop() == 0); 199 } 200 } 201 break; 202 } 203 204 t_wp = &argv[1]; 205 res = !oexpr(t_lex(*t_wp)); 206 207 if (*t_wp != NULL && *++t_wp != NULL) 208 syntax(*t_wp, "unknown operand"); 209 210 return res; 211 } 212 213 static __dead void 214 syntax(const char *op, char *msg) 215 { 216 if (op && *op) 217 errx(2, "%s: %s", op, msg); 218 else 219 errx(2, "%s", msg); 220 } 221 222 static int 223 oexpr(enum token n) 224 { 225 int res; 226 227 res = aexpr(n); 228 if (t_lex(*++t_wp) == BOR) 229 return oexpr(t_lex(*++t_wp)) || res; 230 t_wp--; 231 return res; 232 } 233 234 static int 235 aexpr(enum token n) 236 { 237 int res; 238 239 res = nexpr(n); 240 if (t_lex(*++t_wp) == BAND) 241 return aexpr(t_lex(*++t_wp)) && res; 242 t_wp--; 243 return res; 244 } 245 246 static int 247 nexpr(enum token n) 248 { 249 if (n == UNOT) 250 return !nexpr(t_lex(*++t_wp)); 251 return primary(n); 252 } 253 254 static int 255 primary(enum token n) 256 { 257 int res; 258 259 if (n == EOI) 260 syntax(NULL, "argument expected"); 261 if (n == LPAREN) { 262 res = oexpr(t_lex(*++t_wp)); 263 if (t_lex(*++t_wp) != RPAREN) 264 syntax(NULL, "closing paren expected"); 265 return res; 266 } 267 /* 268 * We need this, if not binary operations with more than 4 269 * arguments will always fall into unary. 270 */ 271 if(t_lex_type(t_wp[1]) == BINOP) { 272 t_lex(t_wp[1]); 273 if (t_wp_op && t_wp_op->op_type == BINOP) 274 return binop(); 275 } 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 return strlen(*t_wp) > 0; 294 } 295 296 static const char * 297 getnstr(const char *s, int *signum, size_t *len) 298 { 299 const char *p, *start; 300 301 /* skip leading whitespaces */ 302 p = s; 303 while (isspace((unsigned char)*p)) 304 p++; 305 306 /* accept optional sign */ 307 if (*p == '-') { 308 *signum = -1; 309 p++; 310 } else { 311 *signum = 1; 312 if (*p == '+') 313 p++; 314 } 315 316 /* skip leading zeros */ 317 while (*p == '0' && isdigit((unsigned char)p[1])) 318 p++; 319 320 /* turn 0 always positive */ 321 if (*p == '0') 322 *signum = 1; 323 324 start = p; 325 while (isdigit((unsigned char)*p)) 326 p++; 327 *len = p - start; 328 329 /* allow trailing whitespaces */ 330 while (isspace((unsigned char)*p)) 331 p++; 332 333 /* validate number */ 334 if (*p != '\0' || *start == '\0') 335 errx(2, "%s: invalid", s); 336 337 return start; 338 } 339 340 static int 341 intcmp(const char *opnd1, const char *opnd2) 342 { 343 const char *p1, *p2; 344 size_t len1, len2; 345 int c, sig1, sig2; 346 347 p1 = getnstr(opnd1, &sig1, &len1); 348 p2 = getnstr(opnd2, &sig2, &len2); 349 350 if (sig1 != sig2) 351 c = sig1; 352 else if (len1 != len2) 353 c = (len1 < len2) ? -sig1 : sig1; 354 else 355 c = strncmp(p1, p2, len1) * sig1; 356 357 return c; 358 } 359 360 static int 361 binop(void) 362 { 363 const char *opnd1, *opnd2; 364 struct t_op const *op; 365 366 opnd1 = *t_wp; 367 (void) t_lex(*++t_wp); 368 op = t_wp_op; 369 370 if ((opnd2 = *++t_wp) == NULL) 371 syntax(op->op_text, "argument expected"); 372 373 switch (op->op_num) { 374 case STREQ: 375 return strcmp(opnd1, opnd2) == 0; 376 case STRNE: 377 return strcmp(opnd1, opnd2) != 0; 378 case STRLT: 379 return strcmp(opnd1, opnd2) < 0; 380 case STRGT: 381 return strcmp(opnd1, opnd2) > 0; 382 case INTEQ: 383 return intcmp(opnd1, opnd2) == 0; 384 case INTNE: 385 return intcmp(opnd1, opnd2) != 0; 386 case INTGE: 387 return intcmp(opnd1, opnd2) >= 0; 388 case INTGT: 389 return intcmp(opnd1, opnd2) > 0; 390 case INTLE: 391 return intcmp(opnd1, opnd2) <= 0; 392 case INTLT: 393 return intcmp(opnd1, opnd2) < 0; 394 case FILNT: 395 return newerf(opnd1, opnd2); 396 case FILOT: 397 return olderf(opnd1, opnd2); 398 case FILEQ: 399 return equalf(opnd1, opnd2); 400 } 401 402 syntax(op->op_text, "not a binary operator"); 403 } 404 405 static enum token_types 406 t_lex_type(char *s) 407 { 408 struct t_op const *op = ops; 409 410 if (s == NULL) 411 return -1; 412 413 while (op->op_text) { 414 if (strcmp(s, op->op_text) == 0) 415 return op->op_type; 416 op++; 417 } 418 return -1; 419 } 420 421 static int 422 filstat(char *nm, enum token mode) 423 { 424 struct stat s; 425 mode_t i; 426 427 switch (mode) { 428 case FILRD: 429 return access(nm, R_OK) == 0; 430 case FILWR: 431 return access(nm, W_OK) == 0; 432 case FILEX: 433 return access(nm, X_OK) == 0; 434 case FILEXIST: 435 return access(nm, F_OK) == 0; 436 default: 437 break; 438 } 439 440 if (mode == FILSYM) { 441 if (lstat(nm, &s) == 0) { 442 i = S_IFLNK; 443 goto filetype; 444 } 445 return 0; 446 } 447 448 if (stat(nm, &s) != 0) 449 return 0; 450 451 switch (mode) { 452 case FILREG: 453 i = S_IFREG; 454 goto filetype; 455 case FILDIR: 456 i = S_IFDIR; 457 goto filetype; 458 case FILCDEV: 459 i = S_IFCHR; 460 goto filetype; 461 case FILBDEV: 462 i = S_IFBLK; 463 goto filetype; 464 case FILFIFO: 465 i = S_IFIFO; 466 goto filetype; 467 case FILSOCK: 468 i = S_IFSOCK; 469 goto filetype; 470 case FILSUID: 471 i = S_ISUID; 472 goto filebit; 473 case FILSGID: 474 i = S_ISGID; 475 goto filebit; 476 case FILSTCK: 477 i = S_ISVTX; 478 goto filebit; 479 case FILGZ: 480 return s.st_size > 0L; 481 case FILUID: 482 return s.st_uid == geteuid(); 483 case FILGID: 484 return s.st_gid == getegid(); 485 default: 486 return 1; 487 } 488 489 filetype: 490 return ((s.st_mode & S_IFMT) == i); 491 492 filebit: 493 return ((s.st_mode & i) != 0); 494 } 495 496 static enum token 497 t_lex(char *s) 498 { 499 struct t_op const *op = ops; 500 501 if (s == 0) { 502 t_wp_op = NULL; 503 return EOI; 504 } 505 while (op->op_text) { 506 if (strcmp(s, op->op_text) == 0) { 507 t_wp_op = op; 508 return op->op_num; 509 } 510 op++; 511 } 512 t_wp_op = NULL; 513 return OPERAND; 514 } 515 516 /* atoi with error detection */ 517 static int 518 getn(const char *s) 519 { 520 char buf[32]; 521 const char *errstr, *p; 522 size_t len; 523 int r, sig; 524 525 p = getnstr(s, &sig, &len); 526 if (sig != 1) 527 errstr = "too small"; 528 else if (len >= sizeof(buf)) 529 errstr = "too large"; 530 else { 531 strlcpy(buf, p, sizeof(buf)); 532 buf[len] = '\0'; 533 r = strtonum(buf, 0, INT_MAX, &errstr); 534 } 535 536 if (errstr != NULL) 537 errx(2, "%s: %s", s, errstr); 538 539 return r; 540 } 541 542 static int 543 newerf(const char *f1, const char *f2) 544 { 545 struct stat b1, b2; 546 547 return (stat(f1, &b1) == 0 && 548 stat(f2, &b2) == 0 && 549 b1.st_mtime > b2.st_mtime); 550 } 551 552 static int 553 olderf(const char *f1, const char *f2) 554 { 555 struct stat b1, b2; 556 557 return (stat(f1, &b1) == 0 && 558 stat(f2, &b2) == 0 && 559 b1.st_mtime < b2.st_mtime); 560 } 561 562 static int 563 equalf(const char *f1, const char *f2) 564 { 565 struct stat b1, b2; 566 567 return (stat(f1, &b1) == 0 && 568 stat(f2, &b2) == 0 && 569 b1.st_dev == b2.st_dev && 570 b1.st_ino == b2.st_ino); 571 } 572