]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/mmap-cache.c
journal: add debug mode for mmap-cache (--enable-debug=mmap-cache)
[thirdparty/systemd.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 unsigned 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 Hashmap *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 == 0) {
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 int r;
235
236 assert(m);
237
238 c = hashmap_get(m->contexts, UINT_TO_PTR(id + 1));
239 if (c)
240 return c;
241
242 r = hashmap_ensure_allocated(&m->contexts, NULL);
243 if (r < 0)
244 return NULL;
245
246 c = new0(Context, 1);
247 if (!c)
248 return NULL;
249
250 c->cache = m;
251 c->id = id;
252
253 r = hashmap_put(m->contexts, UINT_TO_PTR(id + 1), c);
254 if (r < 0) {
255 free(c);
256 return NULL;
257 }
258
259 return c;
260 }
261
262 static void context_free(Context *c) {
263 assert(c);
264
265 context_detach_window(c);
266
267 if (c->cache)
268 assert_se(hashmap_remove(c->cache->contexts, UINT_TO_PTR(c->id + 1)));
269
270 free(c);
271 }
272
273 static void fd_free(FileDescriptor *f) {
274 assert(f);
275
276 while (f->windows)
277 window_free(f->windows);
278
279 if (f->cache)
280 assert_se(hashmap_remove(f->cache->fds, INT_TO_PTR(f->fd + 1)));
281
282 free(f);
283 }
284
285 static FileDescriptor* fd_add(MMapCache *m, int fd) {
286 FileDescriptor *f;
287 int r;
288
289 assert(m);
290 assert(fd >= 0);
291
292 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
293 if (f)
294 return f;
295
296 r = hashmap_ensure_allocated(&m->fds, NULL);
297 if (r < 0)
298 return NULL;
299
300 f = new0(FileDescriptor, 1);
301 if (!f)
302 return NULL;
303
304 f->cache = m;
305 f->fd = fd;
306
307 r = hashmap_put(m->fds, UINT_TO_PTR(fd + 1), f);
308 if (r < 0) {
309 free(f);
310 return NULL;
311 }
312
313 return f;
314 }
315
316 static void mmap_cache_free(MMapCache *m) {
317 Context *c;
318 FileDescriptor *f;
319
320 assert(m);
321
322 while ((c = hashmap_first(m->contexts)))
323 context_free(c);
324
325 hashmap_free(m->contexts);
326
327 while ((f = hashmap_first(m->fds)))
328 fd_free(f);
329
330 hashmap_free(m->fds);
331
332 while (m->unused)
333 window_free(m->unused);
334
335 free(m);
336 }
337
338 MMapCache* mmap_cache_unref(MMapCache *m) {
339 assert(m);
340 assert(m->n_ref > 0);
341
342 m->n_ref --;
343 if (m->n_ref == 0)
344 mmap_cache_free(m);
345
346 return NULL;
347 }
348
349 static int make_room(MMapCache *m) {
350 assert(m);
351
352 if (!m->last_unused)
353 return 0;
354
355 window_free(m->last_unused);
356 return 1;
357 }
358
359 static int try_context(
360 MMapCache *m,
361 int fd,
362 int prot,
363 unsigned context,
364 bool keep_always,
365 uint64_t offset,
366 size_t size,
367 void **ret,
368 void **release_cookie) {
369
370 Context *c;
371
372 assert(m);
373 assert(m->n_ref > 0);
374 assert(fd >= 0);
375 assert(size > 0);
376
377 c = hashmap_get(m->contexts, UINT_TO_PTR(context+1));
378 if (!c)
379 return 0;
380
381 assert(c->id == context);
382
383 if (!c->window)
384 return 0;
385
386 if (!window_matches(c->window, fd, prot, offset, size)) {
387
388 /* Drop the reference to the window, since it's unnecessary now */
389 context_detach_window(c);
390 return 0;
391 }
392
393 c->window->keep_always += keep_always;
394
395 if (ret)
396 *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset);
397 if (keep_always && release_cookie)
398 *release_cookie = c->window;
399 return 1;
400 }
401
402 static int find_mmap(
403 MMapCache *m,
404 int fd,
405 int prot,
406 unsigned context,
407 bool keep_always,
408 uint64_t offset,
409 size_t size,
410 void **ret,
411 void **release_cookie) {
412
413 FileDescriptor *f;
414 Window *w;
415 Context *c;
416
417 assert(m);
418 assert(m->n_ref > 0);
419 assert(fd >= 0);
420 assert(size > 0);
421
422 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
423 if (!f)
424 return 0;
425
426 assert(f->fd == fd);
427
428 LIST_FOREACH(by_fd, w, f->windows)
429 if (window_matches(w, fd, prot, offset, size))
430 break;
431
432 if (!w)
433 return 0;
434
435 c = context_add(m, context);
436 if (!c)
437 return -ENOMEM;
438
439 context_attach_window(c, w);
440 w->keep_always += keep_always;
441
442 if (ret)
443 *ret = (uint8_t*) w->ptr + (offset - w->offset);
444 if (keep_always && release_cookie)
445 *release_cookie = c->window;
446 return 1;
447 }
448
449 static int add_mmap(
450 MMapCache *m,
451 int fd,
452 int prot,
453 unsigned context,
454 bool keep_always,
455 uint64_t offset,
456 size_t size,
457 struct stat *st,
458 void **ret,
459 void **release_cookie) {
460
461 uint64_t woffset, wsize;
462 Context *c;
463 FileDescriptor *f;
464 Window *w;
465 void *d;
466 int r;
467
468 assert(m);
469 assert(m->n_ref > 0);
470 assert(fd >= 0);
471 assert(size > 0);
472
473 woffset = offset & ~((uint64_t) page_size() - 1ULL);
474 wsize = size + (offset - woffset);
475 wsize = PAGE_ALIGN(wsize);
476
477 if (wsize < WINDOW_SIZE) {
478 uint64_t delta;
479
480 delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
481
482 if (delta > offset)
483 woffset = 0;
484 else
485 woffset -= delta;
486
487 wsize = WINDOW_SIZE;
488 }
489
490 if (st) {
491 /* Memory maps that are larger then the files
492 underneath have undefined behavior. Hence, clamp
493 things to the file size if we know it */
494
495 if (woffset >= (uint64_t) st->st_size)
496 return -EADDRNOTAVAIL;
497
498 if (woffset + wsize > (uint64_t) st->st_size)
499 wsize = PAGE_ALIGN(st->st_size - woffset);
500 }
501
502 for (;;) {
503 d = mmap(NULL, wsize, prot, MAP_SHARED, fd, woffset);
504 if (d != MAP_FAILED)
505 break;
506 if (errno != ENOMEM)
507 return -errno;
508
509 r = make_room(m);
510 if (r < 0)
511 return r;
512 if (r == 0)
513 return -ENOMEM;
514 }
515
516 c = context_add(m, context);
517 if (!c)
518 goto outofmem;
519
520 f = fd_add(m, fd);
521 if (!f)
522 goto outofmem;
523
524 w = window_add(m);
525 if (!w)
526 goto outofmem;
527
528 w->keep_always = keep_always;
529 w->ptr = d;
530 w->offset = woffset;
531 w->prot = prot;
532 w->size = wsize;
533 w->fd = f;
534
535 LIST_PREPEND(by_fd, f->windows, w);
536
537 context_detach_window(c);
538 c->window = w;
539 LIST_PREPEND(by_window, w->contexts, c);
540
541 if (ret)
542 *ret = (uint8_t*) w->ptr + (offset - w->offset);
543 if (keep_always && release_cookie)
544 *release_cookie = c->window;
545 return 1;
546
547 outofmem:
548 munmap(d, wsize);
549 return -ENOMEM;
550 }
551
552 int mmap_cache_get(
553 MMapCache *m,
554 int fd,
555 int prot,
556 unsigned context,
557 bool keep_always,
558 uint64_t offset,
559 size_t size,
560 struct stat *st,
561 void **ret,
562 void **release_cookie) {
563
564 int r;
565
566 assert(m);
567 assert(m->n_ref > 0);
568 assert(fd >= 0);
569 assert(size > 0);
570
571 /* Check whether the current context is the right one already */
572 r = try_context(m, fd, prot, context, keep_always, offset, size, ret, release_cookie);
573 if (r != 0) {
574 m->n_hit ++;
575 return r;
576 }
577
578 /* Search for a matching mmap */
579 r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret, release_cookie);
580 if (r != 0) {
581 m->n_hit ++;
582 return r;
583 }
584
585 m->n_missed++;
586
587 /* Create a new mmap */
588 return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret, release_cookie);
589 }
590
591 int mmap_cache_release(
592 MMapCache *m,
593 int fd,
594 void *release_cookie) {
595
596 FileDescriptor *f;
597 Window *w;
598
599 assert(m);
600 assert(m->n_ref > 0);
601 assert(fd >= 0);
602
603 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
604 if (!f)
605 return -EBADF;
606
607 assert(f->fd == fd);
608
609 LIST_FOREACH(by_fd, w, f->windows)
610 if (w == release_cookie)
611 break;
612
613 if (!w)
614 return -ENOENT;
615
616 if (w->keep_always == 0)
617 return -ENOLCK;
618
619 w->keep_always -= 1;
620 return 0;
621 }
622
623 void mmap_cache_close_fd(MMapCache *m, int fd) {
624 FileDescriptor *f;
625
626 assert(m);
627 assert(fd >= 0);
628
629 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
630 if (!f)
631 return;
632
633 fd_free(f);
634 }
635
636 void mmap_cache_close_context(MMapCache *m, unsigned context) {
637 Context *c;
638
639 assert(m);
640
641 c = hashmap_get(m->contexts, UINT_TO_PTR(context + 1));
642 if (!c)
643 return;
644
645 context_free(c);
646 }
647
648 unsigned mmap_cache_get_hit(MMapCache *m) {
649 assert(m);
650
651 return m->n_hit;
652 }
653
654 unsigned mmap_cache_get_missed(MMapCache *m) {
655 assert(m);
656
657 return m->n_missed;
658 }