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