1 /* $OpenBSD: c_test.c,v 1.25 2018/04/09 17:53:36 tobias 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.
20 /* test(1) accepts the following grammar:
21 oexpr ::= aexpr | aexpr "-o" oexpr ;
22 aexpr ::= nexpr | nexpr "-a" aexpr ;
23 nexpr ::= primary | "!" nexpr ;
24 primary ::= unary-operator operand
25 | operand binary-operator operand
30 unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"|
31 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|
34 binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
36 "<"|">" # rules used for [[ .. ]] expressions
38 operand ::= <any thing>
41 #define T_ERR_EXIT 2 /* POSIX says > 1 for errors */
47 static const struct t_op u_ops
[] = {
74 static const struct t_op b_ops
[] = {
92 static int test_eaccess(const char *, int);
93 static int test_oexpr(Test_env
*, int);
94 static int test_aexpr(Test_env
*, int);
95 static int test_nexpr(Test_env
*, int);
96 static int test_primary(Test_env
*, int);
97 static int ptest_isa(Test_env
*, Test_meta
);
98 static const char *ptest_getopnd(Test_env
*, Test_op
, int);
99 static int ptest_eval(Test_env
*, Test_op
, const char *,
101 static void ptest_error(Test_env
*, int, const char *);
112 te
.getopnd
= ptest_getopnd
;
113 te
.eval
= ptest_eval
;
114 te
.error
= ptest_error
;
116 for (argc
= 0; wp
[argc
]; argc
++)
119 if (strcmp(wp
[0], "[") == 0) {
120 if (strcmp(wp
[--argc
], "]") != 0) {
121 bi_errorf("missing ]");
127 te
.wp_end
= wp
+ argc
;
130 * Handle the special cases from POSIX.2, section 4.62.4.
131 * Implementation of all the rules isn't necessary since
132 * our parser does the right thing for the omitted steps.
138 const char *opnd1
, *opnd2
;
140 while (--argc
>= 0) {
141 if ((*te
.isa
)(&te
, TM_END
))
144 opnd1
= (*te
.getopnd
)(&te
, TO_NONOP
, 1);
145 if ((op
= (Test_op
) (*te
.isa
)(&te
, TM_BINOP
))) {
146 opnd2
= (*te
.getopnd
)(&te
, op
, 1);
147 res
= (*te
.eval
)(&te
, op
, opnd1
,
149 if (te
.flags
& TEF_ERROR
)
155 /* back up to opnd1 */
159 opnd1
= (*te
.getopnd
)(&te
, TO_NONOP
, 1);
160 /* Historically, -t by itself test if fd 1
161 * is a file descriptor, but POSIX says its
164 if (!Flag(FPOSIX
) && strcmp(opnd1
, "-t") == 0)
166 res
= (*te
.eval
)(&te
, TO_STNZE
, opnd1
,
172 if ((*te
.isa
)(&te
, TM_NOT
)) {
180 return test_parse(&te
);
184 * Generic test routines.
188 test_isop(Test_env
*te
, Test_meta meta
, const char *s
)
191 const struct t_op
*otab
;
193 otab
= meta
== TM_UNOP
? u_ops
: b_ops
;
196 for (; otab
->op_text
[0]; otab
++)
197 if (sc1
== otab
->op_text
[1] &&
198 strcmp(s
, otab
->op_text
) == 0 &&
199 ((te
->flags
& TEF_DBRACKET
) ||
200 (otab
->op_num
!= TO_STLT
&& otab
->op_num
!= TO_STGT
)))
207 test_eval(Test_env
*te
, Test_op op
, const char *opnd1
, const char *opnd2
,
221 case TO_STNZE
: /* -n */
222 return *opnd1
!= '\0';
223 case TO_STZER
: /* -z */
224 return *opnd1
== '\0';
225 case TO_OPTION
: /* -o */
226 if ((not = *opnd1
== '!'))
228 if ((res
= option(opnd1
)) < 0)
236 case TO_FILRD
: /* -r */
237 return test_eaccess(opnd1
, R_OK
) == 0;
238 case TO_FILWR
: /* -w */
239 return test_eaccess(opnd1
, W_OK
) == 0;
240 case TO_FILEX
: /* -x */
241 return test_eaccess(opnd1
, X_OK
) == 0;
242 case TO_FILAXST
: /* -a */
243 return stat(opnd1
, &b1
) == 0;
244 case TO_FILEXST
: /* -e */
245 /* at&t ksh does not appear to do the /dev/fd/ thing for
246 * this (unless the os itself handles it)
248 return stat(opnd1
, &b1
) == 0;
249 case TO_FILREG
: /* -r */
250 return stat(opnd1
, &b1
) == 0 && S_ISREG(b1
.st_mode
);
251 case TO_FILID
: /* -d */
252 return stat(opnd1
, &b1
) == 0 && S_ISDIR(b1
.st_mode
);
253 case TO_FILCDEV
: /* -c */
254 return stat(opnd1
, &b1
) == 0 && S_ISCHR(b1
.st_mode
);
255 case TO_FILBDEV
: /* -b */
256 return stat(opnd1
, &b1
) == 0 && S_ISBLK(b1
.st_mode
);
257 case TO_FILFIFO
: /* -p */
258 return stat(opnd1
, &b1
) == 0 && S_ISFIFO(b1
.st_mode
);
259 case TO_FILSYM
: /* -h -L */
260 return lstat(opnd1
, &b1
) == 0 && S_ISLNK(b1
.st_mode
);
261 case TO_FILSOCK
: /* -S */
262 return stat(opnd1
, &b1
) == 0 && S_ISSOCK(b1
.st_mode
);
263 case TO_FILCDF
:/* -H HP context dependent files (directories) */
265 case TO_FILSETU
: /* -u */
266 return stat(opnd1
, &b1
) == 0 &&
267 (b1
.st_mode
& S_ISUID
) == S_ISUID
;
268 case TO_FILSETG
: /* -g */
269 return stat(opnd1
, &b1
) == 0 &&
270 (b1
.st_mode
& S_ISGID
) == S_ISGID
;
271 case TO_FILSTCK
: /* -k */
272 return stat(opnd1
, &b1
) == 0 &&
273 (b1
.st_mode
& S_ISVTX
) == S_ISVTX
;
274 case TO_FILGZ
: /* -s */
275 return stat(opnd1
, &b1
) == 0 && b1
.st_size
> 0L;
276 case TO_FILTT
: /* -t */
277 if (opnd1
&& !bi_getn(opnd1
, &res
)) {
278 te
->flags
|= TEF_ERROR
;
281 /* generate error if in FPOSIX mode? */
282 res
= isatty(opnd1
? res
: 0);
285 case TO_FILUID
: /* -O */
286 return stat(opnd1
, &b1
) == 0 && b1
.st_uid
== ksheuid
;
287 case TO_FILGID
: /* -G */
288 return stat(opnd1
, &b1
) == 0 && b1
.st_gid
== getegid();
292 case TO_STEQL
: /* = */
293 if (te
->flags
& TEF_DBRACKET
)
294 return gmatch(opnd1
, opnd2
, false);
295 return strcmp(opnd1
, opnd2
) == 0;
296 case TO_STNEQ
: /* != */
297 if (te
->flags
& TEF_DBRACKET
)
298 return !gmatch(opnd1
, opnd2
, false);
299 return strcmp(opnd1
, opnd2
) != 0;
300 case TO_STLT
: /* < */
301 return strcmp(opnd1
, opnd2
) < 0;
302 case TO_STGT
: /* > */
303 return strcmp(opnd1
, opnd2
) > 0;
304 case TO_INTEQ
: /* -eq */
305 case TO_INTNE
: /* -ne */
306 case TO_INTGE
: /* -ge */
307 case TO_INTGT
: /* -gt */
308 case TO_INTLE
: /* -le */
309 case TO_INTLT
: /* -lt */
313 if (!evaluate(opnd1
, &v1
, KSH_RETURN_ERROR
, false) ||
314 !evaluate(opnd2
, &v2
, KSH_RETURN_ERROR
, false)) {
315 /* error already printed.. */
316 te
->flags
|= TEF_ERROR
;
334 case TO_FILNT
: /* -nt */
337 /* ksh88/ksh93 succeed if file2 can't be stated
338 * (subtly different from `does not exist').
340 return stat(opnd1
, &b1
) == 0 &&
341 (((s2
= stat(opnd2
, &b2
)) == 0 &&
342 b1
.st_mtime
> b2
.st_mtime
) || s2
< 0);
344 case TO_FILOT
: /* -ot */
347 /* ksh88/ksh93 succeed if file1 can't be stated
348 * (subtly different from `does not exist').
350 return stat(opnd2
, &b2
) == 0 &&
351 (((s1
= stat(opnd1
, &b1
)) == 0 &&
352 b1
.st_mtime
< b2
.st_mtime
) || s1
< 0);
354 case TO_FILEQ
: /* -ef */
355 return stat (opnd1
, &b1
) == 0 && stat (opnd2
, &b2
) == 0 &&
356 b1
.st_dev
== b2
.st_dev
&& b1
.st_ino
== b2
.st_ino
;
358 (*te
->error
)(te
, 0, "internal error: unknown op");
362 /* Routine to deal with X_OK on non-directories when running as root.
365 test_eaccess(const char *path
, int amode
)
369 res
= access(path
, amode
);
371 * On most (all?) unixes, access() says everything is executable for
372 * root - avoid this on files by using stat().
374 if (res
== 0 && ksheuid
== 0 && (amode
& X_OK
)) {
377 if (stat(path
, &statb
) < 0)
379 else if (S_ISDIR(statb
.st_mode
))
382 res
= (statb
.st_mode
& (S_IXUSR
|S_IXGRP
|S_IXOTH
)) ?
390 test_parse(Test_env
*te
)
394 res
= test_oexpr(te
, 1);
396 if (!(te
->flags
& TEF_ERROR
) && !(*te
->isa
)(te
, TM_END
))
397 (*te
->error
)(te
, 0, "unexpected operator/operand");
399 return (te
->flags
& TEF_ERROR
) ? T_ERR_EXIT
: !res
;
403 test_oexpr(Test_env
*te
, int do_eval
)
407 res
= test_aexpr(te
, do_eval
);
410 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_OR
))
411 return test_oexpr(te
, do_eval
) || res
;
416 test_aexpr(Test_env
*te
, int do_eval
)
420 res
= test_nexpr(te
, do_eval
);
423 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_AND
))
424 return test_aexpr(te
, do_eval
) && res
;
429 test_nexpr(Test_env
*te
, int do_eval
)
431 if (!(te
->flags
& TEF_ERROR
) && (*te
->isa
)(te
, TM_NOT
))
432 return !test_nexpr(te
, do_eval
);
433 return test_primary(te
, do_eval
);
437 test_primary(Test_env
*te
, int do_eval
)
439 const char *opnd1
, *opnd2
;
443 if (te
->flags
& TEF_ERROR
)
445 if ((*te
->isa
)(te
, TM_OPAREN
)) {
446 res
= test_oexpr(te
, do_eval
);
447 if (te
->flags
& TEF_ERROR
)
449 if (!(*te
->isa
)(te
, TM_CPAREN
)) {
450 (*te
->error
)(te
, 0, "missing closing paren");
456 * Binary should have precedence over unary in this case
457 * so that something like test \( -f = -f \) is accepted
459 if ((te
->flags
& TEF_DBRACKET
) || (&te
->pos
.wp
[1] < te
->wp_end
&&
460 !test_isop(te
, TM_BINOP
, te
->pos
.wp
[1]))) {
461 if ((op
= (Test_op
) (*te
->isa
)(te
, TM_UNOP
))) {
462 /* unary expression */
463 opnd1
= (*te
->getopnd
)(te
, op
, do_eval
);
465 (*te
->error
)(te
, -1, "missing argument");
469 return (*te
->eval
)(te
, op
, opnd1
, NULL
,
473 opnd1
= (*te
->getopnd
)(te
, TO_NONOP
, do_eval
);
475 (*te
->error
)(te
, 0, "expression expected");
478 if ((op
= (Test_op
) (*te
->isa
)(te
, TM_BINOP
))) {
479 /* binary expression */
480 opnd2
= (*te
->getopnd
)(te
, op
, do_eval
);
482 (*te
->error
)(te
, -1, "missing second argument");
486 return (*te
->eval
)(te
, op
, opnd1
, opnd2
, do_eval
);
488 if (te
->flags
& TEF_DBRACKET
) {
489 (*te
->error
)(te
, -1, "missing expression operator");
492 return (*te
->eval
)(te
, TO_STNZE
, opnd1
, NULL
, do_eval
);
496 * Plain test (test and [ .. ]) specific routines.
499 /* Test if the current token is a whatever. Accepts the current token if
500 * it is. Returns 0 if it is not, non-zero if it is (in the case of
501 * TM_UNOP and TM_BINOP, the returned value is a Test_op).
504 ptest_isa(Test_env
*te
, Test_meta meta
)
506 /* Order important - indexed by Test_meta values */
507 static const char *const tokens
[] = {
508 "-o", "-a", "!", "(", ")"
512 if (te
->pos
.wp
>= te
->wp_end
)
513 return meta
== TM_END
;
515 if (meta
== TM_UNOP
|| meta
== TM_BINOP
)
516 ret
= (int) test_isop(te
, meta
, *te
->pos
.wp
);
517 else if (meta
== TM_END
)
520 ret
= strcmp(*te
->pos
.wp
, tokens
[(int) meta
]) == 0;
522 /* Accept the token? */
530 ptest_getopnd(Test_env
*te
, Test_op op
, int do_eval
)
532 if (te
->pos
.wp
>= te
->wp_end
)
533 return op
== TO_FILTT
? "1" : NULL
;
534 return *te
->pos
.wp
++;
538 ptest_eval(Test_env
*te
, Test_op op
, const char *opnd1
, const char *opnd2
,
541 return test_eval(te
, op
, opnd1
, opnd2
, do_eval
);
545 ptest_error(Test_env
*te
, int offset
, const char *msg
)
547 const char *op
= te
->pos
.wp
+ offset
>= te
->wp_end
?
548 NULL
: te
->pos
.wp
[offset
];
550 te
->flags
|= TEF_ERROR
;
552 bi_errorf("%s: %s", op
, msg
);
554 bi_errorf("%s", msg
);