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