/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
- /*-
- * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * $FreeBSD$
- */
- #include <sys/types.h>
- #include <assert.h>
- #include <regex.h>
- #include <stdlib.h>
- #include "fnmatch.h"
- #include "globtree.h"
- #include "misc.h"
- /*
- * The "GlobTree" interface allows one to construct arbitrarily complex
- * boolean expressions for evaluating whether to accept or reject a
- * filename. The globtree_test() function returns true or false
- * according to whether the name is accepted or rejected by the
- * expression.
- *
- * Expressions are trees constructed from nodes representing either
- * primitive matching operations (primaries) or operators that are
- * applied to their subexpressions. The simplest primitives are
- * globtree_false(), which matches nothing, and globtree_true(), which
- * matches everything.
- *
- * A more useful primitive is the matching operation, constructed with
- * globtree_match(). It will call fnmatch() with the suppliedi
- * shell-style pattern to determine if the filename matches.
- *
- * Expressions can be combined with the boolean operators AND, OR, and
- * NOT, to form more complex expressions.
- */
- /* Node types. */
- #define GLOBTREE_NOT 0
- #define GLOBTREE_AND 1
- #define GLOBTREE_OR 2
- #define GLOBTREE_MATCH 3
- #define GLOBTREE_REGEX 4
- #define GLOBTREE_TRUE 5
- #define GLOBTREE_FALSE 6
- /* A node. */
- struct globtree {
- int type;
- struct globtree *left;
- struct globtree *right;
- /* The "data" field points to the text pattern for GLOBTREE_MATCH
- nodes, and to the regex_t for GLOBTREE_REGEX nodes. For any
- other node, it is set to NULL. */
- void *data;
- /* The "flags" field contains the flags to pass to fnmatch() for
- GLOBTREE_MATCH nodes. */
- int flags;
- };
- static struct globtree *globtree_new(int);
- static int globtree_eval(struct globtree *, const char *);
- static struct globtree *
- globtree_new(int type)
- {
- struct globtree *gt;
- gt = xmalloc(sizeof(struct globtree));
- gt->type = type;
- gt->data = NULL;
- gt->flags = 0;
- gt->left = NULL;
- gt->right = NULL;
- return (gt);
- }
- struct globtree *
- globtree_true(void)
- {
- struct globtree *gt;
- gt = globtree_new(GLOBTREE_TRUE);
- return (gt);
- }
- struct globtree *
- globtree_false(void)
- {
- struct globtree *gt;
- gt = globtree_new(GLOBTREE_FALSE);
- return (gt);
- }
- struct globtree *
- globtree_match(const char *pattern, int flags)
- {
- struct globtree *gt;
- gt = globtree_new(GLOBTREE_MATCH);
- gt->data = xstrdup(pattern);
- gt->flags = flags;
- return (gt);
- }
- struct globtree *
- globtree_regex(const char *pattern)
- {
- struct globtree *gt;
- int error;
- gt = globtree_new(GLOBTREE_REGEX);
- gt->data = xmalloc(sizeof(regex_t));
- error = regcomp(gt->data, pattern, REG_NOSUB);
- assert(!error);
- return (gt);
- }
- struct globtree *
- globtree_and(struct globtree *left, struct globtree *right)
- {
- struct globtree *gt;
- if (left->type == GLOBTREE_FALSE || right->type == GLOBTREE_FALSE) {
- globtree_free(left);
- globtree_free(right);
- gt = globtree_false();
- return (gt);
- }
- if (left->type == GLOBTREE_TRUE) {
- globtree_free(left);
- return (right);
- }
- if (right->type == GLOBTREE_TRUE) {
- globtree_free(right);
- return (left);
- }
- gt = globtree_new(GLOBTREE_AND);
- gt->left = left;
- gt->right = right;
- return (gt);
- }
- struct globtree *
- globtree_or(struct globtree *left, struct globtree *right)
- {
- struct globtree *gt;
- if (left->type == GLOBTREE_TRUE || right->type == GLOBTREE_TRUE) {
- globtree_free(left);
- globtree_free(right);
- gt = globtree_true();
- return (gt);
- }
- if (left->type == GLOBTREE_FALSE) {
- globtree_free(left);
- return (right);
- }
- if (right->type == GLOBTREE_FALSE) {
- globtree_free(right);
- return (left);
- }
- gt = globtree_new(GLOBTREE_OR);
- gt->left = left;
- gt->right = right;
- return (gt);
- }
- struct globtree *
- globtree_not(struct globtree *child)
- {
- struct globtree *gt;
- if (child->type == GLOBTREE_TRUE) {
- globtree_free(child);
- gt = globtree_new(GLOBTREE_FALSE);
- return (gt);
- }
- if (child->type == GLOBTREE_FALSE) {
- globtree_free(child);
- gt = globtree_new(GLOBTREE_TRUE);
- return (gt);
- }
- gt = globtree_new(GLOBTREE_NOT);
- gt->left = child;
- return (gt);
- }
- /* Evaluate one node (must be a leaf node). */
- static int
- globtree_eval(struct globtree *gt, const char *path)
- {
- int rv;
- switch (gt->type) {
- case GLOBTREE_TRUE:
- return (1);
- case GLOBTREE_FALSE:
- return (0);
- case GLOBTREE_MATCH:
- assert(gt->data != NULL);
- rv = fnmatch(gt->data, path, gt->flags);
- if (rv == 0)
- return (1);
- assert(rv == FNM_NOMATCH);
- return (0);
- case GLOBTREE_REGEX:
- assert(gt->data != NULL);
- rv = regexec(gt->data, path, 0, NULL, 0);
- if (rv == 0)
- return (1);
- assert(rv == REG_NOMATCH);
- return (0);
- }
- assert(0);
- return (-1);
- }
- /* Small stack API to walk the tree iteratively. */
- typedef enum {
- STATE_DOINGLEFT,
- STATE_DOINGRIGHT
- } walkstate_t;
- struct stack {
- struct stackelem *stack;
- size_t size;
- size_t in;
- };
- struct stackelem {
- struct globtree *node;
- walkstate_t state;
- };
- static void
- stack_init(struct stack *stack)
- {
- stack->in = 0;
- stack->size = 8; /* Initial size. */
- stack->stack = xmalloc(sizeof(struct stackelem) * stack->size);
- }
- static size_t
- stack_size(struct stack *stack)
- {
- return (stack->in);
- }
- static void
- stack_push(struct stack *stack, struct globtree *node, walkstate_t state)
- {
- struct stackelem *e;
- if (stack->in == stack->size) {
- stack->size *= 2;
- stack->stack = xrealloc(stack->stack,
- sizeof(struct stackelem) * stack->size);
- }
- e = stack->stack + stack->in++;
- e->node = node;
- e->state = state;
- }
- static void
- stack_pop(struct stack *stack, struct globtree **node, walkstate_t *state)
- {
- struct stackelem *e;
- assert(stack->in > 0);
- e = stack->stack + --stack->in;
- *node = e->node;
- *state = e->state;
- }
- static void
- stack_free(struct stack *s)
- {
- free(s->stack);
- }
- /* Tests if the supplied filename matches. */
- int
- globtree_test(struct globtree *gt, const char *path)
- {
- struct stack stack;
- walkstate_t state;
- int val;
- stack_init(&stack);
- for (;;) {
- doleft:
- /* Descend to the left until we hit bottom. */
- while (gt->left != NULL) {
- stack_push(&stack, gt, STATE_DOINGLEFT);
- gt = gt->left;
- }
- /* Now we're at a leaf node. Evaluate it. */
- val = globtree_eval(gt, path);
- /* Ascend, propagating the value through operator nodes. */
- for (;;) {
- if (stack_size(&stack) == 0) {
- stack_free(&stack);
- return (val);
- }
- stack_pop(&stack, >, &state);
- switch (gt->type) {
- case GLOBTREE_NOT:
- val = !val;
- break;
- case GLOBTREE_AND:
- /* If we haven't yet evaluated the right subtree
- and the partial result is true, descend to
- the right. Otherwise the result is already
- determined to be val. */
- if (state == STATE_DOINGLEFT && val) {
- stack_push(&stack, gt,
- STATE_DOINGRIGHT);
- gt = gt->right;
- goto doleft;
- }
- break;
- case GLOBTREE_OR:
- /* If we haven't yet evaluated the right subtree
- and the partial result is false, descend to
- the right. Otherwise the result is already
- determined to be val. */
- if (state == STATE_DOINGLEFT && !val) {
- stack_push(&stack, gt,
- STATE_DOINGRIGHT);
- gt = gt->right;
- goto doleft;
- }
- break;
- default:
- /* We only push nodes that have children. */
- assert(0);
- return (-1);
- }
- }
- }
- }
- /*
- * We could de-recursify this function using a stack, but it would be
- * overkill since it is never called from a thread context with a
- * limited stack size nor used in a critical path, so I think we can
- * afford keeping it recursive.
- */
- void
- globtree_free(struct globtree *gt)
- {
- if (gt->data != NULL) {
- if (gt->type == GLOBTREE_REGEX)
- regfree(gt->data);
- free(gt->data);
- }
- if (gt->left != NULL)
- globtree_free(gt->left);
- if (gt->right != NULL)
- globtree_free(gt->right);
- free(gt);
- }