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