test.c revision 297766
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 297766 2016-04-09 21:49:57Z 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(void);
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	if (TOKEN_TYPE(t_lex(nargc > 0 ? t_wp[1] : NULL)) == BINOP)
316		return binop();
317
318	return strlen(*t_wp) > 0;
319}
320
321static int
322binop(void)
323{
324	const char *opnd1, *op, *opnd2;
325	enum token n;
326
327	opnd1 = *t_wp;
328	op = nargc > 0 ? t_wp[1] : NULL;
329	n = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL);
330
331	if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL)
332		syntax(op, "argument expected");
333
334	switch (n) {
335	case STREQ:
336		return strcmp(opnd1, opnd2) == 0;
337	case STRNE:
338		return strcmp(opnd1, opnd2) != 0;
339	case STRLT:
340		return strcmp(opnd1, opnd2) < 0;
341	case STRGT:
342		return strcmp(opnd1, opnd2) > 0;
343	case INTEQ:
344		return intcmp(opnd1, opnd2) == 0;
345	case INTNE:
346		return intcmp(opnd1, opnd2) != 0;
347	case INTGE:
348		return intcmp(opnd1, opnd2) >= 0;
349	case INTGT:
350		return intcmp(opnd1, opnd2) > 0;
351	case INTLE:
352		return intcmp(opnd1, opnd2) <= 0;
353	case INTLT:
354		return intcmp(opnd1, opnd2) < 0;
355	case FILNT:
356		return newerf (opnd1, opnd2);
357	case FILOT:
358		return olderf (opnd1, opnd2);
359	case FILEQ:
360		return equalf (opnd1, opnd2);
361	default:
362		abort();
363		/* NOTREACHED */
364	}
365}
366
367static int
368filstat(char *nm, enum token mode)
369{
370	struct stat s;
371
372	if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
373		return 0;
374
375	switch (mode) {
376	case FILRD:
377		return (eaccess(nm, R_OK) == 0);
378	case FILWR:
379		return (eaccess(nm, W_OK) == 0);
380	case FILEX:
381		/* XXX work around eaccess(2) false positives for superuser */
382		if (eaccess(nm, X_OK) != 0)
383			return 0;
384		if (S_ISDIR(s.st_mode) || geteuid() != 0)
385			return 1;
386		return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
387	case FILEXIST:
388		return (eaccess(nm, F_OK) == 0);
389	case FILREG:
390		return S_ISREG(s.st_mode);
391	case FILDIR:
392		return S_ISDIR(s.st_mode);
393	case FILCDEV:
394		return S_ISCHR(s.st_mode);
395	case FILBDEV:
396		return S_ISBLK(s.st_mode);
397	case FILFIFO:
398		return S_ISFIFO(s.st_mode);
399	case FILSOCK:
400		return S_ISSOCK(s.st_mode);
401	case FILSYM:
402		return S_ISLNK(s.st_mode);
403	case FILSUID:
404		return (s.st_mode & S_ISUID) != 0;
405	case FILSGID:
406		return (s.st_mode & S_ISGID) != 0;
407	case FILSTCK:
408		return (s.st_mode & S_ISVTX) != 0;
409	case FILGZ:
410		return s.st_size > (off_t)0;
411	case FILUID:
412		return s.st_uid == geteuid();
413	case FILGID:
414		return s.st_gid == getegid();
415	default:
416		return 1;
417	}
418}
419
420static enum token
421t_lex(char *s)
422{
423	struct t_op const *op = ops;
424
425	if (s == 0) {
426		return EOI;
427	}
428	while (*op->op_text) {
429		if (strcmp(s, op->op_text) == 0) {
430			if (((TOKEN_TYPE(op->op_num) == UNOP ||
431			    TOKEN_TYPE(op->op_num) == BUNOP)
432						&& isunopoperand()) ||
433			    (op->op_num == LPAREN && islparenoperand()) ||
434			    (op->op_num == RPAREN && isrparenoperand()))
435				break;
436			return op->op_num;
437		}
438		op++;
439	}
440	return OPERAND;
441}
442
443static int
444isunopoperand(void)
445{
446	struct t_op const *op = ops;
447	char *s;
448	char *t;
449
450	if (nargc == 1)
451		return 1;
452	s = *(t_wp + 1);
453	if (nargc == 2)
454		return parenlevel == 1 && strcmp(s, ")") == 0;
455	t = *(t_wp + 2);
456	while (*op->op_text) {
457		if (strcmp(s, op->op_text) == 0)
458			return TOKEN_TYPE(op->op_num) == BINOP &&
459			    (parenlevel == 0 || t[0] != ')' || t[1] != '\0');
460		op++;
461	}
462	return 0;
463}
464
465static int
466islparenoperand(void)
467{
468	struct t_op const *op = ops;
469	char *s;
470
471	if (nargc == 1)
472		return 1;
473	s = *(t_wp + 1);
474	if (nargc == 2)
475		return parenlevel == 1 && strcmp(s, ")") == 0;
476	if (nargc != 3)
477		return 0;
478	while (*op->op_text) {
479		if (strcmp(s, op->op_text) == 0)
480			return TOKEN_TYPE(op->op_num) == BINOP;
481		op++;
482	}
483	return 0;
484}
485
486static int
487isrparenoperand(void)
488{
489	char *s;
490
491	if (nargc == 1)
492		return 0;
493	s = *(t_wp + 1);
494	if (nargc == 2)
495		return parenlevel == 1 && strcmp(s, ")") == 0;
496	return 0;
497}
498
499/* atoi with error detection */
500static int
501getn(const char *s)
502{
503	char *p;
504	long r;
505
506	errno = 0;
507	r = strtol(s, &p, 10);
508
509	if (s == p)
510		error("%s: bad number", s);
511
512	if (errno != 0)
513		error((errno == EINVAL) ? "%s: bad number" :
514					  "%s: out of range", s);
515
516	while (isspace((unsigned char)*p))
517		p++;
518
519	if (*p)
520		error("%s: bad number", s);
521
522	return (int) r;
523}
524
525/* atoi with error detection and 64 bit range */
526static intmax_t
527getq(const char *s)
528{
529	char *p;
530	intmax_t r;
531
532	errno = 0;
533	r = strtoimax(s, &p, 10);
534
535	if (s == p)
536		error("%s: bad number", s);
537
538	if (errno != 0)
539		error((errno == EINVAL) ? "%s: bad number" :
540					  "%s: out of range", s);
541
542	while (isspace((unsigned char)*p))
543		p++;
544
545	if (*p)
546		error("%s: bad number", s);
547
548	return r;
549}
550
551static int
552intcmp (const char *s1, const char *s2)
553{
554	intmax_t q1, q2;
555
556
557	q1 = getq(s1);
558	q2 = getq(s2);
559
560	if (q1 > q2)
561		return 1;
562
563	if (q1 < q2)
564		return -1;
565
566	return 0;
567}
568
569static int
570newerf (const char *f1, const char *f2)
571{
572	struct stat b1, b2;
573
574	if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0)
575		return 0;
576
577	if (b1.st_mtim.tv_sec > b2.st_mtim.tv_sec)
578		return 1;
579	if (b1.st_mtim.tv_sec < b2.st_mtim.tv_sec)
580		return 0;
581
582       return (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec);
583}
584
585static int
586olderf (const char *f1, const char *f2)
587{
588	return (newerf(f2, f1));
589}
590
591static int
592equalf (const char *f1, const char *f2)
593{
594	struct stat b1, b2;
595
596	return (stat (f1, &b1) == 0 &&
597		stat (f2, &b2) == 0 &&
598		b1.st_dev == b2.st_dev &&
599		b1.st_ino == b2.st_ino);
600}
601