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/>.
26 #include "alloc-util.h"
32 #include "mmap-cache.h"
36 typedef struct Window Window
;
37 typedef struct Context Context
;
38 typedef struct FileDescriptor FileDescriptor
;
54 LIST_FIELDS(Window
, by_fd
);
55 LIST_FIELDS(Window
, unused
);
57 LIST_HEAD(Context
, contexts
);
65 LIST_FIELDS(Context
, by_window
);
68 struct FileDescriptor
{
72 LIST_HEAD(Window
, windows
);
79 unsigned n_hit
, n_missed
;
83 Context
*contexts
[MMAP_CACHE_MAX_CONTEXTS
];
85 LIST_HEAD(Window
, unused
);
89 #define WINDOWS_MIN 64
91 #ifdef ENABLE_DEBUG_MMAP_CACHE
92 /* Tiny windows increase mmap activity and the chance of exposing unsafe use. */
93 # define WINDOW_SIZE (page_size())
95 # define WINDOW_SIZE (8ULL*1024ULL*1024ULL)
98 MMapCache
* mmap_cache_new(void) {
101 m
= new0(MMapCache
, 1);
109 MMapCache
* mmap_cache_ref(MMapCache
*m
) {
111 assert(m
->n_ref
> 0);
117 static void window_unlink(Window
*w
) {
123 munmap(w
->ptr
, w
->size
);
126 LIST_REMOVE(by_fd
, w
->fd
->windows
, w
);
129 if (w
->cache
->last_unused
== w
)
130 w
->cache
->last_unused
= w
->unused_prev
;
132 LIST_REMOVE(unused
, w
->cache
->unused
, w
);
135 LIST_FOREACH(by_window
, c
, w
->contexts
) {
136 assert(c
->window
== w
);
141 static void window_invalidate(Window
*w
) {
147 /* Replace the window with anonymous pages. This is useful
148 * when we hit a SIGBUS and want to make sure the file cannot
149 * trigger any further SIGBUS, possibly overrunning the sigbus
152 assert_se(mmap(w
->ptr
, w
->size
, w
->prot
, MAP_PRIVATE
|MAP_ANONYMOUS
|MAP_FIXED
, -1, 0) == w
->ptr
);
153 w
->invalidated
= true;
156 static void window_free(Window
*w
) {
160 w
->cache
->n_windows
--;
164 _pure_
static bool window_matches(Window
*w
, int fd
, int prot
, uint64_t offset
, size_t size
) {
173 offset
>= w
->offset
&&
174 offset
+ size
<= w
->offset
+ w
->size
;
177 static Window
*window_add(MMapCache
*m
) {
182 if (!m
->last_unused
|| m
->n_windows
<= WINDOWS_MIN
) {
184 /* Allocate a new window */
191 /* Reuse an existing one */
201 static void context_detach_window(Context
*c
) {
211 LIST_REMOVE(by_window
, w
->contexts
, c
);
213 if (!w
->contexts
&& !w
->keep_always
) {
214 /* Not used anymore? */
215 #ifdef ENABLE_DEBUG_MMAP_CACHE
216 /* Unmap unused windows immediately to expose use-after-unmap
220 LIST_PREPEND(unused
, c
->cache
->unused
, w
);
221 if (!c
->cache
->last_unused
)
222 c
->cache
->last_unused
= w
;
229 static void context_attach_window(Context
*c
, Window
*w
) {
236 context_detach_window(c
);
240 LIST_REMOVE(unused
, c
->cache
->unused
, w
);
241 if (c
->cache
->last_unused
== w
)
242 c
->cache
->last_unused
= w
->unused_prev
;
244 w
->in_unused
= false;
248 LIST_PREPEND(by_window
, w
->contexts
, c
);
251 static Context
*context_add(MMapCache
*m
, unsigned id
) {
260 c
= new0(Context
, 1);
267 assert(!m
->contexts
[id
]);
273 static void context_free(Context
*c
) {
276 context_detach_window(c
);
279 assert(c
->cache
->contexts
[c
->id
] == c
);
280 c
->cache
->contexts
[c
->id
] = NULL
;
286 static void fd_free(FileDescriptor
*f
) {
290 window_free(f
->windows
);
293 assert_se(hashmap_remove(f
->cache
->fds
, FD_TO_PTR(f
->fd
)));
298 static FileDescriptor
* fd_add(MMapCache
*m
, int fd
) {
305 f
= hashmap_get(m
->fds
, FD_TO_PTR(fd
));
309 r
= hashmap_ensure_allocated(&m
->fds
, NULL
);
313 f
= new0(FileDescriptor
, 1);
320 r
= hashmap_put(m
->fds
, FD_TO_PTR(fd
), f
);
329 static void mmap_cache_free(MMapCache
*m
) {
335 for (i
= 0; i
< MMAP_CACHE_MAX_CONTEXTS
; i
++)
337 context_free(m
->contexts
[i
]);
339 while ((f
= hashmap_first(m
->fds
)))
342 hashmap_free(m
->fds
);
345 window_free(m
->unused
);
350 MMapCache
* mmap_cache_unref(MMapCache
*m
) {
352 assert(m
->n_ref
> 0);
361 static int make_room(MMapCache
*m
) {
367 window_free(m
->last_unused
);
371 static int try_context(
384 assert(m
->n_ref
> 0);
389 c
= m
->contexts
[context
];
393 assert(c
->id
== context
);
398 if (!window_matches(c
->window
, fd
, prot
, offset
, size
)) {
400 /* Drop the reference to the window, since it's unnecessary now */
401 context_detach_window(c
);
405 if (c
->window
->fd
->sigbus
)
408 c
->window
->keep_always
|= keep_always
;
410 *ret
= (uint8_t*) c
->window
->ptr
+ (offset
- c
->window
->offset
);
414 static int find_mmap(
429 assert(m
->n_ref
> 0);
433 f
= hashmap_get(m
->fds
, FD_TO_PTR(fd
));
442 LIST_FOREACH(by_fd
, w
, f
->windows
)
443 if (window_matches(w
, fd
, prot
, offset
, size
))
449 c
= context_add(m
, context
);
453 context_attach_window(c
, w
);
454 w
->keep_always
+= keep_always
;
456 *ret
= (uint8_t*) w
->ptr
+ (offset
- w
->offset
);
471 uint64_t woffset
, wsize
;
479 assert(m
->n_ref
> 0);
484 woffset
= offset
& ~((uint64_t) page_size() - 1ULL);
485 wsize
= size
+ (offset
- woffset
);
486 wsize
= PAGE_ALIGN(wsize
);
488 if (wsize
< WINDOW_SIZE
) {
491 delta
= PAGE_ALIGN((WINDOW_SIZE
- wsize
) / 2);
502 /* Memory maps that are larger then the files
503 underneath have undefined behavior. Hence, clamp
504 things to the file size if we know it */
506 if (woffset
>= (uint64_t) st
->st_size
)
507 return -EADDRNOTAVAIL
;
509 if (woffset
+ wsize
> (uint64_t) st
->st_size
)
510 wsize
= PAGE_ALIGN(st
->st_size
- woffset
);
514 d
= mmap(NULL
, wsize
, prot
, MAP_SHARED
, fd
, woffset
);
527 c
= context_add(m
, context
);
539 w
->keep_always
= keep_always
;
546 LIST_PREPEND(by_fd
, f
->windows
, w
);
548 context_detach_window(c
);
550 LIST_PREPEND(by_window
, w
->contexts
, c
);
552 *ret
= (uint8_t*) w
->ptr
+ (offset
- w
->offset
);
574 assert(m
->n_ref
> 0);
578 assert(context
< MMAP_CACHE_MAX_CONTEXTS
);
580 /* Check whether the current context is the right one already */
581 r
= try_context(m
, fd
, prot
, context
, keep_always
, offset
, size
, ret
);
587 /* Search for a matching mmap */
588 r
= find_mmap(m
, fd
, prot
, context
, keep_always
, offset
, size
, ret
);
596 /* Create a new mmap */
597 return add_mmap(m
, fd
, prot
, context
, keep_always
, offset
, size
, st
, ret
);
600 unsigned mmap_cache_get_hit(MMapCache
*m
) {
606 unsigned mmap_cache_get_missed(MMapCache
*m
) {
612 static void mmap_cache_process_sigbus(MMapCache
*m
) {
620 /* Iterate through all triggered pages and mark their files as
626 r
= sigbus_pop(&addr
);
627 if (_likely_(r
== 0))
630 log_error_errno(r
, "SIGBUS handling failed: %m");
635 HASHMAP_FOREACH(f
, m
->fds
, i
) {
638 LIST_FOREACH(by_fd
, w
, f
->windows
) {
639 if ((uint8_t*) addr
>= (uint8_t*) w
->ptr
&&
640 (uint8_t*) addr
< (uint8_t*) w
->ptr
+ w
->size
) {
641 found
= ours
= f
->sigbus
= true;
650 /* Didn't find a matching window, give up */
652 log_error("Unknown SIGBUS page, aborting.");
657 /* The list of triggered pages is now empty. Now, let's remap
658 * all windows of the triggered file to anonymous maps, so
659 * that no page of the file in question is triggered again, so
660 * that we can be sure not to hit the queue size limit. */
661 if (_likely_(!found
))
664 HASHMAP_FOREACH(f
, m
->fds
, i
) {
670 LIST_FOREACH(by_fd
, w
, f
->windows
)
671 window_invalidate(w
);
675 bool mmap_cache_got_sigbus(MMapCache
*m
, int fd
) {
681 mmap_cache_process_sigbus(m
);
683 f
= hashmap_get(m
->fds
, FD_TO_PTR(fd
));
690 void mmap_cache_close_fd(MMapCache
*m
, int fd
) {
696 /* Make sure that any queued SIGBUS are first dispatched, so
697 * that we don't end up with a SIGBUS entry we cannot relate
698 * to any existing memory map */
700 mmap_cache_process_sigbus(m
);
702 f
= hashmap_get(m
->fds
, FD_TO_PTR(fd
));