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