chiark / gitweb /
bus: properly handle if new objects are installed in the node tree while we are dispa...
[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         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(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(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(by_window, w->contexts, c);
184
185         if (!w->contexts && !w->keep_always) {
186                 /* Not used anymore? */
187                 LIST_PREPEND(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(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(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         hashmap_free(m->contexts);
311
312         while ((f = hashmap_first(m->fds)))
313                 fd_free(f);
314
315         hashmap_free(m->fds);
316
317         while (m->unused)
318                 window_free(m->unused);
319
320         free(m);
321 }
322
323 MMapCache* mmap_cache_unref(MMapCache *m) {
324         assert(m);
325         assert(m->n_ref > 0);
326
327         m->n_ref --;
328         if (m->n_ref == 0)
329                 mmap_cache_free(m);
330
331         return NULL;
332 }
333
334 static int make_room(MMapCache *m) {
335         assert(m);
336
337         if (!m->last_unused)
338                 return 0;
339
340         window_free(m->last_unused);
341         return 1;
342 }
343
344 static int try_context(
345                 MMapCache *m,
346                 int fd,
347                 int prot,
348                 unsigned context,
349                 bool keep_always,
350                 uint64_t offset,
351                 size_t size,
352                 void **ret) {
353
354         Context *c;
355
356         assert(m);
357         assert(m->n_ref > 0);
358         assert(fd >= 0);
359         assert(size > 0);
360         assert(ret);
361
362         c = hashmap_get(m->contexts, UINT_TO_PTR(context+1));
363         if (!c)
364                 return 0;
365
366         assert(c->id == context);
367
368         if (!c->window)
369                 return 0;
370
371         if (!window_matches(c->window, fd, prot, offset, size)) {
372
373                 /* Drop the reference to the window, since it's unnecessary now */
374                 context_detach_window(c);
375                 return 0;
376         }
377
378         c->window->keep_always = c->window->keep_always || keep_always;
379
380         *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset);
381         return 1;
382 }
383
384 static int find_mmap(
385                 MMapCache *m,
386                 int fd,
387                 int prot,
388                 unsigned context,
389                 bool keep_always,
390                 uint64_t offset,
391                 size_t size,
392                 void **ret) {
393
394         FileDescriptor *f;
395         Window *w;
396         Context *c;
397
398         assert(m);
399         assert(m->n_ref > 0);
400         assert(fd >= 0);
401         assert(size > 0);
402         assert(ret);
403
404         f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
405         if (!f)
406                 return 0;
407
408         assert(f->fd == fd);
409
410         LIST_FOREACH(by_fd, w, f->windows)
411                 if (window_matches(w, fd, prot, offset, size))
412                         break;
413
414         if (!w)
415                 return 0;
416
417         c = context_add(m, context);
418         if (!c)
419                 return -ENOMEM;
420
421         context_attach_window(c, w);
422         w->keep_always = w->keep_always || keep_always;
423
424         *ret = (uint8_t*) w->ptr + (offset - w->offset);
425         return 1;
426 }
427
428 static int add_mmap(
429                 MMapCache *m,
430                 int fd,
431                 int prot,
432                 unsigned context,
433                 bool keep_always,
434                 uint64_t offset,
435                 size_t size,
436                 struct stat *st,
437                 void **ret) {
438
439         uint64_t woffset, wsize;
440         Context *c;
441         FileDescriptor *f;
442         Window *w;
443         void *d;
444         int r;
445
446         assert(m);
447         assert(m->n_ref > 0);
448         assert(fd >= 0);
449         assert(size > 0);
450         assert(ret);
451
452         woffset = offset & ~((uint64_t) page_size() - 1ULL);
453         wsize = size + (offset - woffset);
454         wsize = PAGE_ALIGN(wsize);
455
456         if (wsize < WINDOW_SIZE) {
457                 uint64_t delta;
458
459                 delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
460
461                 if (delta > offset)
462                         woffset = 0;
463                 else
464                         woffset -= delta;
465
466                 wsize = WINDOW_SIZE;
467         }
468
469         if (st) {
470                 /* Memory maps that are larger then the files
471                    underneath have undefined behavior. Hence, clamp
472                    things to the file size if we know it */
473
474                 if (woffset >= (uint64_t) st->st_size)
475                         return -EADDRNOTAVAIL;
476
477                 if (woffset + wsize > (uint64_t) st->st_size)
478                         wsize = PAGE_ALIGN(st->st_size - woffset);
479         }
480
481         for (;;) {
482                 d = mmap(NULL, wsize, prot, MAP_SHARED, fd, woffset);
483                 if (d != MAP_FAILED)
484                         break;
485                 if (errno != ENOMEM)
486                         return -errno;
487
488                 r = make_room(m);
489                 if (r < 0)
490                         return r;
491                 if (r == 0)
492                         return -ENOMEM;
493         }
494
495         c = context_add(m, context);
496         if (!c)
497                 return -ENOMEM;
498
499         f = fd_add(m, fd);
500         if (!f)
501                 return -ENOMEM;
502
503         w = window_add(m);
504         if (!w)
505                 return -ENOMEM;
506
507         w->keep_always = keep_always;
508         w->ptr = d;
509         w->offset = woffset;
510         w->prot = prot;
511         w->size = wsize;
512         w->fd = f;
513
514         LIST_PREPEND(by_fd, f->windows, w);
515
516         context_detach_window(c);
517         c->window = w;
518         LIST_PREPEND(by_window, w->contexts, c);
519
520         *ret = (uint8_t*) w->ptr + (offset - w->offset);
521         return 1;
522 }
523
524 int mmap_cache_get(
525                 MMapCache *m,
526                 int fd,
527                 int prot,
528                 unsigned context,
529                 bool keep_always,
530                 uint64_t offset,
531                 size_t size,
532                 struct stat *st,
533                 void **ret) {
534
535         int r;
536
537         assert(m);
538         assert(m->n_ref > 0);
539         assert(fd >= 0);
540         assert(size > 0);
541         assert(ret);
542
543         /* Check whether the current context is the right one already */
544         r = try_context(m, fd, prot, context, keep_always, offset, size, ret);
545         if (r != 0)
546                 return r;
547
548         /* Search for a matching mmap */
549         r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret);
550         if (r != 0)
551                 return r;
552
553         /* Create a new mmap */
554         return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret);
555 }
556
557 void mmap_cache_close_fd(MMapCache *m, int fd) {
558         FileDescriptor *f;
559
560         assert(m);
561         assert(fd >= 0);
562
563         f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
564         if (!f)
565                 return;
566
567         fd_free(f);
568 }
569
570 void mmap_cache_close_context(MMapCache *m, unsigned context) {
571         Context *c;
572
573         assert(m);
574
575         c = hashmap_get(m->contexts, UINT_TO_PTR(context + 1));
576         if (!c)
577                 return;
578
579         context_free(c);
580 }