/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley 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; version 2 dated June, 1991, or (at your option) version 3 dated 29 June, 2007. 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, see . */ #include "dnsmasq.h" static struct blockdata *keyblock_free; static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced; static void add_blocks(int n) { struct blockdata *new = whine_malloc(n * sizeof(struct blockdata)); if (new) { int i; new[n-1].next = keyblock_free; keyblock_free = new; for (i = 0; i < n - 1; i++) new[i].next = &new[i+1]; blockdata_alloced += n; } } /* Preallocate some blocks, proportional to cachesize, to reduce heap fragmentation. */ void blockdata_init(void) { keyblock_free = NULL; blockdata_alloced = 0; blockdata_count = 0; blockdata_hwm = 0; /* Note that daemon->cachesize is enforced to have non-zero size if OPT_DNSSEC_VALID is set */ if (option_bool(OPT_DNSSEC_VALID)) add_blocks(daemon->cachesize); } void blockdata_report(void) { my_syslog(LOG_INFO, _("pool memory in use %zu, max %zu, allocated %zu"), blockdata_count * sizeof(struct blockdata), blockdata_hwm * sizeof(struct blockdata), blockdata_alloced * sizeof(struct blockdata)); } static struct blockdata *new_block(void) { struct blockdata *block; if (!keyblock_free) add_blocks(50); if (keyblock_free) { block = keyblock_free; keyblock_free = block->next; blockdata_count++; if (blockdata_hwm < blockdata_count) blockdata_hwm = blockdata_count; block->next = NULL; return block; } return NULL; } static struct blockdata *blockdata_alloc_real(int fd, char *data, size_t len) { struct blockdata *block, *ret = NULL; struct blockdata **prev = &ret; size_t blen; do { if (!(block = new_block())) { /* failed to alloc, free partial chain */ blockdata_free(ret); return NULL; } if ((blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len) > 0) { if (data) { memcpy(block->key, data, blen); data += blen; } else if (!read_write(fd, block->key, blen, 1)) { /* failed read free partial chain */ blockdata_free(ret); return NULL; } } len -= blen; *prev = block; prev = &block->next; } while (len != 0); return ret; } struct blockdata *blockdata_alloc(char *data, size_t len) { return blockdata_alloc_real(0, data, len); } /* Add data to the end of the block. newlen is length of new data, NOT total new length. Use blockdata_alloc(NULL, 0) to make empty block to add to. */ int blockdata_expand(struct blockdata *block, size_t oldlen, char *data, size_t newlen) { struct blockdata *b; /* find size of current final block */ for (b = block; oldlen > KEYBLOCK_LEN && b; b = b->next, oldlen -= KEYBLOCK_LEN); /* chain to short for length, something is broken */ if (oldlen > KEYBLOCK_LEN) { blockdata_free(block); return 0; } while (1) { struct blockdata *new; size_t blocksize = KEYBLOCK_LEN - oldlen; size_t size = (newlen <= blocksize) ? newlen : blocksize; if (size != 0) { memcpy(&b->key[oldlen], data, size); data += size; newlen -= size; } /* full blocks from now on. */ oldlen = 0; if (newlen == 0) break; if ((new = new_block())) { b->next = new; b = new; } else { /* failed to alloc, free partial chain */ blockdata_free(block); return 0; } } return 1; } void blockdata_free(struct blockdata *blocks) { struct blockdata *tmp; if (blocks) { for (tmp = blocks; tmp->next; tmp = tmp->next) blockdata_count--; tmp->next = keyblock_free; keyblock_free = blocks; blockdata_count--; } } /* if data == NULL, return pointer to static block of sufficient size */ void *blockdata_retrieve(struct blockdata *block, size_t len, void *data) { size_t blen; struct blockdata *b; void *new, *d; static unsigned int buff_len = 0; static unsigned char *buff = NULL; if (!data) { if (len > buff_len) { if (!(new = whine_malloc(len))) return NULL; if (buff) free(buff); buff = new; } data = buff; } for (d = data, b = block; len > 0 && b; b = b->next) { blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len; memcpy(d, b->key, blen); d += blen; len -= blen; } return data; } void blockdata_write(struct blockdata *block, size_t len, int fd) { for (; len > 0 && block; block = block->next) { size_t blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len; read_write(fd, block->key, blen, 0); len -= blen; } } struct blockdata *blockdata_read(int fd, size_t len) { return blockdata_alloc_real(fd, NULL, len); }