aboutsummaryrefslogblamecommitdiff
path: root/src/liblzma/common/memory_limiter.c
blob: a2a0cbdc9215f5a7b99efc70d92a79d894d82365 (plain) (tree)
1
2
3

                                                                               
                                























                                                                               









                                                                          










                                                       




                                                                         
                    

                              
                     









                                                                              





                                  


                                                                    




                                                           
                                 



                                           



























                                                   
                      























                                                          







                                                                   
 



                                      
 



                     



































                                                                           


                                                                  



                                                                      
                            
         
 











                                                                            
                            
         
 




                                                                          
































































                                                                         
///////////////////////////////////////////////////////////////////////////////
//
/// \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;
}