logo

utils-std

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

test.c (11000B)


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