4444#endif
4545
4646#include "erl_nif.h"
47-
4847#include "sys.h"
4948#include "global.h"
5049#include "erl_binary.h"
6968#endif
7069#include "jit/beam_asm.h"
7170#include "erl_global_literals.h"
71+
7272#include "erl_iolist.h"
73+ #include "erl_atom_table.h"
7374
7475#include <limits.h>
7576#include <stddef.h> /* offsetof */
7677
78+ #undef SHMOPEN_USE_LINUX_MEMFD
79+ #undef SHMOPEN_USE_SHM_ANON
80+ #undef SHMOPEN_USE_SHM_MKSTEMP
81+ #undef SHMOPEN_USE_POSIX
82+ #undef USE_ERTS_DLOPEN_MEM
83+
84+ #ifdef HAVE_DLFCN_H
85+ #include <dlfcn.h>
86+ #endif
87+ #ifdef HAVE_FCNTL_H
88+ #include <fcntl.h>
89+ #endif
90+ #ifdef HAVE_SYS_MMAN_H
91+ #include <sys/mman.h>
92+ #endif
93+ #ifdef HAVE_SYS_STAT_H
94+ #include <sys/stat.h>
95+ #endif
96+ #ifdef HAVE_SYS_TIME_H
97+ #include <sys/time.h>
98+ #endif
99+ #ifdef HAVE_UNISTD_H
100+ #include <unistd.h>
101+ #endif
102+ #if defined(__linux__ )
103+ # if defined(__linux__ )
104+ # include <sys/syscall.h>
105+ # endif
106+ # include <sys/utsname.h>
107+ # ifdef SYS_memfd_create
108+ # define SHMOPEN_USE_LINUX_MEMFD
109+ # else
110+ # define SHMOPEN_USE_SHM_POSIX
111+ # endif
112+ # define USE_ERTS_DLOPEN_MEM
113+ #elif defined(__FreeBSD__ )
114+ # define SHMOPEN_USE_SHM_ANON
115+ # define USE_ERTS_DLOPEN_MEM
116+ #elif defined(__OpenBSD__ )
117+ # define SHMOPEN_USE_SHM_MKSTEMP
118+ # define USE_ERTS_DLOPEN_MEM
119+ #else
120+ # if defined(__APPLE__ ) || defined(__MACH__ ) || defined(__DARWIN__ ) || \
121+ defined(__NetBSD__ ) || defined(__DragonFly__ ) || defined(__HAIKU__ ) || \
122+ defined(__sun )
123+ # define SHMOPEN_USE_POSIX
124+ # define USE_ERTS_DLOPEN_MEM
125+ # endif
126+ #endif
127+
128+ #define ERTS_NIF_MEM_NAME_MAX 64
129+ #define ERTS_MIN_KERNEL_VSN 317
130+
131+ #ifdef USE_ERTS_DLOPEN_MEM
132+ /* Always declare erts_dlopen_mem for all platforms */
133+ static void * erts_dlopen_mem (const char * filename , const void * mem , size_t size );
134+
135+ static char * erts_shm_name (const char * pfx , const char * sfx , char * buf , size_t buf_len )
136+ {
137+ /* Generate a unique name for the shared memory object. */
138+ struct timeval tv ;
139+ gettimeofday (& tv , NULL );
140+ enif_snprintf (buf , buf_len , "%s-%-06ld%s" , pfx , (long )tv .tv_usec , sfx );
141+ return buf ;
142+ }
143+
144+ /*
145+ * Load a shared object from memory using memfd_create (Linux >= 3.17) or
146+ * shm_open (other POSIX systems). Returns a dlopen(3) handle on success,
147+ * or NULL on failure.
148+ *
149+ * filename - a label associated with the in-memory file (may be NULL)
150+ * mem - pointer to the SO image in memory
151+ * size - byte length of the SO image
152+ */
153+ static void * erts_dlopen_mem (const char * filename , const void * mem , size_t size )
154+ {
155+ char path [PATH_MAX ];
156+ char shm_name [NAME_MAX ];
157+ int shm_fd = -1 ;
158+ void * handle = NULL ;
159+ #if defined(SHMOPEN_USE_SHM_MKSTEMP ) || defined(SHMOPEN_USE_POSIX )
160+ int need_unlink = 0 ;
161+ #endif
162+
163+ path [0 ] = '\0' ;
164+ shm_name [0 ] = '\0' ;
165+
166+ #if defined(SHMOPEN_USE_LINUX_MEMFD )
167+ if (!filename )
168+ filename = erts_shm_name ("erl-nif-mem-" , ".so" , shm_name , sizeof (shm_name ));
169+ shm_fd = memfd_create (filename , MFD_CLOEXEC );
170+ if (shm_fd < 0 )
171+ return NULL ;
172+ enif_snprintf (path , sizeof (path ), "/proc/self/fd/%d" , shm_fd );
173+
174+ #elif defined(SHMOPEN_USE_SHM_ANON )
175+ if (filename )
176+ return NULL ;
177+ shm_fd = shm_open (SHM_ANON , O_RDWR , 0 );
178+ if (shm_fd < 0 )
179+ return NULL ;
180+ # if defined(__APPLE__ )
181+ if (fcntl (shm_fd , F_GETPATH , path ) < 0 )
182+ goto err ;
183+ # else
184+ enif_snprintf (path , sizeof (path ), "/dev/fd/%d" , shm_fd );
185+ # endif
186+
187+ #elif defined(SHMOPEN_USE_SHM_MKSTEMP )
188+ enif_snprintf (path , sizeof (path ), "/tmp/%s-XXXXXX" , filename ? filename : "enif-shm" );
189+ shm_fd = shm_mkstemp (path );
190+ if (shm_fd < 0 )
191+ return NULL ;
192+ need_unlink = 1 ;
193+
194+ #else /* SHMOPEN_USE_POSIX */
195+ if (filename ) {
196+ enif_snprintf (shm_name , sizeof (shm_name ), "%s" , filename );
197+ } else {
198+ erts_shm_name ("enif-shm-" , ".so" , shm_name , sizeof (shm_name ));
199+ }
200+ enif_snprintf (path , sizeof (path ), "/dev/shm/%s" , shm_name );
201+ shm_fd = shm_open (path , O_RDWR | O_CREAT | O_EXCL , 0600 );
202+ if (shm_fd < 0 )
203+ return NULL ;
204+ need_unlink = 1 ;
205+ #endif
206+
207+ if (ftruncate (shm_fd , size ) || write (shm_fd , mem , size ) != (ssize_t )size )
208+ goto err ;
209+
210+ handle = dlopen (path , RTLD_NOW | RTLD_LOCAL );
211+
212+ err :
213+ # if defined(SHMOPEN_USE_SHM_MKSTEMP )
214+ if (need_unlink )
215+ unlink (path );
216+ # elif defined(SHMOPEN_USE_POSIX )
217+ if (need_unlink )
218+ shm_unlink (path );
219+ # endif
220+ if (shm_fd >= 0 )
221+ close (shm_fd );
222+ return handle ;
223+ }
224+ #endif /* USE_ERTS_DLOPEN_MEM */
225+
77226#define ERTS_NIF_HALT_INFO_FLAG_BLOCK (1 << 0)
78227#define ERTS_NIF_HALT_INFO_FLAG_HALTING (1 << 1)
79228#define ERTS_NIF_HALT_INFO_FLAG_WAITING (1 << 2)
@@ -4721,6 +4870,8 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
47214870 Eterm ret = am_ok ;
47224871 int veto ;
47234872 int is_static = 0 ;
4873+ int load_from_mem = 0 ; /* non-zero when loading from in-memory binary */
4874+ Eterm nif_binary = THE_NON_VALUE ;
47244875 struct erl_module_nif * lib = NULL ;
47254876 struct erl_module_instance * this_mi ;
47264877 struct erl_module_instance * prev_mi ;
@@ -4731,13 +4882,45 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
47314882 /* since lib_name is used in error messages */
47324883 encoding = ERL_FILENAME_UTF8 ;
47334884 }
4885+
4886+ /*
4887+ * Accept either:
4888+ * Filename::string()|binary() -- load from file (existing behaviour)
4889+ * #{filename := string()|binary()} -- load from file (map form, no memory)
4890+ * #{memory := NifSO} -- load from memory, auto-generated filename
4891+ * #{memory := NifSO, filename := Filename} -- load from memory with explicit filename
4892+ *
4893+ * If the map contains only 'filename', it falls back to the string|binary filename implementation.
4894+ */
4895+ if (is_map (filename )) {
4896+ const Eterm * fnm_val = erts_maps_get (am_filename , filename );
4897+ const Eterm * mem_val = erts_maps_get (am_memory , filename );
4898+ if (fnm_val == NULL && mem_val == NULL ) {
4899+ return load_nif_error (c_p , "bad_lib" ,
4900+ "load_nif/2: map argument must contain a 'memory' or 'filename' key" );
4901+ }
4902+ if (mem_val ) {
4903+ if (!is_bitstring (* mem_val ) || TAIL_BITS (bitstring_size (* mem_val )) != 0 ) {
4904+ return load_nif_error (c_p , "bad_lib" ,
4905+ "load_nif/2: 'memory' value must be a list or binary" );
4906+ }
4907+ nif_binary = * mem_val ;
4908+ load_from_mem = 1 ;
4909+ }
4910+ if (fnm_val ) {
4911+ filename = * fnm_val ; /* fall through to filename decoding from argument */
4912+ } else {
4913+ lib_name = NULL ; /* anonymous: erts_dlopen_mem will generate a name */
4914+ goto after_lib_name ;
4915+ }
4916+ }
47344917 lib_name = erts_convert_filename_to_encoding (filename , NULL , 0 ,
47354918 ERTS_ALC_T_TMP , 1 , 0 , encoding ,
4736- NULL , 0 );
4737- if (!lib_name ) {
4919+ NULL , 0 );
4920+ if (!lib_name )
47384921 return THE_NON_VALUE ;
4739- }
47404922
4923+ after_lib_name :
47414924 /* Find calling module */
47424925 caller = erts_find_function_from_pc (I );
47434926 ASSERT (caller != NULL );
@@ -4756,24 +4939,65 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
47564939 this_mi = & module_p -> curr ;
47574940 prev_mi = & module_p -> old ;
47584941 if (in_area (caller , module_p -> old .code_hdr , module_p -> old .code_length )) {
4759- ret = load_nif_error (c_p , "old_code" , "Calling load_nif from old "
4760- "module '%T' not allowed" , mod_atom );
4761- goto error ;
4942+ ret = load_nif_error (c_p , "old_code" , "Calling load_nif from old "
4943+ "module '%T' not allowed" , mod_atom );
4944+ goto error ;
47624945 } else if (module_p -> on_load ) {
4763- ASSERT (((module_p -> on_load )-> code_hdr )-> on_load );
4764- if (module_p -> curr .code_hdr ) {
4765- prev_mi = & module_p -> curr ;
4766- } else {
4767- prev_mi = & module_p -> old ;
4768- }
4769- this_mi = module_p -> on_load ;
4946+ ASSERT (((module_p -> on_load )-> code_hdr )-> on_load );
4947+ if (module_p -> curr .code_hdr ) {
4948+ prev_mi = & module_p -> curr ;
4949+ } else {
4950+ prev_mi = & module_p -> old ;
4951+ }
4952+ this_mi = module_p -> on_load ;
4953+ }
4954+
4955+ /* If the caller passed #{memory => NifSO::binary(), filename => Filename},
4956+ * open the shared object from memory now so that `handle` is ready for
4957+ * the common else-if chain below (which runs
4958+ * erts_sys_ddll_load_nif_init / call_nif_init, version checks, and
4959+ * create_lib for both file and memory loads). */
4960+ if (!is_static && load_from_mem ) {
4961+ #if defined(USE_ERTS_DLOPEN_MEM )
4962+ const byte * bin_bytes = NULL ;
4963+ Uint bin_size = 0 ;
4964+ const byte * tmp_alloc = NULL ;
4965+ static const int max_path_len = PATH_MAX - 9 ; /* space for "/dev/shm/" */
4966+ if (lib_name != NULL ) {
4967+ int lib_name_len = sys_strlen (lib_name );
4968+ if (lib_name_len > max_path_len - 1 ) { /* -1 for null terminator added */
4969+ ret = load_nif_error (c_p , "load_failed" ,
4970+ "NIF library path too long: %d (max %d bytes)" ,
4971+ lib_name_len , max_path_len );
4972+ goto error ;
4973+ }
4974+ }
4975+ bin_bytes = erts_get_aligned_binary_bytes (nif_binary , & bin_size , & tmp_alloc );
4976+ if (!bin_bytes ) {
4977+ ret = load_nif_error (c_p , "load_failed" ,
4978+ "Failed to access NIF binary data" );
4979+ goto error ;
4980+ }
4981+ handle = erts_dlopen_mem (lib_name , bin_bytes , (size_t )bin_size );
4982+ erts_free_aligned_binary_bytes (tmp_alloc );
4983+ if (!handle ) {
4984+ ret = load_nif_error (c_p , "load_failed" ,
4985+ "Failed to load NIF library from memory: '%s'" ,
4986+ dlerror ());
4987+ goto error ;
4988+ }
4989+ #else
4990+ ret = load_nif_error (c_p , "load_failed" ,
4991+ "Loading NIF from memory is not supported on this platform" );
4992+ goto error ;
4993+ #endif
47704994 }
47714995
47724996 if (this_mi -> nif != NULL ) {
47734997 ret = load_nif_error (c_p ,"reload" ,"NIF library already loaded"
47744998 " (reload disallowed since OTP 20)." );
47754999 }
4776- else if (!is_static &&
5000+ else if (!is_static && ! load_from_mem &&
47775001 (err = erts_sys_ddll_open (lib_name , & handle , & errdesc )) != ERL_DE_NO_ERROR ) {
47785002 const char slogan [] = "Failed to load NIF library" ;
47795003 if (strstr (errdesc .str , lib_name ) != NULL ) {
@@ -4785,14 +5009,14 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
47855009 }
47865010 else if (!is_static &&
47875011 erts_sys_ddll_load_nif_init (handle , & init_func , & errdesc ) != ERL_DE_NO_ERROR ) {
4788- ret = load_nif_error (c_p , bad_lib , "Failed to find library init"
5012+ ret = load_nif_error (c_p , bad_lib , "Failed to find library init"
47895013 " function: '%s'" , errdesc .str );
47905014
47915015 }
47925016 else if (!is_static &&
47935017 (erts_add_taint (mod_atom ),
47945018 (entry = erts_sys_ddll_call_nif_init (init_func )) == NULL )) {
4795- ret = load_nif_error (c_p , bad_lib , "Library init-call unsuccessful" );
5019+ ret = load_nif_error (c_p , bad_lib , "Library init-call unsuccessful" );
47965020 }
47975021 else if (entry -> major > ERL_NIF_MAJOR_VERSION
47985022 || (entry -> major == ERL_NIF_MAJOR_VERSION
@@ -4811,12 +5035,12 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
48115035 }
48125036 else if (AT_LEAST_VERSION (entry , 2 , 1 )
48135037 && sys_strcmp (entry -> vm_variant , ERL_NIF_VM_VARIANT ) != 0 ) {
4814- ret = load_nif_error (c_p , bad_lib , "Library (%s) not compiled for "
5038+ ret = load_nif_error (c_p , bad_lib , "Library (%s) not compiled for "
48155039 "this vm variant (%s)." ,
48165040 entry -> vm_variant , ERL_NIF_VM_VARIANT );
48175041 }
48185042 else if (!erts_is_atom_str ((char * )entry -> name , mod_atom , 1 )) {
4819- ret = load_nif_error (c_p , bad_lib , "Library module name '%s' does not"
5043+ ret = load_nif_error (c_p , bad_lib , "Library module name '%s' does not"
48205044 " match calling module '%T'" , entry -> name , mod_atom );
48215045 }
48225046 else {
@@ -4941,7 +5165,7 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
49415165 }
49425166
49435167 if (ret != am_ok ) {
4944- goto error ;
5168+ goto error ;
49455169 }
49465170 ASSERT (lib );
49475171 ASSERT (lib -> finish -> nstubs_hashed == lib -> entry .num_of_funcs );
@@ -5016,22 +5240,23 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
50165240 }
50175241 else {
50185242 error :
5019- rollback_opened_resource_types ();
5020- ASSERT (ret != am_ok );
5021- if (lib != NULL ) {
5022- if (lib -> finish != NULL ) {
5023- erase_hashed_stubs (lib -> finish );
5024- erts_free (ERTS_ALC_T_NIF , lib -> finish );
5025- }
5026- erts_free (ERTS_ALC_T_NIF , lib );
5027- }
5028- if (handle != NULL ) {
5029- erts_sys_ddll_close (handle );
5030- }
5031- erts_sys_ddll_free_error (& errdesc );
5243+ rollback_opened_resource_types ();
5244+ ASSERT (ret != am_ok );
5245+ if (lib != NULL ) {
5246+ if (lib -> finish != NULL ) {
5247+ erase_hashed_stubs (lib -> finish );
5248+ erts_free (ERTS_ALC_T_NIF , lib -> finish );
5249+ }
5250+ erts_free (ERTS_ALC_T_NIF , lib );
5251+ }
5252+ if (handle != NULL ) {
5253+ erts_sys_ddll_close (handle );
5254+ }
5255+ erts_sys_ddll_free_error (& errdesc );
50325256 }
50335257
5034- erts_free (ERTS_ALC_T_TMP , lib_name );
5258+ if (lib_name )
5259+ erts_free (ERTS_ALC_T_TMP , lib_name );
50355260
50365261 BIF_RET (ret );
50375262}
0 commit comments