sbase/test.c
sin 8311023781 Fix test(1) semantics
Evan Gates says:

After writing my own test[0] I checked and sbase already has test. I'm
including a patch to remove test from the TODO. I also noticed that
sbase's test handles a few specific cases incorrectly (documentation
at [1]).

test ! = foo
When there are 3 arguments and the second is a valid binary primary
test should perform that binary test. Only if the second argument is
not a valid binary primary and the first is ! should test negate the
two argument test. I've attached a patch that should fix this.

test ! ! !
test ! ! ! !
When there are 3 arguments and the second is not a valid primary and
the first is !, test should return the negation of the remaining two
argument test. In this case sbase's test works correctly for ! and ! !
but fails afterwards as it's not recursive. I don't yet have a patch
for this but I'm working on one.

Then again both of these areas may be places in which worse is better.
[0] 11329f3834/test.c
[1] http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
2014-10-16 10:13:05 +01:00

165 lines
3.1 KiB
C

/* See LICENSE file for copyright and license details. */
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include "util.h"
static char *optexts[] = {
"-eq", "-ge", "-gt",
"-le", "-lt", "-ne",
"=", "!="
};
static bool unary(const char *, const char *);
static bool binary(const char *, const char *, const char *);
static void
usage(void)
{
const char *ket = (*argv0 == '[') ? " ]" : "";
eprintf("usage: %s string%s\n"
" %s [!] [-bcdefgkhLnprSstuwxz] string%s\n", argv0, ket, argv0, ket);
}
int
main(int argc, char *argv[])
{
bool ret = false, not = false;
int i = LEN(optexts);
argv0 = argv[0];
/* [ ... ] alias */
if(!strcmp(argv[0], "[")) {
if(strcmp(argv[argc-1], "]") != 0)
usage();
argc--;
}
if(argc == 4)
for(i = 0; i < LEN(optexts); i++)
if(strcmp(argv[2], optexts[i]) == 0)
break;
if(argc > 1 && !strcmp(argv[1], "!") && i == LEN(optexts)) {
not = true;
argv++;
argc--;
}
switch(argc) {
case 2:
ret = *argv[1] != '\0';
break;
case 3:
ret = unary(argv[1], argv[2]);
break;
case 4:
ret = binary(argv[1], argv[2], argv[3]);
break;
default:
break;
}
if(not)
ret = !ret;
return ret ? 0 : 1;
}
static bool
unary(const char *op, const char *arg)
{
struct stat st;
int r;
if(op[0] != '-' || op[1] == '\0' || op[2] != '\0')
usage();
switch(op[1]) {
case 'b': case 'c': case 'd': case 'f': case 'g':
case 'k': case 'p': case 'S': case 's': case 'u':
if((r = stat(arg, &st)) == -1)
return false; /* -e */
switch(op[1]) {
case 'b':
return S_ISBLK(st.st_mode);
case 'c':
return S_ISCHR(st.st_mode);
case 'd':
return S_ISDIR(st.st_mode);
case 'f':
return S_ISREG(st.st_mode);
case 'g':
return st.st_mode & S_ISGID;
case 'k':
return st.st_mode & S_ISVTX;
case 'p':
return S_ISFIFO(st.st_mode);
case 'S':
return S_ISSOCK(st.st_mode);
case 's':
return st.st_size > 0;
case 'u':
return st.st_mode & S_ISUID;
}
case 'e':
return access(arg, F_OK) == 0;
case 'r':
return access(arg, R_OK) == 0;
case 'w':
return access(arg, W_OK) == 0;
case 'x':
return access(arg, X_OK) == 0;
case 'h': case 'L':
return lstat(arg, &st) == 0 && S_ISLNK(st.st_mode);
case 't':
return isatty((int)estrtol(arg, 0));
case 'n':
return arg[0] != '\0';
case 'z':
return arg[0] == '\0';
default:
usage();
}
return false; /* should not reach */
}
static bool
binary(const char *arg1, const char *op, const char *arg2)
{
int i;
long narg1, narg2;
enum operator { EQ, GE, GT, LE, LT, NE, STREQ, STRNE } oper;
for (i = 0; i < LEN(optexts); i++) {
if (strcmp(op, optexts[i]) != 0)
continue;
oper = i;
switch (oper) {
case STREQ:
return strcmp(arg1, arg2) == 0;
case STRNE:
return strcmp(arg1, arg2) != 0;
default:
narg1 = estrtol(arg1, 0);
narg2 = estrtol(arg2, 0);
switch (oper) {
case EQ:
return narg1 == narg2;
case GE:
return narg1 >= narg2;
case GT:
return narg1 > narg2;
case LE:
return narg1 <= narg2;
case LT:
return narg1 < narg2;
case NE:
return narg1 != narg2;
default:
usage();
}
}
}
return false;
}