]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/mmap-cache.c
91ed3cd519a6c773a65fe5e10a92cf6f414c96a4
1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2012 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
25 #include "alloc-util.h"
31 #include "mmap-cache.h"
35 typedef struct Window Window
;
36 typedef struct Context Context
;
50 MMapFileDescriptor
*fd
;
52 LIST_FIELDS(Window
, by_fd
);
53 LIST_FIELDS(Window
, unused
);
55 LIST_HEAD(Context
, contexts
);
63 LIST_FIELDS(Context
, by_window
);
66 struct MMapFileDescriptor
{
70 LIST_HEAD(Window
, windows
);
77 unsigned n_hit
, n_missed
;
80 Context
*contexts
[MMAP_CACHE_MAX_CONTEXTS
];
82 LIST_HEAD(Window
, unused
);
86 #define WINDOWS_MIN 64
88 #if ENABLE_DEBUG_MMAP_CACHE
89 /* Tiny windows increase mmap activity and the chance of exposing unsafe use. */
90 # define WINDOW_SIZE (page_size())
92 # define WINDOW_SIZE (8ULL*1024ULL*1024ULL)
95 MMapCache
* mmap_cache_new(void) {
98 m
= new0(MMapCache
, 1);
106 MMapCache
* mmap_cache_ref(MMapCache
*m
) {
108 assert(m
->n_ref
> 0);
114 static void window_unlink(Window
*w
) {
120 munmap(w
->ptr
, w
->size
);
123 LIST_REMOVE(by_fd
, w
->fd
->windows
, w
);
126 if (w
->cache
->last_unused
== w
)
127 w
->cache
->last_unused
= w
->unused_prev
;
129 LIST_REMOVE(unused
, w
->cache
->unused
, w
);
132 LIST_FOREACH(by_window
, c
, w
->contexts
) {
133 assert(c
->window
== w
);
138 static void window_invalidate(Window
*w
) {
144 /* Replace the window with anonymous pages. This is useful
145 * when we hit a SIGBUS and want to make sure the file cannot
146 * trigger any further SIGBUS, possibly overrunning the sigbus
149 assert_se(mmap(w
->ptr
, w
->size
, w
->prot
, MAP_PRIVATE
|MAP_ANONYMOUS
|MAP_FIXED
, -1, 0) == w
->ptr
);
150 w
->invalidated
= true;
153 static void window_free(Window
*w
) {
157 w
->cache
->n_windows
--;
161 _pure_
static inline bool window_matches(Window
*w
, int prot
, uint64_t offset
, size_t size
) {
167 offset
>= w
->offset
&&
168 offset
+ size
<= w
->offset
+ w
->size
;
171 _pure_
static bool window_matches_fd(Window
*w
, MMapFileDescriptor
*f
, int prot
, uint64_t offset
, size_t size
) {
177 f
->fd
== w
->fd
->fd
&&
178 window_matches(w
, prot
, offset
, size
);
181 static Window
*window_add(MMapCache
*m
, MMapFileDescriptor
*f
, int prot
, bool keep_always
, uint64_t offset
, size_t size
, void *ptr
) {
187 if (!m
->last_unused
|| m
->n_windows
<= WINDOWS_MIN
) {
189 /* Allocate a new window */
196 /* Reuse an existing one */
205 w
->keep_always
= keep_always
;
210 LIST_PREPEND(by_fd
, f
->windows
, w
);
215 static void context_detach_window(Context
*c
) {
223 w
= TAKE_PTR(c
->window
);
224 LIST_REMOVE(by_window
, w
->contexts
, c
);
226 if (!w
->contexts
&& !w
->keep_always
) {
227 /* Not used anymore? */
228 #if ENABLE_DEBUG_MMAP_CACHE
229 /* Unmap unused windows immediately to expose use-after-unmap
233 LIST_PREPEND(unused
, c
->cache
->unused
, w
);
234 if (!c
->cache
->last_unused
)
235 c
->cache
->last_unused
= w
;
242 static void context_attach_window(Context
*c
, Window
*w
) {
249 context_detach_window(c
);
253 LIST_REMOVE(unused
, c
->cache
->unused
, w
);
254 if (c
->cache
->last_unused
== w
)
255 c
->cache
->last_unused
= w
->unused_prev
;
257 w
->in_unused
= false;
261 LIST_PREPEND(by_window
, w
->contexts
, c
);
264 static Context
*context_add(MMapCache
*m
, unsigned id
) {
273 c
= new0(Context
, 1);
280 assert(!m
->contexts
[id
]);
286 static void context_free(Context
*c
) {
289 context_detach_window(c
);
292 assert(c
->cache
->contexts
[c
->id
] == c
);
293 c
->cache
->contexts
[c
->id
] = NULL
;
299 static void mmap_cache_free(MMapCache
*m
) {
304 for (i
= 0; i
< MMAP_CACHE_MAX_CONTEXTS
; i
++)
306 context_free(m
->contexts
[i
]);
308 hashmap_free(m
->fds
);
311 window_free(m
->unused
);
316 MMapCache
* mmap_cache_unref(MMapCache
*m
) {
321 assert(m
->n_ref
> 0);
330 static int make_room(MMapCache
*m
) {
336 window_free(m
->last_unused
);
340 static int try_context(
342 MMapFileDescriptor
*f
,
354 assert(m
->n_ref
> 0);
359 c
= m
->contexts
[context
];
363 assert(c
->id
== context
);
368 if (!window_matches_fd(c
->window
, f
, prot
, offset
, size
)) {
370 /* Drop the reference to the window, since it's unnecessary now */
371 context_detach_window(c
);
375 if (c
->window
->fd
->sigbus
)
378 c
->window
->keep_always
= c
->window
->keep_always
|| keep_always
;
380 *ret
= (uint8_t*) c
->window
->ptr
+ (offset
- c
->window
->offset
);
382 *ret_size
= c
->window
->size
- (offset
- c
->window
->offset
);
387 static int find_mmap(
389 MMapFileDescriptor
*f
,
402 assert(m
->n_ref
> 0);
409 LIST_FOREACH(by_fd
, w
, f
->windows
)
410 if (window_matches(w
, prot
, offset
, size
))
416 c
= context_add(m
, context
);
420 context_attach_window(c
, w
);
421 w
->keep_always
= w
->keep_always
|| keep_always
;
423 *ret
= (uint8_t*) w
->ptr
+ (offset
- w
->offset
);
425 *ret_size
= w
->size
- (offset
- w
->offset
);
430 static int mmap_try_harder(MMapCache
*m
, void *addr
, MMapFileDescriptor
*f
, int prot
, int flags
, uint64_t offset
, size_t size
, void **res
) {
440 ptr
= mmap(addr
, size
, prot
, flags
, f
->fd
, offset
);
441 if (ptr
!= MAP_FAILED
)
444 return negative_errno();
459 MMapFileDescriptor
*f
,
469 uint64_t woffset
, wsize
;
476 assert(m
->n_ref
> 0);
481 woffset
= offset
& ~((uint64_t) page_size() - 1ULL);
482 wsize
= size
+ (offset
- woffset
);
483 wsize
= PAGE_ALIGN(wsize
);
485 if (wsize
< WINDOW_SIZE
) {
488 delta
= PAGE_ALIGN((WINDOW_SIZE
- wsize
) / 2);
499 /* Memory maps that are larger then the files
500 underneath have undefined behavior. Hence, clamp
501 things to the file size if we know it */
503 if (woffset
>= (uint64_t) st
->st_size
)
504 return -EADDRNOTAVAIL
;
506 if (woffset
+ wsize
> (uint64_t) st
->st_size
)
507 wsize
= PAGE_ALIGN(st
->st_size
- woffset
);
510 r
= mmap_try_harder(m
, NULL
, f
, prot
, MAP_SHARED
, woffset
, wsize
, &d
);
514 c
= context_add(m
, context
);
518 w
= window_add(m
, f
, prot
, keep_always
, woffset
, wsize
, d
);
522 context_attach_window(c
, w
);
524 *ret
= (uint8_t*) w
->ptr
+ (offset
- w
->offset
);
526 *ret_size
= w
->size
- (offset
- w
->offset
);
531 (void) munmap(d
, wsize
);
537 MMapFileDescriptor
*f
,
550 assert(m
->n_ref
> 0);
554 assert(context
< MMAP_CACHE_MAX_CONTEXTS
);
556 /* Check whether the current context is the right one already */
557 r
= try_context(m
, f
, prot
, context
, keep_always
, offset
, size
, ret
, ret_size
);
563 /* Search for a matching mmap */
564 r
= find_mmap(m
, f
, prot
, context
, keep_always
, offset
, size
, ret
, ret_size
);
572 /* Create a new mmap */
573 return add_mmap(m
, f
, prot
, context
, keep_always
, offset
, size
, st
, ret
, ret_size
);
576 unsigned mmap_cache_get_hit(MMapCache
*m
) {
582 unsigned mmap_cache_get_missed(MMapCache
*m
) {
588 static void mmap_cache_process_sigbus(MMapCache
*m
) {
590 MMapFileDescriptor
*f
;
596 /* Iterate through all triggered pages and mark their files as
602 r
= sigbus_pop(&addr
);
603 if (_likely_(r
== 0))
606 log_error_errno(r
, "SIGBUS handling failed: %m");
611 HASHMAP_FOREACH(f
, m
->fds
, i
) {
614 LIST_FOREACH(by_fd
, w
, f
->windows
) {
615 if ((uint8_t*) addr
>= (uint8_t*) w
->ptr
&&
616 (uint8_t*) addr
< (uint8_t*) w
->ptr
+ w
->size
) {
617 found
= ours
= f
->sigbus
= true;
626 /* Didn't find a matching window, give up */
628 log_error("Unknown SIGBUS page, aborting.");
633 /* The list of triggered pages is now empty. Now, let's remap
634 * all windows of the triggered file to anonymous maps, so
635 * that no page of the file in question is triggered again, so
636 * that we can be sure not to hit the queue size limit. */
637 if (_likely_(!found
))
640 HASHMAP_FOREACH(f
, m
->fds
, i
) {
646 LIST_FOREACH(by_fd
, w
, f
->windows
)
647 window_invalidate(w
);
651 bool mmap_cache_got_sigbus(MMapCache
*m
, MMapFileDescriptor
*f
) {
655 mmap_cache_process_sigbus(m
);
660 MMapFileDescriptor
* mmap_cache_add_fd(MMapCache
*m
, int fd
) {
661 MMapFileDescriptor
*f
;
667 f
= hashmap_get(m
->fds
, FD_TO_PTR(fd
));
671 r
= hashmap_ensure_allocated(&m
->fds
, NULL
);
675 f
= new0(MMapFileDescriptor
, 1);
682 r
= hashmap_put(m
->fds
, FD_TO_PTR(fd
), f
);
689 void mmap_cache_free_fd(MMapCache
*m
, MMapFileDescriptor
*f
) {
693 /* Make sure that any queued SIGBUS are first dispatched, so
694 * that we don't end up with a SIGBUS entry we cannot relate
695 * to any existing memory map */
697 mmap_cache_process_sigbus(m
);
700 window_free(f
->windows
);
703 assert_se(hashmap_remove(f
->cache
->fds
, FD_TO_PTR(f
->fd
)));