]> icculus.org git repositories - icculus/xz.git/blob - src/liblzma/common/memory_limitter.c
Imported to git.
[icculus/xz.git] / src / liblzma / common / memory_limitter.c
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 /// \file       memory_limitter.c
4 /// \brief      Limitting memory usage
5 //
6 //  Copyright (C) 2007 Lasse Collin
7 //
8 //  This library is free software; you can redistribute it and/or
9 //  modify it under the terms of the GNU Lesser General Public
10 //  License as published by the Free Software Foundation; either
11 //  version 2.1 of the License, or (at your option) any later version.
12 //
13 //  This library is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 //  Lesser General Public License for more details.
17 //
18 ///////////////////////////////////////////////////////////////////////////////
19
20 #include "common.h"
21
22
23 /// Rounds an unsigned integer upwards to the next multiple.
24 #define my_ceil(num, multiple) \
25         ((num) + (((multiple) - ((num) % (multiple))) % (multiple)))
26
27
28 /// Rounds upwards to the next multiple of 2 * sizeof(void*).
29 /// malloc() tends to align allocations this way.
30 #define malloc_ceil(num) my_ceil(num, 2 * sizeof(void *))
31
32
33 typedef struct lzma_memlimit_list_s lzma_memlimit_list;
34 struct lzma_memlimit_list_s {
35         lzma_memlimit_list *next;
36         void *ptr;
37         size_t size;
38 };
39
40
41 struct lzma_memlimit_s {
42         size_t used;
43         size_t limit;
44         lzma_memlimit_list *list;
45 };
46
47
48 extern LZMA_API lzma_memlimit *
49 lzma_memlimit_create(size_t limit)
50 {
51         if (limit < sizeof(lzma_memlimit))
52                 return NULL;
53
54         lzma_memlimit *mem = malloc(sizeof(lzma_memlimit));
55
56         if (mem != NULL) {
57                 mem->used = sizeof(lzma_memlimit);
58                 mem->limit = limit;
59                 mem->list = NULL;
60         }
61
62         return mem;
63 }
64
65
66 extern LZMA_API void
67 lzma_memlimit_set(lzma_memlimit *mem, size_t limit)
68 {
69         mem->limit = limit;
70         return;
71 }
72
73
74 extern LZMA_API size_t
75 lzma_memlimit_get(const lzma_memlimit *mem)
76 {
77         return mem->limit;
78 }
79
80
81 extern LZMA_API size_t
82 lzma_memlimit_used(const lzma_memlimit *mem)
83 {
84         return mem->used;
85 }
86
87
88 extern LZMA_API void
89 lzma_memlimit_end(lzma_memlimit *mem, lzma_bool free_allocated)
90 {
91         if (mem == NULL)
92                 return;
93
94         lzma_memlimit_list *record = mem->list;
95         while (record != NULL) {
96                 if (free_allocated)
97                         free(record->ptr);
98
99                 lzma_memlimit_list *tmp = record;
100                 record = record->next;
101                 free(tmp);
102         }
103
104         free(mem);
105
106         return;
107 }
108
109
110 extern LZMA_API void *
111 lzma_memlimit_alloc(lzma_memlimit *mem, size_t nmemb, size_t size)
112 {
113         // While liblzma always sets nmemb to one, do this multiplication
114         // to make these functions usable e.g. with zlib and libbzip2.
115         // Making sure that this doesn't overflow is up to the application.
116         size *= nmemb;
117
118         // Some malloc() implementations return NULL on malloc(0). We like
119         // to get a non-NULL value.
120         if (size == 0)
121                 size = 1;
122
123         // Calculate how much memory we are going to allocate in reality.
124         // TODO: We should add some rough estimate how much malloc() needs
125         // for its internal structures.
126         const size_t total_size = malloc_ceil(size)
127                         + malloc_ceil(sizeof(lzma_memlimit_list));
128
129         // Integer overflow protection
130         if (SIZE_MAX - size <= total_size)
131                 return NULL;
132
133         if (mem->limit < mem->used || mem->limit - mem->used < total_size)
134                 return NULL;
135
136         lzma_memlimit_list *record = malloc(sizeof(lzma_memlimit_list));
137         void *ptr = malloc(size);
138
139         if (record == NULL || ptr == NULL) {
140                 free(record);
141                 free(ptr);
142                 return NULL;
143         }
144
145         // Add the new entry to the beginning of the list. This should be
146         // more efficient when freeing memory, because usually it is
147         // "last allocated, first freed".
148         record->next = mem->list;
149         record->ptr = ptr;
150         record->size = total_size;
151
152         mem->list = record;
153         mem->used += total_size;
154
155         return ptr;
156 }
157
158
159 extern LZMA_API void
160 lzma_memlimit_detach(lzma_memlimit *mem, void *ptr)
161 {
162         if (ptr == NULL || mem->list == NULL)
163                 return;
164
165         lzma_memlimit_list *record = mem->list;
166         lzma_memlimit_list *prev = NULL;
167
168         while (record->ptr != ptr) {
169                 prev = record;
170                 record = record->next;
171                 if (record == NULL)
172                         return;
173         }
174
175         if (prev != NULL)
176                 prev->next = record->next;
177         else
178                 mem->list = record->next;
179
180         assert(mem->used >= record->size);
181         mem->used -= record->size;
182
183         free(record);
184
185         return;
186 }
187
188
189 extern LZMA_API void
190 lzma_memlimit_free(lzma_memlimit *mem, void *ptr)
191 {
192         if (ptr == NULL)
193                 return;
194
195         lzma_memlimit_detach(mem, ptr);
196
197         free(ptr);
198
199         return;
200 }