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