test.c 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. /* test.c - evaluate expression
  2. *
  3. * Copyright 2018 Rob Landley <rob@landley.net>
  4. *
  5. * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
  6. *
  7. * TODO sh [[ ]] options: < aaa<bbb > bbb>aaa ~= regex
  8. USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
  9. USE_TEST_GLUE(OLDTOY([, test, TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_NOHELP))
  10. config TEST
  11. bool "test"
  12. default y
  13. help
  14. usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y]
  15. Return true or false by performing tests. (With no arguments return false.)
  16. --- Tests with a single argument (after the option):
  17. PATH is/has:
  18. -b block device -f regular file -p fifo -u setuid bit
  19. -c char device -g setgid -r read bit -w write bit
  20. -d directory -h symlink -S socket -x execute bit
  21. -e exists -L symlink -s nonzero size -k sticky bit
  22. STRING is:
  23. -n nonzero size -z zero size (STRING by itself implies -n)
  24. FD (integer file descriptor) is:
  25. -t a TTY
  26. --- Tests with one argument on each side of an operator:
  27. Two strings:
  28. = are identical != differ
  29. Two integers:
  30. -eq equal -gt first > second -lt first < second
  31. -ne not equal -ge first >= second -le first <= second
  32. --- Modify or combine tests:
  33. ! EXPR not (swap true/false) EXPR -a EXPR and (are both true)
  34. ( EXPR ) evaluate this first EXPR -o EXPR or (is either true)
  35. config TEST_GLUE
  36. bool
  37. default y
  38. depends on TEST || SH
  39. */
  40. #include "toys.h"
  41. // Consume 3, 2, or 1 argument test, returning result and *count used.
  42. static int do_test(char **args, int *count)
  43. {
  44. char c, *s;
  45. int i;
  46. if (*count>=3) {
  47. *count = 3;
  48. char *s = args[1], *ss = "eqnegtgeltle";
  49. if (!strcmp(s, "=") || !strcmp(s, "==")) return !strcmp(args[0], args[2]);
  50. if (!strcmp(s, "!=")) return strcmp(args[0], args[2]);
  51. if (*s=='-' && strlen(s)==3 && (s = strstr(ss, s+1)) && !((i = s-ss)&1)) {
  52. long long a = atolx(args[0]), b = atolx(args[2]);
  53. if (!i) return a == b;
  54. if (i==2) return a != b;
  55. if (i==4) return a > b;
  56. if (i==6) return a >= b;
  57. if (i==8) return a < b;
  58. if (i==10) return a<= b;
  59. }
  60. }
  61. s = *args;
  62. if (*count>=2 && *s == '-' && s[1] && !s[2]) {
  63. *count = 2;
  64. c = s[1];
  65. if (-1 != (i = stridx("hLbcdefgkpSusxwr", c))) {
  66. struct stat st;
  67. // stat or lstat, then handle rwx and s
  68. if (-1 == ((i<2) ? lstat : stat)(args[1], &st)) return 0;
  69. if (i>=13) return !!(st.st_mode&(0111<<(i-13)));
  70. if (c == 's') return !!st.st_size; // otherwise 1<<32 == 0
  71. // handle file type checking and SUID/SGID
  72. if ((i = ((char []){80,80,48,16,32,0,64,2,1,8,96,4}[i])<<9)>=4096)
  73. return (st.st_mode&S_IFMT) == i;
  74. else return (st.st_mode & i) == i;
  75. } else if (c == 'z') return !*args[1];
  76. else if (c == 'n') return *args[1];
  77. else if (c == 't') return isatty(atolx(args[1]));
  78. }
  79. return *count = 0;
  80. }
  81. #define NOT 1 // Most recent test had an odd number of preceding !
  82. #define AND 2 // test before -a failed since -o or ( so force false
  83. #define OR 4 // test before -o succeeded since ( so force true
  84. void test_main(void)
  85. {
  86. char *s;
  87. int pos, paren, pstack, result = 0;
  88. toys.exitval = 2;
  89. if (CFG_TOYBOX && !strcmp("[", toys.which->name))
  90. if (!toys.optc || strcmp("]", toys.optargs[--toys.optc]))
  91. error_exit("Missing ']'");
  92. // loop through command line arguments
  93. if (toys.optc) for (pos = paren = pstack = 0; ; pos++) {
  94. int len = toys.optc-pos;
  95. if (!len) perror_exit("need arg @%d", pos);
  96. // Evaluate next test
  97. result = do_test(toys.optargs+pos, &len);
  98. pos += len;
  99. // Single argument could be ! ( or nonempty
  100. if (!len) {
  101. if (toys.optargs[pos+1]) {
  102. if (!strcmp("!", toys.optargs[pos])) {
  103. pstack ^= NOT;
  104. continue;
  105. }
  106. if (!strcmp("(", toys.optargs[pos])) {
  107. if (++paren>9) perror_exit("bad (");
  108. pstack <<= 3;
  109. continue;
  110. }
  111. }
  112. result = *toys.optargs[pos++];
  113. }
  114. s = toys.optargs[pos];
  115. for (;;) {
  116. // Handle pending ! -a -o (the else means -o beats -a)
  117. if (pstack&NOT) result = !result;
  118. pstack &= ~NOT;
  119. if (pstack&OR) result = 1;
  120. else if (pstack&AND) result = 0;
  121. // Do it again for every )
  122. if (!paren || pos==toys.optc || strcmp(")", s)) break;
  123. paren--;
  124. pstack >>= 3;
  125. s = toys.optargs[++pos];
  126. }
  127. // Out of arguments?
  128. if (pos==toys.optc) {
  129. if (paren) perror_exit("need )");
  130. break;
  131. }
  132. // are we followed by -a or -o?
  133. if (!strcmp("-a", s)) {
  134. if (!result) pstack |= AND;
  135. } else if (!strcmp("-o", s)) {
  136. // -o flushes -a even if previous test was false
  137. pstack &=~AND;
  138. if (result) pstack |= OR;
  139. } else error_exit("too many arguments");
  140. }
  141. // Invert C logic to get shell logic
  142. toys.exitval = !result;
  143. }