]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/mmap-cache.c
68dbe7015bce8cb05c253c3f8fb5b426ce1c8c74
[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 <assert.h>
23 #include <sys/mman.h>
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "util.h"
29
30 #include "mmap-cache.h"
31
32 #define WINDOW_SIZE (8ULL*1024ULL*1024ULL)
33 #define WINDOWS_MAX 32
34
35 typedef struct Window {
36 int fd;
37 void *ptr;
38 uint64_t offset;
39 uint64_t size;
40
41 unsigned n_ref;
42 unsigned lru_prev;
43 unsigned lru_next;
44
45 unsigned by_fd_prev;
46 unsigned by_fd_next;
47 } Window;
48
49 typedef struct FileDescriptor {
50 int fd;
51 unsigned windows;
52 } FileDescriptor;
53
54 struct MMapCache {
55 unsigned n_ref;
56
57 unsigned contexts_max;
58 unsigned windows_max;
59 unsigned fds_max;
60
61 unsigned n_windows;
62 unsigned n_fds;
63
64 unsigned lru_first, lru_last;
65
66 Window *windows;
67 unsigned *by_context;
68 FileDescriptor *by_fd;
69 };
70
71 static void mmap_cache_window_unmap(MMapCache *m, unsigned w) {
72 Window *v;
73
74 assert(m);
75 assert(w < m->n_windows);
76
77 v = m->windows + w;
78 if (!v->ptr)
79 return;
80
81 munmap(v->ptr, v->size);
82 v->ptr = NULL;
83 }
84
85 static void mmap_cache_window_add_lru(MMapCache *m, unsigned w) {
86 Window *v;
87
88 assert(m);
89 assert(w < m->n_windows);
90
91 v = m->windows + w;
92 v->lru_prev = m->lru_last;
93 v->lru_next = (unsigned) -1;
94
95 m->lru_last = w;
96 if (m->lru_first == (unsigned) -1)
97 m->lru_first = w;
98 }
99
100 static void mmap_cache_window_remove_lru(MMapCache *m, unsigned w) {
101 Window *v;
102
103 assert(m);
104 assert(w < m->n_windows);
105
106 v = m->windows + w;
107
108 if (v->lru_prev == (unsigned) -1)
109 m->lru_first = v->lru_next;
110 else
111 m->windows[v->lru_prev].lru_next = v->lru_next;
112
113 if (v->lru_next == (unsigned) -1)
114 m->lru_last = v->lru_prev;
115 else
116 m->windows[v->lru_next].lru_prev = v->lru_prev;
117 }
118
119 static void mmap_cache_fd_add(MMapCache *m, unsigned fd_index, unsigned w) {
120 Window *v;
121
122 assert(m);
123 assert(fd_index < m->n_fds);
124
125 v = m->windows + w;
126 v->by_fd_next = m->by_fd[fd_index].windows;
127 v->by_fd_prev = (unsigned) -1;
128
129 m->by_fd[fd_index].windows = w;
130 }
131
132 static void mmap_cache_fd_remove(MMapCache *m, unsigned fd_index, unsigned w) {
133 Window *v;
134
135 assert(m);
136 assert(fd_index < m->n_fds);
137
138 v = m->windows + w;
139 if (v->by_fd_prev == (unsigned) -1)
140 m->by_fd[fd_index].windows = v->by_fd_next;
141 else
142 m->windows[v->by_fd_prev].by_fd_next = v->by_fd_next;
143
144 if (v->by_fd_next != (unsigned) -1)
145 m->windows[v->by_fd_next].by_fd_prev = v->by_fd_prev;
146 }
147
148 static void mmap_cache_context_unset(MMapCache *m, unsigned c) {
149 Window *v;
150 unsigned w;
151
152 assert(m);
153 assert(c < m->contexts_max);
154
155 if (m->by_context[c] == (unsigned) -1)
156 return;
157
158 w = m->by_context[c];
159 m->by_context[c] = (unsigned) -1;
160
161 v = m->windows + w;
162 assert(v->n_ref > 0);
163 v->n_ref --;
164
165 if (v->n_ref == 0)
166 mmap_cache_window_add_lru(m, w);
167 }
168
169 static void mmap_cache_context_set(MMapCache *m, unsigned c, unsigned w) {
170 Window *v;
171
172 assert(m);
173 assert(c < m->contexts_max);
174 assert(w < m->n_windows);
175
176 if (m->by_context[c] == w)
177 return;
178
179 mmap_cache_context_unset(m, c);
180
181 m->by_context[c] = w;
182
183 v = m->windows + w;
184 v->n_ref ++;
185 if (v->n_ref == 1)
186 mmap_cache_window_remove_lru(m, w);
187 }
188
189 static void mmap_cache_free(MMapCache *m) {
190
191 assert(m);
192
193 if (m->windows) {
194 unsigned w;
195
196 for (w = 0; w < m->n_windows; w++)
197 mmap_cache_window_unmap(m, w);
198
199 free(m->windows);
200 }
201
202 free(m->by_context);
203 free(m->by_fd);
204 free(m);
205 }
206
207 MMapCache* mmap_cache_new(unsigned contexts_max, unsigned fds_max) {
208 MMapCache *m;
209
210 assert(contexts_max > 0);
211 assert(fds_max > 0);
212
213 m = new0(MMapCache, 1);
214 if (!m)
215 return NULL;
216
217 m->contexts_max = contexts_max;
218 m->fds_max = fds_max;
219 m->windows_max = MAX(m->contexts_max, WINDOWS_MAX);
220 m->n_ref = 1;
221 m->lru_first = (unsigned) -1;
222 m->lru_last = (unsigned) -1;
223
224 m->windows = new(Window, m->windows_max);
225 if (!m->windows) {
226 mmap_cache_free(m);
227 return NULL;
228 }
229
230 m->by_context = new(unsigned, m->contexts_max);
231 if (!m->by_context) {
232 mmap_cache_free(m);
233 return NULL;
234 }
235
236 memset(m->by_context, -1, m->contexts_max * sizeof(unsigned));
237
238 m->by_fd = new(FileDescriptor, m->fds_max);
239 if (!m->by_fd) {
240 mmap_cache_free(m);
241 return NULL;
242 }
243
244 return m;
245 }
246
247 MMapCache* mmap_cache_ref(MMapCache *m) {
248 assert(m);
249 assert(m->n_ref > 0);
250
251 m->n_ref++;
252 return m;
253 }
254
255 MMapCache* mmap_cache_unref(MMapCache *m) {
256 assert(m);
257 assert(m->n_ref > 0);
258
259 if (m->n_ref == 1)
260 mmap_cache_free(m);
261 else
262 m->n_ref--;
263
264 return NULL;
265 }
266
267 static int mmap_cache_allocate_window(MMapCache *m, unsigned *w) {
268 assert(m);
269 assert(w);
270
271 if (m->n_windows < m->windows_max) {
272 *w = m->n_windows ++;
273 return 0;
274 }
275
276 if (m->lru_first == (unsigned) -1)
277 return -E2BIG;
278
279 *w = m->lru_first;
280 mmap_cache_window_unmap(m, *w);
281 mmap_cache_window_remove_lru(m, *w);
282
283 return 0;
284 }
285
286 static int mmap_cache_make_room(MMapCache *m) {
287 unsigned w;
288
289 assert(m);
290
291 w = m->lru_first;
292 while (w != (unsigned) -1) {
293 Window *v;
294
295 v = m->windows + w;
296
297 if (v->ptr) {
298 mmap_cache_window_unmap(m, w);
299 return 1;
300 }
301
302 w = v->lru_next;
303 }
304
305 return 0;
306 }
307
308 static int mmap_cache_put(
309 MMapCache *m,
310 int fd,
311 unsigned fd_index,
312 int prot,
313 unsigned context,
314 uint64_t offset,
315 uint64_t size,
316 void **ret) {
317
318 unsigned w;
319 Window *v;
320 void *d;
321 uint64_t woffset, wsize;
322 int r;
323
324 assert(m);
325 assert(fd >= 0);
326 assert(context < m->contexts_max);
327 assert(size > 0);
328 assert(ret);
329
330 woffset = offset & ~((uint64_t) page_size() - 1ULL);
331 wsize = size + (offset - woffset);
332 wsize = PAGE_ALIGN(wsize);
333
334 if (wsize < WINDOW_SIZE) {
335 uint64_t delta;
336
337 delta = (WINDOW_SIZE - wsize) / 2;
338
339 if (delta > offset)
340 woffset = 0;
341 else
342 woffset -= delta;
343
344 wsize = WINDOW_SIZE;
345 }
346
347 for (;;) {
348 d = mmap(NULL, wsize, prot, MAP_SHARED, fd, woffset);
349 if (d != MAP_FAILED)
350 break;
351 if (errno != ENOMEM)
352 return -errno;
353
354 r = mmap_cache_make_room(m);
355 if (r < 0)
356 return r;
357 if (r == 0)
358 return -ENOMEM;
359 }
360
361 r = mmap_cache_allocate_window(m, &w);
362 if (r < 0) {
363 munmap(d, wsize);
364 return r;
365 }
366
367 v = m->windows + w;
368 v->fd = fd;
369 v->ptr = d;
370 v->offset = woffset;
371 v->size = wsize;
372
373 v->n_ref = 0;
374 v->lru_prev = v->lru_next = (unsigned) -1;
375
376 mmap_cache_fd_add(m, fd_index, w);
377 mmap_cache_context_set(m, context, w);
378
379 *ret = (uint8_t*) d + (offset - woffset);
380 return 1;
381 }
382
383 static int fd_cmp(const void *_a, const void *_b) {
384 const FileDescriptor *a = _a, *b = _b;
385
386 if (a->fd < b->fd)
387 return -1;
388 if (a->fd > b->fd)
389 return 1;
390
391 return 0;
392 }
393
394 static int mmap_cache_get_fd_index(MMapCache *m, int fd, unsigned *fd_index) {
395 FileDescriptor *j;
396
397 assert(m);
398 assert(fd >= 0);
399 assert(fd_index);
400
401 j = bsearch(&fd, m->by_fd, m->n_fds, sizeof(m->by_fd[0]), fd_cmp);
402 if (!j) {
403 if (m->n_fds >= m->fds_max)
404 return -E2BIG;
405
406 j = m->by_fd + m->n_fds ++;
407 j->fd = fd;
408 j->windows = (unsigned) -1;
409
410 qsort(m->by_fd, m->n_fds, sizeof(m->by_fd[0]), fd_cmp);
411 j = bsearch(&fd, m->by_fd, m->n_fds, sizeof(m->by_fd[0]), fd_cmp);
412 }
413
414 *fd_index = (unsigned) (j - m->by_fd);
415 return 0;
416 }
417
418 static bool mmap_cache_test_window(
419 MMapCache *m,
420 unsigned w,
421 uint64_t offset,
422 uint64_t size) {
423 Window *v;
424
425 assert(m);
426 assert(w < m->n_windows);
427 assert(size > 0);
428
429 v = m->windows + w;
430
431 return offset >= v->offset &&
432 offset + size <= v->offset + v->size;
433 }
434
435 static int mmap_cache_current(
436 MMapCache *m,
437 int fd,
438 unsigned context,
439 uint64_t offset,
440 uint64_t size,
441 void **ret) {
442
443 Window *v;
444 unsigned w;
445
446 assert(m);
447 assert(fd >= 0);
448 assert(context < m->contexts_max);
449 assert(size > 0);
450 assert(ret);
451
452 if (m->by_context[context] == (unsigned) -1)
453 return 0;
454
455 w = m->by_context[context];
456 v = m->windows + w;
457
458 if (v->fd != fd)
459 return 0;
460
461 if (!mmap_cache_test_window(m, w, offset, size))
462 return 0;
463
464 *ret = (uint8_t*) v->ptr + (offset - v->offset);
465 return 1;
466 }
467
468 static int mmap_cache_find(
469 MMapCache *m,
470 unsigned fd_index,
471 unsigned context,
472 uint64_t offset,
473 uint64_t size,
474 void **ret) {
475
476 Window *v = NULL;
477 unsigned w;
478
479 assert(m);
480 assert(fd_index < m->n_fds);
481 assert(context < m->contexts_max);
482 assert(size > 0);
483 assert(ret);
484
485 w = m->by_fd[fd_index].windows;
486 while (w != (unsigned) -1) {
487 if (mmap_cache_test_window(m, w, offset, size))
488 break;
489
490 w = m->windows[w].by_fd_next;
491 }
492
493 if (w == (unsigned) -1)
494 return 0;
495
496 mmap_cache_context_set(m, context, w);
497
498 v = m->windows + w;
499 *ret = (uint8_t*) v->ptr + (offset - v->offset);
500 return 1;
501 }
502
503 int mmap_cache_get(
504 MMapCache *m,
505 int fd,
506 int prot,
507 unsigned context,
508 uint64_t offset,
509 uint64_t size,
510 void **ret) {
511
512 unsigned fd_index;
513 int r;
514
515 assert(m);
516 assert(fd >= 0);
517 assert(context < m->contexts_max);
518 assert(size > 0);
519 assert(ret);
520
521 /* Maybe the current pointer for this context is already the
522 * right one? */
523 r = mmap_cache_current(m, fd, context, offset, size, ret);
524 if (r != 0)
525 return r;
526
527 /* OK, let's find the chain for this FD */
528 r = mmap_cache_get_fd_index(m, fd, &fd_index);
529 if (r < 0)
530 return r;
531
532 /* And let's look through the available mmaps */
533 r = mmap_cache_find(m, fd_index, context, offset, size, ret);
534 if (r != 0)
535 return r;
536
537 /* Not found? Then, let's add it */
538 return mmap_cache_put(m, fd, fd_index, prot, context, offset, size, ret);
539 }
540
541 void mmap_cache_close_fd(MMapCache *m, int fd) {
542 FileDescriptor *j;
543 unsigned fd_index, c, w;
544
545 assert(m);
546 assert(fd > 0);
547
548 j = bsearch(&fd, m->by_fd, m->n_fds, sizeof(m->by_fd[0]), fd_cmp);
549 if (!j)
550 return;
551 fd_index = (unsigned) (j - m->by_fd);
552
553 for (c = 0; c < m->contexts_max; c++) {
554 w = m->by_context[c];
555 if (w == (unsigned) -1)
556 continue;
557
558 if (m->windows[w].fd == fd)
559 mmap_cache_context_unset(m, c);
560 }
561
562 w = m->by_fd[fd_index].windows;
563 while (w != (unsigned) -1) {
564
565 mmap_cache_fd_remove(m, fd_index, w);
566 mmap_cache_window_unmap(m, w);
567
568 w = m->by_fd[fd_index].windows;
569 }
570
571 memmove(m->by_fd + fd_index, m->by_fd + fd_index + 1, (m->n_fds - (fd_index + 1)) * sizeof(FileDescriptor));
572 m->n_fds --;
573 }
574
575 void mmap_cache_close_context(MMapCache *m, unsigned context) {
576 mmap_cache_context_unset(m, context);
577 }