Current section

Files

Jump to
emmap c_src emmap.cpp
Raw

c_src/emmap.cpp

// vim:ts=2:sw=2:et
#include "erl_nif_compat.h"
#include <sys/mman.h>
#include <sys/errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <atomic>
static ErlNifResourceType* MMAP_RESOURCE;
#ifndef MAP_NOCACHE
/* No MAP_NOCACHE on Linux - just bypass this option */
# define MAP_NOCACHE (0)
#endif
// MAP_ANON is deprecated on Linux, and MAP_ANONYMOUS is not present on Mac
#ifndef MAP_ANONYMOUS
# define MAP_ANONYMOUS MAP_ANON
#endif
typedef struct
{
size_t position;
int direct;
int prot;
bool closed;
bool debug;
ErlNifRWLock* rwlock;
void* mem;
size_t len;
bool auto_unlink;
char path[1024];
} mhandle;
static int on_load(ErlNifEnv*, void**, ERL_NIF_TERM);
static int on_upgrade(ErlNifEnv* env, void** priv_data, void**, ERL_NIF_TERM load_info);
static int emmap_unmap(mhandle *handle, bool from_dtor)
{
if (handle->mem != 0) {
if (from_dtor || !handle->direct) {
int result = munmap(handle->mem, handle->len);
handle->mem = 0;
return result;
} else {
handle->closed = true;
}
}
return 0;
}
void emmap_dtor(ErlNifEnv* env, void* arg)
{
mhandle* handle = (mhandle*)arg;
emmap_unmap(handle, true);
// only the destructor destroys the rwlock
if (handle->rwlock != 0) enif_rwlock_destroy(handle->rwlock);
}
#define RW_UNLOCK if (handle->rwlock != 0) enif_rwlock_rwunlock(handle->rwlock)
#define RW_LOCK if (handle->rwlock != 0) enif_rwlock_rwlock(handle->rwlock)
#define R_UNLOCK if (handle->rwlock != 0) enif_rwlock_runlock(handle->rwlock)
#define R_LOCK if (handle->rwlock != 0) enif_rwlock_rlock(handle->rwlock)
static ERL_NIF_TERM ATOM_OK;
static ERL_NIF_TERM ATOM_TRUE;
static ERL_NIF_TERM ATOM_FALSE;
static ERL_NIF_TERM ATOM_DEBUG;
static ERL_NIF_TERM ATOM_ERROR;
static ERL_NIF_TERM ATOM_ADDRESS;
static ERL_NIF_TERM ATOM_LOCK;
static ERL_NIF_TERM ATOM_NOLOCK;
static ERL_NIF_TERM ATOM_DIRECT;
static ERL_NIF_TERM ATOM_READ;
static ERL_NIF_TERM ATOM_WRITE;
static ERL_NIF_TERM ATOM_CREATE;
static ERL_NIF_TERM ATOM_TRUNCATE;
static ERL_NIF_TERM ATOM_CHMOD;
static ERL_NIF_TERM ATOM_NONE;
static ERL_NIF_TERM ATOM_PRIVATE;
static ERL_NIF_TERM ATOM_POPULATE;
static ERL_NIF_TERM ATOM_SHARED;
static ERL_NIF_TERM ATOM_SHARED_VALIDATE;
static ERL_NIF_TERM ATOM_SYNC;
static ERL_NIF_TERM ATOM_ANON;
static ERL_NIF_TERM ATOM_FILE;
static ERL_NIF_TERM ATOM_FIXED;
static ERL_NIF_TERM ATOM_NOCACHE;
static ERL_NIF_TERM ATOM_NORESERVE;
static ERL_NIF_TERM ATOM_UNINITIALIZED;
static ERL_NIF_TERM ATOM_AUTO_UNLINK;
static ERL_NIF_TERM ATOM_ADD;
static ERL_NIF_TERM ATOM_SUB;
static ERL_NIF_TERM ATOM_BAND;
static ERL_NIF_TERM ATOM_BOR;
static ERL_NIF_TERM ATOM_BXOR;
static ERL_NIF_TERM ATOM_XCHG;
static ERL_NIF_TERM ATOM_BOF;
static ERL_NIF_TERM ATOM_CUR;
static ERL_NIF_TERM ATOM_EOF;
static ERL_NIF_TERM emmap_open (ErlNifEnv*, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM emmap_read (ErlNifEnv*, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM emmap_read_line (ErlNifEnv*, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM emmap_close (ErlNifEnv*, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM emmap_pread (ErlNifEnv*, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM emmap_pwrite (ErlNifEnv*, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM emmap_position (ErlNifEnv*, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM emmap_patomic (ErlNifEnv*, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM emmap_patomic_read_int (ErlNifEnv*, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM emmap_patomic_write_int(ErlNifEnv*, int argc, const ERL_NIF_TERM argv[]);
extern "C" {
static ErlNifFunc nif_funcs[] =
{
{"open_nif", 4, emmap_open},
{"close_nif", 1, emmap_close},
{"pread_nif", 3, emmap_pread},
{"pwrite_nif", 3, emmap_pwrite},
{"patomic_nif", 4, emmap_patomic},
{"patomic_read_int_nif", 2, emmap_patomic_read_int},
{"patomic_write_int_nif", 3, emmap_patomic_write_int},
{"position_nif", 3, emmap_position},
{"read_nif", 2, emmap_read},
{"read_line_nif", 1, emmap_read_line},
};
};
ERL_NIF_INIT(emmap, nif_funcs, &on_load, nullptr, on_upgrade, nullptr);
static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
{
ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);
MMAP_RESOURCE = enif_open_resource_type_compat(env, "mmap_resource", &emmap_dtor, flags, 0);
ATOM_OK = enif_make_atom(env, "ok");
ATOM_TRUE = enif_make_atom(env, "true");
ATOM_FALSE = enif_make_atom(env, "false");
ATOM_ERROR = enif_make_atom(env, "error");
ATOM_DEBUG = enif_make_atom(env, "debug");
ATOM_ADDRESS = enif_make_atom(env, "address");
ATOM_DIRECT = enif_make_atom(env, "direct");
ATOM_READ = enif_make_atom(env, "read");
ATOM_WRITE = enif_make_atom(env, "write");
ATOM_CREATE = enif_make_atom(env, "create");
ATOM_TRUNCATE = enif_make_atom(env, "truncate");
ATOM_CHMOD = enif_make_atom(env, "chmod");
ATOM_NONE = enif_make_atom(env, "none");
ATOM_PRIVATE = enif_make_atom(env, "private");
ATOM_POPULATE = enif_make_atom(env, "populate");
ATOM_SHARED = enif_make_atom(env, "shared");
ATOM_SHARED_VALIDATE = enif_make_atom(env, "shared_validate");
ATOM_SYNC = enif_make_atom(env, "sync");
ATOM_ANON = enif_make_atom(env, "anon");
ATOM_FILE = enif_make_atom(env, "file");
ATOM_FIXED = enif_make_atom(env, "fixed");
ATOM_NOCACHE = enif_make_atom(env, "nocache");
ATOM_NORESERVE = enif_make_atom(env, "noreserve");
ATOM_AUTO_UNLINK = enif_make_atom(env, "auto_unlink");
ATOM_UNINITIALIZED = enif_make_atom(env, "uninitialized");
ATOM_BOF = enif_make_atom(env, "bof");
ATOM_CUR = enif_make_atom(env, "cur");
ATOM_EOF = enif_make_atom(env, "eof");
ATOM_LOCK = enif_make_atom(env, "lock");
ATOM_NOLOCK = enif_make_atom(env, "nolock");
ATOM_ADD = enif_make_atom(env, "add");
ATOM_SUB = enif_make_atom(env, "sub");
ATOM_BAND = enif_make_atom(env, "band");
ATOM_BOR = enif_make_atom(env, "bor");
ATOM_BXOR = enif_make_atom(env, "bxor");
ATOM_XCHG = enif_make_atom(env, "xchg");
return 0;
}
static int on_upgrade(ErlNifEnv* env, void** priv_data, void**, ERL_NIF_TERM load_info)
{
return on_load(env, priv_data, load_info);
}
static ERL_NIF_TERM describe_error(ErlNifEnv* env, int err) {
switch (err) {
case EAGAIN:
return enif_make_atom(env, "eagain");
case EINVAL:
return enif_make_atom(env, "einval");
case ENOSPC:
return enif_make_atom(env, "enospc");
case ENOENT:
return enif_make_atom(env, "enoent");
case ENOMEM:
return enif_make_atom(env, "enomem");
case EACCES:
return enif_make_atom(env, "eacces");
case EBADF:
return enif_make_atom(env, "ebadf");
case ENODEV:
return enif_make_atom(env, "enodev");
case ENXIO:
return enif_make_atom(env, "enxio");
case EOVERFLOW:
return enif_make_atom(env, "eoverflow");
case EOPNOTSUPP:
return enif_make_atom(env, "eopnotsupp");
}
return enif_make_tuple2(env,
enif_make_atom(env, "errno"),
enif_make_int(env, err));
}
static ERL_NIF_TERM make_error_tuple(ErlNifEnv* env, int err) {
return enif_make_tuple2(env, ATOM_ERROR, describe_error(env, err));
}
static ERL_NIF_TERM make_error_tuple(ErlNifEnv* env, const char* err) {
return enif_make_tuple2(env, ATOM_ERROR, enif_make_string(env, err, ERL_NIF_LATIN1));
}
template <typename... Args>
static void debug(mhandle* h, const char* fmt, Args&&... args)
{
if (h->debug)
fprintf(stderr, fmt, std::forward<Args>(args)...);
}
static bool decode_flags(ErlNifEnv* env, ERL_NIF_TERM list, int* prot, int* flags,
long* open_flags, long* mode, bool* direct, bool* lock,
bool* auto_unlink, size_t* address, bool* debug)
{
bool l = true;
bool d = false;
int f = MAP_FILE;
int p = 0;
int arity;
const ERL_NIF_TERM* tuple;
*auto_unlink = false;
*address = 0x0;
*open_flags = O_RDONLY;
*mode = 0600;
*debug = false;
ERL_NIF_TERM head;
while (enif_get_list_cell(env, list, &head, &list)) {
if (enif_is_identical(head, ATOM_READ)) {
p |= PROT_READ;
} else if (enif_is_identical(head, ATOM_DEBUG)) {
*debug = true;
} else if (enif_is_identical(head, ATOM_CREATE)) {
*open_flags |= O_CREAT;
} else if (enif_is_identical(head, ATOM_TRUNCATE)) {
*open_flags |= O_TRUNC;
} else if (enif_is_identical(head, ATOM_DIRECT)) {
d = true;
} else if (enif_is_identical(head, ATOM_LOCK)) {
l = true;
} else if (enif_is_identical(head, ATOM_NOLOCK)) {
l = false;
} else if (enif_is_identical(head, ATOM_WRITE)) {
p |= PROT_WRITE;
*open_flags |= O_RDWR;
// } else if (enif_is_identical(head, ATOM_NONE)) {
// p |= PROT_NONE;
} else if (enif_is_identical(head, ATOM_PRIVATE)) {
f |= MAP_PRIVATE;
#if ERL_NIF_MINOR_VERSION > 4
#ifdef MAP_POPULATE
} else if (enif_is_identical(head, ATOM_POPULATE)) {
f |= MAP_POPULATE;
#endif
#endif
} else if (enif_is_identical(head, ATOM_SHARED_VALIDATE)) {
f |= MAP_SHARED_VALIDATE;
} else if (enif_is_identical(head, ATOM_SHARED)) {
f |= MAP_SHARED;
} else if (enif_is_identical(head, ATOM_ANON)) {
f |= MAP_ANONYMOUS;
} else if (enif_is_identical(head, ATOM_SYNC)) {
f |= MAP_SYNC;
} else if (enif_is_identical(head, ATOM_FIXED)) {
f |= MAP_FIXED;
// } else if (enif_is_identical(head, ATOM_FILE)) {
// f |= MAP_FILE;
} else if (enif_is_identical(head, ATOM_NOCACHE)) {
f |= MAP_NOCACHE;
} else if (enif_is_identical(head, ATOM_UNINITIALIZED)) {
f |= MAP_NOCACHE;
#ifdef MAP_NORESERVE
} else if (enif_is_identical(head, ATOM_NORESERVE)) {
f |= MAP_NORESERVE;
#endif
} else if (enif_is_identical(head, ATOM_AUTO_UNLINK)) {
*auto_unlink = true;
} else if (enif_get_tuple(env, head, &arity, &tuple) && arity == 2) {
if (enif_is_identical (tuple[0], ATOM_ADDRESS) &&
enif_get_ulong(env, tuple[1], address)) {
// If address is given, set the "MAP_FIXED" option
if (*address) f |= MAP_FIXED;
continue;
} else if (enif_is_identical (tuple[0], ATOM_CHMOD) &&
enif_get_long(env, tuple[1], mode)) {
continue;
} else
return false;
} else {
return false;
}
}
// direct cannot be write
//if (d & ((p & PROT_WRITE) != 0))
// return 0;
// default to private
if ((f & (MAP_SHARED|MAP_PRIVATE)) == 0)
f |= MAP_PRIVATE;
// default to read-only
if ((p & (PROT_READ|PROT_WRITE)) == 0)
p |= PROT_READ;
*flags = f;
*prot = p;
*direct = d;
*lock = l;
return true;
}
static ERL_NIF_TERM emmap_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int flags;
int prot;
long open_flags;
long mode;
bool direct, lock, auto_unlink;
unsigned long int len;
unsigned long int offset;
size_t address;
#ifndef NDEBUG
if (sizeof(long int) != sizeof(size_t))
abort();
#endif
mhandle* handle = (mhandle*)enif_alloc_resource_compat(env, MMAP_RESOURCE,
sizeof(mhandle));
if (argc != 4
|| !enif_get_string(env, argv[0], handle->path, 1024, ERL_NIF_LATIN1)
|| !enif_get_ulong(env, argv[1], &offset)
|| !enif_get_ulong(env, argv[2], &len)
|| !decode_flags(env, argv[3], &prot, &flags, &open_flags, &mode, &direct,
&lock, &auto_unlink, &address, &handle->debug))
return enif_make_badarg(env);
int fd = -1;
if ((flags & MAP_ANON) == 0) {
bool exists = access(handle->path, F_OK) == 0;
if (!exists) {
if (len == 0)
return make_error_tuple(env, "Missing {size, N} option");
if ((open_flags & O_CREAT) == 0)
return make_error_tuple(env, "Missing 'create' option");
}
fd = open(handle->path, open_flags, (mode_t)mode);
if (fd < 0) {
debug(handle, "open: %s\r\n", strerror(errno));
return make_error_tuple(env, errno);
}
off_t fsize = 0;
struct stat st;
if (fstat(fd, &st) < 0) {
debug(handle, "fstat: %s\r\n", strerror(errno));
int err = errno;
close(fd);
return make_error_tuple(env, err);
} else {
fsize = st.st_size;
}
if (exists && len > 0 && fsize != long(len) && fsize > 0) {
char buf[128];
snprintf(buf, sizeof(buf), "File has different size (%ld) than requested (%ld)", fsize, len);
close(fd);
return make_error_tuple(env, buf);
}
// Stretch the file size to the requested size
if ((!exists && ((open_flags & (O_CREAT|O_TRUNC)) > 0)) ||
(exists && fsize == 0)) {
bool is_ok = ftruncate(fd, 0) == 0 &&
ftruncate(fd, len) == 0;
if (!is_ok) {
debug(handle, "ftruncate: %s\r\n", strerror(errno));
int err = errno;
close(fd);
return make_error_tuple(env, err);
}
}
}
off_t pa_offset = 0;
if (offset > 0) {
pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1); // offset for mmap() must be page aligned
if (offset >= len) {
if (fd >= 0) close(fd);
char buf[128];
snprintf(buf, sizeof(buf), "Offset %ld is past end of file", offset);
return make_error_tuple(env, buf);
}
}
void* res = mmap((void*)address, (size_t)len + offset - pa_offset, prot, flags, fd,
(size_t)pa_offset);
if (res == MAP_FAILED)
return make_error_tuple(env, errno);
if (fd >= 0)
close(fd);
handle->rwlock = lock ? enif_rwlock_create((char*)"mmap") : 0;
handle->prot = prot;
handle->mem = res;
handle->len = len;
handle->closed = false;
handle->direct = direct;
handle->position = 0;
handle->auto_unlink = auto_unlink;
ERL_NIF_TERM resource = enif_make_resource(env, handle);
enif_release_resource_compat(env, handle);
return enif_make_tuple2(env, enif_make_atom(env, "ok"), resource);
}
static ERL_NIF_TERM emmap_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
mhandle *handle;
if (argc!=1 || !enif_get_resource(env, argv[0], MMAP_RESOURCE, (void**)&handle))
return enif_make_badarg(env);
int res;
RW_LOCK;
res = emmap_unmap(handle, false);
RW_UNLOCK;
if(handle->auto_unlink && unlink(handle->path) != 0)
return make_error_tuple(env, errno);
if (res == 0)
return ATOM_OK;
return make_error_tuple(env, errno);
}
static ERL_NIF_TERM emmap_pread(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
unsigned long pos, bytes;
mhandle *handle;
if (argc!=3
|| !enif_get_resource(env, argv[0], MMAP_RESOURCE, (void**)&handle)
|| !enif_get_ulong(env, argv[1], &pos)
|| !enif_get_ulong(env, argv[2], &bytes)
|| pos >= handle->len
)
return enif_make_badarg(env);
// Adjust bytes to behave like original file:pread/3
if (pos + bytes > handle->len) bytes = handle->len - pos;
ErlNifBinary bin;
if ((handle->prot & PROT_READ) == 0)
return make_error_tuple(env, EACCES);
// if this mmap is direct, use a resource binary
if (handle->direct) {
ERL_NIF_TERM res = enif_make_resource_binary
(env, handle, (void*) (((char*)handle->mem) + pos), bytes);
return enif_make_tuple2(env, ATOM_OK, res);
} else {
// When it is non-direct, we have to allocate the binary
if (!enif_alloc_binary((size_t) bytes, &bin))
return make_error_tuple(env, ENOMEM);
R_LOCK;
if (handle->closed) {
R_UNLOCK;
return enif_make_badarg(env);
}
memcpy(bin.data, (void*) (((char*)handle->mem) + pos), bytes);
R_UNLOCK;
ERL_NIF_TERM res = enif_make_binary(env, &bin);
return enif_make_tuple2(env, ATOM_OK, res);
}
}
static ERL_NIF_TERM emmap_pwrite(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin;
unsigned long pos;
mhandle *handle;
if (argc!=3
|| !enif_get_resource(env, argv[0], MMAP_RESOURCE, (void**)&handle)
|| !enif_get_ulong(env, argv[1], &pos)
|| !enif_inspect_binary(env, argv[2], &bin)
|| (pos + bin.size) > handle->len
)
return enif_make_badarg(env);
if ((handle->prot & PROT_WRITE) == 0) {
return make_error_tuple(env, EACCES);
}
RW_LOCK;
if (handle->closed) {
RW_UNLOCK;
return enif_make_badarg(env);
} else {
memcpy((void*) (((char*)handle->mem) + pos), bin.data, bin.size);
RW_UNLOCK;
}
return ATOM_OK;
}
/// Atomically read a 64-bit value from the memoty at given position
/// Args: Handle, Position::integer()
/// Return: Value::integer()
static ERL_NIF_TERM emmap_patomic_read_int(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
mhandle* handle;
unsigned long pos;
if (!(argc==2
&& enif_get_resource(env, argv[0], MMAP_RESOURCE, (void**)&handle)
&& enif_get_ulong (env, argv[1], &pos)
&& (pos + 8) <= handle->len
))
return enif_make_badarg(env);
if (handle->closed)
return enif_make_badarg(env);
void* mem = (char*)handle->mem + pos;
auto p = static_cast<std::atomic<int64_t>*>(mem);
long res = p->load(std::memory_order_relaxed);
return enif_make_tuple2(env, ATOM_OK, enif_make_long(env, res));
}
/// Atomically write a 64-bit value to the memoty at given position
/// Args: Handle, Position::integer(), Value::integer()
static ERL_NIF_TERM emmap_patomic_write_int(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
mhandle* handle;
unsigned long pos;
long value;
if (!(argc==3
&& enif_get_resource(env, argv[0], MMAP_RESOURCE, (void**)&handle)
&& enif_get_ulong (env, argv[1], &pos)
&& enif_get_long (env, argv[2], &value)
&& (pos + 8) <= handle->len
))
return enif_make_badarg(env);
if (handle->closed)
return enif_make_badarg(env);
void* mem = (char*)handle->mem + pos;
((std::atomic<int64_t>*)mem)->store(value);
return ATOM_OK;
}
/// Atomically add a 64-bit value to the memoty at given position
/// Args: Handle, Position::integer(), Op, Increment::integer()
/// where Op :: add|sub|and|or|xor|xchg
/// Return: OldValue::integer()
static ERL_NIF_TERM emmap_patomic(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
mhandle* handle;
unsigned long pos;
long value;
if (argc!=4
|| !enif_get_resource(env, argv[0], MMAP_RESOURCE, (void**)&handle)
|| !enif_get_ulong (env, argv[1], &pos)
|| !enif_is_atom (env, argv[2])
|| !enif_get_long (env, argv[3], &value)
|| (pos + 8) > handle->len
)
return enif_make_badarg(env);
if ((handle->prot & PROT_WRITE) == 0)
return make_error_tuple(env, EACCES);
if (handle->closed)
return enif_make_badarg(env);
void* mem = (char*)handle->mem + pos;
long res;
ERL_NIF_TERM op = argv[2];
if (op == ATOM_ADD)
res = ((std::atomic<int64_t>*)mem)->fetch_add(value, std::memory_order_relaxed);
else if (op == ATOM_SUB)
res = ((std::atomic<int64_t>*)mem)->fetch_sub(value, std::memory_order_relaxed);
else if (op == ATOM_BAND)
// Atomically replaces the cur value with the result of bitwise (value & arg)
res = ((std::atomic<int64_t>*)mem)->fetch_and(value, std::memory_order_relaxed);
else if (op == ATOM_BOR)
// Atomically replaces the cur value with the result of bitwise (value | arg)
res = ((std::atomic<int64_t>*)mem)->fetch_or(value, std::memory_order_relaxed);
else if (op == ATOM_BXOR)
// Atomically replaces the cur value with the result of bitwise (value ^ arg)
res = ((std::atomic<int64_t>*)mem)->fetch_xor(value, std::memory_order_relaxed);
else if (op == ATOM_XCHG)
// Atomically replaces the cur value with the arg
res = ((std::atomic<int64_t>*)mem)->exchange(value, std::memory_order_acq_rel);
else
return enif_make_badarg(env);
return enif_make_tuple2(env, ATOM_OK, enif_make_long(env, res));
}
static ERL_NIF_TERM emmap_read(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
mhandle *handle;
unsigned long bytes;
if (!enif_get_resource(env, argv[0], MMAP_RESOURCE, (void**)&handle) ||
!enif_get_ulong(env, argv[1], &bytes))
return enif_make_badarg(env);
RW_LOCK;
if (handle->position == handle->len) {
RW_UNLOCK;
return ATOM_EOF;
}
unsigned long new_pos = handle->position + bytes;
if (new_pos > handle->len) { new_pos = handle->len; }
long size = new_pos - handle->position;
long start = handle->position;
handle->position = new_pos;
RW_UNLOCK;
if (handle->direct) {
ERL_NIF_TERM res = enif_make_resource_binary
(env, handle, (void*) (((char*)handle->mem) + start), size);
return enif_make_tuple2(env, ATOM_OK, res);
} else {
ErlNifBinary bin;
// When it is non-direct, we have to allocate the binary
if (!enif_alloc_binary((size_t) size, &bin))
return make_error_tuple(env, ENOMEM);
memcpy(bin.data, (void*) (((char*)handle->mem) + start), size);
ERL_NIF_TERM res = enif_make_binary(env, &bin);
return enif_make_tuple2(env, ATOM_OK, res);
}
}
static ERL_NIF_TERM emmap_read_line(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
mhandle *handle;
if (!enif_get_resource(env, argv[0], MMAP_RESOURCE, (void**)&handle))
return enif_make_badarg(env);
RW_LOCK;
if (handle->position == handle->len) {
RW_UNLOCK;
return ATOM_EOF;
}
long start = handle->position;
char *current = ((char*)handle->mem) + handle->position;
long linelen = 0; // Length of line without trailing \n
long no_crlf_len = 0;
bool have_cr = false;
bool got_eof = false;
// Read buffer until \n or EOF is reached
while (*current != '\n') {
handle->position ++;
current ++;
if (handle->position == handle->len) {
got_eof = true;
break;
}
}
// Step to next byte if EOF is not reached
if (not got_eof)
handle->position ++;
no_crlf_len = linelen = handle->position - start;
if (not got_eof) {
// Found LF -- exclude it from line
no_crlf_len --;
// If line length before \n is non-zero check if we have \r before \n
if ((no_crlf_len > 0) && (*(current - 1) == '\r')) {
have_cr = true;
no_crlf_len --;
}
}
RW_UNLOCK;
// We must not include CR before LF in result, so use direct only without CR
if ((handle->direct) && (not have_cr)) {
// Include trailing LF if we have it
ERL_NIF_TERM res = enif_make_resource_binary
(env, handle, (void*) (((char*)handle->mem) + start), linelen);
return enif_make_tuple2(env, ATOM_OK, res);
} else {
if (not got_eof) linelen = no_crlf_len + 1;
ErlNifBinary bin;
// When it is non-direct, we have to allocate the binary
if (!enif_alloc_binary((size_t) linelen, &bin)) {
return make_error_tuple(env, ENOMEM);
}
memcpy(bin.data, (void*) (((char*)handle->mem) + start), no_crlf_len);
// Set trailing \n if needed
if (not got_eof) *(((char*)bin.data) + no_crlf_len) = '\n';
ERL_NIF_TERM res = enif_make_binary(env, &bin);
return enif_make_tuple2(env, ATOM_OK, res);
}
}
static ERL_NIF_TERM emmap_position(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
mhandle *handle;
long position;
long relpos;
if (argc!=3
|| !enif_get_resource(env, argv[0], MMAP_RESOURCE, (void**)&handle)
|| !enif_get_long(env, argv[2], &relpos)
|| (argv[1] != ATOM_CUR && argv[1] != ATOM_BOF && argv[1] != ATOM_EOF))
return enif_make_badarg(env);
RW_LOCK;
if (argv[1] == ATOM_BOF)
position = 0L + relpos;
else if (argv[1] == ATOM_CUR)
position = handle->position + relpos;
else if (argv[1] == ATOM_EOF)
position = handle->len - relpos;
else
position = -1;
if (position < 0L || ((unsigned long)position) > handle->len) {
RW_UNLOCK;
return enif_make_badarg(env);
}
handle->position = position;
RW_UNLOCK;
return enif_make_tuple2(env, ATOM_OK, enif_make_ulong(env, position));
}