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