/usr.bin/csup/globtree.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 393 lines · 273 code · 45 blank · 75 comment · 52 complexity · 6b88efd0db40ae5cbb101c94e915b311 MD5 · raw file

  1. /*-
  2. * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions
  7. * are met:
  8. * 1. Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * 2. Redistributions in binary form must reproduce the above copyright
  11. * notice, this list of conditions and the following disclaimer in the
  12. * documentation and/or other materials provided with the distribution.
  13. *
  14. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  15. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  16. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  17. * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  18. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  19. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  20. * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  21. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  22. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  23. * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  24. * SUCH DAMAGE.
  25. *
  26. * $FreeBSD$
  27. */
  28. #include <sys/types.h>
  29. #include <assert.h>
  30. #include <regex.h>
  31. #include <stdlib.h>
  32. #include "fnmatch.h"
  33. #include "globtree.h"
  34. #include "misc.h"
  35. /*
  36. * The "GlobTree" interface allows one to construct arbitrarily complex
  37. * boolean expressions for evaluating whether to accept or reject a
  38. * filename. The globtree_test() function returns true or false
  39. * according to whether the name is accepted or rejected by the
  40. * expression.
  41. *
  42. * Expressions are trees constructed from nodes representing either
  43. * primitive matching operations (primaries) or operators that are
  44. * applied to their subexpressions. The simplest primitives are
  45. * globtree_false(), which matches nothing, and globtree_true(), which
  46. * matches everything.
  47. *
  48. * A more useful primitive is the matching operation, constructed with
  49. * globtree_match(). It will call fnmatch() with the suppliedi
  50. * shell-style pattern to determine if the filename matches.
  51. *
  52. * Expressions can be combined with the boolean operators AND, OR, and
  53. * NOT, to form more complex expressions.
  54. */
  55. /* Node types. */
  56. #define GLOBTREE_NOT 0
  57. #define GLOBTREE_AND 1
  58. #define GLOBTREE_OR 2
  59. #define GLOBTREE_MATCH 3
  60. #define GLOBTREE_REGEX 4
  61. #define GLOBTREE_TRUE 5
  62. #define GLOBTREE_FALSE 6
  63. /* A node. */
  64. struct globtree {
  65. int type;
  66. struct globtree *left;
  67. struct globtree *right;
  68. /* The "data" field points to the text pattern for GLOBTREE_MATCH
  69. nodes, and to the regex_t for GLOBTREE_REGEX nodes. For any
  70. other node, it is set to NULL. */
  71. void *data;
  72. /* The "flags" field contains the flags to pass to fnmatch() for
  73. GLOBTREE_MATCH nodes. */
  74. int flags;
  75. };
  76. static struct globtree *globtree_new(int);
  77. static int globtree_eval(struct globtree *, const char *);
  78. static struct globtree *
  79. globtree_new(int type)
  80. {
  81. struct globtree *gt;
  82. gt = xmalloc(sizeof(struct globtree));
  83. gt->type = type;
  84. gt->data = NULL;
  85. gt->flags = 0;
  86. gt->left = NULL;
  87. gt->right = NULL;
  88. return (gt);
  89. }
  90. struct globtree *
  91. globtree_true(void)
  92. {
  93. struct globtree *gt;
  94. gt = globtree_new(GLOBTREE_TRUE);
  95. return (gt);
  96. }
  97. struct globtree *
  98. globtree_false(void)
  99. {
  100. struct globtree *gt;
  101. gt = globtree_new(GLOBTREE_FALSE);
  102. return (gt);
  103. }
  104. struct globtree *
  105. globtree_match(const char *pattern, int flags)
  106. {
  107. struct globtree *gt;
  108. gt = globtree_new(GLOBTREE_MATCH);
  109. gt->data = xstrdup(pattern);
  110. gt->flags = flags;
  111. return (gt);
  112. }
  113. struct globtree *
  114. globtree_regex(const char *pattern)
  115. {
  116. struct globtree *gt;
  117. int error;
  118. gt = globtree_new(GLOBTREE_REGEX);
  119. gt->data = xmalloc(sizeof(regex_t));
  120. error = regcomp(gt->data, pattern, REG_NOSUB);
  121. assert(!error);
  122. return (gt);
  123. }
  124. struct globtree *
  125. globtree_and(struct globtree *left, struct globtree *right)
  126. {
  127. struct globtree *gt;
  128. if (left->type == GLOBTREE_FALSE || right->type == GLOBTREE_FALSE) {
  129. globtree_free(left);
  130. globtree_free(right);
  131. gt = globtree_false();
  132. return (gt);
  133. }
  134. if (left->type == GLOBTREE_TRUE) {
  135. globtree_free(left);
  136. return (right);
  137. }
  138. if (right->type == GLOBTREE_TRUE) {
  139. globtree_free(right);
  140. return (left);
  141. }
  142. gt = globtree_new(GLOBTREE_AND);
  143. gt->left = left;
  144. gt->right = right;
  145. return (gt);
  146. }
  147. struct globtree *
  148. globtree_or(struct globtree *left, struct globtree *right)
  149. {
  150. struct globtree *gt;
  151. if (left->type == GLOBTREE_TRUE || right->type == GLOBTREE_TRUE) {
  152. globtree_free(left);
  153. globtree_free(right);
  154. gt = globtree_true();
  155. return (gt);
  156. }
  157. if (left->type == GLOBTREE_FALSE) {
  158. globtree_free(left);
  159. return (right);
  160. }
  161. if (right->type == GLOBTREE_FALSE) {
  162. globtree_free(right);
  163. return (left);
  164. }
  165. gt = globtree_new(GLOBTREE_OR);
  166. gt->left = left;
  167. gt->right = right;
  168. return (gt);
  169. }
  170. struct globtree *
  171. globtree_not(struct globtree *child)
  172. {
  173. struct globtree *gt;
  174. if (child->type == GLOBTREE_TRUE) {
  175. globtree_free(child);
  176. gt = globtree_new(GLOBTREE_FALSE);
  177. return (gt);
  178. }
  179. if (child->type == GLOBTREE_FALSE) {
  180. globtree_free(child);
  181. gt = globtree_new(GLOBTREE_TRUE);
  182. return (gt);
  183. }
  184. gt = globtree_new(GLOBTREE_NOT);
  185. gt->left = child;
  186. return (gt);
  187. }
  188. /* Evaluate one node (must be a leaf node). */
  189. static int
  190. globtree_eval(struct globtree *gt, const char *path)
  191. {
  192. int rv;
  193. switch (gt->type) {
  194. case GLOBTREE_TRUE:
  195. return (1);
  196. case GLOBTREE_FALSE:
  197. return (0);
  198. case GLOBTREE_MATCH:
  199. assert(gt->data != NULL);
  200. rv = fnmatch(gt->data, path, gt->flags);
  201. if (rv == 0)
  202. return (1);
  203. assert(rv == FNM_NOMATCH);
  204. return (0);
  205. case GLOBTREE_REGEX:
  206. assert(gt->data != NULL);
  207. rv = regexec(gt->data, path, 0, NULL, 0);
  208. if (rv == 0)
  209. return (1);
  210. assert(rv == REG_NOMATCH);
  211. return (0);
  212. }
  213. assert(0);
  214. return (-1);
  215. }
  216. /* Small stack API to walk the tree iteratively. */
  217. typedef enum {
  218. STATE_DOINGLEFT,
  219. STATE_DOINGRIGHT
  220. } walkstate_t;
  221. struct stack {
  222. struct stackelem *stack;
  223. size_t size;
  224. size_t in;
  225. };
  226. struct stackelem {
  227. struct globtree *node;
  228. walkstate_t state;
  229. };
  230. static void
  231. stack_init(struct stack *stack)
  232. {
  233. stack->in = 0;
  234. stack->size = 8; /* Initial size. */
  235. stack->stack = xmalloc(sizeof(struct stackelem) * stack->size);
  236. }
  237. static size_t
  238. stack_size(struct stack *stack)
  239. {
  240. return (stack->in);
  241. }
  242. static void
  243. stack_push(struct stack *stack, struct globtree *node, walkstate_t state)
  244. {
  245. struct stackelem *e;
  246. if (stack->in == stack->size) {
  247. stack->size *= 2;
  248. stack->stack = xrealloc(stack->stack,
  249. sizeof(struct stackelem) * stack->size);
  250. }
  251. e = stack->stack + stack->in++;
  252. e->node = node;
  253. e->state = state;
  254. }
  255. static void
  256. stack_pop(struct stack *stack, struct globtree **node, walkstate_t *state)
  257. {
  258. struct stackelem *e;
  259. assert(stack->in > 0);
  260. e = stack->stack + --stack->in;
  261. *node = e->node;
  262. *state = e->state;
  263. }
  264. static void
  265. stack_free(struct stack *s)
  266. {
  267. free(s->stack);
  268. }
  269. /* Tests if the supplied filename matches. */
  270. int
  271. globtree_test(struct globtree *gt, const char *path)
  272. {
  273. struct stack stack;
  274. walkstate_t state;
  275. int val;
  276. stack_init(&stack);
  277. for (;;) {
  278. doleft:
  279. /* Descend to the left until we hit bottom. */
  280. while (gt->left != NULL) {
  281. stack_push(&stack, gt, STATE_DOINGLEFT);
  282. gt = gt->left;
  283. }
  284. /* Now we're at a leaf node. Evaluate it. */
  285. val = globtree_eval(gt, path);
  286. /* Ascend, propagating the value through operator nodes. */
  287. for (;;) {
  288. if (stack_size(&stack) == 0) {
  289. stack_free(&stack);
  290. return (val);
  291. }
  292. stack_pop(&stack, &gt, &state);
  293. switch (gt->type) {
  294. case GLOBTREE_NOT:
  295. val = !val;
  296. break;
  297. case GLOBTREE_AND:
  298. /* If we haven't yet evaluated the right subtree
  299. and the partial result is true, descend to
  300. the right. Otherwise the result is already
  301. determined to be val. */
  302. if (state == STATE_DOINGLEFT && val) {
  303. stack_push(&stack, gt,
  304. STATE_DOINGRIGHT);
  305. gt = gt->right;
  306. goto doleft;
  307. }
  308. break;
  309. case GLOBTREE_OR:
  310. /* If we haven't yet evaluated the right subtree
  311. and the partial result is false, descend to
  312. the right. Otherwise the result is already
  313. determined to be val. */
  314. if (state == STATE_DOINGLEFT && !val) {
  315. stack_push(&stack, gt,
  316. STATE_DOINGRIGHT);
  317. gt = gt->right;
  318. goto doleft;
  319. }
  320. break;
  321. default:
  322. /* We only push nodes that have children. */
  323. assert(0);
  324. return (-1);
  325. }
  326. }
  327. }
  328. }
  329. /*
  330. * We could de-recursify this function using a stack, but it would be
  331. * overkill since it is never called from a thread context with a
  332. * limited stack size nor used in a critical path, so I think we can
  333. * afford keeping it recursive.
  334. */
  335. void
  336. globtree_free(struct globtree *gt)
  337. {
  338. if (gt->data != NULL) {
  339. if (gt->type == GLOBTREE_REGEX)
  340. regfree(gt->data);
  341. free(gt->data);
  342. }
  343. if (gt->left != NULL)
  344. globtree_free(gt->left);
  345. if (gt->right != NULL)
  346. globtree_free(gt->right);
  347. free(gt);
  348. }