/* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License v.2.1. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "dmlib.h" #include "parse_rx.h" #ifdef DEBUG #include static void _regex_print(struct rx_node *rx, int depth, unsigned show_nodes) { int i, numchars; if (rx->left) { if (rx->left->type != CHARSET && (show_nodes || (!((rx->type == CAT || rx->type == OR) && rx->left->type == CAT)))) printf("("); _regex_print(rx->left, depth + 1, show_nodes); if (rx->left->type != CHARSET && (show_nodes || (!((rx->type == CAT || rx->type == OR) && rx->left->type == CAT)))) printf(")"); } /* display info about the node */ switch (rx->type) { case CAT: break; case OR: printf("|"); break; case STAR: printf("*"); break; case PLUS: printf("+"); break; case QUEST: printf("?"); break; case CHARSET: numchars = 0; for (i = 0; i < 256; i++) if (dm_bit(rx->charset, i) && (isprint(i) || i == HAT_CHAR || i == DOLLAR_CHAR)) numchars++; if (numchars == 97) { printf("."); break; } if (numchars > 1) printf("["); for (i = 0; i < 256; i++) if (dm_bit(rx->charset, i)) { if isprint(i) printf("%c", (char) i); else if (i == HAT_CHAR) printf("^"); else if (i == DOLLAR_CHAR) printf("$"); } if (numchars > 1) printf("]"); break; default: fprintf(stderr, "Unknown type"); } if (rx->right) { if (rx->right->type != CHARSET && (show_nodes || (!(rx->type == CAT && rx->right->type == CAT) && rx->right->right))) printf("("); _regex_print(rx->right, depth + 1, show_nodes); if (rx->right->type != CHARSET && (show_nodes || (!(rx->type == CAT && rx->right->type == CAT) && rx->right->right))) printf(")"); } if (!depth) printf("\n"); } #endif /* DEBUG */ struct parse_sp { /* scratch pad for the parsing process */ struct dm_pool *mem; int type; /* token type, 0 indicates a charset */ dm_bitset_t charset; /* The current charset */ const char *cursor; /* where we are in the regex */ const char *rx_end; /* 1pte for the expression being parsed */ }; static struct rx_node *_or_term(struct parse_sp *ps); static void _single_char(struct parse_sp *ps, unsigned int c, const char *ptr) { ps->type = 0; ps->cursor = ptr + 1; dm_bit_clear_all(ps->charset); dm_bit_set(ps->charset, c); } /* * Get the next token from the regular expression. * Returns: 1 success, 0 end of input, -1 error. */ static int _rx_get_token(struct parse_sp *ps) { int neg = 0, range = 0; char c, lc = 0; const char *ptr = ps->cursor; if (ptr == ps->rx_end) { /* end of input ? */ ps->type = -1; return 0; } switch (*ptr) { /* charsets and ncharsets */ case '[': ptr++; if (*ptr == '^') { dm_bit_set_all(ps->charset); /* never transition on zero */ dm_bit_clear(ps->charset, 0); neg = 1; ptr++; } else dm_bit_clear_all(ps->charset); while ((ptr < ps->rx_end) && (*ptr != ']')) { if (*ptr == '\\') { /* an escaped character */ ptr++; switch (*ptr) { case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; default: c = *ptr; } } else if (*ptr == '-' && lc) { /* we've got a range on our hands */ range = 1; ptr++; if (ptr == ps->rx_end) { log_error("Incomplete range" "specification"); return -1; } c = *ptr; } else c = *ptr; if (range) { /* add lc - c into the bitset */ if (lc > c) { char tmp = c; c = lc; lc = tmp; } for (; lc <= c; lc++) { if (neg) dm_bit_clear(ps->charset, lc); else dm_bit_set(ps->charset, lc); } range = 0; } else { /* add c into the bitset */ if (neg) dm_bit_clear(ps->charset, c); else dm_bit_set(ps->charset, c); } ptr++; lc = c; } if (ptr >= ps->rx_end) { ps->type = -1; return -1; } ps->type = 0; ps->cursor = ptr + 1; break; /* These characters are special, we just return their ASCII codes as the type. Sorted into ascending order to help the compiler */ case '(': case ')': case '*': case '+': case '?': case '|': ps->type = (int) *ptr; ps->cursor = ptr + 1; break; case '^': _single_char(ps, HAT_CHAR, ptr); break; case '$': _single_char(ps, DOLLAR_CHAR, ptr); break; case '.': /* The 'all but newline' character set */ ps->type = 0; ps->cursor = ptr + 1; dm_bit_set_all(ps->charset); dm_bit_clear(ps->charset, (int) '\n'); dm_bit_clear(ps->charset, (int) '\r'); dm_bit_clear(ps->charset, 0); break; case '\\': /* escaped character */ ptr++; if (ptr >= ps->rx_end) { log_error("Badly quoted character at end " "of expression"); ps->type = -1; return -1; } ps->type = 0; ps->cursor = ptr + 1; dm_bit_clear_all(ps->charset); switch (*ptr) { case 'n': dm_bit_set(ps->charset, (int) '\n'); break; case 'r': dm_bit_set(ps->charset, (int) '\r'); break; case 't': dm_bit_set(ps->charset, (int) '\t'); break; default: dm_bit_set(ps->charset, (int) *ptr); } break; default: /* add a single character to the bitset */ ps->type = 0; ps->cursor = ptr + 1; dm_bit_clear_all(ps->charset); dm_bit_set(ps->charset, (int) (unsigned char) *ptr); break; } return 1; } static struct rx_node *_node(struct dm_pool *mem, int type, struct rx_node *l, struct rx_node *r) { struct rx_node *n = dm_pool_zalloc(mem, sizeof(*n)); if (n) { if (type == CHARSET && !(n->charset = dm_bitset_create(mem, 256))) { dm_pool_free(mem, n); return NULL; } n->type = type; n->left = l; n->right = r; } return n; } static struct rx_node *_term(struct parse_sp *ps) { struct rx_node *n; switch (ps->type) { case 0: if (!(n = _node(ps->mem, CHARSET, NULL, NULL))) { stack; return NULL; } dm_bit_copy(n->charset, ps->charset); _rx_get_token(ps); /* match charset */ break; case '(': _rx_get_token(ps); /* match '(' */ n = _or_term(ps); if (ps->type != ')') { log_error("missing ')' in regular expression"); return 0; } _rx_get_token(ps); /* match ')' */ break; default: n = 0; } return n; } static struct rx_node *_closure_term(struct parse_sp *ps) { struct rx_node *l, *n; if (!(l = _term(ps))) return NULL; for (;;) { switch (ps->type) { case '*': n = _node(ps->mem, STAR, l, NULL); break; case '+': n = _node(ps->mem, PLUS, l, NULL); break; case '?': n = _node(ps->mem, QUEST, l, NULL); break; default: return l; } if (!n) { stack; return NULL; } _rx_get_token(ps); l = n; } return n; } static struct rx_node *_cat_term(struct parse_sp *ps) { struct rx_node *l, *r, *n; if (!(l = _closure_term(ps))) return NULL; if (ps->type == '|') return l; if (!(r = _cat_term(ps))) return l; if (!(n = _node(ps->mem, CAT, l, r))) stack; return n; } static struct rx_node *_or_term(struct parse_sp *ps) { struct rx_node *l, *r, *n; if (!(l = _cat_term(ps))) return NULL; if (ps->type != '|') return l; _rx_get_token(ps); /* match '|' */ if (!(r = _or_term(ps))) { log_error("Badly formed 'or' expression"); return NULL; } if (!(n = _node(ps->mem, OR, l, r))) stack; return n; } /*----------------------------------------------------------------*/ /* Macros for left and right nodes. Inverted if 'leftmost' is set. */ #define LEFT(a) (leftmost ? (a)->left : (a)->right) #define RIGHT(a) (leftmost ? (a)->right : (a)->left) /* * The optimiser spots common prefixes on either side of an 'or' node, and * lifts them outside the 'or' with a 'cat'. */ static unsigned _depth(struct rx_node *r, unsigned leftmost) { int count = 1; while (r->type != CHARSET && LEFT(r) && (leftmost || r->type != OR)) { count++; r = LEFT(r); } return count; } /* * FIXME: a unique key could be built up as part of the parse, to make the * comparison quick. Alternatively we could use cons-hashing, and then * this would simply be a pointer comparison. */ static int _nodes_equal(struct rx_node *l, struct rx_node *r) { if (l->type != r->type) return 0; switch (l->type) { case CAT: case OR: return _nodes_equal(l->left, r->left) && _nodes_equal(l->right, r->right); case STAR: case PLUS: case QUEST: return _nodes_equal(l->left, r->left); case CHARSET: /* * Never change anything containing TARGET_TRANS * used by matcher as boundary marker between concatenated * expressions. */ return (!dm_bit(l->charset, TARGET_TRANS) && dm_bitset_equal(l->charset, r->charset)); } /* NOTREACHED */ return_0; } static int _find_leftmost_common(struct rx_node *or, struct rx_node **l, struct rx_node **r, unsigned leftmost) { struct rx_node *left = or->left, *right = or->right; unsigned left_depth = _depth(left, leftmost); unsigned right_depth = _depth(right, leftmost); while (left_depth > right_depth && left->type != OR) { left = LEFT(left); left_depth--; } while (right_depth > left_depth && right->type != OR) { right = LEFT(right); right_depth--; } if (left_depth != right_depth) return 0; while (left_depth) { if (left->type == CAT && right->type == CAT) { if (_nodes_equal(LEFT(left), LEFT(right))) { *l = left; *r = right; return 1; } } if (left->type == OR || right->type == OR) break; left = LEFT(left); right = LEFT(right); left_depth--; } return 0; } /* If top node is OR, rotate (leftmost example) from ((ab)|((ac)|d)) to (((ab)|(ac))|d) */ static int _rotate_ors(struct rx_node *r, unsigned leftmost) { struct rx_node *old_node; if (r->type != OR || RIGHT(r)->type != OR) return 0; old_node = RIGHT(r); if (leftmost) { r->right = RIGHT(old_node); old_node->right = LEFT(old_node); old_node->left = LEFT(r); r->left = old_node; } else { r->left = RIGHT(old_node); old_node->left = LEFT(old_node); old_node->right = LEFT(r); r->right = old_node; } return 1; } static struct rx_node *_exchange_nodes(struct dm_pool *mem, struct rx_node *r, struct rx_node *left_cat, struct rx_node *right_cat, unsigned leftmost) { struct rx_node *new_r; if (leftmost) new_r = _node(mem, CAT, LEFT(left_cat), r); else new_r = _node(mem, CAT, r, LEFT(right_cat)); if (!new_r) return_NULL; memcpy(left_cat, RIGHT(left_cat), sizeof(*left_cat)); memcpy(right_cat, RIGHT(right_cat), sizeof(*right_cat)); return new_r; } static struct rx_node *_pass(struct dm_pool *mem, struct rx_node *r, int *changed) { struct rx_node *left, *right; /* * walk the tree, optimising every 'or' node. */ switch (r->type) { case CAT: if (!(r->left = _pass(mem, r->left, changed))) return_NULL; if (!(r->right = _pass(mem, r->right, changed))) return_NULL; break; case STAR: case PLUS: case QUEST: if (!(r->left = _pass(mem, r->left, changed))) return_NULL; break; case OR: /* It's important we optimise sub nodes first */ if (!(r->left = _pass(mem, r->left, changed))) return_NULL; if (!(r->right = _pass(mem, r->right, changed))) return_NULL; /* * If rotate_ors changes the tree, left and right are stale, * so just set 'changed' to repeat the search. * * FIXME Check we can't 'bounce' between left and right rotations here. */ if (_find_leftmost_common(r, &left, &right, 1)) { if (!_rotate_ors(r, 1)) r = _exchange_nodes(mem, r, left, right, 1); *changed = 1; } else if (_find_leftmost_common(r, &left, &right, 0)) { if (!_rotate_ors(r, 0)) r = _exchange_nodes(mem, r, left, right, 0); *changed = 1; } break; case CHARSET: break; } return r; } static struct rx_node *_optimise(struct dm_pool *mem, struct rx_node *r) { /* * We're looking for (or (... (cat a)) (... (cat b))) * and want to turn it into (cat (or (... a) (... b))) * * (fa)|(fb) becomes f(a|b) */ /* * Initially done as an inefficient multipass algorithm. */ int changed; do { changed = 0; r = _pass(mem, r, &changed); } while (r && changed); return r; } /*----------------------------------------------------------------*/ struct rx_node *rx_parse_tok(struct dm_pool *mem, const char *begin, const char *end) { struct rx_node *r; struct parse_sp *ps = dm_pool_zalloc(mem, sizeof(*ps)); if (!ps) return_NULL; ps->mem = mem; if (!(ps->charset = dm_bitset_create(mem, 256))) { log_error("Regex charset allocation failed"); dm_pool_free(mem, ps); return NULL; } ps->cursor = begin; ps->rx_end = end; _rx_get_token(ps); /* load the first token */ if (!(r = _or_term(ps))) { log_error("Parse error in regex"); dm_pool_free(mem, ps); return NULL; } if (!(r = _optimise(mem, r))) { log_error("Regex optimisation error"); dm_pool_free(mem, ps); return NULL; } return r; } struct rx_node *rx_parse_str(struct dm_pool *mem, const char *str) { return rx_parse_tok(mem, str, str + strlen(str)); }