logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/

test.c (11042B)


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