/* * Copyright (C) 1996-2023 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ /** \verbatim * Rationale: * ---------- * * Here is how one would Comm::Write an object without MemBuffer: * * { * -- allocate: * buf = malloc(big_enough); * * -- "pack": * snprintf object(s) piece-by-piece constantly checking for overflows * and maintaining (buf+offset); * ... * * -- write * Comm::Write(buf, free, ...); * } * * The whole "packing" idea is quite messy: We are given a buffer of fixed * size and we have to check all the time that we still fit. Sounds logical. * * However, what happens if we have more data? If we are lucky to stop before * we overrun any buffers, we still may have garbage (e.g. half of ETag) in * the buffer. * * MemBuffer: * ---------- * * MemBuffer is a memory-resident buffer with printf()-like interface. It * hides all offset handling and overflow checking. Moreover, it has a * built-in control that no partial data has been written. * * MemBuffer is designed to handle relatively small data. It starts with a * small buffer of configurable size to avoid allocating huge buffers all the * time. MemBuffer doubles the buffer when needed. It assert()s that it will * not grow larger than a configurable limit. MemBuffer has virtually no * overhead (and can even reduce memory consumption) compared to old * "packing" approach. * * MemBuffer eliminates both "packing" mess and truncated data: * * { * -- setup * MemBuf buf; * * -- required init with optional size tuning (see #defines for defaults) * buf.init(initial-size, absolute-maximum); * * -- "pack" (no need to handle offsets or check for overflows) * buf.Printf(...); * ... * * -- write * Comm::Write(fd, buf, callback); * * -- *iff* you did not give the buffer away, free it yourself * -- buf.clean(); * } \endverbatim */ #include "squid.h" #include "mem/forward.h" #include "MemBuf.h" /* local constants */ /* default values for buffer sizes, used by memBufDefInit */ #define MEM_BUF_INIT_SIZE (2*1024) #define MEM_BUF_MAX_SIZE (2*1000*1024*1024) CBDATA_CLASS_INIT(MemBuf); /** init with defaults */ void MemBuf::init() { init(MEM_BUF_INIT_SIZE, MEM_BUF_MAX_SIZE); } /** init with specific sizes */ void MemBuf::init(mb_size_t szInit, mb_size_t szMax) { assert(szInit > 0 && szMax > 0); buf = nullptr; size = 0; max_capacity = szMax; capacity = 0; stolen = 0; grow(szInit); terminate(); } /** * cleans the mb; last function to call if you do not give .buf away with * memBufFreeFunc */ void MemBuf::clean() { if (isNull()) { // nothing to do } else { assert(buf); assert(!stolen); /* not frozen */ memFreeBuf(capacity, buf); buf = nullptr; size = capacity = max_capacity = 0; } } /** * Cleans the buffer without changing its capacity * if called with a Null buffer, calls memBufDefInit() */ void MemBuf::reset() { if (isNull()) { init(); } else { assert(!stolen); /* not frozen */ /* reset */ memset(buf, 0, capacity); size = 0; } } /** * Unfortunate hack to test if the buffer has been Init()ialized */ int MemBuf::isNull() const { if (!buf && !max_capacity && !capacity && !size) return 1; /* is null (not initialized) */ assert(buf && max_capacity && capacity); /* paranoid */ return 0; } mb_size_t MemBuf::spaceSize() const { const mb_size_t terminatedSize = size + 1; return (terminatedSize < capacity) ? capacity - terminatedSize : 0; } mb_size_t MemBuf::potentialSpaceSize() const { const mb_size_t terminatedSize = size + 1; return (terminatedSize < max_capacity) ? max_capacity - terminatedSize : 0; } /// removes sz bytes and "packs" by moving content left void MemBuf::consume(mb_size_t shiftSize) { const mb_size_t cSize = contentSize(); assert(0 <= shiftSize && shiftSize <= cSize); assert(!stolen); /* not frozen */ if (shiftSize > 0) { if (shiftSize < cSize) memmove(buf, buf + shiftSize, cSize - shiftSize); size -= shiftSize; terminate(); } } /// removes all whitespace prefix bytes and "packs" by moving content left void MemBuf::consumeWhitespacePrefix() { if (contentSize() > 0) { const char *end = buf + contentSize(); const char *p = buf; for (; p 0) consume(p-buf); } } // removes last tailSize bytes void MemBuf::truncate(mb_size_t tailSize) { const mb_size_t cSize = contentSize(); assert(0 <= tailSize && tailSize <= cSize); assert(!stolen); /* not frozen */ size -= tailSize; } /** * calls memcpy, appends exactly size bytes, * extends buffer or creates buffer if needed. */ void MemBuf::append(const char *newContent, int sz) { assert(sz >= 0); assert(buf || (0==capacity && 0==size)); assert(!stolen); /* not frozen */ if (sz > 0) { if (size + sz + 1 > capacity) grow(size + sz + 1); assert(size + sz <= capacity); /* paranoid */ memcpy(space(), newContent, sz); appended(sz); } } /// updates content size after external append void MemBuf::appended(mb_size_t sz) { assert(size + sz <= capacity); size += sz; terminate(); } /** * Null-terminate in case we are used as a string. * Extra octet is not counted in the content size (or space size) * \note XXX: but the extra octet is counted when growth decisions are made! * This will cause the buffer to grow when spaceSize() == 1 on append, * which will assert() if the buffer cannot grow any more. */ void MemBuf::terminate() { assert(size < capacity); *space() = '\0'; } /** * vappendf for other printf()'s to use; calls vsnprintf, extends buf if needed */ void MemBuf::vappendf(const char *fmt, va_list vargs) { int sz = 0; assert(fmt); assert(buf); assert(!stolen); /* not frozen */ /* assert in Grow should quit first, but we do not want to have a scary infinite loop */ while (capacity <= max_capacity) { mb_size_t free_space = capacity - size; /* put as much as we can */ /* Fix of bug 753r. The value of vargs is undefined * after vsnprintf() returns. Make a copy of vargs * in case we loop around and call vsnprintf() again. */ va_list ap; va_copy(ap,vargs); sz = vsnprintf(buf + size, free_space, fmt, ap); va_end(ap); /* check for possible overflow */ /* snprintf on Linuz returns -1 on overflows */ /* snprintf on FreeBSD returns at least free_space on overflows */ if (sz < 0 || sz >= free_space) grow(capacity + 1); else break; } size += sz; /* on Linux and FreeBSD, '\0' is not counted in return value */ /* on XXX it might be counted */ /* check that '\0' is appended and not counted */ if (!size || buf[size - 1]) { assert(!buf[size]); } else { --size; } } /** * Important: * calling this function "freezes" mb, * do not _update_ mb after that in any way * (you still can read-access .buf and .size) * \retval free() function to be used. */ FREE * MemBuf::freeFunc() { FREE *ff; assert(buf); assert(!stolen); /* not frozen */ ff = memFreeBufFunc((size_t) capacity); stolen = 1; /* freeze */ return ff; } /** * Grows (doubles) internal buffer to satisfy required minimal capacity */ void MemBuf::grow(mb_size_t min_cap) { size_t new_cap; size_t buf_cap; assert(!stolen); assert(capacity < min_cap); /* determine next capacity */ if (min_cap > 64 * 1024) { new_cap = 64 * 1024; while (new_cap < (size_t) min_cap) new_cap += 64 * 1024; /* increase in reasonable steps */ } else { new_cap = (size_t) min_cap; } /* last chance to fit before we assert(!overflow) */ if (new_cap > (size_t) max_capacity) new_cap = (size_t) max_capacity; assert(new_cap <= (size_t) max_capacity); /* no overflow */ assert(new_cap > (size_t) capacity); /* progress */ buf_cap = (size_t) capacity; buf = (char *)memReallocBuf(buf, new_cap, &buf_cap); /* done */ capacity = (mb_size_t) buf_cap; } /* Reports */ /** * Puts report on MemBuf _module_ usage into mb */ void memBufReport(MemBuf * mb) { assert(mb); mb->appendf("memBufReport is not yet implemented @?@\n"); }