#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include "file.h"
#include "queue.h"
#include "mmap.h"
#include "sys_util.h"
#include "os.h"
int Mmap_no_random;
void *Mmap_hint;
enum map_tracker_flag {
MTF_DIRECT_MAPPED = 0x0001,
MTF_MASK = MTF_DIRECT_MAPPED
};
struct map_tracker {
SORTEDQ_ENTRY(map_tracker) entry;
const void *base_addr;
const void *end_addr;
enum map_tracker_flag flags;
#ifdef _WIN32
HANDLE FileHandle;
HANDLE FileMappingHandle;
DWORD Access;
os_off_t Offset;
size_t FileLen;
#endif
};
static SORTEDQ_HEAD(map_list_head, map_tracker) Mmap_list =
SORTEDQ_HEAD_INITIALIZER(Mmap_list);
static os_rwlock_t Mmap_list_lock;
void
util_mmap_init(void)
{
LOG(3, NULL);
if ((errno = os_rwlock_init(&Mmap_list_lock)))
FATAL("!os_rwlock_init");
char *e = os_getenv("PMEM_MMAP_HINT");
if (e) {
char *endp;
errno = 0;
unsigned long long val = strtoull(e, &endp, 16);
if (errno || endp == e) {
LOG(2, "Invalid PMEM_MMAP_HINT");
} else if (os_access(OS_MAPFILE, R_OK)) {
LOG(2, "No /proc, PMEM_MMAP_HINT ignored");
} else {
Mmap_hint = (void *)val;
Mmap_no_random = 1;
LOG(3, "PMEM_MMAP_HINT set to %p", Mmap_hint);
}
}
}
void
util_mmap_fini(void)
{
LOG(3, NULL);
if ((errno = os_rwlock_destroy(&Mmap_list_lock)))
FATAL("!os_rwlock_destroy");
}
void *
util_map(int fd, size_t len, int flags, int rdonly, size_t req_align)
{
LOG(3, "fd %d len %zu flags %d rdonly %d req_align %zu", fd, len, flags,
rdonly, req_align);
void *base;
void *addr = util_map_hint(len, req_align);
if (addr == MAP_FAILED) {
ERR("cannot find a contiguous region of given size");
return NULL;
}
if (req_align)
ASSERTeq((uintptr_t)addr % req_align, 0);
if ((base = mmap(addr, len, rdonly ? PROT_READ : PROT_READ|PROT_WRITE,
flags, fd, 0)) == MAP_FAILED) {
ERR("!mmap %zu bytes", len);
return NULL;
}
LOG(3, "mapped at %p", base);
return base;
}
int
util_unmap(void *addr, size_t len)
{
LOG(3, "addr %p len %zu", addr, len);
int retval = munmap(addr, len);
if (retval < 0)
ERR("!munmap");
return retval;
}
void *
util_map_tmpfile(const char *dir, size_t size, size_t req_align)
{
int oerrno;
if (((os_off_t)size) < 0) {
ERR("invalid size (%zu) for os_off_t", size);
errno = EFBIG;
return NULL;
}
int fd = util_tmpfile(dir, OS_DIR_SEP_STR "vmem.XXXXXX", O_EXCL);
if (fd == -1) {
LOG(2, "cannot create temporary file in dir %s", dir);
goto err;
}
if ((errno = os_posix_fallocate(fd, 0, (os_off_t)size)) != 0) {
ERR("!posix_fallocate");
goto err;
}
void *base;
if ((base = util_map(fd, size, MAP_SHARED, 0, req_align)) == NULL) {
LOG(2, "cannot mmap temporary file");
goto err;
}
(void) os_close(fd);
return base;
err:
oerrno = errno;
if (fd != -1)
(void) os_close(fd);
errno = oerrno;
return NULL;
}
int
util_range_ro(void *addr, size_t len)
{
LOG(3, "addr %p len %zu", addr, len);
uintptr_t uptr;
int retval;
len += (uintptr_t)addr & (Pagesize - 1);
uptr = (uintptr_t)addr & ~(Pagesize - 1);
if ((retval = mprotect((void *)uptr, len, PROT_READ)) < 0)
ERR("!mprotect: PROT_READ");
return retval;
}
int
util_range_rw(void *addr, size_t len)
{
LOG(3, "addr %p len %zu", addr, len);
uintptr_t uptr;
int retval;
len += (uintptr_t)addr & (Pagesize - 1);
uptr = (uintptr_t)addr & ~(Pagesize - 1);
if ((retval = mprotect((void *)uptr, len, PROT_READ|PROT_WRITE)) < 0)
ERR("!mprotect: PROT_READ|PROT_WRITE");
return retval;
}
int
util_range_none(void *addr, size_t len)
{
LOG(3, "addr %p len %zu", addr, len);
uintptr_t uptr;
int retval;
len += (uintptr_t)addr & (Pagesize - 1);
uptr = (uintptr_t)addr & ~(Pagesize - 1);
if ((retval = mprotect((void *)uptr, len, PROT_NONE)) < 0)
ERR("!mprotect: PROT_NONE");
return retval;
}
static intptr_t
util_range_comparer(struct map_tracker *a, struct map_tracker *b)
{
return ((intptr_t)a->base_addr - (intptr_t)b->base_addr);
}
static struct map_tracker *
util_range_find(const void *addr, size_t len)
{
LOG(10, "addr %p len %zu", addr, len);
void *end = (char *)addr + len;
struct map_tracker *mt;
SORTEDQ_FOREACH(mt, &Mmap_list, entry) {
if (addr < mt->end_addr &&
(addr >= mt->base_addr || end > mt->base_addr))
return mt;
if (addr < mt->base_addr)
break;
}
return NULL;
}
int
util_range_register(const void *addr, size_t len)
{
LOG(3, "addr %p len %zu", addr, len);
int ret = 0;
if (os_rwlock_wrlock(&Mmap_list_lock)) {
errno = EBUSY;
ERR("!cannot lock map tracking list");
return -1;
}
struct map_tracker *mt = util_range_find(addr, len);
ASSERTeq(mt, NULL);
mt = Malloc(sizeof(struct map_tracker));
if (mt == NULL) {
ERR("!Malloc");
ret = -1;
goto err;
}
mt->base_addr = addr;
mt->end_addr = (void *)((char *)addr + len);
mt->flags = MTF_DIRECT_MAPPED;
SORTEDQ_INSERT(&Mmap_list, mt, entry, struct map_tracker,
util_range_comparer);
err:
util_rwlock_unlock(&Mmap_list_lock);
return ret;
}
static int
util_range_split(struct map_tracker *mt, const void *addr, const void *end)
{
LOG(3, "begin %p end %p", addr, end);
ASSERTne(mt, NULL);
ASSERTeq((uintptr_t)addr % Mmap_align, 0);
ASSERTeq((uintptr_t)end % Mmap_align, 0);
struct map_tracker *mtb = NULL;
struct map_tracker *mte = NULL;
if (addr > mt->base_addr) {
mtb = Malloc(sizeof(struct map_tracker));
if (mtb == NULL) {
ERR("!Malloc");
goto err;
}
mtb->flags = mt->flags;
mtb->base_addr = mt->base_addr;
mtb->end_addr = addr;
}
if (end < mt->end_addr) {
mte = Malloc(sizeof(struct map_tracker));
if (mte == NULL) {
ERR("!Malloc");
goto err;
}
mte->flags = mt->flags;
mte->base_addr = end;
mte->end_addr = mt->end_addr;
}
SORTEDQ_REMOVE(&Mmap_list, mt, entry);
if (mtb) {
SORTEDQ_INSERT(&Mmap_list, mtb, entry,
struct map_tracker, util_range_comparer);
}
if (mte) {
SORTEDQ_INSERT(&Mmap_list, mte, entry,
struct map_tracker, util_range_comparer);
}
Free(mt);
return 0;
err:
Free(mtb);
Free(mte);
return -1;
}
int
util_range_unregister(const void *addr, size_t len)
{
LOG(3, "addr %p len %zu", addr, len);
int ret = 0;
if (os_rwlock_wrlock(&Mmap_list_lock)) {
errno = EBUSY;
ERR("!cannot lock map tracking list");
return -1;
}
void *end = (char *)addr + len;
struct map_tracker *mt;
while ((mt = util_range_find(addr, len)) != NULL) {
if (util_range_split(mt, addr, end) != 0) {
ret = -1;
break;
}
}
util_rwlock_unlock(&Mmap_list_lock);
return ret;
}
int
util_range_is_pmem(const void *addr, size_t len)
{
LOG(10, "addr %p len %zu", addr, len);
int retval = 1;
if (os_rwlock_rdlock(&Mmap_list_lock)) {
errno = EBUSY;
ERR("!cannot lock map tracking list");
return 0;
}
do {
struct map_tracker *mt = util_range_find(addr, len);
if (mt == NULL) {
LOG(4, "address not found %p", addr);
retval = 0;
break;
}
LOG(10, "range found - begin %p end %p flags %x",
mt->base_addr, mt->end_addr, mt->flags);
if (mt->base_addr > addr) {
LOG(10, "base address doesn't match: %p > %p",
mt->base_addr, addr);
retval = 0;
break;
}
retval &= ((mt->flags & MTF_DIRECT_MAPPED) != 0);
uintptr_t map_len = ((uintptr_t)mt->end_addr - (uintptr_t)addr);
if (map_len > len)
map_len = len;
len -= map_len;
addr = (char *)addr + map_len;
} while (len > 0);
util_rwlock_unlock(&Mmap_list_lock);
return retval;
}