diff options
Diffstat (limited to 'erts/emulator/beam/erl_bif_ddll.c')
-rw-r--r-- | erts/emulator/beam/erl_bif_ddll.c | 1964 |
1 files changed, 1964 insertions, 0 deletions
diff --git a/erts/emulator/beam/erl_bif_ddll.c b/erts/emulator/beam/erl_bif_ddll.c new file mode 100644 index 0000000000..9d5f0d9c02 --- /dev/null +++ b/erts/emulator/beam/erl_bif_ddll.c @@ -0,0 +1,1964 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2006-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* + * BIFs belonging to the 'erl_ddll' module together with utility + * functions for dynamic loading. The actual loading is done in + * erl_sys_ddll.c in respective system dependent directory. The + * driver structure contains a handle to the actual loaded "module" as + * well as record keeping information about processes having loaded + * the driver and processes monitoring the driver. A process in any + * way involved in ddll-drivers, get a special flag, which triggers + * cleenup at process exit. + */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define ERL_SYS_DRV + +#include "sys.h" +#include "erl_vm.h" +#include "global.h" +#include "erl_process.h" +#include "error.h" +#include "erl_driver.h" +#include "bif.h" +#include "big.h" +#include "dist.h" +#include "erl_version.h" + +#ifdef ERTS_SMP +#define DDLL_SMP 1 +#else +#define DDLL_SMP 0 +#endif + + +/* + * Local types + */ + +typedef struct { + Eterm pid; + Process *proc; + Uint status; + Uint count; +} ProcEntryInfo; + +/* + * Forward + */ +static char *pick_list_or_atom(Eterm name_term); +static erts_driver_t *lookup_driver(char *name); +static Eterm mkatom(char *str); +static void add_proc_loaded(DE_Handle *dh, Process *proc); +static void add_proc_loaded_deref(DE_Handle *dh, Process *proc); +static void set_driver_reloading(DE_Handle *dh, Process *proc, char *path, char *name, Uint flags); +static int load_driver_entry(DE_Handle **dhp, char *path, char *name); +static int do_unload_driver_entry(DE_Handle *dh, Eterm *save_name); +static int do_load_driver_entry(DE_Handle *dh, char *path, char *name); +#if 0 +static void unload_driver_entry(DE_Handle *dh); +#endif +static int reload_driver_entry(DE_Handle *dh); +static int build_proc_info(DE_Handle *dh, ProcEntryInfo **out_pei, Uint filter); +static int is_last_user(DE_Handle *dh, Process *proc); +static DE_ProcEntry *find_proc_entry(DE_Handle *dh, Process *proc, Uint status); +static void remove_proc_entry(DE_Handle *dh, DE_ProcEntry *pe); +static int num_procs(DE_Handle *dh, Uint status); +/*static int num_entries(DE_Handle *dh, Process *proc, Uint status);*/ +static void notify_proc(Process *proc, Eterm ref, Eterm driver_name, + Eterm type, Eterm tag, int errcode); +static void notify_all(DE_Handle *dh, char *name, Uint awaiting, Eterm type, Eterm tag); +static int load_error_need(int code); +static Eterm build_load_error_hp(Eterm *hp, int code); +static Eterm build_load_error(Process *p, int code); +static int errdesc_to_code(Eterm errdesc, int *code /* out */); +static Eterm add_monitor(Process *p, DE_Handle *dh, Uint status); +static Eterm notify_when_loaded(Process *p, Eterm name_term, char *name, + ErtsProcLocks plocks); +static Eterm notify_when_unloaded(Process *p, Eterm name_term, char *name, + ErtsProcLocks plocks, Uint flag); +static void first_ddll_reference(DE_Handle *dh); +static void dereference_all_processes(DE_Handle *dh); +static void restore_process_references(DE_Handle *dh); +static void ddll_no_more_references(void *vdh); + +#define lock_drv_list() erts_smp_mtx_lock(&erts_driver_list_lock) +#define unlock_drv_list() erts_smp_mtx_unlock(&erts_driver_list_lock) +#define assert_drv_list_locked() \ + ERTS_SMP_LC_ASSERT(erts_smp_lc_mtx_is_locked(&erts_driver_list_lock)) +#define assert_drv_list_not_locked() \ + ERTS_SMP_LC_ASSERT(!erts_smp_lc_mtx_is_locked(&erts_driver_list_lock)) + + +#define FREE_PORT_FLAGS (ERTS_PORT_SFLGS_DEAD & (~ERTS_PORT_SFLG_INITIALIZING)) + +/* + * try_load(Path, Name, OptionList) -> {ok,Status} | + * {ok, PendingStatus, Ref} | + * {error, ErrorDesc} + * Path = Name = string() | atom() + * OptionList = [ Option ] + * Option = {driver_options, DriverOptionList} | + * {monitor,MonitorOption} | + * {reload, ReloadOption} + * DriverOptionList = [ DriverOption ] + * DriverOption = kill_ports + * MonitorOption = pending_driver | pending + * ReloadOption = pending_driver | pending + * Status = loaded | already_loaded | PendingStatus + * PendingStatus = pending_driver | pending_process + * Ref = ref() + * ErrorDesc = ErrorAtom | OpaqueError + * ErrorAtom = linked_in_driver | inconsistent | + * permanent | pending + */ +/* + * Try to load. If the driver is OK, add as LOADED. If the driver is + * UNLOAD, possibly change to reload and add as LOADED, + * there should be no other + * LOADED tagged pid's. If the driver is RELOAD then add/increment as + * LOADED (should be some LOADED pid). If the driver is not present, + * really load and add as LOADED {ok,loaded} {ok,pending_driver} + * {error, permanent} {error,load_error()} + */ +BIF_RETTYPE erl_ddll_try_load_3(Process *p, Eterm path_term, + Eterm name_term, Eterm options) +{ + char *path = NULL; + int path_len; + char *name = NULL; + DE_Handle *dh; + erts_driver_t *drv; + int res; + Eterm soft_error_term = NIL; + Eterm ok_term = NIL; + Eterm *hp; + Eterm t; + int monitor = 0; + int reload = 0; + Eterm l; + Uint flags = 0; + int kill_ports = 0; + int do_build_load_error = 0; + int build_this_load_error = 0; + + for(l = options; is_list(l); l = CDR(list_val(l))) { + Eterm opt = CAR(list_val(l)); + Eterm *tp; + if (is_not_tuple(opt)) { + goto error; + } + tp = tuple_val(opt); + if (*tp != make_arityval(2) || is_not_atom(tp[1])) { + goto error; + } + switch (tp[1]) { + case am_driver_options: + { + Eterm ll; + for(ll = tp[2]; is_list(ll); ll = CDR(list_val(ll))) { + Eterm dopt = CAR(list_val(ll)); + if (dopt == am_kill_ports) { + flags |= ERL_DE_FL_KILL_PORTS; + } else { + goto error; + } + } + if (is_not_nil(ll)) { + goto error; + } + } + break; + case am_monitor: + if (tp[2] == am_pending_driver) { + monitor = 1; + } else if (tp[2] == am_pending ) { + monitor = 2; + } else { + goto error; + } + break; + case am_reload: + if (tp[2] == am_pending_driver) { + reload = 1; + } else if (tp[2] == am_pending ) { + reload = 2; + } else { + goto error; + } + break; + default: + goto error; + } + } + if (is_not_nil(l)) { + goto error; + } + + + if ((name = pick_list_or_atom(name_term)) == NULL) { + goto error; + } + + path_len = io_list_len(path_term); + + if (path_len <= 0) { + goto error; + } + path = erts_alloc(ERTS_ALC_T_DDLL_TMP_BUF, path_len + 1 /* might need path separator */ + sys_strlen(name) + 1); + if (io_list_to_buf(path_term, path, path_len) != 0) { + goto error; + } + while (path_len > 0 && (path[path_len-1] == '\\' || path[path_len-1] == '/')) { + --path_len; + } + path[path_len++] = '/'; + /*path[path_len] = '\0';*/ + sys_strcpy(path+path_len,name); + +#if DDLL_SMP + erts_smp_proc_unlock(p, ERTS_PROC_LOCK_MAIN); + lock_drv_list(); +#endif + if ((drv = lookup_driver(name)) != NULL) { + if (drv->handle == NULL) { + /* static_driver */ + soft_error_term = am_linked_in_driver; + goto soft_error; + } else { + dh = drv->handle; + if (dh->status == ERL_DE_OK) { + int is_last = is_last_user(dh,p); + if (reload == 1 && !is_last) { + /*Want reload if no other users, + but there are others...*/ + soft_error_term = am_pending_process; + goto soft_error; + } + if (reload != 0) { + DE_ProcEntry *old; + if ((dh->flags & ERL_FL_CONSISTENT_MASK) != + (flags & ERL_FL_CONSISTENT_MASK)) { + soft_error_term = am_inconsistent; + goto soft_error; + } + if ((old = find_proc_entry(dh, p, ERL_DE_PROC_LOADED)) == + NULL) { + soft_error_term = am_not_loaded_by_this_process; + goto soft_error; + } else { + remove_proc_entry(dh, old); + erts_ddll_dereference_driver(dh); + erts_free(ERTS_ALC_T_DDLL_PROCESS, old); + } + /* Reload requested and granted */ + dereference_all_processes(dh); + set_driver_reloading(dh, p, path, name, flags); + if (dh->flags & ERL_DE_FL_KILL_PORTS) { + kill_ports = 1; + } + ok_term = (reload == 1) ? am_pending_driver : + am_pending_process; + } else { + /* Already loaded and healthy (might be by me) */ + if (sys_strcmp(dh->full_path, path) || + (dh->flags & ERL_FL_CONSISTENT_MASK) != + (flags & ERL_FL_CONSISTENT_MASK)) { + soft_error_term = am_inconsistent; + goto soft_error; + } + add_proc_loaded(dh,p); + erts_ddll_reference_driver(dh); + monitor = 0; + ok_term = mkatom("already_loaded"); + } + } else if (dh->status == ERL_DE_UNLOAD || + dh->status == ERL_DE_FORCE_UNLOAD) { + /* pending driver */ + if (reload != 0) { + soft_error_term = am_not_loaded_by_this_process; + goto soft_error; + } + if (sys_strcmp(dh->full_path, path) || + (dh->flags & ERL_FL_CONSISTENT_MASK) != + (flags & ERL_FL_CONSISTENT_MASK)) { + soft_error_term = am_inconsistent; + goto soft_error; + } + dh->status = ERL_DE_OK; + notify_all(dh, drv->name, + ERL_DE_PROC_AWAIT_UNLOAD, am_UP, + am_unload_cancelled); + add_proc_loaded(dh,p); + erts_ddll_reference_driver(dh); + monitor = 0; + ok_term = mkatom("already_loaded"); + } else if (dh->status == ERL_DE_RELOAD || + dh->status == ERL_DE_FORCE_RELOAD) { + if (reload != 0) { + soft_error_term = am_pending_reload; + goto soft_error; + } + if (sys_strcmp(dh->reload_full_path, path) || + (dh->reload_flags & ERL_FL_CONSISTENT_MASK) != + (flags & ERL_FL_CONSISTENT_MASK)) { + soft_error_term = am_inconsistent; + goto soft_error; + } + /* Load of granted unload... */ + add_proc_loaded_deref(dh,p); /* Dont reference, will happen after reload */ + ++monitor; + ok_term = am_pending_driver; + } else { /* ERL_DE_PERMANENT */ + soft_error_term = am_permanent; + goto soft_error; + } + } + } else { /* driver non-existing */ + if (reload != 0) { + soft_error_term = am_not_loaded; + goto soft_error; + } + if ((res = load_driver_entry(&dh, path, name)) != ERL_DE_NO_ERROR) { + build_this_load_error = res; + do_build_load_error = 1; + soft_error_term = am_undefined; + goto soft_error; + } else { + dh->flags = flags; + add_proc_loaded(dh,p); + first_ddll_reference(dh); + monitor = 0; + ok_term = mkatom("loaded"); + } + } + assert_drv_list_locked(); + if (kill_ports) { + int j; + /* Avoid closing the driver by referencing it */ + erts_ddll_reference_driver(dh); + ASSERT(dh->status == ERL_DE_RELOAD); + dh->status = ERL_DE_FORCE_RELOAD; +#if DDLL_SMP + unlock_drv_list(); +#endif + for (j = 0; j < erts_max_ports; j++) { + Port* prt = &erts_port[j]; +#ifdef DDLL_SMP + erts_smp_port_state_lock(prt); +#endif + if (!(prt->status & FREE_PORT_FLAGS) && + prt->drv_ptr->handle == dh) { +#if DDLL_SMP + erts_smp_atomic_inc(&prt->refc); + /* Extremely rare spinlock */ + while(prt->status & ERTS_PORT_SFLG_INITIALIZING) { + erts_smp_port_state_unlock(prt); + erts_smp_port_state_lock(prt); + } + erts_smp_port_state_unlock(prt); + erts_smp_mtx_lock(prt->lock); + if (!(prt->status & ERTS_PORT_SFLGS_DEAD)) { + driver_failure_atom(j, "driver_unloaded"); + } +#else + driver_failure_atom(j, "driver_unloaded"); +#endif + erts_port_release(prt); + } + else erts_smp_port_state_unlock(prt); + } + /* Dereference, eventually causing driver destruction */ +#if DDLL_SMP + lock_drv_list(); +#endif + erts_ddll_dereference_driver(dh); + } + +#if DDLL_SMP + erts_ddll_reference_driver(dh); + unlock_drv_list(); + erts_smp_proc_lock(p, ERTS_PROC_LOCK_MAIN); + lock_drv_list(); + erts_ddll_dereference_driver(dh); +#endif + + p->flags |= F_USING_DDLL; + if (monitor) { + Eterm mref = add_monitor(p, dh, ERL_DE_PROC_AWAIT_LOAD); + hp = HAlloc(p,4); + t = TUPLE3(hp, am_ok, ok_term, mref); + } else { + hp = HAlloc(p,3); + t = TUPLE2(hp, am_ok, ok_term); + } +#if DDLL_SMP + unlock_drv_list(); +#endif + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) path); + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); + ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(p)); + BIF_RET(t); + soft_error: +#if DDLL_SMP + unlock_drv_list(); + erts_smp_proc_lock(p, ERTS_PROC_LOCK_MAIN); +#endif + if (do_build_load_error) { + soft_error_term = build_load_error(p, build_this_load_error); + } + + hp = HAlloc(p,3); + t = TUPLE2(hp, am_error, soft_error_term); + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) path); + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); + ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(p)); + BIF_RET(t); + error: + assert_drv_list_not_locked(); + ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(p)); + if (path != NULL) { + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) path); + } + if (name != NULL) { + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); + } + BIF_ERROR(p,BADARG); +} + +/* + * try_unload(Name, OptionList) -> {ok,Status} | + * {ok,PendingStatus, Ref} | + * {error, ErrorAtom} + * Name = string() | atom() + * OptionList = [ Option ] + * Option = {monitor,MonitorOption} | kill_ports + * MonitorOption = pending_driver | pending + * Status = unloaded | PendingStatus + * PendingStatus = pending_driver | pending_process + * Ref = ref() + * ErrorAtom = linked_in_driver | not_loaded | + * not_loaded_by_this_process | permanent + */ + +/* + You have to have loaded the driver and the pid state + is LOADED or AWAIT_LOAD. You will be removed from the list + regardless of driver state. + If the driver is loaded by someone else to, return is + {ok, pending_process} + If the driver is loaded but locked by a port, return is + {ok, pending_driver} + If the driver is loaded and free to unload (you're the last holding it) + {ok, unloaded} + If it's not loaded or not loaded by you + {error, not_loaded} or {error, not_loaded_by_you} + + Internally, if its in state UNLOADING, just return {ok, pending_driver} and + remove/decrement this pid (which should be an LOADED tagged one). + If the state is RELOADING, this pid should be in list as LOADED tagged, + only AWAIT_LOAD would be possible but not allowed for unloading, remove it + and, if the last LOADED tagged, change from RELOAD to UNLOAD and notify + any AWAIT_LOAD-waiters with {'DOWN', ref(), driver, name(), load_cancelled} + If the driver made itself permanent, {'UP', ref(), driver, name(), permanent} +*/ +Eterm erl_ddll_try_unload_2(Process *p, Eterm name_term, Eterm options) +{ + char *name = NULL; + Eterm ok_term = NIL; + Eterm soft_error_term = NIL; + erts_driver_t *drv; + DE_Handle *dh; + DE_ProcEntry *pe; + Eterm *hp; + Eterm t; + int monitor = 0; + Eterm l; + int kill_ports = 0; + + erts_smp_proc_unlock(p, ERTS_PROC_LOCK_MAIN); + + for(l = options; is_list(l); l = CDR(list_val(l))) { + Eterm opt = CAR(list_val(l)); + Eterm *tp; + if (is_not_tuple(opt)) { + if (opt == am_kill_ports) { + kill_ports = 1; + continue; + } else { + goto error; + } + } + tp = tuple_val(opt); + if (*tp != make_arityval(2) || tp[1] != am_monitor) { + goto error; + } + if (tp[2] == am_pending_driver) { + monitor = 1; + } else if (tp[2] == am_pending) { + monitor = 2; + } else { + goto error; + } + } + if (is_not_nil(l)) { + goto error; + } + + if ((name = pick_list_or_atom(name_term)) == NULL) { + goto error; + } + +#if DDLL_SMP + lock_drv_list(); +#endif + + if ((drv = lookup_driver(name)) == NULL) { + soft_error_term = am_not_loaded; + goto soft_error; + } + + if (drv->handle == NULL) { + soft_error_term = am_linked_in_driver; + goto soft_error; + } else if (drv->handle->status == ERL_DE_PERMANENT) { + soft_error_term = am_permanent; + goto soft_error; + } + dh = drv->handle; + if (dh->flags & ERL_DE_FL_KILL_PORTS) { + kill_ports = 1; + } + if ((pe = find_proc_entry(dh, p, ERL_DE_PROC_LOADED)) == NULL) { + if (num_procs(dh, ERL_DE_PROC_LOADED) > 0) { + soft_error_term = am_not_loaded_by_this_process; + goto soft_error; + } + } else { + remove_proc_entry(dh, pe); + if (!(pe->flags & ERL_DE_FL_DEREFERENCED)) { + erts_ddll_dereference_driver(dh); + } + erts_free(ERTS_ALC_T_DDLL_PROCESS, pe); + } + if (num_procs(dh, ERL_DE_PROC_LOADED) > 0) { + ok_term = am_pending_process; + --monitor; + goto done; + } + if (dh->status == ERL_DE_RELOAD || + dh->status == ERL_DE_FORCE_RELOAD) { + notify_all(dh, drv->name, + ERL_DE_PROC_AWAIT_LOAD, am_DOWN, am_load_cancelled); + erts_free(ERTS_ALC_T_DDLL_HANDLE,dh->reload_full_path); + erts_free(ERTS_ALC_T_DDLL_HANDLE,dh->reload_driver_name); + dh->reload_full_path = dh->reload_driver_name = NULL; + dh->reload_flags = 0; + } + if (dh->port_count > 0) { + ++kill_ports; + } + dh->status = ERL_DE_UNLOAD; + ok_term = am_pending_driver; +done: + assert_drv_list_locked(); + if (kill_ports > 1) { + int j; + /* Avoid closing the driver by referencing it */ + erts_ddll_reference_driver(dh); + dh->status = ERL_DE_FORCE_UNLOAD; +#if DDLL_SMP + unlock_drv_list(); +#endif + for (j = 0; j < erts_max_ports; j++) { + Port* prt = &erts_port[j]; +#if DDLL_SMP + erts_smp_port_state_lock(prt); +#endif + if (!(prt->status & FREE_PORT_FLAGS) + && prt->drv_ptr->handle == dh) { +#if DDLL_SMP + erts_smp_atomic_inc(&prt->refc); + /* Extremely rare spinlock */ + while(prt->status & ERTS_PORT_SFLG_INITIALIZING) { + erts_smp_port_state_unlock(prt); + erts_smp_port_state_lock(prt); + } + erts_smp_port_state_unlock(prt); + erts_smp_mtx_lock(prt->lock); + if (!(prt->status & ERTS_PORT_SFLGS_DEAD)) { + driver_failure_atom(j, "driver_unloaded"); + } +#else + driver_failure_atom(j, "driver_unloaded"); +#endif + erts_port_release(prt); + } + else erts_smp_port_state_unlock(prt); + } +#if DDLL_SMP + lock_drv_list(); +#endif + erts_ddll_dereference_driver(dh); + } + +#if DDLL_SMP + erts_ddll_reference_driver(dh); + unlock_drv_list(); + erts_smp_proc_lock(p, ERTS_PROC_LOCK_MAIN); + lock_drv_list(); + erts_ddll_dereference_driver(dh); +#endif + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); + p->flags |= F_USING_DDLL; + if (monitor > 0) { + Eterm mref = add_monitor(p, dh, ERL_DE_PROC_AWAIT_UNLOAD); + hp = HAlloc(p,4); + t = TUPLE3(hp, am_ok, ok_term, mref); + } else { + hp = HAlloc(p,3); + t = TUPLE2(hp, am_ok, ok_term); + } + if (kill_ports > 1) { + ERTS_BIF_CHK_EXITED(p); /* May be exited by port killing */ + } +#if DDLL_SMP + unlock_drv_list(); +#endif + BIF_RET(t); + +soft_error: +#if DDLL_SMP + unlock_drv_list(); +#endif + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); + erts_smp_proc_lock(p, ERTS_PROC_LOCK_MAIN); + hp = HAlloc(p,3); + t = TUPLE2(hp, am_error, soft_error_term); + BIF_RET(t); + + error: /* No lock fiddling before going here */ + assert_drv_list_not_locked(); + if (name != NULL) { + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); + } + erts_smp_proc_lock(p, ERTS_PROC_LOCK_MAIN); + BIF_ERROR(p,BADARG); +} + + +/* + * A shadow of the "real" demonitor BIF + */ +BIF_RETTYPE erl_ddll_demonitor_1(Process *p, Eterm ref) +{ + if (is_not_internal_ref(ref)) { + BIF_ERROR(p, BADARG); + } + if (p->flags & F_USING_DDLL) { + erts_ddll_remove_monitor(p, ref, ERTS_PROC_LOCK_MAIN); + } + BIF_RET(am_true); +} + +/* + * A shadow of the "real" monitor BIF + */ +BIF_RETTYPE erl_ddll_monitor_2(Process *p, Eterm dr, Eterm what) +{ + if (dr != am_driver) { + BIF_ERROR(p,BADARG); + } + return erts_ddll_monitor_driver(p, what, ERTS_PROC_LOCK_MAIN); +} + +/* + * Return list of loaded drivers {ok,[string()]} + */ +Eterm erl_ddll_loaded_drivers_0(Process *p) +{ + Eterm *hp; + int need = 3; + Eterm res = NIL; + erts_driver_t *drv; +#if DDLL_SMP + lock_drv_list(); +#endif + for (drv = driver_list; drv; drv = drv->next) { + need += sys_strlen(drv->name)*2+2; + } + hp = HAlloc(p,need); + for (drv = driver_list; drv; drv = drv->next) { + Eterm l; + l = buf_to_intlist(&hp, drv->name, sys_strlen(drv->name), NIL); + res = CONS(hp,l,res); + hp += 2; + } + res = TUPLE2(hp,am_ok,res); + /* hp += 3 */ +#if DDLL_SMP + unlock_drv_list(); +#endif + BIF_RET(res); +} + +/* + * More detailed info about loaded drivers: + * item is processes, driver_options, port_count, linked_in_driver, + * permanent, awaiting_load, awaiting_unload + */ +Eterm erl_ddll_info_2(Process *p, Eterm name_term, Eterm item) +{ + char *name = NULL; + Eterm res = NIL; + erts_driver_t *drv; + ProcEntryInfo *pei = NULL; + int num_pei; + Eterm *hp; + int i; + Uint filter; +#if DDLL_SMP + int have_lock = 0; +#endif + + if ((name = pick_list_or_atom(name_term)) == NULL) { + goto error; + } + + if (!is_atom(item)) { + goto error; + } + +#if DDLL_SMP + lock_drv_list(); + have_lock = 1; +#endif + if ((drv = lookup_driver(name)) == NULL) { + goto error; + } + + switch (item) { + case am_processes: + filter = ERL_DE_PROC_LOADED; + break; + case am_driver_options: + if (drv->handle == NULL) { + res = am_linked_in_driver; + } else { + Uint start_flags = drv->handle->flags & ERL_FL_CONSISTENT_MASK; + /* Cheating, only one flag for now... */ + if (start_flags & ERL_DE_FL_KILL_PORTS) { + Eterm *myhp; + myhp = HAlloc(p,2); + res = CONS(myhp,am_kill_ports,NIL); + } else { + res = NIL; + } + } + goto done; + case am_port_count: + if (drv->handle == NULL) { + res = am_linked_in_driver; + } else if (drv->handle->status == ERL_DE_PERMANENT) { + res = am_permanent; + } else { + res = make_small(drv->handle->port_count); + } + goto done; + case am_linked_in_driver: + if (drv->handle == NULL){ + res = am_true; + } else { + res = am_false; + } + goto done; + case am_permanent: + if (drv->handle != NULL && drv->handle->status == ERL_DE_PERMANENT) { + res = am_true; + } else { + res = am_false; + } + goto done; + case am_awaiting_load: + filter = ERL_DE_PROC_AWAIT_LOAD; + break; + case am_awaiting_unload: + filter = ERL_DE_PROC_AWAIT_UNLOAD; + break; + default: + goto error; + } + + if (drv->handle == NULL) { + res = am_linked_in_driver; + goto done; + } else if (drv->handle->status == ERL_DE_PERMANENT) { + res = am_permanent; + goto done; + } + num_pei = build_proc_info(drv->handle, &pei, filter); + if (!num_pei) { + goto done; + } + hp = HAlloc(p,num_pei * (2+3)); + for (i = 0; i < num_pei; ++ i) { + Eterm tpl = TUPLE2(hp,pei[i].pid,make_small(pei[i].count)); + hp += 3; + res = CONS(hp,tpl,res); + hp += 2; + } + done: +#if DDLL_SMP + unlock_drv_list(); +#endif + if (pei) + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, pei); + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); + BIF_RET(res); + error: + if (name != NULL) { + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); + } +#if DDLL_SMP + if (have_lock) { + unlock_drv_list(); + } +#endif + BIF_ERROR(p,BADARG); +} + +/* + * Backend for erl_ddll:format_error, handles all "soft" errors returned by builtins, + * possibly by calling the system specific error handler + */ +Eterm erl_ddll_format_error_int_1(Process *p, Eterm code_term) +{ + char *errstring = NULL; + int errint; + int len; + Eterm ret = NIL; + Eterm *hp; + + /* These errors can only appear in the erlang interface, not in the interface provided + to drivers... */ + switch (code_term) { + case am_inconsistent: + errstring = "Driver name and/or driver options are inconsistent with " + "currently loaded driver"; + break; + case am_linked_in_driver: + errstring = "Driver is statically linked and " + "cannot be loaded/unloaded"; + break; + case am_permanent: + errstring = "DDLL driver is permanent an can not be unloaded/loaded"; + break; + case am_not_loaded: + errstring = "DDLL driver is not loaded"; + break; + case am_not_loaded_by_this_process: + errstring = "DDLL driver was not loaded by this process"; + break; + case am_not_pending: + errstring = "DDLL load not pending for this driver name"; + break; + case am_already_loaded: + errstring = "DDLL driver is already loaded successfully"; + break; + case am_pending_reload: + errstring = "Driver reloading is already pending"; + break; + case am_pending_process: + errstring = "Driver is loaded by others when attempting " + "option {reload, pending_driver}"; + break; + default: + /* A "real" error, we translate the atom to a code and translate the code + to a string in the same manner as in the interface provided to drivers... */ + if (errdesc_to_code(code_term,&errint) != 0) { + goto error; + } +#if DDLL_SMP + lock_drv_list(); +#endif + errstring = erts_ddll_error(errint); +#if DDLL_SMP + unlock_drv_list(); +#endif + break; + } + if (errstring == NULL) { + goto error; + } + len = sys_strlen(errstring); + hp = HAlloc(p, 2 * len); + ret = buf_to_intlist(&hp, errstring, len, NIL); + BIF_RET(ret); + error: + BIF_ERROR(p,BADARG); +} + +void erts_ddll_init(void) +{ + erl_sys_ddll_init(); +} + +/* Return value as a bif, called by erlang:monitor */ +Eterm erts_ddll_monitor_driver(Process *p, + Eterm description, + ErtsProcLocks plocks) +{ + Eterm *tp; + Eterm ret; + char *name; + + if (is_not_tuple(description)) { + BIF_ERROR(p,BADARG); + } + tp = tuple_val(description); + if (*tp != make_arityval(2)) { + BIF_ERROR(p,BADARG); + } + if ((name = pick_list_or_atom(tp[1])) == NULL) { + BIF_ERROR(p,BADARG); + } + switch (tp[2]) { + case am_loaded: + ERTS_BIF_PREP_RET(ret, notify_when_loaded(p,tp[1],name,plocks)); + break; + case am_unloaded: + ERTS_BIF_PREP_RET(ret, notify_when_unloaded(p,tp[1],name,plocks, + ERL_DE_PROC_AWAIT_UNLOAD)); + break; + case am_unloaded_only: + ERTS_BIF_PREP_RET(ret, + notify_when_unloaded(p,tp[1],name,plocks, + ERL_DE_PROC_AWAIT_UNLOAD_ONLY)); + break; + default: + ERTS_BIF_PREP_ERROR(ret,p,BADARG); + break; + } + + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); + return ret; +} + +void erts_ddll_remove_monitor(Process *p, Eterm ref, ErtsProcLocks plocks) +{ + erts_driver_t *drv; + erts_smp_proc_unlock(p, plocks); + lock_drv_list(); + drv = driver_list; + while (drv != NULL) { + if (drv->handle != NULL && drv->handle->status != ERL_DE_PERMANENT) { + DE_ProcEntry **pe = &(drv->handle->procs); + while ((*pe) != NULL) { + if ((*pe)->proc == p && + ((*pe)->awaiting_status == ERL_DE_PROC_AWAIT_LOAD || + (*pe)->awaiting_status == ERL_DE_PROC_AWAIT_UNLOAD || + (*pe)->awaiting_status == + ERL_DE_PROC_AWAIT_UNLOAD_ONLY) && + eq(make_internal_ref(&((*pe)->heap)),ref)) { + DE_ProcEntry *r = *pe; + *pe = r->next; + erts_free(ERTS_ALC_T_DDLL_PROCESS, (void *) r); + goto done; + } + pe = &((*pe)->next); + } + } + drv = drv->next; + } + done: + unlock_drv_list(); + erts_smp_proc_lock(p, plocks); +} + +/* + * Called from erl_process.c. + */ +void erts_ddll_proc_dead(Process *p, ErtsProcLocks plocks) +{ + erts_driver_t *drv; + erts_smp_proc_unlock(p, plocks); + lock_drv_list(); + drv = driver_list; + while (drv != NULL) { + if (drv->handle != NULL && drv->handle->status != ERL_DE_PERMANENT) { + DE_ProcEntry **pe = &(drv->handle->procs); + int kill_ports = (drv->handle->flags & ERL_DE_FL_KILL_PORTS); + int left = 0; + while ((*pe) != NULL) { + if ((*pe)->proc == p) { + DE_ProcEntry *r = *pe; + *pe = r->next; + if (!(r->flags & ERL_DE_FL_DEREFERENCED) && + r->awaiting_status == ERL_DE_PROC_LOADED) { + erts_ddll_dereference_driver(drv->handle); + } + erts_free(ERTS_ALC_T_DDLL_PROCESS, (void *) r); + } else { + if ((*pe)->awaiting_status == ERL_DE_PROC_LOADED) { + ++left; + } + pe = &((*pe)->next); + } + } + if (!left) { + DE_Handle *dh = drv->handle; + if (dh->status == ERL_DE_RELOAD || + dh->status == ERL_DE_FORCE_RELOAD) { + notify_all(dh, drv->name, + ERL_DE_PROC_AWAIT_LOAD, am_DOWN, am_load_cancelled); + erts_free(ERTS_ALC_T_DDLL_HANDLE,dh->reload_full_path); + erts_free(ERTS_ALC_T_DDLL_HANDLE,dh->reload_driver_name); + dh->reload_full_path = dh->reload_driver_name = NULL; + dh->reload_flags = 0; + } + dh->status = ERL_DE_UNLOAD; + } + if (!left && drv->handle->port_count > 0) { + if (kill_ports) { + int j; + DE_Handle *dh = drv->handle; + erts_ddll_reference_driver(dh); + dh->status = ERL_DE_FORCE_UNLOAD; +#if DDLL_SMP + unlock_drv_list(); +#endif + for (j = 0; j < erts_max_ports; j++) { + Port* prt = &erts_port[j]; +#if DDLL_SMP + erts_smp_port_state_lock(prt); +#endif + if (!(prt->status & FREE_PORT_FLAGS) && + prt->drv_ptr->handle == dh) { +#if DDLL_SMP + erts_smp_atomic_inc(&prt->refc); + while(prt->status & ERTS_PORT_SFLG_INITIALIZING) { + erts_smp_port_state_unlock(prt); + erts_smp_port_state_lock(prt); + } + erts_smp_port_state_unlock(prt); + erts_smp_mtx_lock(prt->lock); + if (!(prt->status & ERTS_PORT_SFLGS_DEAD)) { + driver_failure_atom(j, "driver_unloaded"); + } +#else + driver_failure_atom(j, "driver_unloaded"); +#endif + erts_port_release(prt); + } + else erts_smp_port_state_unlock(prt); + } +#if DDLL_SMP + lock_drv_list(); /* Needed for future list operations */ +#endif + drv = drv->next; /* before allowing destruction */ + erts_ddll_dereference_driver(dh); + } else { + drv = drv->next; + } + } else { + drv = drv->next; + } + } else { + drv = drv->next; + } + } + unlock_drv_list(); + erts_smp_proc_lock(p, plocks); +} +void erts_ddll_lock_driver(DE_Handle *dh, char *name) +{ + DE_ProcEntry *p,*q; + assert_drv_list_locked(); + notify_all(dh, name, + ERL_DE_PROC_AWAIT_LOAD, am_UP, am_permanent); + notify_all(dh, name, + ERL_DE_PROC_AWAIT_UNLOAD, am_UP, am_permanent); + notify_all(dh, name, + ERL_DE_PROC_AWAIT_UNLOAD_ONLY, am_UP, am_permanent); + + p = dh->procs; + while(p != NULL) { + q = p; + p = p->next; + erts_free(ERTS_ALC_T_DDLL_PROCESS, (void *) q); + } + dh->procs = NULL; + erts_ddll_reference_driver(dh); + dh->status = ERL_DE_PERMANENT; +} + + +void erts_ddll_increment_port_count(DE_Handle *dh) +{ + assert_drv_list_locked(); + dh->port_count++; +} + +void erts_ddll_decrement_port_count(DE_Handle *dh) +{ + assert_drv_list_locked(); + ASSERT(dh->port_count > 0); + dh->port_count--; +} + +static void first_ddll_reference(DE_Handle *dh) +{ + assert_drv_list_locked(); + erts_refc_init(&(dh->refc),1); +} + +void erts_ddll_reference_driver(DE_Handle *dh) +{ + assert_drv_list_locked(); + if (erts_refc_inctest(&(dh->refc),1) == 1) { + erts_refc_inc(&(dh->refc),2); /* add a reference for the scheduled operation */ + } +} + +void erts_ddll_reference_referenced_driver(DE_Handle *dh) +{ + erts_refc_inc(&(dh->refc),2); +} + +void erts_ddll_dereference_driver(DE_Handle *dh) +{ + if (erts_refc_dectest(&(dh->refc),0) == 0) { + /* No lock here, but if the driver is referenced again, + the scheduled deletion is added as a reference too, see above */ + erts_schedule_misc_op(ddll_no_more_references, (void *) dh); + } +} +static void dereference_all_processes(DE_Handle *dh) +{ + DE_ProcEntry *p; + assert_drv_list_locked(); + for(p = dh->procs;p != NULL; p = p->next) { + if (p->awaiting_status == ERL_DE_PROC_LOADED) { + ASSERT(!(p->flags & ERL_DE_FL_DEREFERENCED)); + erts_ddll_dereference_driver(dh); + p->flags |= ERL_DE_FL_DEREFERENCED; + } + } +} + +static void restore_process_references(DE_Handle *dh) +{ + DE_ProcEntry *p; + assert_drv_list_locked(); + ASSERT(erts_refc_read(&(dh->refc),0) == 0); + for(p = dh->procs;p != NULL; p = p->next) { + if (p->awaiting_status == ERL_DE_PROC_LOADED) { + ASSERT(p->flags & ERL_DE_FL_DEREFERENCED); + erts_refc_inc(&(dh->refc),1); + p->flags &= ~ERL_DE_FL_DEREFERENCED; + } + } +} + + +int erts_ddll_driver_ok(DE_Handle *dh) +{ + assert_drv_list_locked(); + return ((dh == NULL) || (dh->status != ERL_DE_FORCE_UNLOAD && + dh->status != ERL_DE_FORCE_RELOAD)); +} + + +static void ddll_no_more_references(void *vdh) +{ + DE_Handle *dh = (DE_Handle *) vdh; + int x; + + lock_drv_list(); + + x = erts_refc_read(&(dh->refc),0); + if (x > 0) { + x = erts_refc_dectest(&(dh->refc),0); /* delete the reference added for me */ + } + + + if (x == 0) { + DE_ProcEntry **p = &(dh->procs); + Eterm save_driver_name = am_undefined; + ASSERT(dh->status != ERL_DE_OK); + do_unload_driver_entry(dh,&save_driver_name); + while (*p != NULL) { + DE_ProcEntry *q; + if ((*p)->awaiting_status == ERL_DE_PROC_AWAIT_UNLOAD || + (*p)->awaiting_status == ERL_DE_PROC_AWAIT_UNLOAD_ONLY) { + notify_proc((*p)->proc, + make_internal_ref(&((*p)->heap)), + save_driver_name,am_DOWN,am_unloaded, 0); + q = *p; + *p = q->next; + erts_free(ERTS_ALC_T_DDLL_PROCESS, (void *) q); + } else { + ASSERT(dh->status == ERL_DE_RELOAD || + dh->status == ERL_DE_FORCE_RELOAD); + p = &((*p)->next); + } + } + + if (dh->status == ERL_DE_UNLOAD || dh->status == ERL_DE_FORCE_UNLOAD) { + ASSERT(dh->full_path != NULL); + erts_free(ERTS_ALC_T_DDLL_HANDLE, (void *) dh->full_path); + erts_free(ERTS_ALC_T_DDLL_HANDLE, (void *) dh); + } else { /* ERL_DE_RELOAD || ERL_DE_FORCE_RELOAD */ + int reload_res = + reload_driver_entry(dh); + p = &(dh->procs); + while (*p != NULL) { + DE_ProcEntry *q; + if ((*p)->awaiting_status == ERL_DE_PROC_AWAIT_LOAD) { + if (reload_res == 0) { + notify_proc((*p)->proc, + make_internal_ref(&((*p)->heap)), + save_driver_name, am_UP, am_loaded, 0); + } else { + notify_proc((*p)->proc, + make_internal_ref(&((*p)->heap)), + save_driver_name, am_DOWN, am_load_failure, reload_res); + } + q = *p; + *p = q->next; + erts_free(ERTS_ALC_T_DDLL_PROCESS, (void *) q); + } else { + if (reload_res != 0) { + DE_ProcEntry *q = *p; + *p = q->next; + erts_free(ERTS_ALC_T_DDLL_PROCESS, (void *) q); + } else { + p = &((*p)->next); + } + } + } + if (reload_res != 0) { + ASSERT(dh->full_path == NULL); + erts_free(ERTS_ALC_T_DDLL_HANDLE, (void *) dh); + } + } + } + unlock_drv_list(); +} + +char *erts_ddll_error(int code) { + switch (code) { + case ERL_DE_NO_ERROR: + return "No error"; + case ERL_DE_LOAD_ERROR_NO_INIT: + return "No driver init in dynamic library"; + case ERL_DE_LOAD_ERROR_FAILED_INIT: + return "Driver init failed"; + case ERL_DE_LOAD_ERROR_BAD_NAME: + return "Bad driver name"; + case ERL_DE_LOAD_ERROR_NAME_TO_LONG: + return "Driver name to long"; + case ERL_DE_LOAD_ERROR_INCORRECT_VERSION: + return "Driver compiled with incorrect version of erl_driver.h"; + case ERL_DE_ERROR_NO_DDLL_FUNCTIONALITY: + return "DDLL functionality not available on this platform"; + case ERL_DE_ERROR_UNSPECIFIED: + return "Unspecified dynamic library error"; + case ERL_DE_LOOKUP_ERROR_NOT_FOUND: + return "Symbol not found in dynamic library"; + default: + return erts_sys_ddll_error(code); + } +} + +/* + * Utilities + */ +static Eterm notify_when_loaded(Process *p, Eterm name_term, char *name, ErtsProcLocks plocks) +{ + Eterm r = NIL; + Eterm immediate_tag = NIL; + Eterm immediate_type = NIL; + erts_driver_t *drv; + + ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN & plocks); +#if DDLL_SMP + lock_drv_list(); +#endif + if ((drv = lookup_driver(name)) == NULL) { + immediate_tag = am_unloaded; + immediate_type = am_DOWN; + goto immediate; + } + if (drv->handle == NULL || drv->handle->status == ERL_DE_PERMANENT) { + immediate_tag = am_permanent; + immediate_type = am_UP; + goto immediate; + } + + switch (drv->handle->status) { + case ERL_DE_OK: + immediate_tag = am_loaded; + immediate_type = am_UP; + goto immediate; + case ERL_DE_UNLOAD: + case ERL_DE_FORCE_UNLOAD: + immediate_tag = am_load_cancelled; + immediate_type = am_DOWN; + goto immediate; + case ERL_DE_RELOAD: + case ERL_DE_FORCE_RELOAD: + break; + default: + erl_exit(1,"Internal error, unknown state %u in dynamic driver.", drv->handle->status); + } + p->flags |= F_USING_DDLL; + r = add_monitor(p, drv->handle, ERL_DE_PROC_AWAIT_LOAD); +#if DDLL_SMP + unlock_drv_list(); +#endif + BIF_RET(r); + immediate: + r = erts_make_ref(p); +#if DDLL_SMP + erts_smp_proc_unlock(p, plocks); +#endif + notify_proc(p, r, name_term, immediate_type, immediate_tag, 0); +#if DDLL_SMP + unlock_drv_list(); + erts_smp_proc_lock(p, plocks); +#endif + BIF_RET(r); +} + +static Eterm notify_when_unloaded(Process *p, Eterm name_term, char *name, ErtsProcLocks plocks, Uint flag) +{ + Eterm r = NIL; + Eterm immediate_tag = NIL; + Eterm immediate_type = NIL; + erts_driver_t *drv; + + ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN & plocks); +#if DDLL_SMP + lock_drv_list(); +#endif + if ((drv = lookup_driver(name)) == NULL) { + immediate_tag = am_unloaded; + immediate_type = am_DOWN; + goto immediate; + } + if (drv->handle == NULL || drv->handle->status == ERL_DE_PERMANENT) { + immediate_tag = am_permanent; + immediate_type = am_UP; + goto immediate; + } + + p->flags |= F_USING_DDLL; + r = add_monitor(p, drv->handle, flag); +#if DDLL_SMP + unlock_drv_list(); +#endif + BIF_RET(r); + immediate: + r = erts_make_ref(p); +#if DDLL_SMP + erts_smp_proc_unlock(p, plocks); +#endif + notify_proc(p, r, name_term, immediate_type, immediate_tag, 0); +#if DDLL_SMP + unlock_drv_list(); + erts_smp_proc_lock(p, plocks); +#endif + BIF_RET(r); +} + + +static int is_last_user(DE_Handle *dh, Process *proc) { + DE_ProcEntry *p = dh->procs; + int found = 0; + + assert_drv_list_locked(); + + while (p != NULL) { + if (p->proc == proc && p->awaiting_status == ERL_DE_PROC_LOADED) { + if (found == 0) { + found = 1; + } else { + return 0; + } + } else if (p->awaiting_status == ERL_DE_PROC_LOADED) { + return 0; + } + p = p->next; + } + return found; +} + +static DE_ProcEntry *find_proc_entry(DE_Handle *dh, Process *proc, Uint status) +{ + DE_ProcEntry *p = dh->procs; + + assert_drv_list_locked(); + + while (p != NULL) { + if (p->proc == proc && p->awaiting_status == status) { + return p; + } + p = p->next; + } + return NULL; +} + +static void remove_proc_entry(DE_Handle *dh, DE_ProcEntry *pe) +{ + DE_ProcEntry **p = &(dh->procs); + + while (*p != NULL && *p != pe) { + p = &((*p)->next); + } + if ((*p) != NULL) { + *p = (*p)->next; + } +} + +static int num_procs(DE_Handle *dh, Uint status) { + DE_ProcEntry *p = dh->procs; + int i = 0; + + assert_drv_list_locked(); + + while (p != NULL) { + if (p->awaiting_status == status) { + ++i; + } + p = p->next; + } + return i; +} +/* +static int num_entries(DE_Handle *dh, Process *proc, Uint status) { + DE_ProcEntry *p = dh->procs; + int i = 0; + + assert_drv_list_locked(); + while (p != NULL) { + if (p->awaiting_status == status && p->proc == proc) { + ++i; + } + p = p->next; + } + return i; +} +*/ +static void add_proc_loaded(DE_Handle *dh, Process *proc) +{ + DE_ProcEntry *p; + assert_drv_list_locked(); + p = erts_alloc(ERTS_ALC_T_DDLL_PROCESS, sizeof(DE_ProcEntry)); + p->proc = proc; + p->flags = 0; + p->awaiting_status = ERL_DE_PROC_LOADED; + p->next = dh->procs; + dh->procs = p; +} + +static void add_proc_loaded_deref(DE_Handle *dh, Process *proc) +{ + DE_ProcEntry *p; + assert_drv_list_locked(); + p = erts_alloc(ERTS_ALC_T_DDLL_PROCESS, sizeof(DE_ProcEntry)); + p->proc = proc; + p->awaiting_status = ERL_DE_PROC_LOADED; + p->flags = ERL_DE_FL_DEREFERENCED; + p->next = dh->procs; + dh->procs = p; +} + +static Eterm copy_ref(Eterm ref, Eterm *hp) +{ + RefThing *ptr = ref_thing_ptr(ref); + memcpy(hp, ptr, sizeof(RefThing)); + return (make_internal_ref(hp)); +} + +static void add_proc_waiting(DE_Handle *dh, Process *proc, + Uint status, Eterm ref) +{ + DE_ProcEntry *p; + assert_drv_list_locked(); + p = erts_alloc(ERTS_ALC_T_DDLL_PROCESS, sizeof(DE_ProcEntry)); + p->proc = proc; + p->flags = 0; + p->awaiting_status = status; + copy_ref(ref, p->heap); + p->next = dh->procs; + dh->procs = p; +} + +static Eterm add_monitor(Process *p, DE_Handle *dh, Uint status) +{ + Eterm r; + + assert_drv_list_locked(); + r = erts_make_ref(p); + add_proc_waiting(dh, p, status, r); + return r; +} + + +static void set_driver_reloading(DE_Handle *dh, Process *proc, char *path, char *name, Uint flags) +{ + DE_ProcEntry *p; + + assert_drv_list_locked(); + p = erts_alloc(ERTS_ALC_T_DDLL_PROCESS, sizeof(DE_ProcEntry)); + p->proc = proc; + p->awaiting_status = ERL_DE_OK; + p->next = dh->procs; + p->flags = ERL_DE_FL_DEREFERENCED; + dh->procs = p; + dh->status = ERL_DE_RELOAD; + dh->reload_full_path = erts_alloc(ERTS_ALC_T_DDLL_HANDLE, sys_strlen(path) + 1); + strcpy(dh->reload_full_path,path); + dh->reload_driver_name = erts_alloc(ERTS_ALC_T_DDLL_HANDLE, sys_strlen(name) + 1); + strcpy(dh->reload_driver_name,name); + dh->reload_flags = flags; +} + +static int do_load_driver_entry(DE_Handle *dh, char *path, char *name) +{ + void *init_handle; + int res; + ErlDrvEntry *dp; + + assert_drv_list_locked(); + + if ((res = erts_sys_ddll_open(path, &(dh->handle))) != ERL_DE_NO_ERROR) { + return res; + } + + if ((res = erts_sys_ddll_load_driver_init(dh->handle, + &init_handle)) != ERL_DE_NO_ERROR) { + erts_sys_ddll_close(dh->handle); + return ERL_DE_LOAD_ERROR_NO_INIT; + } + + dp = erts_sys_ddll_call_init(init_handle); + if (dp == NULL) { + erts_sys_ddll_close(dh->handle); + return ERL_DE_LOAD_ERROR_FAILED_INIT; + } + + switch (dp->extended_marker) { + case 0: + /* + * This may be an old driver that has been recompiled. If so, + * at least the fields that existed in extended driver version + * 1.0 should be zero. If not, a it is a bad driver. We cannot + * be completely certain that this is a valid driver but this is + * the best we can do with old drivers... + */ + if (dp->major_version != 0 + || dp->minor_version != 0 + || dp->driver_flags != 0 + || dp->handle2 != NULL + || dp->process_exit != NULL) { + /* Old driver; needs to be recompiled... */ + return ERL_DE_LOAD_ERROR_INCORRECT_VERSION; + } + break; + case ERL_DRV_EXTENDED_MARKER: + if (ERL_DRV_EXTENDED_MAJOR_VERSION != dp->major_version + || ERL_DRV_EXTENDED_MINOR_VERSION < dp->minor_version) { + /* Incompatible driver version */ + return ERL_DE_LOAD_ERROR_INCORRECT_VERSION; + } + break; + default: + /* Old driver; needs to be recompiled... */ + return ERL_DE_LOAD_ERROR_INCORRECT_VERSION; + } + + if (strcmp(name, dp->driver_name) != 0) { + erts_sys_ddll_close(dh->handle); + return ERL_DE_LOAD_ERROR_BAD_NAME; + } + erts_smp_atomic_init(&(dh->refc), (long) 0); + dh->port_count = 0; + dh->full_path = erts_alloc(ERTS_ALC_T_DDLL_HANDLE, sys_strlen(path) + 1); + sys_strcpy(dh->full_path, path); + dh->flags = 0; + dh->status = ERL_DE_OK; + + if (erts_add_driver_entry(dp, dh, 1) != 0 /* io.c */) { + /* + * The init in the driver struct did not return 0 + */ + erts_free(ERTS_ALC_T_DDLL_HANDLE, dh->full_path); + dh->full_path = NULL; + erts_sys_ddll_close(dh->handle); + return ERL_DE_LOAD_ERROR_FAILED_INIT; + } + + return ERL_DE_NO_ERROR; +} + +static int do_unload_driver_entry(DE_Handle *dh, Eterm *save_name) +{ + erts_driver_t *q, *p = driver_list; + + assert_drv_list_locked(); + + while (p != NULL) { + if (p->handle == dh) { + + q = p; + if (p->prev == NULL) { + driver_list = p->next; + } else { + p->prev->next = p->next; + } + if (p->next != NULL) { + p->next->prev = p->prev; + } + + if (save_name != NULL) { + *save_name = mkatom(q->name); + } + /* XXX:PaN Future locking problems? Don't dare to let go of the diver_list lock here!*/ + if (q->finish) { + int fpe_was_unmasked = erts_block_fpe(); + (*(q->finish))(); + erts_unblock_fpe(fpe_was_unmasked); + } + erts_sys_ddll_close(dh->handle); + erts_destroy_driver(q); + return 1; + } + p = p->next; + } + return 0; +} + +static int load_driver_entry(DE_Handle **dhp, char *path, char *name) +{ + int res; + DE_Handle *dh = erts_alloc(ERTS_ALC_T_DDLL_HANDLE, sizeof(DE_Handle)); + + assert_drv_list_locked(); + + dh->handle = NULL; + dh->procs = NULL; + dh->port_count = 0; + erts_refc_init(&(dh->refc), (long) 0); + dh->status = -1; + dh->reload_full_path = NULL; + dh->reload_driver_name = NULL; + dh->reload_flags = 0; + dh->full_path = NULL; + dh->flags = 0; + + if ((res = do_load_driver_entry(dh, path, name)) != ERL_DE_NO_ERROR) { + erts_free(ERTS_ALC_T_DDLL_HANDLE, (void *) dh); + dh = NULL; + } + *dhp = dh; + return res; +} + +#if 0 +static void unload_driver_entry(DE_Handle *dh) +{ + do_unload_driver_entry(dh, NULL); + if (dh->full_path != NULL) { + erts_free(ERTS_ALC_T_DDLL_HANDLE, (void *) dh->full_path); + } + erts_free(ERTS_ALC_T_DDLL_HANDLE, (void *) dh); +} +#endif +static int reload_driver_entry(DE_Handle *dh) +{ + char *path = dh->reload_full_path; + char *name = dh->reload_driver_name; + int loadres; + Uint flags = dh->reload_flags; + + assert_drv_list_locked(); + + dh->reload_full_path = NULL; + dh->reload_driver_name = NULL; + + ASSERT(erts_refc_read(&(dh->refc),0) == 0); + ASSERT(dh->full_path != NULL); + erts_free(ERTS_ALC_T_DDLL_HANDLE, (void *) dh->full_path); + dh->full_path = NULL; + + loadres = do_load_driver_entry(dh, path, name); + erts_free(ERTS_ALC_T_DDLL_HANDLE, (void *) path); + erts_free(ERTS_ALC_T_DDLL_HANDLE, (void *) name); + if (loadres == ERL_DE_NO_ERROR) { + dh->status = ERL_DE_OK; + dh->flags = flags; + } + restore_process_references(dh); + return loadres; +} + +/* + * Notification {tag = atom(), ref = ref(), driver_name = atom()} or + * {'$DDLL_load_failure', ref = ref(), driver_name = atom(), + * error_term = atom() | {system_error, int()}} + */ + +static void notify_proc(Process *proc, Eterm ref, Eterm driver_name, Eterm type, + Eterm tag, int errcode) +{ + Eterm mess; + Eterm r; + Eterm *hp; + ErlHeapFragment *bp; + ErlOffHeap *ohp; + ErtsProcLocks rp_locks = 0; + ERTS_SMP_CHK_NO_PROC_LOCKS; + + assert_drv_list_locked(); + if (errcode != 0) { + int need = load_error_need(errcode); + Eterm e; + hp = erts_alloc_message_heap(6 /* tuple */ + 3 /* Error tuple */ + + REF_THING_SIZE + need, &bp, &ohp, + proc, &rp_locks); + r = copy_ref(ref,hp); + hp += REF_THING_SIZE; + e = build_load_error_hp(hp, errcode); + hp += need; + mess = TUPLE2(hp,tag,e); + hp += 3; + mess = TUPLE5(hp,type,r,am_driver,driver_name,mess); + } else { + hp = erts_alloc_message_heap(6 /* tuple */ + REF_THING_SIZE, &bp, &ohp, proc, &rp_locks); + r = copy_ref(ref,hp); + hp += REF_THING_SIZE; + mess = TUPLE5(hp,type,r,am_driver,driver_name,tag); + } + erts_queue_message(proc, &rp_locks, bp, mess, am_undefined); + erts_smp_proc_unlock(proc, rp_locks); + ERTS_SMP_CHK_NO_PROC_LOCKS; +} + +static void notify_all(DE_Handle *dh, char *name, Uint awaiting, Eterm type, Eterm tag) +{ + DE_ProcEntry **p; + + assert_drv_list_locked(); + + p = &(dh->procs); + while (*p != NULL) { + if ((*p)->awaiting_status == awaiting) { + DE_ProcEntry *pe; + pe = *p; + *p = pe->next; + notify_proc(pe->proc, make_internal_ref(&(pe->heap)), mkatom(name), type, tag, 0); + erts_free(ERTS_ALC_T_DDLL_PROCESS, (void *) pe); + } else { + p = &((*p)->next); + } + } +} + + + +typedef struct errcode_entry { + char *atm; + int code; +} ErrcodeEntry; + +static ErrcodeEntry errcode_tab[] = { + {"no_error", ERL_DE_NO_ERROR}, + {"no_driver_init", ERL_DE_LOAD_ERROR_NO_INIT}, + {"driver_init_failed", ERL_DE_LOAD_ERROR_FAILED_INIT}, + {"bad_driver_name", ERL_DE_LOAD_ERROR_BAD_NAME}, + {"driver_name_to_long", ERL_DE_LOAD_ERROR_NAME_TO_LONG}, + {"driver_incorrect_version", ERL_DE_LOAD_ERROR_INCORRECT_VERSION}, + {"no_ddll_available", ERL_DE_ERROR_NO_DDLL_FUNCTIONALITY}, + {"unspecified_error", ERL_DE_ERROR_UNSPECIFIED}, + {"symbol_not_found", ERL_DE_LOOKUP_ERROR_NOT_FOUND}, + {NULL,0} +}; + +static int errdesc_to_code(Eterm errdesc, int *code /* out */) +{ + int i; + if (is_atom(errdesc)) { + Atom *ap = atom_tab(atom_val(errdesc)); + for (i = 0; errcode_tab[i].atm != NULL; ++i) { + int len = sys_strlen(errcode_tab[i].atm); + if (len == ap->len && + !sys_strncmp(errcode_tab[i].atm,(char *) ap->name,len)) { + *code = errcode_tab[i].code; + return 0; + } + } + return -1; + } else if (is_tuple(errdesc)) { + Eterm *tp = tuple_val(errdesc); + if (*tp != make_arityval(2) || tp[1] != am_open_error || is_not_small(tp[2])) { + return -1; + } + *code = signed_val(tp[2]); + return 0; + } + return -1; +} + +static Eterm build_load_error(Process *p, int code) +{ + int need = load_error_need(code); + Eterm *hp = NULL; + ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(p)); + if (need) { + hp = HAlloc(p,need); + } + return build_load_error_hp(hp,code); +} + +static int load_error_need(int code) +{ + ErrcodeEntry *ee = errcode_tab; + while (ee->atm != NULL) { + if (ee->code == code) { + return 0; + } + ++ee; + } + return 3; +} + +static Eterm build_load_error_hp(Eterm *hp, int code) +{ + ErrcodeEntry *ee = errcode_tab; + while (ee->atm != NULL) { + if (ee->code == code) { + return mkatom(ee->atm); + } + ++ee; + } + return TUPLE2(hp,am_open_error, make_small(code)); +} + + + +static Eterm mkatom(char *str) +{ + return am_atom_put(str, sys_strlen(str)); +} + +static char *pick_list_or_atom(Eterm name_term) +{ + char *name = NULL; + int name_len; + if (is_atom(name_term)) { + Atom *ap = atom_tab(atom_val(name_term)); + if (ap->len == 0) { + /* If io_lists with zero length is not allowed, + then the empty atom shouldn't */ + goto error; + } + name = erts_alloc(ERTS_ALC_T_DDLL_TMP_BUF, ap->len + 1); + memcpy(name,ap->name,ap->len); + name[ap->len] = '\0'; + } else { + name_len = io_list_len(name_term); + if (name_len <= 0) { + goto error; + } + name = erts_alloc(ERTS_ALC_T_DDLL_TMP_BUF, name_len + 1); + if (io_list_to_buf(name_term, name, name_len) != 0) { + goto error; + } + name[name_len] = '\0'; + } + return name; + error: + if (name != NULL) { + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); + } + return NULL; +} + +static int build_proc_info(DE_Handle *dh, ProcEntryInfo **out_pei, Uint filter) +{ + ProcEntryInfo *pei = NULL; + int num_pei = 0; + int num_pei_allocated = 0; + int i; + DE_ProcEntry *pe; + + assert_drv_list_locked(); + + for (pe = dh->procs; pe != NULL; pe = pe->next) { + Eterm id = pe->proc->id; + Uint stat = pe->awaiting_status; + if (stat == ERL_DE_PROC_AWAIT_UNLOAD_ONLY) { + stat = ERL_DE_PROC_AWAIT_UNLOAD; + } + if (stat != filter) { + continue; + } + for (i = 0; i < num_pei; ++i) { + if (pei[i].pid == id && pei[i].status == stat) { + break; + } + } + if (i < num_pei) { + pei[i].count++; + } else { + if (num_pei >= num_pei_allocated) { + pei = (pei == NULL) + ? erts_alloc(ERTS_ALC_T_DDLL_TMP_BUF, + sizeof(ProcEntryInfo) * (num_pei_allocated = 10)) + : erts_realloc(ERTS_ALC_T_DDLL_TMP_BUF, pei, + sizeof(ProcEntryInfo) * (num_pei_allocated += 10)); + } + pei[num_pei].pid = id; + pei[num_pei].proc = pe->proc; + pei[num_pei].status = stat; + pei[num_pei].count = 1; + ++num_pei; + } + } + *out_pei = pei; + return num_pei; +} + + + +static erts_driver_t *lookup_driver(char *name) +{ + erts_driver_t *drv; + assert_drv_list_locked(); + for (drv = driver_list; drv != NULL && strcmp(drv->name, name); drv = drv->next) + ; + return drv; +} |