1 /* $NetBSD: c_test.c,v 1.5 2004/07/07 19:20:09 mycroft Exp $ */
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 Michael Rendell to add Korn's [[ .. ]] expressions.
9 * modified by J.T. Conklin to add POSIX compatibility.
11 #include <sys/cdefs.h>
14 __RCSID("$NetBSD: c_test.c,v 1.5 2004/07/07 19:20:09 mycroft Exp $");
22 /* test(1) accepts the following grammar:
23 oexpr ::= aexpr | aexpr "-o" oexpr ;
24 aexpr ::= nexpr | nexpr "-a" aexpr ;
25 nexpr ::= primary | "!" nexpr ;
26 primary ::= unary-operator operand
27 | operand binary-operator operand
32 unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"|
33 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|
36 binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
38 "<"|">" # rules used for [[ .. ]] expressions
40 operand ::= <any thing>
43 #define T_ERR_EXIT 2 /* POSIX says > 1 for errors */
49 static const struct t_op u_ops
[] = {
76 static const struct t_op b_ops
[] = {
96 static int test_stat
ARGS((const char *, struct stat
*));
97 static int test_eaccess
ARGS((const char *, int));
98 static int test_oexpr
ARGS((Test_env
*, int));
99 static int test_aexpr
ARGS((Test_env
*, int));
100 static int test_nexpr
ARGS((Test_env
*, int));
101 static int test_primary
ARGS((Test_env
*, int));
102 static int ptest_isa
ARGS((Test_env
*, Test_meta
));
103 static const char *ptest_getopnd
ARGS((Test_env
*, Test_op
, int));
104 static int ptest_eval
ARGS((Test_env
*, Test_op
, const char *,
106 static void ptest_error
ARGS((Test_env
*, int, const char *));
118 te
.getopnd
= ptest_getopnd
;
119 te
.eval
= ptest_eval
;
120 te
.error
= ptest_error
;
122 for (argc
= 0; wp
[argc
]; argc
++)
125 if (strcmp(wp
[0], "[") == 0) {
126 if (strcmp(wp
[--argc
], "]") != 0) {
127 bi_errorf("missing ]");
133 te
.wp_end
= wp
+ argc
;
136 * Handle the special cases from POSIX.2, section 4.62.4.
137 * Implementation of all the rules isn't necessary since
138 * our parser does the right thing for the omitted steps.
144 const char *opnd1
, *opnd2
;
146 while (--argc
>= 0) {
147 if ((*te
.isa
)(&te
, TM_END
))
150 opnd1
= (*te
.getopnd
)(&te
, TO_NONOP
, 1);
151 if ((op
= (Test_op
) (*te
.isa
)(&te
, TM_BINOP
))) {
152 opnd2
= (*te
.getopnd
)(&te
, op
, 1);
153 res
= (*te
.eval
)(&te
, op
, opnd1
, opnd2
,
155 if (te
.flags
& TEF_ERROR
)
161 /* back up to opnd1 */
165 opnd1
= (*te
.getopnd
)(&te
, TO_NONOP
, 1);
166 /* Historically, -t by itself test if fd 1
167 * is a file descriptor, but POSIX says its
170 if (!Flag(FPOSIX
) && strcmp(opnd1
, "-t") == 0)
172 res
= (*te
.eval
)(&te
, TO_STNZE
, opnd1
,
178 if ((*te
.isa
)(&te
, TM_NOT
)) {
186 return test_parse(&te
);
190 * Generic test routines.
194 test_isop(te
, meta
, s
)
200 const struct t_op
*otab
;
202 otab
= meta
== TM_UNOP
? u_ops
: b_ops
;
205 for (; otab
->op_text
[0]; otab
++)
206 if (sc1
== otab
->op_text
[1]
207 && strcmp(s
, otab
->op_text
) == 0
208 && ((te
->flags
& TEF_DBRACKET
)
209 || (otab
->op_num
!= TO_STLT
210 && otab
->op_num
!= TO_STGT
)))
217 test_eval(te
, op
, opnd1
, opnd2
, do_eval
)
235 case TO_STNZE
: /* -n */
236 return *opnd1
!= '\0';
237 case TO_STZER
: /* -z */
238 return *opnd1
== '\0';
239 case TO_OPTION
: /* -o */
240 if ((not = *opnd1
== '!'))
242 if ((res
= option(opnd1
)) < 0)
250 case TO_FILRD
: /* -r */
251 return test_eaccess(opnd1
, R_OK
) == 0;
252 case TO_FILWR
: /* -w */
253 return test_eaccess(opnd1
, W_OK
) == 0;
254 case TO_FILEX
: /* -x */
255 return test_eaccess(opnd1
, X_OK
) == 0;
256 case TO_FILAXST
: /* -a */
257 return test_stat(opnd1
, &b1
) == 0;
258 case TO_FILEXST
: /* -e */
259 /* at&t ksh does not appear to do the /dev/fd/ thing for
260 * this (unless the os itself handles it)
262 return stat(opnd1
, &b1
) == 0;
263 case TO_FILREG
: /* -r */
264 return test_stat(opnd1
, &b1
) == 0 && S_ISREG(b1
.st_mode
);
265 case TO_FILID
: /* -d */
266 return test_stat(opnd1
, &b1
) == 0 && S_ISDIR(b1
.st_mode
);
267 case TO_FILCDEV
: /* -c */
269 return test_stat(opnd1
, &b1
) == 0 && S_ISCHR(b1
.st_mode
);
273 case TO_FILBDEV
: /* -b */
275 return test_stat(opnd1
, &b1
) == 0 && S_ISBLK(b1
.st_mode
);
279 case TO_FILFIFO
: /* -p */
281 return test_stat(opnd1
, &b1
) == 0 && S_ISFIFO(b1
.st_mode
);
285 case TO_FILSYM
: /* -h -L */
287 return lstat(opnd1
, &b1
) == 0 && S_ISLNK(b1
.st_mode
);
291 case TO_FILSOCK
: /* -S */
293 return test_stat(opnd1
, &b1
) == 0 && S_ISSOCK(b1
.st_mode
);
297 case TO_FILCDF
:/* -H HP context dependent files (directories) */
300 /* Append a + to filename and check to see if result is a
301 * setuid directory. CDF stuff in general is hookey, since
302 * it breaks for the following sequence: echo hi > foo+;
303 * mkdir foo; echo bye > foo/default; chmod u+s foo
304 * (foo+ refers to the file with hi in it, there is no way
305 * to get at the file with bye in it - please correct me if
306 * I'm wrong about this).
308 int len
= strlen(opnd1
);
309 char *p
= str_nsave(opnd1
, len
+ 1, ATEMP
);
313 return stat(p
, &b1
) == 0 && S_ISCDF(b1
.st_mode
);
318 case TO_FILSETU
: /* -u */
320 return test_stat(opnd1
, &b1
) == 0
321 && (b1
.st_mode
& S_ISUID
) == S_ISUID
;
325 case TO_FILSETG
: /* -g */
327 return test_stat(opnd1
, &b1
) == 0
328 && (b1
.st_mode
& S_ISGID
) == S_ISGID
;
332 case TO_FILSTCK
: /* -k */
333 return test_stat(opnd1
, &b1
) == 0
334 && (b1
.st_mode
& S_ISVTX
) == S_ISVTX
;
335 case TO_FILGZ
: /* -s */
336 return test_stat(opnd1
, &b1
) == 0 && b1
.st_size
> 0L;
337 case TO_FILTT
: /* -t */
338 if (opnd1
&& !bi_getn(opnd1
, &res
)) {
339 te
->flags
|= TEF_ERROR
;
342 /* generate error if in FPOSIX mode? */
343 res
= isatty(opnd1
? res
: 0);
346 case TO_FILUID
: /* -O */
347 return test_stat(opnd1
, &b1
) == 0 && b1
.st_uid
== ksheuid
;
348 case TO_FILGID
: /* -G */
349 return test_stat(opnd1
, &b1
) == 0 && b1
.st_gid
== getegid();
353 case TO_STEQL
: /* = */
354 if (te
->flags
& TEF_DBRACKET
)
355 return gmatch(opnd1
, opnd2
, FALSE
);
356 return strcmp(opnd1
, opnd2
) == 0;
357 case TO_STNEQ
: /* != */
358 if (te
->flags
& TEF_DBRACKET
)
359 return !gmatch(opnd1
, opnd2
, FALSE
);
360 return strcmp(opnd1
, opnd2
) != 0;
361 case TO_STLT
: /* < */
362 return strcmp(opnd1
, opnd2
) < 0;
363 case TO_STGT
: /* > */
364 return strcmp(opnd1
, opnd2
) > 0;
365 case TO_INTEQ
: /* -eq */
366 case TO_INTNE
: /* -ne */
367 case TO_INTGE
: /* -ge */
368 case TO_INTGT
: /* -gt */
369 case TO_INTLE
: /* -le */
370 case TO_INTLT
: /* -lt */
374 if (!evaluate(opnd1
, &v1
, KSH_RETURN_ERROR
)
375 || !evaluate(opnd2
, &v2
, KSH_RETURN_ERROR
))
377 /* error already printed.. */
378 te
->flags
|= TEF_ERROR
;
396 case TO_FILNT
: /* -nt */
399 /* ksh88/ksh93 succeed if file2 can't be stated
400 * (subtly different from `does not exist').
402 return stat(opnd1
, &b1
) == 0
403 && (((s2
= stat(opnd2
, &b2
)) == 0
404 && b1
.st_mtime
> b2
.st_mtime
) || s2
< 0);
406 case TO_FILOT
: /* -ot */
409 /* ksh88/ksh93 succeed if file1 can't be stated
410 * (subtly different from `does not exist').
412 return stat(opnd2
, &b2
) == 0
413 && (((s1
= stat(opnd1
, &b1
)) == 0
414 && b1
.st_mtime
< b2
.st_mtime
) || s1
< 0);
416 case TO_FILEQ
: /* -ef */
417 return stat (opnd1
, &b1
) == 0 && stat (opnd2
, &b2
) == 0
418 && b1
.st_dev
== b2
.st_dev
419 && b1
.st_ino
== b2
.st_ino
;
421 (*te
->error
)(te
, 0, "internal error: unknown op");
425 /* Nasty kludge to handle Korn's bizarre /dev/fd hack */
427 test_stat(pathx
, statb
)
431 #if !defined(HAVE_DEV_FD)
434 if (strncmp(pathx
, "/dev/fd/", 8) == 0 && getn(pathx
+ 8, &fd
))
435 return fstat(fd
, statb
);
436 #endif /* !HAVE_DEV_FD */
438 return stat(pathx
, statb
);
441 /* Routine to handle Korn's /dev/fd hack, and to deal with X_OK on
442 * non-directories when running as root.
445 test_eaccess(pathx
, mode
)
451 #if !defined(HAVE_DEV_FD)
454 /* Note: doesn't handle //dev/fd, etc.. (this is ok) */
455 if (strncmp(pathx
, "/dev/fd/", 8) == 0 && getn(pathx
+ 8, &fd
)) {
458 if ((flags
= fcntl(fd
, F_GETFL
, 0)) < 0
460 || ((mode
& W_OK
) && (flags
& O_ACCMODE
) == O_RDONLY
)
461 || ((mode
& R_OK
) && (flags
& O_ACCMODE
) == O_WRONLY
))
465 #endif /* !HAVE_DEV_FD */
467 res
= eaccess(pathx
, mode
);
469 * On most (all?) unixes, access() says everything is executable for
470 * root - avoid this on files by using stat().
472 if (res
== 0 && ksheuid
== 0 && (mode
& X_OK
)) {
475 if (stat(pathx
, &statb
) < 0)
477 else if (S_ISDIR(statb
.st_mode
))
480 res
= (statb
.st_mode
& (S_IXUSR
|S_IXGRP
|S_IXOTH
))
493 res
= test_oexpr(te
, 1);
495 if (!(te
->flags
& TEF_ERROR
) && !(*te
->isa
)(te
, TM_END
))
496 (*te
->error
)(te
, 0, "unexpected operator/operand");
498 return (te
->flags
& TEF_ERROR
) ? T_ERR_EXIT
: !res
;
502 test_oexpr(te
, do_eval
)
508 res
= test_aexpr(te
, do_eval
);
511 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_OR
))
512 return test_oexpr(te
, do_eval
) || res
;
517 test_aexpr(te
, do_eval
)
523 res
= test_nexpr(te
, do_eval
);
526 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_AND
))
527 return test_aexpr(te
, do_eval
) && res
;
532 test_nexpr(te
, do_eval
)
536 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_NOT
))
537 return !test_nexpr(te
, do_eval
);
538 return test_primary(te
, do_eval
);
542 test_primary(te
, do_eval
)
546 const char *opnd1
, *opnd2
;
550 if (te
->flags
& TEF_ERROR
)
552 if ((*te
->isa
)(te
, TM_OPAREN
)) {
553 res
= test_oexpr(te
, do_eval
);
554 if (te
->flags
& TEF_ERROR
)
556 if (!(*te
->isa
)(te
, TM_CPAREN
)) {
557 (*te
->error
)(te
, 0, "missing closing paren");
562 if ((op
= (Test_op
) (*te
->isa
)(te
, TM_UNOP
))) {
563 /* unary expression */
564 opnd1
= (*te
->getopnd
)(te
, op
, do_eval
);
566 (*te
->error
)(te
, -1, "missing argument");
570 return (*te
->eval
)(te
, op
, opnd1
, (const char *) 0, do_eval
);
572 opnd1
= (*te
->getopnd
)(te
, TO_NONOP
, do_eval
);
574 (*te
->error
)(te
, 0, "expression expected");
577 if ((op
= (Test_op
) (*te
->isa
)(te
, TM_BINOP
))) {
578 /* binary expression */
579 opnd2
= (*te
->getopnd
)(te
, op
, do_eval
);
581 (*te
->error
)(te
, -1, "missing second argument");
585 return (*te
->eval
)(te
, op
, opnd1
, opnd2
, do_eval
);
587 if (te
->flags
& TEF_DBRACKET
) {
588 (*te
->error
)(te
, -1, "missing expression operator");
591 return (*te
->eval
)(te
, TO_STNZE
, opnd1
, (const char *) 0, do_eval
);
595 * Plain test (test and [ .. ]) specific routines.
598 /* Test if the current token is a whatever. Accepts the current token if
599 * it is. Returns 0 if it is not, non-zero if it is (in the case of
600 * TM_UNOP and TM_BINOP, the returned value is a Test_op).
607 /* Order important - indexed by Test_meta values */
608 static const char *const tokens
[] = {
609 "-o", "-a", "!", "(", ")"
613 if (te
->pos
.wp
>= te
->wp_end
)
614 return meta
== TM_END
;
616 if (meta
== TM_UNOP
|| meta
== TM_BINOP
)
617 ret
= (int) test_isop(te
, meta
, *te
->pos
.wp
);
618 else if (meta
== TM_END
)
621 ret
= strcmp(*te
->pos
.wp
, tokens
[(int) meta
]) == 0;
623 /* Accept the token? */
631 ptest_getopnd(te
, op
, do_eval
)
636 if (te
->pos
.wp
>= te
->wp_end
)
637 return op
== TO_FILTT
? "1" : (const char *) 0;
638 return *te
->pos
.wp
++;
642 ptest_eval(te
, op
, opnd1
, opnd2
, do_eval
)
649 return test_eval(te
, op
, opnd1
, opnd2
, do_eval
);
653 ptest_error(te
, offset
, msg
)
658 const char *op
= te
->pos
.wp
+ offset
>= te
->wp_end
?
659 (const char *) 0 : te
->pos
.wp
[offset
];
661 te
->flags
|= TEF_ERROR
;
663 bi_errorf("%s: %s", op
, msg
);
665 bi_errorf("%s", msg
);