#define _GNU_SOURCE
#include <assert.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <pthread.h>
#include <threads.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <emscripten/console.h>
#include <emscripten/threading.h>
#include <emscripten/promise.h>
#include <emscripten/proxying.h>
#include "dynlink.h"
#include "pthread_impl.h"
#include "emscripten_internal.h"
#ifdef DYLINK_DEBUG
#define dbg(fmt, ...) emscripten_dbgf(fmt, ##__VA_ARGS__)
#else
#define dbg(fmt, ...)
#endif
struct async_data {
em_dlopen_callback onsuccess;
em_arg_callback_func onerror;
void* user_data;
};
void __dl_vseterr(const char*, va_list);
struct dlevent {
struct dlevent *next, *prev;
int sym_index;
struct dso* dso;
#ifdef DYLINK_DEBUG
int id;
#endif
};
static struct dso main_dso = {
.name = "__main__",
.flags = 0,
};
static struct dlevent main_event = {
.prev = NULL,
.next = NULL,
.sym_index = -1,
.dso = &main_dso,
};
static struct dlevent* _Atomic head = &main_event;
static struct dlevent* _Atomic tail = &main_event;
#ifdef _REENTRANT
static thread_local struct dlevent* thread_local_tail = &main_event;
static pthread_mutex_t write_lock = PTHREAD_MUTEX_INITIALIZER;
static thread_local bool skip_dlsync = false;
static void do_write_lock() {
skip_dlsync = true;
pthread_mutex_lock(&write_lock);
}
static void do_write_unlock() {
pthread_mutex_unlock(&write_lock);
skip_dlsync = false;
}
#else
#define do_write_unlock()
#define do_write_lock()
#endif
static void error(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
__dl_vseterr(fmt, ap);
va_end(ap);
#ifdef DYLINK_DEBUG
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
#endif
}
int __dl_invalid_handle(void* h) {
struct dlevent* p;
for (p = head; p; p = p->next)
if (p->sym_index == -1 && p->dso == h)
return 0;
dbg("__dl_invalid_handle %p", h);
error("Invalid library handle %p", (void*)h);
return 1;
}
void new_dlevent(struct dso* p, int sym_index) {
struct dlevent* ev = calloc(1, sizeof(struct dlevent));
ev->dso = p;
ev->sym_index = sym_index;
if (p) p->event = ev;
ev->prev = tail;
if (tail) {
tail->next = ev;
#ifdef DYLINK_DEBUG
ev->id = tail->id + 1;
#endif
}
dbg("new_dlevent: ev=%p id=%d %s dso=%p sym_index=%d",
ev,
ev->id,
p ? p->name : "RTLD_DEFAULT",
p,
sym_index);
tail = ev;
#if _REENTRANT
thread_local_tail = ev;
#endif
}
static void load_library_done(struct dso* p) {
dbg("load_library_done: dso=%p mem_addr=%p mem_size=%zu "
"table_addr=%p table_size=%zu",
p,
p->mem_addr,
p->mem_size,
p->table_addr,
p->table_size);
new_dlevent(p, -1);
#ifdef _REENTRANT
_emscripten_dlsync_threads();
#endif
}
static struct dso* load_library_start(const char* name, int flags) {
if (!(flags & (RTLD_LAZY | RTLD_NOW))) {
error("invalid mode for dlopen(): Either RTLD_LAZY or RTLD_NOW is required");
return NULL;
}
struct dso* p;
size_t alloc_size = sizeof *p + strlen(name) + 1;
p = calloc(1, alloc_size);
p->flags = flags;
strcpy(p->name, name);
struct stat statbuf;
if (stat(name, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) {
int fd = open(name, O_RDONLY);
if (fd >= 0) {
off_t size = lseek(fd, 0, SEEK_END);
if (size != (off_t)-1) {
lseek(fd, 0, SEEK_SET);
p->file_data = malloc(size);
if (p->file_data) {
if (read(fd, p->file_data, size) == size) {
p->file_data_size = size;
} else {
free(p->file_data);
}
}
}
close(fd);
}
}
return p;
}
#ifdef _REENTRANT
#define ABORT_ON_SYNC_FAILURE 1
static void dlsync_next(struct dlevent* dlevent, em_promise_t promise);
static void sync_one_onsuccess(struct dso* dso, void* user_data) {
em_promise_t promise = (em_promise_t)user_data;
dbg("sync_one_onsuccess dso=%p event=%p promise=%p", dso, dso->event, promise);
thread_local_tail = dso->event;
dlsync_next(thread_local_tail->next, promise);
}
static void sync_one_onerror(struct dso* dso, void* user_data) {
#if ABORT_ON_SYNC_FAILURE
abort();
#else
em_promise_t promise = (em_promise_t)user_data;
emscripten_promise_reject(promise);
#endif
}
static void dlsync_next(struct dlevent* dlevent, em_promise_t promise) {
dbg("dlsync_next event=%p promise=%p", dlevent, promise);
while (dlevent && dlevent->sym_index != -1) {
dbg("calling _dlsym_catchup_js ....");
void* success = _dlsym_catchup_js(dlevent->dso, dlevent->sym_index);
if (!success) {
emscripten_errf("_dlsym_catchup_js failed: %s", dlerror());
sync_one_onerror(dlevent->dso, promise);
return;
}
dlevent = dlevent->next;
}
if (!dlevent) {
emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL);
return;
}
dbg("dlsync_next calling _emscripten_dlopen_js: dso=%p", dlevent->dso);
_emscripten_dlopen_js(
dlevent->dso, sync_one_onsuccess, sync_one_onerror, promise);
}
void _emscripten_dlsync_self_async(em_promise_t promise) {
dbg("_emscripten_dlsync_self_async promise=%p", promise);
dlsync_next(thread_local_tail->next, promise);
}
bool _emscripten_dlsync_self() {
assert(!emscripten_is_main_runtime_thread());
if (thread_local_tail == tail) {
dbg("_emscripten_dlsync_self: already in sync");
return true;
}
dbg("_emscripten_dlsync_self: catching up %p %p", thread_local_tail, tail);
while (thread_local_tail->next) {
struct dlevent* p = thread_local_tail->next;
if (p->sym_index != -1) {
dbg("_emscripten_dlsync_self: id=%d %s sym_index=%d",
p->id,
p->dso->name,
p->sym_index);
void* success = _dlsym_catchup_js(p->dso, p->sym_index);
if (!success) {
emscripten_errf("_dlsym_catchup_js failed: %s", dlerror());
return false;
}
} else {
dbg("_emscripten_dlsync_self: id=%d %s mem_addr=%p "
"mem_size=%zu table_addr=%p table_size=%zu",
p->id,
p->dso->name,
p->dso->mem_addr,
p->dso->mem_size,
p->dso->table_addr,
p->dso->table_size);
void* success = _dlopen_js(p->dso);
if (!success) {
emscripten_errf("_dlopen_js failed: %s", dlerror());
return false;
}
}
thread_local_tail = p;
}
dbg("_emscripten_dlsync_self: done");
return true;
}
struct promise_result {
em_promise_t promise;
bool result;
};
static void do_thread_sync(void* arg) {
dbg("do_thread_sync");
struct promise_result* info = arg;
info->result = _emscripten_dlsync_self();
}
static void do_thread_sync_out(void* arg) {
dbg("do_thread_sync_out");
int* result = (int*)arg;
*result = _emscripten_dlsync_self();
}
static void thread_sync_cancelled(void* arg) {
struct promise_result* info = arg;
dbg("thread_sync_cancelled: promise=%p result=%i", info->promise, info->result);
emscripten_promise_resolve(info->promise, EM_PROMISE_FULFILL, NULL);
emscripten_promise_destroy(info->promise);
free(info);
}
static void thread_sync_done(void* arg) {
struct promise_result* info = arg;
em_promise_t promise = info->promise;
dbg("thread_sync_done: promise=%p result=%i", promise, info->result);
if (info->result) {
emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL);
} else {
#if ABORT_ON_SYNC_FAILURE
abort();
#else
emscripten_promise_reject(promise);
#endif
}
emscripten_promise_destroy(promise);
free(info);
}
static em_proxying_queue * _Atomic dlopen_proxying_queue = NULL;
static thread_local bool processing_queue = false;
void _emscripten_process_dlopen_queue() {
if (dlopen_proxying_queue && !processing_queue) {
assert(!emscripten_is_main_runtime_thread());
processing_queue = true;
emscripten_proxy_execute_queue(dlopen_proxying_queue);
processing_queue = false;
}
}
int _emscripten_proxy_dlsync_async(pthread_t target_thread, em_promise_t promise) {
assert(emscripten_is_main_runtime_thread());
if (!dlopen_proxying_queue) {
dlopen_proxying_queue = em_proxying_queue_create();
}
struct promise_result* info = malloc(sizeof(struct promise_result));
if (!info) {
return false;
}
*info = (struct promise_result){
.promise = promise,
.result = false,
};
int rtn = emscripten_proxy_callback(dlopen_proxying_queue,
target_thread,
do_thread_sync,
thread_sync_done,
thread_sync_cancelled,
info);
if (!rtn) {
emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL);
emscripten_promise_destroy(promise);
free(info);
} else if (target_thread->sleeping) {
emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL);
return 0;
}
return rtn;
}
int _emscripten_proxy_dlsync(pthread_t target_thread) {
assert(emscripten_is_main_runtime_thread());
if (!dlopen_proxying_queue) {
dlopen_proxying_queue = em_proxying_queue_create();
}
int result;
if (!emscripten_proxy_sync(
dlopen_proxying_queue, target_thread, do_thread_sync_out, &result)) {
return 0;
}
return result;
}
#endif
static void dlopen_onsuccess(struct dso* dso, void* user_data) {
struct async_data* data = (struct async_data*)user_data;
dbg("dlopen_js_onsuccess: dso=%p mem_addr=%p mem_size=%zu",
dso,
dso->mem_addr,
dso->mem_size);
load_library_done(dso);
do_write_unlock();
data->onsuccess(data->user_data, dso);
free(data);
}
static void dlopen_onerror(struct dso* dso, void* user_data) {
struct async_data* data = (struct async_data*)user_data;
dbg("dlopen_js_onerror: dso=%p", dso);
do_write_unlock();
data->onerror(data->user_data);
free(dso);
free(data);
}
static int path_find(const char *name, const char *s, char *buf, size_t buf_size) {
if (s == NULL) {
return -1;
}
size_t l;
int fd;
for (;;) {
s += strspn(s, ":\n");
l = strcspn(s, ":\n");
if (l-1 >= INT_MAX) return -1;
if (snprintf(buf, buf_size, "%.*s/%s", (int)l, s, name) < buf_size) {
dbg("dlopen: path_find: %s", buf);
struct stat statbuf;
if (stat(buf, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) {
return 0;
}
switch (errno) {
case ENOENT:
case ENOTDIR:
case EACCES:
case ENAMETOOLONG:
break;
default:
dbg("dlopen: path_find failed: %s", strerror(errno));
return -2;
}
}
s += l;
}
}
const char* _emscripten_find_dylib(char* buf, const char* rpath, const char* file, size_t buflen) {
if (strchr(file, '/')) {
return NULL;
}
const char* env_path = getenv("LD_LIBRARY_PATH");
if (path_find(file, env_path, buf, buflen) == 0) {
dbg("dlopen: found in LD_LIBRARY_PATH: %s", buf);
return buf;
}
if (path_find(file, rpath, buf, buflen) == 0) {
dbg("dlopen: found in RPATH: %s", buf);
return buf;
}
return NULL;
}
static const char* find_dylib(char* buf, const char* file, size_t buflen) {
const char* res = _emscripten_find_dylib(buf, NULL, file, buflen);
if (res) {
return res;
}
return file;
}
static struct dso* find_existing(const char* file) {
for (struct dlevent* e = head; e; e = e->next) {
if (e->sym_index == -1 && !strcmp(e->dso->name, file)) {
dbg("dlopen: already opened: %p", e->dso);
return e->dso;
}
}
return NULL;
}
static struct dso* _dlopen(const char* file, int flags) {
if (!file) {
dbg("dlopen: NULL -> %p", head->dso);
return head->dso;
}
dbg("dlopen: %s [%d]", file, flags);
int cs;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
do_write_lock();
char buf[2*NAME_MAX+2];
file = find_dylib(buf, file, sizeof buf);
struct dso* p = find_existing(file);
if (p) {
goto end;
}
p = load_library_start(file, flags);
if (!p) {
goto end;
}
void* success = _dlopen_js(p);
if (!success) {
dbg("dlopen_js: failed: %p", p);
free(p);
p = NULL;
goto end;
}
dbg("dlopen_js: success: %p", p);
load_library_done(p);
end:
dbg("dlopen(%s): done: %p", file, p);
do_write_unlock();
pthread_setcancelstate(cs, 0);
return p;
}
void* dlopen(const char* file, int flags) {
return _dlopen(file, flags);
}
void emscripten_dlopen(const char* filename, int flags, void* user_data,
em_dlopen_callback onsuccess, em_arg_callback_func onerror) {
dbg("emscripten_dlopen: %s", filename);
if (!filename) {
onsuccess(user_data, head->dso);
return;
}
do_write_lock();
char buf[2*NAME_MAX+2];
filename = find_dylib(buf, filename, sizeof buf);
struct dso* p = find_existing(filename);
if (p) {
onsuccess(user_data, p);
return;
}
p = load_library_start(filename, flags);
if (!p) {
do_write_unlock();
onerror(user_data);
return;
}
struct async_data* d = malloc(sizeof(struct async_data));
d->user_data = user_data;
d->onsuccess = onsuccess;
d->onerror = onerror;
dbg("calling emscripten_dlopen_js %p", p);
_emscripten_dlopen_js(p, dlopen_onsuccess, dlopen_onerror, d);
}
static void promise_onsuccess(void* user_data, void* handle) {
em_promise_t p = (em_promise_t)user_data;
dbg("promise_onsuccess: %p", p);
emscripten_promise_resolve(p, EM_PROMISE_FULFILL, handle);
emscripten_promise_destroy(p);
}
static void promise_onerror(void* user_data) {
em_promise_t p = (em_promise_t)user_data;
dbg("promise_onerror: %p", p);
emscripten_promise_resolve(p, EM_PROMISE_REJECT, NULL);
emscripten_promise_destroy(p);
}
em_promise_t emscripten_dlopen_promise(const char* filename, int flags) {
em_promise_t p = emscripten_promise_create();
emscripten_dlopen(filename, flags, p, promise_onsuccess, promise_onerror);
em_promise_t ret = emscripten_promise_create();
emscripten_promise_resolve(ret, EM_PROMISE_MATCH, p);
return ret;
}
void* __dlsym(void* restrict p, const char* restrict s, void* restrict ra) {
dbg("__dlsym dso:%p sym:%s", p, s);
if (p != RTLD_DEFAULT && p != RTLD_NEXT && __dl_invalid_handle(p)) {
return 0;
}
if (p == head->dso) {
p = RTLD_DEFAULT;
}
void* res;
int sym_index = -1;
do_write_lock();
res = _dlsym_js(p, s, &sym_index);
if (sym_index != -1) {
new_dlevent(p, sym_index);
#ifdef _REENTRANT
_emscripten_dlsync_threads();
#endif
}
dbg("__dlsym done dso:%p res:%p", p, res);
do_write_unlock();
return res;
}