xref: /minix3/bin/test/test.c (revision 02efe53e8c43842ad2e7f1bfd138e4a74f48d85f)
1*02efe53eSSevan Janiyan /* $NetBSD: test.c,v 1.41 2016/09/05 01:00:07 sevan Exp $ */
23a19ae75SLionel Sambuc 
33a19ae75SLionel Sambuc /*
43a19ae75SLionel Sambuc  * test(1); version 7-like  --  author Erik Baalbergen
53a19ae75SLionel Sambuc  * modified by Eric Gisin to be used as built-in.
63a19ae75SLionel Sambuc  * modified by Arnold Robbins to add SVR3 compatibility
73a19ae75SLionel Sambuc  * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
83a19ae75SLionel Sambuc  * modified by J.T. Conklin for NetBSD.
93a19ae75SLionel Sambuc  *
103a19ae75SLionel Sambuc  * This program is in the Public Domain.
113a19ae75SLionel Sambuc  */
123a19ae75SLionel Sambuc 
133a19ae75SLionel Sambuc #include <sys/cdefs.h>
143a19ae75SLionel Sambuc #ifndef lint
15*02efe53eSSevan Janiyan __RCSID("$NetBSD: test.c,v 1.41 2016/09/05 01:00:07 sevan Exp $");
163a19ae75SLionel Sambuc #endif
173a19ae75SLionel Sambuc 
183a19ae75SLionel Sambuc #include <sys/stat.h>
193a19ae75SLionel Sambuc #include <sys/types.h>
203a19ae75SLionel Sambuc 
213a19ae75SLionel Sambuc #include <ctype.h>
223a19ae75SLionel Sambuc #include <err.h>
233a19ae75SLionel Sambuc #include <errno.h>
243a19ae75SLionel Sambuc #include <limits.h>
253a19ae75SLionel Sambuc #include <locale.h>
263a19ae75SLionel Sambuc #include <stdio.h>
273a19ae75SLionel Sambuc #include <stdlib.h>
283a19ae75SLionel Sambuc #include <string.h>
293a19ae75SLionel Sambuc #include <unistd.h>
303a19ae75SLionel Sambuc #include <stdarg.h>
313a19ae75SLionel Sambuc 
323a19ae75SLionel Sambuc /* test(1) accepts the following grammar:
333a19ae75SLionel Sambuc 	oexpr	::= aexpr | aexpr "-o" oexpr ;
343a19ae75SLionel Sambuc 	aexpr	::= nexpr | nexpr "-a" aexpr ;
353a19ae75SLionel Sambuc 	nexpr	::= primary | "!" primary
363a19ae75SLionel Sambuc 	primary	::= unary-operator operand
373a19ae75SLionel Sambuc 		| operand binary-operator operand
383a19ae75SLionel Sambuc 		| operand
393a19ae75SLionel Sambuc 		| "(" oexpr ")"
403a19ae75SLionel Sambuc 		;
413a19ae75SLionel Sambuc 	unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
423a19ae75SLionel Sambuc 		"-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
433a19ae75SLionel Sambuc 
443a19ae75SLionel Sambuc 	binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
453a19ae75SLionel Sambuc 			"-nt"|"-ot"|"-ef";
463a19ae75SLionel Sambuc 	operand ::= <any legal UNIX file name>
473a19ae75SLionel Sambuc */
483a19ae75SLionel Sambuc 
493a19ae75SLionel Sambuc enum token {
503a19ae75SLionel Sambuc 	EOI,
513a19ae75SLionel Sambuc 	FILRD,
523a19ae75SLionel Sambuc 	FILWR,
533a19ae75SLionel Sambuc 	FILEX,
543a19ae75SLionel Sambuc 	FILEXIST,
553a19ae75SLionel Sambuc 	FILREG,
563a19ae75SLionel Sambuc 	FILDIR,
573a19ae75SLionel Sambuc 	FILCDEV,
583a19ae75SLionel Sambuc 	FILBDEV,
593a19ae75SLionel Sambuc 	FILFIFO,
603a19ae75SLionel Sambuc 	FILSOCK,
613a19ae75SLionel Sambuc 	FILSYM,
623a19ae75SLionel Sambuc 	FILGZ,
633a19ae75SLionel Sambuc 	FILTT,
643a19ae75SLionel Sambuc 	FILSUID,
653a19ae75SLionel Sambuc 	FILSGID,
663a19ae75SLionel Sambuc 	FILSTCK,
673a19ae75SLionel Sambuc 	FILNT,
683a19ae75SLionel Sambuc 	FILOT,
693a19ae75SLionel Sambuc 	FILEQ,
703a19ae75SLionel Sambuc 	FILUID,
713a19ae75SLionel Sambuc 	FILGID,
723a19ae75SLionel Sambuc 	STREZ,
733a19ae75SLionel Sambuc 	STRNZ,
743a19ae75SLionel Sambuc 	STREQ,
753a19ae75SLionel Sambuc 	STRNE,
763a19ae75SLionel Sambuc 	STRLT,
773a19ae75SLionel Sambuc 	STRGT,
783a19ae75SLionel Sambuc 	INTEQ,
793a19ae75SLionel Sambuc 	INTNE,
803a19ae75SLionel Sambuc 	INTGE,
813a19ae75SLionel Sambuc 	INTGT,
823a19ae75SLionel Sambuc 	INTLE,
833a19ae75SLionel Sambuc 	INTLT,
843a19ae75SLionel Sambuc 	UNOT,
853a19ae75SLionel Sambuc 	BAND,
863a19ae75SLionel Sambuc 	BOR,
873a19ae75SLionel Sambuc 	LPAREN,
883a19ae75SLionel Sambuc 	RPAREN,
893a19ae75SLionel Sambuc 	OPERAND
903a19ae75SLionel Sambuc };
913a19ae75SLionel Sambuc 
923a19ae75SLionel Sambuc enum token_types {
933a19ae75SLionel Sambuc 	UNOP,
943a19ae75SLionel Sambuc 	BINOP,
953a19ae75SLionel Sambuc 	BUNOP,
963a19ae75SLionel Sambuc 	BBINOP,
973a19ae75SLionel Sambuc 	PAREN
983a19ae75SLionel Sambuc };
993a19ae75SLionel Sambuc 
1003a19ae75SLionel Sambuc struct t_op {
1013a19ae75SLionel Sambuc 	const char *op_text;
1023a19ae75SLionel Sambuc 	short op_num, op_type;
1033a19ae75SLionel Sambuc };
1043a19ae75SLionel Sambuc 
1053a19ae75SLionel Sambuc static const struct t_op cop[] = {
1063a19ae75SLionel Sambuc 	{"!",	UNOT,	BUNOP},
1073a19ae75SLionel Sambuc 	{"(",	LPAREN,	PAREN},
1083a19ae75SLionel Sambuc 	{")",	RPAREN,	PAREN},
1093a19ae75SLionel Sambuc 	{"<",	STRLT,	BINOP},
1103a19ae75SLionel Sambuc 	{"=",	STREQ,	BINOP},
1113a19ae75SLionel Sambuc 	{">",	STRGT,	BINOP},
1123a19ae75SLionel Sambuc };
1133a19ae75SLionel Sambuc 
1143a19ae75SLionel Sambuc static const struct t_op cop2[] = {
1153a19ae75SLionel Sambuc 	{"!=",	STRNE,	BINOP},
1163a19ae75SLionel Sambuc };
1173a19ae75SLionel Sambuc 
1183a19ae75SLionel Sambuc static const struct t_op mop3[] = {
1193a19ae75SLionel Sambuc 	{"ef",	FILEQ,	BINOP},
1203a19ae75SLionel Sambuc 	{"eq",	INTEQ,	BINOP},
1213a19ae75SLionel Sambuc 	{"ge",	INTGE,	BINOP},
1223a19ae75SLionel Sambuc 	{"gt",	INTGT,	BINOP},
1233a19ae75SLionel Sambuc 	{"le",	INTLE,	BINOP},
1243a19ae75SLionel Sambuc 	{"lt",	INTLT,	BINOP},
1253a19ae75SLionel Sambuc 	{"ne",	INTNE,	BINOP},
1263a19ae75SLionel Sambuc 	{"nt",	FILNT,	BINOP},
1273a19ae75SLionel Sambuc 	{"ot",	FILOT,	BINOP},
1283a19ae75SLionel Sambuc };
1293a19ae75SLionel Sambuc 
1303a19ae75SLionel Sambuc static const struct t_op mop2[] = {
1313a19ae75SLionel Sambuc 	{"G",	FILGID,	UNOP},
1323a19ae75SLionel Sambuc 	{"L",	FILSYM,	UNOP},
1333a19ae75SLionel Sambuc 	{"O",	FILUID,	UNOP},
1343a19ae75SLionel Sambuc 	{"S",	FILSOCK,UNOP},
1353a19ae75SLionel Sambuc 	{"a",	BAND,	BBINOP},
1363a19ae75SLionel Sambuc 	{"b",	FILBDEV,UNOP},
1373a19ae75SLionel Sambuc 	{"c",	FILCDEV,UNOP},
1383a19ae75SLionel Sambuc 	{"d",	FILDIR,	UNOP},
1393a19ae75SLionel Sambuc 	{"e",	FILEXIST,UNOP},
1403a19ae75SLionel Sambuc 	{"f",	FILREG,	UNOP},
1413a19ae75SLionel Sambuc 	{"g",	FILSGID,UNOP},
1423a19ae75SLionel Sambuc 	{"h",	FILSYM,	UNOP},		/* for backwards compat */
1433a19ae75SLionel Sambuc 	{"k",	FILSTCK,UNOP},
1443a19ae75SLionel Sambuc 	{"n",	STRNZ,	UNOP},
1453a19ae75SLionel Sambuc 	{"o",	BOR,	BBINOP},
1463a19ae75SLionel Sambuc 	{"p",	FILFIFO,UNOP},
1473a19ae75SLionel Sambuc 	{"r",	FILRD,	UNOP},
1483a19ae75SLionel Sambuc 	{"s",	FILGZ,	UNOP},
1493a19ae75SLionel Sambuc 	{"t",	FILTT,	UNOP},
1503a19ae75SLionel Sambuc 	{"u",	FILSUID,UNOP},
1513a19ae75SLionel Sambuc 	{"w",	FILWR,	UNOP},
1523a19ae75SLionel Sambuc 	{"x",	FILEX,	UNOP},
1533a19ae75SLionel Sambuc 	{"z",	STREZ,	UNOP},
1543a19ae75SLionel Sambuc };
1553a19ae75SLionel Sambuc 
1563a19ae75SLionel Sambuc static char **t_wp;
1573a19ae75SLionel Sambuc static struct t_op const *t_wp_op;
1583a19ae75SLionel Sambuc 
1593a19ae75SLionel Sambuc __dead static void syntax(const char *, const char *);
1603a19ae75SLionel Sambuc static int oexpr(enum token);
1613a19ae75SLionel Sambuc static int aexpr(enum token);
1623a19ae75SLionel Sambuc static int nexpr(enum token);
1633a19ae75SLionel Sambuc static int primary(enum token);
1643a19ae75SLionel Sambuc static int binop(void);
1653a19ae75SLionel Sambuc static int test_access(struct stat *, mode_t);
1663a19ae75SLionel Sambuc static int filstat(char *, enum token);
1673a19ae75SLionel Sambuc static enum token t_lex(char *);
1683a19ae75SLionel Sambuc static int isoperand(void);
1693a19ae75SLionel Sambuc static long long getn(const char *);
1703a19ae75SLionel Sambuc static int newerf(const char *, const char *);
1713a19ae75SLionel Sambuc static int olderf(const char *, const char *);
1723a19ae75SLionel Sambuc static int equalf(const char *, const char *);
1733a19ae75SLionel Sambuc 
1743a19ae75SLionel Sambuc #if defined(SHELL)
1753a19ae75SLionel Sambuc extern void error(const char *, ...) __dead __printflike(1, 2);
1763a19ae75SLionel Sambuc extern void *ckmalloc(size_t);
1773a19ae75SLionel Sambuc #else
1783a19ae75SLionel Sambuc static void error(const char *, ...) __dead __printflike(1, 2);
1793a19ae75SLionel Sambuc 
1803a19ae75SLionel Sambuc static void
error(const char * msg,...)1813a19ae75SLionel Sambuc error(const char *msg, ...)
1823a19ae75SLionel Sambuc {
1833a19ae75SLionel Sambuc 	va_list ap;
1843a19ae75SLionel Sambuc 
1853a19ae75SLionel Sambuc 	va_start(ap, msg);
1863a19ae75SLionel Sambuc 	verrx(2, msg, ap);
1873a19ae75SLionel Sambuc 	/*NOTREACHED*/
1883a19ae75SLionel Sambuc 	va_end(ap);
1893a19ae75SLionel Sambuc }
1903a19ae75SLionel Sambuc 
1913a19ae75SLionel Sambuc static void *ckmalloc(size_t);
1923a19ae75SLionel Sambuc static void *
ckmalloc(size_t nbytes)1933a19ae75SLionel Sambuc ckmalloc(size_t nbytes)
1943a19ae75SLionel Sambuc {
1953a19ae75SLionel Sambuc 	void *p = malloc(nbytes);
1963a19ae75SLionel Sambuc 
1973a19ae75SLionel Sambuc 	if (!p)
1983a19ae75SLionel Sambuc 		error("Not enough memory!");
1993a19ae75SLionel Sambuc 	return p;
2003a19ae75SLionel Sambuc }
2013a19ae75SLionel Sambuc #endif
2023a19ae75SLionel Sambuc 
2033a19ae75SLionel Sambuc #ifdef SHELL
2043a19ae75SLionel Sambuc int testcmd(int, char **);
2053a19ae75SLionel Sambuc 
2063a19ae75SLionel Sambuc int
testcmd(int argc,char ** argv)2073a19ae75SLionel Sambuc testcmd(int argc, char **argv)
2083a19ae75SLionel Sambuc #else
2093a19ae75SLionel Sambuc int
2103a19ae75SLionel Sambuc main(int argc, char *argv[])
2113a19ae75SLionel Sambuc #endif
2123a19ae75SLionel Sambuc {
2133a19ae75SLionel Sambuc 	int res;
2143a19ae75SLionel Sambuc 	const char *argv0;
2153a19ae75SLionel Sambuc 
2163a19ae75SLionel Sambuc #ifdef SHELL
2173a19ae75SLionel Sambuc 	argv0 = argv[0];
2183a19ae75SLionel Sambuc #else
2193a19ae75SLionel Sambuc 	setprogname(argv[0]);
2203a19ae75SLionel Sambuc 	(void)setlocale(LC_ALL, "");
2213a19ae75SLionel Sambuc 	argv0 = getprogname();
2223a19ae75SLionel Sambuc #endif
2233a19ae75SLionel Sambuc 	if (strcmp(argv0, "[") == 0) {
2243a19ae75SLionel Sambuc 		if (strcmp(argv[--argc], "]"))
2253a19ae75SLionel Sambuc 			error("missing ]");
2263a19ae75SLionel Sambuc 		argv[argc] = NULL;
2273a19ae75SLionel Sambuc 	}
2283a19ae75SLionel Sambuc 
2293a19ae75SLionel Sambuc 	if (argc < 2)
2303a19ae75SLionel Sambuc 		return 1;
2313a19ae75SLionel Sambuc 
2323a19ae75SLionel Sambuc 	t_wp = &argv[1];
2333a19ae75SLionel Sambuc 	res = !oexpr(t_lex(*t_wp));
2343a19ae75SLionel Sambuc 
2353a19ae75SLionel Sambuc 	if (*t_wp != NULL && *++t_wp != NULL)
2363a19ae75SLionel Sambuc 		syntax(*t_wp, "unexpected operator");
2373a19ae75SLionel Sambuc 
2383a19ae75SLionel Sambuc 	return res;
2393a19ae75SLionel Sambuc }
2403a19ae75SLionel Sambuc 
2413a19ae75SLionel Sambuc static void
syntax(const char * op,const char * msg)2423a19ae75SLionel Sambuc syntax(const char *op, const char *msg)
2433a19ae75SLionel Sambuc {
2443a19ae75SLionel Sambuc 
2453a19ae75SLionel Sambuc 	if (op && *op)
2463a19ae75SLionel Sambuc 		error("%s: %s", op, msg);
2473a19ae75SLionel Sambuc 	else
2483a19ae75SLionel Sambuc 		error("%s", msg);
2493a19ae75SLionel Sambuc }
2503a19ae75SLionel Sambuc 
2513a19ae75SLionel Sambuc static int
oexpr(enum token n)2523a19ae75SLionel Sambuc oexpr(enum token n)
2533a19ae75SLionel Sambuc {
2543a19ae75SLionel Sambuc 	int res;
2553a19ae75SLionel Sambuc 
2563a19ae75SLionel Sambuc 	res = aexpr(n);
2573a19ae75SLionel Sambuc 	if (*t_wp == NULL)
2583a19ae75SLionel Sambuc 		return res;
2593a19ae75SLionel Sambuc 	if (t_lex(*++t_wp) == BOR)
2603a19ae75SLionel Sambuc 		return oexpr(t_lex(*++t_wp)) || res;
2613a19ae75SLionel Sambuc 	t_wp--;
2623a19ae75SLionel Sambuc 	return res;
2633a19ae75SLionel Sambuc }
2643a19ae75SLionel Sambuc 
2653a19ae75SLionel Sambuc static int
aexpr(enum token n)2663a19ae75SLionel Sambuc aexpr(enum token n)
2673a19ae75SLionel Sambuc {
2683a19ae75SLionel Sambuc 	int res;
2693a19ae75SLionel Sambuc 
2703a19ae75SLionel Sambuc 	res = nexpr(n);
2713a19ae75SLionel Sambuc 	if (*t_wp == NULL)
2723a19ae75SLionel Sambuc 		return res;
2733a19ae75SLionel Sambuc 	if (t_lex(*++t_wp) == BAND)
2743a19ae75SLionel Sambuc 		return aexpr(t_lex(*++t_wp)) && res;
2753a19ae75SLionel Sambuc 	t_wp--;
2763a19ae75SLionel Sambuc 	return res;
2773a19ae75SLionel Sambuc }
2783a19ae75SLionel Sambuc 
2793a19ae75SLionel Sambuc static int
nexpr(enum token n)2803a19ae75SLionel Sambuc nexpr(enum token n)
2813a19ae75SLionel Sambuc {
2823a19ae75SLionel Sambuc 
2833a19ae75SLionel Sambuc 	if (n == UNOT)
2843a19ae75SLionel Sambuc 		return !nexpr(t_lex(*++t_wp));
2853a19ae75SLionel Sambuc 	return primary(n);
2863a19ae75SLionel Sambuc }
2873a19ae75SLionel Sambuc 
2883a19ae75SLionel Sambuc static int
primary(enum token n)2893a19ae75SLionel Sambuc primary(enum token n)
2903a19ae75SLionel Sambuc {
2913a19ae75SLionel Sambuc 	enum token nn;
2923a19ae75SLionel Sambuc 	int res;
2933a19ae75SLionel Sambuc 
2943a19ae75SLionel Sambuc 	if (n == EOI)
2953a19ae75SLionel Sambuc 		return 0;		/* missing expression */
2963a19ae75SLionel Sambuc 	if (n == LPAREN) {
2973a19ae75SLionel Sambuc 		if ((nn = t_lex(*++t_wp)) == RPAREN)
2983a19ae75SLionel Sambuc 			return 0;	/* missing expression */
2993a19ae75SLionel Sambuc 		res = oexpr(nn);
3003a19ae75SLionel Sambuc 		if (t_lex(*++t_wp) != RPAREN)
3013a19ae75SLionel Sambuc 			syntax(NULL, "closing paren expected");
3023a19ae75SLionel Sambuc 		return res;
3033a19ae75SLionel Sambuc 	}
3043a19ae75SLionel Sambuc 	if (t_wp_op && t_wp_op->op_type == UNOP) {
3053a19ae75SLionel Sambuc 		/* unary expression */
3063a19ae75SLionel Sambuc 		if (*++t_wp == NULL)
3073a19ae75SLionel Sambuc 			syntax(t_wp_op->op_text, "argument expected");
3083a19ae75SLionel Sambuc 		switch (n) {
3093a19ae75SLionel Sambuc 		case STREZ:
3103a19ae75SLionel Sambuc 			return strlen(*t_wp) == 0;
3113a19ae75SLionel Sambuc 		case STRNZ:
3123a19ae75SLionel Sambuc 			return strlen(*t_wp) != 0;
3133a19ae75SLionel Sambuc 		case FILTT:
3143a19ae75SLionel Sambuc 			return isatty((int)getn(*t_wp));
3153a19ae75SLionel Sambuc 		default:
3163a19ae75SLionel Sambuc 			return filstat(*t_wp, n);
3173a19ae75SLionel Sambuc 		}
3183a19ae75SLionel Sambuc 	}
3193a19ae75SLionel Sambuc 
3203a19ae75SLionel Sambuc 	if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
3213a19ae75SLionel Sambuc 		return binop();
3223a19ae75SLionel Sambuc 	}
3233a19ae75SLionel Sambuc 
3243a19ae75SLionel Sambuc 	return strlen(*t_wp) > 0;
3253a19ae75SLionel Sambuc }
3263a19ae75SLionel Sambuc 
3273a19ae75SLionel Sambuc static int
binop(void)3283a19ae75SLionel Sambuc binop(void)
3293a19ae75SLionel Sambuc {
3303a19ae75SLionel Sambuc 	const char *opnd1, *opnd2;
3313a19ae75SLionel Sambuc 	struct t_op const *op;
3323a19ae75SLionel Sambuc 
3333a19ae75SLionel Sambuc 	opnd1 = *t_wp;
3343a19ae75SLionel Sambuc 	(void) t_lex(*++t_wp);
3353a19ae75SLionel Sambuc 	op = t_wp_op;
3363a19ae75SLionel Sambuc 
3373a19ae75SLionel Sambuc 	if ((opnd2 = *++t_wp) == NULL)
3383a19ae75SLionel Sambuc 		syntax(op->op_text, "argument expected");
3393a19ae75SLionel Sambuc 
3403a19ae75SLionel Sambuc 	switch (op->op_num) {
3413a19ae75SLionel Sambuc 	case STREQ:
3423a19ae75SLionel Sambuc 		return strcmp(opnd1, opnd2) == 0;
3433a19ae75SLionel Sambuc 	case STRNE:
3443a19ae75SLionel Sambuc 		return strcmp(opnd1, opnd2) != 0;
3453a19ae75SLionel Sambuc 	case STRLT:
3463a19ae75SLionel Sambuc 		return strcmp(opnd1, opnd2) < 0;
3473a19ae75SLionel Sambuc 	case STRGT:
3483a19ae75SLionel Sambuc 		return strcmp(opnd1, opnd2) > 0;
3493a19ae75SLionel Sambuc 	case INTEQ:
3503a19ae75SLionel Sambuc 		return getn(opnd1) == getn(opnd2);
3513a19ae75SLionel Sambuc 	case INTNE:
3523a19ae75SLionel Sambuc 		return getn(opnd1) != getn(opnd2);
3533a19ae75SLionel Sambuc 	case INTGE:
3543a19ae75SLionel Sambuc 		return getn(opnd1) >= getn(opnd2);
3553a19ae75SLionel Sambuc 	case INTGT:
3563a19ae75SLionel Sambuc 		return getn(opnd1) > getn(opnd2);
3573a19ae75SLionel Sambuc 	case INTLE:
3583a19ae75SLionel Sambuc 		return getn(opnd1) <= getn(opnd2);
3593a19ae75SLionel Sambuc 	case INTLT:
3603a19ae75SLionel Sambuc 		return getn(opnd1) < getn(opnd2);
3613a19ae75SLionel Sambuc 	case FILNT:
3623a19ae75SLionel Sambuc 		return newerf(opnd1, opnd2);
3633a19ae75SLionel Sambuc 	case FILOT:
3643a19ae75SLionel Sambuc 		return olderf(opnd1, opnd2);
3653a19ae75SLionel Sambuc 	case FILEQ:
3663a19ae75SLionel Sambuc 		return equalf(opnd1, opnd2);
3673a19ae75SLionel Sambuc 	default:
3683a19ae75SLionel Sambuc 		abort();
3693a19ae75SLionel Sambuc 		/* NOTREACHED */
3703a19ae75SLionel Sambuc 	}
3713a19ae75SLionel Sambuc }
3723a19ae75SLionel Sambuc 
3733a19ae75SLionel Sambuc /*
3743a19ae75SLionel Sambuc  * The manual, and IEEE POSIX 1003.2, suggests this should check the mode bits,
3753a19ae75SLionel Sambuc  * not use access():
3763a19ae75SLionel Sambuc  *
3773a19ae75SLionel Sambuc  *	True shall indicate only that the write flag is on.  The file is not
3783a19ae75SLionel Sambuc  *	writable on a read-only file system even if this test indicates true.
3793a19ae75SLionel Sambuc  *
3803a19ae75SLionel Sambuc  * Unfortunately IEEE POSIX 1003.1-2001, as quoted in SuSv3, says only:
3813a19ae75SLionel Sambuc  *
3823a19ae75SLionel Sambuc  *	True shall indicate that permission to read from file will be granted,
3833a19ae75SLionel Sambuc  *	as defined in "File Read, Write, and Creation".
3843a19ae75SLionel Sambuc  *
3853a19ae75SLionel Sambuc  * and that section says:
3863a19ae75SLionel Sambuc  *
3873a19ae75SLionel Sambuc  *	When a file is to be read or written, the file shall be opened with an
3883a19ae75SLionel Sambuc  *	access mode corresponding to the operation to be performed.  If file
3893a19ae75SLionel Sambuc  *	access permissions deny access, the requested operation shall fail.
3903a19ae75SLionel Sambuc  *
3913a19ae75SLionel Sambuc  * and of course access permissions are described as one might expect:
3923a19ae75SLionel Sambuc  *
3933a19ae75SLionel Sambuc  *     * If a process has the appropriate privilege:
3943a19ae75SLionel Sambuc  *
3953a19ae75SLionel Sambuc  *        * If read, write, or directory search permission is requested,
3963a19ae75SLionel Sambuc  *          access shall be granted.
3973a19ae75SLionel Sambuc  *
3983a19ae75SLionel Sambuc  *        * If execute permission is requested, access shall be granted if
3993a19ae75SLionel Sambuc  *          execute permission is granted to at least one user by the file
4003a19ae75SLionel Sambuc  *          permission bits or by an alternate access control mechanism;
4013a19ae75SLionel Sambuc  *          otherwise, access shall be denied.
4023a19ae75SLionel Sambuc  *
4033a19ae75SLionel Sambuc  *   * Otherwise:
4043a19ae75SLionel Sambuc  *
4053a19ae75SLionel Sambuc  *        * The file permission bits of a file contain read, write, and
4063a19ae75SLionel Sambuc  *          execute/search permissions for the file owner class, file group
4073a19ae75SLionel Sambuc  *          class, and file other class.
4083a19ae75SLionel Sambuc  *
4093a19ae75SLionel Sambuc  *        * Access shall be granted if an alternate access control mechanism
4103a19ae75SLionel Sambuc  *          is not enabled and the requested access permission bit is set for
4113a19ae75SLionel Sambuc  *          the class (file owner class, file group class, or file other class)
4123a19ae75SLionel Sambuc  *          to which the process belongs, or if an alternate access control
4133a19ae75SLionel Sambuc  *          mechanism is enabled and it allows the requested access; otherwise,
4143a19ae75SLionel Sambuc  *          access shall be denied.
4153a19ae75SLionel Sambuc  *
4163a19ae75SLionel Sambuc  * and when I first read this I thought:  surely we can't go about using
4173a19ae75SLionel Sambuc  * open(O_WRONLY) to try this test!  However the POSIX 1003.1-2001 Rationale
4183a19ae75SLionel Sambuc  * section for test does in fact say:
4193a19ae75SLionel Sambuc  *
4203a19ae75SLionel Sambuc  *	On historical BSD systems, test -w directory always returned false
4213a19ae75SLionel Sambuc  *	because test tried to open the directory for writing, which always
4223a19ae75SLionel Sambuc  *	fails.
4233a19ae75SLionel Sambuc  *
4243a19ae75SLionel Sambuc  * and indeed this is in fact true for Seventh Edition UNIX, UNIX 32V, and UNIX
4253a19ae75SLionel Sambuc  * System III, and thus presumably also for BSD up to and including 4.3.
4263a19ae75SLionel Sambuc  *
4273a19ae75SLionel Sambuc  * Secondly I remembered why using open() and/or access() are bogus.  They
4283a19ae75SLionel Sambuc  * don't work right for detecting read and write permissions bits when called
4293a19ae75SLionel Sambuc  * by root.
4303a19ae75SLionel Sambuc  *
4313a19ae75SLionel Sambuc  * Interestingly the 'test' in 4.4BSD was closer to correct (as per
4323a19ae75SLionel Sambuc  * 1003.2-1992) and it was implemented efficiently with stat() instead of
4333a19ae75SLionel Sambuc  * open().
4343a19ae75SLionel Sambuc  *
4353a19ae75SLionel Sambuc  * This was apparently broken in NetBSD around about 1994/06/30 when the old
4363a19ae75SLionel Sambuc  * 4.4BSD implementation was replaced with a (arguably much better coded)
4373a19ae75SLionel Sambuc  * implementation derived from pdksh.
4383a19ae75SLionel Sambuc  *
4393a19ae75SLionel Sambuc  * Note that modern pdksh is yet different again, but still not correct, at
4403a19ae75SLionel Sambuc  * least not w.r.t. 1003.2-1992.
4413a19ae75SLionel Sambuc  *
4423a19ae75SLionel Sambuc  * As I think more about it and read more of the related IEEE docs I don't like
4433a19ae75SLionel Sambuc  * that wording about 'test -r' and 'test -w' in 1003.1-2001 at all.  I very
4443a19ae75SLionel Sambuc  * much prefer the original wording in 1003.2-1992.  It is much more useful,
4453a19ae75SLionel Sambuc  * and so that's what I've implemented.
4463a19ae75SLionel Sambuc  *
4473a19ae75SLionel Sambuc  * (Note that a strictly conforming implementation of 1003.1-2001 is in fact
4483a19ae75SLionel Sambuc  * totally useless for the case in question since its 'test -w' and 'test -r'
4493a19ae75SLionel Sambuc  * can never fail for root for any existing files, i.e. files for which 'test
4503a19ae75SLionel Sambuc  * -e' succeeds.)
4513a19ae75SLionel Sambuc  *
4523a19ae75SLionel Sambuc  * The rationale for 1003.1-2001 suggests that the wording was "clarified" in
4533a19ae75SLionel Sambuc  * 1003.1-2001 to align with the 1003.2b draft.  1003.2b Draft 12 (July 1999),
4543a19ae75SLionel Sambuc  * which is the latest copy I have, does carry the same suggested wording as is
4553a19ae75SLionel Sambuc  * in 1003.1-2001, with its rationale saying:
4563a19ae75SLionel Sambuc  *
4573a19ae75SLionel Sambuc  * 	This change is a clarification and is the result of interpretation
4583a19ae75SLionel Sambuc  * 	request PASC 1003.2-92 #23 submitted for IEEE Std 1003.2-1992.
4593a19ae75SLionel Sambuc  *
4603a19ae75SLionel Sambuc  * That interpretation can be found here:
4613a19ae75SLionel Sambuc  *
4623a19ae75SLionel Sambuc  *   http://www.pasc.org/interps/unofficial/db/p1003.2/pasc-1003.2-23.html
4633a19ae75SLionel Sambuc  *
4643a19ae75SLionel Sambuc  * Not terribly helpful, unfortunately.  I wonder who that fence sitter was.
4653a19ae75SLionel Sambuc  *
4663a19ae75SLionel Sambuc  * Worse, IMVNSHO, I think the authors of 1003.2b-D12 have mis-interpreted the
4673a19ae75SLionel Sambuc  * PASC interpretation and appear to be gone against at least one widely used
4683a19ae75SLionel Sambuc  * implementation (namely 4.4BSD).  The problem is that for file access by root
4693a19ae75SLionel Sambuc  * this means that if test '-r' and '-w' are to behave as if open() were called
4703a19ae75SLionel Sambuc  * then there's no way for a shell script running as root to check if a file
4713a19ae75SLionel Sambuc  * has certain access bits set other than by the grotty means of interpreting
4723a19ae75SLionel Sambuc  * the output of 'ls -l'.  This was widely considered to be a bug in V7's
4733a19ae75SLionel Sambuc  * "test" and is, I believe, one of the reasons why direct use of access() was
4743a19ae75SLionel Sambuc  * avoided in some more recent implementations!
4753a19ae75SLionel Sambuc  *
4763a19ae75SLionel Sambuc  * I have always interpreted '-r' to match '-w' and '-x' as per the original
4773a19ae75SLionel Sambuc  * wording in 1003.2-1992, not the other way around.  I think 1003.2b goes much
4783a19ae75SLionel Sambuc  * too far the wrong way without any valid rationale and that it's best if we
4793a19ae75SLionel Sambuc  * stick with 1003.2-1992 and test the flags, and not mimic the behaviour of
4803a19ae75SLionel Sambuc  * open() since we already know very well how it will work -- existance of the
4813a19ae75SLionel Sambuc  * file is all that matters to open() for root.
4823a19ae75SLionel Sambuc  *
4833a19ae75SLionel Sambuc  * Unfortunately the SVID is no help at all (which is, I guess, partly why
4843a19ae75SLionel Sambuc  * we're in this mess in the first place :-).
4853a19ae75SLionel Sambuc  *
4863a19ae75SLionel Sambuc  * The SysV implementation (at least in the 'test' builtin in /bin/sh) does use
4873a19ae75SLionel Sambuc  * access(name, 2) even though it also goes to much greater lengths for '-x'
4883a19ae75SLionel Sambuc  * matching the 1003.2-1992 definition (which is no doubt where that definition
4893a19ae75SLionel Sambuc  * came from).
4903a19ae75SLionel Sambuc  *
4913a19ae75SLionel Sambuc  * The ksh93 implementation uses access() for '-r' and '-w' if
4923a19ae75SLionel Sambuc  * (euid==uid&&egid==gid), but uses st_mode for '-x' iff running as root.
4933a19ae75SLionel Sambuc  * i.e. it does strictly conform to 1003.1-2001 (and presumably 1003.2b).
4943a19ae75SLionel Sambuc  */
4953a19ae75SLionel Sambuc static int
test_access(struct stat * sp,mode_t stmode)4963a19ae75SLionel Sambuc test_access(struct stat *sp, mode_t stmode)
4973a19ae75SLionel Sambuc {
4983a19ae75SLionel Sambuc 	gid_t *groups;
4993a19ae75SLionel Sambuc 	register int n;
5003a19ae75SLionel Sambuc 	uid_t euid;
5013a19ae75SLionel Sambuc 	int maxgroups;
5023a19ae75SLionel Sambuc 
5033a19ae75SLionel Sambuc 	/*
5043a19ae75SLionel Sambuc 	 * I suppose we could use access() if not running as root and if we are
5053a19ae75SLionel Sambuc 	 * running with ((euid == uid) && (egid == gid)), but we've already
5063a19ae75SLionel Sambuc 	 * done the stat() so we might as well just test the permissions
5073a19ae75SLionel Sambuc 	 * directly instead of asking the kernel to do it....
5083a19ae75SLionel Sambuc 	 */
5093a19ae75SLionel Sambuc 	euid = geteuid();
5103a19ae75SLionel Sambuc 	if (euid == 0)				/* any bit is good enough */
5113a19ae75SLionel Sambuc 		stmode = (stmode << 6) | (stmode << 3) | stmode;
5123a19ae75SLionel Sambuc  	else if (sp->st_uid == euid)
5133a19ae75SLionel Sambuc 		stmode <<= 6;
5143a19ae75SLionel Sambuc 	else if (sp->st_gid == getegid())
5153a19ae75SLionel Sambuc 		stmode <<= 3;
5163a19ae75SLionel Sambuc 	else {
5173a19ae75SLionel Sambuc 		/* XXX stolen almost verbatim from ksh93.... */
5183a19ae75SLionel Sambuc 		/* on some systems you can be in several groups */
5193a19ae75SLionel Sambuc 		if ((maxgroups = getgroups(0, NULL)) <= 0)
5203a19ae75SLionel Sambuc 			maxgroups = NGROUPS_MAX;	/* pre-POSIX system? */
5213a19ae75SLionel Sambuc 		groups = ckmalloc((maxgroups + 1) * sizeof(gid_t));
5223a19ae75SLionel Sambuc 		n = getgroups(maxgroups, groups);
5233a19ae75SLionel Sambuc 		while (--n >= 0) {
5243a19ae75SLionel Sambuc 			if (groups[n] == sp->st_gid) {
5253a19ae75SLionel Sambuc 				stmode <<= 3;
5263a19ae75SLionel Sambuc 				break;
5273a19ae75SLionel Sambuc 			}
5283a19ae75SLionel Sambuc 		}
5293a19ae75SLionel Sambuc 		free(groups);
5303a19ae75SLionel Sambuc 	}
5313a19ae75SLionel Sambuc 
5323a19ae75SLionel Sambuc 	return sp->st_mode & stmode;
5333a19ae75SLionel Sambuc }
5343a19ae75SLionel Sambuc 
5353a19ae75SLionel Sambuc static int
filstat(char * nm,enum token mode)5363a19ae75SLionel Sambuc filstat(char *nm, enum token mode)
5373a19ae75SLionel Sambuc {
5383a19ae75SLionel Sambuc 	struct stat s;
5393a19ae75SLionel Sambuc 
5403a19ae75SLionel Sambuc 	if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
5413a19ae75SLionel Sambuc 		return 0;
5423a19ae75SLionel Sambuc 
5433a19ae75SLionel Sambuc 	switch (mode) {
5443a19ae75SLionel Sambuc 	case FILRD:
5453a19ae75SLionel Sambuc 		return test_access(&s, S_IROTH);
5463a19ae75SLionel Sambuc 	case FILWR:
5473a19ae75SLionel Sambuc 		return test_access(&s, S_IWOTH);
5483a19ae75SLionel Sambuc 	case FILEX:
5493a19ae75SLionel Sambuc 		return test_access(&s, S_IXOTH);
5503a19ae75SLionel Sambuc 	case FILEXIST:
5513a19ae75SLionel Sambuc 		return 1; /* the successful lstat()/stat() is good enough */
5523a19ae75SLionel Sambuc 	case FILREG:
5533a19ae75SLionel Sambuc 		return S_ISREG(s.st_mode);
5543a19ae75SLionel Sambuc 	case FILDIR:
5553a19ae75SLionel Sambuc 		return S_ISDIR(s.st_mode);
5563a19ae75SLionel Sambuc 	case FILCDEV:
5573a19ae75SLionel Sambuc 		return S_ISCHR(s.st_mode);
5583a19ae75SLionel Sambuc 	case FILBDEV:
5593a19ae75SLionel Sambuc 		return S_ISBLK(s.st_mode);
5603a19ae75SLionel Sambuc 	case FILFIFO:
5613a19ae75SLionel Sambuc 		return S_ISFIFO(s.st_mode);
5623a19ae75SLionel Sambuc 	case FILSOCK:
5633a19ae75SLionel Sambuc 		return S_ISSOCK(s.st_mode);
5643a19ae75SLionel Sambuc 	case FILSYM:
5653a19ae75SLionel Sambuc 		return S_ISLNK(s.st_mode);
5663a19ae75SLionel Sambuc 	case FILSUID:
5673a19ae75SLionel Sambuc 		return (s.st_mode & S_ISUID) != 0;
5683a19ae75SLionel Sambuc 	case FILSGID:
5693a19ae75SLionel Sambuc 		return (s.st_mode & S_ISGID) != 0;
5703a19ae75SLionel Sambuc 	case FILSTCK:
5713a19ae75SLionel Sambuc 		return (s.st_mode & S_ISVTX) != 0;
5723a19ae75SLionel Sambuc 	case FILGZ:
5733a19ae75SLionel Sambuc 		return s.st_size > (off_t)0;
5743a19ae75SLionel Sambuc 	case FILUID:
5753a19ae75SLionel Sambuc 		return s.st_uid == geteuid();
5763a19ae75SLionel Sambuc 	case FILGID:
5773a19ae75SLionel Sambuc 		return s.st_gid == getegid();
5783a19ae75SLionel Sambuc 	default:
5793a19ae75SLionel Sambuc 		return 1;
5803a19ae75SLionel Sambuc 	}
5813a19ae75SLionel Sambuc }
5823a19ae75SLionel Sambuc 
5833a19ae75SLionel Sambuc #define VTOC(x)	(const unsigned char *)((const struct t_op *)x)->op_text
5843a19ae75SLionel Sambuc 
5853a19ae75SLionel Sambuc static int
compare1(const void * va,const void * vb)5863a19ae75SLionel Sambuc compare1(const void *va, const void *vb)
5873a19ae75SLionel Sambuc {
5883a19ae75SLionel Sambuc 	const unsigned char *a = va;
5893a19ae75SLionel Sambuc 	const unsigned char *b = VTOC(vb);
5903a19ae75SLionel Sambuc 
5913a19ae75SLionel Sambuc 	return a[0] - b[0];
5923a19ae75SLionel Sambuc }
5933a19ae75SLionel Sambuc 
5943a19ae75SLionel Sambuc static int
compare2(const void * va,const void * vb)5953a19ae75SLionel Sambuc compare2(const void *va, const void *vb)
5963a19ae75SLionel Sambuc {
5973a19ae75SLionel Sambuc 	const unsigned char *a = va;
5983a19ae75SLionel Sambuc 	const unsigned char *b = VTOC(vb);
5993a19ae75SLionel Sambuc 	int z = a[0] - b[0];
6003a19ae75SLionel Sambuc 
6013a19ae75SLionel Sambuc 	return z ? z : (a[1] - b[1]);
6023a19ae75SLionel Sambuc }
6033a19ae75SLionel Sambuc 
6043a19ae75SLionel Sambuc static struct t_op const *
findop(const char * s)6053a19ae75SLionel Sambuc findop(const char *s)
6063a19ae75SLionel Sambuc {
6073a19ae75SLionel Sambuc 	if (s[0] == '-') {
6083a19ae75SLionel Sambuc 		if (s[1] == '\0')
6093a19ae75SLionel Sambuc 			return NULL;
6103a19ae75SLionel Sambuc 		if (s[2] == '\0')
6113a19ae75SLionel Sambuc 			return bsearch(s + 1, mop2, __arraycount(mop2),
6123a19ae75SLionel Sambuc 			    sizeof(*mop2), compare1);
6133a19ae75SLionel Sambuc 		else if (s[3] != '\0')
6143a19ae75SLionel Sambuc 			return NULL;
6153a19ae75SLionel Sambuc 		else
6163a19ae75SLionel Sambuc 			return bsearch(s + 1, mop3, __arraycount(mop3),
6173a19ae75SLionel Sambuc 			    sizeof(*mop3), compare2);
6183a19ae75SLionel Sambuc 	} else {
6193a19ae75SLionel Sambuc 		if (s[1] == '\0')
6203a19ae75SLionel Sambuc 			return bsearch(s, cop, __arraycount(cop), sizeof(*cop),
6213a19ae75SLionel Sambuc 			    compare1);
6223a19ae75SLionel Sambuc 		else if (strcmp(s, cop2[0].op_text) == 0)
6233a19ae75SLionel Sambuc 			return cop2;
6243a19ae75SLionel Sambuc 		else
6253a19ae75SLionel Sambuc 			return NULL;
6263a19ae75SLionel Sambuc 	}
6273a19ae75SLionel Sambuc }
6283a19ae75SLionel Sambuc 
6293a19ae75SLionel Sambuc static enum token
t_lex(char * s)6303a19ae75SLionel Sambuc t_lex(char *s)
6313a19ae75SLionel Sambuc {
6323a19ae75SLionel Sambuc 	struct t_op const *op;
6333a19ae75SLionel Sambuc 
6343a19ae75SLionel Sambuc 	if (s == NULL) {
6353a19ae75SLionel Sambuc 		t_wp_op = NULL;
6363a19ae75SLionel Sambuc 		return EOI;
6373a19ae75SLionel Sambuc 	}
6383a19ae75SLionel Sambuc 
6393a19ae75SLionel Sambuc 	if ((op = findop(s)) != NULL) {
6403a19ae75SLionel Sambuc 		if (!((op->op_type == UNOP && isoperand()) ||
6413a19ae75SLionel Sambuc 		    (op->op_num == LPAREN && *(t_wp+1) == 0))) {
6423a19ae75SLionel Sambuc 			t_wp_op = op;
6433a19ae75SLionel Sambuc 			return op->op_num;
6443a19ae75SLionel Sambuc 		}
6453a19ae75SLionel Sambuc 	}
6463a19ae75SLionel Sambuc 	t_wp_op = NULL;
6473a19ae75SLionel Sambuc 	return OPERAND;
6483a19ae75SLionel Sambuc }
6493a19ae75SLionel Sambuc 
6503a19ae75SLionel Sambuc static int
isoperand(void)6513a19ae75SLionel Sambuc isoperand(void)
6523a19ae75SLionel Sambuc {
6533a19ae75SLionel Sambuc 	struct t_op const *op;
6543a19ae75SLionel Sambuc 	char *s, *t;
6553a19ae75SLionel Sambuc 
6563a19ae75SLionel Sambuc 	if ((s  = *(t_wp+1)) == 0)
6573a19ae75SLionel Sambuc 		return 1;
6583a19ae75SLionel Sambuc 	if ((t = *(t_wp+2)) == 0)
6593a19ae75SLionel Sambuc 		return 0;
6603a19ae75SLionel Sambuc 	if ((op = findop(s)) != NULL)
6613a19ae75SLionel Sambuc 		return op->op_type == BINOP && (t[0] != ')' || t[1] != '\0');
6623a19ae75SLionel Sambuc 	return 0;
6633a19ae75SLionel Sambuc }
6643a19ae75SLionel Sambuc 
6653a19ae75SLionel Sambuc /* atoi with error detection */
6663a19ae75SLionel Sambuc static long long
getn(const char * s)6673a19ae75SLionel Sambuc getn(const char *s)
6683a19ae75SLionel Sambuc {
6693a19ae75SLionel Sambuc 	char *p;
6703a19ae75SLionel Sambuc 	long long r;
6713a19ae75SLionel Sambuc 
6723a19ae75SLionel Sambuc 	errno = 0;
6733a19ae75SLionel Sambuc 	r = strtoll(s, &p, 10);
6743a19ae75SLionel Sambuc 
6753a19ae75SLionel Sambuc 	if (errno != 0)
6763a19ae75SLionel Sambuc 	if (errno == ERANGE && (r == LLONG_MAX || r == LLONG_MIN))
6773a19ae75SLionel Sambuc 	      error("%s: out of range", s);
6783a19ae75SLionel Sambuc 
6793a19ae75SLionel Sambuc 	while (isspace((unsigned char)*p))
6803a19ae75SLionel Sambuc 	      p++;
6813a19ae75SLionel Sambuc 
6823a19ae75SLionel Sambuc 	if (*p || p == s)
6833a19ae75SLionel Sambuc 	      error("%s: bad number", s);
6843a19ae75SLionel Sambuc 
6853a19ae75SLionel Sambuc 	return r;
6863a19ae75SLionel Sambuc }
6873a19ae75SLionel Sambuc 
6883a19ae75SLionel Sambuc static int
newerf(const char * f1,const char * f2)6893a19ae75SLionel Sambuc newerf(const char *f1, const char *f2)
6903a19ae75SLionel Sambuc {
6913a19ae75SLionel Sambuc 	struct stat b1, b2;
6923a19ae75SLionel Sambuc 
6933a19ae75SLionel Sambuc 	return (stat(f1, &b1) == 0 &&
6943a19ae75SLionel Sambuc 		stat(f2, &b2) == 0 &&
69584d9c625SLionel Sambuc 		timespeccmp(&b1.st_mtim, &b2.st_mtim, >));
6963a19ae75SLionel Sambuc }
6973a19ae75SLionel Sambuc 
6983a19ae75SLionel Sambuc static int
olderf(const char * f1,const char * f2)6993a19ae75SLionel Sambuc olderf(const char *f1, const char *f2)
7003a19ae75SLionel Sambuc {
7013a19ae75SLionel Sambuc 	struct stat b1, b2;
7023a19ae75SLionel Sambuc 
7033a19ae75SLionel Sambuc 	return (stat(f1, &b1) == 0 &&
7043a19ae75SLionel Sambuc 		stat(f2, &b2) == 0 &&
70584d9c625SLionel Sambuc 		timespeccmp(&b1.st_mtim, &b2.st_mtim, <));
7063a19ae75SLionel Sambuc }
7073a19ae75SLionel Sambuc 
7083a19ae75SLionel Sambuc static int
equalf(const char * f1,const char * f2)7093a19ae75SLionel Sambuc equalf(const char *f1, const char *f2)
7103a19ae75SLionel Sambuc {
7113a19ae75SLionel Sambuc 	struct stat b1, b2;
7123a19ae75SLionel Sambuc 
7133a19ae75SLionel Sambuc 	return (stat(f1, &b1) == 0 &&
7143a19ae75SLionel Sambuc 		stat(f2, &b2) == 0 &&
7153a19ae75SLionel Sambuc 		b1.st_dev == b2.st_dev &&
7163a19ae75SLionel Sambuc 		b1.st_ino == b2.st_ino);
7173a19ae75SLionel Sambuc }
718