/*******************************************************************/
/* sltdl: a surrogate ltdl implementation */
/* Copyright (C) 2019 SysDeer Technologies, LLC */
/* Released under the Standard MIT License; see COPYING.SLTDL. */
/*******************************************************************/
#include <limits.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sltdl/sltdl.h>
#include "sltdl_core.h"
#include "sltdl_module.h"
#ifdef O_EXEC
#define SLTDL_MODULE_OPEN_OPTIONS (O_CLOEXEC|O_EXEC)
#else
#define SLTDL_MODULE_OPEN_OPTIONS (O_CLOEXEC)
#endif
static int lt_status;
static off_t lt_plen;
static off_t lt_plocs;
static char * lt_upath;
static char * lt_vpath;
static char ** lt_vmark;
static char ** lt_pathv;
static struct lt_modctx * lt_modv_head;
static struct lt_modctx * lt_modv_tail;
static struct lt_modctx * lt_modv_next;
static struct lt_modctx * lt_modv_cap;
static int lt_setstatus(int ret, int status)
{
lt_status = status;
return ret;
}
const char * lt_dlgetsearchpath(void)
{
return lt_upath;
}
static int lt_dlsetsearchpath_locked(const char * path)
{
const char * ch;
char * uch;
char * vch;
char ** pathv;
off_t elements;
if (path[0] != '/')
return lt_setstatus(1,SLTDL_PATH_INVALID_FIRST_CHAR);
elements = 1;
for (ch=&path[1]; *ch; ch++) {
if (*ch == ':') {
if (ch[1] != '/')
return lt_setstatus(1,SLTDL_PATH_INVALID_FIRST_CHAR);
elements++;
}
}
if (++elements > lt_plocs) {
if (lt_pathv) {
free(lt_pathv);
lt_pathv = 0;
}
lt_plocs = elements;
lt_plocs += 0x3f;
lt_plocs |= 0x3f;
lt_plocs ^= 0x3f;
if (!(lt_pathv = calloc(lt_plocs,sizeof(char *))))
return lt_setstatus(1,SLTDL_SYSTEM_ERROR);
}
if ((ch - path) > lt_plen) {
if (lt_upath) {
free(lt_upath);
lt_upath = 0;
}
lt_plen = ((ch - path + 1) <= 0x800)
? 0x800 : ch - path + 1;
lt_plen += 0x7ff;
lt_plen |= 0x7ff;
lt_plen ^= 0x7ff;
if (!(lt_upath = calloc(2*lt_plen,1)))
return lt_setstatus(1,SLTDL_SYSTEM_ERROR);
lt_vpath = <_upath[lt_plen];
}
pathv = lt_pathv;
*pathv++ = lt_vpath;
for (ch=path, uch=lt_upath, vch=lt_vpath; *ch; ch++) {
if (*ch == ':') {
*uch++ = ch[0];
*uch++ = ch[1];
*vch++ = 0;
*pathv = vch;
*vch++ = ch[1];
ch++;
pathv++;
} else {
*uch++ = *ch;
*vch++ = *ch;
}
}
lt_vmark = pathv;
return lt_setstatus(0,SLTDL_OK);
}
int lt_dlsetsearchpath(const char * path)
{
int ret;
lt_slock();
ret = lt_dlsetsearchpath_locked(path);
return lt_sunlock(ret,lt_status);
}
static int lt_dladdsearchdir_locked(const char * path)
{
int ret;
const char * ch;
char * buf;
off_t alen;
off_t plen;
if (path[0] != '/')
return lt_setstatus(1,SLTDL_PATH_INVALID_FIRST_CHAR);
for (ch=path; *ch; ch++)
if (*ch == ':')
return lt_setstatus(1,SLTDL_PATH_INVALID_SEPARATTOR_CHAR);
alen = strlen(path);
plen = strlen(lt_upath);
/* no allocation needed? */
if (!lt_pathv[lt_plocs - 2] && ((plen + 1 + alen + 1) <= lt_plen)) {
lt_upath[plen] = ':';
lt_vpath[plen] = 0;
plen++;
strcpy(<_upath[plen],path);
strcpy(<_vpath[plen],path);
*lt_vmark++ = <_vpath[plen];
return lt_setstatus(0,SLTDL_OK);
}
/* (allocation needed) */
if (!(buf = malloc(plen + 1 + alen + 1)))
return lt_setstatus(1,SLTDL_SYSTEM_ERROR);
sprintf(buf,"%s:%s",lt_upath,path);
ret = lt_dlsetsearchpath_locked(buf);
free(buf);
return ret;
}
int lt_dladdsearchdir(const char * path)
{
int ret;
lt_slock();
ret = lt_dladdsearchdir_locked(path);
return lt_sunlock(ret,lt_status);
}
int lt_dlinsertsearchdir(const char * mark, const char * path)
{
int ret;
const char * ch;
char * buf;
char * dst;
off_t alen;
off_t plen;
off_t slen;
off_t offset;
char ** pathv;
if (!mark)
return lt_dladdsearchdir(path);
lt_slock();
if (path[0] != '/')
return lt_sunlock(1,SLTDL_PATH_INVALID_FIRST_CHAR);
for (ch=path; *ch; ch++)
if (*ch == ':')
return lt_sunlock(1,SLTDL_PATH_INVALID_SEPARATTOR_CHAR);
alen = strlen(path);
plen = strlen(lt_upath);
if ((mark < lt_upath) || (mark >= <_upath[plen]))
return lt_sunlock(1,SLTDL_PATH_INVALID_MARK);
if ((mark > lt_upath) && (mark[-1] != ':'))
return lt_sunlock(1,SLTDL_PATH_INVALID_MARK);
mark = <_vpath[mark - lt_upath];
/* no allocation needed? */
if (!lt_pathv[lt_plocs - 2] && ((plen + 1 + alen + 1) <= lt_plen)) {
for (pathv=lt_vmark; pathv>=lt_pathv; pathv--) {
slen = strlen(pathv[-1]);
offset = pathv[-1] - lt_vpath;
offset += alen + 1;
memcpy(<_upath[offset],pathv[-1],slen);
memcpy(<_vpath[offset],<_upath[offset],slen);
if (pathv < lt_vmark)
lt_upath[offset + slen] = ':';
pathv[0] = pathv[-1] + alen + 1;
if (pathv[-1] == mark) {
offset = mark - lt_vpath;
strcpy(<_vpath[offset],path);
memcpy(<_upath[offset],path,alen);
lt_upath[offset+alen] = ':';
lt_vmark++;
return lt_sunlock(0,SLTDL_OK);
}
}
}
/* (allocation needed) */
if (!(buf = malloc(plen + 1 + alen + 1)))
return lt_sunlock(1,SLTDL_SYSTEM_ERROR);
for (dst=buf, pathv=lt_pathv; *pathv; pathv++) {
if (*pathv == mark)
dst += sprintf(dst,"%s:",path);
if (pathv[1])
dst += sprintf(dst,"%s:",*pathv);
else
dst += sprintf(dst,"%s",*pathv);
}
ret = lt_dlsetsearchpath_locked(buf);
free(buf);
return lt_sunlock(ret,lt_status);
}
static int lt_dlpathopen_locked(
const char * module,
const char ** extv,
char ** mpath)
{
int fdat;
int fdmod;
char ** ppath;
const char ** pext;
size_t plen;
size_t mlen;
size_t elen;
char path[1024];
if ((mlen = strlen(module)) >= sizeof(path))
return lt_setstatus(-1,SLTDL_PATH_INVALID_LEN);
memcpy(path,module,mlen);
for (ppath=lt_pathv; *ppath; ppath++) {
fdat = open(*ppath,O_RDONLY|O_DIRECTORY|O_CLOEXEC,0);
if (fdat >= 0) {
for (pext=extv; *pext; pext++) {
if (mlen + (elen = strlen(*pext)) >= (sizeof(path))) {
close(fdat);
return lt_setstatus(-1,SLTDL_PATH_INVALID_LEN);
}
memcpy(&path[mlen],*pext,elen);
path[mlen+elen] = 0;
fdmod = openat(fdat,path,SLTDL_MODULE_OPEN_OPTIONS,0);
if (fdmod >= 0) {
if (mpath) {
plen = strlen(*ppath);
plen += mlen + 1 + elen + 1;
if (!(*mpath = malloc(plen))) {
close(fdat);
close(fdmod);
return lt_setstatus(-1,SLTDL_SYSTEM_ERROR);
}
sprintf(*mpath,"%s/%s",*ppath,path);
}
close(fdat);
return lt_setstatus(fdmod,SLTDL_OK);
}
}
close(fdat);
}
}
return lt_setstatus(-1,SLTDL_PATH_NO_ENTRY);
}
int lt_dlpathopen(const char * module, const char ** extv)
{
int ret;
lt_slock();
ret = lt_dlpathopen_locked(module,extv,0);
return lt_sunlock(ret,lt_status);
}
static struct lt_modctx * lt_dlopen_locked(
const char * module,
const char ** extv,
int mode)
{
int fdmod;
char * mpath;
void * maddr;
struct lt_modctx * modctx;
struct lt_modctx * modctx_buf;
/* path open */
if ((fdmod = lt_dlpathopen_locked(module,extv,&mpath)) < 0)
return 0;
close(fdmod);
/* entry alloc */
if (lt_modv_next == lt_modv_cap) {
if (!(modctx_buf = calloc(64,sizeof(*modctx)))) {
free(mpath);
lt_setstatus(0,SLTDL_SYSTEM_ERROR);
return 0;
}
lt_modv_next = modctx_buf;
lt_modv_cap = <_modv_next[64];
}
/* dlopen */
if (!(maddr = dlopen(mpath,mode))) {
free(mpath);
lt_setstatus(0,SLTDL_DLFCN_ERROR);
return 0;
}
/* already dlopen'ed? */
for (modctx=lt_modv_head; modctx; modctx=modctx->mnext) {
if (!strcmp(modctx->mpath,mpath)) {
free(mpath);
modctx->mrefs++;
return modctx;
}
}
/* module entry */
modctx = lt_modv_next;
modctx->maddr = maddr;
modctx->mpath = mpath;
modctx->mrefs = 1;
lt_modv_next++;
/* add to list */
if (lt_modv_tail) {
lt_modv_tail->mnext = modctx;
lt_modv_tail = modctx;
} else {
lt_modv_head = modctx;
lt_modv_tail = modctx;
}
/* all done */
return modctx;
}
struct lt_modctx * lt_dlopen(const char * module)
{
struct lt_modctx * modctx;
const char * extv[2] = {"",0};
lt_slock();
modctx = lt_dlopen_locked(module,extv,RTLD_NOW);
lt_sunlock(0,lt_status);
return modctx;
}
struct lt_modctx * lt_dlopenext(const char * module)
{
struct lt_modctx * modctx;
const char * extv[3] = {"",OS_LIB_SUFFIX,0};
lt_slock();
modctx = lt_dlopen_locked(module,extv,RTLD_NOW);
lt_sunlock(0,lt_status);
return modctx;
}
struct lt_modctx * lt_dlopenadvise(const char * module, struct lt_modctl * modctl)
{
(void)modctl;
return lt_dlopenext(module);
}
void * lt_dlsym(struct lt_modctx * modctx, const char * symname)
{
return dlsym(modctx->maddr,symname);
}
int lt_dlclose(struct lt_modctx * modctx)
{
struct lt_modctx * pmod;
lt_slock();
for (pmod=lt_modv_head; pmod ; pmod=pmod->mnext) {
if (pmod == modctx) {
if (pmod->mrefs) {
pmod->mrefs--;
return lt_sunlock(0,SLTDL_OK);
}
return lt_sunlock(-1,SLTDL_MODULE_REF_COUNT);
}
}
return lt_sunlock(-1,SLTDL_MODULE_PTR_INVALID);
}