169 lines
5.0 KiB
C
169 lines
5.0 KiB
C
/* test.c - evaluate expression
|
|
*
|
|
* Copyright 2018 Rob Landley <rob@landley.net>
|
|
*
|
|
* See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
|
|
*
|
|
* TODO sh [[ ]] options: < aaa<bbb > bbb>aaa ~= regex
|
|
|
|
USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
|
|
USE_TEST_GLUE(OLDTOY([, test, TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_NOHELP))
|
|
|
|
config TEST
|
|
bool "test"
|
|
default y
|
|
help
|
|
usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y]
|
|
|
|
Return true or false by performing tests. (With no arguments return false.)
|
|
|
|
--- Tests with a single argument (after the option):
|
|
PATH is/has:
|
|
-b block device -f regular file -p fifo -u setuid bit
|
|
-c char device -g setgid -r read bit -w write bit
|
|
-d directory -h symlink -S socket -x execute bit
|
|
-e exists -L symlink -s nonzero size -k sticky bit
|
|
STRING is:
|
|
-n nonzero size -z zero size (STRING by itself implies -n)
|
|
FD (integer file descriptor) is:
|
|
-t a TTY
|
|
|
|
--- Tests with one argument on each side of an operator:
|
|
Two strings:
|
|
= are identical != differ
|
|
|
|
Two integers:
|
|
-eq equal -gt first > second -lt first < second
|
|
-ne not equal -ge first >= second -le first <= second
|
|
|
|
--- Modify or combine tests:
|
|
! EXPR not (swap true/false) EXPR -a EXPR and (are both true)
|
|
( EXPR ) evaluate this first EXPR -o EXPR or (is either true)
|
|
|
|
config TEST_GLUE
|
|
bool
|
|
default y
|
|
depends on TEST || SH
|
|
*/
|
|
|
|
#include "toys.h"
|
|
|
|
// Consume 3, 2, or 1 argument test, returning result and *count used.
|
|
static int do_test(char **args, int *count)
|
|
{
|
|
char c, *s;
|
|
int i;
|
|
|
|
if (*count>=3) {
|
|
*count = 3;
|
|
char *s = args[1], *ss = "eqnegtgeltle";
|
|
if (!strcmp(s, "=") || !strcmp(s, "==")) return !strcmp(args[0], args[2]);
|
|
if (!strcmp(s, "!=")) return strcmp(args[0], args[2]);
|
|
if (*s=='-' && strlen(s)==3 && (s = strstr(ss, s+1)) && !((i = s-ss)&1)) {
|
|
long long a = atolx(args[0]), b = atolx(args[2]);
|
|
|
|
if (!i) return a == b;
|
|
if (i==2) return a != b;
|
|
if (i==4) return a > b;
|
|
if (i==6) return a >= b;
|
|
if (i==8) return a < b;
|
|
if (i==10) return a<= b;
|
|
}
|
|
}
|
|
s = *args;
|
|
if (*count>=2 && *s == '-' && s[1] && !s[2]) {
|
|
*count = 2;
|
|
c = s[1];
|
|
if (-1 != (i = stridx("hLbcdefgkpSusxwr", c))) {
|
|
struct stat st;
|
|
|
|
// stat or lstat, then handle rwx and s
|
|
if (-1 == ((i<2) ? lstat : stat)(args[1], &st)) return 0;
|
|
if (i>=13) return !!(st.st_mode&(0111<<(i-13)));
|
|
if (c == 's') return !!st.st_size; // otherwise 1<<32 == 0
|
|
|
|
// handle file type checking and SUID/SGID
|
|
if ((i = ((char []){80,80,48,16,32,0,64,2,1,8,96,4}[i])<<9)>=4096)
|
|
return (st.st_mode&S_IFMT) == i;
|
|
else return (st.st_mode & i) == i;
|
|
} else if (c == 'z') return !*args[1];
|
|
else if (c == 'n') return *args[1];
|
|
else if (c == 't') return isatty(atolx(args[1]));
|
|
}
|
|
return *count = 0;
|
|
}
|
|
|
|
#define NOT 1 // Most recent test had an odd number of preceding !
|
|
#define AND 2 // test before -a failed since -o or ( so force false
|
|
#define OR 4 // test before -o succeeded since ( so force true
|
|
void test_main(void)
|
|
{
|
|
char *s;
|
|
int pos, paren, pstack, result = 0;
|
|
|
|
toys.exitval = 2;
|
|
if (CFG_TOYBOX && !strcmp("[", toys.which->name))
|
|
if (!toys.optc || strcmp("]", toys.optargs[--toys.optc]))
|
|
error_exit("Missing ']'");
|
|
|
|
// loop through command line arguments
|
|
if (toys.optc) for (pos = paren = pstack = 0; ; pos++) {
|
|
int len = toys.optc-pos;
|
|
|
|
if (!len) perror_exit("need arg @%d", pos);
|
|
|
|
// Evaluate next test
|
|
result = do_test(toys.optargs+pos, &len);
|
|
pos += len;
|
|
// Single argument could be ! ( or nonempty
|
|
if (!len) {
|
|
if (toys.optargs[pos+1]) {
|
|
if (!strcmp("!", toys.optargs[pos])) {
|
|
pstack ^= NOT;
|
|
continue;
|
|
}
|
|
if (!strcmp("(", toys.optargs[pos])) {
|
|
if (++paren>9) perror_exit("bad (");
|
|
pstack <<= 3;
|
|
continue;
|
|
}
|
|
}
|
|
result = *toys.optargs[pos++];
|
|
}
|
|
s = toys.optargs[pos];
|
|
for (;;) {
|
|
|
|
// Handle pending ! -a -o (the else means -o beats -a)
|
|
if (pstack&NOT) result = !result;
|
|
pstack &= ~NOT;
|
|
if (pstack&OR) result = 1;
|
|
else if (pstack&AND) result = 0;
|
|
|
|
// Do it again for every )
|
|
if (!paren || pos==toys.optc || strcmp(")", s)) break;
|
|
paren--;
|
|
pstack >>= 3;
|
|
s = toys.optargs[++pos];
|
|
}
|
|
|
|
// Out of arguments?
|
|
if (pos==toys.optc) {
|
|
if (paren) perror_exit("need )");
|
|
break;
|
|
}
|
|
|
|
// are we followed by -a or -o?
|
|
|
|
if (!strcmp("-a", s)) {
|
|
if (!result) pstack |= AND;
|
|
} else if (!strcmp("-o", s)) {
|
|
// -o flushes -a even if previous test was false
|
|
pstack &=~AND;
|
|
if (result) pstack |= OR;
|
|
} else error_exit("too many arguments");
|
|
}
|
|
|
|
// Invert C logic to get shell logic
|
|
toys.exitval = !result;
|
|
}
|