File:  [MW Coherent from dump] / coherent / b / bin / test.c.steve
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Wed May 29 04:56:36 2019 UTC (7 years, 2 months ago) by root
Branches: MarkWilliams, MAIN
CVS tags: relic, HEAD
coherent

/*
 * cmd/test.c
 * 5/4/93
 * Set status based on specified conditions, mostly related to files.
 * Used for control flow in shell scripts.
 * Cf. POSIX P1003.2/D11.2 section 4.62; extensions marked !POSIX below.
 * Usage: see usage() below.
 * Exit status:
 *	0	expression is true
 *	1	expression is false or absent
 *	2	syntax error or other error
 * This used to be a yacc grammar test.y, but the grammar is not really LR(1);
 * e.g. "-ne" can be used either as an ordinary argument or as a string,
 * which causes great complication in yylex()/yyparse() interaction.
 * This version uses ad hoc recursive parse instead.
 */

#include <stdio.h>
#include <stdlib.h>
#include <access.h>
#include <sys/stat.h>

#define	ERROR	2				/* error exit status */
#define	FALSE	""				/* false (empty) arg */
#define	TRUE	"T"				/* true (nonempty) arg */
#define	equal(s1, s2)	(strcmp((s1), (s2)) == 0)

/* Primary operators. */
typedef	struct	prim	{
	char	*p_name;			/* operator name */
	int	(*p_fn)();			/* function */
}	PRIM;

/* Forward function definitions. */
int	(*is_binary)();
int	(*is_unary)();
int	rparen();
int	test();
int	testsub();
void	usage();

/* Elementary test functions. */
int	x_b();
int	x_c();
int	x_d();
int	x_e();
int	x_ef();
int	x_eq();
int	x_f();
int	x_g();
int	x_ge();
int	x_gt();
int	x_k();
int	x_L();
int	x_le();
int	x_lt();
int	x_n();
int	x_ne();
int	x_nt();
int	x_ot();
int	x_p();
int	x_r();
int	x_s();
int	x_str_eq();
int	x_str_gt();
int	x_str_lt();
int	x_str_ne();
int	x_t();
int	x_u();
int	x_w();
int	x_x();
int	x_z();

/* Globals. */
struct	stat	sb, sb2;		/* stat() buffers */

/* Unary primaries, not including "!"; implicit leading '-'. */
PRIM	uprims[] = {
	"b",	x_b,
	"c",	x_c,
	"d",	x_d,
	"e",	x_e,
	"f",	x_f,			/* POSIX semantics, not BSD */
	"g",	x_g,
	"k",	x_k,			/* !POSIX */
	"L",	x_L,			/* !POSIX */
	"n",	x_n,
	"p",	x_p,
	"r",	x_r,
	"s",	x_s,
	"t",	x_t,			/* POSIX semantics, arg not optional */
	"u",	x_u,
	"w",	x_w,
	"x",	x_x,
	"z",	x_z
};
#define	NUPRIM	(sizeof(uprims)/sizeof(uprims[0]))

/* Binary primaries, not including string binaries (below), "-a", "-o"; implicit leading '-'. */
PRIM	bprims[] = {
	"ef",	x_ef,			/* !POSIX */
	"eq",	x_eq,
	"ge",	x_ge,
	"gt",	x_gt,
	"le",	x_le,
	"lt",	x_lt,
	"ne",	x_ne,
	"nt",	x_nt,			/* !POSIX */
	"ot",	x_ot			/* !POSIX */
};
#define	NBPRIM	(sizeof(bprims)/sizeof(bprims[0]))

/* String binary primaries, no leading '-'. */
PRIM	sprims[] = {
	"=",	x_str_eq,
	">",	x_str_gt,		/* !POSIX */
	"<",	x_str_lt,		/* !POSIX */
	"!=",	x_str_ne
};
#define	NSPRIM	(sizeof(sprims)/sizeof(sprims[0]))

main(argc, argv) register int argc; char *argv[];
{
	register int n;

	--argc;
	if (equal(argv[0], "[")) {
		if (!equal(argv[argc], "]")) {
			fprintf(stderr, "[: missing ]\n");
			exit(ERROR);
		}
		argc--;
	}
	++argv;
	if ((n = test(argc, argv)) == -1)
		usage();		/* syntax error */
	exit(!n);			/* flip returned status */
}

/* Return function pointer if arg is a binary primary, else return NULL. */
int
(*is_binary(s)) register char *s;
{
	register PRIM *pp, *endp;

	if (*s != '-') {	/* no leading '-', search string primaries */
		pp = sprims;
		endp = &sprims[NSPRIM];
	} else {		/* leading '-', search nonstring primaries */
		++s;		/* and skip leading '-' */
		pp = bprims;
		endp = &bprims[NBPRIM];
	}
	for ( ; pp < endp; pp++)
		if (equal(pp->p_name, s))
			return pp->p_fn;
	return NULL;
}

/* Return function pointer if arg is a unary primary, else return NULL. */
int
(*is_unary(s)) register char *s;
{
	register PRIM *pp;

	if (*s++ != '-')
		return NULL;
	for (pp = uprims; pp < &uprims[NUPRIM]; pp++)
		if (equal(pp->p_name, s))
			return pp->p_fn;
	return NULL;
}

/*
 * argv[0] is '(', so find the matching ')' and return its index.
 * Return -1 if not found.
 */
int
rparen(argc, argv) int argc; char *argv[];
{
	register int n, count;

	for (count = n = 1; n < argc; n++) {
		if (equal(argv[n], "("))
			++count;
		else if (equal(argv[n], ")") && --count == 0)
			return n;
	}
	return -1;
}

/*
 * Parse and evaluate the test expression.
 * The order in which subexpressions are tried here determines the parsing.
 * Handle parens, "-a", "-o", "!" directly here.
 * Tricky stuff, this may need some tinkering.
 * Return 1 if true, 0 if false, -1 if syntax error.
 */
int
test(argc, argv) register int argc; char *argv[];
{
	register int (*fnp)();
	register int n, i;

#if	DEBUG
	printf("test(argc=%d, argv={ ", argc);
	for (n = 0; n < argc; n++)
		printf("%s ", argv[n]);
	printf("})\n");
#endif
	if (argc == 0)
		return 0;
	if (argc == 1)
		return argv[0][0] != 0;
	/* We can assume argc >= 2 below here. */
	if (argc == 2 && (fnp = is_unary(argv[0])) != NULL)
		return (*fnp)(argv[1]);
	if (argc == 3 && (fnp = is_binary(argv[1])) != NULL)
		return (*fnp)(argv[0], argv[2]);
	if (argc <= 4 && equal(argv[0], "!")) {
		if (argc == 2)
			return argv[1][0] == 0;
		if (argc == 3 && (fnp = is_unary(argv[1])) != NULL)
			return !(*fnp)(argv[2]);
		if (argc == 4 && (fnp = is_binary(argv[2])) != NULL)
			return !(*fnp)(argv[1], argv[3]);
	}
	if (equal(argv[0], "(")) {
		/* Find the matching ")" and evaluate the subexpression. */
		if ((n = rparen(argc, argv)) == -1)
			return -1;
		else if ((i = test(n - 1, &argv[1])) == -1)
			return -1;
		/* Replace the subexpression by TRUE or FALSE, evaluate the rest. */
		argv[n] = (i) ? TRUE : FALSE;
		return test(argc - n, &argv[n]);
	}
	if ((n = testsub(argc, argv, "-o")) != -1)
		return n;
	if ((n = testsub(argc, argv, "-a")) != -1)
		return n;
	if (equal(argv[0], "!") && (n = test(argc-1, &argv[1])) != -1)
		return !n;
#if	DEBUG
	printf("\ttest(): failed!\n");
#endif
	return -1;
}

/*
 * Try to split test expression involving "-a" or "-o" into subexpressions.
 * The scan is r-to-l to make "-a" and "-o" left associative.
 * Watch out for parens.
 * Return 1 if true, 0 if false, -1 if syntax error.
 */
int
testsub(argc, argv, opt) int argc; char *argv[]; char *opt;
{
	register int n, parens, n1, n2;

	for (parens = 0, n = argc-1; n > 0; n--) {
		if (equal(argv[n], ")"))
			++parens;
		else if (equal(argv[n], "("))
			--parens;
		else if (equal(argv[n], opt) && parens == 0 && n < argc-1) {
			/* Try subdividing at arg n. */
			if ((n1 = test(n, argv)) != -1
			 && (n2 = test(argc-n-1, &argv[n+1])) != -1) {
#if	DEBUG
				printf("\ttestsub(): use %s at arg %d\n", opt, n);
#endif
				return (equal(opt, "-o")) ? (n1 || n2) : (n1 && n2);
			}
		}
	}
	return -1;
}

/* Print verbose usage message and die. */
void
usage()
{
	fprintf(stderr,
		"test: test expression syntax error\n"
		"Usage: test [ expression ]\n"
		);
#if	!DEBUG
	fprintf(stderr,
		"Unary primaries:\n"
		"\t-b file\t\tfile exists and is a block special file\n"
		"\t-c file\t\tfile exists and is a character special file\n"
		"\t-d file\t\tfile exists and is a directory\n"
		"\t-e file\t\tfile exists\n"
		"\t-f file\t\tfile exists and is a regular file\n"
		"\t-g file\t\tfile exists and is setgid\n"
		"\t-k file\t\tfile exists and has sticky bit set\t(not Posix)\n"
		"\t-L file\t\tfile is a link\t\t\t\t(not Posix)\n"
		"\t-n string\tstring length is nonzero\n"
		"\t-p file\t\tfile exists and is a named pipe (FIFO)\n"
		"\t-r file\t\tfile exists and is readable\n"
		"\t-s file\t\tfile exists and has nonzero size\n"
		"\t-t fd\t\tfd is the file descriptor of a terminal\n"
		"\t-u file\t\tfile exists and is setuid\n"
		"\t-w file\t\tfile exists and is writable\n"
		"\t-x file\t\tfile exists and is executable\n"
		"\t-z string\tstring length is zero\n"
		"\tstring\t\tstring is not the empty string\n"
		);
	fprintf(stderr,
		"Binary primaries:\n"
		"\ts1 = s2\t\tstrings s1 and s2 are identical\n"
		"\ts1 != s2\tstrings s1 and s2 are not identical\n"
		"\ts1 < s2\t\tstring s1 is less than s2\t\t(not Posix)\n"
		"\ts1 > s2\t\tstring s1 is greater than s2\t\t(not Posix)\n"
		"\tfile1 -ef file2\tfile1 and file2 are identical\t\t(not Posix)\n"
		"\tn1 -eq n2\tnumbers n1 and n2 are equal\n"
		"\tn1 -ge n2\tnumber n1 is greater than or equal to n2\n"
		"\tn1 -gt n2\tnumber n1 is greater than n2\n"
		"\tn1 -le n2\tnumber n1 is less than or equal to n2\n"
		"\tn1 -lt n2\tnumber n1 is less than n2\n"
		"\tn1 -ne n2\tnumbers n1 and n2 are not equal\n"
		"\tfile1 -nt file2\tfile1 is newer than file2\t\t(not Posix)\n"
		"\tfile1 -ot file2\tfile1 is older than file2\t\t(not Posix)\n"
		);
	fprintf(stderr,
		"Expression grouping:\n"
		"\t! exp\t\texp is false\n"
		"\texp1 -a exp2\texp1 and exp2 are true\t\t\t(not Posix)\n"
		"\texp1 -o exp2\texp1 or exp2 is true\t\t\t(not Posix)\n"
		"\t( exp )\t\tparentheses for grouping\t\t(not Posix)\n"
		);
#endif
	exit(ERROR);
}

/* Elementary test routines; each returns 1 if true, 0 if false. */
/* File exists and is a block special file. */
int
x_b(s) char *s;
{
	return stat(s, &sb)>=0 && (sb.st_mode&S_IFMT)==S_IFBLK;
}

/* File exists and is a character special file. */
int
x_c(s) char *s;
{
	return stat(s, &sb)>=0 && (sb.st_mode&S_IFMT)==S_IFCHR;
}

/* File exists and is a directory. */
int
x_d(s) char *s;
{
	return stat(s, &sb)>=0 && (sb.st_mode&S_IFMT)==S_IFDIR;
}

/* File exists. */
int
x_e(s) char *s;
{
	return stat(s, &sb)>=0;
}

/* Two files are the same. */
int
x_ef(s1, s2) char *s1, *s2;
{
	return stat(s1, &sb)>=0
	    && stat(s2, &sb2)>=0
	    && sb.st_dev==sb2.st_dev && sb.st_ino==sb2.st_ino;
}

/* Two numbers are equal. */
int
x_eq(s1, s2) char *s1, *s2;
{
	return atol(s1) == atol(s2);
}

/* File exists and is an ordinary file. */
/* This is POSIX semantics; BSD uses "file exists and is not a directory." */
int
x_f(s) char *s;
{
	return stat(s, &sb)>=0 && (sb.st_mode&S_IFMT)==S_IFREG;
}

/* File exists and is setgid. */
int
x_g(s) char *s;
{
	return stat(s, &sb)>=0 && (sb.st_mode&S_ISGID)!=0;
}

/* First number is greater than or equal to the second. */
int
x_ge(s1, s2) char *s1, *s2;
{
	return atol(s1) >= atol(s2);
}

/* First number is greater than the second. */
int
x_gt(s1, s2) char *s1, *s2;
{
	return atol(s1) > atol(s2);
}

/* File exists and has sticky bit set. */
int
x_k(s) char *s;
{
	return stat(s, &sb)>=0 && (sb.st_mode&S_ISVTX)!=0;
}

/* File is a link. */
int
x_L(s) char *s;
{
	return stat(s, &sb)>=0 && sb.st_nlink>1;
}

/* First number is less than or equal to the second. */
int
x_le(s1, s2) char *s1, *s2;
{
	return atol(s1) <= atol(s2);
}

/* First number is less than the second. */
int
x_lt(s1, s2) char *s1, *s2;
{
	return atol(s1) < atol(s2);
}

/* String length is non-zero. */
int
x_n(s) char *s;
{
	return s[0] != '\0';
}

/* Two numbers are not equal. */
int
x_ne(s1, s2) char *s1, *s2;
{
	return atol(s1) != atol(s2);
}

/* First file is newer than the second. */
int
x_nt(s1, s2) char *s1, *s2;
{
	return stat(s1, &sb)>=0
	    && stat(s2, &sb2)>=0
	    && sb.st_mtime>sb2.st_mtime;
}

/* First file is older than the second. */
int
x_ot(s1, s2) char *s1, *s2;
{
	return stat(s1, &sb)>=0
	    && stat(s2, &sb2)>=0
	    && sb.st_mtime<sb2.st_mtime;
}

/* File exists and is a named pipe. */
int
x_p(s) char *s;
{
	return stat(s, &sb)>=0 && (sb.st_mode&S_IFMT)==S_IFPIP;
}

/* File exists and is readable. */
int
x_r(s) char *s;
{
	return access(s, AREAD) >= 0;
}

/* File exists and has a non-zero size. */
int
x_s(s) char *s;
{
	return stat(s, &sb)>=0 && sb.st_size>0;
}

/* Two strings are lexicographically equal. */
int
x_str_eq(s1, s2) char *s1, *s2;
{
	return equal(s1, s2);
}

/* First string is lexicographically less than second. */
int
x_str_lt(s1, s2) char *s1, *s2;
{
	return strcmp(s1, s2) < 0;
}

/* First string is lexicographically greater than second. */
int
x_str_gt(s1, s2) char *s1, *s2;
{
	return strcmp(s1, s2) > 0;
}

/* Two strings are lexicographically unequal. */
int
x_str_ne(s1, s2) char *s1, *s2;
{
	return !equal(s1, s2);
}

/* File descriptor is associated with a terminal. */
/* Posix says the fd in "-t fd" arg is required, not optional. */
int
x_t(s) char *s;
{
	return isatty(atoi(s));
}

/* File exists and is setuid. */
int
x_u(s) char *s;
{
	return stat(s, &sb)>=0 && (sb.st_mode&S_ISUID)!=0;
}

/* File exists and is writeable. */
int
x_w(s) char *s;
{
	return access(s, AWRITE) >= 0;
}

/* File exists and is executable. */
int
x_x(s) char *s;
{
	return access(s, AEXEC) >= 0;
}

/* String length is zero. */
int
x_z(s) char *s;
{
	return s[0] == '\0';
}

/* end of cmd/test.c */

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.