]> git.ipfire.org Git - thirdparty/bash.git/blame - test.c
changes for PATH being the empty string; more minor asan fixes
[thirdparty/bash.git] / test.c
CommitLineData
2e4498b3 1/* test.c - GNU test program (ksb and mjb) */
726f6388
JA
2
3/* Modified to run with the GNU shell Apr 25, 1988 by bfox. */
4
b2613ad1 5/* Copyright (C) 1987-2023 Free Software Foundation, Inc.
726f6388
JA
6
7 This file is part of GNU Bash, the Bourne Again SHell.
8
2e4498b3
CR
9 Bash is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
726f6388 13
2e4498b3
CR
14 Bash is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
726f6388 18
2e4498b3
CR
19 You should have received a copy of the GNU General Public License
20 along with Bash. If not, see <http://www.gnu.org/licenses/>.
21*/
726f6388 22
ccc6cda3
JA
23/* Define PATTERN_MATCHING to get the csh-like =~ and !~ pattern-matching
24 binary operators. */
25/* #define PATTERN_MATCHING */
26
27#if defined (HAVE_CONFIG_H)
28# include <config.h>
29#endif
30
726f6388 31#include <stdio.h>
ccc6cda3 32
d166f048 33#include "bashtypes.h"
ccc6cda3 34
fd58d46e 35#if !defined (HAVE_LIMITS_H) && defined (HAVE_SYS_PARAM_H)
ccc6cda3
JA
36# include <sys/param.h>
37#endif
38
39#if defined (HAVE_UNISTD_H)
40# include <unistd.h>
41#endif
42
e8ce775d
JA
43#include <errno.h>
44#if !defined (errno)
45extern int errno;
46#endif /* !errno */
47
d3a24ed2 48#if !defined (_POSIX_VERSION) && defined (HAVE_SYS_FILE_H)
ccc6cda3
JA
49# include <sys/file.h>
50#endif /* !_POSIX_VERSION */
51#include "posixstat.h"
52#include "filecntl.h"
278286c9 53#include "stat-time.h"
726f6388 54
5e13499c
CR
55#include "bashintl.h"
56
d166f048 57#include "shell.h"
cce855bc
JA
58#include "pathexp.h"
59#include "test.h"
d166f048 60#include "builtins/common.h"
726f6388 61
f73dda09 62#include <glob/strmatch.h>
cce855bc 63
726f6388
JA
64#if !defined (STRLEN)
65# define STRLEN(s) ((s)[0] ? ((s)[1] ? ((s)[2] ? strlen(s) : 2) : 1) : 0)
66#endif
67
726f6388 68#if !defined (STREQ)
a8fd3f3e 69# define STREQ(a, b) ((a)[0] == (b)[0] && strcmp ((a), (b)) == 0)
726f6388 70#endif /* !STREQ */
a8fd3f3e 71#define STRCOLLEQ(a, b) ((a)[0] == (b)[0] && strcoll ((a), (b)) == 0)
726f6388 72
726f6388
JA
73#if !defined (R_OK)
74#define R_OK 4
75#define W_OK 2
76#define X_OK 1
77#define F_OK 0
78#endif /* R_OK */
79
ccc6cda3
JA
80#define EQ 0
81#define NE 1
82#define LT 2
83#define GT 3
84#define LE 4
85#define GE 5
86
87#define NT 0
88#define OT 1
89#define EF 2
90
726f6388
JA
91/* The following few defines control the truth and false output of each stage.
92 TRUE and FALSE are what we use to compute the final output value.
93 SHELL_BOOLEAN is the form which returns truth or falseness in shell terms.
ccc6cda3 94 Default is TRUE = 1, FALSE = 0, SHELL_BOOLEAN = (!value). */
726f6388
JA
95#define TRUE 1
96#define FALSE 0
97#define SHELL_BOOLEAN(value) (!(value))
726f6388 98
b72432fd
JA
99#define TEST_ERREXIT_STATUS 2
100
ccc6cda3
JA
101static procenv_t test_exit_buf;
102static int test_error_return;
d166f048 103#define test_exit(val) \
de608191 104 do { test_error_return = val; sh_longjmp (test_exit_buf, 1); } while (0)
726f6388 105
a61ffa78 106extern int sh_stat (const char *, struct stat *);
726f6388
JA
107
108static int pos; /* The offset of the current argument in ARGV. */
109static int argc; /* The number of arguments present in ARGV. */
110static char **argv; /* The argument list. */
111static int noeval;
112
a61ffa78
CR
113static void test_syntax_error (char *, char *) __attribute__((__noreturn__));
114static void beyond (void) __attribute__((__noreturn__));
115static void integer_expected_error (char *) __attribute__((__noreturn__));
f73dda09 116
a61ffa78
CR
117static int unary_operator (void);
118static int binary_operator (void);
119static int two_arguments (void);
120static int three_arguments (void);
121static int posixtest (void);
726f6388 122
a61ffa78
CR
123static int expr (void);
124static int term (void);
125static int and (void);
126static int or (void);
726f6388 127
b2613ad1 128static int filecomp (const char *, const char *, int);
a61ffa78
CR
129static int arithcomp (char *, char *, int, int);
130static int patcomp (char *, char *, int);
ccc6cda3 131
726f6388 132static void
a61ffa78 133test_syntax_error (char *format, char *arg)
726f6388 134{
7117c2d2 135 builtin_error (format, arg);
b72432fd 136 test_exit (TEST_ERREXIT_STATUS);
726f6388
JA
137}
138
cce855bc
JA
139/*
140 * beyond - call when we're beyond the end of the argument list (an
141 * error condition)
142 */
143static void
a61ffa78 144beyond (void)
cce855bc 145{
5e13499c 146 test_syntax_error (_("argument expected"), (char *)NULL);
cce855bc
JA
147}
148
149/* Syntax error for when an integer argument was expected, but
150 something else was found. */
151static void
a61ffa78 152integer_expected_error (char *pch)
cce855bc 153{
96115811 154 test_syntax_error (_("%s: integer expected"), pch);
cce855bc
JA
155}
156
726f6388 157/* Increment our position in the argument list. Check that we're not
39feef01 158 past the end of the argument list. This check is suppressed if the
726f6388 159 argument is FALSE. Made a macro for efficiency. */
726f6388 160#define advance(f) do { ++pos; if (f && pos >= argc) beyond (); } while (0)
726f6388
JA
161#define unary_advance() do { advance (1); ++pos; } while (0)
162
163/*
cce855bc
JA
164 * expr:
165 * or
726f6388 166 */
cce855bc 167static int
a61ffa78 168expr (void)
726f6388 169{
cce855bc
JA
170 if (pos >= argc)
171 beyond ();
172
173 return (FALSE ^ or ()); /* Same with this. */
726f6388
JA
174}
175
cce855bc
JA
176/*
177 * or:
178 * and
179 * and '-o' or
180 */
181static int
a61ffa78 182or (void)
726f6388 183{
cce855bc
JA
184 int value, v2;
185
186 value = and ();
f73dda09 187 if (pos < argc && argv[pos][0] == '-' && argv[pos][1] == 'o' && !argv[pos][2])
cce855bc
JA
188 {
189 advance (0);
190 v2 = or ();
191 return (value || v2);
192 }
193
194 return (value);
195}
196
197/*
198 * and:
199 * term
200 * term '-a' and
201 */
202static int
a61ffa78 203and (void)
cce855bc
JA
204{
205 int value, v2;
206
207 value = term ();
f73dda09 208 if (pos < argc && argv[pos][0] == '-' && argv[pos][1] == 'a' && !argv[pos][2])
cce855bc
JA
209 {
210 advance (0);
211 v2 = and ();
212 return (value && v2);
213 }
214 return (value);
726f6388
JA
215}
216
726f6388
JA
217/*
218 * term - parse a term and return 1 or 0 depending on whether the term
219 * evaluates to true or false, respectively.
220 *
221 * term ::=
cce855bc
JA
222 * '-'('a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'k'|'p'|'r'|'s'|'u'|'w'|'x') filename
223 * '-'('G'|'L'|'O'|'S'|'N') filename
ccc6cda3 224 * '-t' [int]
726f6388 225 * '-'('z'|'n') string
c4c90ef8 226 * '-'('v'|'R') varname
cce855bc 227 * '-o' option
726f6388 228 * string
ccc6cda3 229 * string ('!='|'='|'==') string
726f6388
JA
230 * <int> '-'(eq|ne|le|lt|ge|gt) <int>
231 * file '-'(nt|ot|ef) file
232 * '(' <expr> ')'
233 * int ::=
726f6388
JA
234 * positive and negative integers
235 */
236static int
a61ffa78 237term (void)
726f6388
JA
238{
239 int value;
240
241 if (pos >= argc)
242 beyond ();
243
ccc6cda3
JA
244 /* Deal with leading `not's. */
245 if (argv[pos][0] == '!' && argv[pos][1] == '\0')
726f6388 246 {
ccc6cda3
JA
247 value = 0;
248 while (pos < argc && argv[pos][0] == '!' && argv[pos][1] == '\0')
726f6388
JA
249 {
250 advance (1);
ccc6cda3 251 value = 1 - value;
726f6388
JA
252 }
253
ccc6cda3 254 return (value ? !term() : term());
726f6388
JA
255 }
256
ccc6cda3 257 /* A paren-bracketed argument. */
5e13499c 258 if (argv[pos][0] == '(' && argv[pos][1] == '\0') /* ) */
726f6388
JA
259 {
260 advance (1);
261 value = expr ();
5e13499c
CR
262 if (argv[pos] == 0) /* ( */
263 test_syntax_error (_("`)' expected"), (char *)NULL);
264 else if (argv[pos][0] != ')' || argv[pos][1]) /* ( */
265 test_syntax_error (_("`)' expected, found %s"), argv[pos]);
726f6388 266 advance (0);
ccc6cda3 267 return (value);
726f6388
JA
268 }
269
270 /* are there enough arguments left that this could be dyadic? */
cce855bc 271 if ((pos + 3 <= argc) && test_binop (argv[pos + 1]))
726f6388
JA
272 value = binary_operator ();
273
48394b08
CR
274 /* Might be a switch type argument -- make sure we have enough arguments for
275 the unary operator and argument */
276 else if ((pos + 2) <= argc && test_unop (argv[pos]))
277 value = unary_operator ();
278
726f6388
JA
279 else
280 {
ccc6cda3 281 value = argv[pos][0] != '\0';
726f6388
JA
282 advance (0);
283 }
284
285 return (value);
286}
287
278286c9 288static int
b2613ad1 289stat_mtime (const char *fn, struct stat *st, struct timespec *ts)
278286c9
CR
290{
291 int r;
292
293 r = sh_stat (fn, st);
294 if (r < 0)
295 return r;
296 *ts = get_stat_mtime (st);
297 return 0;
298}
299
726f6388 300static int
b2613ad1 301filecomp (const char *s, const char *t, int op)
726f6388 302{
ccc6cda3 303 struct stat st1, st2;
278286c9 304 struct timespec ts1, ts2;
7117c2d2 305 int r1, r2;
726f6388 306
278286c9 307 if ((r1 = stat_mtime (s, &st1, &ts1)) < 0)
28ef6c31 308 {
28ef6c31
JA
309 if (op == EF)
310 return (FALSE);
311 }
278286c9 312 if ((r2 = stat_mtime (t, &st2, &ts2)) < 0)
28ef6c31 313 {
28ef6c31
JA
314 if (op == EF)
315 return (FALSE);
316 }
317
ccc6cda3 318 switch (op)
726f6388 319 {
278286c9
CR
320 case OT: return (r1 < r2 || (r2 == 0 && timespec_cmp (ts1, ts2) < 0));
321 case NT: return (r1 > r2 || (r1 == 0 && timespec_cmp (ts1, ts2) > 0));
e77a3058 322 case EF: return (same_file (s, t, &st1, &st2));
ccc6cda3
JA
323 }
324 return (FALSE);
325}
726f6388 326
ccc6cda3 327static int
a61ffa78 328arithcomp (char *s, char *t, int op, int flags)
ccc6cda3 329{
7117c2d2 330 intmax_t l, r;
cce855bc
JA
331 int expok;
332
35bc7025 333 if (flags & TEST_ARITHEXP) /* conditional command */
cce855bc 334 {
35bc7025
CR
335 int eflag;
336
35bc7025 337 eflag = (shell_compatibility_level > 51) ? 0 : EXP_EXPANDED;
35bc7025 338 l = evalexp (s, eflag, &expok);
cce855bc
JA
339 if (expok == 0)
340 return (FALSE); /* should probably longjmp here */
35bc7025 341 r = evalexp (t, eflag, &expok);
cce855bc
JA
342 if (expok == 0)
343 return (FALSE); /* ditto */
344 }
345 else
346 {
347 if (legal_number (s, &l) == 0)
348 integer_expected_error (s);
349 if (legal_number (t, &r) == 0)
350 integer_expected_error (t);
351 }
726f6388 352
ccc6cda3
JA
353 switch (op)
354 {
355 case EQ: return (l == r);
356 case NE: return (l != r);
357 case LT: return (l < r);
358 case GT: return (l > r);
359 case LE: return (l <= r);
360 case GE: return (l >= r);
726f6388 361 }
cce855bc 362
ccc6cda3
JA
363 return (FALSE);
364}
365
ccc6cda3 366static int
a61ffa78 367patcomp (char *string, char *pat, int op)
ccc6cda3
JA
368{
369 int m;
370
2206f89a 371 m = strmatch (pat, string, FNMATCH_EXTFLAG|FNMATCH_IGNCASE);
cce855bc
JA
372 return ((op == EQ) ? (m == 0) : (m != 0));
373}
374
375int
a61ffa78 376binary_test (char *op, char *arg1, char *arg2, int flags)
cce855bc
JA
377{
378 int patmatch;
379
380 patmatch = (flags & TEST_PATMATCH);
381
382 if (op[0] == '=' && (op[1] == '\0' || (op[1] == '=' && op[2] == '\0')))
383 return (patmatch ? patcomp (arg1, arg2, EQ) : STREQ (arg1, arg2));
cce855bc 384 else if ((op[0] == '>' || op[0] == '<') && op[1] == '\0')
a8fd3f3e 385 {
10e78433 386#if defined (HAVE_STRCOLL)
407d9afc 387 if (shell_compatibility_level > 40 && (flags & TEST_LOCALE))
a8fd3f3e
CR
388 return ((op[0] == '>') ? (strcoll (arg1, arg2) > 0) : (strcoll (arg1, arg2) < 0));
389 else
10e78433 390#endif
a8fd3f3e
CR
391 return ((op[0] == '>') ? (strcmp (arg1, arg2) > 0) : (strcmp (arg1, arg2) < 0));
392 }
cce855bc
JA
393 else if (op[0] == '!' && op[1] == '=' && op[2] == '\0')
394 return (patmatch ? patcomp (arg1, arg2, NE) : (STREQ (arg1, arg2) == 0));
a8fd3f3e 395
cce855bc
JA
396
397 else if (op[2] == 't')
398 {
399 switch (op[1])
400 {
28ef6c31
JA
401 case 'n': return (filecomp (arg1, arg2, NT)); /* -nt */
402 case 'o': return (filecomp (arg1, arg2, OT)); /* -ot */
cce855bc
JA
403 case 'l': return (arithcomp (arg1, arg2, LT, flags)); /* -lt */
404 case 'g': return (arithcomp (arg1, arg2, GT, flags)); /* -gt */
405 }
406 }
407 else if (op[1] == 'e')
726f6388 408 {
cce855bc
JA
409 switch (op[2])
410 {
411 case 'f': return (filecomp (arg1, arg2, EF)); /* -ef */
412 case 'q': return (arithcomp (arg1, arg2, EQ, flags)); /* -eq */
413 }
414 }
415 else if (op[2] == 'e')
416 {
417 switch (op[1])
418 {
419 case 'n': return (arithcomp (arg1, arg2, NE, flags)); /* -ne */
420 case 'g': return (arithcomp (arg1, arg2, GE, flags)); /* -ge */
421 case 'l': return (arithcomp (arg1, arg2, LE, flags)); /* -le */
422 }
726f6388 423 }
cce855bc
JA
424
425 return (FALSE); /* should never get here */
ccc6cda3 426}
cce855bc 427
ccc6cda3 428static int
a61ffa78 429binary_operator (void)
ccc6cda3
JA
430{
431 int value;
432 char *w;
433
434 w = argv[pos + 1];
cce855bc
JA
435 if ((w[0] == '=' && (w[1] == '\0' || (w[1] == '=' && w[2] == '\0'))) || /* =, == */
436 ((w[0] == '>' || w[0] == '<') && w[1] == '\0') || /* <, > */
437 (w[0] == '!' && w[1] == '=' && w[2] == '\0')) /* != */
726f6388 438 {
407d9afc
CR
439#if 0 /* TAG: bash-5.3 POSIX interp 375 11/9/2022 */
440 value = binary_test (w, argv[pos], argv[pos + 2], (posixly_correct ? TEST_LOCALE : 0));
441#else
cce855bc 442 value = binary_test (w, argv[pos], argv[pos + 2], 0);
407d9afc 443#endif
ccc6cda3
JA
444 pos += 3;
445 return (value);
446 }
cce855bc 447
ccc6cda3
JA
448#if defined (PATTERN_MATCHING)
449 if ((w[0] == '=' || w[0] == '!') && w[1] == '~' && w[2] == '\0')
450 {
451 value = patcomp (argv[pos], argv[pos + 2], w[0] == '=' ? EQ : NE);
452 pos += 3;
453 return (value);
454 }
455#endif
ccc6cda3 456
cce855bc 457 if ((w[0] != '-' || w[3] != '\0') || test_binop (w) == 0)
ccc6cda3 458 {
5e13499c 459 test_syntax_error (_("%s: binary operator expected"), w);
ccc6cda3
JA
460 /* NOTREACHED */
461 return (FALSE);
726f6388 462 }
726f6388 463
cce855bc 464 value = binary_test (w, argv[pos], argv[pos + 2], 0);
ccc6cda3
JA
465 pos += 3;
466 return value;
726f6388
JA
467}
468
469static int
a61ffa78 470unary_operator (void)
726f6388 471{
f73dda09 472 char *op;
7117c2d2 473 intmax_t r;
726f6388 474
cce855bc
JA
475 op = argv[pos];
476 if (test_unop (op) == 0)
477 return (FALSE);
478
479 /* the only tricky case is `-t', which may or may not take an argument. */
480 if (op[1] == 't')
726f6388 481 {
cce855bc 482 advance (0);
28ef6c31 483 if (pos < argc)
cce855bc 484 {
28ef6c31
JA
485 if (legal_number (argv[pos], &r))
486 {
487 advance (0);
35bc7025 488 return (unary_test (op, argv[pos - 1], 0));
28ef6c31
JA
489 }
490 else
491 return (FALSE);
cce855bc
JA
492 }
493 else
35bc7025 494 return (unary_test (op, "1", 0));
cce855bc 495 }
726f6388 496
cce855bc
JA
497 /* All of the unary operators take an argument, so we first call
498 unary_advance (), which checks to make sure that there is an
499 argument, and then advances pos right past it. This means that
500 pos - 1 is the location of the argument. */
501 unary_advance ();
35bc7025 502 return (unary_test (op, argv[pos - 1], 0));
cce855bc 503}
726f6388 504
cce855bc 505int
a61ffa78 506unary_test (char *op, char *arg, int flags)
cce855bc 507{
7117c2d2 508 intmax_t r;
cce855bc 509 struct stat stat_buf;
e2169ae9 510 struct timespec mtime, atime;
6faad625 511 SHELL_VAR *v;
7b024db8 512 int aflags;
cce855bc
JA
513
514 switch (op[1])
515 {
726f6388
JA
516 case 'a': /* file exists in the file system? */
517 case 'e':
ac18b312 518 return (sh_stat (arg, &stat_buf) == 0);
726f6388
JA
519
520 case 'r': /* file is readable? */
ac18b312 521 return (sh_eaccess (arg, R_OK) == 0);
726f6388
JA
522
523 case 'w': /* File is writeable? */
ac18b312 524 return (sh_eaccess (arg, W_OK) == 0);
726f6388
JA
525
526 case 'x': /* File is executable? */
ac18b312 527 return (sh_eaccess (arg, X_OK) == 0);
726f6388
JA
528
529 case 'O': /* File is owned by you? */
ac18b312 530 return (sh_stat (arg, &stat_buf) == 0 &&
d166f048 531 (uid_t) current_user.euid == (uid_t) stat_buf.st_uid);
726f6388
JA
532
533 case 'G': /* File is owned by your group? */
ac18b312 534 return (sh_stat (arg, &stat_buf) == 0 &&
d166f048 535 (gid_t) current_user.egid == (gid_t) stat_buf.st_gid);
726f6388 536
cce855bc 537 case 'N':
e2169ae9
CR
538 if (sh_stat (arg, &stat_buf) < 0)
539 return (FALSE);
540 atime = get_stat_atime (&stat_buf);
541 mtime = get_stat_mtime (&stat_buf);
542 return (timespec_cmp (mtime, atime) > 0);
cce855bc 543
726f6388 544 case 'f': /* File is a file? */
ac18b312 545 if (sh_stat (arg, &stat_buf) < 0)
726f6388
JA
546 return (FALSE);
547
ccc6cda3 548 /* -f is true if the given file exists and is a regular file. */
726f6388 549#if defined (S_IFMT)
ccc6cda3 550 return (S_ISREG (stat_buf.st_mode) || (stat_buf.st_mode & S_IFMT) == 0);
726f6388 551#else
ccc6cda3 552 return (S_ISREG (stat_buf.st_mode));
726f6388
JA
553#endif /* !S_IFMT */
554
555 case 'd': /* File is a directory? */
ac18b312 556 return (sh_stat (arg, &stat_buf) == 0 && (S_ISDIR (stat_buf.st_mode)));
726f6388
JA
557
558 case 's': /* File has something in it? */
ac18b312 559 return (sh_stat (arg, &stat_buf) == 0 && stat_buf.st_size > (off_t) 0);
726f6388
JA
560
561 case 'S': /* File is a socket? */
562#if !defined (S_ISSOCK)
563 return (FALSE);
564#else
ac18b312 565 return (sh_stat (arg, &stat_buf) == 0 && S_ISSOCK (stat_buf.st_mode));
ccc6cda3 566#endif /* S_ISSOCK */
726f6388
JA
567
568 case 'c': /* File is character special? */
ac18b312 569 return (sh_stat (arg, &stat_buf) == 0 && S_ISCHR (stat_buf.st_mode));
726f6388
JA
570
571 case 'b': /* File is block special? */
ac18b312 572 return (sh_stat (arg, &stat_buf) == 0 && S_ISBLK (stat_buf.st_mode));
726f6388
JA
573
574 case 'p': /* File is a named pipe? */
726f6388
JA
575#ifndef S_ISFIFO
576 return (FALSE);
577#else
ac18b312 578 return (sh_stat (arg, &stat_buf) == 0 && S_ISFIFO (stat_buf.st_mode));
ccc6cda3 579#endif /* S_ISFIFO */
726f6388
JA
580
581 case 'L': /* Same as -h */
726f6388 582 case 'h': /* File is a symbolic link? */
ccc6cda3 583#if !defined (S_ISLNK) || !defined (HAVE_LSTAT)
726f6388
JA
584 return (FALSE);
585#else
cce855bc
JA
586 return ((arg[0] != '\0') &&
587 (lstat (arg, &stat_buf) == 0) && S_ISLNK (stat_buf.st_mode));
ccc6cda3 588#endif /* S_IFLNK && HAVE_LSTAT */
726f6388
JA
589
590 case 'u': /* File is setuid? */
ac18b312 591 return (sh_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISUID) != 0);
726f6388
JA
592
593 case 'g': /* File is setgid? */
ac18b312 594 return (sh_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISGID) != 0);
726f6388
JA
595
596 case 'k': /* File has sticky bit set? */
726f6388
JA
597#if !defined (S_ISVTX)
598 /* This is not Posix, and is not defined on some Posix systems. */
599 return (FALSE);
600#else
ac18b312 601 return (sh_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISVTX) != 0);
726f6388
JA
602#endif
603
cce855bc
JA
604 case 't': /* File fd is a terminal? */
605 if (legal_number (arg, &r) == 0)
606 return (FALSE);
f73dda09 607 return ((r == (int)r) && isatty ((int)r));
726f6388
JA
608
609 case 'n': /* True if arg has some length. */
cce855bc 610 return (arg[0] != '\0');
726f6388
JA
611
612 case 'z': /* True if arg has no length. */
cce855bc 613 return (arg[0] == '\0');
726f6388 614
cce855bc
JA
615 case 'o': /* True if option `arg' is set. */
616 return (minus_o_option_value (arg) == 1);
6faad625
CR
617
618 case 'v':
595e3e69 619#if defined (ARRAY_VARS)
7b024db8
CR
620 aflags = assoc_expand_once ? AV_NOEXPAND : 0;
621 if (valid_array_reference (arg, aflags))
595e3e69
CR
622 {
623 char *t;
b2b78a63
CR
624 int ret;
625 array_eltstate_t es;
8ba5ed63
CR
626
627 /* Let's assume that this has already been expanded once. */
35bc7025
CR
628 /* XXX - TAG:bash-5.2 fix with corresponding fix to execute_cmd.c:
629 execute_cond_node() that passes TEST_ARRAYEXP in FLAGS */
fffe80d4 630
a30f513f 631 if (shell_compatibility_level > 51)
fffe80d4
CR
632 /* Allow associative arrays to use `test -v array[@]' to look for
633 a key named `@'. */
b2b78a63
CR
634 aflags |= AV_ATSTARKEYS; /* XXX */
635 init_eltstate (&es);
636 t = get_array_value (arg, aflags|AV_ALLOWALL, &es);
78a3f8a4 637 ret = t ? TRUE : FALSE;
b2b78a63 638 if (es.subtype > 0) /* subscript is * or @ */
78a3f8a4 639 free (t);
b2b78a63 640 flush_eltstate (&es);
78a3f8a4 641 return ret;
595e3e69 642 }
439b8c2c 643 else if (legal_number (arg, &r)) /* -v n == is $n set? */
9831556e 644 return ((r >= 0 && r <= number_of_args()) ? TRUE : FALSE);
8d125d8b
CR
645 v = find_variable (arg);
646 if (v && invisible_p (v) == 0 && array_p (v))
595e3e69
CR
647 {
648 char *t;
649 /* [[ -v foo ]] == [[ -v foo[0] ]] */
650 t = array_reference (array_cell (v), 0);
651 return (t ? TRUE : FALSE);
652 }
653 else if (v && invisible_p (v) == 0 && assoc_p (v))
654 {
655 char *t;
656 t = assoc_reference (assoc_cell (v), "0");
657 return (t ? TRUE : FALSE);
658 }
8d125d8b
CR
659#else
660 v = find_variable (arg);
595e3e69 661#endif
3087e51c 662 return (v && invisible_p (v) == 0 && var_isset (v) ? TRUE : FALSE);
15623760
CR
663
664 case 'R':
15baad62
CR
665 v = find_variable_noref (arg);
666 return ((v && invisible_p (v) == 0 && var_isset (v) && nameref_p (v)) ? TRUE : FALSE);
726f6388 667 }
f73dda09
JA
668
669 /* We can't actually get here, but this shuts up gcc. */
670 return (FALSE);
726f6388
JA
671}
672
cce855bc
JA
673/* Return TRUE if OP is one of the test command's binary operators. */
674int
a61ffa78 675test_binop (char *op)
726f6388 676{
cce855bc 677 if (op[0] == '=' && op[1] == '\0')
ccc6cda3 678 return (1); /* '=' */
cce855bc 679 else if ((op[0] == '<' || op[0] == '>') && op[1] == '\0') /* string <, > */
ccc6cda3 680 return (1);
cce855bc 681 else if ((op[0] == '=' || op[0] == '!') && op[1] == '=' && op[2] == '\0')
ccc6cda3
JA
682 return (1); /* `==' and `!=' */
683#if defined (PATTERN_MATCHING)
cce855bc 684 else if (op[2] == '\0' && op[1] == '~' && (op[0] == '=' || op[0] == '!'))
ccc6cda3
JA
685 return (1);
686#endif
61c476d2 687 else if (op[0] != '-' || op[1] == '\0' || op[2] == '\0' || op[3] != '\0')
ccc6cda3
JA
688 return (0);
689 else
690 {
cce855bc
JA
691 if (op[2] == 't')
692 switch (op[1])
ccc6cda3 693 {
cce855bc
JA
694 case 'n': /* -nt */
695 case 'o': /* -ot */
696 case 'l': /* -lt */
697 case 'g': /* -gt */
698 return (1);
699 default:
700 return (0);
ccc6cda3 701 }
cce855bc
JA
702 else if (op[1] == 'e')
703 switch (op[2])
ccc6cda3 704 {
cce855bc
JA
705 case 'q': /* -eq */
706 case 'f': /* -ef */
707 return (1);
708 default:
709 return (0);
ccc6cda3 710 }
cce855bc
JA
711 else if (op[2] == 'e')
712 switch (op[1])
ccc6cda3 713 {
cce855bc
JA
714 case 'n': /* -ne */
715 case 'g': /* -ge */
716 case 'l': /* -le */
717 return (1);
718 default:
719 return (0);
ccc6cda3
JA
720 }
721 else
28ef6c31 722 return (0);
ccc6cda3 723 }
726f6388
JA
724}
725
726/* Return non-zero if OP is one of the test command's unary operators. */
cce855bc 727int
a61ffa78 728test_unop (char *op)
726f6388 729{
99210c29 730 if (op[0] != '-' || (op[1] && op[2] != 0))
cce855bc
JA
731 return (0);
732
733 switch (op[1])
ccc6cda3
JA
734 {
735 case 'a': case 'b': case 'c': case 'd': case 'e':
736 case 'f': case 'g': case 'h': case 'k': case 'n':
cce855bc 737 case 'o': case 'p': case 'r': case 's': case 't':
6faad625 738 case 'u': case 'v': case 'w': case 'x': case 'z':
cce855bc 739 case 'G': case 'L': case 'O': case 'S': case 'N':
15baad62 740 case 'R':
ccc6cda3
JA
741 return (1);
742 }
cce855bc 743
ccc6cda3 744 return (0);
726f6388
JA
745}
746
747static int
a61ffa78 748two_arguments (void)
726f6388 749{
ccc6cda3
JA
750 if (argv[pos][0] == '!' && argv[pos][1] == '\0')
751 return (argv[pos + 1][0] == '\0');
1975c9b5 752 else if (argv[pos][0] == '-' && argv[pos][1] && argv[pos][2] == '\0')
726f6388 753 {
cce855bc 754 if (test_unop (argv[pos]))
ccc6cda3 755 return (unary_operator ());
726f6388 756 else
5e13499c 757 test_syntax_error (_("%s: unary operator expected"), argv[pos]);
726f6388
JA
758 }
759 else
5e13499c 760 test_syntax_error (_("%s: unary operator expected"), argv[pos]);
726f6388 761
ccc6cda3 762 return (0);
726f6388
JA
763}
764
1975c9b5 765#define ANDOR(s) (s[0] == '-' && (s[1] == 'a' || s[1] == 'o') && s[2] == 0)
ccc6cda3 766
f73dda09
JA
767/* This could be augmented to handle `-t' as equivalent to `-t 1', but
768 POSIX requires that `-t' be given an argument. */
ccc6cda3
JA
769#define ONE_ARG_TEST(s) ((s)[0] != '\0')
770
726f6388 771static int
a61ffa78 772three_arguments (void)
726f6388
JA
773{
774 int value;
775
cce855bc 776 if (test_binop (argv[pos+1]))
ccc6cda3
JA
777 {
778 value = binary_operator ();
779 pos = argc;
780 }
781 else if (ANDOR (argv[pos+1]))
782 {
783 if (argv[pos+1][1] == 'a')
28ef6c31 784 value = ONE_ARG_TEST(argv[pos]) && ONE_ARG_TEST(argv[pos+2]);
ccc6cda3 785 else
28ef6c31 786 value = ONE_ARG_TEST(argv[pos]) || ONE_ARG_TEST(argv[pos+2]);
ccc6cda3
JA
787 pos = argc;
788 }
cce855bc 789 else if (argv[pos][0] == '!' && argv[pos][1] == '\0')
726f6388
JA
790 {
791 advance (1);
792 value = !two_arguments ();
42768bef 793 pos = argc;
726f6388 794 }
ccc6cda3 795 else if (argv[pos][0] == '(' && argv[pos+2][0] == ')')
726f6388 796 {
ccc6cda3 797 value = ONE_ARG_TEST(argv[pos+1]);
726f6388
JA
798 pos = argc;
799 }
726f6388 800 else
5e13499c 801 test_syntax_error (_("%s: binary operator expected"), argv[pos+1]);
ccc6cda3 802
726f6388
JA
803 return (value);
804}
805
806/* This is an implementation of a Posix.2 proposal by David Korn. */
807static int
a61ffa78 808posixtest (void)
726f6388
JA
809{
810 int value;
811
812 switch (argc - 1) /* one extra passed in */
813 {
814 case 0:
815 value = FALSE;
816 pos = argc;
817 break;
818
819 case 1:
ccc6cda3 820 value = ONE_ARG_TEST(argv[1]);
726f6388
JA
821 pos = argc;
822 break;
823
824 case 2:
825 value = two_arguments ();
826 pos = argc;
827 break;
828
829 case 3:
830 value = three_arguments ();
831 break;
832
833 case 4:
ccc6cda3 834 if (argv[pos][0] == '!' && argv[pos][1] == '\0')
726f6388
JA
835 {
836 advance (1);
837 value = !three_arguments ();
838 break;
839 }
ae0865bc
CR
840 else if (argv[pos][0] == '(' && argv[pos][1] == '\0' && argv[argc-1][0] == ')' && argv[argc-1][1] == '\0')
841 {
842 advance (1);
843 value = two_arguments ();
844 pos = argc;
845 break;
846 }
726f6388 847 /* FALLTHROUGH */
726f6388
JA
848 default:
849 value = expr ();
850 }
851
852 return (value);
853}
854
855/*
856 * [:
857 * '[' expr ']'
858 * test:
859 * test expr
860 */
861int
a61ffa78 862test_command (int margc, char **margv)
726f6388
JA
863{
864 int value;
726f6388
JA
865 int code;
866
f73dda09
JA
867 USE_VAR(margc);
868
36eb585c 869 code = setjmp_nosigs (test_exit_buf);
726f6388
JA
870
871 if (code)
872 return (test_error_return);
726f6388
JA
873
874 argv = margv;
875
ccc6cda3 876 if (margv[0] && margv[0][0] == '[' && margv[0][1] == '\0')
726f6388
JA
877 {
878 --margc;
879
726f6388 880 if (margv[margc] && (margv[margc][0] != ']' || margv[margc][1]))
5e13499c 881 test_syntax_error (_("missing `]'"), (char *)NULL);
28ef6c31
JA
882
883 if (margc < 2)
884 test_exit (SHELL_BOOLEAN (FALSE));
726f6388
JA
885 }
886
887 argc = margc;
888 pos = 1;
889
890 if (pos >= argc)
891 test_exit (SHELL_BOOLEAN (FALSE));
892
893 noeval = 0;
894 value = posixtest ();
895
896 if (pos != argc)
48394b08
CR
897 {
898 if (pos < argc && argv[pos][0] == '-')
899 test_syntax_error (_("syntax error: `%s' unexpected"), argv[pos]);
900 else
901 test_syntax_error (_("too many arguments"), (char *)NULL);
902 }
726f6388
JA
903
904 test_exit (SHELL_BOOLEAN (value));
905}