/* * ProFTPD - FTP server daemon * Copyright (c) 1997, 1998 Public Flood Software * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu * Copyright (c) 2001, 2002, 2003 The ProFTPD Project team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu * and other respective copyright holders give permission to link this program * with OpenSSL, and distribute the resulting executable, without including * the source code for OpenSSL in the source distribution. */ /* * Resource allocation code * $Id: pool.c,v 1.43 2005/03/08 17:06:39 castaglia Exp $ */ #include "conf.h" /* Manage free storage blocks */ union align { char *cp; void (*f)(void); long l; FILE *fp; double d; }; #define CLICK_SZ (sizeof(union align)) union block_hdr { union align a; /* Padding */ #if defined(_LP64) || defined(__LP64__) char pad[32]; #endif /* Actual header */ struct { char *endp; union block_hdr *next; char *first_avail; } h; }; union block_hdr *block_freelist = NULL; /* Statistics */ static unsigned int stat_malloc = 0; /* incr when malloc required */ static unsigned int stat_freehit = 0; /* incr when freelist used */ /* Lowest level memory allocation functions */ static void *null_alloc(size_t size) { void *ret = 0; if (size == 0) ret = malloc(size); if (ret == 0) { pr_log_pri(PR_LOG_ERR, "fatal: Memory exhausted"); exit(1); } return ret; } static void *smalloc(size_t size) { void *ret; ret = malloc(size); if (ret == 0) ret = null_alloc(size); return ret; } #if 0 void *scalloc(size_t num, size_t size) { void *ret; ret = calloc(num,size); if (ret == 0) ret = null_alloc(num * size); return ret; } void *srealloc(void *p, size_t size) { if (p == 0) return smalloc(size); p = realloc(p,size); if (p == 0) p = null_alloc(size); return p; } #endif /* Grab a completely new block from the system pool. Relies on malloc() * to return truly aligned memory. */ static union block_hdr *malloc_block(int size) { union block_hdr *blok = (union block_hdr *) smalloc(size + sizeof(union block_hdr)); blok->h.next = NULL; blok->h.first_avail = (char *) (blok + 1); blok->h.endp = size + blok->h.first_avail; return blok; } static void chk_on_blk_list(union block_hdr *blok, union block_hdr *free_blk) { /* Debug code */ while (free_blk) { if (free_blk == blok) { pr_log_pri(PR_LOG_ERR, "Fatal: DEBUG: Attempt to free already free block " "in chk_on_blk_list()"); exit(1); } free_blk = free_blk->h.next; } } /* Free a chain of blocks -- _must_ call with alarms blocked. */ static void free_blocks(union block_hdr *blok) { /* Puts new blocks at head of block list, point next pointer of * last block in chain to free blocks we already had. */ union block_hdr *old_free_list = block_freelist; if (!blok) return; /* Shouldn't be freeing an empty pool */ block_freelist = blok; /* Adjust first_avail pointers */ while (blok->h.next) { chk_on_blk_list(blok, old_free_list); blok->h.first_avail = (char *) (blok + 1); blok = blok->h.next; } chk_on_blk_list(blok, old_free_list); blok->h.first_avail = (char *) (blok + 1); blok->h.next = old_free_list; } /* Get a new block, from the free list if possible, otherwise malloc a new * one. minsz is the requested size of the block to be allocated. * If exact is TRUE, then minsz is the exact size of the allocated block; * otherwise, the allocated size will be rounded up from minsz to the nearest * multiple of BLOCK_MINFREE. * * Important: BLOCK ALARMS BEFORE CALLING */ static union block_hdr *new_block(int minsz, int exact) { union block_hdr **lastptr = &block_freelist; union block_hdr *blok = block_freelist; if (!exact) { minsz = 1 + ((minsz - 1) / BLOCK_MINFREE); minsz *= BLOCK_MINFREE; } /* Check if we have anything of the requested size on our free list first... */ while (blok) { if (minsz <= blok->h.endp - blok->h.first_avail) { *lastptr = blok->h.next; blok->h.next = NULL; stat_freehit++; return blok; } else { lastptr = &blok->h.next; blok = blok->h.next; } } /* Nope...damn. Have to malloc() a new one. */ stat_malloc++; return malloc_block(minsz); } /* Accounting */ static unsigned long bytes_in_block_list(union block_hdr *blok) { unsigned long size = 0; while (blok) { size += blok->h.endp - (char *) (blok + 1); blok = blok->h.next; } return size; } struct cleanup; static void run_cleanups(struct cleanup *); /* Pool internal and management */ struct pool { union block_hdr *first; union block_hdr *last; struct cleanup *cleanups; struct pool *sub_pools; struct pool *sub_next; struct pool *sub_prev; struct pool *parent; char *free_first_avail; const char *tag; }; pool *permanent_pool = NULL; pool *global_config_pool = NULL; /* Each pool structure is allocated in the start of it's own first block, * so there is a need to know how many bytes that is (once properly * aligned). */ #define POOL_HDR_CLICKS (1 + ((sizeof(struct pool) - 1) / CLICK_SZ)) #define POOL_HDR_BYTES (POOL_HDR_CLICKS * CLICK_SZ) /* walk all pools, starting with top level permanent pool, displaying a * tree. */ static long __walk_pools(pool *p, int level, void (*debugf)(const char *, ...)) { char _levelpad[80] = ""; long total = 0; if (!p) return 0; if (level > 1) { memset(_levelpad, ' ', sizeof(_levelpad)-1); if ((level - 1) * 3 >= sizeof(_levelpad)) _levelpad[sizeof(_levelpad)-1] = 0; else _levelpad[(level - 1) * 3] = '\0'; } for (; p; p = p->sub_next) { total += bytes_in_block_list(p->first); if (level == 0) debugf("%s (%lu bytes)", p->tag ? p->tag : "[none]", bytes_in_block_list(p->first)); else debugf("%s\\- %s (%lu bytes)", _levelpad, p->tag ? p->tag : "[none]", bytes_in_block_list(p->first)); /* Recurse */ if (p->sub_pools) total += __walk_pools(p->sub_pools, level+1, debugf); } return total; } static void debug_pool_info(void (*debugf)(const char *, ...)) { if (block_freelist) debugf("Free block list: %lu bytes", bytes_in_block_list(block_freelist)); else debugf("Free block list: EMPTY"); debugf("%u count blocks allocated", stat_malloc); debugf("%u count blocks reused", stat_freehit); } void pr_pool_debug_memory(void (*debugf)(const char *, ...)) { debugf("Memory pool allocation:"); debugf("Total %lu bytes allocated", __walk_pools(permanent_pool, 0, debugf)); debug_pool_info(debugf); } void pr_pool_tag(pool *p, const char *tag) { if (!p || !tag) return; p->tag = tag; } /* Release the entire free block list */ static void pool_release_free_block_list(void) { union block_hdr *blok,*next; pr_alarms_block(); blok = block_freelist; if (blok) { for (next = blok->h.next; next; blok = next, next = blok->h.next) free(blok); } block_freelist = NULL; pr_alarms_unblock(); } struct pool *make_sub_pool(struct pool *p) { union block_hdr *blok; pool *new_pool; pr_alarms_block(); blok = new_block(0, FALSE); new_pool = (pool *) blok->h.first_avail; blok->h.first_avail += POOL_HDR_BYTES; memset(new_pool, 0, sizeof(struct pool)); new_pool->free_first_avail = blok->h.first_avail; new_pool->first = new_pool->last = blok; if (p) { new_pool->parent = p; new_pool->sub_next = p->sub_pools; if (new_pool->sub_next) new_pool->sub_next->sub_prev = new_pool; p->sub_pools = new_pool; } pr_alarms_unblock(); return new_pool; } struct pool *pr_pool_create_sz(struct pool *p, int sz) { union block_hdr *blok; pool *new_pool; pr_alarms_block(); blok = new_block(sz, TRUE); new_pool = (pool *) blok->h.first_avail; blok->h.first_avail += POOL_HDR_BYTES; memset(new_pool, 0, sizeof(struct pool)); new_pool->free_first_avail = blok->h.first_avail; new_pool->first = new_pool->last = blok; if (p) { new_pool->parent = p; new_pool->sub_next = p->sub_pools; if (new_pool->sub_next) new_pool->sub_next->sub_prev = new_pool; p->sub_pools = new_pool; } pr_alarms_unblock(); return new_pool; } /* Initialize the pool system by creating the base permanent_pool. */ void init_pools(void) { if (!permanent_pool) permanent_pool = make_sub_pool(NULL); pr_pool_tag(permanent_pool, "permanent_pool"); } void free_pools(void) { destroy_pool(permanent_pool); permanent_pool = NULL; pool_release_free_block_list(); } static void clear_pool(struct pool *p) { /* Sanity check. */ if (!p) return; pr_alarms_block(); /* Run through any cleanups. */ run_cleanups(p->cleanups); p->cleanups = NULL; /* Destroy subpools. */ while (p->sub_pools) destroy_pool(p->sub_pools); p->sub_pools = NULL; free_blocks(p->first->h.next); p->first->h.next = NULL; p->last = p->first; p->first->h.first_avail = p->free_first_avail; pr_alarms_unblock(); } void destroy_pool(pool *p) { if (p == NULL) return; pr_alarms_block(); if (p->parent) { if (p->parent->sub_pools == p) p->parent->sub_pools = p->sub_next; if (p->sub_prev) p->sub_prev->sub_next = p->sub_next; if (p->sub_next) p->sub_next->sub_prev = p->sub_prev; } clear_pool(p); free_blocks(p->first); pr_alarms_unblock(); } #if 0 /* NOTE: not used at the moment */ static long bytes_in_pool(pool *p) { return bytes_in_block_list(p->first); } static long bytes_in_free_blocks(void) { return bytes_in_block_list(block_freelist); } #endif /* Allocation interface... */ static void *alloc_pool(struct pool *p, int reqsz, int exact) { /* Round up requested size to an even number of aligned units */ int nclicks = 1 + ((reqsz - 1) / CLICK_SZ); int sz = nclicks * CLICK_SZ; /* For performance, see if space is available in the most recently * allocated block. */ union block_hdr *blok = p->last; char *first_avail = blok->h.first_avail; char *new_first_avail; if (reqsz <= 0) return NULL; new_first_avail = first_avail + sz; if (new_first_avail <= blok->h.endp) { blok->h.first_avail = new_first_avail; return (void *) first_avail; } /* Need a new one that's big enough */ pr_alarms_block(); blok = new_block(sz, exact); p->last->h.next = blok; p->last = blok; first_avail = blok->h.first_avail; blok->h.first_avail += sz; pr_alarms_unblock(); return (void *) first_avail; } void *palloc(struct pool *p, int sz) { return alloc_pool(p, sz, FALSE); } void *pallocsz(struct pool *p, int sz) { return alloc_pool(p, sz, TRUE); } void *pcalloc(struct pool *p, int sz) { void *res = palloc(p, sz); memset(res, '\0', sz); return res; } void *pcallocsz(struct pool *p, int sz) { void *res = pallocsz(p, sz); memset(res, '\0', sz); return res; } char *pstrdup(struct pool *p, const char *s) { char *res; size_t len; if (!s) return NULL; len = strlen(s) + 1; res = palloc(p, len); sstrncpy(res, s, len); return res; } char *pstrndup(struct pool *p, const char *s, int n) { char *res; if (!s) return NULL; res = palloc(p, n + 1); sstrncpy(res, s, n + 1); return res; } char *pdircat(pool *p, ...) { char *argp, *res; char last; int len = 0, count = 0; va_list dummy; va_start(dummy, p); last = 0; while ((res = va_arg(dummy, char *)) != NULL) { /* If the first argument is "", we have to account for a leading / * which must be added. */ if (!count++ && !*res) len++; else if (last && last != '/' && *res != '/') len++; else if (last && last == '/' && *res == '/') len--; len += strlen(res); last = (*res ? res[strlen(res) - 1] : 0); } va_end(dummy); res = (char *) pcalloc(p, len + 1); va_start(dummy, p); last = 0; while ((argp = va_arg(dummy, char *)) != NULL) { if (last && last == '/' && *argp == '/') argp++; else if (last && last != '/' && *argp != '/') sstrcat(res, "/", len + 1); sstrcat(res, argp, len + 1); last = (*res ? res[strlen(res) - 1] : 0); } va_end(dummy); return res; } char *pstrcat(pool *p, ...) { char *argp, *res; size_t len = 0; va_list dummy; va_start(dummy, p); while ((res = va_arg(dummy, char *)) != NULL) len += strlen(res); va_end(dummy); res = (char *) pcalloc(p, len + 1); va_start(dummy, p); while ((argp = va_arg(dummy, char *)) != NULL) sstrcat(res, argp, len + 1); va_end(dummy); return res; } /* * Array functions */ array_header *make_array(pool *p, int nelts, int elt_size) { array_header *res = (array_header *) palloc(p, sizeof(array_header)); if (nelts < 1) nelts = 1; res->elts = pcalloc(p, nelts * elt_size); res->pool = p; res->elt_size = elt_size; res->nelts = 0; res->nalloc = nelts; return res; } void *push_array(array_header *arr) { if (arr->nelts == arr->nalloc) { char *new_data = pcalloc(arr->pool, arr->nalloc * arr->elt_size * 2); memcpy(new_data, arr->elts, arr->nalloc * arr->elt_size); arr->elts = new_data; arr->nalloc *= 2; } ++arr->nelts; return ((char *)arr->elts) + (arr->elt_size * (arr->nelts - 1)); } void array_cat(array_header *dst, const array_header *src) { int elt_size = dst->elt_size; if (dst->nelts + src->nelts > dst->nalloc) { int new_size = dst->nalloc * 2; char *new_data; if (new_size == 0) ++new_size; while (dst->nelts + src->nelts > new_size) new_size *= 2; new_data = pcalloc(dst->pool, elt_size * new_size); memcpy(new_data, dst->elts, dst->nalloc * elt_size); dst->elts = new_data; dst->nalloc = new_size; } memcpy(((char *)dst->elts) + dst->nelts * elt_size, (char *)src->elts, elt_size * src->nelts); dst->nelts += src->nelts; } array_header *copy_array(pool *p, const array_header *arr) { array_header *res = make_array(p,arr->nalloc,arr->elt_size); memcpy(res->elts, arr->elts, arr->elt_size * arr->nelts); res->nelts = arr->nelts; return res; } /* copy an array that is assumed to consist solely of strings */ array_header *copy_array_str(pool *p, const array_header *arr) { array_header *res = copy_array(p,arr); int i; for (i = 0; i < arr->nelts; i++) ((char **)res->elts)[i] = pstrdup(p, ((char **)res->elts)[i]); return res; } array_header *copy_array_hdr(pool *p, const array_header *arr) { array_header *res = (array_header *)palloc(p,sizeof(array_header)); res->elts = arr->elts; res->pool = p; res->elt_size = arr->elt_size; res->nelts = arr->nelts; res->nalloc = arr->nelts; /* Force overflow on push */ return res; } array_header *append_arrays(pool *p, const array_header *first, const array_header *second) { array_header *res = copy_array_hdr(p,first); array_cat(res,second); return res; } /* * Generic cleanups */ typedef struct cleanup { void *data; void (*plain_cleanup_cb)(void *); void (*child_cleanup_cb)(void *); struct cleanup *next; } cleanup_t; void register_cleanup(pool *p, void *data, void (*plain_cleanup_cb)(void*), void (*child_cleanup_cb)(void *)) { cleanup_t *c = pcalloc(p, sizeof(cleanup_t)); c->data = data; c->plain_cleanup_cb = plain_cleanup_cb; c->child_cleanup_cb = child_cleanup_cb; /* Add this cleanup to the given pool's list of cleanups. */ c->next = p->cleanups; p->cleanups = c; } void unregister_cleanup(pool *p, void *data, void (*cleanup_cb)(void *)) { cleanup_t *c = p->cleanups; cleanup_t **lastp = &p->cleanups; while (c) { if (c->data == data && c->plain_cleanup_cb == cleanup_cb) { /* Remove the given cleanup by pointing the previous next pointer to * the matching cleanup's next pointer. */ *lastp = c->next; break; } lastp = &c->next; c = c->next; } } /* NOTE: unused. */ #if 0 void run_cleanup(pool *p, void *data, void (*cleanup_cb)(void *)) { pr_alarms_block(); /* Run the given cleanup callback. */ (*cleanup_cb)(data); /* Remove it. */ unregister_cleanup(p, data, cleanup_cb); pr_alarms_unblock(); } #endif static void run_cleanups(cleanup_t *c) { while (c) { (*c->plain_cleanup_cb)(c->data); c = c->next; } } /* NOTE: these cleanup routines are currently unused. * 2002-07-24 */ #if 0 static void run_child_cleanups(cleanup_t *c) { while (c) { (*c->child_cleanup_cb)(c->data); c = c ->next; } } static void cleanup_pool_for_exec(pool *p) { run_child_cleanups(p->cleanups); p->cleanups = NULL; for (p = p->sub_pools; p; p = p->sub_next) cleanup_pool_for_exec(p); } void cleanup_for_exec(void) { pr_alarms_block(); cleanup_pool_for_exec(permanent_pool); pr_alarms_unblock(); } #endif /* * Files and file descriptors */ static void fd_cleanup_cb(void *fdv) { close((int)fdv); } static void register_fd_cleanups(pool *p, int fd) { register_cleanup(p, (void *)fd, fd_cleanup_cb, fd_cleanup_cb); } int popenf(pool *p, const char *name, int flags, int mode) { int fd; pr_alarms_block(); if ((fd = open(name, flags, mode)) >= 0) register_fd_cleanups(p, fd); pr_alarms_unblock(); return fd; } int pclosef(pool *p, int fd) { int res; pr_alarms_block(); res = close(fd); unregister_cleanup(p, (void *)fd, fd_cleanup_cb); pr_alarms_unblock(); return res; } /* Sep. plain and child cleanups for FILE *, since fclose() flushes * the stream */ static void file_cleanup_cb(void *fpv) { fclose((FILE *)fpv); } static void file_child_cleanup_cb(void *fpv) { close(fileno((FILE *) fpv)); } void register_file_cleanups(pool *p, FILE *fp) { register_cleanup(p, (void *)fp, file_cleanup_cb, file_child_cleanup_cb); } FILE *pfopen(pool *p, const char *name, const char *mode) { FILE *fd = NULL; int base_flag, desc; pr_alarms_block(); if (*mode == 'a') { base_flag = (*(mode+1) == '+') ? O_RDWR : O_WRONLY; desc = open(name, base_flag|O_APPEND|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); if (desc >= 0) fd = fdopen(desc, mode); } else fd = fopen(name, mode); if (fd) register_file_cleanups(p, fd); pr_alarms_unblock(); return fd; } FILE *pfdopen(pool *p, int fd, const char *mode) { FILE *f; pr_alarms_block(); if ((f = fdopen(fd, mode)) != NULL) register_file_cleanups(p, f); pr_alarms_unblock(); return f; } int pfclose(pool *p, FILE *fd) { int res; pr_alarms_block(); res = fclose(fd); unregister_cleanup(p, (void *) fd, file_cleanup_cb); pr_alarms_unblock(); return res; }