/* * %CopyrightBegin% * * Copyright Ericsson AB 2006-2018. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions 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" #include "erl_bif_unique.h" #include "dtrace-wrapper.h" #include "lttng-wrapper.h" /* * 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_rwmtx_rwlock(&erts_driver_list_lock) #define unlock_drv_list() erts_rwmtx_rwunlock(&erts_driver_list_lock) #define assert_drv_list_locked() \ ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(&erts_driver_list_lock) \ || erts_lc_rwmtx_is_rlocked(&erts_driver_list_lock)) #define assert_drv_list_rwlocked() \ ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(&erts_driver_list_lock)) #define assert_drv_list_rlocked() \ ERTS_LC_ASSERT(erts_lc_rwmtx_is_rlocked(&erts_driver_list_lock)) #define assert_drv_list_not_locked() \ ERTS_LC_ASSERT(!erts_lc_rwmtx_is_rwlocked(&erts_driver_list_lock) \ && !erts_lc_rwmtx_is_rlocked(&erts_driver_list_lock)) #define FREE_PORT_FLAGS (ERTS_PORT_SFLGS_DEAD & (~ERTS_PORT_SFLG_INITIALIZING)) static void kill_ports_driver_unloaded(DE_Handle *dh) { int ix, max = erts_ptab_max(&erts_port); for (ix = 0; ix < max; ix++) { erts_aint32_t state; Port* prt = erts_pix2port(ix); if (!prt) continue; ERTS_THR_DATA_DEPENDENCY_READ_MEMORY_BARRIER; state = erts_atomic32_read_nob(&prt->state); if (state & FREE_PORT_FLAGS) continue; erts_port_lock(prt); state = erts_atomic32_read_nob(&prt->state); if (!(state & ERTS_PORT_SFLGS_DEAD) && prt->drv_ptr->handle == dh) driver_failure_atom(ERTS_Port2ErlDrvPort(prt), "driver_unloaded"); erts_port_release(prt); } } /* * 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(BIF_ALIST_3) { Eterm path_term = BIF_ARG_1; Eterm name_term = BIF_ARG_2; Eterm options = BIF_ARG_3; char *path = NULL; Sint 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; int encoding; 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; } encoding = erts_get_native_filename_encoding(); if (encoding == ERL_FILENAME_WIN_WCHAR) { /* Do not convert the lib name to utf-16le yet, do that in win32 specific code */ /* since lib_name is used in error messages */ encoding = ERL_FILENAME_UTF8; } path = erts_convert_filename_to_encoding(path_term, NULL, 0, ERTS_ALC_T_DDLL_TMP_BUF, 1, 0, encoding, &path_len, sys_strlen(name) + 2); /* might need path separator */ if (!path) { goto error; } ASSERT(path_len > 0 && path[path_len-1] == 0); while (--path_len > 0 && (path[path_len-1] == '\\' || path[path_len-1] == '/')) ; path[path_len++] = '/'; sys_strcpy(path+path_len,name); erts_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); lock_drv_list(); 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, BIF_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, BIF_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, BIF_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, BIF_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, BIF_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... */ /* Don't reference, will happen after reload */ add_proc_loaded_deref(dh, BIF_P); ++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, BIF_P); first_ddll_reference(dh); monitor = 0; ok_term = mkatom("loaded"); } } assert_drv_list_rwlocked(); if (kill_ports) { /* Avoid closing the driver by referencing it */ erts_ddll_reference_driver(dh); ASSERT(dh->status == ERL_DE_RELOAD); dh->status = ERL_DE_FORCE_RELOAD; unlock_drv_list(); kill_ports_driver_unloaded(dh); /* Dereference, eventually causing driver destruction */ lock_drv_list(); erts_ddll_dereference_driver(dh); } erts_ddll_reference_driver(dh); unlock_drv_list(); erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); lock_drv_list(); erts_ddll_dereference_driver(dh); BIF_P->flags |= F_USING_DDLL; if (monitor) { Eterm mref = add_monitor(BIF_P, dh, ERL_DE_PROC_AWAIT_LOAD); hp = HAlloc(BIF_P, 4); t = TUPLE3(hp, am_ok, ok_term, mref); } else { hp = HAlloc(BIF_P, 3); t = TUPLE2(hp, am_ok, ok_term); } unlock_drv_list(); erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) path); erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); ERTS_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(BIF_P)); BIF_RET(t); soft_error: unlock_drv_list(); erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); if (do_build_load_error) { soft_error_term = build_load_error(BIF_P, build_this_load_error); } hp = HAlloc(BIF_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_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(BIF_P)); BIF_RET(t); error: assert_drv_list_not_locked(); ERTS_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(BIF_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(BIF_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(BIF_ALIST_2) { Eterm name_term = BIF_ARG_1; Eterm options = BIF_ARG_2; 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_proc_unlock(BIF_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; } lock_drv_list(); 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, BIF_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 (erts_atomic32_read_nob(&dh->port_count) > 0) { ++kill_ports; } dh->status = ERL_DE_UNLOAD; ok_term = am_pending_driver; done: assert_drv_list_rwlocked(); if (kill_ports > 1) { /* Avoid closing the driver by referencing it */ erts_ddll_reference_driver(dh); dh->status = ERL_DE_FORCE_UNLOAD; unlock_drv_list(); kill_ports_driver_unloaded(dh); lock_drv_list(); erts_ddll_dereference_driver(dh); } erts_ddll_reference_driver(dh); unlock_drv_list(); erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); lock_drv_list(); erts_ddll_dereference_driver(dh); erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); BIF_P->flags |= F_USING_DDLL; if (monitor > 0) { Eterm mref = add_monitor(BIF_P, dh, ERL_DE_PROC_AWAIT_UNLOAD); hp = HAlloc(BIF_P, 4); t = TUPLE3(hp, am_ok, ok_term, mref); } else { hp = HAlloc(BIF_P, 3); t = TUPLE2(hp, am_ok, ok_term); } if (kill_ports > 1) { ERTS_BIF_CHK_EXITED(BIF_P); /* May be exited by port killing */ } unlock_drv_list(); BIF_RET(t); soft_error: unlock_drv_list(); erts_free(ERTS_ALC_T_DDLL_TMP_BUF, (void *) name); erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); hp = HAlloc(BIF_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_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); BIF_ERROR(BIF_P, BADARG); } /* * A shadow of the "real" demonitor BIF */ BIF_RETTYPE erl_ddll_demonitor_1(BIF_ALIST_1) { if (is_not_internal_ref(BIF_ARG_1)) { BIF_ERROR(BIF_P, BADARG); } if (BIF_P->flags & F_USING_DDLL) { erts_ddll_remove_monitor(BIF_P, BIF_ARG_1, ERTS_PROC_LOCK_MAIN); } BIF_RET(am_true); } /* * A shadow of the "real" monitor BIF */ BIF_RETTYPE erl_ddll_monitor_2(BIF_ALIST_2) { if (BIF_ARG_1 != am_driver) { BIF_ERROR(BIF_P, BADARG); } return erts_ddll_monitor_driver(BIF_P, BIF_ARG_2, ERTS_PROC_LOCK_MAIN); } /* * Return list of loaded drivers {ok,[string()]} */ BIF_RETTYPE erl_ddll_loaded_drivers_0(BIF_ALIST_0) { Eterm *hp; int need = 3; Eterm res = NIL; erts_driver_t *drv; lock_drv_list(); for (drv = driver_list; drv; drv = drv->next) { need += sys_strlen(drv->name)*2+2; } hp = HAlloc(BIF_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 */ unlock_drv_list(); BIF_RET(res); } /* * More detailed info about loaded drivers: * item is processes, driver_options, port_count, linked_in_driver, * permanent, awaiting_load, awaiting_unload */ BIF_RETTYPE erl_ddll_info_2(BIF_ALIST_2) { Process *p = BIF_P; Eterm name_term = BIF_ARG_1; Eterm item = BIF_ARG_2; char *name = NULL; Eterm res = NIL; erts_driver_t *drv; ProcEntryInfo *pei = NULL; int num_pei; Eterm *hp; int i; Uint filter; int have_lock = 0; if ((name = pick_list_or_atom(name_term)) == NULL) { goto error; } if (!is_atom(item)) { goto error; } lock_drv_list(); have_lock = 1; 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(erts_atomic32_read_nob(&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: unlock_drv_list(); 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 (have_lock) { unlock_drv_list(); } 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 */ BIF_RETTYPE erl_ddll_format_error_int_1(BIF_ALIST_1) { Process *p = BIF_P; Eterm code_term = BIF_ARG_1; 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 cannot 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; } lock_drv_list(); errstring = erts_ddll_error(errint); unlock_drv_list(); 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_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_proc_lock(p, plocks); } /* * Called from erl_process.c. */ void erts_ddll_proc_dead(Process *p, ErtsProcLocks plocks) { erts_driver_t *drv; erts_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 && erts_atomic32_read_nob(&drv->handle->port_count) > 0) { if (kill_ports) { DE_Handle *dh = drv->handle; erts_ddll_reference_driver(dh); dh->status = ERL_DE_FORCE_UNLOAD; unlock_drv_list(); kill_ports_driver_unloaded(dh); lock_drv_list(); /* Needed for future list operations */ 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_proc_lock(p, plocks); } void erts_ddll_lock_driver(DE_Handle *dh, char *name) { DE_ProcEntry *p,*q; assert_drv_list_rwlocked(); 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(); erts_atomic32_inc_nob(&dh->port_count); } void erts_ddll_decrement_port_count(DE_Handle *dh) { assert_drv_list_locked(); #ifdef DEBUG ASSERT(erts_atomic32_dec_read_nob(&dh->port_count) >= 0); #else erts_atomic32_dec_nob(&dh->port_count); #endif } static void first_ddll_reference(DE_Handle *dh) { assert_drv_list_rwlocked(); 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_rwlocked(); 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_rwlocked(); 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; erts_aint_t 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_LC_ASSERT(ERTS_PROC_LOCK_MAIN & plocks); lock_drv_list(); 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: erts_exit(ERTS_ERROR_EXIT,"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); unlock_drv_list(); BIF_RET(r); immediate: r = erts_make_ref(p); erts_proc_unlock(p, plocks); notify_proc(p, r, name_term, immediate_type, immediate_tag, 0); unlock_drv_list(); erts_proc_lock(p, plocks); 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_LC_ASSERT(ERTS_PROC_LOCK_MAIN & plocks); lock_drv_list(); 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); unlock_drv_list(); BIF_RET(r); immediate: r = erts_make_ref(p); erts_proc_unlock(p, plocks); notify_proc(p, r, name_term, immediate_type, immediate_tag, 0); unlock_drv_list(); erts_proc_lock(p, plocks); BIF_RET(r); } static int is_last_user(DE_Handle *dh, Process *proc) { DE_ProcEntry *p = dh->procs; int found = 0; assert_drv_list_rwlocked(); 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_rwlocked(); 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_rwlocked(); 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_rwlocked(); 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_rwlocked(); 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_rwlocked(); 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) { ErtsORefThing *ptr; ASSERT(is_internal_ordinary_ref(ref)); ptr = ordinary_ref_thing_ptr(ref); sys_memcpy(hp, ptr, sizeof(ErtsORefThing)); 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_rwlocked(); 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_rwlocked(); 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_rwlocked(); 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); sys_strcpy(dh->reload_full_path,path); dh->reload_driver_name = erts_alloc(ERTS_ALC_T_DDLL_HANDLE, sys_strlen(name) + 1); sys_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_rwlocked(); if ((res = erts_sys_ddll_open(path, &(dh->handle), NULL)) != ERL_DE_NO_ERROR) { return res; } if ((res = erts_sys_ddll_load_driver_init(dh->handle, &init_handle)) != ERL_DE_NO_ERROR) { res = ERL_DE_LOAD_ERROR_NO_INIT; goto error; } dp = erts_sys_ddll_call_init(init_handle); if (dp == NULL) { res = ERL_DE_LOAD_ERROR_FAILED_INIT; goto error; } switch (dp->extended_marker) { case ERL_DRV_EXTENDED_MARKER: if (dp->major_version < ERL_DRV_MIN_REQUIRED_MAJOR_VERSION_ON_LOAD || (ERL_DRV_EXTENDED_MAJOR_VERSION < dp->major_version || (ERL_DRV_EXTENDED_MAJOR_VERSION == dp->major_version && ERL_DRV_EXTENDED_MINOR_VERSION < dp->minor_version))) { /* Incompatible driver version */ res = ERL_DE_LOAD_ERROR_INCORRECT_VERSION; goto error; } break; default: /* Old driver; needs to be recompiled... */ res = ERL_DE_LOAD_ERROR_INCORRECT_VERSION; goto error; } if (sys_strcmp(name, dp->driver_name) != 0) { res = ERL_DE_LOAD_ERROR_BAD_NAME; goto error; } erts_atomic_init_nob(&(dh->refc), (erts_aint_t) 0); erts_atomic32_init_nob(&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, 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; res = ERL_DE_LOAD_ERROR_FAILED_INIT; goto error; } return ERL_DE_NO_ERROR; error: erts_sys_ddll_close(dh->handle); return res; } static int do_unload_driver_entry(DE_Handle *dh, Eterm *save_name) { erts_driver_t *q, *p = driver_list; assert_drv_list_rwlocked(); 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); } /* 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(); DTRACE1(driver_finish, q->name); LTTNG1(driver_finish, q->name); (*(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_rwlocked(); dh->handle = NULL; dh->procs = NULL; erts_atomic32_init_nob(&dh->port_count, 0); erts_refc_init(&(dh->refc), (erts_aint_t) 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_rwlocked(); 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; ErtsMessage *mp; ErtsProcLocks rp_locks = 0; ErlOffHeap *ohp; ERTS_CHK_NO_PROC_LOCKS; assert_drv_list_rwlocked(); if (errcode != 0) { int need = load_error_need(errcode); Eterm e; mp = erts_alloc_message_heap(proc, &rp_locks, (6 /* tuple */ + 3 /* Error tuple */ + ERTS_REF_THING_SIZE + need), &hp, &ohp); r = copy_ref(ref,hp); hp += ERTS_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 { mp = erts_alloc_message_heap(proc, &rp_locks, 6 /* tuple */ + ERTS_REF_THING_SIZE, &hp, &ohp); r = copy_ref(ref,hp); hp += ERTS_REF_THING_SIZE; mess = TUPLE5(hp,type,r,am_driver,driver_name,tag); } erts_queue_message(proc, rp_locks, mp, mess, am_system); erts_proc_unlock(proc, rp_locks); ERTS_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_rwlocked(); 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_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 erts_atom_put((byte *) str, sys_strlen(str), ERTS_ATOM_ENC_LATIN1, 1); } static char *pick_list_or_atom(Eterm name_term) { char *name = NULL; ErlDrvSizeT 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); sys_memcpy(name,ap->name,ap->len); name[ap->len] = '\0'; } else { if (erts_iolist_size(name_term, &name_len)) { goto error; } name = erts_alloc(ERTS_ALC_T_DDLL_TMP_BUF, name_len + 1); if (erts_iolist_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_rwlocked(); for (pe = dh->procs; pe != NULL; pe = pe->next) { Eterm id = pe->proc->common.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 && sys_strcmp(drv->name, name); drv = drv->next) ; return drv; }