aboutsummaryrefslogtreecommitdiff
path: root/src/liblzma/common/memory_limiter.c
diff options
context:
space:
mode:
authorLasse Collin <lasse.collin@tukaani.org>2008-04-28 17:06:34 +0300
committerLasse Collin <lasse.collin@tukaani.org>2008-04-28 17:06:34 +0300
commit3be21fb12f4cec2cf07799e8960382f4cb375369 (patch)
treea20ea08ae1eb3237a73cc68c2f0828a57f56e381 /src/liblzma/common/memory_limiter.c
parentPrevent LZ encoder from hanging with known uncompressed (diff)
downloadxz-3be21fb12f4cec2cf07799e8960382f4cb375369.tar.xz
Fixed wrong spelling "limitter" to "limiter". This affects
liblzma's API.
Diffstat (limited to 'src/liblzma/common/memory_limiter.c')
-rw-r--r--src/liblzma/common/memory_limiter.c288
1 files changed, 288 insertions, 0 deletions
diff --git a/src/liblzma/common/memory_limiter.c b/src/liblzma/common/memory_limiter.c
new file mode 100644
index 00000000..a2a0cbdc
--- /dev/null
+++ b/src/liblzma/common/memory_limiter.c
@@ -0,0 +1,288 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file memory_limiter.c
+/// \brief Limitting memory usage
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "common.h"
+
+
+/// Rounds an unsigned integer upwards to the next multiple.
+#define my_ceil(num, multiple) \
+ ((num) + (((multiple) - ((num) % (multiple))) % (multiple)))
+
+
+/// Add approximated overhead of malloc() to size and round upwards to the
+/// next multiple of 2 * sizeof(size_t). I suppose that most malloc()
+/// implementations align small allocations this way, but the overhead
+/// varies due to several reasons (free lists, mmap() usage etc.).
+///
+/// This doesn't need to be exact at all. It's enough to take into account
+/// that there is some overhead. That way our memory usage count won't be
+/// horribly wrong if we are used to allocate lots of small memory chunks.
+#define malloc_ceil(size) \
+ my_ceil((size) + 2 * sizeof(void *), 2 * sizeof(size_t))
+
+
+typedef struct lzma_memlimit_list_s lzma_memlimit_list;
+struct lzma_memlimit_list_s {
+ lzma_memlimit_list *next;
+ void *ptr;
+ size_t size;
+};
+
+
+struct lzma_memlimit_s {
+ /// List of allocated memory chunks
+ lzma_memlimit_list *list;
+
+ /// Number of bytes currently allocated; this includes the memory
+ /// needed for the helper structures.
+ size_t used;
+
+ /// Memory usage limit
+ size_t limit;
+
+ /// Maximum amount of memory that have been or would have been needed.
+ /// That is, this is updated also if memory allocation fails, letting
+ /// the application check how much memory was tried to be allocated
+ /// in total.
+ size_t max;
+
+ /// True if lzma_memlimit_alloc() has returned NULL due to memory
+ /// usage limit.
+ bool limit_reached;
+};
+
+
+extern LZMA_API lzma_memlimit *
+lzma_memlimit_create(size_t limit)
+{
+ const size_t base_size = malloc_ceil(sizeof(lzma_memlimit));
+
+ if (limit < base_size)
+ return NULL;
+
+ lzma_memlimit *mem = malloc(sizeof(lzma_memlimit));
+
+ if (mem != NULL) {
+ mem->list = NULL;
+ mem->used = base_size;
+ mem->limit = limit;
+ mem->max = base_size;
+ mem->limit_reached = false;
+ }
+
+ return mem;
+}
+
+
+extern LZMA_API void
+lzma_memlimit_set(lzma_memlimit *mem, size_t limit)
+{
+ mem->limit = limit;
+ return;
+}
+
+
+extern LZMA_API size_t
+lzma_memlimit_get(const lzma_memlimit *mem)
+{
+ return mem->limit;
+}
+
+
+extern LZMA_API size_t
+lzma_memlimit_used(const lzma_memlimit *mem)
+{
+ return mem->used;
+}
+
+
+extern LZMA_API size_t
+lzma_memlimit_max(lzma_memlimit *mem, lzma_bool clear)
+{
+ const size_t ret = mem->max;
+
+ if (clear)
+ mem->max = mem->used;
+
+ return ret;
+}
+
+
+extern LZMA_API lzma_bool
+lzma_memlimit_reached(lzma_memlimit *mem, lzma_bool clear)
+{
+ const bool ret = mem->limit_reached;
+
+ if (clear)
+ mem->limit_reached = false;
+
+ return ret;
+}
+
+
+extern LZMA_API size_t
+lzma_memlimit_count(const lzma_memlimit *mem)
+{
+ // This is slow; we could have a counter in lzma_memlimit
+ // for fast version. I expect the primary use of this
+ // function to be limited to easy checking of memory leaks,
+ // in which this implementation is just fine.
+ size_t count = 0;
+ const lzma_memlimit_list *record = mem->list;
+
+ while (record != NULL) {
+ ++count;
+ record = record->next;
+ }
+
+ return count;
+}
+
+
+extern LZMA_API void
+lzma_memlimit_end(lzma_memlimit *mem, lzma_bool free_allocated)
+{
+ if (mem == NULL)
+ return;
+
+ lzma_memlimit_list *record = mem->list;
+ while (record != NULL) {
+ if (free_allocated)
+ free(record->ptr);
+
+ lzma_memlimit_list *tmp = record;
+ record = record->next;
+ free(tmp);
+ }
+
+ free(mem);
+
+ return;
+}
+
+
+extern LZMA_API void *
+lzma_memlimit_alloc(lzma_memlimit *mem, size_t nmemb, size_t size)
+{
+ // While liblzma always sets nmemb to one, do this multiplication
+ // to make these functions usable e.g. with zlib and libbzip2.
+ // Making sure that this doesn't overflow is up to the application.
+ size *= nmemb;
+
+ // Some malloc() implementations return NULL on malloc(0). We like
+ // to get a non-NULL value.
+ if (size == 0)
+ size = 1;
+
+ // Calculate how much memory we are going to allocate in reality.
+ const size_t total_size = malloc_ceil(size)
+ + malloc_ceil(sizeof(lzma_memlimit_list));
+
+ // Integer overflow protection for total_size and mem->used.
+ if (total_size <= size || SIZE_MAX - total_size < mem->used) {
+ mem->max = SIZE_MAX;
+ mem->limit_reached = true;
+ return NULL;
+ }
+
+ // Update the maximum memory requirement counter if needed. This
+ // is updated even if memory allocation would fail or limit would
+ // be reached.
+ if (mem->used + total_size > mem->max)
+ mem->max = mem->used + total_size;
+
+ // Check if we would stay in the memory usage limits. We need to
+ // check also that the current usage is in the limits, because
+ // the application could have decreased the limit between calls
+ // to this function.
+ if (mem->limit < mem->used || mem->limit - mem->used < total_size) {
+ mem->limit_reached = true;
+ return NULL;
+ }
+
+ // Allocate separate memory chunks for lzma_memlimit_list and the
+ // actual requested memory. Optimizing this to use only one
+ // allocation is not a good idea, because applications may want to
+ // detach lzma_extra structures that have been allocated with
+ // lzma_memlimit_alloc().
+ lzma_memlimit_list *record = malloc(sizeof(lzma_memlimit_list));
+ void *ptr = malloc(size);
+
+ if (record == NULL || ptr == NULL) {
+ free(record);
+ free(ptr);
+ return NULL;
+ }
+
+ // Add the new entry to the beginning of the list. This should be
+ // more efficient when freeing memory, because usually it is
+ // "last allocated, first freed".
+ record->next = mem->list;
+ record->ptr = ptr;
+ record->size = total_size;
+
+ mem->list = record;
+ mem->used += total_size;
+
+ return ptr;
+}
+
+
+extern LZMA_API void
+lzma_memlimit_detach(lzma_memlimit *mem, void *ptr)
+{
+ if (ptr == NULL || mem->list == NULL)
+ return;
+
+ lzma_memlimit_list *record = mem->list;
+ lzma_memlimit_list *prev = NULL;
+
+ while (record->ptr != ptr) {
+ prev = record;
+ record = record->next;
+ if (record == NULL)
+ return;
+ }
+
+ if (prev != NULL)
+ prev->next = record->next;
+ else
+ mem->list = record->next;
+
+ assert(mem->used >= record->size);
+ mem->used -= record->size;
+
+ free(record);
+
+ return;
+}
+
+
+extern LZMA_API void
+lzma_memlimit_free(lzma_memlimit *mem, void *ptr)
+{
+ if (ptr == NULL)
+ return;
+
+ lzma_memlimit_detach(mem, ptr);
+
+ free(ptr);
+
+ return;
+}