chiark / gitweb /
mac: also rename use_{smack,selinux,apparmor}() calls so that they share the new...
[elogind.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         unsigned 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         unsigned n_hit, n_missed;
76
77
78         Hashmap *fds;
79         Hashmap *contexts;
80
81         LIST_HEAD(Window, unused);
82         Window *last_unused;
83 };
84
85 #define WINDOWS_MIN 64
86 #define WINDOW_SIZE (8ULL*1024ULL*1024ULL)
87
88 MMapCache* mmap_cache_new(void) {
89         MMapCache *m;
90
91         m = new0(MMapCache, 1);
92         if (!m)
93                 return NULL;
94
95         m->n_ref = 1;
96         return m;
97 }
98
99 MMapCache* mmap_cache_ref(MMapCache *m) {
100         assert(m);
101         assert(m->n_ref > 0);
102
103         m->n_ref ++;
104         return m;
105 }
106
107 static void window_unlink(Window *w) {
108         Context *c;
109
110         assert(w);
111
112         if (w->ptr)
113                 munmap(w->ptr, w->size);
114
115         if (w->fd)
116                 LIST_REMOVE(by_fd, w->fd->windows, w);
117
118         if (w->in_unused) {
119                 if (w->cache->last_unused == w)
120                         w->cache->last_unused = w->unused_prev;
121
122                 LIST_REMOVE(unused, w->cache->unused, w);
123         }
124
125         LIST_FOREACH(by_window, c, w->contexts) {
126                 assert(c->window == w);
127                 c->window = NULL;
128         }
129 }
130
131 static void window_free(Window *w) {
132         assert(w);
133
134         window_unlink(w);
135         w->cache->n_windows--;
136         free(w);
137 }
138
139 _pure_ static bool window_matches(Window *w, int fd, int prot, uint64_t offset, size_t size) {
140         assert(w);
141         assert(fd >= 0);
142         assert(size > 0);
143
144         return
145                 w->fd &&
146                 fd == w->fd->fd &&
147                 prot == w->prot &&
148                 offset >= w->offset &&
149                 offset + size <= w->offset + w->size;
150 }
151
152 static Window *window_add(MMapCache *m) {
153         Window *w;
154
155         assert(m);
156
157         if (!m->last_unused || m->n_windows <= WINDOWS_MIN) {
158
159                 /* Allocate a new window */
160                 w = new0(Window, 1);
161                 if (!w)
162                         return NULL;
163                 m->n_windows++;
164         } else {
165
166                 /* Reuse an existing one */
167                 w = m->last_unused;
168                 window_unlink(w);
169                 zero(*w);
170         }
171
172         w->cache = m;
173         return w;
174 }
175
176 static void context_detach_window(Context *c) {
177         Window *w;
178
179         assert(c);
180
181         if (!c->window)
182                 return;
183
184         w = c->window;
185         c->window = NULL;
186         LIST_REMOVE(by_window, w->contexts, c);
187
188         if (!w->contexts && w->keep_always == 0) {
189                 /* Not used anymore? */
190                 LIST_PREPEND(unused, c->cache->unused, w);
191                 if (!c->cache->last_unused)
192                         c->cache->last_unused = w;
193
194                 w->in_unused = true;
195         }
196 }
197
198 static void context_attach_window(Context *c, Window *w) {
199         assert(c);
200         assert(w);
201
202         if (c->window == w)
203                 return;
204
205         context_detach_window(c);
206
207         if (w->in_unused) {
208                 /* Used again? */
209                 LIST_REMOVE(unused, c->cache->unused, w);
210                 if (c->cache->last_unused == w)
211                         c->cache->last_unused = w->unused_prev;
212
213                 w->in_unused = false;
214         }
215
216         c->window = w;
217         LIST_PREPEND(by_window, w->contexts, c);
218 }
219
220 static Context *context_add(MMapCache *m, unsigned id) {
221         Context *c;
222         int r;
223
224         assert(m);
225
226         c = hashmap_get(m->contexts, UINT_TO_PTR(id + 1));
227         if (c)
228                 return c;
229
230         r = hashmap_ensure_allocated(&m->contexts, NULL);
231         if (r < 0)
232                 return NULL;
233
234         c = new0(Context, 1);
235         if (!c)
236                 return NULL;
237
238         c->cache = m;
239         c->id = id;
240
241         r = hashmap_put(m->contexts, UINT_TO_PTR(id + 1), c);
242         if (r < 0) {
243                 free(c);
244                 return NULL;
245         }
246
247         return c;
248 }
249
250 static void context_free(Context *c) {
251         assert(c);
252
253         context_detach_window(c);
254
255         if (c->cache)
256                 assert_se(hashmap_remove(c->cache->contexts, UINT_TO_PTR(c->id + 1)));
257
258         free(c);
259 }
260
261 static void fd_free(FileDescriptor *f) {
262         assert(f);
263
264         while (f->windows)
265                 window_free(f->windows);
266
267         if (f->cache)
268                 assert_se(hashmap_remove(f->cache->fds, INT_TO_PTR(f->fd + 1)));
269
270         free(f);
271 }
272
273 static FileDescriptor* fd_add(MMapCache *m, int fd) {
274         FileDescriptor *f;
275         int r;
276
277         assert(m);
278         assert(fd >= 0);
279
280         f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
281         if (f)
282                 return f;
283
284         r = hashmap_ensure_allocated(&m->fds, NULL);
285         if (r < 0)
286                 return NULL;
287
288         f = new0(FileDescriptor, 1);
289         if (!f)
290                 return NULL;
291
292         f->cache = m;
293         f->fd = fd;
294
295         r = hashmap_put(m->fds, UINT_TO_PTR(fd + 1), f);
296         if (r < 0) {
297                 free(f);
298                 return NULL;
299         }
300
301         return f;
302 }
303
304 static void mmap_cache_free(MMapCache *m) {
305         Context *c;
306         FileDescriptor *f;
307
308         assert(m);
309
310         while ((c = hashmap_first(m->contexts)))
311                 context_free(c);
312
313         hashmap_free(m->contexts);
314
315         while ((f = hashmap_first(m->fds)))
316                 fd_free(f);
317
318         hashmap_free(m->fds);
319
320         while (m->unused)
321                 window_free(m->unused);
322
323         free(m);
324 }
325
326 MMapCache* mmap_cache_unref(MMapCache *m) {
327         assert(m);
328         assert(m->n_ref > 0);
329
330         m->n_ref --;
331         if (m->n_ref == 0)
332                 mmap_cache_free(m);
333
334         return NULL;
335 }
336
337 static int make_room(MMapCache *m) {
338         assert(m);
339
340         if (!m->last_unused)
341                 return 0;
342
343         window_free(m->last_unused);
344         return 1;
345 }
346
347 static int try_context(
348                 MMapCache *m,
349                 int fd,
350                 int prot,
351                 unsigned context,
352                 bool keep_always,
353                 uint64_t offset,
354                 size_t size,
355                 void **ret,
356                 void **release_cookie) {
357
358         Context *c;
359
360         assert(m);
361         assert(m->n_ref > 0);
362         assert(fd >= 0);
363         assert(size > 0);
364
365         c = hashmap_get(m->contexts, UINT_TO_PTR(context+1));
366         if (!c)
367                 return 0;
368
369         assert(c->id == context);
370
371         if (!c->window)
372                 return 0;
373
374         if (!window_matches(c->window, fd, prot, offset, size)) {
375
376                 /* Drop the reference to the window, since it's unnecessary now */
377                 context_detach_window(c);
378                 return 0;
379         }
380
381         c->window->keep_always += keep_always;
382
383         if (ret)
384                 *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset);
385         if (keep_always && release_cookie)
386                 *release_cookie = c->window;
387         return 1;
388 }
389
390 static int find_mmap(
391                 MMapCache *m,
392                 int fd,
393                 int prot,
394                 unsigned context,
395                 bool keep_always,
396                 uint64_t offset,
397                 size_t size,
398                 void **ret,
399                 void **release_cookie) {
400
401         FileDescriptor *f;
402         Window *w;
403         Context *c;
404
405         assert(m);
406         assert(m->n_ref > 0);
407         assert(fd >= 0);
408         assert(size > 0);
409
410         f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
411         if (!f)
412                 return 0;
413
414         assert(f->fd == fd);
415
416         LIST_FOREACH(by_fd, w, f->windows)
417                 if (window_matches(w, fd, prot, offset, size))
418                         break;
419
420         if (!w)
421                 return 0;
422
423         c = context_add(m, context);
424         if (!c)
425                 return -ENOMEM;
426
427         context_attach_window(c, w);
428         w->keep_always += keep_always;
429
430         if (ret)
431                 *ret = (uint8_t*) w->ptr + (offset - w->offset);
432         if (keep_always && release_cookie)
433                 *release_cookie = c->window;
434         return 1;
435 }
436
437 static int add_mmap(
438                 MMapCache *m,
439                 int fd,
440                 int prot,
441                 unsigned context,
442                 bool keep_always,
443                 uint64_t offset,
444                 size_t size,
445                 struct stat *st,
446                 void **ret,
447                 void **release_cookie) {
448
449         uint64_t woffset, wsize;
450         Context *c;
451         FileDescriptor *f;
452         Window *w;
453         void *d;
454         int r;
455
456         assert(m);
457         assert(m->n_ref > 0);
458         assert(fd >= 0);
459         assert(size > 0);
460
461         woffset = offset & ~((uint64_t) page_size() - 1ULL);
462         wsize = size + (offset - woffset);
463         wsize = PAGE_ALIGN(wsize);
464
465         if (wsize < WINDOW_SIZE) {
466                 uint64_t delta;
467
468                 delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
469
470                 if (delta > offset)
471                         woffset = 0;
472                 else
473                         woffset -= delta;
474
475                 wsize = WINDOW_SIZE;
476         }
477
478         if (st) {
479                 /* Memory maps that are larger then the files
480                    underneath have undefined behavior. Hence, clamp
481                    things to the file size if we know it */
482
483                 if (woffset >= (uint64_t) st->st_size)
484                         return -EADDRNOTAVAIL;
485
486                 if (woffset + wsize > (uint64_t) st->st_size)
487                         wsize = PAGE_ALIGN(st->st_size - woffset);
488         }
489
490         for (;;) {
491                 d = mmap(NULL, wsize, prot, MAP_SHARED, fd, woffset);
492                 if (d != MAP_FAILED)
493                         break;
494                 if (errno != ENOMEM)
495                         return -errno;
496
497                 r = make_room(m);
498                 if (r < 0)
499                         return r;
500                 if (r == 0)
501                         return -ENOMEM;
502         }
503
504         c = context_add(m, context);
505         if (!c)
506                 goto outofmem;
507
508         f = fd_add(m, fd);
509         if (!f)
510                 goto outofmem;
511
512         w = window_add(m);
513         if (!w)
514                 goto outofmem;
515
516         w->keep_always = keep_always;
517         w->ptr = d;
518         w->offset = woffset;
519         w->prot = prot;
520         w->size = wsize;
521         w->fd = f;
522
523         LIST_PREPEND(by_fd, f->windows, w);
524
525         context_detach_window(c);
526         c->window = w;
527         LIST_PREPEND(by_window, w->contexts, c);
528
529         if (ret)
530                 *ret = (uint8_t*) w->ptr + (offset - w->offset);
531         if (keep_always && release_cookie)
532                 *release_cookie = c->window;
533         return 1;
534
535 outofmem:
536         munmap(d, wsize);
537         return -ENOMEM;
538 }
539
540 int mmap_cache_get(
541                 MMapCache *m,
542                 int fd,
543                 int prot,
544                 unsigned context,
545                 bool keep_always,
546                 uint64_t offset,
547                 size_t size,
548                 struct stat *st,
549                 void **ret,
550                 void **release_cookie) {
551
552         int r;
553
554         assert(m);
555         assert(m->n_ref > 0);
556         assert(fd >= 0);
557         assert(size > 0);
558
559         /* Check whether the current context is the right one already */
560         r = try_context(m, fd, prot, context, keep_always, offset, size, ret, release_cookie);
561         if (r != 0) {
562                 m->n_hit ++;
563                 return r;
564         }
565
566         /* Search for a matching mmap */
567         r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret, release_cookie);
568         if (r != 0) {
569                 m->n_hit ++;
570                 return r;
571         }
572
573         m->n_missed++;
574
575         /* Create a new mmap */
576         return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret, release_cookie);
577 }
578
579 int mmap_cache_release(
580                 MMapCache *m,
581                 int fd,
582                 void *release_cookie) {
583
584         FileDescriptor *f;
585         Window *w;
586
587         assert(m);
588         assert(m->n_ref > 0);
589         assert(fd >= 0);
590
591         f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
592         if (!f)
593                 return -EBADF;
594
595         assert(f->fd == fd);
596
597         LIST_FOREACH(by_fd, w, f->windows)
598                 if (w == release_cookie)
599                         break;
600
601         if (!w)
602                 return -ENOENT;
603
604         if (w->keep_always == 0)
605                 return -ENOLCK;
606
607         w->keep_always -= 1;
608         return 0;
609 }
610
611 void mmap_cache_close_fd(MMapCache *m, int fd) {
612         FileDescriptor *f;
613
614         assert(m);
615         assert(fd >= 0);
616
617         f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
618         if (!f)
619                 return;
620
621         fd_free(f);
622 }
623
624 void mmap_cache_close_context(MMapCache *m, unsigned context) {
625         Context *c;
626
627         assert(m);
628
629         c = hashmap_get(m->contexts, UINT_TO_PTR(context + 1));
630         if (!c)
631                 return;
632
633         context_free(c);
634 }
635
636 unsigned mmap_cache_get_hit(MMapCache *m) {
637         assert(m);
638
639         return m->n_hit;
640 }
641
642 unsigned mmap_cache_get_missed(MMapCache *m) {
643         assert(m);
644
645         return m->n_missed;
646 }