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