1 #ifdef HAVE_CONFIG_H
2 # include "config.h"
3 #endif
4 
5 #ifdef HAVE_VALGRIND
6 # include <valgrind.h>
7 # include <memcheck.h>
8 #endif
9 
10 #include <stdint.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <fcntl.h>
14 #include <inttypes.h>
15 
16 #ifdef _WIN32
17 # include <evil_private.h> /* mmap */
18 #else
19 # include <sys/mman.h>
20 #endif
21 
22 #include "eina_config.h"
23 #include "eina_private.h"
24 
25 #define _EINA_INTERNAL_SAFEPOINTER
26 #include "eina_safepointer.h"
27 #include "eina_mempool.h"
28 #include "eina_trash.h"
29 #include "eina_log.h"
30 #include "eina_lock.h"
31 
32 typedef struct _Eina_Memory_Header Eina_Memory_Header;
33 
34 #ifdef ERR
35 #undef ERR
36 #endif
37 #define ERR(...) EINA_LOG_DOM_ERR(_eina_sp_log_dom, __VA_ARGS__)
38 
39 #ifdef DBG
40 #undef DBG
41 #endif
42 #define DBG(...) EINA_LOG_DOM_DBG(_eina_sp_log_dom, __VA_ARGS__)
43 
44 /* Macro used to compose an Eo id */
45 #define SP_COMPOSE_PARTIAL_ID(MID_TABLE, TABLE)                         \
46   ( \
47     ((Eina_Sp_Id)(MID_TABLE & EINA_MASK_MID_TABLE_ID) << EINA_SHIFT_MID_TABLE_ID)   |  \
48     ((Eina_Sp_Id)(TABLE & EINA_MASK_TABLE_ID) << EINA_SHIFT_TABLE_ID) \
49     )
50 
51 #define SP_COMPOSE_FINAL_ID(PARTIAL_ID, ENTRY, GENERATION)     \
52     (PARTIAL_ID                                             |  \
53      ((ENTRY & EINA_MASK_ENTRY_ID) << EINA_SHIFT_ENTRY_ID)            |  \
54      ((GENERATION & EINA_MASK_GENERATIONS) << EINA_SHIFT_GENERATION))
55 
56 struct _Eina_Memory_Header
57 {
58    EINA_MAGIC;
59    size_t size;
60 };
61 
62 EAPI Eina_Memory_Table **_eina_sp_ids_tables[EINA_MAX_MID_TABLE_ID] = { NULL };
63 EAPI int _eina_sp_log_dom = -1;
64 
65 /* Spare empty table */
66 static Eina_Memory_Table *empty_table = NULL;
67 
68 // We are using a Spinlock even with the amount of syscall we do as it shouldn't
69 // take that long anyway.
70 static Eina_Spinlock sl;
71 
72 #define MEM_PAGE_SIZE 4096
73 #define SAFEPOINTER_MAGIC 0x7DEADC03
74 
75 static void *
_eina_safepointer_calloc(int number,size_t size)76 _eina_safepointer_calloc(int number, size_t size)
77 {
78 #ifdef HAVE_MMAP
79 # ifdef HAVE_VALGRIND
80    if (RUNNING_ON_VALGRIND) return calloc(number, size);
81    else
82 # endif
83      {
84         Eina_Memory_Header *header;
85         size_t newsize;
86 
87         size = size * number + sizeof (Eina_Memory_Header);
88         newsize = ((size / MEM_PAGE_SIZE) +
89                    (size % MEM_PAGE_SIZE ? 1 : 0))
90           * MEM_PAGE_SIZE;
91 
92         header = mmap(NULL, newsize, PROT_READ | PROT_WRITE,
93                       MAP_PRIVATE | MAP_ANON, -1, 0);
94         if (header == MAP_FAILED)
95           {
96              ERR("mmap of Eina_Safepointer table region failed.");
97              return NULL;
98           }
99 
100         header->size = newsize;
101         EINA_MAGIC_SET(header, SAFEPOINTER_MAGIC);
102 
103         return (void *)(header + 1);
104      }
105 #else
106    return calloc(number, size);
107 #endif
108 }
109 
110 static void
_eina_safepointer_free(void * pointer)111 _eina_safepointer_free(void *pointer)
112 {
113 #ifdef HAVE_MMAP
114 # ifdef HAVE_VALGRIND
115    if (RUNNING_ON_VALGRIND) free((void *)((uintptr_t) pointer & ~0x3));
116    else
117 # endif
118      {
119         Eina_Memory_Header *header;
120 
121         if (!pointer) return;
122 
123         header = (Eina_Memory_Header*)(pointer) - 1;
124         if (!EINA_MAGIC_CHECK(header, SAFEPOINTER_MAGIC))
125           EINA_MAGIC_FAIL(header, SAFEPOINTER_MAGIC);
126 
127         EINA_MAGIC_SET(header, 0);
128         munmap(header, header->size);
129      }
130 #else
131    free((void *)((uintptr_t) pointer & ~0x3));
132 #endif
133 }
134 
135 #ifdef EINA_DEBUG_MALLOC
136 static void
_eina_safepointer_protect(void * pointer,Eina_Bool may_not_write)137 _eina_safepointer_protect(void *pointer, Eina_Bool may_not_write)
138 {
139 #ifdef HAVE_MMAP
140 # ifdef HAVE_VALGRIND
141    if (RUNNING_ON_VALGRIND) { (void) pointer; }
142    else
143 # endif
144      {
145         Eina_Memory_Header *header;
146 
147         if (!pointer) return;
148 
149         header = (Eina_Memory_Header*)(pointer) - 1;
150         if (!EINA_MAGIC_CHECK(header, SAFEPOINTER_MAGIC))
151           EINA_MAGIC_FAIL(header, SAFEPOINTER_MAGIC);
152 
153         mprotect(header, header->size, PROT_READ | ( may_not_write ? 0 : PROT_WRITE));
154      }
155 #else
156    (void) pointer;
157 #endif
158 }
159 
160 #define   PROTECT(Ptr) _eina_safepointer_protect(Ptr, EINA_TRUE)
161 #define UNPROTECT(Ptr) _eina_safepointer_protect(Ptr, EINA_FALSE)
162 
163 #else
164 
165 #define   PROTECT(Ptr)
166 #define UNPROTECT(Ptr)
167 
168 #endif
169 
170 static Eina_Memory_Table *
_eina_safepointer_table_new(Eina_Table_Index mid_table_id,Eina_Table_Index table_id)171 _eina_safepointer_table_new(Eina_Table_Index mid_table_id,
172                             Eina_Table_Index table_id)
173 {
174    Eina_Memory_Table *table;
175 
176    if (empty_table)
177      {
178         /* Recycle the available empty table */
179         table = empty_table;
180         empty_table = NULL;
181         UNPROTECT(table);
182      }
183    else
184      {
185         table = _eina_safepointer_calloc(1, sizeof (Eina_Memory_Table));
186         if (!table)
187           {
188              ERR("Failed to allocate leaf table at [%i][%i]", mid_table_id, table_id);
189              return NULL;
190           }
191      }
192 
193    table->partial_id = SP_COMPOSE_PARTIAL_ID(mid_table_id,
194                                              table_id);
195    PROTECT(table);
196    UNPROTECT(_eina_sp_ids_tables[mid_table_id]);
197    _eina_sp_ids_tables[mid_table_id][table_id] = table;
198    PROTECT(_eina_sp_ids_tables[mid_table_id]);
199 
200    return table;
201 }
202 
203 static Eina_Memory_Table *
_eina_safepointer_table_find(void)204 _eina_safepointer_table_find(void)
205 {
206    Eina_Table_Index mid_table_id;
207 
208    for (mid_table_id = 0; mid_table_id < EINA_MAX_MID_TABLE_ID; mid_table_id++)
209      {
210         Eina_Table_Index table_id;
211 
212         if (!_eina_sp_ids_tables[mid_table_id])
213           {
214              _eina_sp_ids_tables[mid_table_id] = _eina_safepointer_calloc(EINA_MAX_TABLE_ID, sizeof (Eina_Memory_Table*));
215           }
216         if (!_eina_sp_ids_tables[mid_table_id])
217           {
218              ERR("Failed to allocate mid table at [%i]", mid_table_id);
219              return NULL;
220           }
221 
222         for (table_id = 0; table_id < EINA_MAX_TABLE_ID; table_id++)
223           {
224              Eina_Memory_Table *table;
225 
226              table = _eina_sp_ids_tables[mid_table_id][table_id];
227 
228              if (!table)
229                table = _eina_safepointer_table_new(mid_table_id, table_id);
230 
231              if (!table) return NULL;
232 
233              if (table->trash ||
234                  table->start < EINA_MAX_ENTRY_ID)
235                return table;
236           }
237      }
238 
239    return NULL;
240 }
241 
242 static Eina_Memory_Entry *
_eina_safepointer_entry_find(Eina_Memory_Table * table)243 _eina_safepointer_entry_find(Eina_Memory_Table *table)
244 {
245    Eina_Memory_Entry *entry = NULL;
246 
247    if (table->trash)
248      {
249         entry = eina_trash_pop(&table->trash);
250      }
251    else if (table->start < EINA_MAX_ENTRY_ID)
252      {
253         entry = &(table->entries[table->start]);
254         table->start++;
255      }
256    else
257      {
258         ERR("Impossible to find an entry in %" PRIxPTR ".", table->partial_id);
259      }
260 
261    return entry;
262 }
263 
264 EAPI const Eina_Safepointer *
eina_safepointer_register(const void * target)265 eina_safepointer_register(const void *target)
266 {
267    Eina_Memory_Table *table;
268    Eina_Memory_Entry *entry = NULL;
269    Eina_Sp_Id id = 0;
270    unsigned int gen;
271 
272    // We silently handle NULL
273    if (!target) return NULL;
274 
275    eina_spinlock_take(&sl);
276 
277    table = _eina_safepointer_table_find();
278    if (!table) goto no_table;
279 
280    UNPROTECT(table);
281    entry = _eina_safepointer_entry_find(table);
282    if (!entry) goto on_error;
283 
284    entry->ptr = (void*) target;
285    entry->active = 1;
286    gen = entry->generation + 1;
287    entry->generation = (gen == EINA_MAX_GENERATIONS) ? 1 : gen;
288 
289    id = SP_COMPOSE_FINAL_ID(table->partial_id,
290                             (entry - table->entries),
291                             entry->generation);
292 
293  on_error:
294    PROTECT(table);
295  no_table:
296    eina_spinlock_release(&sl);
297 
298    return (void*) id;
299 }
300 
301 EAPI void
eina_safepointer_unregister(const Eina_Safepointer * safe)302 eina_safepointer_unregister(const Eina_Safepointer *safe)
303 {
304    Eina_Memory_Table *table;
305    Eina_Memory_Entry *entry;
306    Eina_Table_Index entry_id;
307 
308    // We silently handle NULL
309    if (!safe) return ;
310 
311    entry = _eina_safepointer_entry_get(safe, &table);
312    if (!entry) return ;
313 
314    eina_spinlock_take(&sl);
315 
316    // In case of a race condition during a double free attempt
317    // The entry could have been unactivated since we did found it
318    // So check again.
319    if (!entry->active) goto on_error;
320 
321    UNPROTECT(table);
322    entry->active = 0;
323    eina_trash_push(&table->trash, entry);
324    PROTECT(table);
325 
326    entry_id = entry - table->entries;
327    if (entry_id == EINA_MAX_ENTRY_ID - 1)
328      {
329         Eina_Table_Index i;
330 
331         for (i = entry_id; i >= 0; i--)
332           {
333              if (table->entries[i].active)
334                break ;
335           }
336 
337         // No more active entry
338         // Could be speed up by tracking the
339         // number of allocated entries, but
340         // with all the syscall around, not sure
341         // it is worth it.
342         if (i == -1)
343           {
344              Eina_Table_Index mid_table_id, table_id;
345 
346              mid_table_id = (table->partial_id >> EINA_SHIFT_MID_TABLE_ID) & EINA_MASK_MID_TABLE_ID;
347              table_id = (table->partial_id >> EINA_SHIFT_TABLE_ID) & EINA_MASK_TABLE_ID;
348              UNPROTECT(_eina_sp_ids_tables[mid_table_id]);
349              _eina_sp_ids_tables[mid_table_id][table_id] = NULL;
350              PROTECT(_eina_sp_ids_tables[mid_table_id]);
351              if (!empty_table)
352                empty_table = table;
353              else
354                _eina_safepointer_free(table);
355           }
356      }
357 
358  on_error:
359    eina_spinlock_release(&sl);
360 }
361 
362 Eina_Bool
eina_safepointer_init(void)363 eina_safepointer_init(void)
364 {
365    eina_magic_string_set(SAFEPOINTER_MAGIC, "Safepointer");
366    _eina_sp_log_dom = eina_log_domain_register("eina_safepointer",
367                                                EINA_LOG_COLOR_DEFAULT);
368    if (_eina_sp_log_dom < 0)
369      {
370         EINA_LOG_ERR("Could not register log domain: eina_safepointer.");
371         return EINA_FALSE;
372      }
373 
374    eina_spinlock_new(&sl);
375 
376    DBG("entry[Size, Align] = { %zu, %u }",
377        sizeof (Eina_Memory_Entry), eina_mempool_alignof(sizeof (Eina_Memory_Entry)));
378    DBG("table[Size, Align] = { %zu, %u }",
379        sizeof (Eina_Memory_Table), eina_mempool_alignof(sizeof (Eina_Memory_Table)));
380 
381    return EINA_TRUE;
382 }
383 
384 Eina_Bool
eina_safepointer_shutdown(void)385 eina_safepointer_shutdown(void)
386 {
387    eina_spinlock_free(&sl);
388 
389    return EINA_TRUE;
390 }
391