test.c revision 50189
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 14static const char rcsid[] = 15 "$Id: test.c,v 1.25 1999/08/20 16:19:26 green Exp $"; 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 46enum 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 89enum token_types { 90 UNOP, 91 BINOP, 92 BUNOP, 93 BBINOP, 94 PAREN 95}; 96 97struct 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 143struct t_op const *t_wp_op; 144char **t_wp; 145 146static void syntax __P((const char *, const char *)); 147static enum token t_lex __P((char *)); 148static int oexpr __P((enum token)); 149static int aexpr __P((enum token)); 150static int nexpr __P((enum token)); 151static int primary __P((enum token)); 152static int binop __P((void)); 153static int filstat __P((char *, enum token)); 154static int isoperand __P((void)); 155static int getn __P((const char *)); 156static int newerf __P((const char *, const char *)); 157static int olderf __P((const char *, const char *)); 158static int equalf __P((const char *, const char *)); 159 160int 161main(argc, argv) 162 int argc; 163 char **argv; 164{ 165 int res; 166 167 if (strcmp(argv[0], "[") == 0) { 168 if (strcmp(argv[--argc], "]")) 169 errx(2, "missing ]"); 170 argv[argc] = NULL; 171 } 172 173 /* 174 * We need to set our real user and group so that when we call 175 * access(2), it actually reflects our effective credentials, 176 * not the real credentials it wants to use. 177 */ 178 (void)setgid(getegid()); 179 (void)setuid(geteuid()); 180 181 t_wp = &argv[1]; 182 res = !oexpr(t_lex(*t_wp)); 183 184 if (*t_wp != NULL && *++t_wp != NULL) 185 syntax(*t_wp, "unexpected operator"); 186 187 return res; 188} 189 190static void 191syntax(op, msg) 192 const char *op; 193 const char *msg; 194{ 195 196 if (op && *op) 197 errx(2, "%s: %s", op, msg); 198 else 199 errx(2, "%s", msg); 200} 201 202static int 203oexpr(n) 204 enum token n; 205{ 206 int res; 207 208 res = aexpr(n); 209 if (t_lex(*++t_wp) == BOR) 210 return oexpr(t_lex(*++t_wp)) || res; 211 t_wp--; 212 return res; 213} 214 215static int 216aexpr(n) 217 enum token n; 218{ 219 int res; 220 221 res = nexpr(n); 222 if (t_lex(*++t_wp) == BAND) 223 return aexpr(t_lex(*++t_wp)) && res; 224 t_wp--; 225 return res; 226} 227 228static int 229nexpr(n) 230 enum token n; /* token */ 231{ 232 if (n == UNOT) 233 return !nexpr(t_lex(*++t_wp)); 234 return primary(n); 235} 236 237static int 238primary(n) 239 enum token n; 240{ 241 enum token nn; 242 int res; 243 244 if (n == EOI) 245 return 0; /* missing expression */ 246 if (n == LPAREN) { 247 if ((nn = t_lex(*++t_wp)) == RPAREN) 248 return 0; /* missing expression */ 249 res = oexpr(nn); 250 if (t_lex(*++t_wp) != RPAREN) 251 syntax(NULL, "closing paren expected"); 252 return res; 253 } 254 if (t_wp_op && t_wp_op->op_type == UNOP) { 255 /* unary expression */ 256 if (*++t_wp == NULL) 257 syntax(t_wp_op->op_text, "argument expected"); 258 switch (n) { 259 case STREZ: 260 return strlen(*t_wp) == 0; 261 case STRNZ: 262 return strlen(*t_wp) != 0; 263 case FILTT: 264 return isatty(getn(*t_wp)); 265 default: 266 return filstat(*t_wp, n); 267 } 268 } 269 270 if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { 271 return binop(); 272 } 273 274 return strlen(*t_wp) > 0; 275} 276 277static int 278binop() 279{ 280 const char *opnd1, *opnd2; 281 struct t_op const *op; 282 283 opnd1 = *t_wp; 284 (void) t_lex(*++t_wp); 285 op = t_wp_op; 286 287 if ((opnd2 = *++t_wp) == NULL) 288 syntax(op->op_text, "argument expected"); 289 290 switch (op->op_num) { 291 case STREQ: 292 return strcmp(opnd1, opnd2) == 0; 293 case STRNE: 294 return strcmp(opnd1, opnd2) != 0; 295 case STRLT: 296 return strcmp(opnd1, opnd2) < 0; 297 case STRGT: 298 return strcmp(opnd1, opnd2) > 0; 299 case INTEQ: 300 return getn(opnd1) == getn(opnd2); 301 case INTNE: 302 return getn(opnd1) != getn(opnd2); 303 case INTGE: 304 return getn(opnd1) >= getn(opnd2); 305 case INTGT: 306 return getn(opnd1) > getn(opnd2); 307 case INTLE: 308 return getn(opnd1) <= getn(opnd2); 309 case INTLT: 310 return getn(opnd1) < getn(opnd2); 311 case FILNT: 312 return newerf (opnd1, opnd2); 313 case FILOT: 314 return olderf (opnd1, opnd2); 315 case FILEQ: 316 return equalf (opnd1, opnd2); 317 default: 318 abort(); 319 /* NOTREACHED */ 320 } 321} 322 323static int 324filstat(nm, mode) 325 char *nm; 326 enum token mode; 327{ 328 struct stat s; 329 330 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) 331 return 0; 332 333 switch (mode) { 334 case FILRD: 335 return access(nm, R_OK) == 0; 336 case FILWR: 337 return access(nm, W_OK) == 0; 338 case FILEX: 339 /* 340 * We cannot simply use access(2) for this specific case 341 * since it can always return false positives for root. 342 */ 343 if (access(nm, X_OK) != 0) 344 return 0; 345 if (S_ISDIR(s.st_mode) || getuid() != 0) 346 return 1; 347 return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; 348 case FILEXIST: 349 return access(nm, F_OK) == 0; 350 case FILREG: 351 return S_ISREG(s.st_mode); 352 case FILDIR: 353 return S_ISDIR(s.st_mode); 354 case FILCDEV: 355 return S_ISCHR(s.st_mode); 356 case FILBDEV: 357 return S_ISBLK(s.st_mode); 358 case FILFIFO: 359 return S_ISFIFO(s.st_mode); 360 case FILSOCK: 361 return S_ISSOCK(s.st_mode); 362 case FILSYM: 363 return S_ISLNK(s.st_mode); 364 case FILSUID: 365 return (s.st_mode & S_ISUID) != 0; 366 case FILSGID: 367 return (s.st_mode & S_ISGID) != 0; 368 case FILSTCK: 369 return (s.st_mode & S_ISVTX) != 0; 370 case FILGZ: 371 return s.st_size > (off_t)0; 372 case FILUID: 373 return s.st_uid == geteuid(); 374 case FILGID: 375 return s.st_gid == getegid(); 376 default: 377 return 1; 378 } 379} 380 381static enum token 382t_lex(s) 383 char *s; 384{ 385 struct t_op const *op = ops; 386 387 if (s == 0) { 388 t_wp_op = NULL; 389 return EOI; 390 } 391 while (op->op_text) { 392 if (strcmp(s, op->op_text) == 0) { 393 if ((op->op_type == UNOP && isoperand()) || 394 (op->op_num == LPAREN && *(t_wp+1) == 0)) 395 break; 396 t_wp_op = op; 397 return op->op_num; 398 } 399 op++; 400 } 401 t_wp_op = NULL; 402 return OPERAND; 403} 404 405static int 406isoperand() 407{ 408 struct t_op const *op = ops; 409 char *s; 410 char *t; 411 412 if ((s = *(t_wp+1)) == 0) 413 return 1; 414 if ((t = *(t_wp+2)) == 0) 415 return 0; 416 while (op->op_text) { 417 if (strcmp(s, op->op_text) == 0) 418 return op->op_type == BINOP && 419 (t[0] != ')' || t[1] != '\0'); 420 op++; 421 } 422 return 0; 423} 424 425/* atoi with error detection */ 426static int 427getn(s) 428 const char *s; 429{ 430 char *p; 431 long r; 432 433 errno = 0; 434 r = strtol(s, &p, 10); 435 436 if (errno != 0) 437 errx(2, "%s: out of range", s); 438 439 while (isspace((unsigned char)*p)) 440 p++; 441 442 if (*p) 443 errx(2, "%s: bad number", s); 444 445 return (int) r; 446} 447 448static int 449newerf (f1, f2) 450 const char *f1, *f2; 451{ 452 struct stat b1, b2; 453 454 return (stat (f1, &b1) == 0 && 455 stat (f2, &b2) == 0 && 456 b1.st_mtime > b2.st_mtime); 457} 458 459static int 460olderf (f1, f2) 461 const char *f1, *f2; 462{ 463 struct stat b1, b2; 464 465 return (stat (f1, &b1) == 0 && 466 stat (f2, &b2) == 0 && 467 b1.st_mtime < b2.st_mtime); 468} 469 470static int 471equalf (f1, f2) 472 const char *f1, *f2; 473{ 474 struct stat b1, b2; 475 476 return (stat (f1, &b1) == 0 && 477 stat (f2, &b2) == 0 && 478 b1.st_dev == b2.st_dev && 479 b1.st_ino == b2.st_ino); 480} 481