1 /* $NetBSD: test.c,v 1.15 1995/03/21 07:04:06 cgd Exp $ */ 2 3 /* 4 * test(1); version 7-like -- author Erik Baalbergen 5 * modified by Eric Gisin to be used as built-in. 6 * modified by Arnold Robbins to add SVR3 compatibility 7 * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). 8 * modified by J.T. Conklin for NetBSD. 9 * 10 * This program is in the Public Domain. 11 */ 12 13 #ifndef lint 14 static char rcsid[] = "$NetBSD: test.c,v 1.15 1995/03/21 07:04:06 cgd Exp $"; 15 #endif 16 17 #include <sys/types.h> 18 #include <sys/stat.h> 19 #include <unistd.h> 20 #include <ctype.h> 21 #include <errno.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <err.h> 26 27 /* test(1) accepts the following grammar: 28 oexpr ::= aexpr | aexpr "-o" oexpr ; 29 aexpr ::= nexpr | nexpr "-a" aexpr ; 30 nexpr ::= primary | "!" primary 31 primary ::= unary-operator operand 32 | operand binary-operator operand 33 | operand 34 | "(" oexpr ")" 35 ; 36 unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| 37 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; 38 39 binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| 40 "-nt"|"-ot"|"-ef"; 41 operand ::= <any legal UNIX file name> 42 */ 43 44 enum token { 45 EOI, 46 FILRD, 47 FILWR, 48 FILEX, 49 FILEXIST, 50 FILREG, 51 FILDIR, 52 FILCDEV, 53 FILBDEV, 54 FILFIFO, 55 FILSOCK, 56 FILSYM, 57 FILGZ, 58 FILTT, 59 FILSUID, 60 FILSGID, 61 FILSTCK, 62 FILNT, 63 FILOT, 64 FILEQ, 65 FILUID, 66 FILGID, 67 STREZ, 68 STRNZ, 69 STREQ, 70 STRNE, 71 STRLT, 72 STRGT, 73 INTEQ, 74 INTNE, 75 INTGE, 76 INTGT, 77 INTLE, 78 INTLT, 79 UNOT, 80 BAND, 81 BOR, 82 LPAREN, 83 RPAREN, 84 OPERAND 85 }; 86 87 enum token_types { 88 UNOP, 89 BINOP, 90 BUNOP, 91 BBINOP, 92 PAREN 93 }; 94 95 struct t_op { 96 const char *op_text; 97 short op_num, op_type; 98 } const ops [] = { 99 {"-r", FILRD, UNOP}, 100 {"-w", FILWR, UNOP}, 101 {"-x", FILEX, UNOP}, 102 {"-e", FILEXIST,UNOP}, 103 {"-f", FILREG, UNOP}, 104 {"-d", FILDIR, UNOP}, 105 {"-c", FILCDEV,UNOP}, 106 {"-b", FILBDEV,UNOP}, 107 {"-p", FILFIFO,UNOP}, 108 {"-u", FILSUID,UNOP}, 109 {"-g", FILSGID,UNOP}, 110 {"-k", FILSTCK,UNOP}, 111 {"-s", FILGZ, UNOP}, 112 {"-t", FILTT, UNOP}, 113 {"-z", STREZ, UNOP}, 114 {"-n", STRNZ, UNOP}, 115 {"-h", FILSYM, UNOP}, /* for backwards compat */ 116 {"-O", FILUID, UNOP}, 117 {"-G", FILGID, UNOP}, 118 {"-L", FILSYM, UNOP}, 119 {"-S", FILSOCK,UNOP}, 120 {"=", STREQ, BINOP}, 121 {"!=", STRNE, BINOP}, 122 {"<", STRLT, BINOP}, 123 {">", STRGT, BINOP}, 124 {"-eq", INTEQ, BINOP}, 125 {"-ne", INTNE, BINOP}, 126 {"-ge", INTGE, BINOP}, 127 {"-gt", INTGT, BINOP}, 128 {"-le", INTLE, BINOP}, 129 {"-lt", INTLT, BINOP}, 130 {"-nt", FILNT, BINOP}, 131 {"-ot", FILOT, BINOP}, 132 {"-ef", FILEQ, BINOP}, 133 {"!", UNOT, BUNOP}, 134 {"-a", BAND, BBINOP}, 135 {"-o", BOR, BBINOP}, 136 {"(", LPAREN, PAREN}, 137 {")", RPAREN, PAREN}, 138 {0, 0, 0} 139 }; 140 141 char **t_wp; 142 struct t_op const *t_wp_op; 143 144 static enum token t_lex(); 145 static int oexpr(); 146 static int aexpr(); 147 static int nexpr(); 148 static int binop(); 149 static int primary(); 150 static int filstat(); 151 static int getn(); 152 static int newerf(); 153 static int olderf(); 154 static int equalf(); 155 static void syntax(); 156 157 int 158 main(argc, argv) 159 int argc; 160 char **argv; 161 { 162 int res; 163 164 if (strcmp(argv[0], "[") == 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 void 211 syntax(op, msg) 212 char *op; 213 char *msg; 214 { 215 if (op && *op) 216 errx(2, "%s: %s", op, msg); 217 else 218 errx(2, "%s", msg); 219 } 220 221 static int 222 oexpr(n) 223 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(n) 236 enum token n; 237 { 238 int res; 239 240 res = nexpr(n); 241 if (t_lex(*++t_wp) == BAND) 242 return aexpr(t_lex(*++t_wp)) && res; 243 t_wp--; 244 return res; 245 } 246 247 static int 248 nexpr(n) 249 enum token n; /* token */ 250 { 251 if (n == UNOT) 252 return !nexpr(t_lex(*++t_wp)); 253 return primary(n); 254 } 255 256 static int 257 primary(n) 258 enum token n; 259 { 260 int res; 261 262 if (n == EOI) 263 syntax(NULL, "argument expected"); 264 if (n == LPAREN) { 265 res = oexpr(t_lex(*++t_wp)); 266 if (t_lex(*++t_wp) != RPAREN) 267 syntax(NULL, "closing paren expected"); 268 return res; 269 } 270 if (t_wp_op && t_wp_op->op_type == UNOP) { 271 /* unary expression */ 272 if (*++t_wp == NULL) 273 syntax(t_wp_op->op_text, "argument expected"); 274 switch (n) { 275 case STREZ: 276 return strlen(*t_wp) == 0; 277 case STRNZ: 278 return strlen(*t_wp) != 0; 279 case FILTT: 280 return isatty(getn(*t_wp)); 281 default: 282 return filstat(*t_wp, n); 283 } 284 } 285 286 if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { 287 return binop(); 288 } 289 290 return strlen(*t_wp) > 0; 291 } 292 293 static int 294 binop() 295 { 296 register 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) == (char *)0) 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 int 338 filstat(nm, mode) 339 char *nm; 340 enum token mode; 341 { 342 struct stat s; 343 int i; 344 345 if (mode == FILSYM) { 346 #ifdef S_IFLNK 347 if (lstat(nm, &s) == 0) { 348 i = S_IFLNK; 349 goto filetype; 350 } 351 #endif 352 return 0; 353 } 354 355 if (stat(nm, &s) != 0) 356 return 0; 357 358 switch (mode) { 359 case FILRD: 360 return access(nm, R_OK) == 0; 361 case FILWR: 362 return access(nm, W_OK) == 0; 363 case FILEX: 364 return access(nm, X_OK) == 0; 365 case FILEXIST: 366 return access(nm, F_OK) == 0; 367 case FILREG: 368 i = S_IFREG; 369 goto filetype; 370 case FILDIR: 371 i = S_IFDIR; 372 goto filetype; 373 case FILCDEV: 374 i = S_IFCHR; 375 goto filetype; 376 case FILBDEV: 377 i = S_IFBLK; 378 goto filetype; 379 case FILFIFO: 380 #ifdef S_IFIFO 381 i = S_IFIFO; 382 goto filetype; 383 #else 384 return 0; 385 #endif 386 case FILSOCK: 387 #ifdef S_IFSOCK 388 i = S_IFSOCK; 389 goto filetype; 390 #else 391 return 0; 392 #endif 393 case FILSUID: 394 i = S_ISUID; 395 goto filebit; 396 case FILSGID: 397 i = S_ISGID; 398 goto filebit; 399 case FILSTCK: 400 i = S_ISVTX; 401 goto filebit; 402 case FILGZ: 403 return s.st_size > 0L; 404 case FILUID: 405 return s.st_uid == geteuid(); 406 case FILGID: 407 return s.st_gid == getegid(); 408 default: 409 return 1; 410 } 411 412 filetype: 413 return ((s.st_mode & S_IFMT) == i); 414 415 filebit: 416 return ((s.st_mode & i) != 0); 417 } 418 419 static enum token 420 t_lex(s) 421 register char *s; 422 { 423 register struct t_op const *op = ops; 424 425 if (s == 0) { 426 t_wp_op = (struct t_op *)0; 427 return EOI; 428 } 429 while (op->op_text) { 430 if (strcmp(s, op->op_text) == 0) { 431 t_wp_op = op; 432 return op->op_num; 433 } 434 op++; 435 } 436 t_wp_op = (struct t_op *)0; 437 return OPERAND; 438 } 439 440 /* atoi with error detection */ 441 static int 442 getn(s) 443 char *s; 444 { 445 char *p; 446 long r; 447 448 errno = 0; 449 r = strtol(s, &p, 10); 450 451 if (errno != 0) 452 errx(2, "%s: out of range", s); 453 454 while (isspace(*p)) 455 p++; 456 457 if (*p) 458 errx(2, "%s: bad number", s); 459 460 return (int) r; 461 } 462 463 static int 464 newerf (f1, f2) 465 char *f1, *f2; 466 { 467 struct stat b1, b2; 468 469 return (stat (f1, &b1) == 0 && 470 stat (f2, &b2) == 0 && 471 b1.st_mtime > b2.st_mtime); 472 } 473 474 static int 475 olderf (f1, f2) 476 char *f1, *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 equalf (f1, f2) 487 char *f1, *f2; 488 { 489 struct stat b1, b2; 490 491 return (stat (f1, &b1) == 0 && 492 stat (f2, &b2) == 0 && 493 b1.st_dev == b2.st_dev && 494 b1.st_ino == b2.st_ino); 495 } 496