chiark / gitweb /
machined: Move image discovery logic into src/shared, so that we can make use of...
[elogind.git] / src / journal / mmap-cache.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   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   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <stdlib.h>
24 #include <sys/mman.h>
25 #include <string.h>
26
27 #include "hashmap.h"
28 #include "list.h"
29 #include "log.h"
30 #include "util.h"
31 #include "macro.h"
32 #include "mmap-cache.h"
33
34 typedef struct Window Window;
35 typedef struct Context Context;
36 typedef struct FileDescriptor FileDescriptor;
37
38 struct Window {
39         MMapCache *cache;
40
41         bool keep_always;
42         bool in_unused;
43
44         int prot;
45         void *ptr;
46         uint64_t offset;
47         size_t size;
48
49         FileDescriptor *fd;
50
51         LIST_FIELDS(Window, by_fd);
52         LIST_FIELDS(Window, unused);
53
54         LIST_HEAD(Context, contexts);
55 };
56
57 struct Context {
58         MMapCache *cache;
59         unsigned id;
60         Window *window;
61
62         LIST_FIELDS(Context, by_window);
63 };
64
65 struct FileDescriptor {
66         MMapCache *cache;
67         int fd;
68         LIST_HEAD(Window, windows);
69 };
70
71 struct MMapCache {
72         int n_ref;
73         unsigned n_windows;
74
75         unsigned n_hit, n_missed;
76
77
78         Hashmap *fds;
79         Context *contexts[MMAP_CACHE_MAX_CONTEXTS];
80
81         LIST_HEAD(Window, unused);
82         Window *last_unused;
83 };
84
85 #define WINDOWS_MIN 64
86
87 #ifdef ENABLE_DEBUG_MMAP_CACHE
88 /* Tiny windows increase mmap activity and the chance of exposing unsafe use. */
89 # define WINDOW_SIZE (page_size())
90 #else
91 # define WINDOW_SIZE (8ULL*1024ULL*1024ULL)
92 #endif
93
94 MMapCache* mmap_cache_new(void) {
95         MMapCache *m;
96
97         m = new0(MMapCache, 1);
98         if (!m)
99                 return NULL;
100
101         m->n_ref = 1;
102         return m;
103 }
104
105 MMapCache* mmap_cache_ref(MMapCache *m) {
106         assert(m);
107         assert(m->n_ref > 0);
108
109         m->n_ref ++;
110         return m;
111 }
112
113 static void window_unlink(Window *w) {
114         Context *c;
115
116         assert(w);
117
118         if (w->ptr)
119                 munmap(w->ptr, w->size);
120
121         if (w->fd)
122                 LIST_REMOVE(by_fd, w->fd->windows, w);
123
124         if (w->in_unused) {
125                 if (w->cache->last_unused == w)
126                         w->cache->last_unused = w->unused_prev;
127
128                 LIST_REMOVE(unused, w->cache->unused, w);
129         }
130
131         LIST_FOREACH(by_window, c, w->contexts) {
132                 assert(c->window == w);
133                 c->window = NULL;
134         }
135 }
136
137 static void window_free(Window *w) {
138         assert(w);
139
140         window_unlink(w);
141         w->cache->n_windows--;
142         free(w);
143 }
144
145 _pure_ static bool window_matches(Window *w, int fd, int prot, uint64_t offset, size_t size) {
146         assert(w);
147         assert(fd >= 0);
148         assert(size > 0);
149
150         return
151                 w->fd &&
152                 fd == w->fd->fd &&
153                 prot == w->prot &&
154                 offset >= w->offset &&
155                 offset + size <= w->offset + w->size;
156 }
157
158 static Window *window_add(MMapCache *m) {
159         Window *w;
160
161         assert(m);
162
163         if (!m->last_unused || m->n_windows <= WINDOWS_MIN) {
164
165                 /* Allocate a new window */
166                 w = new0(Window, 1);
167                 if (!w)
168                         return NULL;
169                 m->n_windows++;
170         } else {
171
172                 /* Reuse an existing one */
173                 w = m->last_unused;
174                 window_unlink(w);
175                 zero(*w);
176         }
177
178         w->cache = m;
179         return w;
180 }
181
182 static void context_detach_window(Context *c) {
183         Window *w;
184
185         assert(c);
186
187         if (!c->window)
188                 return;
189
190         w = c->window;
191         c->window = NULL;
192         LIST_REMOVE(by_window, w->contexts, c);
193
194         if (!w->contexts && !w->keep_always) {
195                 /* Not used anymore? */
196 #ifdef ENABLE_DEBUG_MMAP_CACHE
197                 /* Unmap unused windows immediately to expose use-after-unmap
198                  * by SIGSEGV. */
199                 window_free(w);
200 #else
201                 LIST_PREPEND(unused, c->cache->unused, w);
202                 if (!c->cache->last_unused)
203                         c->cache->last_unused = w;
204
205                 w->in_unused = true;
206 #endif
207         }
208 }
209
210 static void context_attach_window(Context *c, Window *w) {
211         assert(c);
212         assert(w);
213
214         if (c->window == w)
215                 return;
216
217         context_detach_window(c);
218
219         if (w->in_unused) {
220                 /* Used again? */
221                 LIST_REMOVE(unused, c->cache->unused, w);
222                 if (c->cache->last_unused == w)
223                         c->cache->last_unused = w->unused_prev;
224
225                 w->in_unused = false;
226         }
227
228         c->window = w;
229         LIST_PREPEND(by_window, w->contexts, c);
230 }
231
232 static Context *context_add(MMapCache *m, unsigned id) {
233         Context *c;
234
235         assert(m);
236
237         c = m->contexts[id];
238         if (c)
239                 return c;
240
241         c = new0(Context, 1);
242         if (!c)
243                 return NULL;
244
245         c->cache = m;
246         c->id = id;
247
248         assert(!m->contexts[id]);
249         m->contexts[id] = c;
250
251         return c;
252 }
253
254 static void context_free(Context *c) {
255         assert(c);
256
257         context_detach_window(c);
258
259         if (c->cache) {
260                 assert(c->cache->contexts[c->id] == c);
261                 c->cache->contexts[c->id] = NULL;
262         }
263
264         free(c);
265 }
266
267 static void fd_free(FileDescriptor *f) {
268         assert(f);
269
270         while (f->windows)
271                 window_free(f->windows);
272
273         if (f->cache)
274                 assert_se(hashmap_remove(f->cache->fds, INT_TO_PTR(f->fd + 1)));
275
276         free(f);
277 }
278
279 static FileDescriptor* fd_add(MMapCache *m, int fd) {
280         FileDescriptor *f;
281         int r;
282
283         assert(m);
284         assert(fd >= 0);
285
286         f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
287         if (f)
288                 return f;
289
290         r = hashmap_ensure_allocated(&m->fds, NULL);
291         if (r < 0)
292                 return NULL;
293
294         f = new0(FileDescriptor, 1);
295         if (!f)
296                 return NULL;
297
298         f->cache = m;
299         f->fd = fd;
300
301         r = hashmap_put(m->fds, UINT_TO_PTR(fd + 1), f);
302         if (r < 0) {
303                 free(f);
304                 return NULL;
305         }
306
307         return f;
308 }
309
310 static void mmap_cache_free(MMapCache *m) {
311         FileDescriptor *f;
312         int i;
313
314         assert(m);
315
316         for (i = 0; i < MMAP_CACHE_MAX_CONTEXTS; i++)
317                 if (m->contexts[i])
318                         context_free(m->contexts[i]);
319
320         while ((f = hashmap_first(m->fds)))
321                 fd_free(f);
322
323         hashmap_free(m->fds);
324
325         while (m->unused)
326                 window_free(m->unused);
327
328         free(m);
329 }
330
331 MMapCache* mmap_cache_unref(MMapCache *m) {
332         assert(m);
333         assert(m->n_ref > 0);
334
335         m->n_ref --;
336         if (m->n_ref == 0)
337                 mmap_cache_free(m);
338
339         return NULL;
340 }
341
342 static int make_room(MMapCache *m) {
343         assert(m);
344
345         if (!m->last_unused)
346                 return 0;
347
348         window_free(m->last_unused);
349         return 1;
350 }
351
352 static int try_context(
353                 MMapCache *m,
354                 int fd,
355                 int prot,
356                 unsigned context,
357                 bool keep_always,
358                 uint64_t offset,
359                 size_t size,
360                 void **ret) {
361
362         Context *c;
363
364         assert(m);
365         assert(m->n_ref > 0);
366         assert(fd >= 0);
367         assert(size > 0);
368         assert(ret);
369
370         c = m->contexts[context];
371         if (!c)
372                 return 0;
373
374         assert(c->id == context);
375
376         if (!c->window)
377                 return 0;
378
379         if (!window_matches(c->window, fd, prot, offset, size)) {
380
381                 /* Drop the reference to the window, since it's unnecessary now */
382                 context_detach_window(c);
383                 return 0;
384         }
385
386         c->window->keep_always |= keep_always;
387
388         *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset);
389         return 1;
390 }
391
392 static int find_mmap(
393                 MMapCache *m,
394                 int fd,
395                 int prot,
396                 unsigned context,
397                 bool keep_always,
398                 uint64_t offset,
399                 size_t size,
400                 void **ret) {
401
402         FileDescriptor *f;
403         Window *w;
404         Context *c;
405
406         assert(m);
407         assert(m->n_ref > 0);
408         assert(fd >= 0);
409         assert(size > 0);
410
411         f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
412         if (!f)
413                 return 0;
414
415         assert(f->fd == fd);
416
417         LIST_FOREACH(by_fd, w, f->windows)
418                 if (window_matches(w, fd, prot, offset, size))
419                         break;
420
421         if (!w)
422                 return 0;
423
424         c = context_add(m, context);
425         if (!c)
426                 return -ENOMEM;
427
428         context_attach_window(c, w);
429         w->keep_always += keep_always;
430
431         *ret = (uint8_t*) w->ptr + (offset - w->offset);
432         return 1;
433 }
434
435 static int add_mmap(
436                 MMapCache *m,
437                 int fd,
438                 int prot,
439                 unsigned context,
440                 bool keep_always,
441                 uint64_t offset,
442                 size_t size,
443                 struct stat *st,
444                 void **ret) {
445
446         uint64_t woffset, wsize;
447         Context *c;
448         FileDescriptor *f;
449         Window *w;
450         void *d;
451         int r;
452
453         assert(m);
454         assert(m->n_ref > 0);
455         assert(fd >= 0);
456         assert(size > 0);
457         assert(ret);
458
459         woffset = offset & ~((uint64_t) page_size() - 1ULL);
460         wsize = size + (offset - woffset);
461         wsize = PAGE_ALIGN(wsize);
462
463         if (wsize < WINDOW_SIZE) {
464                 uint64_t delta;
465
466                 delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
467
468                 if (delta > offset)
469                         woffset = 0;
470                 else
471                         woffset -= delta;
472
473                 wsize = WINDOW_SIZE;
474         }
475
476         if (st) {
477                 /* Memory maps that are larger then the files
478                    underneath have undefined behavior. Hence, clamp
479                    things to the file size if we know it */
480
481                 if (woffset >= (uint64_t) st->st_size)
482                         return -EADDRNOTAVAIL;
483
484                 if (woffset + wsize > (uint64_t) st->st_size)
485                         wsize = PAGE_ALIGN(st->st_size - woffset);
486         }
487
488         for (;;) {
489                 d = mmap(NULL, wsize, prot, MAP_SHARED, fd, woffset);
490                 if (d != MAP_FAILED)
491                         break;
492                 if (errno != ENOMEM)
493                         return -errno;
494
495                 r = make_room(m);
496                 if (r < 0)
497                         return r;
498                 if (r == 0)
499                         return -ENOMEM;
500         }
501
502         c = context_add(m, context);
503         if (!c)
504                 goto outofmem;
505
506         f = fd_add(m, fd);
507         if (!f)
508                 goto outofmem;
509
510         w = window_add(m);
511         if (!w)
512                 goto outofmem;
513
514         w->keep_always = keep_always;
515         w->ptr = d;
516         w->offset = woffset;
517         w->prot = prot;
518         w->size = wsize;
519         w->fd = f;
520
521         LIST_PREPEND(by_fd, f->windows, w);
522
523         context_detach_window(c);
524         c->window = w;
525         LIST_PREPEND(by_window, w->contexts, c);
526
527         *ret = (uint8_t*) w->ptr + (offset - w->offset);
528         return 1;
529
530 outofmem:
531         munmap(d, wsize);
532         return -ENOMEM;
533 }
534
535 int mmap_cache_get(
536                 MMapCache *m,
537                 int fd,
538                 int prot,
539                 unsigned context,
540                 bool keep_always,
541                 uint64_t offset,
542                 size_t size,
543                 struct stat *st,
544                 void **ret) {
545
546         int r;
547
548         assert(m);
549         assert(m->n_ref > 0);
550         assert(fd >= 0);
551         assert(size > 0);
552         assert(ret);
553         assert(context < MMAP_CACHE_MAX_CONTEXTS);
554
555         /* Check whether the current context is the right one already */
556         r = try_context(m, fd, prot, context, keep_always, offset, size, ret);
557         if (r != 0) {
558                 m->n_hit ++;
559                 return r;
560         }
561
562         /* Search for a matching mmap */
563         r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret);
564         if (r != 0) {
565                 m->n_hit ++;
566                 return r;
567         }
568
569         m->n_missed++;
570
571         /* Create a new mmap */
572         return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret);
573 }
574
575 void mmap_cache_close_fd(MMapCache *m, int fd) {
576         FileDescriptor *f;
577
578         assert(m);
579         assert(fd >= 0);
580
581         f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
582         if (!f)
583                 return;
584
585         fd_free(f);
586 }
587
588 unsigned mmap_cache_get_hit(MMapCache *m) {
589         assert(m);
590
591         return m->n_hit;
592 }
593
594 unsigned mmap_cache_get_missed(MMapCache *m) {
595         assert(m);
596
597         return m->n_missed;
598 }