blob: a90456c4f22d76faa033f8f53333087ffa7a0b10 [file] [log] [blame]
/*
** Copyright 2003-2004 The Apache Software Foundation
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#include "apr_general.h"
#include "apr_pools.h"
#include "apreq_tables.h"
#include "apr_strings.h"
#include "apr_lib.h"
#if APR_HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if APR_HAVE_STRING_H
#include <string.h>
#endif
#if APR_HAVE_STRINGS_H
#include <strings.h>
#endif
#include "apr_signal.h"
/********************* table_entry structure ********************/
#define CS_OFFSET (8 * (sizeof(unsigned int) - 1))
/* private struct */
typedef struct apreq_table_entry_t {
const apreq_value_t *val;
const char *key; /* = val->name : saves a ptr deref */
unsigned int checksum;
enum { RED, BLACK } color;
int tree[4]; /* LEFT RIGHT UP NEXT */
} apreq_table_entry_t;
/* LEFT & RIGHT must satisfy !LEFT==RIGHT , !RIGHT==LEFT */
#define LEFT 0
#define RIGHT 1
#define UP 2
#define NEXT 3
/********************* table structure ********************/
#if APR_CHARSET_EBCDIC
#define CASE_MASK 0xbfbfbfbf
#else
#define CASE_MASK 0xdfdfdfdf
#endif
#define TABLE_HASH_SIZE 16
#define TABLE_INDEX_MASK 0x0f
#define TABLE_HASH(c) (TABLE_INDEX_MASK & (unsigned char)(c))
#define COMPUTE_KEY_CHECKSUM(k, checksum) \
{ \
unsigned int c = (unsigned int)*(k); \
int j = 0; \
(checksum) = c; \
while (++j < sizeof(c)) { \
(checksum) <<= 8; \
if (c) { \
c = (unsigned int)*++(k); \
checksum |= c; \
} \
} \
if (c) ++(k); \
checksum &= CASE_MASK; \
}
/* XXX: apreq_tables need to be signal( thread? )safe; currently
these LOCK* macros might indicate some unsafe locations in the code. */
#define LOCK_TABLE(t)
#define UNLOCK_TABLE(t)
#define TABLE_IS_LOCKED(t)
#define TF_BALANCE 1
#define TF_LOCK 2
struct apreq_table_t {
apr_array_header_t a;
int ghosts;
apreq_value_copy_t *copy; /* XXX: this may go away soon */
apreq_value_merge_t *merge;
int root[TABLE_HASH_SIZE];
};
/***************************** tree macros ****************************/
#define DEAD(idx) ( (idx)[o].key == NULL )
#define KILL(t,idx) do { (idx)[o].key = NULL; \
(t)->ghosts++; } while (0)
#define RESURRECT(t,idx) do { (idx)[o].key = (idx)[o].val->name; \
COMPUTE_KEY_CHECKSUM((idx)[o].key,(idx)[o].checksum); \
(t)->ghosts--; } while (0)
/* NEVER KILL AN ENTRY THAT'S STILL WITHIN THE FOREST */
#define IN_FOREST(t,idx) ( !DEAD(idx) && ( (idx)[o].tree[UP] >= 0 || \
(idx) == t->root[TABLE_HASH((idx)[o].checksum>>CS_OFFSET)] ) )
/* MUST ensure n's parent exists (>=0) before using LR(n) */
#define LR(n) ( ( (n)[o].tree[UP][o].tree[LEFT] == (n) ) ? LEFT : RIGHT )
#define PROMOTE(r,p) do rotate(o,r,o[p].tree[UP],!LR(p)); \
while (o[p].tree[UP] >= 0)
/********************* (internal) tree operations ********************
*
* good general references for binary tree algorithms:
*
* Ben Pfaff's book on libavl 2.0:
* http://www.msu.edu/user/pfaffben/avl/
*
*/
/**
* pivot child
* / \ / \
* child direction == rotate ===> 1 pivot
* / \ / \
* 1 2 2 direction
*
*/
APR_INLINE
static void rotate(apreq_table_entry_t *const o, int *root,
const int pivot, const int direction)
{
const int child = pivot[o].tree[!direction];
const int parent = pivot[o].tree[UP];
pivot[o].tree[!direction] = child[o].tree[direction];
if (pivot[o].tree[!direction] >= 0)
pivot[o].tree[!direction][o].tree[UP] = pivot;
if (parent >= 0)
parent[o].tree[LR(pivot)] = child;
else
*root = child;
child[o].tree[UP] = parent;
child[o].tree[direction] = pivot;
pivot[o].tree[UP] = child;
}
/*
* puts location of "nearest" index in *elt
* returns 0 if key is found, otherwise the return value's
* sign represents the direction (LEFT <0, RIGHT >0) to move
* from elt. This is where the key "should" be located
* were it added to the tree.
*
*/
APR_INLINE
static int search(const apreq_table_entry_t *const o,int *elt,
const char *key)
{
int idx = *elt;
unsigned int csum;
COMPUTE_KEY_CHECKSUM(key, csum);
while (1) {
const int direction = (csum > idx[o].checksum) ? 1:
(csum < idx[o].checksum) ? -1 :
strcasecmp(key,idx[o].key);
if (direction < 0 && idx[o].tree[LEFT] >= 0)
idx = idx[o].tree[LEFT];
else if (direction > 0 && idx[o].tree[RIGHT] >= 0)
idx = idx[o].tree[RIGHT];
else {
*elt = idx;
return direction;
}
}
return 0;
}
/* returns the index of the first similarly-named elt,
* and -1 otherwise (the elt is new).
*/
static int insert(apreq_table_entry_t *const o, int *root, int x,
apreq_table_entry_t *elt)
{
const int idx = elt - o;
int s = 0;
#define REBALANCE do { \
int parent = x[o].tree[UP]; \
x[o].color = RED; \
if (parent >= 0 && parent[o].color == RED) { \
int parent_direction = LR(parent); \
int grandparent = parent[o].tree[UP]; \
if (parent_direction != LR(x)) { \
rotate(o, root, parent, parent_direction); \
parent = x; \
} \
parent[o].color = BLACK; \
grandparent[o].color = RED; \
rotate(o, root, grandparent, !parent_direction);\
} (*root)[o].color = BLACK; } while (0)
if (x >= 0) {
/*
* M.A. Weiss' single-pass insertion algorithm for red-black trees
* corrects potential red-red violations prior to insertion:
* http://www.cs.uiowa.edu/~hzhang/c44/lec14.PDF
*/
const unsigned int csum = elt->checksum;
const char *const key = elt->key;
while (1) {
int left = x[o].tree[LEFT];
int right = x[o].tree[RIGHT];
s = (csum > x[o].checksum) ? 1 :
(csum < x[o].checksum) ? -1 :
strcasecmp(key,x[o].key);
if (s < 0 && left >= 0) {
if (left[o].color == RED && right >= 0
&& right[o].color == RED)
{
left[o].color = right[o].color = BLACK;
REBALANCE;
}
x = left;
}
else if (s > 0 && right >= 0) {
if (right[o].color == RED && left >= 0
&& left[o].color == RED)
{
left[o].color = right[o].color = BLACK;
REBALANCE;
}
x = right;
}
else
break;
}
}
if (s == 0) { /* found */
int parent = x;
if (parent < 0) { /* empty tree */
*root = idx;
elt->tree[LEFT] = -1;
elt->tree[RIGHT] = -1;
elt->tree[UP] = -1;
elt->color = BLACK;
return -1;
}
elt->color = parent[o].color;
while (parent[o].tree[NEXT] >= 0)
parent = parent[o].tree[NEXT];
parent[o].tree[NEXT] = idx;
elt->tree[UP] = -1;
elt->tree[RIGHT] = -1;
elt->tree[LEFT] = -1;
return x;
}
/* The element wasn't in the tree, so add it */
x[o].tree[s < 0 ? LEFT : RIGHT] = idx;
elt->tree[UP] = x;
elt->tree[RIGHT] = -1;
elt->tree[LEFT] = -1;
x = idx;
REBALANCE;
#undef REBALANCE
return -1;
}
static void delete(apreq_table_entry_t *const o, int *root, const int idx)
{
int x;
int parent = idx[o].tree[UP];
if (idx[o].tree[LEFT] < 0 || idx[o].tree[RIGHT] < 0) {
x = 1 + idx[o].tree[RIGHT] + idx[o].tree[LEFT];
/* remove idx & promote x */
if (parent >= 0)
parent[o].tree[LR(idx)] = x;
else
*root = x;
if (x >= 0)
x[o].tree[UP] = parent;
else
x = parent;
if (idx[o].color == RED)
return;
}
else {
/* find idx's in-order successor & replace idx with it */
int y = idx[o].tree[RIGHT];
while (y[o].tree[LEFT] >= 0)
y = y[o].tree[LEFT];
x = y[o].tree[RIGHT];
if (y[o].tree[UP] != idx) {
y[o].tree[RIGHT] = idx[o].tree[RIGHT];
if (x >= 0) {
/* replace y with x */
x[o].tree[UP] = y[o].tree[UP];
y[o].tree[UP][o].tree[LR(y)] = x;
}
else
x = y[o].tree[UP];
}
else
if (x < 0)
x = parent;
/* copy idx's tree data into y ("RIGHT" is already done). */
y[o].tree[LEFT] = idx[o].tree[LEFT];
y[o].tree[UP] = idx[o].tree[UP];
/* replace idx with y */
if (parent >= 0)
parent[o].tree[LR(idx)] = y;
else
*root = y;
if (y[o].color == RED)
x = *root;
y[o].color = idx[o].color;
}
/* Rebalance tree to deal with violations of
* black node count via standard double-black promotion.
* online ref:
* Bruce Schneier's 1992 DDJ article:
* http://www.ddj.com/documents/s=1049/ddj9204c/9204c.htm
*
*/
while (x != *root && x[o].color == BLACK) {
/* x has a parent & sibling */
int parent = x[o].tree[UP];
const int direction = LR(x);
int sibling = parent[o].tree[!direction];
if (sibling[o].color == RED) {
sibling[o].color = BLACK;
parent[o].color = RED;
rotate(o, root, parent, direction); /* promote sibling */
sibling = parent[o].tree[!direction];
}
if (sibling[o].tree[direction][o].color == BLACK &&
sibling[o].tree[!direction][o].color == BLACK)
{
sibling[o].color = RED;
x = parent;
}
else {
if (sibling[o].tree[!direction][o].color == BLACK) {
sibling[o].tree[direction][o].color = BLACK;
sibling[o].color = RED;
rotate(o, root, sibling, !direction); /* demote sibling */
sibling = parent[o].tree[!direction];
}
sibling[o].color = parent[o].color;
parent[o].color = BLACK;
sibling[o].tree[!direction][o].color = BLACK;
rotate(o, root, parent, direction); /* promote sibling */
x = *root;
}
}
x[o].color = BLACK;
}
/*****************************************************************
*
* The "apreq_table" API.
*/
#define make_array_core(a,p,n,s,c) do { \
if (c) \
(a)->elts = apr_pcalloc(p, (n) * (s)); \
else \
(a)->elts = apr_palloc( p, (n) * (s)); \
(a)->pool = p; \
(a)->elt_size = s; \
(a)->nelts = 0; \
(a)->nalloc = n; \
} while (0)
#define v2c(ptr) ( (ptr) ? (ptr)->data : NULL )
static void *apr_array_push_noclear(apr_array_header_t *arr)
{
if (arr->nelts == arr->nalloc) {
int new_size = (arr->nalloc <= 0) ? 1 : arr->nalloc * 2;
char *new_data;
new_data = apr_palloc(arr->pool, arr->elt_size * new_size);
memcpy(new_data, arr->elts, arr->nalloc * arr->elt_size);
arr->elts = new_data;
arr->nalloc = new_size;
}
++arr->nelts;
return arr->elts + (arr->elt_size * (arr->nelts - 1));
}
#define table_push(t) ((apreq_table_entry_t *)apr_array_push_noclear(&(t)->a))
/********************* table ctors ********************/
APREQ_DECLARE(apreq_table_t *) apreq_table_make(apr_pool_t *p, int nelts)
{
apreq_table_t *t = apr_palloc(p, sizeof *t);
make_array_core(&t->a, p, nelts, sizeof(apreq_table_entry_t), 0);
/* XXX: is memset(*,-1,*) portable ??? */
memset(t->root, -1, TABLE_HASH_SIZE * sizeof(int));
t->merge = apreq_merge_values;
t->copy = apreq_copy_value;
t->ghosts = 0;
return t;
}
APREQ_DECLARE(apreq_table_t *) apreq_table_copy(apr_pool_t *p,
const apreq_table_t *t)
{
apreq_table_t *new = apr_palloc(p, sizeof *new);
make_array_core(&new->a, p, t->a.nalloc, sizeof(apreq_table_entry_t), 0);
memcpy(new->a.elts, t->a.elts, t->a.nelts * sizeof(apreq_table_entry_t));
new->a.nelts = t->a.nelts;
new->ghosts = t->ghosts;
memcpy(new->root, t->root, TABLE_HASH_SIZE * sizeof(int));
new->merge = t->merge;
new->copy = t->copy;
return new;
}
APREQ_DECLARE(apr_table_t *)apreq_table_export(apr_pool_t *p,
const apreq_table_t *t)
{
int idx;
apreq_table_entry_t *const o = (apreq_table_entry_t *)t->a.elts;
apr_table_t *s = apr_table_make(p, t->a.nalloc);
for (idx = 0; idx < t->a.nelts; ++idx) {
if (! DEAD(idx)) {
const apreq_value_t *v = idx[o].val;
apr_table_add(s, v->name, v->data);
}
}
return s;
}
APREQ_DECLARE(apreq_table_t *)apreq_table_import(apr_pool_t *p,
apr_table_t *s)
{
apreq_table_t *t = apreq_table_make(p, APREQ_NELTS);
const apr_array_header_t *a = apr_table_elts(s);
const apr_table_entry_t *e = (const apr_table_entry_t *)a->elts;
const apr_table_entry_t *end = e + a->nelts;
for ( ; e < end; ++e) {
apreq_value_t *v = apreq_make_value(p, e->key, strlen(e->key),
e->val, e->val ?
strlen(e->val) :
0);
apreq_table_addv(t, v);
}
return t;
}
/********************* unary table ops ********************/
APR_INLINE
APREQ_DECLARE(void) apreq_table_clear(apreq_table_t *t)
{
memset(t->root,-1,TABLE_HASH_SIZE * sizeof(int));
t->a.nelts = 0;
t->ghosts = 0;
}
APR_INLINE
APREQ_DECLARE(int) apreq_table_nelts(const apreq_table_t *t)
{
return t->a.nelts - t->ghosts;
}
APR_INLINE
APREQ_DECLARE(int) apreq_table_ghosts(apreq_table_t *t)
{
return t->ghosts;
}
APR_INLINE
APREQ_DECLARE(apr_pool_t *) apreq_table_pool(apreq_table_t *t)
{
return t->a.pool;
}
APR_INLINE
APREQ_DECLARE(apreq_value_copy_t *)apreq_table_copier(apreq_table_t *t,
apreq_value_copy_t *c)
{
apreq_value_copy_t *original = t->copy;
if (c != NULL)
t->copy = c;
return original;
}
APR_INLINE
APREQ_DECLARE(apreq_value_merge_t *)apreq_table_merger(apreq_table_t *t,
apreq_value_merge_t *m)
{
apreq_value_merge_t *original = t->merge;
if (m != NULL)
t->merge = m;
return original;
}
APREQ_DECLARE(apr_status_t) apreq_table_normalize(apreq_table_t *t)
{
apr_status_t status = APR_SUCCESS;
int idx;
apreq_table_entry_t *const o = (apreq_table_entry_t *)t->a.elts;
apreq_value_t *a[APREQ_NELTS];
apr_array_header_t arr = { t->a.pool, sizeof(apreq_value_t *), 0,
APREQ_NELTS, (char *)a };
if (t->merge == NULL)
return APR_ENOTIMPL;
for (idx = 0; idx < t->a.nelts; ++idx) {
int j = idx[o].tree[NEXT];
if ( j >= 0 && IN_FOREST(t,idx) ) {
apreq_value_t *v;
arr.nelts = 0;
*(const apreq_value_t **)apr_array_push(&arr) = idx[o].val;
for ( ; j >= 0; j = j[o].tree[NEXT]) {
KILL(t,j);
*(const apreq_value_t **)apr_array_push(&arr) = j[o].val;
}
v = t->merge(t->a.pool, &arr);
if (v == NULL) {
status = APR_EINCOMPLETE;
for (j = idx[o].tree[NEXT] ; j >= 0; j = j[o].tree[NEXT])
RESURRECT(t,j);
}
else {
idx[o].val = v;
idx[o].tree[NEXT] = -1;
}
}
}
while (DEAD(t->a.nelts-1)) {
t->ghosts--;
t->a.nelts--;
}
return status;
}
static int bury_table_entries(apreq_table_t *t, apr_array_header_t *q)
{
apreq_table_entry_t *const o = (apreq_table_entry_t *)t->a.elts;
int j, m, rv=0, *p = (int *)q->elts;
register int n;
/* add sentinel */
*(int *)apr_array_push(q) = t->a.nelts;
q->nelts--;
/* remove entries O(t->nelts)! */
for ( j=1, n=0; n < q->nelts; ++j, ++n )
for (m = p[n]+1; m < p[n+1]; ++m)
m[o-j] = m[o];
t->a.nelts -= q->nelts;
/* reindex trees */
/* EXPENSIVE: ~O(4 * t->a.nelts * q->nelts) */
#define REINDEX(P) for (n=0; P > p[n]; ++n) ; P -= n; rv += n
for (j=0; j < TABLE_HASH_SIZE; ++j) {
REINDEX( t->root[j] );
}
for ( j=0; j < t->a.nelts; ++j ) {
REINDEX( j[o].tree[LEFT] );
REINDEX( j[o].tree[RIGHT] );
REINDEX( j[o].tree[UP] );
REINDEX( j[o].tree[NEXT] );
}
#undef REINDEX
return rv;
}
APREQ_DECLARE(int) apreq_table_exorcise(apreq_table_t *t)
{
apreq_table_entry_t *const o = (apreq_table_entry_t *)t->a.elts;
int idx;
int a[APREQ_NELTS];
apr_array_header_t arr = { t->a.pool, sizeof(int), 0,
APREQ_NELTS, (char *)a };
if (t->ghosts == 0)
return 0;
for (idx = 0; idx < t->a.nelts; ++idx)
if (DEAD(idx)) {
*(int *)apr_array_push(&arr) = idx;
}
t->ghosts = 0;
return bury_table_entries(t,&arr);
}
/********************* accessors ********************/
APREQ_DECLARE(const char *) apreq_table_get(const apreq_table_t *t,
const char *key)
{
int idx = t->root[TABLE_HASH(*key)];
const apreq_table_entry_t *const o = (apreq_table_entry_t *)t->a.elts;
if ( idx < 0 || search(o,&idx,key) )
return NULL;
return idx[o].val->data;
}
APREQ_DECLARE(apr_status_t) apreq_table_keys(const apreq_table_t *t,
apr_array_header_t *keys)
{
int idx;
apreq_table_entry_t *const o = (apreq_table_entry_t *)t->a.elts;
if (t->a.nelts == t->ghosts)
return APR_NOTFOUND;
for (idx = 0; idx < t->a.nelts; ++idx)
if (IN_FOREST(t,idx))
*(const char **)apr_array_push(keys) = idx[o].val->name;
return APR_SUCCESS;
}
APREQ_DECLARE(void) apreq_table_set(apreq_table_t *t,
const apreq_value_t *val)
{
const char *key = val->name;
int idx;
apreq_table_entry_t *e = table_push(t);
apreq_table_entry_t *const o = (apreq_table_entry_t *)t->a.elts;
e->key = key;
e->val = val;
e->tree[NEXT] = -1;
COMPUTE_KEY_CHECKSUM(e->key,e->checksum);
idx = insert(o,&t->root[TABLE_HASH(*key)],
t->root[TABLE_HASH(*key)] ,e);
if (idx >= 0) {
int n;
idx[o].val = val;
for ( n=idx[o].tree[NEXT]; n>=0; n=n[o].tree[NEXT] )
KILL(t,n);
while (DEAD(t->a.nelts-1)) {
t->ghosts--;
t->a.nelts--;
}
idx[o].tree[NEXT] = -1;
}
}
APREQ_DECLARE(void) apreq_table_unset(apreq_table_t *t, const char *key)
{
int idx = t->root[TABLE_HASH(*key)];
apreq_table_entry_t *const o = (apreq_table_entry_t *)t->a.elts;
if (idx >= 0 && search(o,&idx,key) == 0) {
int n;
delete(o,&t->root[TABLE_HASH(*key)],idx);
for ( n=idx; n>=0; n=n[o].tree[NEXT] )
KILL(t,n);
}
}
APREQ_DECLARE(apr_status_t) apreq_table_merge(apreq_table_t *t,
const apreq_value_t *val)
{
const char *key = val->name;
int idx = t->root[TABLE_HASH(*key)];
apreq_table_entry_t *e = table_push(t);
apreq_table_entry_t *const o = (apreq_table_entry_t *)t->a.elts;
e->key = key;
e->val = val;
e->tree[NEXT] = -1;
COMPUTE_KEY_CHECKSUM(key,e->checksum);
idx = insert(o,&t->root[TABLE_HASH(*key)],idx,e);
if (idx >= 0) {
int n;
apreq_value_t *a[APREQ_NELTS];
apr_array_header_t arr = { t->a.pool, sizeof(apreq_value_t *), 0,
APREQ_NELTS, (char *)a };
*(const apreq_value_t **)apr_array_push(&arr) = idx[o].val;
for (n = idx[o].tree[NEXT]; n >= 0; n = n[o].tree[NEXT]) {
*(const apreq_value_t **)apr_array_push(&arr) = n[o].val;
KILL(t,n);
}
*(const apreq_value_t **)apr_array_push(&arr) = val;
val = t->merge(t->a.pool, &arr);
if (val == NULL) {
for (n = idx[o].tree[NEXT]; n >= 0; n = n[o].tree[NEXT])
RESURRECT(t,n);
return APR_ENOTIMPL;
}
idx[o].val = val;
while (DEAD(t->a.nelts-1)) {
t->ghosts--;
t->a.nelts--;
}
idx[o].tree[NEXT] = -1;
}
return val->status;
}
APREQ_DECLARE(apr_status_t) apreq_table_addv(apreq_table_t *t,
const apreq_value_t *val)
{
const char *key = val->name;
apreq_table_entry_t *elt = table_push(t);
int *root = &t->root[TABLE_HASH(*key)];
apreq_table_entry_t *const o = (apreq_table_entry_t *)t->a.elts;
elt->key = key;
elt->val = val;
elt->tree[NEXT] = -1;
COMPUTE_KEY_CHECKSUM(elt->key,elt->checksum);
insert(o, root, *root, elt);
return APR_SUCCESS;
}
/********************* binary operations ********************/
APREQ_DECLARE(void) apreq_table_cat(apreq_table_t *t,
const apreq_table_t *s)
{
const int n = t->a.nelts;
int idx;
apreq_table_entry_t *o;
if (t->ghosts == n) {
t->a.nelts = 0;
t->ghosts = s->ghosts;
apr_array_cat(&t->a,&s->a);
memcpy(t->root, s->root, TABLE_HASH_SIZE * sizeof(int));
return;
}
apr_array_cat(&t->a, &s->a);
o = (apreq_table_entry_t *)t->a.elts;
t->ghosts += s->ghosts;
for (idx = n; idx < t->a.nelts; ++idx) {
const unsigned char hash = TABLE_HASH(idx[o].checksum>>CS_OFFSET);
if (DEAD(idx))
continue;
if (idx[o].tree[NEXT] >= 0)
idx[o].tree[NEXT] += n;
if (t->root[hash] < 0) {
if (idx[o].tree[LEFT] >= 0)
idx[o].tree[LEFT] += n;
if (idx[o].tree[RIGHT] >= 0)
idx[o].tree[RIGHT] += n;
if (idx[o].tree[UP] >= 0)
idx[o].tree[UP] += n;
t->root[hash] = idx;
}
else if ( idx[o].tree[UP] >= 0 || s->root[hash] == idx-n )
insert(o, &t->root[hash], t->root[hash], idx+o);
}
}
APREQ_DECLARE(apreq_table_t *) apreq_table_overlay(apr_pool_t *p,
const apreq_table_t *overlay,
const apreq_table_t *base)
{
apreq_table_t *t = apr_palloc(p, sizeof *t);
t->a.pool = p;
t->a.elt_size = sizeof(apreq_table_entry_t);
t->copy = overlay->copy;
t->merge = overlay->merge;
t->ghosts = overlay->ghosts;
memcpy(t->root, overlay->root, TABLE_HASH_SIZE * sizeof(int));
t->a.elts = apr_palloc(p, t->a.elt_size *
(overlay->a.nalloc + base->a.nalloc));
memcpy(t->a.elts, overlay->a.elts, overlay->a.elt_size *
overlay->a.nelts);
t->a.nelts = overlay->a.nelts;
t->a.nalloc = overlay->a.nalloc + base->a.nalloc;
if (base->a.nelts) {
if (t->a.nelts)
apreq_table_cat(t, base);
else
memcpy(t->root, base->root, TABLE_HASH_SIZE * sizeof(int));
}
return t;
}
static apreq_value_t *collapse(apr_pool_t *p,
const apr_array_header_t *arr)
{
apreq_value_t **a = (apreq_value_t **)arr->elts;
return (arr->nelts == 0) ? NULL : a[arr->nelts - 1];
}
APREQ_DECLARE(apr_status_t) apreq_table_overlap(apreq_table_t *a,
const apreq_table_t *b,
const unsigned flags)
{
const int m = a->a.nelts;
const int n = b->a.nelts;
apr_pool_t *p = b->a.pool;
apr_status_t s;
if (m + n == 0)
return APR_SUCCESS;
/* copy (extend) a using b's pool */
if (a->a.pool != p) {
make_array_core(&a->a, p, m+n, sizeof(apreq_table_entry_t), 0);
}
apreq_table_cat(a, b);
if (flags == APR_OVERLAP_TABLES_SET) {
apreq_value_merge_t *f = a->merge;
a->merge = collapse;
s = apreq_table_normalize(a);
a->merge = f;
}
else
s = apreq_table_normalize(a);
return s;
}
/********************* iterators ********************/
APR_INLINE
APREQ_DECLARE(const apr_array_header_t *)apreq_table_elts(apreq_table_t *t)
{
if (t->ghosts)
apreq_table_exorcise(t);
return (const apr_array_header_t *)t;
}
/* And now for something completely abstract ...
* For each key value given as a vararg:
* run the function pointed to as
* int comp(void *r, char *key, char *value);
* on each valid key-value pair in the apr_table_t t that matches the vararg key,
* or once for every valid key-value pair if the vararg list is empty,
* until the function returns false (0) or we finish the table.
*
* Note that we restart the traversal for each vararg, which means that
* duplicate varargs will result in multiple executions of the function
* for each matching key. Note also that if the vararg list is empty,
* only one traversal will be made and will cut short if comp returns 0.
*
* Note that the table_get and table_merge functions assume that each key in
* the apr_table_t is unique (i.e., no multiple entries with the same key). This
* function does not make that assumption, since it (unfortunately) isn't
* true for some of Apache's tables.
*
* Note that rec is simply passed-on to the comp function, so that the
* caller can pass additional info for the task.
*
* ADDENDUM for apr_table_vdo():
*
* The caching api will allow a user to walk the header values:
*
* apr_status_t apr_cache_el_header_walk(apr_cache_el *el,
* int (*comp)(void *, const char *, const char *), void *rec, ...);
*
* So it can be ..., however from there I use a callback that use a va_list:
*
* apr_status_t (*cache_el_header_walk)(apr_cache_el *el,
* int (*comp)(void *, const char *, const char *), void *rec, va_list);
*
* To pass those ...'s on down to the actual module that will handle walking
* their headers, in the file case this is actually just an apr_table - and
* rather than reimplementing apr_table_do (which IMHO would be bad) I just
* called it with the va_list. For mod_shmem_cache I don't need it since I
* can't use apr_table's, but mod_file_cache should (though a good hash would
* be better, but that's a different issue :).
*
* So to make mod_file_cache easier to maintain, it's a good thing
*/
APREQ_DECLARE_NONSTD(int) apreq_table_do(apreq_table_do_callback_fn_t *comp,
void *rec, const apreq_table_t *t, ...)
{
int rv;
va_list vp;
va_start(vp, t);
rv = apreq_table_vdo(comp, rec, t, vp);
va_end(vp);
return rv;
}
/* XXX: do the semantics of this routine make any sense? Right now,
* if the caller passed in a non-empty va_list of keys to search for,
* the "early termination" facility only terminates on *that* key; other
* keys will continue to process. Note that this only has any effect
* at all if there are multiple entries in the table with the same key,
* otherwise the called function can never effectively early-terminate
* this function, as the zero return value is effectively ignored.
*
* Note also that this behavior is at odds with the behavior seen if an
* empty va_list is passed in -- in that case, a zero return value terminates
* the entire apr_table_vdo (which is what I think should happen in
* both cases).
*
* If nobody objects soon, I'm going to change the order of the nested
* loops in this function so that any zero return value from the (*comp)
* function will cause a full termination of apr_table_vdo. I'm hesitant
* at the moment because these (funky) semantics have been around for a
* very long time, and although Apache doesn't seem to use them at all,
* some third-party vendor might. I can only think of one possible reason
* the existing semantics would make any sense, and it's very Apache-centric,
* which is this: if (*comp) is looking for matches of a particular
* substring in request headers (let's say it's looking for a particular
* cookie name in the Set-Cookie headers), then maybe it wants to be
* able to stop searching early as soon as it finds that one and move
* on to the next key. That's only an optimization of course, but changing
* the behavior of this function would mean that any code that tried
* to do that would stop working right.
*
* Sigh. --JCW, 06/28/02
*/
APREQ_DECLARE(int) apreq_table_vdo(apr_table_do_callback_fn_t *comp,
void *rec, const apreq_table_t *t,
va_list vp)
{
char *argp;
const apreq_table_entry_t *const o = (apreq_table_entry_t *)t->a.elts;
int vdorv = 1;
argp = va_arg(vp, char *);
do {
int rv = 1, idx;
if (argp) { /* Scan for entries that match the next key */
idx = t->root[TABLE_HASH(*argp)];
if ( idx >= 0 && search(o,&idx,argp) == 0 )
while (idx >= 0) {
rv = (*comp) (rec, idx[o].val->name, idx[o].val->data);
idx = idx[o].tree[NEXT];
}
}
else { /* Scan the entire table */
for (idx = 0; rv && (idx < t->a.nelts); ++idx)
/* if (idx[o].key) */
if (! DEAD(idx) )
rv = (*comp) (rec, idx[o].val->name, idx[o].val->data);
}
if (rv == 0) {
vdorv = 0;
}
} while (argp && ((argp = va_arg(vp, char *)) != NULL));
return vdorv;
}