/*******************************************************************/ /* sltdl: a surrogate ltdl implementation */ /* Copyright (C) 2019 SysDeer Technologies, LLC */ /* Released under the Standard MIT License; see COPYING.SLTDL. */ /*******************************************************************/ #include #include #include #include #include #include #include #include #include #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); if (path[0] == '/') { if ((fdmod = open(path,SLTDL_MODULE_OPEN_OPTIONS,0)) >= 0) { if (mpath) { if (!(*mpath = realpath(path,0))) { close(fdmod); return lt_setstatus(-1,SLTDL_SYSTEM_ERROR); } } return lt_setstatus(fdmod,SLTDL_OK); } return lt_setstatus(-1,SLTDL_PATH_NO_ENTRY); } for (ppath=lt_pathv; ppath && *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) { char * dot; size_t slen; struct lt_modctx * modctx; const char * extv[2] = {"",0}; char dsobuf[PATH_MAX]; lt_slock(); if ((slen = strlen(module)) >= PATH_MAX) { lt_setstatus(0,SLTDL_SYSTEM_ERROR); lt_sunlock(0,lt_status); return 0; } strcpy(dsobuf,module); if ((dot = strrchr(dsobuf,'.'))) if (!strcmp(dot,".la")) if ((slen = (dot - dsobuf))) if (PATH_MAX - slen > strlen(OS_LIB_SUFFIX)) strcpy(dot,OS_LIB_SUFFIX); module = dsobuf; 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); }