2 * test(1); version 7-like -- author Erik Baalbergen
3 * modified by Eric Gisin to be used as built-in.
4 * modified by Arnold Robbins to add SVR3 compatibility
5 * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
6 * modified by Michael Rendell to add Korn's [[ .. ]] expressions.
7 * modified by J.T. Conklin to add POSIX compatibility.
14 /* test(1) accepts the following grammar:
15 oexpr ::= aexpr | aexpr "-o" oexpr ;
16 aexpr ::= nexpr | nexpr "-a" aexpr ;
17 nexpr ::= primary | "!" nexpr ;
18 primary ::= unary-operator operand
19 | operand binary-operator operand
23 unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"|
24 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|
27 binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
28 "-nt"|"-ot"|"-ef"|"-a"|"-o"|
29 "<"|">" # rules used for [[ .. ]] expressions
31 operand ::= <any thing>
34 #define T_ERR_EXIT 2 /* POSIX says > 1 for errors */
40 static const struct t_op u_ops
[] = {
75 static const struct t_op b_ops
[] = {
99 static int test_stat
ARGS((const char *path
, struct stat
*statb
));
100 static int test_eaccess
ARGS((const char *path
, int mode
));
101 static int test_oexpr
ARGS((Test_env
*te
, int do_eval
));
102 static int test_aexpr
ARGS((Test_env
*te
, int do_eval
));
103 static int test_nexpr
ARGS((Test_env
*te
, int do_eval
));
104 static int test_primary
ARGS((Test_env
*te
, int do_eval
));
105 static int ptest_isa
ARGS((Test_env
*te
, Test_meta meta
));
106 static const char *ptest_getopnd
ARGS((Test_env
*te
, Test_op op
, int do_eval
));
107 static int ptest_eval
ARGS((Test_env
*te
, Test_op op
, const char *opnd1
,
108 const char *opnd2
, int do_eval
));
109 static void ptest_error
ARGS((Test_env
*te
, int offset
, const char *msg
));
112 posh_builtin_test(int argc
, char **wp
, int UNUSED(flags
))
119 te
.getopnd
= ptest_getopnd
;
120 te
.eval
= ptest_eval
;
121 te
.error
= ptest_error
;
123 if (strcmp(wp
[0], "[") == 0) {
124 if (strcmp(wp
[--argc
], "]") != 0) {
125 bi_errorf("missing ]");
131 te
.wp_end
= wp
+ argc
;
134 * Handle the special cases from POSIX.2, section 4.62.4.
135 * Implementation of all the rules isn't necessary since
136 * our parser does the right thing for the ommited steps.
142 const char *opnd1
, *opnd2
;
144 while (--argc
>= 0) {
145 if ((*te
.isa
)(&te
, TM_END
))
148 opnd1
= (*te
.getopnd
)(&te
, TO_NONOP
, 1);
149 if ((op
= (Test_op
) (*te
.isa
)(&te
, TM_BINOP
))) {
150 opnd2
= (*te
.getopnd
)(&te
, op
, 1);
151 res
= (*te
.eval
)(&te
, op
, opnd1
, opnd2
,
153 if (te
.flags
& TEF_ERROR
)
159 /* back up to opnd1 */
163 opnd1
= (*te
.getopnd
)(&te
, TO_NONOP
, 1);
164 /* Historically, -t by itself test if fd 1
165 * is a file descriptor, but POSIX says its
168 res
= (*te
.eval
)(&te
, TO_STNZE
, opnd1
,
174 if ((*te
.isa
)(&te
, TM_NOT
)) {
182 return test_parse(&te
);
186 * Generic test routines.
190 test_isop(te
, meta
, s
)
196 const struct t_op
*otab
;
198 otab
= meta
== TM_UNOP
? u_ops
: b_ops
;
201 for (; otab
->op_text
[0]; otab
++)
202 if (sc1
== otab
->op_text
[1]
203 && strcmp(s
, otab
->op_text
) == 0
204 && ((te
->flags
& TEF_DBRACKET
)
205 || (otab
->op_num
!= TO_STLT
206 && otab
->op_num
!= TO_STGT
)))
213 test_eval(te
, op
, opnd1
, opnd2
, do_eval
)
230 case TO_STNZE
: /* -n */
231 return *opnd1
!= '\0';
232 case TO_STZER
: /* -z */
233 return *opnd1
== '\0';
234 #ifdef SILLY_FEATURES
235 case TO_OPTION
: /* -o */
236 if ((not = *opnd1
== '!'))
238 if ((res
= option(opnd1
)) < 0)
246 #endif /* SILLY_FEATURES */
247 case TO_FILRD
: /* -r */
248 return test_eaccess(opnd1
, R_OK
) == 0;
249 case TO_FILWR
: /* -w */
250 return test_eaccess(opnd1
, W_OK
) == 0;
251 case TO_FILEX
: /* -x */
252 return test_eaccess(opnd1
, X_OK
) == 0;
253 #ifdef SILLY_FEATURES
254 case TO_FILAXST
: /* -a */
255 return test_stat(opnd1
, &b1
) == 0;
257 case TO_FILEXST
: /* -e */
258 /* at&t ksh does not appear to do the /dev/fd/ thing for
259 * this (unless the os itself handles it)
261 return stat(opnd1
, &b1
) == 0;
262 case TO_FILREG
: /* -r */
263 return test_stat(opnd1
, &b1
) == 0 && S_ISREG(b1
.st_mode
);
264 case TO_FILID
: /* -d */
265 return test_stat(opnd1
, &b1
) == 0 && S_ISDIR(b1
.st_mode
);
266 case TO_FILCDEV
: /* -c */
268 return test_stat(opnd1
, &b1
) == 0 && S_ISCHR(b1
.st_mode
);
272 case TO_FILBDEV
: /* -b */
274 return test_stat(opnd1
, &b1
) == 0 && S_ISBLK(b1
.st_mode
);
278 case TO_FILFIFO
: /* -p */
280 return test_stat(opnd1
, &b1
) == 0 && S_ISFIFO(b1
.st_mode
);
284 case TO_FILSYM
: /* -h -L */
286 return lstat(opnd1
, &b1
) == 0 && S_ISLNK(b1
.st_mode
);
290 case TO_FILSOCK
: /* -S */
292 return test_stat(opnd1
, &b1
) == 0 && S_ISSOCK(b1
.st_mode
);
296 #ifdef SILLY_FEATURES
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 #endif /* SILLY_FEATURES */
319 case TO_FILSETU
: /* -u */
321 return test_stat(opnd1
, &b1
) == 0
322 && (b1
.st_mode
& S_ISUID
) == S_ISUID
;
326 case TO_FILSETG
: /* -g */
328 return test_stat(opnd1
, &b1
) == 0
329 && (b1
.st_mode
& S_ISGID
) == S_ISGID
;
333 #ifdef SILLY_FEATURES
334 case TO_FILSTCK
: /* -k */
335 return test_stat(opnd1
, &b1
) == 0
336 && (b1
.st_mode
& S_ISVTX
) == S_ISVTX
;
338 case TO_FILGZ
: /* -s */
339 return test_stat(opnd1
, &b1
) == 0 && b1
.st_size
> 0L;
340 case TO_FILTT
: /* -t */
341 if (opnd1
&& !bi_getn(opnd1
, &res
)) {
342 te
->flags
|= TEF_ERROR
;
345 /* generate error if in FPOSIX mode? */
346 res
= isatty(opnd1
? res
: 0);
349 #ifdef SILLY_FEATURES
350 case TO_FILUID
: /* -O */
351 return test_stat(opnd1
, &b1
) == 0 && b1
.st_uid
== ksheuid
;
352 case TO_FILGID
: /* -G */
353 return test_stat(opnd1
, &b1
) == 0 && b1
.st_gid
== getegid();
358 case TO_STEQL
: /* = */
359 if (te
->flags
& TEF_DBRACKET
)
360 return gmatchx(opnd1
, opnd2
, FALSE
);
361 return strcmp(opnd1
, opnd2
) == 0;
362 case TO_STNEQ
: /* != */
363 if (te
->flags
& TEF_DBRACKET
)
364 return !gmatchx(opnd1
, opnd2
, FALSE
);
365 return strcmp(opnd1
, opnd2
) != 0;
366 #ifdef SILLY_FEATURES
367 case TO_STLT
: /* < */
368 return strcmp(opnd1
, opnd2
) < 0;
369 case TO_STGT
: /* > */
370 return strcmp(opnd1
, opnd2
) > 0;
372 case TO_INTEQ
: /* -eq */
373 case TO_INTNE
: /* -ne */
374 case TO_INTGE
: /* -ge */
375 case TO_INTGT
: /* -gt */
376 case TO_INTLE
: /* -le */
377 case TO_INTLT
: /* -lt */
381 if (!evaluate(opnd1
, &v1
, KSH_RETURN_ERROR
)
382 || !evaluate(opnd2
, &v2
, KSH_RETURN_ERROR
))
384 /* error already printed.. */
385 te
->flags
|= TEF_ERROR
;
403 #ifdef SILLY_FEATURES
404 case TO_FILNT
: /* -nt */
407 /* ksh88/ksh93 succeed if file2 can't be stated
408 * (subtly different from `does not exist').
410 return stat(opnd1
, &b1
) == 0
411 && (((s2
= stat(opnd2
, &b2
)) == 0
412 && b1
.st_mtime
> b2
.st_mtime
) || s2
< 0);
414 case TO_FILOT
: /* -ot */
417 /* ksh88/ksh93 succeed if file1 can't be stated
418 * (subtly different from `does not exist').
420 return stat(opnd2
, &b2
) == 0
421 && (((s1
= stat(opnd1
, &b1
)) == 0
422 && b1
.st_mtime
< b2
.st_mtime
) || s1
< 0);
424 case TO_FILEQ
: /* -ef */
425 return stat (opnd1
, &b1
) == 0 && stat (opnd2
, &b2
) == 0
426 && b1
.st_dev
== b2
.st_dev
427 && b1
.st_ino
== b2
.st_ino
;
428 #endif /* SILLY_FEATURES */
430 (*te
->error
)(te
, 0, "internal error: unknown op");
434 /* Nasty kludge to handle Korn's bizarre /dev/fd hack */
436 test_stat(path
, statb
)
440 #if !defined(HAVE_DEV_FD)
443 if (strncmp(path
, "/dev/fd/", 8) == 0 && getn(path
+ 8, &fd
))
444 return fstat(fd
, statb
);
445 #endif /* !HAVE_DEV_FD */
447 return stat(path
, statb
);
450 /* Routine to handle Korn's /dev/fd hack, and to deal with X_OK on
451 * non-directories when running as root.
454 test_eaccess(path
, mode
)
460 #if !defined(HAVE_DEV_FD)
463 /* Note: doesn't handle //dev/fd, etc.. (this is ok) */
464 if (strncmp(path
, "/dev/fd/", 8) == 0 && getn(path
+ 8, &fd
)) {
467 if ((flags
= fcntl(fd
, F_GETFL
, 0)) < 0
469 || ((mode
& W_OK
) && (flags
& O_ACCMODE
) == O_RDONLY
)
470 || ((mode
& R_OK
) && (flags
& O_ACCMODE
) == O_WRONLY
))
474 #endif /* !HAVE_DEV_FD */
476 /* On most (all?) unixes, access() says everything is executable for
477 * root - avoid this on files by using stat().
479 if ((mode
& X_OK
) && ksheuid
== 0) {
482 if (stat(path
, &statb
) < 0)
484 else if (S_ISDIR(statb
.st_mode
))
487 res
= (statb
.st_mode
& (S_IXUSR
|S_IXGRP
|S_IXOTH
))
489 /* Need to check other permissions? If so, use access() as
490 * this will deal with root on NFS.
492 if (res
== 0 && (mode
& (R_OK
|W_OK
)))
493 res
= eaccess(path
, mode
);
495 res
= eaccess(path
, mode
);
506 res
= test_oexpr(te
, 1);
508 if (!(te
->flags
& TEF_ERROR
) && !(*te
->isa
)(te
, TM_END
))
509 (*te
->error
)(te
, 0, "unexpected operator/operand");
511 return (te
->flags
& TEF_ERROR
) ? T_ERR_EXIT
: !res
;
515 test_oexpr(te
, do_eval
)
521 res
= test_aexpr(te
, do_eval
);
524 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_OR
))
525 return test_oexpr(te
, do_eval
) || res
;
530 test_aexpr(te
, do_eval
)
536 res
= test_nexpr(te
, do_eval
);
539 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_AND
))
540 return test_aexpr(te
, do_eval
) && res
;
545 test_nexpr(te
, do_eval
)
549 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_NOT
))
550 return !test_nexpr(te
, do_eval
);
551 return test_primary(te
, do_eval
);
555 test_primary(Test_env
*te
, int do_eval
)
557 const char *opnd1
, *opnd2
;
560 if (te
->flags
& TEF_ERROR
)
563 if ((*te
->isa
)(te
, TM_OPAREN
)) {
564 res
= test_oexpr(te
, do_eval
);
565 if (te
->flags
& TEF_ERROR
)
567 if (!(*te
->isa
)(te
, TM_CPAREN
)) {
568 (*te
->error
)(te
, 0, "missing closing paren");
574 if ((op
= (Test_op
) (*te
->isa
)(te
, TM_UNOP
))) {
575 /* unary expression */
576 opnd1
= (*te
->getopnd
)(te
, op
, do_eval
);
578 (*te
->error
)(te
, -1, "missing argument");
582 return (*te
->eval
)(te
, op
, opnd1
, (const char *) 0, do_eval
);
584 opnd1
= (*te
->getopnd
)(te
, TO_NONOP
, do_eval
);
586 (*te
->error
)(te
, 0, "expression expected");
589 if ((op
= (Test_op
) (*te
->isa
)(te
, TM_BINOP
))) {
590 /* binary expression */
591 opnd2
= (*te
->getopnd
)(te
, op
, do_eval
);
593 (*te
->error
)(te
, -1, "missing second argument");
597 return (*te
->eval
)(te
, op
, opnd1
, opnd2
, do_eval
);
599 if (te
->flags
& TEF_DBRACKET
) {
600 (*te
->error
)(te
, -1, "missing expression operator");
603 return (*te
->eval
)(te
, TO_STNZE
, opnd1
, (const char *) 0, do_eval
);
607 * Plain test (test and [ .. ]) specific routines.
610 /* Test if the current token is a whatever. Accepts the current token if
611 * it is. Returns 0 if it is not, non-zero if it is (in the case of
612 * TM_UNOP and TM_BINOP, the returned value is a Test_op).
619 /* Order important - indexed by Test_meta values */
620 static const char *const tokens
[] = {
625 if (te
->pos
.wp
>= te
->wp_end
)
626 return meta
== TM_END
;
628 if (meta
== TM_UNOP
|| meta
== TM_BINOP
)
629 ret
= (int) test_isop(te
, meta
, *te
->pos
.wp
);
630 else if (meta
== TM_END
)
633 ret
= strcmp(*te
->pos
.wp
, tokens
[(int) meta
]) == 0;
635 /* Accept the token? */
643 ptest_getopnd(Test_env
*te
, Test_op op
, int UNUSED(do_eval
))
645 if (te
->pos
.wp
>= te
->wp_end
)
646 return op
== TO_FILTT
? "1" : (const char *) 0;
647 return *te
->pos
.wp
++;
651 ptest_eval(te
, op
, opnd1
, opnd2
, do_eval
)
658 return test_eval(te
, op
, opnd1
, opnd2
, do_eval
);
662 ptest_error(te
, offset
, msg
)
667 const char *op
= te
->pos
.wp
+ offset
>= te
->wp_end
?
668 (const char *) 0 : te
->pos
.wp
[offset
];
670 te
->flags
|= TEF_ERROR
;
672 bi_errorf("%s: %s", op
, msg
);
674 bi_errorf("%s", msg
);