freebsd-dev/contrib/csup/globtree.c
2006-03-14 03:51:13 +00:00

394 lines
8.9 KiB
C

/*-
* 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, &gt, &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);
}