1 /* $OpenBSD: c_test.c,v 1.18 2009/03/01 20:11:06 otto 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.
16 /* test(1) accepts the following grammar:
17 oexpr ::= aexpr | aexpr "-o" oexpr ;
18 aexpr ::= nexpr | nexpr "-a" aexpr ;
19 nexpr ::= primary | "!" nexpr ;
20 primary ::= unary-operator operand
21 | operand binary-operator operand
26 unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"|
27 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|
30 binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
32 "<"|">" # rules used for [[ .. ]] expressions
34 operand ::= <any thing>
37 #define T_ERR_EXIT 2 /* POSIX says > 1 for errors */
43 static const struct t_op u_ops
[] = {
70 static const struct t_op b_ops
[] = {
88 static int test_stat(const char *, struct stat
*);
89 static int test_eaccess(const char *, int);
90 static int test_oexpr(Test_env
*, int);
91 static int test_aexpr(Test_env
*, int);
92 static int test_nexpr(Test_env
*, int);
93 static int test_primary(Test_env
*, int);
94 static int ptest_isa(Test_env
*, Test_meta
);
95 static const char *ptest_getopnd(Test_env
*, Test_op
, int);
96 static int ptest_eval(Test_env
*, Test_op
, const char *,
98 static void ptest_error(Test_env
*, int, const char *);
109 te
.getopnd
= ptest_getopnd
;
110 te
.eval
= ptest_eval
;
111 te
.error
= ptest_error
;
113 for (argc
= 0; wp
[argc
]; argc
++)
116 if (strcmp(wp
[0], "[") == 0) {
117 if (strcmp(wp
[--argc
], "]") != 0) {
118 bi_errorf("missing ]");
124 te
.wp_end
= wp
+ argc
;
127 * Handle the special cases from POSIX.2, section 4.62.4.
128 * Implementation of all the rules isn't necessary since
129 * our parser does the right thing for the omitted steps.
135 const char *opnd1
, *opnd2
;
137 while (--argc
>= 0) {
138 if ((*te
.isa
)(&te
, TM_END
))
141 opnd1
= (*te
.getopnd
)(&te
, TO_NONOP
, 1);
142 if ((op
= (Test_op
) (*te
.isa
)(&te
, TM_BINOP
))) {
143 opnd2
= (*te
.getopnd
)(&te
, op
, 1);
144 res
= (*te
.eval
)(&te
, op
, opnd1
,
146 if (te
.flags
& TEF_ERROR
)
152 /* back up to opnd1 */
156 opnd1
= (*te
.getopnd
)(&te
, TO_NONOP
, 1);
157 /* Historically, -t by itself test if fd 1
158 * is a file descriptor, but POSIX says its
161 if (!Flag(FPOSIX
) && strcmp(opnd1
, "-t") == 0)
163 res
= (*te
.eval
)(&te
, TO_STNZE
, opnd1
,
169 if ((*te
.isa
)(&te
, TM_NOT
)) {
177 return test_parse(&te
);
181 * Generic test routines.
185 test_isop(Test_env
*te
, Test_meta meta
, const char *s
)
188 const struct t_op
*otab
;
190 otab
= meta
== TM_UNOP
? u_ops
: b_ops
;
193 for (; otab
->op_text
[0]; otab
++)
194 if (sc1
== otab
->op_text
[1] &&
195 strcmp(s
, otab
->op_text
) == 0 &&
196 ((te
->flags
& TEF_DBRACKET
) ||
197 (otab
->op_num
!= TO_STLT
&& otab
->op_num
!= TO_STGT
)))
204 test_eval(Test_env
*te
, Test_op op
, const char *opnd1
, const char *opnd2
,
218 case TO_STNZE
: /* -n */
219 return *opnd1
!= '\0';
220 case TO_STZER
: /* -z */
221 return *opnd1
== '\0';
222 case TO_OPTION
: /* -o */
223 if ((not = *opnd1
== '!'))
225 if ((res
= option(opnd1
)) < 0)
233 case TO_FILRD
: /* -r */
234 return test_eaccess(opnd1
, R_OK
) == 0;
235 case TO_FILWR
: /* -w */
236 return test_eaccess(opnd1
, W_OK
) == 0;
237 case TO_FILEX
: /* -x */
238 return test_eaccess(opnd1
, X_OK
) == 0;
239 case TO_FILAXST
: /* -a */
240 return test_stat(opnd1
, &b1
) == 0;
241 case TO_FILEXST
: /* -e */
242 /* at&t ksh does not appear to do the /dev/fd/ thing for
243 * this (unless the os itself handles it)
245 return stat(opnd1
, &b1
) == 0;
246 case TO_FILREG
: /* -r */
247 return test_stat(opnd1
, &b1
) == 0 && S_ISREG(b1
.st_mode
);
248 case TO_FILID
: /* -d */
249 return test_stat(opnd1
, &b1
) == 0 && S_ISDIR(b1
.st_mode
);
250 case TO_FILCDEV
: /* -c */
251 return test_stat(opnd1
, &b1
) == 0 && S_ISCHR(b1
.st_mode
);
252 case TO_FILBDEV
: /* -b */
253 return test_stat(opnd1
, &b1
) == 0 && S_ISBLK(b1
.st_mode
);
254 case TO_FILFIFO
: /* -p */
255 return test_stat(opnd1
, &b1
) == 0 && S_ISFIFO(b1
.st_mode
);
256 case TO_FILSYM
: /* -h -L */
257 return lstat(opnd1
, &b1
) == 0 && S_ISLNK(b1
.st_mode
);
258 case TO_FILSOCK
: /* -S */
259 return test_stat(opnd1
, &b1
) == 0 && S_ISSOCK(b1
.st_mode
);
260 case TO_FILCDF
:/* -H HP context dependent files (directories) */
262 case TO_FILSETU
: /* -u */
263 return test_stat(opnd1
, &b1
) == 0 &&
264 (b1
.st_mode
& S_ISUID
) == S_ISUID
;
265 case TO_FILSETG
: /* -g */
266 return test_stat(opnd1
, &b1
) == 0 &&
267 (b1
.st_mode
& S_ISGID
) == S_ISGID
;
268 case TO_FILSTCK
: /* -k */
269 return test_stat(opnd1
, &b1
) == 0 &&
270 (b1
.st_mode
& S_ISVTX
) == S_ISVTX
;
271 case TO_FILGZ
: /* -s */
272 return test_stat(opnd1
, &b1
) == 0 && b1
.st_size
> 0L;
273 case TO_FILTT
: /* -t */
274 if (opnd1
&& !bi_getn(opnd1
, &res
)) {
275 te
->flags
|= TEF_ERROR
;
278 /* generate error if in FPOSIX mode? */
279 res
= isatty(opnd1
? res
: 0);
282 case TO_FILUID
: /* -O */
283 return test_stat(opnd1
, &b1
) == 0 && b1
.st_uid
== ksheuid
;
284 case TO_FILGID
: /* -G */
285 return test_stat(opnd1
, &b1
) == 0 && b1
.st_gid
== getegid();
289 case TO_STEQL
: /* = */
290 if (te
->flags
& TEF_DBRACKET
)
291 return gmatch(opnd1
, opnd2
, false);
292 return strcmp(opnd1
, opnd2
) == 0;
293 case TO_STNEQ
: /* != */
294 if (te
->flags
& TEF_DBRACKET
)
295 return !gmatch(opnd1
, opnd2
, false);
296 return strcmp(opnd1
, opnd2
) != 0;
297 case TO_STLT
: /* < */
298 return strcmp(opnd1
, opnd2
) < 0;
299 case TO_STGT
: /* > */
300 return strcmp(opnd1
, opnd2
) > 0;
301 case TO_INTEQ
: /* -eq */
302 case TO_INTNE
: /* -ne */
303 case TO_INTGE
: /* -ge */
304 case TO_INTGT
: /* -gt */
305 case TO_INTLE
: /* -le */
306 case TO_INTLT
: /* -lt */
310 if (!evaluate(opnd1
, &v1
, KSH_RETURN_ERROR
, false) ||
311 !evaluate(opnd2
, &v2
, KSH_RETURN_ERROR
, false)) {
312 /* error already printed.. */
313 te
->flags
|= TEF_ERROR
;
331 case TO_FILNT
: /* -nt */
334 /* ksh88/ksh93 succeed if file2 can't be stated
335 * (subtly different from `does not exist').
337 return stat(opnd1
, &b1
) == 0 &&
338 (((s2
= stat(opnd2
, &b2
)) == 0 &&
339 b1
.st_mtime
> b2
.st_mtime
) || s2
< 0);
341 case TO_FILOT
: /* -ot */
344 /* ksh88/ksh93 succeed if file1 can't be stated
345 * (subtly different from `does not exist').
347 return stat(opnd2
, &b2
) == 0 &&
348 (((s1
= stat(opnd1
, &b1
)) == 0 &&
349 b1
.st_mtime
< b2
.st_mtime
) || s1
< 0);
351 case TO_FILEQ
: /* -ef */
352 return stat (opnd1
, &b1
) == 0 && stat (opnd2
, &b2
) == 0 &&
353 b1
.st_dev
== b2
.st_dev
&& b1
.st_ino
== b2
.st_ino
;
355 (*te
->error
)(te
, 0, "internal error: unknown op");
359 /* Nasty kludge to handle Korn's bizarre /dev/fd hack */
361 test_stat(const char *path
, struct stat
*statb
)
363 return stat(path
, statb
);
366 /* Routine to handle Korn's /dev/fd hack, and to deal with X_OK on
367 * non-directories when running as root.
370 test_eaccess(const char *path
, int mode
)
374 res
= access(path
, mode
);
376 * On most (all?) unixes, access() says everything is executable for
377 * root - avoid this on files by using stat().
379 if (res
== 0 && ksheuid
== 0 && (mode
& X_OK
)) {
382 if (stat(path
, &statb
) < 0)
384 else if (S_ISDIR(statb
.st_mode
))
387 res
= (statb
.st_mode
& (S_IXUSR
|S_IXGRP
|S_IXOTH
)) ?
395 test_parse(Test_env
*te
)
399 res
= test_oexpr(te
, 1);
401 if (!(te
->flags
& TEF_ERROR
) && !(*te
->isa
)(te
, TM_END
))
402 (*te
->error
)(te
, 0, "unexpected operator/operand");
404 return (te
->flags
& TEF_ERROR
) ? T_ERR_EXIT
: !res
;
408 test_oexpr(Test_env
*te
, int do_eval
)
412 res
= test_aexpr(te
, do_eval
);
415 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_OR
))
416 return test_oexpr(te
, do_eval
) || res
;
421 test_aexpr(Test_env
*te
, int do_eval
)
425 res
= test_nexpr(te
, do_eval
);
428 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_AND
))
429 return test_aexpr(te
, do_eval
) && res
;
434 test_nexpr(Test_env
*te
, int do_eval
)
436 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_NOT
))
437 return !test_nexpr(te
, do_eval
);
438 return test_primary(te
, do_eval
);
442 test_primary(Test_env
*te
, int do_eval
)
444 const char *opnd1
, *opnd2
;
448 if (te
->flags
& TEF_ERROR
)
450 if ((*te
->isa
)(te
, TM_OPAREN
)) {
451 res
= test_oexpr(te
, do_eval
);
452 if (te
->flags
& TEF_ERROR
)
454 if (!(*te
->isa
)(te
, TM_CPAREN
)) {
455 (*te
->error
)(te
, 0, "missing closing paren");
461 * Binary should have precedence over unary in this case
462 * so that something like test \( -f = -f \) is accepted
464 if ((te
->flags
& TEF_DBRACKET
) || (&te
->pos
.wp
[1] < te
->wp_end
&&
465 !test_isop(te
, TM_BINOP
, te
->pos
.wp
[1]))) {
466 if ((op
= (Test_op
) (*te
->isa
)(te
, TM_UNOP
))) {
467 /* unary expression */
468 opnd1
= (*te
->getopnd
)(te
, op
, do_eval
);
470 (*te
->error
)(te
, -1, "missing argument");
474 return (*te
->eval
)(te
, op
, opnd1
, (const char *) 0,
478 opnd1
= (*te
->getopnd
)(te
, TO_NONOP
, do_eval
);
480 (*te
->error
)(te
, 0, "expression expected");
483 if ((op
= (Test_op
) (*te
->isa
)(te
, TM_BINOP
))) {
484 /* binary expression */
485 opnd2
= (*te
->getopnd
)(te
, op
, do_eval
);
487 (*te
->error
)(te
, -1, "missing second argument");
491 return (*te
->eval
)(te
, op
, opnd1
, opnd2
, do_eval
);
493 if (te
->flags
& TEF_DBRACKET
) {
494 (*te
->error
)(te
, -1, "missing expression operator");
497 return (*te
->eval
)(te
, TO_STNZE
, opnd1
, (const char *) 0, do_eval
);
501 * Plain test (test and [ .. ]) specific routines.
504 /* Test if the current token is a whatever. Accepts the current token if
505 * it is. Returns 0 if it is not, non-zero if it is (in the case of
506 * TM_UNOP and TM_BINOP, the returned value is a Test_op).
509 ptest_isa(Test_env
*te
, Test_meta meta
)
511 /* Order important - indexed by Test_meta values */
512 static const char *const tokens
[] = {
513 "-o", "-a", "!", "(", ")"
517 if (te
->pos
.wp
>= te
->wp_end
)
518 return meta
== TM_END
;
520 if (meta
== TM_UNOP
|| meta
== TM_BINOP
)
521 ret
= (int) test_isop(te
, meta
, *te
->pos
.wp
);
522 else if (meta
== TM_END
)
525 ret
= strcmp(*te
->pos
.wp
, tokens
[(int) meta
]) == 0;
527 /* Accept the token? */
535 ptest_getopnd(Test_env
*te
, Test_op op
, int do_eval
)
537 if (te
->pos
.wp
>= te
->wp_end
)
538 return op
== TO_FILTT
? "1" : (const char *) 0;
539 return *te
->pos
.wp
++;
543 ptest_eval(Test_env
*te
, Test_op op
, const char *opnd1
, const char *opnd2
,
546 return test_eval(te
, op
, opnd1
, opnd2
, do_eval
);
550 ptest_error(Test_env
*te
, int offset
, const char *msg
)
552 const char *op
= te
->pos
.wp
+ offset
>= te
->wp_end
?
553 (const char *) 0 : te
->pos
.wp
[offset
];
555 te
->flags
|= TEF_ERROR
;
557 bi_errorf("%s: %s", op
, msg
);
559 bi_errorf("%s", msg
);