freebsd-nq/contrib/apr/memory/unix/apr_pools.c
Peter Wemm 937a200089 Introduce svnlite so that we can check out our source code again.
This is actually a fully functional build except:
* All internal shared libraries are static linked to make sure there
  is no interference with ports (and to reduce build time).
* It does not have the python/perl/etc plugin or API support.
* By default, it installs as "svnlite" rather than "svn".
* If WITH_SVN added in make.conf, you get "svn".
* If WITHOUT_SVNLITE is in make.conf, this is completely disabled.

To be absolutely clear, this is not intended for any use other than
checking out freebsd source and committing, like we once did with cvs.

It should be usable for small scale local repositories that don't
need the python/perl plugin architecture.
2013-06-18 02:53:45 +00:00

2656 lines
71 KiB
C

/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.h"
#include "apr_private.h"
#include "apr_atomic.h"
#include "apr_portable.h" /* for get_os_proc */
#include "apr_strings.h"
#include "apr_general.h"
#include "apr_pools.h"
#include "apr_allocator.h"
#include "apr_lib.h"
#include "apr_thread_mutex.h"
#include "apr_hash.h"
#include "apr_time.h"
#define APR_WANT_MEMFUNC
#include "apr_want.h"
#include "apr_env.h"
#if APR_HAVE_STDLIB_H
#include <stdlib.h> /* for malloc, free and abort */
#endif
#if APR_HAVE_UNISTD_H
#include <unistd.h> /* for getpid and sysconf */
#endif
#if APR_ALLOCATOR_USES_MMAP
#include <sys/mman.h>
#endif
/*
* Magic numbers
*/
/*
* XXX: This is not optimal when using --enable-allocator-uses-mmap on
* XXX: machines with large pagesize, but currently the sink is assumed
* XXX: to be index 0, so MIN_ALLOC must be at least two pages.
*/
#define MIN_ALLOC (2 * BOUNDARY_SIZE)
#define MAX_INDEX 20
#if APR_ALLOCATOR_USES_MMAP && defined(_SC_PAGESIZE)
static unsigned int boundary_index;
static unsigned int boundary_size;
#define BOUNDARY_INDEX boundary_index
#define BOUNDARY_SIZE boundary_size
#else
#define BOUNDARY_INDEX 12
#define BOUNDARY_SIZE (1 << BOUNDARY_INDEX)
#endif
/*
* Timing constants for killing subprocesses
* There is a total 3-second delay between sending a SIGINT
* and sending of the final SIGKILL.
* TIMEOUT_INTERVAL should be set to TIMEOUT_USECS / 64
* for the exponetial timeout alogrithm.
*/
#define TIMEOUT_USECS 3000000
#define TIMEOUT_INTERVAL 46875
/*
* Allocator
*
* @note The max_free_index and current_free_index fields are not really
* indices, but quantities of BOUNDARY_SIZE big memory blocks.
*/
struct apr_allocator_t {
/** largest used index into free[], always < MAX_INDEX */
apr_uint32_t max_index;
/** Total size (in BOUNDARY_SIZE multiples) of unused memory before
* blocks are given back. @see apr_allocator_max_free_set().
* @note Initialized to APR_ALLOCATOR_MAX_FREE_UNLIMITED,
* which means to never give back blocks.
*/
apr_uint32_t max_free_index;
/**
* Memory size (in BOUNDARY_SIZE multiples) that currently must be freed
* before blocks are given back. Range: 0..max_free_index
*/
apr_uint32_t current_free_index;
#if APR_HAS_THREADS
apr_thread_mutex_t *mutex;
#endif /* APR_HAS_THREADS */
apr_pool_t *owner;
/**
* Lists of free nodes. Slot 0 is used for oversized nodes,
* and the slots 1..MAX_INDEX-1 contain nodes of sizes
* (i+1) * BOUNDARY_SIZE. Example for BOUNDARY_INDEX == 12:
* slot 0: nodes larger than 81920
* slot 1: size 8192
* slot 2: size 12288
* ...
* slot 19: size 81920
*/
apr_memnode_t *free[MAX_INDEX];
};
#define SIZEOF_ALLOCATOR_T APR_ALIGN_DEFAULT(sizeof(apr_allocator_t))
/*
* Allocator
*/
APR_DECLARE(apr_status_t) apr_allocator_create(apr_allocator_t **allocator)
{
apr_allocator_t *new_allocator;
*allocator = NULL;
if ((new_allocator = malloc(SIZEOF_ALLOCATOR_T)) == NULL)
return APR_ENOMEM;
memset(new_allocator, 0, SIZEOF_ALLOCATOR_T);
new_allocator->max_free_index = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
*allocator = new_allocator;
return APR_SUCCESS;
}
APR_DECLARE(void) apr_allocator_destroy(apr_allocator_t *allocator)
{
apr_uint32_t index;
apr_memnode_t *node, **ref;
for (index = 0; index < MAX_INDEX; index++) {
ref = &allocator->free[index];
while ((node = *ref) != NULL) {
*ref = node->next;
#if APR_ALLOCATOR_USES_MMAP
munmap(node, (node->index+1) << BOUNDARY_INDEX);
#else
free(node);
#endif
}
}
free(allocator);
}
#if APR_HAS_THREADS
APR_DECLARE(void) apr_allocator_mutex_set(apr_allocator_t *allocator,
apr_thread_mutex_t *mutex)
{
allocator->mutex = mutex;
}
APR_DECLARE(apr_thread_mutex_t *) apr_allocator_mutex_get(
apr_allocator_t *allocator)
{
return allocator->mutex;
}
#endif /* APR_HAS_THREADS */
APR_DECLARE(void) apr_allocator_owner_set(apr_allocator_t *allocator,
apr_pool_t *pool)
{
allocator->owner = pool;
}
APR_DECLARE(apr_pool_t *) apr_allocator_owner_get(apr_allocator_t *allocator)
{
return allocator->owner;
}
APR_DECLARE(void) apr_allocator_max_free_set(apr_allocator_t *allocator,
apr_size_t in_size)
{
apr_uint32_t max_free_index;
apr_uint32_t size = (APR_UINT32_TRUNC_CAST)in_size;
#if APR_HAS_THREADS
apr_thread_mutex_t *mutex;
mutex = apr_allocator_mutex_get(allocator);
if (mutex != NULL)
apr_thread_mutex_lock(mutex);
#endif /* APR_HAS_THREADS */
max_free_index = APR_ALIGN(size, BOUNDARY_SIZE) >> BOUNDARY_INDEX;
allocator->current_free_index += max_free_index;
allocator->current_free_index -= allocator->max_free_index;
allocator->max_free_index = max_free_index;
if (allocator->current_free_index > max_free_index)
allocator->current_free_index = max_free_index;
#if APR_HAS_THREADS
if (mutex != NULL)
apr_thread_mutex_unlock(mutex);
#endif
}
static APR_INLINE
apr_memnode_t *allocator_alloc(apr_allocator_t *allocator, apr_size_t in_size)
{
apr_memnode_t *node, **ref;
apr_uint32_t max_index;
apr_size_t size, i, index;
/* Round up the block size to the next boundary, but always
* allocate at least a certain size (MIN_ALLOC).
*/
size = APR_ALIGN(in_size + APR_MEMNODE_T_SIZE, BOUNDARY_SIZE);
if (size < in_size) {
return NULL;
}
if (size < MIN_ALLOC)
size = MIN_ALLOC;
/* Find the index for this node size by
* dividing its size by the boundary size
*/
index = (size >> BOUNDARY_INDEX) - 1;
if (index > APR_UINT32_MAX) {
return NULL;
}
/* First see if there are any nodes in the area we know
* our node will fit into.
*/
if (index <= allocator->max_index) {
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_lock(allocator->mutex);
#endif /* APR_HAS_THREADS */
/* Walk the free list to see if there are
* any nodes on it of the requested size
*
* NOTE: an optimization would be to check
* allocator->free[index] first and if no
* node is present, directly use
* allocator->free[max_index]. This seems
* like overkill though and could cause
* memory waste.
*/
max_index = allocator->max_index;
ref = &allocator->free[index];
i = index;
while (*ref == NULL && i < max_index) {
ref++;
i++;
}
if ((node = *ref) != NULL) {
/* If we have found a node and it doesn't have any
* nodes waiting in line behind it _and_ we are on
* the highest available index, find the new highest
* available index
*/
if ((*ref = node->next) == NULL && i >= max_index) {
do {
ref--;
max_index--;
}
while (*ref == NULL && max_index > 0);
allocator->max_index = max_index;
}
allocator->current_free_index += node->index + 1;
if (allocator->current_free_index > allocator->max_free_index)
allocator->current_free_index = allocator->max_free_index;
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_unlock(allocator->mutex);
#endif /* APR_HAS_THREADS */
node->next = NULL;
node->first_avail = (char *)node + APR_MEMNODE_T_SIZE;
return node;
}
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_unlock(allocator->mutex);
#endif /* APR_HAS_THREADS */
}
/* If we found nothing, seek the sink (at index 0), if
* it is not empty.
*/
else if (allocator->free[0]) {
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_lock(allocator->mutex);
#endif /* APR_HAS_THREADS */
/* Walk the free list to see if there are
* any nodes on it of the requested size
*/
ref = &allocator->free[0];
while ((node = *ref) != NULL && index > node->index)
ref = &node->next;
if (node) {
*ref = node->next;
allocator->current_free_index += node->index + 1;
if (allocator->current_free_index > allocator->max_free_index)
allocator->current_free_index = allocator->max_free_index;
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_unlock(allocator->mutex);
#endif /* APR_HAS_THREADS */
node->next = NULL;
node->first_avail = (char *)node + APR_MEMNODE_T_SIZE;
return node;
}
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_unlock(allocator->mutex);
#endif /* APR_HAS_THREADS */
}
/* If we haven't got a suitable node, malloc a new one
* and initialize it.
*/
#if APR_ALLOCATOR_USES_MMAP
if ((node = mmap(NULL, size, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANON, -1, 0)) == MAP_FAILED)
#else
if ((node = malloc(size)) == NULL)
#endif
return NULL;
node->next = NULL;
node->index = (APR_UINT32_TRUNC_CAST)index;
node->first_avail = (char *)node + APR_MEMNODE_T_SIZE;
node->endp = (char *)node + size;
return node;
}
static APR_INLINE
void allocator_free(apr_allocator_t *allocator, apr_memnode_t *node)
{
apr_memnode_t *next, *freelist = NULL;
apr_uint32_t index, max_index;
apr_uint32_t max_free_index, current_free_index;
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_lock(allocator->mutex);
#endif /* APR_HAS_THREADS */
max_index = allocator->max_index;
max_free_index = allocator->max_free_index;
current_free_index = allocator->current_free_index;
/* Walk the list of submitted nodes and free them one by one,
* shoving them in the right 'size' buckets as we go.
*/
do {
next = node->next;
index = node->index;
if (max_free_index != APR_ALLOCATOR_MAX_FREE_UNLIMITED
&& index + 1 > current_free_index) {
node->next = freelist;
freelist = node;
}
else if (index < MAX_INDEX) {
/* Add the node to the appropiate 'size' bucket. Adjust
* the max_index when appropiate.
*/
if ((node->next = allocator->free[index]) == NULL
&& index > max_index) {
max_index = index;
}
allocator->free[index] = node;
if (current_free_index >= index + 1)
current_free_index -= index + 1;
else
current_free_index = 0;
}
else {
/* This node is too large to keep in a specific size bucket,
* just add it to the sink (at index 0).
*/
node->next = allocator->free[0];
allocator->free[0] = node;
if (current_free_index >= index + 1)
current_free_index -= index + 1;
else
current_free_index = 0;
}
} while ((node = next) != NULL);
allocator->max_index = max_index;
allocator->current_free_index = current_free_index;
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_unlock(allocator->mutex);
#endif /* APR_HAS_THREADS */
while (freelist != NULL) {
node = freelist;
freelist = node->next;
#if APR_ALLOCATOR_USES_MMAP
munmap(node, (node->index+1) << BOUNDARY_INDEX);
#else
free(node);
#endif
}
}
APR_DECLARE(apr_memnode_t *) apr_allocator_alloc(apr_allocator_t *allocator,
apr_size_t size)
{
return allocator_alloc(allocator, size);
}
APR_DECLARE(void) apr_allocator_free(apr_allocator_t *allocator,
apr_memnode_t *node)
{
allocator_free(allocator, node);
}
/*
* Debug level
*/
#define APR_POOL_DEBUG_GENERAL 0x01
#define APR_POOL_DEBUG_VERBOSE 0x02
#define APR_POOL_DEBUG_LIFETIME 0x04
#define APR_POOL_DEBUG_OWNER 0x08
#define APR_POOL_DEBUG_VERBOSE_ALLOC 0x10
#define APR_POOL_DEBUG_VERBOSE_ALL (APR_POOL_DEBUG_VERBOSE \
| APR_POOL_DEBUG_VERBOSE_ALLOC)
/*
* Structures
*/
typedef struct cleanup_t cleanup_t;
/** A list of processes */
struct process_chain {
/** The process ID */
apr_proc_t *proc;
apr_kill_conditions_e kill_how;
/** The next process in the list */
struct process_chain *next;
};
#if APR_POOL_DEBUG
typedef struct debug_node_t debug_node_t;
struct debug_node_t {
debug_node_t *next;
apr_uint32_t index;
void *beginp[64];
void *endp[64];
};
#define SIZEOF_DEBUG_NODE_T APR_ALIGN_DEFAULT(sizeof(debug_node_t))
#endif /* APR_POOL_DEBUG */
/* The ref field in the apr_pool_t struct holds a
* pointer to the pointer referencing this pool.
* It is used for parent, child, sibling management.
* Look at apr_pool_create_ex() and apr_pool_destroy()
* to see how it is used.
*/
struct apr_pool_t {
apr_pool_t *parent;
apr_pool_t *child;
apr_pool_t *sibling;
apr_pool_t **ref;
cleanup_t *cleanups;
cleanup_t *free_cleanups;
apr_allocator_t *allocator;
struct process_chain *subprocesses;
apr_abortfunc_t abort_fn;
apr_hash_t *user_data;
const char *tag;
#if !APR_POOL_DEBUG
apr_memnode_t *active;
apr_memnode_t *self; /* The node containing the pool itself */
char *self_first_avail;
#else /* APR_POOL_DEBUG */
apr_pool_t *joined; /* the caller has guaranteed that this pool
* will survive as long as ->joined */
debug_node_t *nodes;
const char *file_line;
apr_uint32_t creation_flags;
unsigned int stat_alloc;
unsigned int stat_total_alloc;
unsigned int stat_clear;
#if APR_HAS_THREADS
apr_os_thread_t owner;
apr_thread_mutex_t *mutex;
#endif /* APR_HAS_THREADS */
#endif /* APR_POOL_DEBUG */
#ifdef NETWARE
apr_os_proc_t owner_proc;
#endif /* defined(NETWARE) */
cleanup_t *pre_cleanups;
};
#define SIZEOF_POOL_T APR_ALIGN_DEFAULT(sizeof(apr_pool_t))
/*
* Variables
*/
static apr_byte_t apr_pools_initialized = 0;
static apr_pool_t *global_pool = NULL;
#if !APR_POOL_DEBUG
static apr_allocator_t *global_allocator = NULL;
#endif /* !APR_POOL_DEBUG */
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL)
static apr_file_t *file_stderr = NULL;
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL) */
/*
* Local functions
*/
static void run_cleanups(cleanup_t **c);
static void free_proc_chain(struct process_chain *procs);
#if APR_POOL_DEBUG
static void pool_destroy_debug(apr_pool_t *pool, const char *file_line);
#endif
#if !APR_POOL_DEBUG
/*
* Initialization
*/
APR_DECLARE(apr_status_t) apr_pool_initialize(void)
{
apr_status_t rv;
if (apr_pools_initialized++)
return APR_SUCCESS;
#if APR_ALLOCATOR_USES_MMAP && defined(_SC_PAGESIZE)
boundary_size = sysconf(_SC_PAGESIZE);
boundary_index = 12;
while ( (1 << boundary_index) < boundary_size)
boundary_index++;
boundary_size = (1 << boundary_index);
#endif
if ((rv = apr_allocator_create(&global_allocator)) != APR_SUCCESS) {
apr_pools_initialized = 0;
return rv;
}
if ((rv = apr_pool_create_ex(&global_pool, NULL, NULL,
global_allocator)) != APR_SUCCESS) {
apr_allocator_destroy(global_allocator);
global_allocator = NULL;
apr_pools_initialized = 0;
return rv;
}
apr_pool_tag(global_pool, "apr_global_pool");
/* This has to happen here because mutexes might be backed by
* atomics. It used to be snug and safe in apr_initialize().
*
* Warning: apr_atomic_init() must always be called, by any
* means possible, from apr_initialize().
*/
if ((rv = apr_atomic_init(global_pool)) != APR_SUCCESS) {
return rv;
}
#if APR_HAS_THREADS
{
apr_thread_mutex_t *mutex;
if ((rv = apr_thread_mutex_create(&mutex,
APR_THREAD_MUTEX_DEFAULT,
global_pool)) != APR_SUCCESS) {
return rv;
}
apr_allocator_mutex_set(global_allocator, mutex);
}
#endif /* APR_HAS_THREADS */
apr_allocator_owner_set(global_allocator, global_pool);
return APR_SUCCESS;
}
APR_DECLARE(void) apr_pool_terminate(void)
{
if (!apr_pools_initialized)
return;
if (--apr_pools_initialized)
return;
apr_pool_destroy(global_pool); /* This will also destroy the mutex */
global_pool = NULL;
global_allocator = NULL;
}
/* Node list management helper macros; list_insert() inserts 'node'
* before 'point'. */
#define list_insert(node, point) do { \
node->ref = point->ref; \
*node->ref = node; \
node->next = point; \
point->ref = &node->next; \
} while (0)
/* list_remove() removes 'node' from its list. */
#define list_remove(node) do { \
*node->ref = node->next; \
node->next->ref = node->ref; \
} while (0)
/* Returns the amount of free space in the given node. */
#define node_free_space(node_) ((apr_size_t)(node_->endp - node_->first_avail))
/*
* Memory allocation
*/
APR_DECLARE(void *) apr_palloc(apr_pool_t *pool, apr_size_t in_size)
{
apr_memnode_t *active, *node;
void *mem;
apr_size_t size, free_index;
size = APR_ALIGN_DEFAULT(in_size);
if (size < in_size) {
if (pool->abort_fn)
pool->abort_fn(APR_ENOMEM);
return NULL;
}
active = pool->active;
/* If the active node has enough bytes left, use it. */
if (size <= node_free_space(active)) {
mem = active->first_avail;
active->first_avail += size;
return mem;
}
node = active->next;
if (size <= node_free_space(node)) {
list_remove(node);
}
else {
if ((node = allocator_alloc(pool->allocator, size)) == NULL) {
if (pool->abort_fn)
pool->abort_fn(APR_ENOMEM);
return NULL;
}
}
node->free_index = 0;
mem = node->first_avail;
node->first_avail += size;
list_insert(node, active);
pool->active = node;
free_index = (APR_ALIGN(active->endp - active->first_avail + 1,
BOUNDARY_SIZE) - BOUNDARY_SIZE) >> BOUNDARY_INDEX;
active->free_index = (APR_UINT32_TRUNC_CAST)free_index;
node = active->next;
if (free_index >= node->free_index)
return mem;
do {
node = node->next;
}
while (free_index < node->free_index);
list_remove(active);
list_insert(active, node);
return mem;
}
/* Provide an implementation of apr_pcalloc for backward compatibility
* with code built before apr_pcalloc was a macro
*/
#ifdef apr_pcalloc
#undef apr_pcalloc
#endif
APR_DECLARE(void *) apr_pcalloc(apr_pool_t *pool, apr_size_t size);
APR_DECLARE(void *) apr_pcalloc(apr_pool_t *pool, apr_size_t size)
{
void *mem;
if ((mem = apr_palloc(pool, size)) != NULL) {
memset(mem, 0, size);
}
return mem;
}
/*
* Pool creation/destruction
*/
APR_DECLARE(void) apr_pool_clear(apr_pool_t *pool)
{
apr_memnode_t *active;
/* Run pre destroy cleanups */
run_cleanups(&pool->pre_cleanups);
pool->pre_cleanups = NULL;
/* Destroy the subpools. The subpools will detach themselves from
* this pool thus this loop is safe and easy.
*/
while (pool->child)
apr_pool_destroy(pool->child);
/* Run cleanups */
run_cleanups(&pool->cleanups);
pool->cleanups = NULL;
pool->free_cleanups = NULL;
/* Free subprocesses */
free_proc_chain(pool->subprocesses);
pool->subprocesses = NULL;
/* Clear the user data. */
pool->user_data = NULL;
/* Find the node attached to the pool structure, reset it, make
* it the active node and free the rest of the nodes.
*/
active = pool->active = pool->self;
active->first_avail = pool->self_first_avail;
if (active->next == active)
return;
*active->ref = NULL;
allocator_free(pool->allocator, active->next);
active->next = active;
active->ref = &active->next;
}
APR_DECLARE(void) apr_pool_destroy(apr_pool_t *pool)
{
apr_memnode_t *active;
apr_allocator_t *allocator;
/* Run pre destroy cleanups */
run_cleanups(&pool->pre_cleanups);
pool->pre_cleanups = NULL;
/* Destroy the subpools. The subpools will detach themselve from
* this pool thus this loop is safe and easy.
*/
while (pool->child)
apr_pool_destroy(pool->child);
/* Run cleanups */
run_cleanups(&pool->cleanups);
/* Free subprocesses */
free_proc_chain(pool->subprocesses);
/* Remove the pool from the parents child list */
if (pool->parent) {
#if APR_HAS_THREADS
apr_thread_mutex_t *mutex;
if ((mutex = apr_allocator_mutex_get(pool->parent->allocator)) != NULL)
apr_thread_mutex_lock(mutex);
#endif /* APR_HAS_THREADS */
if ((*pool->ref = pool->sibling) != NULL)
pool->sibling->ref = pool->ref;
#if APR_HAS_THREADS
if (mutex)
apr_thread_mutex_unlock(mutex);
#endif /* APR_HAS_THREADS */
}
/* Find the block attached to the pool structure. Save a copy of the
* allocator pointer, because the pool struct soon will be no more.
*/
allocator = pool->allocator;
active = pool->self;
*active->ref = NULL;
#if APR_HAS_THREADS
if (apr_allocator_owner_get(allocator) == pool) {
/* Make sure to remove the lock, since it is highly likely to
* be invalid now.
*/
apr_allocator_mutex_set(allocator, NULL);
}
#endif /* APR_HAS_THREADS */
/* Free all the nodes in the pool (including the node holding the
* pool struct), by giving them back to the allocator.
*/
allocator_free(allocator, active);
/* If this pool happens to be the owner of the allocator, free
* everything in the allocator (that includes the pool struct
* and the allocator). Don't worry about destroying the optional mutex
* in the allocator, it will have been destroyed by the cleanup function.
*/
if (apr_allocator_owner_get(allocator) == pool) {
apr_allocator_destroy(allocator);
}
}
APR_DECLARE(apr_status_t) apr_pool_create_ex(apr_pool_t **newpool,
apr_pool_t *parent,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator)
{
apr_pool_t *pool;
apr_memnode_t *node;
*newpool = NULL;
if (!parent)
parent = global_pool;
/* parent will always be non-NULL here except the first time a
* pool is created, in which case allocator is guaranteed to be
* non-NULL. */
if (!abort_fn && parent)
abort_fn = parent->abort_fn;
if (allocator == NULL)
allocator = parent->allocator;
if ((node = allocator_alloc(allocator,
MIN_ALLOC - APR_MEMNODE_T_SIZE)) == NULL) {
if (abort_fn)
abort_fn(APR_ENOMEM);
return APR_ENOMEM;
}
node->next = node;
node->ref = &node->next;
pool = (apr_pool_t *)node->first_avail;
node->first_avail = pool->self_first_avail = (char *)pool + SIZEOF_POOL_T;
pool->allocator = allocator;
pool->active = pool->self = node;
pool->abort_fn = abort_fn;
pool->child = NULL;
pool->cleanups = NULL;
pool->free_cleanups = NULL;
pool->pre_cleanups = NULL;
pool->subprocesses = NULL;
pool->user_data = NULL;
pool->tag = NULL;
#ifdef NETWARE
pool->owner_proc = (apr_os_proc_t)getnlmhandle();
#endif /* defined(NETWARE) */
if ((pool->parent = parent) != NULL) {
#if APR_HAS_THREADS
apr_thread_mutex_t *mutex;
if ((mutex = apr_allocator_mutex_get(parent->allocator)) != NULL)
apr_thread_mutex_lock(mutex);
#endif /* APR_HAS_THREADS */
if ((pool->sibling = parent->child) != NULL)
pool->sibling->ref = &pool->sibling;
parent->child = pool;
pool->ref = &parent->child;
#if APR_HAS_THREADS
if (mutex)
apr_thread_mutex_unlock(mutex);
#endif /* APR_HAS_THREADS */
}
else {
pool->sibling = NULL;
pool->ref = NULL;
}
*newpool = pool;
return APR_SUCCESS;
}
/* Deprecated. Renamed to apr_pool_create_unmanaged_ex
*/
APR_DECLARE(apr_status_t) apr_pool_create_core_ex(apr_pool_t **newpool,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator)
{
return apr_pool_create_unmanaged_ex(newpool, abort_fn, allocator);
}
APR_DECLARE(apr_status_t) apr_pool_create_unmanaged_ex(apr_pool_t **newpool,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator)
{
apr_pool_t *pool;
apr_memnode_t *node;
apr_allocator_t *pool_allocator;
*newpool = NULL;
if (!apr_pools_initialized)
return APR_ENOPOOL;
if ((pool_allocator = allocator) == NULL) {
if ((pool_allocator = malloc(SIZEOF_ALLOCATOR_T)) == NULL) {
if (abort_fn)
abort_fn(APR_ENOMEM);
return APR_ENOMEM;
}
memset(pool_allocator, 0, SIZEOF_ALLOCATOR_T);
pool_allocator->max_free_index = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
}
if ((node = allocator_alloc(pool_allocator,
MIN_ALLOC - APR_MEMNODE_T_SIZE)) == NULL) {
if (abort_fn)
abort_fn(APR_ENOMEM);
return APR_ENOMEM;
}
node->next = node;
node->ref = &node->next;
pool = (apr_pool_t *)node->first_avail;
node->first_avail = pool->self_first_avail = (char *)pool + SIZEOF_POOL_T;
pool->allocator = pool_allocator;
pool->active = pool->self = node;
pool->abort_fn = abort_fn;
pool->child = NULL;
pool->cleanups = NULL;
pool->free_cleanups = NULL;
pool->pre_cleanups = NULL;
pool->subprocesses = NULL;
pool->user_data = NULL;
pool->tag = NULL;
pool->parent = NULL;
pool->sibling = NULL;
pool->ref = NULL;
#ifdef NETWARE
pool->owner_proc = (apr_os_proc_t)getnlmhandle();
#endif /* defined(NETWARE) */
if (!allocator)
pool_allocator->owner = pool;
*newpool = pool;
return APR_SUCCESS;
}
/*
* "Print" functions
*/
/*
* apr_psprintf is implemented by writing directly into the current
* block of the pool, starting right at first_avail. If there's
* insufficient room, then a new block is allocated and the earlier
* output is copied over. The new block isn't linked into the pool
* until all the output is done.
*
* Note that this is completely safe because nothing else can
* allocate in this apr_pool_t while apr_psprintf is running. alarms are
* blocked, and the only thing outside of apr_pools.c that's invoked
* is apr_vformatter -- which was purposefully written to be
* self-contained with no callouts.
*/
struct psprintf_data {
apr_vformatter_buff_t vbuff;
apr_memnode_t *node;
apr_pool_t *pool;
apr_byte_t got_a_new_node;
apr_memnode_t *free;
};
#define APR_PSPRINTF_MIN_STRINGSIZE 32
static int psprintf_flush(apr_vformatter_buff_t *vbuff)
{
struct psprintf_data *ps = (struct psprintf_data *)vbuff;
apr_memnode_t *node, *active;
apr_size_t cur_len, size;
char *strp;
apr_pool_t *pool;
apr_size_t free_index;
pool = ps->pool;
active = ps->node;
strp = ps->vbuff.curpos;
cur_len = strp - active->first_avail;
size = cur_len << 1;
/* Make sure that we don't try to use a block that has less
* than APR_PSPRINTF_MIN_STRINGSIZE bytes left in it. This
* also catches the case where size == 0, which would result
* in reusing a block that can't even hold the NUL byte.
*/
if (size < APR_PSPRINTF_MIN_STRINGSIZE)
size = APR_PSPRINTF_MIN_STRINGSIZE;
node = active->next;
if (!ps->got_a_new_node && size <= node_free_space(node)) {
list_remove(node);
list_insert(node, active);
node->free_index = 0;
pool->active = node;
free_index = (APR_ALIGN(active->endp - active->first_avail + 1,
BOUNDARY_SIZE) - BOUNDARY_SIZE) >> BOUNDARY_INDEX;
active->free_index = (APR_UINT32_TRUNC_CAST)free_index;
node = active->next;
if (free_index < node->free_index) {
do {
node = node->next;
}
while (free_index < node->free_index);
list_remove(active);
list_insert(active, node);
}
node = pool->active;
}
else {
if ((node = allocator_alloc(pool->allocator, size)) == NULL)
return -1;
if (ps->got_a_new_node) {
active->next = ps->free;
ps->free = active;
}
ps->got_a_new_node = 1;
}
memcpy(node->first_avail, active->first_avail, cur_len);
ps->node = node;
ps->vbuff.curpos = node->first_avail + cur_len;
ps->vbuff.endpos = node->endp - 1; /* Save a byte for NUL terminator */
return 0;
}
APR_DECLARE(char *) apr_pvsprintf(apr_pool_t *pool, const char *fmt, va_list ap)
{
struct psprintf_data ps;
char *strp;
apr_size_t size;
apr_memnode_t *active, *node;
apr_size_t free_index;
ps.node = active = pool->active;
ps.pool = pool;
ps.vbuff.curpos = ps.node->first_avail;
/* Save a byte for the NUL terminator */
ps.vbuff.endpos = ps.node->endp - 1;
ps.got_a_new_node = 0;
ps.free = NULL;
/* Make sure that the first node passed to apr_vformatter has at least
* room to hold the NUL terminator.
*/
if (ps.node->first_avail == ps.node->endp) {
if (psprintf_flush(&ps.vbuff) == -1) {
if (pool->abort_fn) {
pool->abort_fn(APR_ENOMEM);
}
return NULL;
}
}
if (apr_vformatter(psprintf_flush, &ps.vbuff, fmt, ap) == -1) {
if (pool->abort_fn)
pool->abort_fn(APR_ENOMEM);
return NULL;
}
strp = ps.vbuff.curpos;
*strp++ = '\0';
size = strp - ps.node->first_avail;
size = APR_ALIGN_DEFAULT(size);
strp = ps.node->first_avail;
ps.node->first_avail += size;
if (ps.free)
allocator_free(pool->allocator, ps.free);
/*
* Link the node in if it's a new one
*/
if (!ps.got_a_new_node)
return strp;
active = pool->active;
node = ps.node;
node->free_index = 0;
list_insert(node, active);
pool->active = node;
free_index = (APR_ALIGN(active->endp - active->first_avail + 1,
BOUNDARY_SIZE) - BOUNDARY_SIZE) >> BOUNDARY_INDEX;
active->free_index = (APR_UINT32_TRUNC_CAST)free_index;
node = active->next;
if (free_index >= node->free_index)
return strp;
do {
node = node->next;
}
while (free_index < node->free_index);
list_remove(active);
list_insert(active, node);
return strp;
}
#else /* APR_POOL_DEBUG */
/*
* Debug helper functions
*/
/*
* Walk the pool tree rooted at pool, depth first. When fn returns
* anything other than 0, abort the traversal and return the value
* returned by fn.
*/
static int apr_pool_walk_tree(apr_pool_t *pool,
int (*fn)(apr_pool_t *pool, void *data),
void *data)
{
int rv;
apr_pool_t *child;
rv = fn(pool, data);
if (rv)
return rv;
#if APR_HAS_THREADS
if (pool->mutex) {
apr_thread_mutex_lock(pool->mutex);
}
#endif /* APR_HAS_THREADS */
child = pool->child;
while (child) {
rv = apr_pool_walk_tree(child, fn, data);
if (rv)
break;
child = child->sibling;
}
#if APR_HAS_THREADS
if (pool->mutex) {
apr_thread_mutex_unlock(pool->mutex);
}
#endif /* APR_HAS_THREADS */
return rv;
}
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL)
static void apr_pool_log_event(apr_pool_t *pool, const char *event,
const char *file_line, int deref)
{
if (file_stderr) {
if (deref) {
apr_file_printf(file_stderr,
"POOL DEBUG: "
"[%lu"
#if APR_HAS_THREADS
"/%lu"
#endif /* APR_HAS_THREADS */
"] "
"%7s "
"(%10lu/%10lu/%10lu) "
"0x%pp \"%s\" "
"<%s> "
"(%u/%u/%u) "
"\n",
(unsigned long)getpid(),
#if APR_HAS_THREADS
(unsigned long)apr_os_thread_current(),
#endif /* APR_HAS_THREADS */
event,
(unsigned long)apr_pool_num_bytes(pool, 0),
(unsigned long)apr_pool_num_bytes(pool, 1),
(unsigned long)apr_pool_num_bytes(global_pool, 1),
pool, pool->tag,
file_line,
pool->stat_alloc, pool->stat_total_alloc, pool->stat_clear);
}
else {
apr_file_printf(file_stderr,
"POOL DEBUG: "
"[%lu"
#if APR_HAS_THREADS
"/%lu"
#endif /* APR_HAS_THREADS */
"] "
"%7s "
" "
"0x%pp "
"<%s> "
"\n",
(unsigned long)getpid(),
#if APR_HAS_THREADS
(unsigned long)apr_os_thread_current(),
#endif /* APR_HAS_THREADS */
event,
pool,
file_line);
}
}
}
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL) */
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_LIFETIME)
static int pool_is_child_of(apr_pool_t *parent, void *data)
{
apr_pool_t *pool = (apr_pool_t *)data;
return (pool == parent);
}
static int apr_pool_is_child_of(apr_pool_t *pool, apr_pool_t *parent)
{
if (parent == NULL)
return 0;
return apr_pool_walk_tree(parent, pool_is_child_of, pool);
}
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_LIFETIME) */
static void apr_pool_check_integrity(apr_pool_t *pool)
{
/* Rule of thumb: use of the global pool is always
* ok, since the only user is apr_pools.c. Unless
* people have searched for the top level parent and
* started to use that...
*/
if (pool == global_pool || global_pool == NULL)
return;
/* Lifetime
* This basically checks to see if the pool being used is still
* a relative to the global pool. If not it was previously
* destroyed, in which case we abort().
*/
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_LIFETIME)
if (!apr_pool_is_child_of(pool, global_pool)) {
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL)
apr_pool_log_event(pool, "LIFE",
__FILE__ ":apr_pool_integrity check", 0);
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL) */
abort();
}
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_LIFETIME) */
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_OWNER)
#if APR_HAS_THREADS
if (!apr_os_thread_equal(pool->owner, apr_os_thread_current())) {
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL)
apr_pool_log_event(pool, "THREAD",
__FILE__ ":apr_pool_integrity check", 0);
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL) */
abort();
}
#endif /* APR_HAS_THREADS */
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_OWNER) */
}
/*
* Initialization (debug)
*/
APR_DECLARE(apr_status_t) apr_pool_initialize(void)
{
apr_status_t rv;
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL)
char *logpath;
apr_file_t *debug_log = NULL;
#endif
if (apr_pools_initialized++)
return APR_SUCCESS;
#if APR_ALLOCATOR_USES_MMAP && defined(_SC_PAGESIZE)
boundary_size = sysconf(_SC_PAGESIZE);
boundary_index = 12;
while ( (1 << boundary_index) < boundary_size)
boundary_index++;
boundary_size = (1 << boundary_index);
#endif
/* Since the debug code works a bit differently then the
* regular pools code, we ask for a lock here. The regular
* pools code has got this lock embedded in the global
* allocator, a concept unknown to debug mode.
*/
if ((rv = apr_pool_create_ex(&global_pool, NULL, NULL,
NULL)) != APR_SUCCESS) {
return rv;
}
apr_pool_tag(global_pool, "APR global pool");
apr_pools_initialized = 1;
/* This has to happen here because mutexes might be backed by
* atomics. It used to be snug and safe in apr_initialize().
*/
if ((rv = apr_atomic_init(global_pool)) != APR_SUCCESS) {
return rv;
}
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL)
rv = apr_env_get(&logpath, "APR_POOL_DEBUG_LOG", global_pool);
/* Don't pass file_stderr directly to apr_file_open() here, since
* apr_file_open() can call back to apr_pool_log_event() and that
* may attempt to use then then non-NULL but partially set up file
* object. */
if (rv == APR_SUCCESS) {
apr_file_open(&debug_log, logpath, APR_APPEND|APR_WRITE|APR_CREATE,
APR_OS_DEFAULT, global_pool);
}
else {
apr_file_open_stderr(&debug_log, global_pool);
}
/* debug_log is now a file handle. */
file_stderr = debug_log;
if (file_stderr) {
apr_file_printf(file_stderr,
"POOL DEBUG: [PID"
#if APR_HAS_THREADS
"/TID"
#endif /* APR_HAS_THREADS */
"] ACTION (SIZE /POOL SIZE /TOTAL SIZE) "
"POOL \"TAG\" <__FILE__:__LINE__> (ALLOCS/TOTAL ALLOCS/CLEARS)\n");
apr_pool_log_event(global_pool, "GLOBAL", __FILE__ ":apr_pool_initialize", 0);
}
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL) */
return APR_SUCCESS;
}
APR_DECLARE(void) apr_pool_terminate(void)
{
if (!apr_pools_initialized)
return;
if (--apr_pools_initialized)
return;
apr_pool_destroy(global_pool); /* This will also destroy the mutex */
global_pool = NULL;
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL)
file_stderr = NULL;
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL) */
}
/*
* Memory allocation (debug)
*/
static void *pool_alloc(apr_pool_t *pool, apr_size_t size)
{
debug_node_t *node;
void *mem;
if ((mem = malloc(size)) == NULL) {
if (pool->abort_fn)
pool->abort_fn(APR_ENOMEM);
return NULL;
}
node = pool->nodes;
if (node == NULL || node->index == 64) {
if ((node = malloc(SIZEOF_DEBUG_NODE_T)) == NULL) {
free(mem);
if (pool->abort_fn)
pool->abort_fn(APR_ENOMEM);
return NULL;
}
memset(node, 0, SIZEOF_DEBUG_NODE_T);
node->next = pool->nodes;
pool->nodes = node;
node->index = 0;
}
node->beginp[node->index] = mem;
node->endp[node->index] = (char *)mem + size;
node->index++;
pool->stat_alloc++;
pool->stat_total_alloc++;
return mem;
}
APR_DECLARE(void *) apr_palloc_debug(apr_pool_t *pool, apr_size_t size,
const char *file_line)
{
void *mem;
apr_pool_check_integrity(pool);
mem = pool_alloc(pool, size);
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALLOC)
apr_pool_log_event(pool, "PALLOC", file_line, 1);
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALLOC) */
return mem;
}
APR_DECLARE(void *) apr_pcalloc_debug(apr_pool_t *pool, apr_size_t size,
const char *file_line)
{
void *mem;
apr_pool_check_integrity(pool);
mem = pool_alloc(pool, size);
memset(mem, 0, size);
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALLOC)
apr_pool_log_event(pool, "PCALLOC", file_line, 1);
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALLOC) */
return mem;
}
/*
* Pool creation/destruction (debug)
*/
#define POOL_POISON_BYTE 'A'
static void pool_clear_debug(apr_pool_t *pool, const char *file_line)
{
debug_node_t *node;
apr_uint32_t index;
/* Run pre destroy cleanups */
run_cleanups(&pool->pre_cleanups);
pool->pre_cleanups = NULL;
/* Destroy the subpools. The subpools will detach themselves from
* this pool thus this loop is safe and easy.
*/
while (pool->child)
pool_destroy_debug(pool->child, file_line);
/* Run cleanups */
run_cleanups(&pool->cleanups);
pool->free_cleanups = NULL;
pool->cleanups = NULL;
/* If new child pools showed up, this is a reason to raise a flag */
if (pool->child)
abort();
/* Free subprocesses */
free_proc_chain(pool->subprocesses);
pool->subprocesses = NULL;
/* Clear the user data. */
pool->user_data = NULL;
/* Free the blocks, scribbling over them first to help highlight
* use-after-free issues. */
while ((node = pool->nodes) != NULL) {
pool->nodes = node->next;
for (index = 0; index < node->index; index++) {
memset(node->beginp[index], POOL_POISON_BYTE,
(char *)node->endp[index] - (char *)node->beginp[index]);
free(node->beginp[index]);
}
memset(node, POOL_POISON_BYTE, SIZEOF_DEBUG_NODE_T);
free(node);
}
pool->stat_alloc = 0;
pool->stat_clear++;
}
APR_DECLARE(void) apr_pool_clear_debug(apr_pool_t *pool,
const char *file_line)
{
#if APR_HAS_THREADS
apr_thread_mutex_t *mutex = NULL;
#endif
apr_pool_check_integrity(pool);
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE)
apr_pool_log_event(pool, "CLEAR", file_line, 1);
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE) */
#if APR_HAS_THREADS
if (pool->parent != NULL)
mutex = pool->parent->mutex;
/* Lock the parent mutex before clearing so that if we have our
* own mutex it won't be accessed by apr_pool_walk_tree after
* it has been destroyed.
*/
if (mutex != NULL && mutex != pool->mutex) {
apr_thread_mutex_lock(mutex);
}
#endif
pool_clear_debug(pool, file_line);
#if APR_HAS_THREADS
/* If we had our own mutex, it will have been destroyed by
* the registered cleanups. Recreate the mutex. Unlock
* the mutex we obtained above.
*/
if (mutex != pool->mutex) {
(void)apr_thread_mutex_create(&pool->mutex,
APR_THREAD_MUTEX_NESTED, pool);
if (mutex != NULL)
(void)apr_thread_mutex_unlock(mutex);
}
#endif /* APR_HAS_THREADS */
}
static void pool_destroy_debug(apr_pool_t *pool, const char *file_line)
{
apr_pool_check_integrity(pool);
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE)
apr_pool_log_event(pool, "DESTROY", file_line, 1);
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE) */
pool_clear_debug(pool, file_line);
/* Remove the pool from the parents child list */
if (pool->parent) {
#if APR_HAS_THREADS
apr_thread_mutex_t *mutex;
if ((mutex = pool->parent->mutex) != NULL)
apr_thread_mutex_lock(mutex);
#endif /* APR_HAS_THREADS */
if ((*pool->ref = pool->sibling) != NULL)
pool->sibling->ref = pool->ref;
#if APR_HAS_THREADS
if (mutex)
apr_thread_mutex_unlock(mutex);
#endif /* APR_HAS_THREADS */
}
if (pool->allocator != NULL
&& apr_allocator_owner_get(pool->allocator) == pool) {
apr_allocator_destroy(pool->allocator);
}
/* Free the pool itself */
free(pool);
}
APR_DECLARE(void) apr_pool_destroy_debug(apr_pool_t *pool,
const char *file_line)
{
if (pool->joined) {
/* Joined pools must not be explicitly destroyed; the caller
* has broken the guarantee. */
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL)
apr_pool_log_event(pool, "LIFE",
__FILE__ ":apr_pool_destroy abort on joined", 0);
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE_ALL) */
abort();
}
pool_destroy_debug(pool, file_line);
}
APR_DECLARE(apr_status_t) apr_pool_create_ex_debug(apr_pool_t **newpool,
apr_pool_t *parent,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator,
const char *file_line)
{
apr_pool_t *pool;
*newpool = NULL;
if (!parent) {
parent = global_pool;
}
else {
apr_pool_check_integrity(parent);
if (!allocator)
allocator = parent->allocator;
}
if (!abort_fn && parent)
abort_fn = parent->abort_fn;
if ((pool = malloc(SIZEOF_POOL_T)) == NULL) {
if (abort_fn)
abort_fn(APR_ENOMEM);
return APR_ENOMEM;
}
memset(pool, 0, SIZEOF_POOL_T);
pool->allocator = allocator;
pool->abort_fn = abort_fn;
pool->tag = file_line;
pool->file_line = file_line;
if ((pool->parent = parent) != NULL) {
#if APR_HAS_THREADS
if (parent->mutex)
apr_thread_mutex_lock(parent->mutex);
#endif /* APR_HAS_THREADS */
if ((pool->sibling = parent->child) != NULL)
pool->sibling->ref = &pool->sibling;
parent->child = pool;
pool->ref = &parent->child;
#if APR_HAS_THREADS
if (parent->mutex)
apr_thread_mutex_unlock(parent->mutex);
#endif /* APR_HAS_THREADS */
}
else {
pool->sibling = NULL;
pool->ref = NULL;
}
#if APR_HAS_THREADS
pool->owner = apr_os_thread_current();
#endif /* APR_HAS_THREADS */
#ifdef NETWARE
pool->owner_proc = (apr_os_proc_t)getnlmhandle();
#endif /* defined(NETWARE) */
if (parent == NULL || parent->allocator != allocator) {
#if APR_HAS_THREADS
apr_status_t rv;
/* No matter what the creation flags say, always create
* a lock. Without it integrity_check and apr_pool_num_bytes
* blow up (because they traverse pools child lists that
* possibly belong to another thread, in combination with
* the pool having no lock). However, this might actually
* hide problems like creating a child pool of a pool
* belonging to another thread.
*/
if ((rv = apr_thread_mutex_create(&pool->mutex,
APR_THREAD_MUTEX_NESTED, pool)) != APR_SUCCESS) {
free(pool);
return rv;
}
#endif /* APR_HAS_THREADS */
}
else {
#if APR_HAS_THREADS
if (parent)
pool->mutex = parent->mutex;
#endif /* APR_HAS_THREADS */
}
*newpool = pool;
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE)
apr_pool_log_event(pool, "CREATE", file_line, 1);
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE) */
return APR_SUCCESS;
}
APR_DECLARE(apr_status_t) apr_pool_create_core_ex_debug(apr_pool_t **newpool,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator,
const char *file_line)
{
return apr_pool_create_unmanaged_ex_debug(newpool, abort_fn, allocator,
file_line);
}
APR_DECLARE(apr_status_t) apr_pool_create_unmanaged_ex_debug(apr_pool_t **newpool,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator,
const char *file_line)
{
apr_pool_t *pool;
apr_allocator_t *pool_allocator;
*newpool = NULL;
if ((pool = malloc(SIZEOF_POOL_T)) == NULL) {
if (abort_fn)
abort_fn(APR_ENOMEM);
return APR_ENOMEM;
}
memset(pool, 0, SIZEOF_POOL_T);
pool->abort_fn = abort_fn;
pool->tag = file_line;
pool->file_line = file_line;
#if APR_HAS_THREADS
pool->owner = apr_os_thread_current();
#endif /* APR_HAS_THREADS */
#ifdef NETWARE
pool->owner_proc = (apr_os_proc_t)getnlmhandle();
#endif /* defined(NETWARE) */
if ((pool_allocator = allocator) == NULL) {
apr_status_t rv;
if ((rv = apr_allocator_create(&pool_allocator)) != APR_SUCCESS) {
if (abort_fn)
abort_fn(rv);
return rv;
}
pool_allocator->owner = pool;
}
pool->allocator = pool_allocator;
if (pool->allocator != allocator) {
#if APR_HAS_THREADS
apr_status_t rv;
/* No matter what the creation flags say, always create
* a lock. Without it integrity_check and apr_pool_num_bytes
* blow up (because they traverse pools child lists that
* possibly belong to another thread, in combination with
* the pool having no lock). However, this might actually
* hide problems like creating a child pool of a pool
* belonging to another thread.
*/
if ((rv = apr_thread_mutex_create(&pool->mutex,
APR_THREAD_MUTEX_NESTED, pool)) != APR_SUCCESS) {
free(pool);
return rv;
}
#endif /* APR_HAS_THREADS */
}
*newpool = pool;
#if (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE)
apr_pool_log_event(pool, "CREATE", file_line, 1);
#endif /* (APR_POOL_DEBUG & APR_POOL_DEBUG_VERBOSE) */
return APR_SUCCESS;
}
/*
* "Print" functions (debug)
*/
struct psprintf_data {
apr_vformatter_buff_t vbuff;
char *mem;
apr_size_t size;
};
static int psprintf_flush(apr_vformatter_buff_t *vbuff)
{
struct psprintf_data *ps = (struct psprintf_data *)vbuff;
apr_size_t size;
size = ps->vbuff.curpos - ps->mem;
ps->size <<= 1;
if ((ps->mem = realloc(ps->mem, ps->size)) == NULL)
return -1;
ps->vbuff.curpos = ps->mem + size;
ps->vbuff.endpos = ps->mem + ps->size - 1;
return 0;
}
APR_DECLARE(char *) apr_pvsprintf(apr_pool_t *pool, const char *fmt, va_list ap)
{
struct psprintf_data ps;
debug_node_t *node;
apr_pool_check_integrity(pool);
ps.size = 64;
ps.mem = malloc(ps.size);
ps.vbuff.curpos = ps.mem;
/* Save a byte for the NUL terminator */
ps.vbuff.endpos = ps.mem + ps.size - 1;
if (apr_vformatter(psprintf_flush, &ps.vbuff, fmt, ap) == -1) {
if (pool->abort_fn)
pool->abort_fn(APR_ENOMEM);
return NULL;
}
*ps.vbuff.curpos++ = '\0';
/*
* Link the node in
*/
node = pool->nodes;
if (node == NULL || node->index == 64) {
if ((node = malloc(SIZEOF_DEBUG_NODE_T)) == NULL) {
if (pool->abort_fn)
pool->abort_fn(APR_ENOMEM);
return NULL;
}
node->next = pool->nodes;
pool->nodes = node;
node->index = 0;
}
node->beginp[node->index] = ps.mem;
node->endp[node->index] = ps.mem + ps.size;
node->index++;
return ps.mem;
}
/*
* Debug functions
*/
APR_DECLARE(void) apr_pool_join(apr_pool_t *p, apr_pool_t *sub)
{
#if APR_POOL_DEBUG
if (sub->parent != p) {
abort();
}
sub->joined = p;
#endif
}
static int pool_find(apr_pool_t *pool, void *data)
{
void **pmem = (void **)data;
debug_node_t *node;
apr_uint32_t index;
node = pool->nodes;
while (node) {
for (index = 0; index < node->index; index++) {
if (node->beginp[index] <= *pmem
&& node->endp[index] > *pmem) {
*pmem = pool;
return 1;
}
}
node = node->next;
}
return 0;
}
APR_DECLARE(apr_pool_t *) apr_pool_find(const void *mem)
{
void *pool = (void *)mem;
if (apr_pool_walk_tree(global_pool, pool_find, &pool))
return pool;
return NULL;
}
static int pool_num_bytes(apr_pool_t *pool, void *data)
{
apr_size_t *psize = (apr_size_t *)data;
debug_node_t *node;
apr_uint32_t index;
node = pool->nodes;
while (node) {
for (index = 0; index < node->index; index++) {
*psize += (char *)node->endp[index] - (char *)node->beginp[index];
}
node = node->next;
}
return 0;
}
APR_DECLARE(apr_size_t) apr_pool_num_bytes(apr_pool_t *pool, int recurse)
{
apr_size_t size = 0;
if (!recurse) {
pool_num_bytes(pool, &size);
return size;
}
apr_pool_walk_tree(pool, pool_num_bytes, &size);
return size;
}
APR_DECLARE(void) apr_pool_lock(apr_pool_t *pool, int flag)
{
}
#endif /* !APR_POOL_DEBUG */
#ifdef NETWARE
void netware_pool_proc_cleanup ()
{
apr_pool_t *pool = global_pool->child;
apr_os_proc_t owner_proc = (apr_os_proc_t)getnlmhandle();
while (pool) {
if (pool->owner_proc == owner_proc) {
apr_pool_destroy (pool);
pool = global_pool->child;
}
else {
pool = pool->sibling;
}
}
return;
}
#endif /* defined(NETWARE) */
/*
* "Print" functions (common)
*/
APR_DECLARE_NONSTD(char *) apr_psprintf(apr_pool_t *p, const char *fmt, ...)
{
va_list ap;
char *res;
va_start(ap, fmt);
res = apr_pvsprintf(p, fmt, ap);
va_end(ap);
return res;
}
/*
* Pool Properties
*/
APR_DECLARE(void) apr_pool_abort_set(apr_abortfunc_t abort_fn,
apr_pool_t *pool)
{
pool->abort_fn = abort_fn;
}
APR_DECLARE(apr_abortfunc_t) apr_pool_abort_get(apr_pool_t *pool)
{
return pool->abort_fn;
}
APR_DECLARE(apr_pool_t *) apr_pool_parent_get(apr_pool_t *pool)
{
#ifdef NETWARE
/* On NetWare, don't return the global_pool, return the application pool
as the top most pool */
if (pool->parent == global_pool)
return pool;
else
#endif
return pool->parent;
}
APR_DECLARE(apr_allocator_t *) apr_pool_allocator_get(apr_pool_t *pool)
{
return pool->allocator;
}
/* return TRUE if a is an ancestor of b
* NULL is considered an ancestor of all pools
*/
APR_DECLARE(int) apr_pool_is_ancestor(apr_pool_t *a, apr_pool_t *b)
{
if (a == NULL)
return 1;
#if APR_POOL_DEBUG
/* Find the pool with the longest lifetime guaranteed by the
* caller: */
while (a->joined) {
a = a->joined;
}
#endif
while (b) {
if (a == b)
return 1;
b = b->parent;
}
return 0;
}
APR_DECLARE(void) apr_pool_tag(apr_pool_t *pool, const char *tag)
{
pool->tag = tag;
}
/*
* User data management
*/
APR_DECLARE(apr_status_t) apr_pool_userdata_set(const void *data, const char *key,
apr_status_t (*cleanup) (void *),
apr_pool_t *pool)
{
#if APR_POOL_DEBUG
apr_pool_check_integrity(pool);
#endif /* APR_POOL_DEBUG */
if (pool->user_data == NULL)
pool->user_data = apr_hash_make(pool);
if (apr_hash_get(pool->user_data, key, APR_HASH_KEY_STRING) == NULL) {
char *new_key = apr_pstrdup(pool, key);
apr_hash_set(pool->user_data, new_key, APR_HASH_KEY_STRING, data);
}
else {
apr_hash_set(pool->user_data, key, APR_HASH_KEY_STRING, data);
}
if (cleanup)
apr_pool_cleanup_register(pool, data, cleanup, cleanup);
return APR_SUCCESS;
}
APR_DECLARE(apr_status_t) apr_pool_userdata_setn(const void *data,
const char *key,
apr_status_t (*cleanup)(void *),
apr_pool_t *pool)
{
#if APR_POOL_DEBUG
apr_pool_check_integrity(pool);
#endif /* APR_POOL_DEBUG */
if (pool->user_data == NULL)
pool->user_data = apr_hash_make(pool);
apr_hash_set(pool->user_data, key, APR_HASH_KEY_STRING, data);
if (cleanup)
apr_pool_cleanup_register(pool, data, cleanup, cleanup);
return APR_SUCCESS;
}
APR_DECLARE(apr_status_t) apr_pool_userdata_get(void **data, const char *key,
apr_pool_t *pool)
{
#if APR_POOL_DEBUG
apr_pool_check_integrity(pool);
#endif /* APR_POOL_DEBUG */
if (pool->user_data == NULL) {
*data = NULL;
}
else {
*data = apr_hash_get(pool->user_data, key, APR_HASH_KEY_STRING);
}
return APR_SUCCESS;
}
/*
* Cleanup
*/
struct cleanup_t {
struct cleanup_t *next;
const void *data;
apr_status_t (*plain_cleanup_fn)(void *data);
apr_status_t (*child_cleanup_fn)(void *data);
};
APR_DECLARE(void) apr_pool_cleanup_register(apr_pool_t *p, const void *data,
apr_status_t (*plain_cleanup_fn)(void *data),
apr_status_t (*child_cleanup_fn)(void *data))
{
cleanup_t *c;
#if APR_POOL_DEBUG
apr_pool_check_integrity(p);
#endif /* APR_POOL_DEBUG */
if (p != NULL) {
if (p->free_cleanups) {
/* reuse a cleanup structure */
c = p->free_cleanups;
p->free_cleanups = c->next;
} else {
c = apr_palloc(p, sizeof(cleanup_t));
}
c->data = data;
c->plain_cleanup_fn = plain_cleanup_fn;
c->child_cleanup_fn = child_cleanup_fn;
c->next = p->cleanups;
p->cleanups = c;
}
}
APR_DECLARE(void) apr_pool_pre_cleanup_register(apr_pool_t *p, const void *data,
apr_status_t (*plain_cleanup_fn)(void *data))
{
cleanup_t *c;
#if APR_POOL_DEBUG
apr_pool_check_integrity(p);
#endif /* APR_POOL_DEBUG */
if (p != NULL) {
if (p->free_cleanups) {
/* reuse a cleanup structure */
c = p->free_cleanups;
p->free_cleanups = c->next;
} else {
c = apr_palloc(p, sizeof(cleanup_t));
}
c->data = data;
c->plain_cleanup_fn = plain_cleanup_fn;
c->next = p->pre_cleanups;
p->pre_cleanups = c;
}
}
APR_DECLARE(void) apr_pool_cleanup_kill(apr_pool_t *p, const void *data,
apr_status_t (*cleanup_fn)(void *))
{
cleanup_t *c, **lastp;
#if APR_POOL_DEBUG
apr_pool_check_integrity(p);
#endif /* APR_POOL_DEBUG */
if (p == NULL)
return;
c = p->cleanups;
lastp = &p->cleanups;
while (c) {
#if APR_POOL_DEBUG
/* Some cheap loop detection to catch a corrupt list: */
if (c == c->next
|| (c->next && c == c->next->next)
|| (c->next && c->next->next && c == c->next->next->next)) {
abort();
}
#endif
if (c->data == data && c->plain_cleanup_fn == cleanup_fn) {
*lastp = c->next;
/* move to freelist */
c->next = p->free_cleanups;
p->free_cleanups = c;
break;
}
lastp = &c->next;
c = c->next;
}
/* Remove any pre-cleanup as well */
c = p->pre_cleanups;
lastp = &p->pre_cleanups;
while (c) {
#if APR_POOL_DEBUG
/* Some cheap loop detection to catch a corrupt list: */
if (c == c->next
|| (c->next && c == c->next->next)
|| (c->next && c->next->next && c == c->next->next->next)) {
abort();
}
#endif
if (c->data == data && c->plain_cleanup_fn == cleanup_fn) {
*lastp = c->next;
/* move to freelist */
c->next = p->free_cleanups;
p->free_cleanups = c;
break;
}
lastp = &c->next;
c = c->next;
}
}
APR_DECLARE(void) apr_pool_child_cleanup_set(apr_pool_t *p, const void *data,
apr_status_t (*plain_cleanup_fn)(void *),
apr_status_t (*child_cleanup_fn)(void *))
{
cleanup_t *c;
#if APR_POOL_DEBUG
apr_pool_check_integrity(p);
#endif /* APR_POOL_DEBUG */
if (p == NULL)
return;
c = p->cleanups;
while (c) {
if (c->data == data && c->plain_cleanup_fn == plain_cleanup_fn) {
c->child_cleanup_fn = child_cleanup_fn;
break;
}
c = c->next;
}
}
APR_DECLARE(apr_status_t) apr_pool_cleanup_run(apr_pool_t *p, void *data,
apr_status_t (*cleanup_fn)(void *))
{
apr_pool_cleanup_kill(p, data, cleanup_fn);
return (*cleanup_fn)(data);
}
static void run_cleanups(cleanup_t **cref)
{
cleanup_t *c = *cref;
while (c) {
*cref = c->next;
(*c->plain_cleanup_fn)((void *)c->data);
c = *cref;
}
}
#if !defined(WIN32) && !defined(OS2)
static void run_child_cleanups(cleanup_t **cref)
{
cleanup_t *c = *cref;
while (c) {
*cref = c->next;
(*c->child_cleanup_fn)((void *)c->data);
c = *cref;
}
}
static void cleanup_pool_for_exec(apr_pool_t *p)
{
run_child_cleanups(&p->cleanups);
for (p = p->child; p; p = p->sibling)
cleanup_pool_for_exec(p);
}
APR_DECLARE(void) apr_pool_cleanup_for_exec(void)
{
cleanup_pool_for_exec(global_pool);
}
#else /* !defined(WIN32) && !defined(OS2) */
APR_DECLARE(void) apr_pool_cleanup_for_exec(void)
{
/*
* Don't need to do anything on NT or OS/2, because
* these platforms will spawn the new process - not
* fork for exec. All handles that are not inheritable,
* will be automajically closed. The only problem is
* with file handles that are open, but there isn't
* much that can be done about that (except if the
* child decides to go out and close them, or the
* developer quits opening them shared)
*/
return;
}
#endif /* !defined(WIN32) && !defined(OS2) */
APR_DECLARE_NONSTD(apr_status_t) apr_pool_cleanup_null(void *data)
{
/* do nothing cleanup routine */
return APR_SUCCESS;
}
/* Subprocesses don't use the generic cleanup interface because
* we don't want multiple subprocesses to result in multiple
* three-second pauses; the subprocesses have to be "freed" all
* at once. If other resources are introduced with the same property,
* we might want to fold support for that into the generic interface.
* For now, it's a special case.
*/
APR_DECLARE(void) apr_pool_note_subprocess(apr_pool_t *pool, apr_proc_t *proc,
apr_kill_conditions_e how)
{
struct process_chain *pc = apr_palloc(pool, sizeof(struct process_chain));
pc->proc = proc;
pc->kill_how = how;
pc->next = pool->subprocesses;
pool->subprocesses = pc;
}
static void free_proc_chain(struct process_chain *procs)
{
/* Dispose of the subprocesses we've spawned off in the course of
* whatever it was we're cleaning up now. This may involve killing
* some of them off...
*/
struct process_chain *pc;
int need_timeout = 0;
apr_time_t timeout_interval;
if (!procs)
return; /* No work. Whew! */
/* First, check to see if we need to do the SIGTERM, sleep, SIGKILL
* dance with any of the processes we're cleaning up. If we've got
* any kill-on-sight subprocesses, ditch them now as well, so they
* don't waste any more cycles doing whatever it is that they shouldn't
* be doing anymore.
*/
#ifndef NEED_WAITPID
/* Pick up all defunct processes */
for (pc = procs; pc; pc = pc->next) {
if (apr_proc_wait(pc->proc, NULL, NULL, APR_NOWAIT) != APR_CHILD_NOTDONE)
pc->kill_how = APR_KILL_NEVER;
}
#endif /* !defined(NEED_WAITPID) */
for (pc = procs; pc; pc = pc->next) {
#ifndef WIN32
if ((pc->kill_how == APR_KILL_AFTER_TIMEOUT)
|| (pc->kill_how == APR_KILL_ONLY_ONCE)) {
/*
* Subprocess may be dead already. Only need the timeout if not.
* Note: apr_proc_kill on Windows is TerminateProcess(), which is
* similar to a SIGKILL, so always give the process a timeout
* under Windows before killing it.
*/
if (apr_proc_kill(pc->proc, SIGTERM) == APR_SUCCESS)
need_timeout = 1;
}
else if (pc->kill_how == APR_KILL_ALWAYS) {
#else /* WIN32 knows only one fast, clean method of killing processes today */
if (pc->kill_how != APR_KILL_NEVER) {
need_timeout = 1;
pc->kill_how = APR_KILL_ALWAYS;
#endif
apr_proc_kill(pc->proc, SIGKILL);
}
}
/* Sleep only if we have to. The sleep algorithm grows
* by a factor of two on each iteration. TIMEOUT_INTERVAL
* is equal to TIMEOUT_USECS / 64.
*/
if (need_timeout) {
timeout_interval = TIMEOUT_INTERVAL;
apr_sleep(timeout_interval);
do {
/* check the status of the subprocesses */
need_timeout = 0;
for (pc = procs; pc; pc = pc->next) {
if (pc->kill_how == APR_KILL_AFTER_TIMEOUT) {
if (apr_proc_wait(pc->proc, NULL, NULL, APR_NOWAIT)
== APR_CHILD_NOTDONE)
need_timeout = 1; /* subprocess is still active */
else
pc->kill_how = APR_KILL_NEVER; /* subprocess has exited */
}
}
if (need_timeout) {
if (timeout_interval >= TIMEOUT_USECS) {
break;
}
apr_sleep(timeout_interval);
timeout_interval *= 2;
}
} while (need_timeout);
}
/* OK, the scripts we just timed out for have had a chance to clean up
* --- now, just get rid of them, and also clean up the system accounting
* goop...
*/
for (pc = procs; pc; pc = pc->next) {
if (pc->kill_how == APR_KILL_AFTER_TIMEOUT)
apr_proc_kill(pc->proc, SIGKILL);
}
/* Now wait for all the signaled processes to die */
for (pc = procs; pc; pc = pc->next) {
if (pc->kill_how != APR_KILL_NEVER)
(void)apr_proc_wait(pc->proc, NULL, NULL, APR_WAIT);
}
}
/*
* Pool creation/destruction stubs, for people who are running
* mixed release/debug enviroments.
*/
#if !APR_POOL_DEBUG
APR_DECLARE(void *) apr_palloc_debug(apr_pool_t *pool, apr_size_t size,
const char *file_line)
{
return apr_palloc(pool, size);
}
APR_DECLARE(void *) apr_pcalloc_debug(apr_pool_t *pool, apr_size_t size,
const char *file_line)
{
return apr_pcalloc(pool, size);
}
APR_DECLARE(void) apr_pool_clear_debug(apr_pool_t *pool,
const char *file_line)
{
apr_pool_clear(pool);
}
APR_DECLARE(void) apr_pool_destroy_debug(apr_pool_t *pool,
const char *file_line)
{
apr_pool_destroy(pool);
}
APR_DECLARE(apr_status_t) apr_pool_create_ex_debug(apr_pool_t **newpool,
apr_pool_t *parent,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator,
const char *file_line)
{
return apr_pool_create_ex(newpool, parent, abort_fn, allocator);
}
APR_DECLARE(apr_status_t) apr_pool_create_core_ex_debug(apr_pool_t **newpool,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator,
const char *file_line)
{
return apr_pool_create_unmanaged_ex(newpool, abort_fn, allocator);
}
APR_DECLARE(apr_status_t) apr_pool_create_unmanaged_ex_debug(apr_pool_t **newpool,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator,
const char *file_line)
{
return apr_pool_create_unmanaged_ex(newpool, abort_fn, allocator);
}
#else /* APR_POOL_DEBUG */
#undef apr_palloc
APR_DECLARE(void *) apr_palloc(apr_pool_t *pool, apr_size_t size);
APR_DECLARE(void *) apr_palloc(apr_pool_t *pool, apr_size_t size)
{
return apr_palloc_debug(pool, size, "undefined");
}
#undef apr_pcalloc
APR_DECLARE(void *) apr_pcalloc(apr_pool_t *pool, apr_size_t size);
APR_DECLARE(void *) apr_pcalloc(apr_pool_t *pool, apr_size_t size)
{
return apr_pcalloc_debug(pool, size, "undefined");
}
#undef apr_pool_clear
APR_DECLARE(void) apr_pool_clear(apr_pool_t *pool);
APR_DECLARE(void) apr_pool_clear(apr_pool_t *pool)
{
apr_pool_clear_debug(pool, "undefined");
}
#undef apr_pool_destroy
APR_DECLARE(void) apr_pool_destroy(apr_pool_t *pool);
APR_DECLARE(void) apr_pool_destroy(apr_pool_t *pool)
{
apr_pool_destroy_debug(pool, "undefined");
}
#undef apr_pool_create_ex
APR_DECLARE(apr_status_t) apr_pool_create_ex(apr_pool_t **newpool,
apr_pool_t *parent,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator);
APR_DECLARE(apr_status_t) apr_pool_create_ex(apr_pool_t **newpool,
apr_pool_t *parent,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator)
{
return apr_pool_create_ex_debug(newpool, parent,
abort_fn, allocator,
"undefined");
}
#undef apr_pool_create_core_ex
APR_DECLARE(apr_status_t) apr_pool_create_core_ex(apr_pool_t **newpool,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator);
APR_DECLARE(apr_status_t) apr_pool_create_core_ex(apr_pool_t **newpool,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator)
{
return apr_pool_create_unmanaged_ex_debug(newpool, abort_fn,
allocator, "undefined");
}
#undef apr_pool_create_unmanaged_ex
APR_DECLARE(apr_status_t) apr_pool_create_unmanaged_ex(apr_pool_t **newpool,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator);
APR_DECLARE(apr_status_t) apr_pool_create_unmanaged_ex(apr_pool_t **newpool,
apr_abortfunc_t abort_fn,
apr_allocator_t *allocator)
{
return apr_pool_create_unmanaged_ex_debug(newpool, abort_fn,
allocator, "undefined");
}
#endif /* APR_POOL_DEBUG */