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