1 /*
2 * %CopyrightBegin%
3 *
4 * Copyright Ericsson AB 2009-2020. All Rights Reserved.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * %CopyrightEnd%
19 */
20 #include <erl_nif.h>
21 #include <string.h>
22 #include <stdio.h>
23
24 #include "nif_mod.h"
25
26 #if ERL_NIF_MAJOR_VERSION*100 + ERL_NIF_MINOR_VERSION >= 215
27 # define HAVE_ENIF_MONITOR_PROCESS
28 #endif
29 #if ERL_NIF_MAJOR_VERSION*100 + ERL_NIF_MINOR_VERSION >= 216
30 # define HAVE_ENIF_DYNAMIC_RESOURCE_CALL
31 #endif
32
33 #define CHECK(X) ((void)((X) || (check_abort(__LINE__),1)))
34 #ifdef __GNUC__
35 static void check_abort(unsigned line) __attribute__((noreturn));
36 #endif
check_abort(unsigned line)37 static void check_abort(unsigned line)
38 {
39 enif_fprintf(stderr, "Test CHECK failed at %s:%u\r\n",
40 __FILE__, line);
41 abort();
42 }
43
44 static int static_cntA; /* zero by default */
45 static int static_cntB = NIF_LIB_VER * 100;
46
47 static ERL_NIF_TERM am_true;
48 static ERL_NIF_TERM am_null;
49 static ERL_NIF_TERM am_resource_type;
50 static ERL_NIF_TERM am_resource_dtor_A;
51 static ERL_NIF_TERM am_resource_dtor_B;
52 static ERL_NIF_TERM am_resource_down_D;
53 static ERL_NIF_TERM am_resource_dyncall;
54 static ERL_NIF_TERM am_return;
55
priv_data(ErlNifEnv * env)56 static NifModPrivData* priv_data(ErlNifEnv* env)
57 {
58 return (NifModPrivData*) enif_priv_data(env);
59 }
60
init(ErlNifEnv * env)61 static void init(ErlNifEnv* env)
62 {
63 am_true = enif_make_atom(env, "true");
64 am_null = enif_make_atom(env, "null");
65 am_resource_type = enif_make_atom(env, "resource_type");
66 am_resource_dtor_A = enif_make_atom(env, "resource_dtor_A");
67 am_resource_dtor_B = enif_make_atom(env, "resource_dtor_B");
68 am_resource_down_D = enif_make_atom(env, "resource_down_D");
69 am_resource_dyncall = enif_make_atom(env, "resource_dyncall");
70 am_return = enif_make_atom(env, "return");
71 }
72
add_call_with_arg(ErlNifEnv * env,NifModPrivData * data,const char * func_name,const char * arg,int arg_sz)73 static void add_call_with_arg(ErlNifEnv* env, NifModPrivData* data, const char* func_name,
74 const char* arg, int arg_sz)
75 {
76 CallInfo* call = (CallInfo*)enif_alloc(sizeof(CallInfo)+strlen(func_name) + arg_sz);
77 strcpy(call->func_name, func_name);
78 call->lib_ver = NIF_LIB_VER;
79 call->static_cntA = ++static_cntA;
80 call->static_cntB = ++static_cntB;
81 call->arg_sz = arg_sz;
82 if (arg != NULL) {
83 call->arg = call->func_name + strlen(func_name) + 1;
84 memcpy(call->arg, arg, arg_sz);
85 }
86 else {
87 call->arg = NULL;
88 }
89 enif_mutex_lock(data->mtx);
90 call->next = data->call_history;
91 data->call_history = call;
92 enif_mutex_unlock(data->mtx);
93 }
94
add_call(ErlNifEnv * env,NifModPrivData * data,const char * func_name)95 static void add_call(ErlNifEnv* env, NifModPrivData* data,const char* func_name)
96 {
97 add_call_with_arg(env, data, func_name, NULL, 0);
98 }
99
100 #define ADD_CALL(FUNC_NAME) add_call(env, priv_data(env),FUNC_NAME)
101
102 #define STRINGIFY_(X) #X
103 #define STRINGIFY(X) STRINGIFY_(X)
104
resource_dtor_A(ErlNifEnv * env,void * a)105 static void resource_dtor_A(ErlNifEnv* env, void* a)
106 {
107 const char dtor_name[] = "resource_dtor_A_v" STRINGIFY(NIF_LIB_VER);
108
109 add_call_with_arg(env, priv_data(env), dtor_name, (const char*)a,
110 enif_sizeof_resource(a));
111 }
112
resource_dtor_B(ErlNifEnv * env,void * a)113 static void resource_dtor_B(ErlNifEnv* env, void* a)
114 {
115 const char dtor_name[] = "resource_dtor_B_v" STRINGIFY(NIF_LIB_VER);
116
117 add_call_with_arg(env, priv_data(env), dtor_name, (const char*)a,
118 enif_sizeof_resource(a));
119 }
120
121 #ifdef HAVE_ENIF_MONITOR_PROCESS
resource_down_D(ErlNifEnv * env,void * a,ErlNifPid * pid,ErlNifMonitor * mon)122 static void resource_down_D(ErlNifEnv* env, void* a, ErlNifPid* pid, ErlNifMonitor* mon)
123 {
124 const char down_name[] = "resource_down_D_v" STRINGIFY(NIF_LIB_VER);
125
126 add_call_with_arg(env, priv_data(env), down_name, (const char*)a,
127 enif_sizeof_resource(a));
128 }
129 #endif
130
131 #ifdef HAVE_ENIF_DYNAMIC_RESOURCE_CALL
resource_dyncall(ErlNifEnv * env,void * obj,void * call_data)132 static void resource_dyncall(ErlNifEnv* env, void* obj, void* call_data)
133 {
134 int* p = (int*)call_data;
135 *p += NIF_LIB_VER;
136 }
137 #endif
138
139
140
141 /* {resource_type,
142 Ix|null,
143 ErlNifResourceFlags in,
144 "TypeName",
145 dtor(A|B|null),
146 ErlNifResourceFlags out
147 [, down(D|null)]}
148 */
open_resource_type(ErlNifEnv * env,int arity,const ERL_NIF_TERM * arr)149 static void open_resource_type(ErlNifEnv* env, int arity, const ERL_NIF_TERM* arr)
150 {
151 NifModPrivData* data = priv_data(env);
152 char rt_name[30];
153 union { ErlNifResourceFlags e; int i; } flags, exp_res, got_res;
154 unsigned ix;
155 ErlNifResourceDtor* dtor;
156 ErlNifResourceType* got_ptr;
157
158 CHECK(enif_is_identical(arr[0], am_resource_type));
159 CHECK(enif_get_int(env, arr[2], &flags.i));
160 CHECK(enif_get_string(env, arr[3], rt_name, sizeof(rt_name), ERL_NIF_LATIN1) > 0);
161 CHECK(enif_get_int(env, arr[5], &exp_res.i));
162
163 if (enif_is_identical(arr[4], am_null)) {
164 dtor = NULL;
165 }
166 else if (enif_is_identical(arr[4], am_resource_dtor_A)) {
167 dtor = resource_dtor_A;
168 }
169 else {
170 CHECK(enif_is_identical(arr[4], am_resource_dtor_B));
171 dtor = resource_dtor_B;
172 }
173 #ifdef HAVE_ENIF_MONITOR_PROCESS
174 if (arity == 7) {
175 ErlNifResourceTypeInit init;
176 init.dtor = dtor;
177 init.stop = NULL;
178 init.down = NULL;
179
180 # ifdef HAVE_ENIF_DYNAMIC_RESOURCE_CALL
181 init.members = 0xdead;
182 init.dyncall = (ErlNifResourceDynCall*) 0xdeadbeaf;
183
184 if (enif_is_identical(arr[6], am_resource_dyncall)) {
185 init.dyncall = resource_dyncall;
186 init.members = 4;
187 got_ptr = enif_init_resource_type(env, rt_name, &init,
188 flags.e, &got_res.e);
189 }
190 else
191 # endif
192 {
193 if (enif_is_identical(arr[6], am_resource_down_D)) {
194 init.down = resource_down_D;
195 }
196 else {
197 CHECK(enif_is_identical(arr[6], am_null));
198 }
199 got_ptr = enif_open_resource_type_x(env, rt_name, &init,
200 flags.e, &got_res.e);
201
202 }
203 }
204 else
205 #endif
206 {
207 got_ptr = enif_open_resource_type(env, NULL, rt_name, dtor,
208 flags.e, &got_res.e);
209 }
210 if (enif_get_uint(env, arr[1], &ix) && ix < RT_MAX && got_ptr != NULL) {
211 data->rt_arr[ix] = got_ptr;
212 }
213 else {
214 CHECK(enif_is_identical(arr[1], am_null));
215 CHECK(got_ptr == NULL);
216 }
217 CHECK(got_res.e == exp_res.e);
218 }
219
do_load_info(ErlNifEnv * env,ERL_NIF_TERM load_info,int * retvalp)220 static void do_load_info(ErlNifEnv* env, ERL_NIF_TERM load_info, int* retvalp)
221 {
222 NifModPrivData* data = priv_data(env);
223 ERL_NIF_TERM head, tail;
224 unsigned ix;
225
226 for (ix=0; ix<RT_MAX; ix++) {
227 data->rt_arr[ix] = NULL;
228 }
229 for (head = load_info; enif_get_list_cell(env, head, &head, &tail);
230 head = tail) {
231 const ERL_NIF_TERM* arr;
232 int arity;
233
234 CHECK(enif_get_tuple(env, head, &arity, &arr));
235 switch (arity) {
236 case 6:
237 case 7:
238 open_resource_type(env, arity, arr);
239 break;
240 case 2:
241 CHECK(arr[0] == am_return);
242 CHECK(enif_get_int(env, arr[1], retvalp));
243 break;
244 default:
245 CHECK(0);
246 }
247 }
248 CHECK(enif_is_empty_list(env, head));
249 }
250
251 #if NIF_LIB_VER != 3
load(ErlNifEnv * env,void ** priv,ERL_NIF_TERM load_info)252 static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
253 {
254 NifModPrivData* data;
255 int retval = 0;
256
257 init(env);
258 data = (NifModPrivData*) enif_alloc(sizeof(NifModPrivData));
259 CHECK(data != NULL);
260 *priv = data;
261 data->mtx = enif_mutex_create("nif_mod_priv_data");
262 data->ref_cnt = 1;
263 data->call_history = NULL;
264
265 add_call(env, data, "load");
266
267 do_load_info(env, load_info, &retval);
268 if (retval)
269 NifModPrivData_release(data);
270 return retval;
271 }
272
reload(ErlNifEnv * env,void ** priv,ERL_NIF_TERM load_info)273 static int reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
274 {
275 NifModPrivData* data = (NifModPrivData*) *priv;
276 int retval = 0;
277 init(env);
278 add_call(env, data, "reload");
279
280 do_load_info(env, load_info, &retval);
281 return retval;
282 }
283
upgrade(ErlNifEnv * env,void ** priv,void ** old_priv_data,ERL_NIF_TERM load_info)284 static int upgrade(ErlNifEnv* env, void** priv, void** old_priv_data, ERL_NIF_TERM load_info)
285 {
286 NifModPrivData* data = (NifModPrivData*) *old_priv_data;
287 int retval = 0;
288 init(env);
289 add_call(env, data, "upgrade");
290 data->ref_cnt++;
291
292 *priv = *old_priv_data;
293 do_load_info(env, load_info, &retval);
294 if (retval)
295 NifModPrivData_release(data);
296 return retval;
297 }
298
unload(ErlNifEnv * env,void * priv)299 static void unload(ErlNifEnv* env, void* priv)
300 {
301 NifModPrivData* data = (NifModPrivData*) priv;
302
303 add_call(env, data, "unload");
304 NifModPrivData_release(data);
305 }
306 #endif /* NIF_LIB_VER != 3 */
307
lib_version(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])308 static ERL_NIF_TERM lib_version(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
309 {
310 ADD_CALL("lib_version");
311 return enif_make_int(env, NIF_LIB_VER);
312 }
313
nif_api_version(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])314 static ERL_NIF_TERM nif_api_version(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
315 {
316 /*ADD_CALL("nif_api_version");*/
317 return enif_make_tuple2(env,
318 enif_make_int(env, ERL_NIF_MAJOR_VERSION),
319 enif_make_int(env, ERL_NIF_MINOR_VERSION));
320 }
321
get_priv_data_ptr(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])322 static ERL_NIF_TERM get_priv_data_ptr(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
323 {
324 NifModPrivData** bin_data;
325 ERL_NIF_TERM res;
326 ADD_CALL("get_priv_data_ptr");
327 bin_data = (NifModPrivData**)enif_make_new_binary(env, sizeof(void*), &res);
328 *bin_data = priv_data(env);
329 return res;
330 }
331
make_new_resource(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])332 static ERL_NIF_TERM make_new_resource(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
333 {
334 NifModPrivData* data = priv_data(env);
335 ErlNifBinary ibin;
336 char* a;
337 ERL_NIF_TERM ret;
338 unsigned ix;
339 if (!enif_get_uint(env, argv[0], &ix) || ix >= RT_MAX
340 || !enif_inspect_binary(env, argv[1], &ibin)) {
341 return enif_make_badarg(env);
342 }
343 a = (char*) enif_alloc_resource(data->rt_arr[ix], ibin.size);
344 memcpy(a, ibin.data, ibin.size);
345 ret = enif_make_resource(env, a);
346 enif_release_resource(a);
347 return ret;
348 }
349
get_resource(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])350 static ERL_NIF_TERM get_resource(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
351 {
352 NifModPrivData* data = priv_data(env);
353 ErlNifBinary obin;
354 unsigned ix;
355 void* a;
356 if (!enif_get_uint(env, argv[0], &ix) || ix >= RT_MAX
357 || !enif_get_resource(env, argv[1], data->rt_arr[ix], &a)
358 || !enif_alloc_binary(enif_sizeof_resource(a), &obin)) {
359 return enif_make_badarg(env);
360 }
361 memcpy(obin.data, a, obin.size);
362 return enif_make_binary(env, &obin);
363 }
364
365 #ifdef HAVE_ENIF_MONITOR_PROCESS
monitor_process(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])366 static ERL_NIF_TERM monitor_process(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
367 {
368 NifModPrivData* data = priv_data(env);
369 ErlNifPid pid;
370 unsigned ix;
371 void* obj;
372 int ret;
373
374 if (!enif_get_uint(env, argv[0], &ix) || ix >= RT_MAX
375 || !enif_get_resource(env, argv[1], data->rt_arr[ix], &obj)
376 || !enif_get_local_pid(env, argv[2], &pid)) {
377 return enif_make_badarg(env);
378 }
379 ret = enif_monitor_process(env, obj, &pid, NULL);
380 return enif_make_int(env, ret);
381 }
382 #endif
383
384 static ErlNifFunc nif_funcs[] =
385 {
386 {"lib_version", 0, lib_version},
387 {"nif_api_version", 0, nif_api_version},
388 {"get_priv_data_ptr", 0, get_priv_data_ptr},
389 {"make_new_resource", 2, make_new_resource},
390 {"get_resource", 2, get_resource},
391 #ifdef HAVE_ENIF_MONITOR_PROCESS
392 {"monitor_process", 3, monitor_process},
393 #endif
394 /* Keep lib_version_check last to maximize the loading "patch distance"
395 between it and lib_version */
396 {"lib_version_check", 0, lib_version}
397 };
398
399 #if NIF_LIB_VER != 3
400 ERL_NIF_INIT(nif_mod,nif_funcs,load,reload,upgrade,unload)
401 #else
402 ERL_NIF_INIT_GLOB /* avoid link error on windows */
403 #endif
404
405