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