diff options
-rw-r--r-- | NEWS | 7 | ||||
-rw-r--r-- | doc/eltest.html | 172 | ||||
-rw-r--r-- | doc/index.html | 3 | ||||
-rw-r--r-- | doc/upgrade.html | 3 | ||||
-rw-r--r-- | package/deps.mak | 3 | ||||
-rw-r--r-- | package/info | 2 | ||||
-rw-r--r-- | package/modes | 1 | ||||
-rw-r--r-- | package/targets.mak | 1 | ||||
-rw-r--r-- | src/execline/deps-exe/eltest | 1 | ||||
-rw-r--r-- | src/execline/eltest.c | 505 |
10 files changed, 692 insertions, 6 deletions
@@ -1,9 +1,10 @@ Changelog for execline. -In 2.9.0.2 +In 2.9.1.0 ---------- - Adaptation to skalibs-2.9.0.2. + - New program: eltest. In 2.9.0.1 @@ -16,8 +17,8 @@ In 2.9.0.0 ---------- - Bugfixes. - - New -a/-o options to wait (-o waits for one process only) - - wait now exits 99 on timeout + - New -a/-o options to wait (-o waits for one process only). + - wait now exits 99 on timeout. In 2.8.3.0 diff --git a/doc/eltest.html b/doc/eltest.html new file mode 100644 index 0000000..068cd4c --- /dev/null +++ b/doc/eltest.html @@ -0,0 +1,172 @@ +<html> + <head> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>execline: the eltest program</title> + <meta name="Description" content="execline: the eltest program" /> + <meta name="Keywords" content="execline command eltest test" /> + <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">execline</a><br /> +<a href="//skarnet.org/software/">Software</a><br /> +<a href="//skarnet.org/">skarnet.org</a> +</p> + +<h1> The <tt>eltest</tt> program </h1> + +<p> + eltest evaluates an expression and indicates the result via its +exit status. +</p> + +<h2> Interface </h2> + +<pre> + eltest <em>expression...</em> +</pre> + +<p> + <tt>eltest</tt> acts as the generic POSIX +<a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html">test</a> utility, +but it diverges from the specification on how it parses ambiguous arguments, see below. +</p> + +<p> + <tt>eltest</tt> supports all the standard +<a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html">test</a> +operands, plus all the extensions from +<a href="https://man7.org/linux/man-pages/man1/test.1.html">GNU test</a>, plus a few +extensions from the <tt>test</tt> builtin from +<a href="https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Bash-Conditional-Expressions">bash</a>. +The extensions to POSIX <tt>test</tt> are listed below. +</p> + +<p> + <tt>eltest</tt> accepts an arbitrary number of arguments and, if the expression is +valid, always returns the result of the expression no matter how complex it +is. +</p> + +<h2> Exit codes </h2> + +<ul> + <li> 0: the test is true </li> + <li> 1: the test is false </li> + <li> 100: wrong usage </li> + <li> 101: internal error (should never happen, warrants a bug-report) </li> + <li> 111: system call failure </li> +</ul> + +<h2> Posixness </h2> + +<p> + <tt>eltest</tt> <strong>is not</strong> suitable as a Single Unix +<a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html">test</a> +program, due to the way it disambiguates between arguments and operators, see below. +However, if you never use arguments that start with a backslash, or that have the +same name as an existing operator, then +<tt>eltest</tt> exhibits the same behaviour as <tt>test</tt>. +</p> + +<h2> Extensions to POSIX </h2> + +<ul> + <li> <tt><em>expr1</em> -a <em>expr2</em></tt> : +tests whether <em>expr1</em> <strong>and</strong> <em>expr2</em> are true. +If <em>expr1</em> is false, then <em>expr2</em> is not evaluated. </li> + <li> <tt><em>expr1</em> -o <em>expr2</em></tt> : +tests whether <em>expr1</em> <strong>or</strong> <em>expr2</em> is true. +If <em>expr1</em> is true, then <em>expr2</em> is not evaluated. </li> + <li> <tt>-k <em>file</em></tt> : tests whether <em>file</em> +has the sticky bit. </li> + <li> <tt>-O <em>file</em></tt> : tests whether <em>file</em> +is owned by the effective uid of the current process. </li> + <li> <tt>-U <em>file</em></tt> : same. </li> + <li> <tt>-G <em>file</em></tt> : tests whether <em>file</em>'s gid +is the effective gid of the current process. </li> + <li> <tt>-N <em>file</em></tt> : tests whether <em>file</em> exists +and has been modified since it was last read. </li> + <li> <tt><em>file1</em> -nt <em>file2</em></tt> : +tests whether <em>file1</em> has a (strictly) newer modification date than <em>file2</em>. </li> + <li> <tt><em>file1</em> -ot <em>file2</em></tt> : +tests whether <em>file1</em> has a (strictly) older modification date than <em>file2</em>. </li> + <li> <tt><em>file1</em> -ef <em>file2</em></tt> : +tests whether <em>file1</em> and <em>file2</em> are physically the same +file (same device and inode numbers). </li> + <li> <tt>-v <em>var</em></tt> : tests whether the +<em>var</em> variable is defined in the current environment. </li> + <li> <tt><em>string</em> =~ <em>pattern</em></tt> : +tries to match <em>string</em> against extended regular expression +<em>pattern</em>. True if any part of <em>string</em> matches <em>pattern</em>; +in order to match whole strings, you must anchor <em>pattern</em> with +<tt>^</tt> and <tt>$</tt> markers. </li> +</ul> + +<h2> Argument disambiguation </h2> + +<p> + Unlike <a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html">test</a>, +which has different fixed syntax trees depending on the number of arguments it receives and +has undefined behaviour when called with more than 5 arguments, <tt>eltest</tt> accepts any +number of arguments and builds its syntax trees on the fly. This means that expressions such +as <tt>-n = -n</tt> cannot be automatically disambiguated: <tt>eltest</tt> does not know that +there are 3 arguments, so when it reads the first <tt>-n</tt> it assumes that it is an unary +operator, then when it reads <tt>=</tt> it assumes it is the argument to <tt>-n</tt>, then +when it reads the second <tt>-n</tt> it exits with a syntax error. +</p> + +<p> + Doing otherwise would result in a combinatory explosion of possible syntax trees, making +it easy for users to trigger unbounded RAM consumption, and turning a simple utility into +a programming nightmare. This is why POSIX +<a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html">test</a> +is so restricted. But we don't want the same restrictions. +</p> + +<p> + So, instead, <tt>eltest</tt> provides the user with a mechanism to make sure that +operands are never mistaken for operators: +</p> + +<ul> + <li> An word that looks like an operator will always be interpreted like an operator. +So, expressions like <tt>-n = -n</tt> will result in a syntax error, because the +first <tt>-n</tt> will never be understood as data for the <tt>=</tt> operator. </li> + <li> A word that starts with a <tt>\</tt> (backslash) will always be interpreted +like data, never like an operator, and the backslash will be removed. This +means: <tt>\-n = \-n</tt> is a valid expression testing the equality between +the strings <tt>-n</tt> and </tt>-n</tt>. + <ul> + <li> Be aware that execline as well as the shell use one backlash for their own +unquoting mechanism, so when using backslashes in an execline or shell script, they +must be doubled. You would probably need to type something like <tt>\\-n = \\-n</tt>. + </ul> </li> + <li> So, if your script tests equality between <tt>$a</tt> and <tt>$b</tt>, and there's +a possiblity that the contents of these variables look like <tt>eltest</tt> operators, +the proper syntax would be: <tt>eltest \\${a} = \\${b}</tt>. </li> +</ul> + +<p> + Note that these details are irrelevant to a huge majority of <tt>eltest</tt> use +cases, because most of the time users only need a simple test +such as <tt>eltest -r ${file}</tt> to check that <tt>$file</tt> is readable, and +there's no possible ambiguity. So don't panic over this. +</p> + +<h2> Notes </h2> + +<ul> + <li> <tt>eltest</tt> is a replacement for the ill-named, and now deprecated, +<a href="https://skarnet.org/software/s6-portable-utils/s6-test.html">s6-test</a> +program, part of the (just as ill-named) +<a href="https://skarnet.org/software/s6-portable-utils/">s6-portable-utils</a> +package. It is too valuable a utility to be part of a marginal package, and +has nothing to do with <a href="https://skarnet.org/software/s6/">s6</a>. </li> +</ul> + +</body> +</html> diff --git a/doc/index.html b/doc/index.html index f9c985a..5d2f402 100644 --- a/doc/index.html +++ b/doc/index.html @@ -77,7 +77,7 @@ want nsswitch-like functionality: <h3> Download </h3> <ul> - <li> The current released version of execline is <a href="execline-2.9.0.2.tar.gz">2.9.0.2</a>. </li> + <li> The current released version of execline is <a href="execline-2.9.1.0.tar.gz">2.9.1.0</a>. </li> <li> Alternatively, you can checkout a copy of the <a href="//git.skarnet.org/cgi-bin/cgit.cgi/execline/">execline git repository</a>: @@ -194,6 +194,7 @@ the previous versions of execline and the current one. </li> (Miscellaneous) </p> <ul> +<li><a href="eltest.html">The <tt>eltest</tt> program</a></li> <li><a href="homeof.html">The <tt>homeof</tt> program</a></li> </ul> diff --git a/doc/upgrade.html b/doc/upgrade.html index 26e119d..493c6e1 100644 --- a/doc/upgrade.html +++ b/doc/upgrade.html @@ -18,13 +18,14 @@ <h1> What has changed in execline </h1> -<h2> in 2.9.0.2 </h2> +<h2> in 2.9.1.0 </h2> <ul> <li> <a href="//skarnet.org/software/skalibs/">skalibs</a> dependency bumped to 2.13.0.0. </li> <li> <a href="//skarnet.org/software/nsss/">nsss</a> optional dependency bumped to 0.2.0.2. </li> + <li> New program: <a href="eltest.html">eltest</a>. </li> </ul> <h2> in 2.9.0.1 </h2> diff --git a/package/deps.mak b/package/deps.mak index 17d654e..d9ab00a 100644 --- a/package/deps.mak +++ b/package/deps.mak @@ -10,6 +10,7 @@ src/execline/dollarat.o src/execline/dollarat.lo: src/execline/dollarat.c src/execline/elgetopt.o src/execline/elgetopt.lo: src/execline/elgetopt.c src/include/execline/execline.h src/execline/elgetpositionals.o src/execline/elgetpositionals.lo: src/execline/elgetpositionals.c src/include-local/exlsn.h src/execline/elglob.o src/execline/elglob.lo: src/execline/elglob.c src/include-local/exlsn.h +src/execline/eltest.o src/execline/eltest.lo: src/execline/eltest.c src/execline/emptyenv.o src/execline/emptyenv.lo: src/execline/emptyenv.c src/include/execline/execline.h src/execline/envfile.o src/execline/envfile.lo: src/execline/envfile.c src/execline/exec.o src/execline/exec.lo: src/execline/exec.c @@ -93,6 +94,8 @@ elgetpositionals: EXTRA_LIBS := -lskarnet elgetpositionals: src/execline/elgetpositionals.o ${LIBEXECLINE} elglob: EXTRA_LIBS := -lskarnet elglob: src/execline/elglob.o ${LIBEXECLINE} +eltest: EXTRA_LIBS := -lskarnet +eltest: src/execline/eltest.o emptyenv: EXTRA_LIBS := -lskarnet emptyenv: src/execline/emptyenv.o ${LIBEXECLINE} envfile: EXTRA_LIBS := -lskarnet diff --git a/package/info b/package/info index 2ff5910..5a2157b 100644 --- a/package/info +++ b/package/info @@ -1,4 +1,4 @@ package=execline -version=2.9.0.2 +version=2.9.1.0 category=admin package_macro_name=EXECLINE diff --git a/package/modes b/package/modes index 17c52ce..78671a7 100644 --- a/package/modes +++ b/package/modes @@ -6,6 +6,7 @@ dollarat 0755 elgetopt 0755 elgetpositionals 0755 elglob 0755 +eltest 0755 execline-cd 0755 execline-umask 0755 emptyenv 0755 diff --git a/package/targets.mak b/package/targets.mak index a0e5970..0b41b79 100644 --- a/package/targets.mak +++ b/package/targets.mak @@ -7,6 +7,7 @@ dollarat \ elgetopt \ elgetpositionals \ elglob \ +eltest \ emptyenv \ envfile \ exec \ diff --git a/src/execline/deps-exe/eltest b/src/execline/deps-exe/eltest new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/execline/deps-exe/eltest @@ -0,0 +1 @@ +-lskarnet diff --git a/src/execline/eltest.c b/src/execline/eltest.c new file mode 100644 index 0000000..b14318b --- /dev/null +++ b/src/execline/eltest.c @@ -0,0 +1,505 @@ +/* ISC license. */ + +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <regex.h> + +#include <skalibs/posixplz.h> +#include <skalibs/types.h> +#include <skalibs/strerr.h> +#include <skalibs/djbunix.h> + +enum opnum +{ + T_NOT, + T_AND, + T_OR, + T_LEFTP, + T_RIGHTP, + T_BLOCK, + T_CHAR, + T_DIR, + T_EXIST, + T_REGULAR, + T_SGID, + T_SYMLINK, + T_STICKY, + T_NONZERO, + T_FIFO, + T_READABLE, + T_NONZEROFILE, + T_TERM, + T_SUID, + T_WRITABLE, + T_EXECUTABLE, + T_ZERO, + T_EUID, + T_EGID, + T_SOCKET, + T_MODIFIED, + T_NEWER, + T_OLDER, + T_DEVINO, + T_STREQUAL, + T_STRNEQUAL, + T_STRLESSER, + T_STRLESSERE, + T_STRGREATER, + T_STRGREATERE, + T_NUMEQUAL, + T_NUMNEQUAL, + T_NUMGREATER, + T_NUMGREATERE, + T_NUMLESSER, + T_NUMLESSERE, + T_ENV, + T_MATCH +} ; + +struct token +{ + char const *string ; + enum opnum op ; + unsigned int type ; +} ; + +struct node +{ + enum opnum op ; + unsigned int type ; + unsigned int arg1 ; + unsigned int arg2 ; + char const *data ; +} ; + +static unsigned int lex (struct node *tree, char const *const *argv) +{ + static struct token const tokens[46] = + { + { "-n", T_NONZERO, 2 }, + { "-z", T_ZERO, 2 }, + { "=", T_STREQUAL, 3 }, + { "!=", T_STRNEQUAL, 3 }, + { "-eq", T_NUMEQUAL, 3 }, + { "-ne", T_NUMNEQUAL, 3 }, + { "-gt", T_NUMGREATER, 3 }, + { "-ge", T_NUMGREATERE, 3 }, + { "-lt", T_NUMLESSER, 3 }, + { "-le", T_NUMLESSERE, 3 }, + { "-f", T_REGULAR, 2 }, + { "-h", T_SYMLINK, 2 }, + { "-L", T_SYMLINK, 2 }, + { "-e", T_EXIST, 2 }, + { "-k", T_STICKY, 2 }, + { "-a", T_AND, 7 }, + { "-o", T_OR, 8 }, + { "!", T_NOT, 6 }, + { "(", T_LEFTP, 4 }, + { ")", T_RIGHTP, 5 }, + { "-b", T_BLOCK, 2 }, + { "-c", T_CHAR, 2 }, + { "-d", T_DIR, 2 }, + { "-g", T_SGID, 2 }, + { "-p", T_FIFO, 2 }, + { "-r", T_READABLE, 2 }, + { "-s", T_NONZEROFILE, 2 }, + { "-t", T_TERM, 2 }, + { "-u", T_SUID, 2 }, + { "-w", T_WRITABLE, 2 }, + { "-x", T_EXECUTABLE, 2 }, + { "-O", T_EUID, 2 }, + { "-U", T_EUID, 2 }, + { "-G", T_EGID, 2 }, + { "-S", T_SOCKET, 2 }, + { "-N", T_MODIFIED, 2 }, + { "-nt", T_NEWER, 3 }, + { "-ot", T_OLDER, 3 }, + { "-ef", T_DEVINO, 3 }, + { "<", T_STRLESSER, 3 }, + { "<=", T_STRLESSERE, 3 }, + { ">", T_STRGREATER, 3 }, + { ">=", T_STRGREATERE, 3 }, + { "-v", T_ENV, 2 }, + { "=~", T_MATCH, 3 }, + { 0, 0, 0 } + } ; + unsigned int pos = 0 ; + + for (; argv[pos] ; pos++) + { + unsigned int i = 0 ; + tree[pos].data = argv[pos] ; + for (i = 0 ; tokens[i].string ; i++) + if (!strcmp(argv[pos], tokens[i].string)) + { + tree[pos].op = tokens[i].op ; + tree[pos].type = tokens[i].type ; + break ; + } + if (!tokens[i].string) + { + tree[pos].op = T_NONZERO ; + tree[pos].type = 0 ; + tree[pos].arg1 = pos ; + if (*(argv[pos]) == '\\') tree[pos].data++ ; + } + } + return pos ; +} + +static unsigned int parse (struct node *tree, unsigned int n) +{ + static char const table[9][13] = + { + "xssssxsssxxxx", + "xxxxxaxxxxxxx", + "xsxxsxsssxxxx", + "sxxxxxxxxxxxx", + "xsxxsxsssxxxx", + "nxxxxNxxxAOEs", + "xsxxsxsssxxxx", + "nxxxxNxxxAsxx", + "nxxxxNxxxAOsx" + } ; + + unsigned int stack[n+2] ; + unsigned int sp = 0, pos = 0 ; + int cont = 1 ; + + stack[0] = n+1 ; + tree[n].type = 5 ; /* add ) for the final reduce */ + tree[n+1].type = 1 ; /* add EOF */ + + while (cont) + { + switch (table[tree[pos].type][tree[stack[sp]].type]) + { + case 'x' : /* error */ + { + char fmt[UINT_FMT] ; + fmt[uint_fmt(fmt, pos)] = 0 ; + strerr_dief2x(100, "parse error at argument ", fmt) ; + break ; + } + case 'a' : /* accept */ + { + cont = 0 ; + break ; + } + case 's' : /* shift */ + { + stack[++sp] = pos++ ; + break ; + } + case 'n' : /* reduce -> expr without nots, from atom */ + { + switch (tree[stack[sp-1]].type) + { + case 2 : + { + tree[stack[sp-1]].arg1 = stack[sp] ; + sp-- ; + break ; + } + case 3 : + { + tree[stack[sp-1]].arg1 = stack[sp-2] ; + tree[stack[sp-1]].arg2 = stack[sp] ; + stack[sp-2] = stack[sp-1] ; + sp -= 2 ; + break ; + } + /* default : assert: its a zero */ + } + tree[stack[sp]].type = 9 ; + while (tree[stack[sp-1]].type == 6) + { + tree[stack[sp-1]].type = 9 ; + tree[stack[sp-1]].arg1 = stack[sp] ; + sp-- ; + } + break ; + } + case 'N' : /* reduce -> expr without nots, from expr */ + { + if (tree[stack[sp-2]].type != 4) + { + char fmt[UINT_FMT] ; + fmt[uint_fmt(fmt, pos)] = 0 ; + strerr_dief2x(100, "parse error: bad right parenthesis at argument ", fmt) ; + } + stack[sp-2] = stack[sp-1] ; + sp -= 2 ; + tree[stack[sp]].type = 9 ; + while (tree[stack[sp-1]].type == 6) + { + tree[stack[sp-1]].type = 9 ; + tree[stack[sp-1]].arg1 = stack[sp] ; + sp-- ; + } + break ; + } + case 'A' : /* reduce -> exprs without ands */ + { + if (tree[stack[sp-1]].type == 7) + { + tree[stack[sp-1]].arg1 = stack[sp-2] ; + tree[stack[sp-1]].arg2 = stack[sp] ; + stack[sp-2] = stack[sp-1] ; + sp -= 2 ; + } + tree[stack[sp]].type = 10 ; + break ; + } + case 'O' : /* reduce -> expr without ors */ + { + if (tree[stack[sp-1]].type == 8) + { + tree[stack[sp-1]].arg1 = stack[sp-2] ; + tree[stack[sp-1]].arg2 = stack[sp] ; + stack[sp-2] = stack[sp-1] ; + sp -= 2 ; + } + tree[stack[sp]].type = 11 ; + break ; + } + case 'E' : /* reduce -> expr */ + { + tree[stack[sp]].type = 12 ; + break ; + } + default : /* can't happen */ + strerr_dief1x(101, "internal error, please submit a bug-report.") ; + } + } + if (sp != 2) strerr_dief1x(100, "parse error: too many left parentheses") ; + return stack[1] ; +} + +static int run (struct node const *tree, unsigned int root) +{ + switch (tree[root].op) + { + case T_NOT : + return !run(tree, tree[root].arg1) ; + case T_AND : + return run(tree, tree[root].arg1) && run(tree, tree[root].arg2) ; + case T_OR : + return run(tree, tree[root].arg1) || run(tree, tree[root].arg2) ; + case T_EXIST : + return access(tree[tree[root].arg1].data, F_OK) == 0 ; + case T_BLOCK : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return S_ISBLK(st.st_mode) ; + } + case T_CHAR : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return S_ISCHR(st.st_mode) ; + } + case T_DIR : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return S_ISDIR(st.st_mode) ; + } + case T_REGULAR : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return S_ISREG(st.st_mode) ; + } + case T_FIFO : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return S_ISFIFO(st.st_mode) ; + } + case T_SOCKET : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return S_ISSOCK(st.st_mode) ; + } + case T_SYMLINK : + { + struct stat st ; + if (lstat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return S_ISLNK(st.st_mode) ; + } + case T_SGID : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return (st.st_mode & S_ISGID) ; + } + case T_SUID : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return (st.st_mode & S_ISUID) ; + } + case T_STICKY : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return (st.st_mode & S_ISVTX) ; + } + case T_NONZEROFILE : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return (st.st_size > 0) ; + } + case T_MODIFIED : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return (st.st_mtime > st.st_atime) ; + } + case T_EUID : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return st.st_uid == geteuid() ; + } + case T_EGID : + { + struct stat st ; + if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ; + return st.st_gid == getegid() ; + } + case T_READABLE : + return access(tree[tree[root].arg1].data, R_OK) == 0 ; + case T_WRITABLE : + return access(tree[tree[root].arg1].data, W_OK) == 0 ; + case T_EXECUTABLE : + return access(tree[tree[root].arg1].data, X_OK) == 0 ; + case T_NEWER : + { + struct stat st1, st2 ; + if (stat(tree[tree[root].arg1].data, &st1) == -1) return 0 ; + if (stat(tree[tree[root].arg2].data, &st2) == -1) return 1 ; + return st1.st_mtime > st2.st_mtime ; + } + case T_OLDER : + { + struct stat st1, st2 ; + if (stat(tree[tree[root].arg1].data, &st1) == -1) return 1 ; + if (stat(tree[tree[root].arg2].data, &st2) == -1) return 0 ; + return st1.st_mtime < st2.st_mtime ; + } + case T_DEVINO : + { + struct stat st1, st2 ; + if (stat(tree[tree[root].arg1].data, &st1) == -1) return 0 ; + if (stat(tree[tree[root].arg2].data, &st2) == -1) return 1 ; + return (st1.st_dev == st2.st_dev) && (st1.st_ino == st2.st_ino) ; + } + case T_TERM : + { + unsigned int fd ; + if (!uint0_scan(tree[tree[root].arg1].data, &fd)) + strerr_dief2x(100, tree[root].data, " requires an integer argument") ; + return isatty(fd) ; + } + case T_NONZERO : + return tree[tree[root].arg1].data[0] ; + case T_ZERO : + return !tree[tree[root].arg1].data[0] ; + case T_STREQUAL : + return !strcmp(tree[tree[root].arg1].data, tree[tree[root].arg2].data) ; + case T_STRNEQUAL : + return !!strcmp(tree[tree[root].arg1].data, tree[tree[root].arg2].data) ; + case T_STRLESSER : + return strcmp(tree[tree[root].arg1].data, tree[tree[root].arg2].data) < 0 ; + case T_STRLESSERE : + return strcmp(tree[tree[root].arg1].data, tree[tree[root].arg2].data) <= 0 ; + case T_STRGREATER : + return strcmp(tree[tree[root].arg1].data, tree[tree[root].arg2].data) > 0 ; + case T_STRGREATERE : + return strcmp(tree[tree[root].arg1].data, tree[tree[root].arg2].data) >= 0 ; + case T_NUMEQUAL : + { + int n1, n2 ; + if (!int_scan(tree[tree[root].arg1].data, &n1) + || !int_scan(tree[tree[root].arg2].data, &n2)) + goto errorint ; + return n1 == n2 ; + } + case T_NUMNEQUAL : + { + int n1, n2 ; + if (!int_scan(tree[tree[root].arg1].data, &n1) + || !int_scan(tree[tree[root].arg2].data, &n2)) + goto errorint ; + return n1 != n2 ; + } + case T_NUMGREATER : + { + int n1, n2 ; + if (!int_scan(tree[tree[root].arg1].data, &n1) + || !int_scan(tree[tree[root].arg2].data, &n2)) + goto errorint ; + return n1 > n2 ; + } + case T_NUMGREATERE : + { + int n1, n2 ; + if (!int_scan(tree[tree[root].arg1].data, &n1) + || !int_scan(tree[tree[root].arg2].data, &n2)) + goto errorint ; + return n1 >= n2 ; + } + case T_NUMLESSER : + { + int n1, n2 ; + if (!int_scan(tree[tree[root].arg1].data, &n1) + || !int_scan(tree[tree[root].arg2].data, &n2)) + goto errorint ; + return n1 < n2 ; + } + case T_NUMLESSERE : + { + int n1, n2 ; + if (!int_scan(tree[tree[root].arg1].data, &n1) + || !int_scan(tree[tree[root].arg2].data, &n2)) + goto errorint ; + return n1 <= n2 ; + } + case T_ENV : + return !!getenv(tree[tree[root].arg1].data) ; + case T_MATCH : + { + regex_t re ; + int r = skalibs_regcomp(&re, tree[tree[root].arg2].data, REG_EXTENDED | REG_NOSUB) ; + if (r) + { + char buf[256] ; + regerror(r, &re, buf, 256) ; + strerr_diefu4x(r == REG_ESPACE ? 111 : 100, "compile ", tree[tree[root].arg2].data, " into a regular expression: ", buf) ; + } + r = regexec(&re, tree[tree[root].arg1].data, 0, 0, 0) ; + regfree(&re) ; + return !r ; + } + default: + strerr_dief1x(101, "operation not implemented") ; + } + +errorint: + strerr_dief2x(100, tree[root].data, " requires integer arguments") ; +} + +int main (int argc, char const *const *argv) +{ + struct node tree[argc+2] ; + if (argc <= 1) return 1 ; + PROG = "eltest" ; + return !run(tree, parse(tree, lex(tree, argv+1))) ; +} |