1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
|
/*
* This file is part of the Green End SFTP Server.
* Copyright (C) 2007, 2011, 2014 Richard Kettlewell
*
* 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
*/
/** @file alloc.c @brief Allocator implementation */
#include "sftpserver.h"
#include "alloc.h"
#include "utils.h"
#include "debug.h"
#include <stdlib.h>
#include <assert.h>
#include <string.h>
union block;
/** @brief One chunk in an allocator
*
* A chunk is a contiguous region of memory, divided into @ref block objects,
* supporting efficient allocation.
*/
struct chunk {
/** @brief Next chunk */
struct chunk *next;
/** @brief Pointer to next allocatable block */
union block *ptr;
/** @brief Number of blocks left in this chunk */
size_t left;
/** @brief Padding
*
* size_t will usually have the same size as a pointer; by chucking an extra
* one in we become 4 * the size of a pointer, which is much more likely to
* be a power of 2 than 3 *.
*/
size_t spare;
};
/** @brief Alignment block for an allocator
*/
union block {
int i;
long l;
float f;
double d;
void *vp;
double *ip;
int (*fnp)(void);
struct chunk c;
};
/** @brief Default number of blocks in a new chunk
*
* A chunk may be bigger than this if a large allocation was requested.
*/
#define NBLOCKS 512
struct allocator *sftp_alloc_init(struct allocator *a) {
a->chunks = 0;
return a;
}
/** @brief Convert @p nbytes to a block count
* @param nbytes Number of bytes
* @return Number of blocks necessary to contain @p nbytes
*/
static inline size_t blocks(size_t nbytes) {
return nbytes / sizeof(union block) + !!(nbytes % sizeof(union block));
}
void *sftp_alloc(struct allocator *a, size_t n) {
/* calculate number of blocks */
const size_t m = blocks(n);
struct chunk *c;
if(!m)
return 0;
assert(a != 0);
assert(m != SIZE_MAX); /* ...and so m+1 > 0 */
/* See if there's enough room */
if(!(c = a->chunks) || c->left < m) {
/* Make sure we allocate enough space */
const size_t cs = m >= NBLOCKS ? m + 1 : NBLOCKS;
/* sftp_xcalloc -> calloc which 0-fills */
union block *nb;
nb = sftp_xcalloc(cs, sizeof(union block));
c = &nb->c;
c->next = a->chunks;
c->ptr = nb + 1;
c->left = cs - 1;
a->chunks = c;
}
assert(m <= c->left);
/* We always return 0-filled memory. In this case we fill by block, which is
* guaranteed to be at least enough (compare below). */
sftp_memset(c->ptr, 0, m * sizeof(union block));
c->left -= m;
c->ptr += m;
return c->ptr - m;
}
void *sftp_alloc_more(struct allocator *a, void *ptr, size_t oldn,
size_t newn) {
const size_t oldm = blocks(oldn), newm = blocks(newn);
void *newptr;
if(ptr) {
assert(a->chunks != 0);
D(("ptr=%p oldm=%zu a->chunks->ptr=%p blocksize=%zu", ptr, oldm,
a->chunks->ptr, sizeof(union block)));
if((union block *)ptr + oldm == a->chunks->ptr) {
/* ptr is the most recently allocated block. We could do better and
* search all the chunks for it. */
if(newm <= oldm) {
/* We can always shrink. We capture the no-change case here too. */
a->chunks->ptr -= oldm - newm;
a->chunks->left += oldm - newm;
return ptr;
} else if(a->chunks->left >= newm - oldm) {
/* There is space to expand */
a->chunks->ptr += newm - oldm;
a->chunks->left -= newm - oldm;
/* 0-fill the new space. Note that we do this in byte terms (compare
* above), to deal with the case where the allocation shrinks by (say)
* a single non-zero byte but then expands again. */
sftp_memset((char *)ptr + oldn, 0, newn - oldn);
return ptr;
}
/* If we get here then we are expanding but there is not enough space to
* do so in the same chunk */
} else if(newm == oldm) {
/* This is not the most recently block but we're not changing its size
* anyway */
return ptr;
}
/* We have no choice but to allocate new space */
newptr = sftp_alloc(a, newn);
sftp_memcpy(newptr, ptr, oldn);
return newptr;
} else
/* There was no old allocation, just create a new one the easy way */
return sftp_alloc(a, newn);
}
void sftp_alloc_destroy(struct allocator *a) {
struct chunk *c, *d;
c = a->chunks;
while((d = c)) {
c = c->next;
free(d);
}
a->chunks = 0;
}
/*
Local Variables:
c-basic-offset:2
comment-column:40
fill-column:79
indent-tabs-mode:nil
End:
*/
|