]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/mmap-cache.c
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
) {
225 LIST_REMOVE(by_window
, w
->contexts
, c
);
227 if (!w
->contexts
&& !w
->keep_always
) {
228 /* Not used anymore? */
229 #if ENABLE_DEBUG_MMAP_CACHE
230 /* Unmap unused windows immediately to expose use-after-unmap
234 LIST_PREPEND(unused
, c
->cache
->unused
, w
);
235 if (!c
->cache
->last_unused
)
236 c
->cache
->last_unused
= w
;
243 static void context_attach_window(Context
*c
, Window
*w
) {
250 context_detach_window(c
);
254 LIST_REMOVE(unused
, c
->cache
->unused
, w
);
255 if (c
->cache
->last_unused
== w
)
256 c
->cache
->last_unused
= w
->unused_prev
;
258 w
->in_unused
= false;
262 LIST_PREPEND(by_window
, w
->contexts
, c
);
265 static Context
*context_add(MMapCache
*m
, unsigned id
) {
274 c
= new0(Context
, 1);
281 assert(!m
->contexts
[id
]);
287 static void context_free(Context
*c
) {
290 context_detach_window(c
);
293 assert(c
->cache
->contexts
[c
->id
] == c
);
294 c
->cache
->contexts
[c
->id
] = NULL
;
300 static void mmap_cache_free(MMapCache
*m
) {
305 for (i
= 0; i
< MMAP_CACHE_MAX_CONTEXTS
; i
++)
307 context_free(m
->contexts
[i
]);
309 hashmap_free(m
->fds
);
312 window_free(m
->unused
);
317 MMapCache
* mmap_cache_unref(MMapCache
*m
) {
322 assert(m
->n_ref
> 0);
331 static int make_room(MMapCache
*m
) {
337 window_free(m
->last_unused
);
341 static int try_context(
343 MMapFileDescriptor
*f
,
355 assert(m
->n_ref
> 0);
360 c
= m
->contexts
[context
];
364 assert(c
->id
== context
);
369 if (!window_matches_fd(c
->window
, f
, prot
, offset
, size
)) {
371 /* Drop the reference to the window, since it's unnecessary now */
372 context_detach_window(c
);
376 if (c
->window
->fd
->sigbus
)
379 c
->window
->keep_always
= c
->window
->keep_always
|| keep_always
;
381 *ret
= (uint8_t*) c
->window
->ptr
+ (offset
- c
->window
->offset
);
383 *ret_size
= c
->window
->size
- (offset
- c
->window
->offset
);
388 static int find_mmap(
390 MMapFileDescriptor
*f
,
403 assert(m
->n_ref
> 0);
410 LIST_FOREACH(by_fd
, w
, f
->windows
)
411 if (window_matches(w
, prot
, offset
, size
))
417 c
= context_add(m
, context
);
421 context_attach_window(c
, w
);
422 w
->keep_always
= w
->keep_always
|| keep_always
;
424 *ret
= (uint8_t*) w
->ptr
+ (offset
- w
->offset
);
426 *ret_size
= w
->size
- (offset
- w
->offset
);
431 static int mmap_try_harder(MMapCache
*m
, void *addr
, MMapFileDescriptor
*f
, int prot
, int flags
, uint64_t offset
, size_t size
, void **res
) {
441 ptr
= mmap(addr
, size
, prot
, flags
, f
->fd
, offset
);
442 if (ptr
!= MAP_FAILED
)
445 return negative_errno();
460 MMapFileDescriptor
*f
,
470 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
);
511 r
= mmap_try_harder(m
, NULL
, f
, prot
, MAP_SHARED
, woffset
, wsize
, &d
);
515 c
= context_add(m
, context
);
519 w
= window_add(m
, f
, prot
, keep_always
, woffset
, wsize
, d
);
523 context_attach_window(c
, w
);
525 *ret
= (uint8_t*) w
->ptr
+ (offset
- w
->offset
);
527 *ret_size
= w
->size
- (offset
- w
->offset
);
532 (void) munmap(d
, wsize
);
538 MMapFileDescriptor
*f
,
551 assert(m
->n_ref
> 0);
555 assert(context
< MMAP_CACHE_MAX_CONTEXTS
);
557 /* Check whether the current context is the right one already */
558 r
= try_context(m
, f
, prot
, context
, keep_always
, offset
, size
, ret
, ret_size
);
564 /* Search for a matching mmap */
565 r
= find_mmap(m
, f
, prot
, context
, keep_always
, offset
, size
, ret
, ret_size
);
573 /* Create a new mmap */
574 return add_mmap(m
, f
, prot
, context
, keep_always
, offset
, size
, st
, ret
, ret_size
);
577 unsigned mmap_cache_get_hit(MMapCache
*m
) {
583 unsigned mmap_cache_get_missed(MMapCache
*m
) {
589 static void mmap_cache_process_sigbus(MMapCache
*m
) {
591 MMapFileDescriptor
*f
;
597 /* Iterate through all triggered pages and mark their files as
603 r
= sigbus_pop(&addr
);
604 if (_likely_(r
== 0))
607 log_error_errno(r
, "SIGBUS handling failed: %m");
612 HASHMAP_FOREACH(f
, m
->fds
, i
) {
615 LIST_FOREACH(by_fd
, w
, f
->windows
) {
616 if ((uint8_t*) addr
>= (uint8_t*) w
->ptr
&&
617 (uint8_t*) addr
< (uint8_t*) w
->ptr
+ w
->size
) {
618 found
= ours
= f
->sigbus
= true;
627 /* Didn't find a matching window, give up */
629 log_error("Unknown SIGBUS page, aborting.");
634 /* The list of triggered pages is now empty. Now, let's remap
635 * all windows of the triggered file to anonymous maps, so
636 * that no page of the file in question is triggered again, so
637 * that we can be sure not to hit the queue size limit. */
638 if (_likely_(!found
))
641 HASHMAP_FOREACH(f
, m
->fds
, i
) {
647 LIST_FOREACH(by_fd
, w
, f
->windows
)
648 window_invalidate(w
);
652 bool mmap_cache_got_sigbus(MMapCache
*m
, MMapFileDescriptor
*f
) {
656 mmap_cache_process_sigbus(m
);
661 MMapFileDescriptor
* mmap_cache_add_fd(MMapCache
*m
, int fd
) {
662 MMapFileDescriptor
*f
;
668 f
= hashmap_get(m
->fds
, FD_TO_PTR(fd
));
672 r
= hashmap_ensure_allocated(&m
->fds
, NULL
);
676 f
= new0(MMapFileDescriptor
, 1);
683 r
= hashmap_put(m
->fds
, FD_TO_PTR(fd
), f
);
690 void mmap_cache_free_fd(MMapCache
*m
, MMapFileDescriptor
*f
) {
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
);
701 window_free(f
->windows
);
704 assert_se(hashmap_remove(f
->cache
->fds
, FD_TO_PTR(f
->fd
)));