aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/beam/erl_bif_ddll.c
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/beam/erl_bif_ddll.c')
-rw-r--r--erts/emulator/beam/erl_bif_ddll.c1964
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;
+}