test.c revision 100774
11573Srgrimes/*	$NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $	*/
21573Srgrimes
31573Srgrimes/*
41573Srgrimes * test(1); version 7-like  --  author Erik Baalbergen
51573Srgrimes * modified by Eric Gisin to be used as built-in.
61573Srgrimes * modified by Arnold Robbins to add SVR3 compatibility
71573Srgrimes * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
81573Srgrimes * modified by J.T. Conklin for NetBSD.
91573Srgrimes *
101573Srgrimes * This program is in the Public Domain.
111573Srgrimes */
121573Srgrimes
131573Srgrimes#include <sys/cdefs.h>
141573Srgrimes__FBSDID("$FreeBSD: head/bin/test/test.c 100774 2002-07-27 22:53:44Z dwmalone $");
151573Srgrimes
161573Srgrimes#include <sys/types.h>
171573Srgrimes#include <sys/stat.h>
181573Srgrimes
191573Srgrimes#include <ctype.h>
201573Srgrimes#include <err.h>
211573Srgrimes#include <errno.h>
221573Srgrimes#include <inttypes.h>
231573Srgrimes#include <limits.h>
241573Srgrimes#include <stdarg.h>
251573Srgrimes#include <stdio.h>
261573Srgrimes#include <stdlib.h>
271573Srgrimes#include <string.h>
281573Srgrimes#include <unistd.h>
291573Srgrimes
301573Srgrimes#ifdef SHELL
311573Srgrimes#define main testcmd
321573Srgrimes#include "bltin/bltin.h"
331573Srgrimes#else
341573Srgrimes#include <locale.h>
351573Srgrimes
361573Srgrimesstatic void error(const char *, ...) __dead2 __printf0like(1, 2);
3790039Sobrien
3890039Sobrienstatic void
391573Srgrimeserror(const char *msg, ...)
4071579Sdeischen{
411573Srgrimes	va_list ap;
421573Srgrimes	va_start(ap, msg);
431573Srgrimes	verrx(2, msg, ap);
4471579Sdeischen	/*NOTREACHED*/
451573Srgrimes	va_end(ap);
461573Srgrimes}
471573Srgrimes#endif
481573Srgrimes
491573Srgrimes/* test(1) accepts the following grammar:
501573Srgrimes	oexpr	::= aexpr | aexpr "-o" oexpr ;
511573Srgrimes	aexpr	::= nexpr | nexpr "-a" aexpr ;
521573Srgrimes	nexpr	::= primary | "!" primary
531573Srgrimes	primary	::= unary-operator operand
541573Srgrimes		| operand binary-operator operand
551573Srgrimes		| operand
561573Srgrimes		| "(" oexpr ")"
571573Srgrimes		;
581573Srgrimes	unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
591573Srgrimes		"-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
601573Srgrimes
611573Srgrimes	binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
621573Srgrimes			"-nt"|"-ot"|"-ef";
631573Srgrimes	operand ::= <any legal UNIX file name>
641573Srgrimes*/
651573Srgrimes
661573Srgrimesenum token {
6756698Sjasone	EOI,
6871579Sdeischen	FILRD,
6971579Sdeischen	FILWR,
7071579Sdeischen	FILEX,
711573Srgrimes	FILEXIST,
7256698Sjasone	FILREG,
731573Srgrimes	FILDIR,
741573Srgrimes	FILCDEV,
751573Srgrimes	FILBDEV,
76	FILFIFO,
77	FILSOCK,
78	FILSYM,
79	FILGZ,
80	FILTT,
81	FILSUID,
82	FILSGID,
83	FILSTCK,
84	FILNT,
85	FILOT,
86	FILEQ,
87	FILUID,
88	FILGID,
89	STREZ,
90	STRNZ,
91	STREQ,
92	STRNE,
93	STRLT,
94	STRGT,
95	INTEQ,
96	INTNE,
97	INTGE,
98	INTGT,
99	INTLE,
100	INTLT,
101	UNOT,
102	BAND,
103	BOR,
104	LPAREN,
105	RPAREN,
106	OPERAND
107};
108
109enum token_types {
110	UNOP,
111	BINOP,
112	BUNOP,
113	BBINOP,
114	PAREN
115};
116
117struct t_op {
118	const char *op_text;
119	short op_num, op_type;
120} const ops [] = {
121	{"-r",	FILRD,	UNOP},
122	{"-w",	FILWR,	UNOP},
123	{"-x",	FILEX,	UNOP},
124	{"-e",	FILEXIST,UNOP},
125	{"-f",	FILREG,	UNOP},
126	{"-d",	FILDIR,	UNOP},
127	{"-c",	FILCDEV,UNOP},
128	{"-b",	FILBDEV,UNOP},
129	{"-p",	FILFIFO,UNOP},
130	{"-u",	FILSUID,UNOP},
131	{"-g",	FILSGID,UNOP},
132	{"-k",	FILSTCK,UNOP},
133	{"-s",	FILGZ,	UNOP},
134	{"-t",	FILTT,	UNOP},
135	{"-z",	STREZ,	UNOP},
136	{"-n",	STRNZ,	UNOP},
137	{"-h",	FILSYM,	UNOP},		/* for backwards compat */
138	{"-O",	FILUID,	UNOP},
139	{"-G",	FILGID,	UNOP},
140	{"-L",	FILSYM,	UNOP},
141	{"-S",	FILSOCK,UNOP},
142	{"=",	STREQ,	BINOP},
143	{"!=",	STRNE,	BINOP},
144	{"<",	STRLT,	BINOP},
145	{">",	STRGT,	BINOP},
146	{"-eq",	INTEQ,	BINOP},
147	{"-ne",	INTNE,	BINOP},
148	{"-ge",	INTGE,	BINOP},
149	{"-gt",	INTGT,	BINOP},
150	{"-le",	INTLE,	BINOP},
151	{"-lt",	INTLT,	BINOP},
152	{"-nt",	FILNT,	BINOP},
153	{"-ot",	FILOT,	BINOP},
154	{"-ef",	FILEQ,	BINOP},
155	{"!",	UNOT,	BUNOP},
156	{"-a",	BAND,	BBINOP},
157	{"-o",	BOR,	BBINOP},
158	{"(",	LPAREN,	PAREN},
159	{")",	RPAREN,	PAREN},
160	{0,	0,	0}
161};
162
163struct t_op const *t_wp_op;
164char **t_wp;
165
166static int	aexpr(enum token);
167static int	binop(void);
168static int	equalf(const char *, const char *);
169static int	filstat(char *, enum token);
170static int	getn(const char *);
171static intmax_t	getq(const char *);
172static int	intcmp(const char *, const char *);
173static int	isoperand(void);
174static int	newerf(const char *, const char *);
175static int	nexpr(enum token);
176static int	oexpr(enum token);
177static int	olderf(const char *, const char *);
178static int	primary(enum token);
179static void	syntax(const char *, const char *);
180static enum	token t_lex(char *);
181
182int
183main(int argc, char **argv)
184{
185	int	i, res;
186	char	*p;
187	char	**nargv;
188
189	/*
190	 * XXX copy the whole contents of argv to a newly allocated
191	 * space with two extra cells filled with NULL's - this source
192	 * code totally depends on their presence.
193	 */
194	if ((nargv = (char **)malloc((argc + 2) * sizeof(char *))) == NULL)
195		error("Out of space");
196
197	for (i = 0; i < argc; i++)
198		nargv[i] = argv[i];
199
200	nargv[i] = nargv[i + 1] = NULL;
201	argv = nargv;
202
203	if ((p = rindex(argv[0], '/')) == NULL)
204		p = argv[0];
205	else
206		p++;
207	if (strcmp(p, "[") == 0) {
208		if (strcmp(argv[--argc], "]") != 0)
209			error("missing ]");
210		argv[argc] = NULL;
211	}
212
213#ifndef SHELL
214	(void)setlocale(LC_CTYPE, "");
215#endif
216	t_wp = &argv[1];
217	res = !oexpr(t_lex(*t_wp));
218
219	if (*t_wp != NULL && *++t_wp != NULL)
220		syntax(*t_wp, "unexpected operator");
221	free(nargv);
222
223	return res;
224}
225
226static void
227syntax(const char *op, const char *msg)
228{
229
230	if (op && *op)
231		error("%s: %s", op, msg);
232	else
233		error("%s", msg);
234}
235
236static int
237oexpr(enum token n)
238{
239	int res;
240
241	res = aexpr(n);
242	if (t_lex(*++t_wp) == BOR)
243		return oexpr(t_lex(*++t_wp)) || res;
244	t_wp--;
245	return res;
246}
247
248static int
249aexpr(enum token n)
250{
251	int res;
252
253	res = nexpr(n);
254	if (t_lex(*++t_wp) == BAND)
255		return aexpr(t_lex(*++t_wp)) && res;
256	t_wp--;
257	return res;
258}
259
260static int
261nexpr(enum token n)
262{
263	if (n == UNOT)
264		return !nexpr(t_lex(*++t_wp));
265	return primary(n);
266}
267
268static int
269primary(enum token n)
270{
271	enum token nn;
272	int res;
273
274	if (n == EOI)
275		return 0;		/* missing expression */
276	if (n == LPAREN) {
277		if ((nn = t_lex(*++t_wp)) == RPAREN)
278			return 0;	/* missing expression */
279		res = oexpr(nn);
280		if (t_lex(*++t_wp) != RPAREN)
281			syntax(NULL, "closing paren expected");
282		return res;
283	}
284	if (t_wp_op && t_wp_op->op_type == UNOP) {
285		/* unary expression */
286		if (*++t_wp == NULL)
287			syntax(t_wp_op->op_text, "argument expected");
288		switch (n) {
289		case STREZ:
290			return strlen(*t_wp) == 0;
291		case STRNZ:
292			return strlen(*t_wp) != 0;
293		case FILTT:
294			return isatty(getn(*t_wp));
295		default:
296			return filstat(*t_wp, n);
297		}
298	}
299
300	if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
301		return binop();
302	}
303
304	return strlen(*t_wp) > 0;
305}
306
307static int
308binop(void)
309{
310	const char *opnd1, *opnd2;
311	struct t_op const *op;
312
313	opnd1 = *t_wp;
314	(void) t_lex(*++t_wp);
315	op = t_wp_op;
316
317	if ((opnd2 = *++t_wp) == NULL)
318		syntax(op->op_text, "argument expected");
319
320	switch (op->op_num) {
321	case STREQ:
322		return strcmp(opnd1, opnd2) == 0;
323	case STRNE:
324		return strcmp(opnd1, opnd2) != 0;
325	case STRLT:
326		return strcmp(opnd1, opnd2) < 0;
327	case STRGT:
328		return strcmp(opnd1, opnd2) > 0;
329	case INTEQ:
330		return intcmp(opnd1, opnd2) == 0;
331	case INTNE:
332		return intcmp(opnd1, opnd2) != 0;
333	case INTGE:
334		return intcmp(opnd1, opnd2) >= 0;
335	case INTGT:
336		return intcmp(opnd1, opnd2) > 0;
337	case INTLE:
338		return intcmp(opnd1, opnd2) <= 0;
339	case INTLT:
340		return intcmp(opnd1, opnd2) < 0;
341	case FILNT:
342		return newerf (opnd1, opnd2);
343	case FILOT:
344		return olderf (opnd1, opnd2);
345	case FILEQ:
346		return equalf (opnd1, opnd2);
347	default:
348		abort();
349		/* NOTREACHED */
350	}
351}
352
353static int
354filstat(char *nm, enum token mode)
355{
356	struct stat s;
357
358	if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
359		return 0;
360
361	switch (mode) {
362	case FILRD:
363		return (eaccess(nm, R_OK) == 0);
364	case FILWR:
365		return (eaccess(nm, W_OK) == 0);
366	case FILEX:
367		/* XXX work around eaccess(2) false positives for superuser */
368		if (eaccess(nm, X_OK) != 0)
369			return 0;
370		if (S_ISDIR(s.st_mode) || geteuid() != 0)
371			return 1;
372		return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
373	case FILEXIST:
374		return (eaccess(nm, F_OK) == 0);
375	case FILREG:
376		return S_ISREG(s.st_mode);
377	case FILDIR:
378		return S_ISDIR(s.st_mode);
379	case FILCDEV:
380		return S_ISCHR(s.st_mode);
381	case FILBDEV:
382		return S_ISBLK(s.st_mode);
383	case FILFIFO:
384		return S_ISFIFO(s.st_mode);
385	case FILSOCK:
386		return S_ISSOCK(s.st_mode);
387	case FILSYM:
388		return S_ISLNK(s.st_mode);
389	case FILSUID:
390		return (s.st_mode & S_ISUID) != 0;
391	case FILSGID:
392		return (s.st_mode & S_ISGID) != 0;
393	case FILSTCK:
394		return (s.st_mode & S_ISVTX) != 0;
395	case FILGZ:
396		return s.st_size > (off_t)0;
397	case FILUID:
398		return s.st_uid == geteuid();
399	case FILGID:
400		return s.st_gid == getegid();
401	default:
402		return 1;
403	}
404}
405
406static enum token
407t_lex(char *s)
408{
409	struct t_op const *op = ops;
410
411	if (s == 0) {
412		t_wp_op = NULL;
413		return EOI;
414	}
415	while (op->op_text) {
416		if (strcmp(s, op->op_text) == 0) {
417			if ((op->op_type == UNOP && isoperand()) ||
418			    (op->op_num == LPAREN && *(t_wp+1) == 0))
419				break;
420			t_wp_op = op;
421			return op->op_num;
422		}
423		op++;
424	}
425	t_wp_op = NULL;
426	return OPERAND;
427}
428
429static int
430isoperand(void)
431{
432	struct t_op const *op = ops;
433	char *s;
434	char *t;
435
436	if ((s  = *(t_wp+1)) == 0)
437		return 1;
438	if ((t = *(t_wp+2)) == 0)
439		return 0;
440	while (op->op_text) {
441		if (strcmp(s, op->op_text) == 0)
442			return op->op_type == BINOP &&
443			    (t[0] != ')' || t[1] != '\0');
444		op++;
445	}
446	return 0;
447}
448
449/* atoi with error detection */
450static int
451getn(const char *s)
452{
453	char *p;
454	long r;
455
456	errno = 0;
457	r = strtol(s, &p, 10);
458
459	if (s == p)
460		error("%s: bad number", s);
461
462	if (errno != 0)
463		error((errno == EINVAL) ? "%s: bad number" :
464					  "%s: out of range", s);
465
466	while (isspace((unsigned char)*p))
467		p++;
468
469	if (*p)
470		error("%s: bad number", s);
471
472	return (int) r;
473}
474
475/* atoi with error detection and 64 bit range */
476static intmax_t
477getq(const char *s)
478{
479	char *p;
480	intmax_t r;
481
482	errno = 0;
483	r = strtoimax(s, &p, 10);
484
485	if (s == p)
486		error("%s: bad number", s);
487
488	if (errno != 0)
489		error((errno == EINVAL) ? "%s: bad number" :
490					  "%s: out of range", s);
491
492	while (isspace((unsigned char)*p))
493		p++;
494
495	if (*p)
496		error("%s: bad number", s);
497
498	return r;
499}
500
501static int
502intcmp (const char *s1, const char *s2)
503{
504	intmax_t q1, q2;
505
506
507	q1 = getq(s1);
508	q2 = getq(s2);
509
510	if (q1 > q2)
511		return 1;
512
513	if (q1 < q2)
514		return -1;
515
516	return 0;
517}
518
519static int
520newerf (const char *f1, const char *f2)
521{
522	struct stat b1, b2;
523
524	if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0)
525		return 0;
526
527	if (b1.st_mtimespec.tv_sec > b2.st_mtimespec.tv_sec)
528		return 1;
529	if (b1.st_mtimespec.tv_sec < b2.st_mtimespec.tv_sec)
530		return 0;
531
532       return (b1.st_mtimespec.tv_nsec > b2.st_mtimespec.tv_nsec);
533}
534
535static int
536olderf (const char *f1, const char *f2)
537{
538	return (newerf(f2, f1));
539}
540
541static int
542equalf (const char *f1, const char *f2)
543{
544	struct stat b1, b2;
545
546	return (stat (f1, &b1) == 0 &&
547		stat (f2, &b2) == 0 &&
548		b1.st_dev == b2.st_dev &&
549		b1.st_ino == b2.st_ino);
550}
551