* "This program is in the Public Domain."
*/
//config:config TEST
-//config: bool "test"
+//config: bool "test (3.6 kb)"
//config: default y
//config: help
-//config: test is used to check file types and compare values,
-//config: returning an appropriate exit code. The bash shell
-//config: has test built in, ash can build it in optionally.
+//config: test is used to check file types and compare values,
+//config: returning an appropriate exit code. The bash shell
+//config: has test built in, ash can build it in optionally.
+//config:
+//config:config TEST1
+//config: bool "test as ["
+//config: default y
+//config: help
+//config: Provide test command in the "[ EXPR ]" form
+//config:
+//config:config TEST2
+//config: bool "test as [["
+//config: default y
+//config: help
+//config: Provide test command in the "[[ EXPR ]]" form
//config:
//config:config FEATURE_TEST_64
//config: bool "Extend test to 64 bit"
//config: default y
-//config: depends on TEST || ASH_BUILTIN_TEST || HUSH
+//config: depends on TEST || TEST1 || TEST2 || ASH_TEST || HUSH_TEST
//config: help
-//config: Enable 64-bit support in test.
+//config: Enable 64-bit support in test.
-//applet:IF_TEST(APPLET_NOFORK([, test, BB_DIR_USR_BIN, BB_SUID_DROP, test))
-//applet:IF_TEST(APPLET_NOFORK([[, test, BB_DIR_USR_BIN, BB_SUID_DROP, test))
//applet:IF_TEST(APPLET_NOFORK(test, test, BB_DIR_USR_BIN, BB_SUID_DROP, test))
+//applet:IF_TEST1(APPLET_NOFORK([, test, BB_DIR_USR_BIN, BB_SUID_DROP, test))
+//applet:IF_TEST2(APPLET_NOFORK([[, test, BB_DIR_USR_BIN, BB_SUID_DROP, test))
+
+//kbuild:lib-$(CONFIG_TEST) += test.o test_ptr_hack.o
+//kbuild:lib-$(CONFIG_TEST1) += test.o test_ptr_hack.o
+//kbuild:lib-$(CONFIG_TEST2) += test.o test_ptr_hack.o
-//kbuild:lib-$(CONFIG_TEST) += test.o test_ptr_hack.o
-//kbuild:lib-$(CONFIG_ASH) += test.o test_ptr_hack.o
-//kbuild:lib-$(CONFIG_HUSH) += test.o test_ptr_hack.o
+//kbuild:lib-$(CONFIG_ASH_TEST) += test.o test_ptr_hack.o
+//kbuild:lib-$(CONFIG_HUSH_TEST) += test.o test_ptr_hack.o
/* "test --help" is special-cased to ignore --help */
//usage:#define test_trivial_usage NOUSAGE_STR
//usage: "1\n"
#include "libbb.h"
-#include <setjmp.h>
/* This is a NOFORK applet. Be very careful! */
{ /* "-L" */ FILSYM , UNOP },
{ /* "-S" */ FILSOCK , UNOP },
{ /* "=" */ STREQ , BINOP },
+ /* "==" is bashism, http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
+ * lists only "=" as comparison operator.
+ */
{ /* "==" */ STREQ , BINOP },
{ /* "!=" */ STRNE , BINOP },
{ /* "<" */ STRLT , BINOP },
"-L" "\0"
"-S" "\0"
"=" "\0"
+ /* "==" is bashism */
"==" "\0"
"!=" "\0"
"<" "\0"
/*return 1; - NOTREACHED */
}
-
static void initialize_group_array(void)
{
- int n;
-
- /* getgroups may be expensive, try to use it only once */
- ngroups = 32;
- do {
- /* FIXME: ash tries so hard to not die on OOM,
- * and we spoil it with just one xrealloc here */
- /* We realloc, because test_main can be entered repeatedly by shell.
- * Testcase (ash): 'while true; do test -x some_file; done'
- * and watch top. (some_file must have owner != you) */
- n = ngroups;
- group_array = xrealloc(group_array, n * sizeof(gid_t));
- ngroups = getgroups(n, group_array);
- } while (ngroups > n);
+ group_array = bb_getgroups(&ngroups, NULL);
}
-
/* Return non-zero if GID is one that we have in our groups list. */
//XXX: FIXME: duplicate of existing libbb function?
// see toplevel TODO file:
/* Do the same thing access(2) does, but use the effective uid and gid,
and don't make the mistake of telling root that any file is
executable. */
-static int test_eaccess(char *path, int mode)
+static int test_eaccess(struct stat *st, int mode)
{
- struct stat st;
unsigned int euid = geteuid();
- if (stat(path, &st) < 0)
- return -1;
-
if (euid == 0) {
/* Root can read or write any file. */
if (mode != X_OK)
/* Root can execute any file that has any one of the execute
* bits set. */
- if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
+ if (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
return 0;
}
- if (st.st_uid == euid) /* owner */
+ if (st->st_uid == euid) /* owner */
mode <<= 6;
- else if (is_a_group_member(st.st_gid))
+ else if (is_a_group_member(st->st_gid))
mode <<= 3;
- if (st.st_mode & mode)
+ if (st->st_mode & mode)
return 0;
return -1;
i = W_OK;
if (mode == FILEX)
i = X_OK;
- return test_eaccess(nm, i) == 0;
+ return test_eaccess(&s, i) == 0;
}
if (is_file_type(mode)) {
if (mode == FILREG)
const char *arg0;
arg0 = bb_basename(argv[0]);
- if (arg0[0] == '[') {
+ if ((ENABLE_TEST1 || ENABLE_TEST2 || ENABLE_ASH_TEST || ENABLE_HUSH_TEST)
+ && (arg0[0] == '[')
+ ) {
--argc;
if (!arg0[1]) { /* "[" ? */
if (NOT_LONE_CHAR(argv[argc], ']')) {
res = (argv[0][0] == '\0');
goto ret_special;
}
- if (argv[2] && !argv[3]) {
- check_operator(argv[1]);
- if (last_operator->op_type == BINOP) {
- /* "test [!] arg1 <binary_op> arg2" */
- args = argv;
- res = (binop() == 0);
+ if (argv[2]) {
+ if (!argv[3]) {
+ /*
+ * http://pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
+ * """ 3 arguments:
+ * If $2 is a binary primary, perform the binary test of $1 and $3.
+ * """
+ */
+ check_operator(argv[1]);
+ if (last_operator->op_type == BINOP) {
+ /* "test [!] arg1 <binary_op> arg2" */
+ args = argv;
+ res = (binop() == 0);
ret_special:
- /* If there was leading "!" op... */
- res ^= negate;
- goto ret;
+ /* If there was leading "!" op... */
+ res ^= negate;
+ goto ret;
+ }
+ /* """If $1 is '(' and $3 is ')', perform the unary test of $2."""
+ * Looks like this works without additional coding.
+ */
+ goto check_negate;
+ }
+ /* argv[3] exists (at least 4 args), is it exactly 4 args? */
+ if (!argv[4]) {
+ /*
+ * """ 4 arguments:
+ * If $1 is '!', negate the three-argument test of $2, $3, and $4.
+ * If $1 is '(' and $4 is ')', perform the two-argument test of $2 and $3.
+ * """
+ * Example why code below is necessary: test '(' ! -e ')'
+ */
+ if (LONE_CHAR(argv[0], '(')
+ && LONE_CHAR(argv[3], ')')
+ ) {
+ /* "test [!] ( x y )" */
+ argv[3] = NULL;
+ argv++;
+ }
}
}
+ check_negate:
if (LONE_CHAR(argv[0], '!')) {
argv++;
negate ^= 1;