1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
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.
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.
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/>.
32 #include "mmap-cache.h"
34 typedef struct Window Window;
35 typedef struct Context Context;
36 typedef struct FileDescriptor FileDescriptor;
52 LIST_FIELDS(Window, by_fd);
53 LIST_FIELDS(Window, unused);
55 LIST_HEAD(Context, contexts);
63 LIST_FIELDS(Context, by_window);
66 struct FileDescriptor {
70 LIST_HEAD(Window, windows);
77 unsigned n_hit, n_missed;
81 Context *contexts[MMAP_CACHE_MAX_CONTEXTS];
83 LIST_HEAD(Window, unused);
87 #define WINDOWS_MIN 64
89 #ifdef ENABLE_DEBUG_MMAP_CACHE
90 /* Tiny windows increase mmap activity and the chance of exposing unsafe use. */
91 # define WINDOW_SIZE (page_size())
93 # define WINDOW_SIZE (8ULL*1024ULL*1024ULL)
96 MMapCache* mmap_cache_new(void) {
99 m = new0(MMapCache, 1);
107 MMapCache* mmap_cache_ref(MMapCache *m) {
109 assert(m->n_ref > 0);
115 static void window_unlink(Window *w) {
121 munmap(w->ptr, w->size);
124 LIST_REMOVE(by_fd, w->fd->windows, w);
127 if (w->cache->last_unused == w)
128 w->cache->last_unused = w->unused_prev;
130 LIST_REMOVE(unused, w->cache->unused, w);
133 LIST_FOREACH(by_window, c, w->contexts) {
134 assert(c->window == w);
139 static void window_invalidate(Window *w) {
145 /* Replace the window with anonymous pages. This is useful
146 * when we hit a SIGBUS and want to make sure the file cannot
147 * trigger any further SIGBUS, possibly overrunning the sigbus
150 assert_se(mmap(w->ptr, w->size, w->prot, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == w->ptr);
151 w->invalidated = true;
154 static void window_free(Window *w) {
158 w->cache->n_windows--;
162 _pure_ static bool window_matches(Window *w, int fd, int prot, uint64_t offset, size_t size) {
171 offset >= w->offset &&
172 offset + size <= w->offset + w->size;
175 static Window *window_add(MMapCache *m) {
180 if (!m->last_unused || m->n_windows <= WINDOWS_MIN) {
182 /* Allocate a new window */
189 /* Reuse an existing one */
199 static void context_detach_window(Context *c) {
209 LIST_REMOVE(by_window, w->contexts, c);
211 if (!w->contexts && !w->keep_always) {
212 /* Not used anymore? */
213 #ifdef ENABLE_DEBUG_MMAP_CACHE
214 /* Unmap unused windows immediately to expose use-after-unmap
218 LIST_PREPEND(unused, c->cache->unused, w);
219 if (!c->cache->last_unused)
220 c->cache->last_unused = w;
227 static void context_attach_window(Context *c, Window *w) {
234 context_detach_window(c);
238 LIST_REMOVE(unused, c->cache->unused, w);
239 if (c->cache->last_unused == w)
240 c->cache->last_unused = w->unused_prev;
242 w->in_unused = false;
246 LIST_PREPEND(by_window, w->contexts, c);
249 static Context *context_add(MMapCache *m, unsigned id) {
258 c = new0(Context, 1);
265 assert(!m->contexts[id]);
271 static void context_free(Context *c) {
274 context_detach_window(c);
277 assert(c->cache->contexts[c->id] == c);
278 c->cache->contexts[c->id] = NULL;
284 static void fd_free(FileDescriptor *f) {
288 window_free(f->windows);
291 assert_se(hashmap_remove(f->cache->fds, INT_TO_PTR(f->fd + 1)));
296 static FileDescriptor* fd_add(MMapCache *m, int fd) {
303 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
307 r = hashmap_ensure_allocated(&m->fds, NULL);
311 f = new0(FileDescriptor, 1);
318 r = hashmap_put(m->fds, UINT_TO_PTR(fd + 1), f);
327 static void mmap_cache_free(MMapCache *m) {
333 for (i = 0; i < MMAP_CACHE_MAX_CONTEXTS; i++)
335 context_free(m->contexts[i]);
337 while ((f = hashmap_first(m->fds)))
340 hashmap_free(m->fds);
343 window_free(m->unused);
348 MMapCache* mmap_cache_unref(MMapCache *m) {
350 assert(m->n_ref > 0);
359 static int make_room(MMapCache *m) {
365 window_free(m->last_unused);
369 static int try_context(
382 assert(m->n_ref > 0);
387 c = m->contexts[context];
391 assert(c->id == context);
396 if (!window_matches(c->window, fd, prot, offset, size)) {
398 /* Drop the reference to the window, since it's unnecessary now */
399 context_detach_window(c);
403 if (c->window->fd->sigbus)
406 c->window->keep_always |= keep_always;
408 *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset);
412 static int find_mmap(
427 assert(m->n_ref > 0);
431 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
440 LIST_FOREACH(by_fd, w, f->windows)
441 if (window_matches(w, fd, prot, offset, size))
447 c = context_add(m, context);
451 context_attach_window(c, w);
452 w->keep_always += keep_always;
454 *ret = (uint8_t*) w->ptr + (offset - w->offset);
469 uint64_t woffset, wsize;
477 assert(m->n_ref > 0);
482 woffset = offset & ~((uint64_t) page_size() - 1ULL);
483 wsize = size + (offset - woffset);
484 wsize = PAGE_ALIGN(wsize);
486 if (wsize < WINDOW_SIZE) {
489 delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
500 /* Memory maps that are larger then the files
501 underneath have undefined behavior. Hence, clamp
502 things to the file size if we know it */
504 if (woffset >= (uint64_t) st->st_size)
505 return -EADDRNOTAVAIL;
507 if (woffset + wsize > (uint64_t) st->st_size)
508 wsize = PAGE_ALIGN(st->st_size - woffset);
512 d = mmap(NULL, wsize, prot, MAP_SHARED, fd, woffset);
525 c = context_add(m, context);
537 w->keep_always = keep_always;
544 LIST_PREPEND(by_fd, f->windows, w);
546 context_detach_window(c);
548 LIST_PREPEND(by_window, w->contexts, c);
550 *ret = (uint8_t*) w->ptr + (offset - w->offset);
572 assert(m->n_ref > 0);
576 assert(context < MMAP_CACHE_MAX_CONTEXTS);
578 /* Check whether the current context is the right one already */
579 r = try_context(m, fd, prot, context, keep_always, offset, size, ret);
585 /* Search for a matching mmap */
586 r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret);
594 /* Create a new mmap */
595 return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret);
598 unsigned mmap_cache_get_hit(MMapCache *m) {
604 unsigned mmap_cache_get_missed(MMapCache *m) {
610 static void mmap_cache_process_sigbus(MMapCache *m) {
618 /* Iterate through all triggered pages and mark their files as
624 r = sigbus_pop(&addr);
625 if (_likely_(r == 0))
628 log_error_errno(r, "SIGBUS handling failed: %m");
633 HASHMAP_FOREACH(f, m->fds, i) {
636 LIST_FOREACH(by_fd, w, f->windows) {
637 if ((uint8_t*) addr >= (uint8_t*) w->ptr &&
638 (uint8_t*) addr < (uint8_t*) w->ptr + w->size) {
639 found = ours = f->sigbus = true;
648 /* Didn't find a matching window, give up */
650 log_error("Unknown SIGBUS page, aborting.");
655 /* The list of triggered pages is now empty. Now, let's remap
656 * all windows of the triggered file to anonymous maps, so
657 * that no page of the file in question is triggered again, so
658 * that we can be sure not to hit the queue size limit. */
659 if (_likely_(!found))
662 HASHMAP_FOREACH(f, m->fds, i) {
668 LIST_FOREACH(by_fd, w, f->windows)
669 window_invalidate(w);
673 bool mmap_cache_got_sigbus(MMapCache *m, int fd) {
679 mmap_cache_process_sigbus(m);
681 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
688 void mmap_cache_close_fd(MMapCache *m, int fd) {
694 /* Make sure that any queued SIGBUS are first dispatched, so
695 * that we don't end up with a SIGBUS entry we cannot relate
696 * to any existing memory map */
698 mmap_cache_process_sigbus(m);
700 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));