mmap-cache.c revision b26fa1a2fbcfee7d03b0c8fd15ec3aa64ae70b9f
/***
This file is part of systemd.
Copyright 2012 Lennart Poettering
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <stdlib.h>
#include "alloc-util.h"
#include "fd-util.h"
#include "hashmap.h"
#include "list.h"
#include "log.h"
#include "macro.h"
#include "mmap-cache.h"
#include "sigbus.h"
#include "util.h"
typedef struct FileDescriptor FileDescriptor;
struct Window {
bool invalidated:1;
bool keep_always:1;
bool in_unused:1;
int prot;
void *ptr;
};
struct Context {
unsigned id;
};
struct FileDescriptor {
int fd;
bool sigbus;
};
struct MMapCache {
int n_ref;
unsigned n_windows;
};
#define WINDOWS_MIN 64
#ifdef ENABLE_DEBUG_MMAP_CACHE
/* Tiny windows increase mmap activity and the chance of exposing unsafe use. */
# define WINDOW_SIZE (page_size())
#else
#endif
MMapCache* mmap_cache_new(void) {
MMapCache *m;
if (!m)
return NULL;
m->n_ref = 1;
return m;
}
assert(m);
m->n_ref ++;
return m;
}
static void window_unlink(Window *w) {
Context *c;
assert(w);
if (w->ptr)
if (w->fd)
if (w->in_unused) {
if (w->cache->last_unused == w)
}
}
}
static void window_invalidate(Window *w) {
assert(w);
if (w->invalidated)
return;
/* Replace the window with anonymous pages. This is useful
* when we hit a SIGBUS and want to make sure the file cannot
* trigger any further SIGBUS, possibly overrunning the sigbus
* queue. */
w->invalidated = true;
}
static void window_free(Window *w) {
assert(w);
window_unlink(w);
free(w);
}
assert(w);
return
w->fd &&
}
static Window *window_add(MMapCache *m, FileDescriptor *fd, int prot, bool keep_always, uint64_t offset, size_t size, void *ptr) {
Window *w;
assert(m);
/* Allocate a new window */
if (!w)
return NULL;
m->n_windows++;
} else {
/* Reuse an existing one */
w = m->last_unused;
window_unlink(w);
zero(*w);
}
w->cache = m;
w->keep_always = keep_always;
return w;
}
static void context_detach_window(Context *c) {
Window *w;
assert(c);
if (!c->window)
return;
w = c->window;
if (!w->contexts && !w->keep_always) {
/* Not used anymore? */
#ifdef ENABLE_DEBUG_MMAP_CACHE
/* Unmap unused windows immediately to expose use-after-unmap
* by SIGSEGV. */
window_free(w);
#else
if (!c->cache->last_unused)
c->cache->last_unused = w;
w->in_unused = true;
#endif
}
}
assert(c);
assert(w);
if (c->window == w)
return;
if (w->in_unused) {
/* Used again? */
if (c->cache->last_unused == w)
w->in_unused = false;
}
c->window = w;
}
Context *c;
assert(m);
if (c)
return c;
if (!c)
return NULL;
c->cache = m;
return c;
}
static void context_free(Context *c) {
assert(c);
if (c->cache) {
}
free(c);
}
static void fd_free(FileDescriptor *f) {
assert(f);
while (f->windows)
window_free(f->windows);
if (f->cache)
free(f);
}
FileDescriptor *f;
int r;
assert(m);
if (f)
return f;
if (r < 0)
return NULL;
if (!f)
return NULL;
f->cache = m;
if (r < 0) {
free(f);
return NULL;
}
return f;
}
static void mmap_cache_free(MMapCache *m) {
FileDescriptor *f;
int i;
assert(m);
for (i = 0; i < MMAP_CACHE_MAX_CONTEXTS; i++)
if (m->contexts[i])
context_free(m->contexts[i]);
while ((f = hashmap_first(m->fds)))
fd_free(f);
hashmap_free(m->fds);
while (m->unused)
window_free(m->unused);
free(m);
}
if (!m)
return NULL;
m->n_ref --;
if (m->n_ref == 0)
mmap_cache_free(m);
return NULL;
}
assert(m);
if (!m->last_unused)
return 0;
window_free(m->last_unused);
return 1;
}
static int try_context(
MMapCache *m,
int fd,
int prot,
unsigned context,
bool keep_always,
void **ret) {
Context *c;
assert(m);
if (!c)
return 0;
if (!c->window)
return 0;
/* Drop the reference to the window, since it's unnecessary now */
return 0;
}
return -EIO;
return 1;
}
static int find_mmap(
MMapCache *m,
int fd,
int prot,
unsigned context,
bool keep_always,
void **ret) {
FileDescriptor *f;
Window *w;
Context *c;
assert(m);
if (!f)
return 0;
if (f->sigbus)
return -EIO;
break;
if (!w)
return 0;
c = context_add(m, context);
if (!c)
return -ENOMEM;
context_attach_window(c, w);
return 1;
}
static int mmap_try_harder(MMapCache *m, void *addr, int fd, int prot, int flags, uint64_t offset, size_t size, void **res) {
void *ptr;
assert(m);
for (;;) {
int r;
if (ptr != MAP_FAILED)
break;
return -errno;
r = make_room(m);
if (r < 0)
return r;
if (r == 0)
return -ENOMEM;
}
return 0;
}
static int add_mmap(
MMapCache *m,
int fd,
int prot,
unsigned context,
bool keep_always,
void **ret) {
Context *c;
FileDescriptor *f;
Window *w;
void *d;
int r;
assert(m);
if (wsize < WINDOW_SIZE) {
woffset = 0;
else
wsize = WINDOW_SIZE;
}
if (st) {
/* Memory maps that are larger then the files
underneath have undefined behavior. Hence, clamp
things to the file size if we know it */
return -EADDRNOTAVAIL;
}
if (r < 0)
return r;
c = context_add(m, context);
if (!c)
goto outofmem;
if (!f)
goto outofmem;
if (!w)
goto outofmem;
c->window = w;
return 1;
return -ENOMEM;
}
int mmap_cache_get(
MMapCache *m,
int fd,
int prot,
unsigned context,
bool keep_always,
void **ret) {
int r;
assert(m);
/* Check whether the current context is the right one already */
if (r != 0) {
m->n_hit ++;
return r;
}
/* Search for a matching mmap */
if (r != 0) {
m->n_hit ++;
return r;
}
m->n_missed++;
/* Create a new mmap */
}
unsigned mmap_cache_get_hit(MMapCache *m) {
assert(m);
return m->n_hit;
}
unsigned mmap_cache_get_missed(MMapCache *m) {
assert(m);
return m->n_missed;
}
static void mmap_cache_process_sigbus(MMapCache *m) {
bool found = false;
FileDescriptor *f;
Iterator i;
int r;
assert(m);
/* Iterate through all triggered pages and mark their files as
* invalidated */
for (;;) {
bool ours;
void *addr;
r = sigbus_pop(&addr);
if (_likely_(r == 0))
break;
if (r < 0) {
log_error_errno(r, "SIGBUS handling failed: %m");
abort();
}
ours = false;
HASHMAP_FOREACH(f, m->fds, i) {
Window *w;
break;
}
}
if (ours)
break;
}
/* Didn't find a matching window, give up */
if (!ours) {
log_error("Unknown SIGBUS page, aborting.");
abort();
}
}
/* The list of triggered pages is now empty. Now, let's remap
* all windows of the triggered file to anonymous maps, so
* that no page of the file in question is triggered again, so
* that we can be sure not to hit the queue size limit. */
return;
HASHMAP_FOREACH(f, m->fds, i) {
Window *w;
if (!f->sigbus)
continue;
}
}
FileDescriptor *f;
assert(m);
if (!f)
return false;
return f->sigbus;
}
FileDescriptor *f;
assert(m);
/* Make sure that any queued SIGBUS are first dispatched, so
* that we don't end up with a SIGBUS entry we cannot relate
* to any existing memory map */
if (!f)
return;
fd_free(f);
}