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 #include <dos/dosextens.h>
15 #include <proto/dos.h>
17 #include <proto/exec.h>
21 /* test(1) accepts the following grammar:
22 oexpr ::= aexpr | aexpr "-o" oexpr ;
23 aexpr ::= nexpr | nexpr "-a" aexpr ;
24 nexpr ::= primary | "!" nexpr ;
25 primary ::= unary-operator operand
26 | operand binary-operator operand
31 unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"|
32 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|
35 binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
37 "<"|">" # rules used for [[ .. ]] expressions
39 operand ::= <any thing>
42 #define T_ERR_EXIT 2 /* POSIX says > 1 for errors */
48 static const struct t_op u_ops
[] = {
75 static const struct t_op b_ops
[] = {
93 static int test_stat(const char *, struct stat
*);
94 static int test_eaccess(const char *, int);
95 static int test_oexpr(Test_env
*, int);
96 static int test_aexpr(Test_env
*, int);
97 static int test_nexpr(Test_env
*, int);
98 static int test_primary(Test_env
*, int);
99 static int ptest_isa(Test_env
*, Test_meta
);
100 static const char *ptest_getopnd(Test_env
*, Test_op
, int);
101 static int ptest_eval(Test_env
*, Test_op
, const char *,
103 static void ptest_error(Test_env
*, int, const char *);
114 te
.getopnd
= ptest_getopnd
;
115 te
.eval
= ptest_eval
;
116 te
.error
= ptest_error
;
118 for (argc
= 0; wp
[argc
]; argc
++)
121 if (strcmp(wp
[0], "[") == 0) {
122 if (strcmp(wp
[--argc
], "]") != 0) {
123 bi_errorf("missing ]");
129 te
.wp_end
= wp
+ argc
;
132 * Handle the special cases from POSIX.2, section 4.62.4.
133 * Implementation of all the rules isn't necessary since
134 * our parser does the right thing for the omitted steps.
140 const char *opnd1
, *opnd2
;
142 while (--argc
>= 0) {
143 if ((*te
.isa
)(&te
, TM_END
))
146 opnd1
= (*te
.getopnd
)(&te
, TO_NONOP
, 1);
147 if ((op
= (Test_op
) (*te
.isa
)(&te
, TM_BINOP
))) {
148 opnd2
= (*te
.getopnd
)(&te
, op
, 1);
149 res
= (*te
.eval
)(&te
, op
, opnd1
,
151 if (te
.flags
& TEF_ERROR
)
157 /* back up to opnd1 */
161 opnd1
= (*te
.getopnd
)(&te
, TO_NONOP
, 1);
162 /* Historically, -t by itself test if fd 1
163 * is a file descriptor, but POSIX says its
166 if (!Flag(FPOSIX
) && strcmp(opnd1
, "-t") == 0)
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(Test_env
*te
, Test_meta meta
, const char *s
)
193 const struct t_op
*otab
;
195 otab
= meta
== TM_UNOP
? u_ops
: b_ops
;
198 for (; otab
->op_text
[0]; otab
++)
199 if (sc1
== otab
->op_text
[1] &&
200 strcmp(s
, otab
->op_text
) == 0 &&
201 ((te
->flags
& TEF_DBRACKET
) ||
202 (otab
->op_num
!= TO_STLT
&&
203 otab
->op_num
!= TO_STGT
)))
210 test_eval(Test_env
*te
, Test_op op
, const char *opnd1
, const char *opnd2
,
223 case TO_STNZE
: /* -n */
224 return *opnd1
!= '\0';
225 case TO_STZER
: /* -z */
226 return *opnd1
== '\0';
227 case TO_OPTION
: /* -o */
228 if ((not = *opnd1
== '!'))
230 if ((res
= option(opnd1
)) < 0)
238 case TO_FILRD
: /* -r */
239 return test_eaccess(opnd1
, R_OK
) == 0;
240 case TO_FILWR
: /* -w */
241 return test_eaccess(opnd1
, W_OK
) == 0;
242 case TO_FILEX
: /* -x */
243 return test_eaccess(opnd1
, X_OK
) == 0;
244 case TO_FILAXST
: /* -a */
245 return test_stat(opnd1
, &b1
) == 0;
246 case TO_FILEXST
: /* -e */
247 /* at&t ksh does not appear to do the /dev/fd/ thing for
248 * this (unless the os itself handles it)
250 return test_stat(opnd1
, &b1
) == 0;
251 case TO_FILREG
: /* -r */
252 return test_stat(opnd1
, &b1
) == 0 && S_ISREG(b1
.st_mode
);
253 case TO_FILID
: /* -d */
254 return test_stat(opnd1
, &b1
) == 0 && S_ISDIR(b1
.st_mode
);
255 case TO_FILCDEV
: /* -c */
256 return test_stat(opnd1
, &b1
) == 0 && S_ISCHR(b1
.st_mode
);
257 case TO_FILBDEV
: /* -b */
258 return test_stat(opnd1
, &b1
) == 0 && S_ISBLK(b1
.st_mode
);
259 case TO_FILFIFO
: /* -p */
260 return test_stat(opnd1
, &b1
) == 0 && S_ISFIFO(b1
.st_mode
);
261 case TO_FILSYM
: /* -h -L */
262 return lstat(opnd1
, &b1
) == 0 && S_ISLNK(b1
.st_mode
);
263 case TO_FILSOCK
: /* -S */
265 return test_stat(opnd1
, &b1
) == 0 && S_ISSOCK(b1
.st_mode
);
269 case TO_FILSETU
: /* -u */
270 return test_stat(opnd1
, &b1
) == 0 &&
271 (b1
.st_mode
& S_ISUID
) == S_ISUID
;
272 case TO_FILSETG
: /* -g */
273 return test_stat(opnd1
, &b1
) == 0 &&
274 (b1
.st_mode
& S_ISGID
) == S_ISGID
;
275 case TO_FILSTCK
: /* -k */
276 return test_stat(opnd1
, &b1
) == 0 &&
277 (b1
.st_mode
& S_ISVTX
) == S_ISVTX
;
278 case TO_FILGZ
: /* -s */
279 return test_stat(opnd1
, &b1
) == 0 && b1
.st_size
> 0L;
280 case TO_FILTT
: /* -t */
281 if (opnd1
&& !bi_getn(opnd1
, &res
)) {
282 te
->flags
|= TEF_ERROR
;
285 /* generate error if in FPOSIX mode? */
286 res
= isatty(opnd1
? res
: 0);
289 case TO_FILUID
: /* -O */
290 return test_stat(opnd1
, &b1
) == 0 && b1
.st_uid
== ksheuid
;
291 case TO_FILGID
: /* -G */
292 return test_stat(opnd1
, &b1
) == 0 && b1
.st_gid
== kshegid
;
296 case TO_STEQL
: /* = */
297 if (te
->flags
& TEF_DBRACKET
)
298 return gmatch(opnd1
, opnd2
, false);
299 return strcmp(opnd1
, opnd2
) == 0;
300 case TO_STNEQ
: /* != */
301 if (te
->flags
& TEF_DBRACKET
)
302 return !gmatch(opnd1
, opnd2
, false);
303 return strcmp(opnd1
, opnd2
) != 0;
304 case TO_STLT
: /* < */
305 return strcmp(opnd1
, opnd2
) < 0;
306 case TO_STGT
: /* > */
307 return strcmp(opnd1
, opnd2
) > 0;
308 case TO_INTEQ
: /* -eq */
309 case TO_INTNE
: /* -ne */
310 case TO_INTGE
: /* -ge */
311 case TO_INTGT
: /* -gt */
312 case TO_INTLE
: /* -le */
313 case TO_INTLT
: /* -lt */
317 if (!evaluate(opnd1
, &v1
, KSH_RETURN_ERROR
, false) ||
318 !evaluate(opnd2
, &v2
, KSH_RETURN_ERROR
, false))
320 /* error already printed.. */
321 te
->flags
|= TEF_ERROR
;
339 case TO_FILNT
: /* -nt */
342 /* ksh88/ksh93 succeed if file2 can't be stated
343 * (subtly different from `does not exist').
345 return stat(opnd1
, &b1
) == 0 &&
346 (((s2
= stat(opnd2
, &b2
)) == 0 &&
347 b1
.st_mtime
> b2
.st_mtime
) || s2
< 0);
349 case TO_FILOT
: /* -ot */
352 /* ksh88/ksh93 succeed if file1 can't be stated
353 * (subtly different from `does not exist').
355 return stat(opnd2
, &b2
) == 0 &&
356 (((s1
= stat(opnd1
, &b1
)) == 0 &&
357 b1
.st_mtime
< b2
.st_mtime
) || s1
< 0);
359 case TO_FILEQ
: /* -ef */
360 return stat (opnd1
, &b1
) == 0 && stat (opnd2
, &b2
) == 0 &&
361 b1
.st_dev
== b2
.st_dev
&& b1
.st_ino
== b2
.st_ino
;
363 (*te
->error
)(te
, 0, "internal error: unknown op");
368 static APTR
SetProcWindow(APTR new_proc_window
)
370 struct Process
* pr
= (struct Process
*) FindTask(NULL
);
371 APTR old_proc_window
= pr
->pr_WindowPtr
;
372 pr
->pr_WindowPtr
= new_proc_window
;
373 return old_proc_window
;
377 /* Nasty kludge to handle Korn's bizarre /dev/fd hack */
380 test_stat(const char *path
, struct stat
*statb
)
382 #if defined AMIGA && !defined __AROS__
384 APTR old_proc_window
;
385 old_proc_window
= SetProcWindow((APTR
)-1);
386 res
= stat(path
, statb
);
387 SetProcWindow(old_proc_window
);
390 return stat(path
, statb
);
394 /* Routine to handle Korn's /dev/fd hack, and to deal with X_OK on
395 * non-directories when running as root.
398 test_eaccess(const char *path
, int mode
)
403 APTR old_proc_window
;
404 old_proc_window
= SetProcWindow((APTR
)-1);
407 res
= access(path
, mode
);
410 * On most (all?) unixes, access() says everything is executable for
411 * root - avoid this on files by using stat().
413 if (res
== 0 && ksheuid
== 0 && (mode
& X_OK
)) {
416 if (stat(path
, &statb
) < 0)
418 else if (S_ISDIR(statb
.st_mode
))
421 res
= (statb
.st_mode
& (S_IXUSR
|S_IXGRP
|S_IXOTH
)) ?
425 SetProcWindow(old_proc_window
);
431 test_parse(Test_env
*te
)
435 res
= test_oexpr(te
, 1);
437 if (!(te
->flags
& TEF_ERROR
) && !(*te
->isa
)(te
, TM_END
))
438 (*te
->error
)(te
, 0, "unexpected operator/operand");
440 return (te
->flags
& TEF_ERROR
) ? T_ERR_EXIT
: !res
;
444 test_oexpr(Test_env
*te
, int do_eval
)
448 res
= test_aexpr(te
, do_eval
);
451 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_OR
))
452 return test_oexpr(te
, do_eval
) || res
;
457 test_aexpr(Test_env
*te
, int do_eval
)
461 res
= test_nexpr(te
, do_eval
);
464 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_AND
))
465 return test_aexpr(te
, do_eval
) && res
;
470 test_nexpr(Test_env
*te
, int do_eval
)
472 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_NOT
))
473 return !test_nexpr(te
, do_eval
);
474 return test_primary(te
, do_eval
);
478 test_primary(Test_env
*te
, int do_eval
)
480 const char *opnd1
, *opnd2
;
484 if (te
->flags
& TEF_ERROR
)
486 if ((*te
->isa
)(te
, TM_OPAREN
)) {
487 res
= test_oexpr(te
, do_eval
);
488 if (te
->flags
& TEF_ERROR
)
490 if (!(*te
->isa
)(te
, TM_CPAREN
)) {
491 (*te
->error
)(te
, 0, "missing closing paren");
496 if ((op
= (Test_op
) (*te
->isa
)(te
, TM_UNOP
))) {
497 /* unary expression */
498 opnd1
= (*te
->getopnd
)(te
, op
, do_eval
);
500 (*te
->error
)(te
, -1, "missing argument");
504 return (*te
->eval
)(te
, op
, opnd1
, (const char *) 0, do_eval
);
506 opnd1
= (*te
->getopnd
)(te
, TO_NONOP
, do_eval
);
508 (*te
->error
)(te
, 0, "expression expected");
511 if ((op
= (Test_op
) (*te
->isa
)(te
, TM_BINOP
))) {
512 /* binary expression */
513 opnd2
= (*te
->getopnd
)(te
, op
, do_eval
);
515 (*te
->error
)(te
, -1, "missing second argument");
519 return (*te
->eval
)(te
, op
, opnd1
, opnd2
, do_eval
);
521 if (te
->flags
& TEF_DBRACKET
) {
522 (*te
->error
)(te
, -1, "missing expression operator");
525 return (*te
->eval
)(te
, TO_STNZE
, opnd1
, (const char *) 0, do_eval
);
529 * Plain test (test and [ .. ]) specific routines.
532 /* Test if the current token is a whatever. Accepts the current token if
533 * it is. Returns 0 if it is not, non-zero if it is (in the case of
534 * TM_UNOP and TM_BINOP, the returned value is a Test_op).
537 ptest_isa(Test_env
*te
, Test_meta meta
)
539 /* Order important - indexed by Test_meta values */
540 static const char *const tokens
[] = {
541 "-o", "-a", "!", "(", ")"
545 if (te
->pos
.wp
>= te
->wp_end
)
546 return meta
== TM_END
;
548 if (meta
== TM_UNOP
|| meta
== TM_BINOP
)
549 ret
= (int) test_isop(te
, meta
, *te
->pos
.wp
);
550 else if (meta
== TM_END
)
553 ret
= strcmp(*te
->pos
.wp
, tokens
[(int) meta
]) == 0;
555 /* Accept the token? */
563 ptest_getopnd(Test_env
*te
, Test_op op
, int do_eval
)
565 if (te
->pos
.wp
>= te
->wp_end
)
566 return op
== TO_FILTT
? "1" : (const char *) 0;
567 return *te
->pos
.wp
++;
571 ptest_eval(Test_env
*te
, Test_op op
, const char *opnd1
, const char *opnd2
,
574 return test_eval(te
, op
, opnd1
, opnd2
, do_eval
);
578 ptest_error(Test_env
*te
, int offset
, const char *msg
)
580 const char *op
= te
->pos
.wp
+ offset
>= te
->wp_end
?
581 (const char *) 0 : te
->pos
.wp
[offset
];
583 te
->flags
|= TEF_ERROR
;
585 bi_errorf("%s: %s", op
, msg
);
587 bi_errorf("%s", msg
);