/* * ns_set support for mod_aolserver. * Copyright 2000, Robert S. Thau. */ #include #include #include #include "nsd.h" /* * This implementation makes an Ns_Set a wrapper around an Apache table. * * The table APIs are case-insensitive so we reimplement the lookup * functions here. * * Some of the sets we want to create are wrappers around existing * Apache tables (e.g. [ns_conn headers] and [ns_conn outputheaders]). * Others are standalone sets. We treat these differently because in the * case of standalone sets we want to try to be efficient about memory * usage, and you can't free storage piecemeal in the Apache memory * management model. * * The reason we want to be efficient is that every database row * returned via one handle is stored in the same set, overlaying the * previous contents of the set. If we're not careful, we'll end up * allocating enough storage to store every database row ever selected * on each handle. * * So in the case of a standalone set, we allocate two subpools for * it. One stores the table struct and the keys; the other stores the * values. Ns_SetTrunc will clear both pools and reallocate the table * struct. Ns_SetClearValues (a new API for mod_aolserver) will just * clear the value pool and set all the table values to the empty * string. */ /* Amount of space for elements in a newly created set */ #define NEW_SET_ELTS 20 /* Possible sentinel values */ #define LIVE_SENTINEL 0x5c4e3a01 /* valid ns_set */ #define DEAD_SENTINEL 0xdeadbeef /* freed ns_set */ /* Ns_Set flag values */ /* Did we allocate our own table, or are we wrapping an existing one? */ #define FLAG_PRIVATE_TABLE 1 /* Should we clear all the set values on the next Ns_SetPutValue? */ #define FLAG_CLEAR_VALUES_ON_PUT_VALUE 2 /*--------------------------------------------------------------------*/ /* When dealing with sets in tcl, we need to encode them in strings * somehow. Aolserver itself has a grubby scheme involving hash * tables. We take the riskier approach of just using the address, * with a prefix that makes valid looking handles unlikely to arise by * accident. Which of course raises the problem of doing integrity * checks --- we require that all valid sets have the LIVE_SENTINEL * above in their headers, and arrange (via Apache cleanups) that that * value is always overwritten when the set is invalidated by any * means. * * Handles are 'set.' followed by the ns_set's address in mock hex. * So we need to know how long the address is... */ #define HANDLE_LEN (4+sizeof(Ns_Set*)*2) void ns_enter_set (Tcl_Interp *interp, Ns_Set *set_ptr) { char handle[HANDLE_LEN+1]; char *cp = handle; char *ptr_p = (char *)&set_ptr; int i; *cp++ = 's'; *cp++ = 'e'; *cp++ = 't'; *cp++ = '.'; for (i = 0; i < sizeof(Ns_Set*); ++i) { char byte = *ptr_p++; *cp++ = '0' + ((byte >> 4) & 0xf); *cp++ = '0' + (byte & 0xf); } handle[HANDLE_LEN] = '\0'; Tcl_AppendResult (interp, handle, NULL); } /* That's the code to create a handle. Here's how you get a handle * out of them... */ Ns_Set *ns_unpack_handle (char *handle) { Ns_Set *addr; char *bytep = handle + 4; char *addrp = (char *)&addr; int i; if (strlen (handle) != HANDLE_LEN || handle[0] != 's' || handle[1] != 'e' || handle[2] != 't' || handle[3] != '.') { return NULL; } for (i = 0; i < sizeof(Ns_Set*); ++i) { char hi_nybble = (*bytep++) - '0'; char lo_nybble = (*bytep++) - '0'; *addrp++ = ((hi_nybble << 4) | lo_nybble); } if (addr->sentinel != LIVE_SENTINEL) { return NULL; } return addr; } /*- support functions ------------------------------------------------*/ static void ns_set_cleanup (void *ptr) { /* This is really just a debugging aid. */ ((Ns_Set *)ptr)->sentinel = DEAD_SENTINEL; } /* Pass a table to be wrapped in an Ns_Set, or NULL for a standalone Ns_Set. */ Ns_Set *ns_set_create_internal(char *name, table *t) { Ns_Set *set; pool *outer_pool; pool *key_pool; pool *value_pool; int flags = 0; if (t != NULL) { outer_pool = ap_table_elts(t)->pool; key_pool = outer_pool; value_pool = key_pool; } else { flags |= FLAG_PRIVATE_TABLE; outer_pool = TCL_POOL(); key_pool = ap_make_sub_pool(outer_pool); value_pool = ap_make_sub_pool(outer_pool); t = ap_make_table(key_pool, NEW_SET_ELTS); } set = ap_pcalloc(outer_pool, sizeof(*set)); set->name = ap_pstrdup(outer_pool, name); set->sentinel = LIVE_SENTINEL; set->flags = flags; set->key_pool = key_pool; set->value_pool = value_pool; set->real_table = t; ap_register_cleanup(outer_pool, (void*)set, ns_set_cleanup, ns_set_cleanup); return set; } /*- (most of the) AOLserver Ns_Set API -------------------------------*/ void Ns_SetUpdate (Ns_Set *set, char *key, char *value) { Ns_SetDeleteKey(set, key); Ns_SetPut(set, key, value); } Ns_Set *Ns_SetCreate (char *name) { return ns_set_create_internal(name, NULL); } void Ns_SetFree (Ns_Set *set) { set->sentinel = DEAD_SENTINEL; if (set->flags & FLAG_PRIVATE_TABLE) { ap_destroy_pool(set->key_pool); } } int Ns_SetPut (Ns_Set *set, char *key, char *value) { array_header *arr = ap_table_elts(set->real_table); table_entry *elts = (table_entry *) ap_push_array (arr); elts->key = ap_pstrdup (set->key_pool, key); elts->val = ap_pstrdup (set->value_pool, value); return arr->nelts - 1; } int Ns_SetUniqueCmp (Ns_Set *set, char *key, int (*cmp) (const char*, const char*)) { int nelem = Ns_SetSize (set); int count = 0; int i; for (i = 0; i < nelem; ++i) { char *skey = Ns_SetKey (set, i); if ((!key && !skey) || (key && skey && !cmp (key, skey))) { if (++count > 1) return 0; } } return 1; } int Ns_SetFindCmp (Ns_Set *set, char *key, int (*cmp) (const char *, const char *)) { table *t = set->real_table; table_entry *elts = (table_entry *)ap_table_elts(t)->elts; int nelts = ap_table_elts(t)->nelts; int i; if (key == NULL) return -1; for (i = 0; i < nelts; ++i) if (!cmp(elts[i].key, key)) return i; return -1; } char *Ns_SetGetCmp (Ns_Set *set, char *key, int (*cmp) (const char *, const char *)) { table *t = set->real_table; table_entry *elts = (table_entry *)ap_table_elts(t)->elts; int i = Ns_SetFindCmp(set, key, cmp); return (i < 0) ? NULL : elts[i].val; } int Ns_SetUnique (Ns_Set *s, char* k) { return Ns_SetUniqueCmp (s, k, strcmp); } int Ns_SetIUnique (Ns_Set *s, char* k) { return Ns_SetUniqueCmp (s, k, strcasecmp); } int Ns_SetFind (Ns_Set *set, char *key) { return Ns_SetFindCmp (set, key, strcmp); } int Ns_SetIFind (Ns_Set *set, char *key) { return Ns_SetFindCmp (set, key, strcasecmp); } char *Ns_SetGet (Ns_Set *set, char *key) { return Ns_SetGetCmp(set, key, strcmp); } char *Ns_SetIGet (Ns_Set *set, char *key) { return Ns_SetGetCmp(set, key, strcasecmp); } void Ns_SetTrunc (Ns_Set *set, int size) { if (size >= Ns_SetSize(set)) { return; } else if (size == 0) { if (set->flags & FLAG_PRIVATE_TABLE) { ap_clear_pool(set->key_pool); ap_clear_pool(set->value_pool); set->real_table = ap_make_table(set->key_pool, NEW_SET_ELTS); } else { ap_clear_table(set->real_table); } } else { ap_table_elts(set->real_table)->nelts = size; } } /* * This function isn't part of the AOLserver API but we need it to * control memory consumption of database selects. */ void Ns_SetClearValues (Ns_Set *set) { int i; for (i = 0; i < Ns_SetSize(set); i++) { ns_set_elts(set)[i].val = ""; } if (set->flags & FLAG_PRIVATE_TABLE) { ap_clear_pool(set->value_pool); } } void Ns_SetClearValuesOnPutValue(Ns_Set *set) { set->flags |= FLAG_CLEAR_VALUES_ON_PUT_VALUE; } void Ns_SetDelete (Ns_Set *set, int index) { table *t = set->real_table; int nelts = ap_table_elts(t)->nelts; if (index < 0 || index >= nelts) { return; } else { table_entry *elts = (table_entry *) ap_table_elts(t)->elts; memmove (&elts[index], &elts[index + 1], (nelts - index - 1)*sizeof(table_entry)); --ap_table_elts(t)->nelts; } } void Ns_SetPutValue (Ns_Set *set, int index, char *value) { if (set->flags & FLAG_CLEAR_VALUES_ON_PUT_VALUE) { Ns_SetClearValues(set); set->flags &= ~FLAG_CLEAR_VALUES_ON_PUT_VALUE; } if (index >= 0 && index < Ns_SetSize (set)) ns_set_elts(set)[index].val = ap_pstrdup (ns_set_table (set)->pool, value); } void Ns_SetDeleteKey (Ns_Set *set, char *key) { Ns_SetDelete(set, Ns_SetFind(set, key)); } void Ns_SetIDeleteKey (Ns_Set *set, char *key) { Ns_SetDelete(set, Ns_SetIFind(set, key)); } void Ns_SetMerge (Ns_Set *high, Ns_Set *low) { int i; int nlow = Ns_SetSize (low); for (i = 0; i < nlow; ++i) if (Ns_SetFind(high, Ns_SetKey(low, i)) < 0) Ns_SetPut(high, Ns_SetKey(low, i), Ns_SetValue(low, i)); } Ns_Set *Ns_SetCopy (Ns_Set *old) { int i; Ns_Set *new; if (old == NULL) return NULL; new = Ns_SetCreate(old->name); for (i = 0; i < Ns_SetSize(old); i++) { Ns_SetPut(new, Ns_SetKey(old, i), Ns_SetValue(old, i)); } return new; } void Ns_SetMove (Ns_Set *to, Ns_Set *from) { int i; for (i = 0; i < Ns_SetSize(from); i++) { Ns_SetPut(to, Ns_SetKey(from, i), Ns_SetValue(from, i)); } Ns_SetTrunc(from, 0); } void Ns_SetPrint (Ns_Set *set) { int i; if (set->name) fprintf (stderr, "%s\n", set->name); else fputs ("\n", stderr); for (i = 0; i < Ns_SetSize(set); ++i) { char *key = Ns_SetKey(set, i)? Ns_SetKey(set, i) : "(null)"; char *val = Ns_SetValue(set, i)? Ns_SetValue(set, i) : "(null)"; fprintf (stderr, "\t%s = %s\n", key, val); } }