/* fully GNU plug-in test, but much cleaner, a little faster, and a lot smaller.
   (100 lines instead of 1000; binary is 4K instead of 18K on an i486-Linux)
   *loosely* based upon 'v7test.c' at uunet, but cleaned up and much enhanced.
                                                            Charles Blake, 1994

test has the following grammar:
expr   ::= bexpr | bexpr -o expr ;
bexpr  ::= primary | primary -a bexpr ;
primary::= unary-operator operand
         | operand binary-operator operand
         | operand
         | "(" expr ")"
         | "!" expr
unary-operator::= "-"+ r|w|x|f|d|s|t|e|S|p|c|b|L|h|k|u|g|O|G|z|n
binary-operator::= -nt|-ot|-ef|=|!=|-eq|-ne|-ge|-gt|-le|-lt
operand ::= <any legal UNIX file name>
*/

#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

enum opnum { EOI,FILRD,FILWR,FILEX,FILND,FILID,FILGZ,FILTT,FILPR,FILSO,FILFF,
        FILCS,FILBS,FILSL,FILHL,FILSB,FILUI,FILGI,FILOU,FILOG,FNEWR,FOLDR,FSAME,
        STZER,STNZE,STEQL,STNEQ,INTEQ,INTNE,INTGE,INTGT,INTLE,INTLT,
        UNEGN,BAND,BOR,LPAREN,RPAREN,OPERAND};

enum optype { UNOP, BINOP, BUNOP, BBINOP, PAREN }; /* maybe UNOP=1 ? */

struct T_sop {
    char *text; enum opnum num; enum optype type;
}T_ops[]={{"-r", FILRD, UNOP},  {"-w", FILWR, UNOP},  {"-x", FILEX, UNOP},
          {"-f", FILND, UNOP},  {"-d", FILID, UNOP},  {"-s", FILGZ, UNOP},   
          {"-t", FILTT, UNOP},  {"-e", FILPR, UNOP},  {"-S", FILSO, UNOP},
          {"-p", FILFF, UNOP},  {"-c", FILCS, UNOP},  {"-b", FILBS, UNOP},
          {"-L", FILSL, UNOP},  {"-h", FILHL, UNOP},  {"-k", FILSB, UNOP},
          {"-u", FILUI, UNOP},  {"-g", FILGI, UNOP},  {"-O", FILOU, UNOP},
          {"-G", FILOG, UNOP},  {"-nt",FNEWR, BINOP}, {"-ot",FOLDR, BINOP},
          {"-ef",FSAME, BINOP}, {"-z", STZER, UNOP},  {"-n", STNZE, UNOP},
          {"=",  STEQL, BINOP}, {"!=", STNEQ, BINOP}, {"-eq",INTEQ, BINOP},
          {"-ne",INTNE, BINOP}, {"-ge",INTGE, BINOP}, {"-gt",INTGT, BINOP},
          {"-le",INTLE, BINOP}, {"-lt",INTLT, BINOP}, {"!",  UNEGN, BUNOP},
          {"-a", BAND,  BBINOP},{"-o", BOR,   BBINOP},{"(",  LPAREN,PAREN},
          {")",  RPAREN,PAREN}, {  0,    0,     0}};
 
char** T_ip;
struct T_sop *T_op;
int T_error=0;

#ifdef ADDON

#include "addon.h"
typedef enum bool { FALSE, TRUE } bool;
extern void set(bool);
void b_test(char* argv[]) {
    int i=0;                   /* truncate off possible trailing ']' s */
    while (argv[i]!=0) i++;
    if (argv[i-1][0]==']') argv[i-1]=0;
    T_ip = &argv[1];
    if (T_error==1) { set(FALSE); return; }
    if ((!(T_expr(T_lex(*T_ip)) && *++T_ip == 0))==0)
        set(TRUE);
    else
        set(FALSE);
}

#else
int main(int argc, char* argv[]) {
    int i=0;
    while (argv[i]!=0) i++;  /* truncate off possible trailing ']' s */
    if (argv[i-1][0]==']') argv[i-1]=0;
    T_ip = &argv[1];
    return(T_error==1 || !(T_expr(T_lex(*T_ip))&& *++T_ip == 0));
}
#endif /* ADDON */

int T_expr(int n) {
    int res;
    if (n==EOI) { T_error=1; return 1; }
    res=T_bexpr(n);
    if (T_lex(*++T_ip)==BOR) return T_expr(T_lex(*++T_ip)) || res;
    T_ip--;
    return res;
}

int T_bexpr(int n) {
    int res;
    if (n == EOI) { T_error=1; return 1;}
    res = T_primary(n);
    if (T_lex(*++T_ip) == BAND) return T_bexpr(T_lex(*++T_ip)) && res;
    T_ip--;
    return res;
}

int T_primary(int n) {
    struct stat s, s2;
    char *opnd1, *opnd2;
    int res;

    if (n==EOI) { T_error=1; return 1;}
    if (n==UNEGN) return !T_expr(T_lex(*++T_ip));
    if (n==LPAREN) {
        res=T_expr(T_lex(*++T_ip));
        if (T_lex(*++T_ip) != RPAREN) { T_error=1; return 1;}
        return res;
    }
    if (n==OPERAND) {
        opnd1= *T_ip;
        (void) T_lex(*++T_ip);
        if (T_op && T_op->type==BINOP) {
            struct T_sop *op = T_op;
            if ((opnd2 = *++T_ip) == (char *)0) { T_error=1; return 1;}
            switch (op->num) {
            case STEQL: return strcmp(opnd1, opnd2) == 0;
            case STNEQ: return strcmp(opnd1, opnd2) != 0;
            case INTEQ: return atoi(opnd1) == atoi(opnd2);
            case INTNE: return atoi(opnd1) != atoi(opnd2);
            case INTGE: return atoi(opnd1) >= atoi(opnd2);
            case INTGT: return atoi(opnd1) > atoi(opnd2);
            case INTLE: return atoi(opnd1) <= atoi(opnd2);
            case INTLT: return atoi(opnd1) < atoi(opnd2);
            case FNEWR: return stat(opnd1,&s)==0 && stat(opnd2,&s2)==0 && s.st_mtime > s2.st_mtime;
            case FOLDR: return stat(opnd1,&s)==0 && stat(opnd2,&s2)==0 && s.st_mtime < s2.st_mtime;
            case FSAME: return stat(opnd1,&s)==0 && stat(opnd2,&s2)==0 && s.st_ino==s2.st_ino && s.st_dev==s2.st_dev;
            }
        }
        T_ip--;
        return strlen(opnd1) > 0;
    }
    /* unary expression */
    if (T_op->type != UNOP || *++T_ip == 0) { T_error=1; return 1;}
    if (n == STZER) return strlen(*T_ip) == 0;
    if (n == STNZE) return strlen(*T_ip) != 0;
    switch (n) {
        case FILRD: return access(*T_ip,R_OK)==0;
        case FILWR: return access(*T_ip,W_OK)==0;
        case FILEX: return access(*T_ip,X_OK)==0;
        case FILND: return stat(*T_ip,&s)==0 && ((s.st_mode & S_IFMT)!=S_IFDIR);
        case FILID: return stat(*T_ip,&s)==0 && ((s.st_mode & S_IFMT)==S_IFDIR);
        case FILGZ: return stat(*T_ip,&s)==0 && (s.st_size > 0L);
        case FILTT: T_error=1; return 1; /* unimplimented */
        case FILPR: return stat(*T_ip,&s)==0;
        case FILSO: return stat(*T_ip,&s)==0 && ((s.st_mode & S_IFMT)==S_IFSOCK);
        case FILFF: return stat(*T_ip,&s)==0 && ((s.st_mode & S_IFMT)==S_IFIFO);
        case FILCS: return stat(*T_ip,&s)==0 && ((s.st_mode & S_IFMT)==S_IFCHR);
        case FILBS: return stat(*T_ip,&s)==0 && ((s.st_mode & S_IFMT)==S_IFBLK);
        case FILSL: return lstat(*T_ip,&s)==0&& ((s.st_mode & S_IFMT)==S_IFLNK);
        case FILHL: return stat(*T_ip,&s)==0 && (s.st_nlink > 1);
        case FILSB: return stat(*T_ip,&s)==0 && (s.st_mode & S_ISVTX)==S_ISVTX;
        case FILUI: return stat(*T_ip,&s)==0 && (s.st_mode & S_ISUID)==S_ISUID;
        case FILGI: return stat(*T_ip,&s)==0 && (s.st_mode & S_ISGID)==S_ISGID;
        case FILOU: return stat(*T_ip,&s)==0 && (s.st_uid==geteuid());
        case FILOG: return stat(*T_ip,&s)==0 && (s.st_uid==geteuid());
    }
}

int T_lex(char* s) {
    struct T_sop *op = T_ops;
    if (s == 0) return EOI;
    while (op->text) {
        if (strcmp(s, op->text) == 0) {
            T_op = op;
            return op->num;
        }
        op++;
    }
    T_op = (struct T_sop *)0;
    return OPERAND;
}
