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