test.c revision 297767
1/*	$NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $	*/
2
3/*-
4 * test(1); version 7-like  --  author Erik Baalbergen
5 * modified by Eric Gisin to be used as built-in.
6 * modified by Arnold Robbins to add SVR3 compatibility
7 * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
8 * modified by J.T. Conklin for NetBSD.
9 *
10 * This program is in the Public Domain.
11 */
12/*
13 * Important: This file is used both as a standalone program /bin/test and
14 * as a builtin for /bin/sh (#define SHELL).
15 */
16
17#include <sys/cdefs.h>
18__FBSDID("$FreeBSD: stable/10/bin/test/test.c 297767 2016-04-09 21:55:58Z jilles $");
19
20#include <sys/types.h>
21#include <sys/stat.h>
22
23#include <ctype.h>
24#include <err.h>
25#include <errno.h>
26#include <inttypes.h>
27#include <limits.h>
28#include <stdarg.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <unistd.h>
33
34#ifdef SHELL
35#define main testcmd
36#include "bltin/bltin.h"
37#else
38#include <locale.h>
39
40static void error(const char *, ...) __dead2 __printf0like(1, 2);
41
42static void
43error(const char *msg, ...)
44{
45	va_list ap;
46	va_start(ap, msg);
47	verrx(2, msg, ap);
48	/*NOTREACHED*/
49	va_end(ap);
50}
51#endif
52
53/* test(1) accepts the following grammar:
54	oexpr	::= aexpr | aexpr "-o" oexpr ;
55	aexpr	::= nexpr | nexpr "-a" aexpr ;
56	nexpr	::= primary | "!" primary
57	primary	::= unary-operator operand
58		| operand binary-operator operand
59		| operand
60		| "(" oexpr ")"
61		;
62	unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
63		"-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
64
65	binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
66			"-nt"|"-ot"|"-ef";
67	operand ::= <any legal UNIX file name>
68*/
69
70enum token_types {
71	UNOP = 0x100,
72	BINOP = 0x200,
73	BUNOP = 0x300,
74	BBINOP = 0x400,
75	PAREN = 0x500
76};
77
78enum token {
79	EOI,
80	OPERAND,
81	FILRD = UNOP + 1,
82	FILWR,
83	FILEX,
84	FILEXIST,
85	FILREG,
86	FILDIR,
87	FILCDEV,
88	FILBDEV,
89	FILFIFO,
90	FILSOCK,
91	FILSYM,
92	FILGZ,
93	FILTT,
94	FILSUID,
95	FILSGID,
96	FILSTCK,
97	STREZ,
98	STRNZ,
99	FILUID,
100	FILGID,
101	FILNT = BINOP + 1,
102	FILOT,
103	FILEQ,
104	STREQ,
105	STRNE,
106	STRLT,
107	STRGT,
108	INTEQ,
109	INTNE,
110	INTGE,
111	INTGT,
112	INTLE,
113	INTLT,
114	UNOT = BUNOP + 1,
115	BAND = BBINOP + 1,
116	BOR,
117	LPAREN = PAREN + 1,
118	RPAREN
119};
120
121#define TOKEN_TYPE(token) ((token) & 0xff00)
122
123static struct t_op {
124	char op_text[4];
125	short op_num;
126} const ops [] = {
127	{"-r",	FILRD},
128	{"-w",	FILWR},
129	{"-x",	FILEX},
130	{"-e",	FILEXIST},
131	{"-f",	FILREG},
132	{"-d",	FILDIR},
133	{"-c",	FILCDEV},
134	{"-b",	FILBDEV},
135	{"-p",	FILFIFO},
136	{"-u",	FILSUID},
137	{"-g",	FILSGID},
138	{"-k",	FILSTCK},
139	{"-s",	FILGZ},
140	{"-t",	FILTT},
141	{"-z",	STREZ},
142	{"-n",	STRNZ},
143	{"-h",	FILSYM},		/* for backwards compat */
144	{"-O",	FILUID},
145	{"-G",	FILGID},
146	{"-L",	FILSYM},
147	{"-S",	FILSOCK},
148	{"=",	STREQ},
149	{"==",	STREQ},
150	{"!=",	STRNE},
151	{"<",	STRLT},
152	{">",	STRGT},
153	{"-eq",	INTEQ},
154	{"-ne",	INTNE},
155	{"-ge",	INTGE},
156	{"-gt",	INTGT},
157	{"-le",	INTLE},
158	{"-lt",	INTLT},
159	{"-nt",	FILNT},
160	{"-ot",	FILOT},
161	{"-ef",	FILEQ},
162	{"!",	UNOT},
163	{"-a",	BAND},
164	{"-o",	BOR},
165	{"(",	LPAREN},
166	{")",	RPAREN},
167	{"",	0}
168};
169
170static int nargc;
171static char **t_wp;
172static int parenlevel;
173
174static int	aexpr(enum token);
175static int	binop(enum token);
176static int	equalf(const char *, const char *);
177static int	filstat(char *, enum token);
178static int	getn(const char *);
179static intmax_t	getq(const char *);
180static int	intcmp(const char *, const char *);
181static int	isunopoperand(void);
182static int	islparenoperand(void);
183static int	isrparenoperand(void);
184static int	newerf(const char *, const char *);
185static int	nexpr(enum token);
186static int	oexpr(enum token);
187static int	olderf(const char *, const char *);
188static int	primary(enum token);
189static void	syntax(const char *, const char *);
190static enum	token t_lex(char *);
191
192int
193main(int argc, char **argv)
194{
195	int	res;
196	char	*p;
197
198	if ((p = strrchr(argv[0], '/')) == NULL)
199		p = argv[0];
200	else
201		p++;
202	if (strcmp(p, "[") == 0) {
203		if (strcmp(argv[--argc], "]") != 0)
204			error("missing ]");
205		argv[argc] = NULL;
206	}
207
208	/* no expression => false */
209	if (--argc <= 0)
210		return 1;
211
212#ifndef SHELL
213	(void)setlocale(LC_CTYPE, "");
214#endif
215	nargc = argc;
216	t_wp = &argv[1];
217	parenlevel = 0;
218	if (nargc == 4 && strcmp(*t_wp, "!") == 0) {
219		/* Things like ! "" -o x do not fit in the normal grammar. */
220		--nargc;
221		++t_wp;
222		res = oexpr(t_lex(*t_wp));
223	} else
224		res = !oexpr(t_lex(*t_wp));
225
226	if (--nargc > 0)
227		syntax(*t_wp, "unexpected operator");
228
229	return res;
230}
231
232static void
233syntax(const char *op, const char *msg)
234{
235
236	if (op && *op)
237		error("%s: %s", op, msg);
238	else
239		error("%s", msg);
240}
241
242static int
243oexpr(enum token n)
244{
245	int res;
246
247	res = aexpr(n);
248	if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR)
249		return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ||
250		    res;
251	t_wp--;
252	nargc++;
253	return res;
254}
255
256static int
257aexpr(enum token n)
258{
259	int res;
260
261	res = nexpr(n);
262	if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND)
263		return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) &&
264		    res;
265	t_wp--;
266	nargc++;
267	return res;
268}
269
270static int
271nexpr(enum token n)
272{
273	if (n == UNOT)
274		return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL));
275	return primary(n);
276}
277
278static int
279primary(enum token n)
280{
281	enum token nn;
282	int res;
283
284	if (n == EOI)
285		return 0;		/* missing expression */
286	if (n == LPAREN) {
287		parenlevel++;
288		if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ==
289		    RPAREN) {
290			parenlevel--;
291			return 0;	/* missing expression */
292		}
293		res = oexpr(nn);
294		if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN)
295			syntax(NULL, "closing paren expected");
296		parenlevel--;
297		return res;
298	}
299	if (TOKEN_TYPE(n) == UNOP) {
300		/* unary expression */
301		if (--nargc == 0)
302			syntax(NULL, "argument expected"); /* impossible */
303		switch (n) {
304		case STREZ:
305			return strlen(*++t_wp) == 0;
306		case STRNZ:
307			return strlen(*++t_wp) != 0;
308		case FILTT:
309			return isatty(getn(*++t_wp));
310		default:
311			return filstat(*++t_wp, n);
312		}
313	}
314
315	nn = t_lex(nargc > 0 ? t_wp[1] : NULL);
316	if (TOKEN_TYPE(nn) == BINOP)
317		return binop(nn);
318
319	return strlen(*t_wp) > 0;
320}
321
322static int
323binop(enum token n)
324{
325	const char *opnd1, *op, *opnd2;
326
327	opnd1 = *t_wp;
328	op = nargc > 0 ? (--nargc, *++t_wp) : NULL;
329
330	if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL)
331		syntax(op, "argument expected");
332
333	switch (n) {
334	case STREQ:
335		return strcmp(opnd1, opnd2) == 0;
336	case STRNE:
337		return strcmp(opnd1, opnd2) != 0;
338	case STRLT:
339		return strcmp(opnd1, opnd2) < 0;
340	case STRGT:
341		return strcmp(opnd1, opnd2) > 0;
342	case INTEQ:
343		return intcmp(opnd1, opnd2) == 0;
344	case INTNE:
345		return intcmp(opnd1, opnd2) != 0;
346	case INTGE:
347		return intcmp(opnd1, opnd2) >= 0;
348	case INTGT:
349		return intcmp(opnd1, opnd2) > 0;
350	case INTLE:
351		return intcmp(opnd1, opnd2) <= 0;
352	case INTLT:
353		return intcmp(opnd1, opnd2) < 0;
354	case FILNT:
355		return newerf (opnd1, opnd2);
356	case FILOT:
357		return olderf (opnd1, opnd2);
358	case FILEQ:
359		return equalf (opnd1, opnd2);
360	default:
361		abort();
362		/* NOTREACHED */
363	}
364}
365
366static int
367filstat(char *nm, enum token mode)
368{
369	struct stat s;
370
371	if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
372		return 0;
373
374	switch (mode) {
375	case FILRD:
376		return (eaccess(nm, R_OK) == 0);
377	case FILWR:
378		return (eaccess(nm, W_OK) == 0);
379	case FILEX:
380		/* XXX work around eaccess(2) false positives for superuser */
381		if (eaccess(nm, X_OK) != 0)
382			return 0;
383		if (S_ISDIR(s.st_mode) || geteuid() != 0)
384			return 1;
385		return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
386	case FILEXIST:
387		return (eaccess(nm, F_OK) == 0);
388	case FILREG:
389		return S_ISREG(s.st_mode);
390	case FILDIR:
391		return S_ISDIR(s.st_mode);
392	case FILCDEV:
393		return S_ISCHR(s.st_mode);
394	case FILBDEV:
395		return S_ISBLK(s.st_mode);
396	case FILFIFO:
397		return S_ISFIFO(s.st_mode);
398	case FILSOCK:
399		return S_ISSOCK(s.st_mode);
400	case FILSYM:
401		return S_ISLNK(s.st_mode);
402	case FILSUID:
403		return (s.st_mode & S_ISUID) != 0;
404	case FILSGID:
405		return (s.st_mode & S_ISGID) != 0;
406	case FILSTCK:
407		return (s.st_mode & S_ISVTX) != 0;
408	case FILGZ:
409		return s.st_size > (off_t)0;
410	case FILUID:
411		return s.st_uid == geteuid();
412	case FILGID:
413		return s.st_gid == getegid();
414	default:
415		return 1;
416	}
417}
418
419static enum token
420t_lex(char *s)
421{
422	struct t_op const *op = ops;
423
424	if (s == 0) {
425		return EOI;
426	}
427	while (*op->op_text) {
428		if (strcmp(s, op->op_text) == 0) {
429			if (((TOKEN_TYPE(op->op_num) == UNOP ||
430			    TOKEN_TYPE(op->op_num) == BUNOP)
431						&& isunopoperand()) ||
432			    (op->op_num == LPAREN && islparenoperand()) ||
433			    (op->op_num == RPAREN && isrparenoperand()))
434				break;
435			return op->op_num;
436		}
437		op++;
438	}
439	return OPERAND;
440}
441
442static int
443isunopoperand(void)
444{
445	struct t_op const *op = ops;
446	char *s;
447	char *t;
448
449	if (nargc == 1)
450		return 1;
451	s = *(t_wp + 1);
452	if (nargc == 2)
453		return parenlevel == 1 && strcmp(s, ")") == 0;
454	t = *(t_wp + 2);
455	while (*op->op_text) {
456		if (strcmp(s, op->op_text) == 0)
457			return TOKEN_TYPE(op->op_num) == BINOP &&
458			    (parenlevel == 0 || t[0] != ')' || t[1] != '\0');
459		op++;
460	}
461	return 0;
462}
463
464static int
465islparenoperand(void)
466{
467	struct t_op const *op = ops;
468	char *s;
469
470	if (nargc == 1)
471		return 1;
472	s = *(t_wp + 1);
473	if (nargc == 2)
474		return parenlevel == 1 && strcmp(s, ")") == 0;
475	if (nargc != 3)
476		return 0;
477	while (*op->op_text) {
478		if (strcmp(s, op->op_text) == 0)
479			return TOKEN_TYPE(op->op_num) == BINOP;
480		op++;
481	}
482	return 0;
483}
484
485static int
486isrparenoperand(void)
487{
488	char *s;
489
490	if (nargc == 1)
491		return 0;
492	s = *(t_wp + 1);
493	if (nargc == 2)
494		return parenlevel == 1 && strcmp(s, ")") == 0;
495	return 0;
496}
497
498/* atoi with error detection */
499static int
500getn(const char *s)
501{
502	char *p;
503	long r;
504
505	errno = 0;
506	r = strtol(s, &p, 10);
507
508	if (s == p)
509		error("%s: bad number", s);
510
511	if (errno != 0)
512		error((errno == EINVAL) ? "%s: bad number" :
513					  "%s: out of range", s);
514
515	while (isspace((unsigned char)*p))
516		p++;
517
518	if (*p)
519		error("%s: bad number", s);
520
521	return (int) r;
522}
523
524/* atoi with error detection and 64 bit range */
525static intmax_t
526getq(const char *s)
527{
528	char *p;
529	intmax_t r;
530
531	errno = 0;
532	r = strtoimax(s, &p, 10);
533
534	if (s == p)
535		error("%s: bad number", s);
536
537	if (errno != 0)
538		error((errno == EINVAL) ? "%s: bad number" :
539					  "%s: out of range", s);
540
541	while (isspace((unsigned char)*p))
542		p++;
543
544	if (*p)
545		error("%s: bad number", s);
546
547	return r;
548}
549
550static int
551intcmp (const char *s1, const char *s2)
552{
553	intmax_t q1, q2;
554
555
556	q1 = getq(s1);
557	q2 = getq(s2);
558
559	if (q1 > q2)
560		return 1;
561
562	if (q1 < q2)
563		return -1;
564
565	return 0;
566}
567
568static int
569newerf (const char *f1, const char *f2)
570{
571	struct stat b1, b2;
572
573	if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0)
574		return 0;
575
576	if (b1.st_mtim.tv_sec > b2.st_mtim.tv_sec)
577		return 1;
578	if (b1.st_mtim.tv_sec < b2.st_mtim.tv_sec)
579		return 0;
580
581       return (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec);
582}
583
584static int
585olderf (const char *f1, const char *f2)
586{
587	return (newerf(f2, f1));
588}
589
590static int
591equalf (const char *f1, const char *f2)
592{
593	struct stat b1, b2;
594
595	return (stat (f1, &b1) == 0 &&
596		stat (f2, &b2) == 0 &&
597		b1.st_dev == b2.st_dev &&
598		b1.st_ino == b2.st_ino);
599}
600