summaryrefslogtreecommitdiffstats
path: root/src/kmk/kmkbuiltin/test.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/kmk/kmkbuiltin/test.c')
-rw-r--r--src/kmk/kmkbuiltin/test.c869
1 files changed, 869 insertions, 0 deletions
diff --git a/src/kmk/kmkbuiltin/test.c b/src/kmk/kmkbuiltin/test.c
new file mode 100644
index 0000000..259f223
--- /dev/null
+++ b/src/kmk/kmkbuiltin/test.c
@@ -0,0 +1,869 @@
+/* $NetBSD: test.c,v 1.33 2007/06/24 18:54:58 christos Exp $ */
+
+/*
+ * test(1); version 7-like -- author Erik Baalbergen
+ * modified by Eric Gisin to be used as built-in.
+ * modified by Arnold Robbins to add SVR3 compatibility
+ * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
+ * modified by J.T. Conklin for NetBSD.
+ *
+ * This program is in the Public Domain.
+ */
+
+/*#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: test.c,v 1.33 2007/06/24 18:54:58 christos Exp $");
+#endif*/
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "config.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include "err.h"
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _MSC_VER
+# include <direct.h>
+# include <io.h>
+# include <process.h>
+# include "mscfakes.h"
+# include "quote_argv.h"
+#else
+# include <unistd.h>
+#endif
+#include <stdarg.h>
+#include <sys/stat.h>
+
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifndef __arraycount
+# define __arraycount(a) ( sizeof(a) / sizeof(a[0]) )
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/* test(1) accepts the following grammar:
+ oexpr ::= aexpr | aexpr "-o" oexpr ;
+ aexpr ::= nexpr | nexpr "-a" aexpr ;
+ nexpr ::= primary | "!" primary
+ primary ::= unary-operator operand
+ | operand binary-operator operand
+ | operand
+ | "(" oexpr ")"
+ ;
+ unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
+ "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
+
+ binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
+ "-nt"|"-ot"|"-ef";
+ operand ::= <any legal UNIX file name>
+*/
+
+enum token {
+ EOI,
+ FILRD,
+ FILWR,
+ FILEX,
+ FILEXIST,
+ FILREG,
+ FILDIR,
+ FILCDEV,
+ FILBDEV,
+ FILFIFO,
+ FILSOCK,
+ FILSYM,
+ FILGZ,
+ FILTT,
+ FILSUID,
+ FILSGID,
+ FILSTCK,
+ FILNT,
+ FILOT,
+ FILEQ,
+ FILUID,
+ FILGID,
+ STREZ,
+ STRNZ,
+ STREQ,
+ STRNE,
+ STRLT,
+ STRGT,
+ INTEQ,
+ INTNE,
+ INTGE,
+ INTGT,
+ INTLE,
+ INTLT,
+ UNOT,
+ BAND,
+ BOR,
+ LPAREN,
+ RPAREN,
+ OPERAND
+};
+
+enum token_types {
+ UNOP,
+ BINOP,
+ BUNOP,
+ BBINOP,
+ PAREN
+};
+
+struct t_op {
+ const char *op_text;
+ short op_num, op_type;
+};
+
+/** kmk_test instance data. */
+typedef struct TESTINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ char **t_wp;
+ struct t_op const *t_wp_op;
+} TESTINSTANCE;
+typedef TESTINSTANCE *PTESTINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const struct t_op cop[] = {
+ {"!", UNOT, BUNOP},
+ {"(", LPAREN, PAREN},
+ {")", RPAREN, PAREN},
+ {"<", STRLT, BINOP},
+ {"=", STREQ, BINOP},
+ {">", STRGT, BINOP},
+};
+
+static const struct t_op cop2[] = {
+ {"!=", STRNE, BINOP},
+};
+
+static const struct t_op mop3[] = {
+ {"ef", FILEQ, BINOP},
+ {"eq", INTEQ, BINOP},
+ {"ge", INTGE, BINOP},
+ {"gt", INTGT, BINOP},
+ {"le", INTLE, BINOP},
+ {"lt", INTLT, BINOP},
+ {"ne", INTNE, BINOP},
+ {"nt", FILNT, BINOP},
+ {"ot", FILOT, BINOP},
+};
+
+static const struct t_op mop2[] = {
+ {"G", FILGID, UNOP},
+ {"L", FILSYM, UNOP},
+ {"O", FILUID, UNOP},
+ {"S", FILSOCK,UNOP},
+ {"a", BAND, BBINOP},
+ {"b", FILBDEV,UNOP},
+ {"c", FILCDEV,UNOP},
+ {"d", FILDIR, UNOP},
+ {"e", FILEXIST,UNOP},
+ {"f", FILREG, UNOP},
+ {"g", FILSGID,UNOP},
+ {"h", FILSYM, UNOP}, /* for backwards compat */
+ {"k", FILSTCK,UNOP},
+ {"n", STRNZ, UNOP},
+ {"o", BOR, BBINOP},
+ {"p", FILFIFO,UNOP},
+ {"r", FILRD, UNOP},
+ {"s", FILGZ, UNOP},
+ {"t", FILTT, UNOP},
+ {"u", FILSUID,UNOP},
+ {"w", FILWR, UNOP},
+ {"x", FILEX, UNOP},
+ {"z", STREZ, UNOP},
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int syntax(PTESTINSTANCE, const char *, const char *);
+static int oexpr(PTESTINSTANCE, enum token);
+static int aexpr(PTESTINSTANCE, enum token);
+static int nexpr(PTESTINSTANCE, enum token);
+static int primary(PTESTINSTANCE, enum token);
+static int binop(PTESTINSTANCE);
+static int test_access(struct stat *, mode_t);
+static int filstat(char *, enum token);
+static enum token t_lex(PTESTINSTANCE, char *);
+static int isoperand(PTESTINSTANCE);
+static int getn(PTESTINSTANCE, const char *);
+static int newerf(const char *, const char *);
+static int olderf(const char *, const char *);
+static int equalf(const char *, const char *);
+static int usage(PKMKBUILTINCTX, int);
+
+#if !defined(KMK_BUILTIN_STANDALONE) || defined(ELECTRIC_HEAP)
+extern void *xmalloc(unsigned int);
+#else
+extern void *xmalloc(unsigned int sz)
+{
+ void *p = malloc(sz);
+ if (!p) {
+ fprintf(stderr, "kmk_test: malloc(%u) failed\n", sz);
+ exit(1);
+ }
+ return p;
+}
+#endif
+
+
+
+int kmk_builtin_test(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, char ***ppapszArgvSpawn)
+{
+ TESTINSTANCE This;
+ int res;
+ char **argv_spawn;
+ int i;
+
+ This.pCtx = pCtx;
+ This.t_wp = NULL;
+ This.t_wp_op = NULL;
+
+ /* look for the '--', '--help' and '--version'. */
+ argv_spawn = NULL;
+ for (i = 1; i < argc; i++) {
+ if ( argv[i][0] == '-'
+ && argv[i][1] == '-') {
+ if (argv[i][2] == '\0') {
+ argc = i;
+ argv[i] = NULL;
+
+ /* skip blank arguments (happens inside kmk) */
+ while (argv[++i]) {
+ const char *psz = argv[i];
+ while (isspace(*psz))
+ psz++;
+ if (*psz)
+ break;
+ }
+ argv_spawn = &argv[i];
+ break;
+ }
+ if (!strcmp(argv[i], "--help"))
+ return usage(pCtx, 0);
+ if (!strcmp(argv[i], "--version"))
+ return kbuild_version(argv[0]);
+ }
+ }
+
+ /* are we '['? then check for ']'. */
+ if (strcmp(argv[0], "[") == 0) { /** @todo should skip the path in g_progname */
+ if (strcmp(argv[--argc], "]"))
+ return errx(pCtx, 1, "missing ]");
+ argv[argc] = NULL;
+ }
+
+ /* evaluate the expression */
+ if (argc < 2)
+ res = 1;
+ else {
+ This.t_wp = &argv[1];
+ res = oexpr(&This, t_lex(&This, *This.t_wp));
+ if (res != -42 && *This.t_wp != NULL && *++This.t_wp != NULL)
+ res = syntax(&This, *This.t_wp, "unexpected operator");
+ if (res == -42)
+ return 1; /* don't mix syntax errors with the argv_spawn ignore */
+ res = !res;
+ }
+
+ /* anything to execute on success? */
+ if (argv_spawn) {
+ if (res != 0 || !argv_spawn[0])
+ res = 0; /* ignored */
+ else {
+#ifdef KMK_BUILTIN_STANDALONE
+ /* try exec the specified process */
+# if defined(_MSC_VER)
+ int argc_spawn = 0;
+ while (argv_spawn[argc_spawn])
+ argc_spawn++;
+ if (quote_argv(argc, argv_spawn, 0 /*fWatcomBrainDamage*/, 0/*fFreeOrLeak*/) != -1)
+ {
+ res = _spawnvp(_P_WAIT, argv_spawn[0], argv_spawn);
+ if (res == -1)
+ res = err(pCtx, 1, "_spawnvp(_P_WAIT,%s,..)", argv_spawn[0]);
+ }
+ else
+ res = err(pCtx, 1, "quote_argv: out of memory");
+# else
+ execvp(argv_spawn[0], argv_spawn);
+ res = err(pCtx, 1, "execvp(%s,..)", argv_spawn[0]);
+# endif
+#else /* in kmk */
+ /* let job.c spawn the process, make a job.c style argv_spawn copy. */
+ char *cur, **argv_new;
+ size_t sz = 0;
+ int argc_new = 0;
+ while (argv_spawn[argc_new]) {
+ size_t len = strlen(argv_spawn[argc_new]) + 1;
+ sz += (len + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
+ argc_new++;
+ }
+
+ argv_new = xmalloc((argc_new + 1) * sizeof(char *));
+ cur = xmalloc(sz);
+ for (i = 0; i < argc_new; i++) {
+ size_t len = strlen(argv_spawn[i]) + 1;
+ argv_new[i] = memcpy(cur, argv_spawn[i], len);
+ cur += (len + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
+ }
+ argv_new[i] = NULL;
+
+ *ppapszArgvSpawn = argv_new;
+ res = 0;
+#endif /* in kmk */
+ }
+ }
+
+ return res;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_test", NULL };
+ return kmk_builtin_test(argc, argv, envp, &Ctx, NULL);
+}
+#endif
+
+static int
+syntax(PTESTINSTANCE pThis, const char *op, const char *msg)
+{
+
+ if (op && *op)
+ errx(pThis->pCtx, 1, "%s: %s", op, msg);
+ else
+ errx(pThis->pCtx, 1, "%s", msg);
+ return -42;
+}
+
+static int
+oexpr(PTESTINSTANCE pThis, enum token n)
+{
+ int res;
+
+ res = aexpr(pThis, n);
+ if (res == -42 || *pThis->t_wp == NULL)
+ return res;
+ if (t_lex(pThis, *++(pThis->t_wp)) == BOR) {
+ int res2 = oexpr(pThis, t_lex(pThis, *++(pThis->t_wp)));
+ return res2 != -42 ? res2 || res : res2;
+ }
+ pThis->t_wp--;
+ return res;
+}
+
+static int
+aexpr(PTESTINSTANCE pThis, enum token n)
+{
+ int res;
+
+ res = nexpr(pThis, n);
+ if (res == -42 || *pThis->t_wp == NULL)
+ return res;
+ if (t_lex(pThis, *++(pThis->t_wp)) == BAND) {
+ int res2 = aexpr(pThis, t_lex(pThis, *++(pThis->t_wp)));
+ return res2 != -42 ? res2 && res : res2;
+ }
+ pThis->t_wp--;
+ return res;
+}
+
+static int
+nexpr(PTESTINSTANCE pThis, enum token n)
+{
+ if (n == UNOT) {
+ int res = nexpr(pThis, t_lex(pThis, *++(pThis->t_wp)));
+ return res != -42 ? !res : res;
+ }
+ return primary(pThis, n);
+}
+
+static int
+primary(PTESTINSTANCE pThis, enum token n)
+{
+ enum token nn;
+ int res;
+
+ if (n == EOI)
+ return 0; /* missing expression */
+ if (n == LPAREN) {
+ if ((nn = t_lex(pThis, *++(pThis->t_wp))) == RPAREN)
+ return 0; /* missing expression */
+ res = oexpr(pThis, nn);
+ if (res != -42 && t_lex(pThis, *++(pThis->t_wp)) != RPAREN)
+ return syntax(pThis, NULL, "closing paren expected");
+ return res;
+ }
+ if (pThis->t_wp_op && pThis->t_wp_op->op_type == UNOP) {
+ /* unary expression */
+ if (*++(pThis->t_wp) == NULL)
+ return syntax(pThis, pThis->t_wp_op->op_text, "argument expected");
+ switch (n) {
+ case STREZ:
+ return strlen(*pThis->t_wp) == 0;
+ case STRNZ:
+ return strlen(*pThis->t_wp) != 0;
+ case FILTT:
+ return isatty(getn(pThis, *pThis->t_wp));
+ default:
+ return filstat(*pThis->t_wp, n);
+ }
+ }
+
+ if (t_lex(pThis, pThis->t_wp[1]), pThis->t_wp_op && pThis->t_wp_op->op_type == BINOP) {
+ return binop(pThis);
+ }
+
+ return strlen(*pThis->t_wp) > 0;
+}
+
+static int
+binop(PTESTINSTANCE pThis)
+{
+ const char *opnd1, *opnd2;
+ struct t_op const *op;
+
+ opnd1 = *pThis->t_wp;
+ (void) t_lex(pThis, *++(pThis->t_wp));
+ op = pThis->t_wp_op;
+
+ if ((opnd2 = *++(pThis->t_wp)) == NULL)
+ return syntax(pThis, op->op_text, "argument expected");
+
+ switch (op->op_num) {
+ case STREQ:
+ return strcmp(opnd1, opnd2) == 0;
+ case STRNE:
+ return strcmp(opnd1, opnd2) != 0;
+ case STRLT:
+ return strcmp(opnd1, opnd2) < 0;
+ case STRGT:
+ return strcmp(opnd1, opnd2) > 0;
+ case INTEQ:
+ return getn(pThis, opnd1) == getn(pThis, opnd2);
+ case INTNE:
+ return getn(pThis, opnd1) != getn(pThis, opnd2);
+ case INTGE:
+ return getn(pThis, opnd1) >= getn(pThis, opnd2);
+ case INTGT:
+ return getn(pThis, opnd1) > getn(pThis, opnd2);
+ case INTLE:
+ return getn(pThis, opnd1) <= getn(pThis, opnd2);
+ case INTLT:
+ return getn(pThis, opnd1) < getn(pThis, opnd2);
+ case FILNT:
+ return newerf(opnd1, opnd2);
+ case FILOT:
+ return olderf(opnd1, opnd2);
+ case FILEQ:
+ return equalf(opnd1, opnd2);
+ default:
+ abort();
+ /* NOTREACHED */
+#ifdef _MSC_VER
+ return -42;
+#endif
+ }
+}
+
+/*
+ * The manual, and IEEE POSIX 1003.2, suggests this should check the mode bits,
+ * not use access():
+ *
+ * True shall indicate only that the write flag is on. The file is not
+ * writable on a read-only file system even if this test indicates true.
+ *
+ * Unfortunately IEEE POSIX 1003.1-2001, as quoted in SuSv3, says only:
+ *
+ * True shall indicate that permission to read from file will be granted,
+ * as defined in "File Read, Write, and Creation".
+ *
+ * and that section says:
+ *
+ * When a file is to be read or written, the file shall be opened with an
+ * access mode corresponding to the operation to be performed. If file
+ * access permissions deny access, the requested operation shall fail.
+ *
+ * and of course access permissions are described as one might expect:
+ *
+ * * If a process has the appropriate privilege:
+ *
+ * * If read, write, or directory search permission is requested,
+ * access shall be granted.
+ *
+ * * If execute permission is requested, access shall be granted if
+ * execute permission is granted to at least one user by the file
+ * permission bits or by an alternate access control mechanism;
+ * otherwise, access shall be denied.
+ *
+ * * Otherwise:
+ *
+ * * The file permission bits of a file contain read, write, and
+ * execute/search permissions for the file owner class, file group
+ * class, and file other class.
+ *
+ * * Access shall be granted if an alternate access control mechanism
+ * is not enabled and the requested access permission bit is set for
+ * the class (file owner class, file group class, or file other class)
+ * to which the process belongs, or if an alternate access control
+ * mechanism is enabled and it allows the requested access; otherwise,
+ * access shall be denied.
+ *
+ * and when I first read this I thought: surely we can't go about using
+ * open(O_WRONLY) to try this test! However the POSIX 1003.1-2001 Rationale
+ * section for test does in fact say:
+ *
+ * On historical BSD systems, test -w directory always returned false
+ * because test tried to open the directory for writing, which always
+ * fails.
+ *
+ * and indeed this is in fact true for Seventh Edition UNIX, UNIX 32V, and UNIX
+ * System III, and thus presumably also for BSD up to and including 4.3.
+ *
+ * Secondly I remembered why using open() and/or access() are bogus. They
+ * don't work right for detecting read and write permissions bits when called
+ * by root.
+ *
+ * Interestingly the 'test' in 4.4BSD was closer to correct (as per
+ * 1003.2-1992) and it was implemented efficiently with stat() instead of
+ * open().
+ *
+ * This was apparently broken in NetBSD around about 1994/06/30 when the old
+ * 4.4BSD implementation was replaced with a (arguably much better coded)
+ * implementation derived from pdksh.
+ *
+ * Note that modern pdksh is yet different again, but still not correct, at
+ * least not w.r.t. 1003.2-1992.
+ *
+ * As I think more about it and read more of the related IEEE docs I don't like
+ * that wording about 'test -r' and 'test -w' in 1003.1-2001 at all. I very
+ * much prefer the original wording in 1003.2-1992. It is much more useful,
+ * and so that's what I've implemented.
+ *
+ * (Note that a strictly conforming implementation of 1003.1-2001 is in fact
+ * totally useless for the case in question since its 'test -w' and 'test -r'
+ * can never fail for root for any existing files, i.e. files for which 'test
+ * -e' succeeds.)
+ *
+ * The rationale for 1003.1-2001 suggests that the wording was "clarified" in
+ * 1003.1-2001 to align with the 1003.2b draft. 1003.2b Draft 12 (July 1999),
+ * which is the latest copy I have, does carry the same suggested wording as is
+ * in 1003.1-2001, with its rationale saying:
+ *
+ * This change is a clarification and is the result of interpretation
+ * request PASC 1003.2-92 #23 submitted for IEEE Std 1003.2-1992.
+ *
+ * That interpretation can be found here:
+ *
+ * http://www.pasc.org/interps/unofficial/db/p1003.2/pasc-1003.2-23.html
+ *
+ * Not terribly helpful, unfortunately. I wonder who that fence sitter was.
+ *
+ * Worse, IMVNSHO, I think the authors of 1003.2b-D12 have mis-interpreted the
+ * PASC interpretation and appear to be gone against at least one widely used
+ * implementation (namely 4.4BSD). The problem is that for file access by root
+ * this means that if test '-r' and '-w' are to behave as if open() were called
+ * then there's no way for a shell script running as root to check if a file
+ * has certain access bits set other than by the grotty means of interpreting
+ * the output of 'ls -l'. This was widely considered to be a bug in V7's
+ * "test" and is, I believe, one of the reasons why direct use of access() was
+ * avoided in some more recent implementations!
+ *
+ * I have always interpreted '-r' to match '-w' and '-x' as per the original
+ * wording in 1003.2-1992, not the other way around. I think 1003.2b goes much
+ * too far the wrong way without any valid rationale and that it's best if we
+ * stick with 1003.2-1992 and test the flags, and not mimic the behaviour of
+ * open() since we already know very well how it will work -- existance of the
+ * file is all that matters to open() for root.
+ *
+ * Unfortunately the SVID is no help at all (which is, I guess, partly why
+ * we're in this mess in the first place :-).
+ *
+ * The SysV implementation (at least in the 'test' builtin in /bin/sh) does use
+ * access(name, 2) even though it also goes to much greater lengths for '-x'
+ * matching the 1003.2-1992 definition (which is no doubt where that definition
+ * came from).
+ *
+ * The ksh93 implementation uses access() for '-r' and '-w' if
+ * (euid==uid&&egid==gid), but uses st_mode for '-x' iff running as root.
+ * i.e. it does strictly conform to 1003.1-2001 (and presumably 1003.2b).
+ */
+static int
+test_access(struct stat *sp, mode_t stmode)
+{
+#ifdef _MSC_VER
+ /* just pretend to be root for now. */
+ stmode = (stmode << 6) | (stmode << 3) | stmode;
+ return !!(sp->st_mode & stmode);
+#else
+ gid_t *groups;
+ register int n;
+ uid_t euid;
+ int maxgroups;
+
+ /*
+ * I suppose we could use access() if not running as root and if we are
+ * running with ((euid == uid) && (egid == gid)), but we've already
+ * done the stat() so we might as well just test the permissions
+ * directly instead of asking the kernel to do it....
+ */
+ euid = geteuid();
+ if (euid == 0) /* any bit is good enough */
+ stmode = (stmode << 6) | (stmode << 3) | stmode;
+ else if (sp->st_uid == euid)
+ stmode <<= 6;
+ else if (sp->st_gid == getegid())
+ stmode <<= 3;
+ else {
+ /* XXX stolen almost verbatim from ksh93.... */
+ /* on some systems you can be in several groups */
+ if ((maxgroups = getgroups(0, NULL)) <= 0)
+ maxgroups = NGROUPS_MAX; /* pre-POSIX system? */
+ groups = xmalloc((maxgroups + 1) * sizeof(gid_t));
+ n = getgroups(maxgroups, groups);
+ while (--n >= 0) {
+ if (groups[n] == sp->st_gid) {
+ stmode <<= 3;
+ break;
+ }
+ }
+ free(groups);
+ }
+
+ return !!(sp->st_mode & stmode);
+#endif
+}
+
+static int
+filstat(char *nm, enum token mode)
+{
+ struct stat s;
+
+ if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
+ return 0;
+
+ switch (mode) {
+ case FILRD:
+ return test_access(&s, S_IROTH);
+ case FILWR:
+ return test_access(&s, S_IWOTH);
+ case FILEX:
+ return test_access(&s, S_IXOTH);
+ case FILEXIST:
+ return 1; /* the successful lstat()/stat() is good enough */
+ case FILREG:
+ return S_ISREG(s.st_mode);
+ case FILDIR:
+ return S_ISDIR(s.st_mode);
+ case FILCDEV:
+#ifdef S_ISCHR
+ return S_ISCHR(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILBDEV:
+#ifdef S_ISBLK
+ return S_ISBLK(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILFIFO:
+#ifdef S_ISFIFO
+ return S_ISFIFO(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILSOCK:
+#ifdef S_ISSOCK
+ return S_ISSOCK(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILSYM:
+#ifdef S_ISLNK
+ return S_ISLNK(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILSUID:
+ return (s.st_mode & S_ISUID) != 0;
+ case FILSGID:
+ return (s.st_mode & S_ISGID) != 0;
+ case FILSTCK:
+#ifdef S_ISVTX
+ return (s.st_mode & S_ISVTX) != 0;
+#else
+ return 0;
+#endif
+ case FILGZ:
+ return s.st_size > (off_t)0;
+ case FILUID:
+ return s.st_uid == geteuid();
+ case FILGID:
+ return s.st_gid == getegid();
+ default:
+ return 1;
+ }
+}
+
+#define VTOC(x) (const unsigned char *)((const struct t_op *)x)->op_text
+
+static int
+compare1(const void *va, const void *vb)
+{
+ const unsigned char *a = va;
+ const unsigned char *b = VTOC(vb);
+
+ return a[0] - b[0];
+}
+
+static int
+compare2(const void *va, const void *vb)
+{
+ const unsigned char *a = va;
+ const unsigned char *b = VTOC(vb);
+ int z = a[0] - b[0];
+
+ return z ? z : (a[1] - b[1]);
+}
+
+static struct t_op const *
+findop(const char *s)
+{
+ if (s[0] == '-') {
+ if (s[1] == '\0')
+ return NULL;
+ if (s[2] == '\0')
+ return bsearch(s + 1, mop2, __arraycount(mop2), sizeof(*mop2), compare1);
+ else if (s[3] != '\0')
+ return NULL;
+ else
+ return bsearch(s + 1, mop3, __arraycount(mop3), sizeof(*mop3), compare2);
+ } else {
+ if (s[1] == '\0')
+ return bsearch(s, cop, __arraycount(cop), sizeof(*cop), compare1);
+ else if (strcmp(s, cop2[0].op_text) == 0)
+ return cop2;
+ else
+ return NULL;
+ }
+}
+
+static enum token
+t_lex(PTESTINSTANCE pThis, char *s)
+{
+ struct t_op const *op;
+
+ if (s == NULL) {
+ pThis->t_wp_op = NULL;
+ return EOI;
+ }
+
+ if ((op = findop(s)) != NULL) {
+ if (!((op->op_type == UNOP && isoperand(pThis)) ||
+ (op->op_num == LPAREN && *(pThis->t_wp+1) == 0))) {
+ pThis->t_wp_op = op;
+ return op->op_num;
+ }
+ }
+ pThis->t_wp_op = NULL;
+ return OPERAND;
+}
+
+static int
+isoperand(PTESTINSTANCE pThis)
+{
+ struct t_op const *op;
+ char *s, *t;
+
+ if ((s = *(pThis->t_wp+1)) == 0)
+ return 1;
+ if ((t = *(pThis->t_wp+2)) == 0)
+ return 0;
+ if ((op = findop(s)) != NULL)
+ return op->op_type == BINOP && (t[0] != ')' || t[1] != '\0');
+ return 0;
+}
+
+/* atoi with error detection */
+static int
+getn(PTESTINSTANCE pThis, const char *s)
+{
+ char *p;
+ long r;
+
+ errno = 0;
+ r = strtol(s, &p, 10);
+
+ if (errno != 0)
+ return errx(pThis->pCtx, -42, "%s: out of range", s);
+
+ while (isspace((unsigned char)*p))
+ p++;
+
+ if (*p)
+ return errx(pThis->pCtx, -42, "%s: bad number", s);
+
+ return (int) r;
+}
+
+static int
+newerf(const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (stat(f1, &b1) == 0 &&
+ stat(f2, &b2) == 0 &&
+ b1.st_mtime > b2.st_mtime);
+}
+
+static int
+olderf(const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (stat(f1, &b1) == 0 &&
+ stat(f2, &b2) == 0 &&
+ b1.st_mtime < b2.st_mtime);
+}
+
+static int
+equalf(const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (stat(f1, &b1) == 0 &&
+ stat(f2, &b2) == 0 &&
+ b1.st_dev == b2.st_dev &&
+ b1.st_ino == b2.st_ino);
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr, "usage: %s expression [-- <prog> [args]]\n", pCtx->pszProgName);
+ return 0; /* only used in --help. */
+}
+