Update copyright dates
[src/app-framework-binder.git] / src / afb-api-so.c
1 /*
2  * Copyright (C) 2015-2020 "IoT.bzh"
3  * Author José Bollo <jose.bollo@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #if WITH_DYNAMIC_BINDING
19
20 #define _GNU_SOURCE
21
22 #include <stdio.h>
23 #include <dlfcn.h>
24 #include <string.h>
25 #include <dirent.h>
26 #include <errno.h>
27 #include <sys/stat.h>
28
29 #include "afb-api-so.h"
30 #include "afb-api-so-v3.h"
31 #include "verbose.h"
32 #include "sig-monitor.h"
33
34 #if WITH_LEGACY_BINDING_V1
35 #   include "afb-api-so-v1.h"
36 #endif
37 #if WITH_LEGACY_BINDING_VDYN
38 #   include "afb-api-so-vdyn.h"
39 #endif
40 #if WITH_LEGACY_BINDING_V2
41 #   include "afb-api-so-v2.h"
42 #endif
43
44 struct safe_dlopen
45 {
46         const char *path;
47         void *handle;
48         int flags;
49 };
50
51 static void safe_dlopen_cb(int sig, void *closure)
52 {
53         struct safe_dlopen *sd = closure;
54         if (!sig)
55                 sd->handle = dlopen(sd->path, sd->flags);
56         else {
57                 ERROR("dlopen of %s raised signal %s", sd->path, strsignal(sig));
58                 sd->handle = NULL;
59         }
60 }
61
62 static void *safe_dlopen(const char *filename, int flags)
63 {
64         struct safe_dlopen sd;
65         sd.path = filename;
66         sd.flags = flags;
67         sd.handle = NULL;
68         sig_monitor(0, safe_dlopen_cb, &sd);
69         return sd.handle;
70 }
71
72 static int load_binding(const char *path, int force, struct afb_apiset *declare_set, struct afb_apiset * call_set)
73 {
74         int obsolete = 0;
75         int rc;
76         void *handle;
77
78         // This is a loadable library let's check if it's a binding
79         rc = -!!force;
80         handle = safe_dlopen(path, RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND);
81         if (handle == NULL) {
82                 if (force)
83                         ERROR("binding [%s] not loadable: %s", path, dlerror());
84                 else
85                         WARNING("binding [%s] not loadable: %s", path, dlerror());
86                 goto error;
87         }
88
89         /* try the version 3 */
90         rc = afb_api_so_v3_add(path, handle, declare_set, call_set);
91         if (rc < 0) {
92                 /* error when loading a valid v3 binding */
93                 goto error2;
94         }
95         if (rc)
96                 return 0; /* yes version 3 */
97
98 #if WITH_LEGACY_BINDING_V2
99         /* try the version 2 */
100         rc = afb_api_so_v2_add(path, handle, declare_set, call_set);
101         if (rc < 0) {
102                 /* error when loading a valid v2 binding */
103                 goto error2;
104         }
105         if (rc)
106                 return 0; /* yes version 2 */
107 #else
108         if (dlsym(handle, "afbBindingV2")) {
109                 WARNING("binding [%s]: version 2 not supported", path);
110                 obsolete = 1;
111         }
112 #endif
113
114 #if WITH_LEGACY_BINDING_VDYN
115         /* try the version dyn */
116         rc = afb_api_so_vdyn_add(path, handle, declare_set, call_set);
117         if (rc < 0) {
118                 /* error when loading a valid dyn binding */
119                 goto error2;
120         }
121         if (rc)
122                 return 0; /* yes version dyn */
123 #else
124         if (dlsym(handle, "afbBindingVdyn")) {
125                 WARNING("binding [%s]: version DYN not supported", path);
126                 obsolete = 1;
127         }
128 #endif
129
130 #if WITH_LEGACY_BINDING_V1
131         /* try the version 1 */
132         rc = afb_api_so_v1_add(path, handle, declare_set, call_set);
133         if (rc < 0) {
134                 /* error when loading a valid v1 binding */
135                 goto error2;
136         }
137         if (rc)
138                 return 0; /* yes version 1 */
139 #else
140         if (dlsym(handle, "afbBindingV1Register")) {
141                 WARNING("binding [%s]: version 1 not supported", path);
142                 obsolete = 1;
143         }
144 #endif
145
146         /* not a valid binding */
147         _VERBOSE_(force ? Log_Level_Error : Log_Level_Info, "binding [%s] %s",
148                         path, obsolete ? "is obsolete" : "isn't an AFB binding");
149
150 error2:
151         dlclose(handle);
152 error:
153         return rc;
154 }
155
156
157 int afb_api_so_add_binding(const char *path, struct afb_apiset *declare_set, struct afb_apiset * call_set)
158 {
159         return load_binding(path, 1, declare_set, call_set);
160 }
161
162 static int adddirs(char path[PATH_MAX], size_t end, struct afb_apiset *declare_set, struct afb_apiset * call_set, int failstops)
163 {
164         DIR *dir;
165         struct dirent *dent;
166         struct stat st;
167         size_t len;
168         int rc = 0;
169
170         /* open the DIR now */
171         dir = opendir(path);
172         if (dir == NULL) {
173                 ERROR("can't scan binding directory %s, %m", path);
174                 return -1;
175         }
176         INFO("Scanning dir=[%s] for bindings", path);
177
178         /* scan each entry */
179         if (end)
180                 path[end++] = '/';
181         for (;;) {
182                 errno = 0;
183                 dent = readdir(dir);
184                 if (dent == NULL) {
185                         if (errno != 0)
186                                 ERROR("read error while scanning directory %.*s: %m", (int)(end - 1), path);
187                         break;
188                 }
189
190                 /* get the name and inspect dereferenced link instead of the directory entry */
191                 len = strlen(dent->d_name);
192                 if (len + end >= PATH_MAX) {
193                         ERROR("path too long while scanning bindings for %.*s%s", (int)end, path, dent->d_name);
194                         continue;
195                 }
196                 memcpy(&path[end], dent->d_name, len+1);
197                 rc = stat(path, &st);
198                 if (rc < 0) {
199                         ERROR("getting status of %s failed: %m", path);
200                         continue;
201                 }
202                 else if (S_ISDIR(st.st_mode)) {
203                         /* case of directories */
204                         if (dent->d_name[0] == '.') {
205 /*
206 Exclude from the search of bindings any
207 directory starting with a dot (.) by default.
208
209 It is possible to reactivate the prvious behaviour
210 by defining the following preprocessor variables
211
212  - AFB_API_SO_ACCEPT_DOT_PREFIXED_DIRS
213
214    When this variable is defined, the directories
215    starting with a dot are searched except
216    if their name is "." or ".." or ".debug"
217
218  - AFB_API_SO_ACCEPT_DOT_DEBUG_DIRS
219
220    When this variable is defined and the variable
221    AFB_API_SO_ACCEPT_DOT_PREFIXED_DIRS is also defined
222    scans any directory not being "." or "..".
223
224 The previous behaviour was like difining the 2 variables,
225 meaning that only . and .. were excluded from the search.
226
227 This change is intended to definitely solve the issue
228 SPEC-662. Yocto installed the debugging symbols in the
229 subdirectory .debug. For example the binding.so also
230 had a .debug/binding.so file attached. Opening that
231 debug file made dlopen crashing.
232 See https://sourceware.org/bugzilla/show_bug.cgi?id=22101
233  */
234 #if !defined(AFB_API_SO_ACCEPT_DOT_PREFIXED_DIRS) /* not defined by default */
235                                 continue; /* ignore any directory beginning with a dot */
236 #else
237                                 if (len == 1)
238                                         continue; /* . */
239                                 if (dent->d_name[1] == '.' && len == 2)
240                                         continue; /* .. */
241 #if !defined(AFB_API_SO_ACCEPT_DOT_DEBUG_DIRS) /* not defined by default */
242                                 if (len == 6
243                                  && dent->d_name[1] == 'd'
244                                  && dent->d_name[2] == 'e'
245                                  && dent->d_name[3] == 'b'
246                                  && dent->d_name[4] == 'u'
247                                  && dent->d_name[5] == 'g')
248                                         continue; /* .debug */
249 #endif
250 #endif
251                         }
252                         rc = adddirs(path, end+len, declare_set, call_set, failstops);
253                 } else if (S_ISREG(st.st_mode)) {
254                         /* case of files */
255                         if (memcmp(&dent->d_name[len - 3], ".so", 4))
256                                 continue;
257                         rc = load_binding(path, 0, declare_set, call_set);
258                 }
259                 if (rc < 0 && failstops) {
260                         closedir(dir);
261                         return rc;
262                 }
263         }
264         closedir(dir);
265         return 0;
266 }
267
268 int afb_api_so_add_directory(const char *path, struct afb_apiset *declare_set, struct afb_apiset * call_set, int failstops)
269 {
270         size_t length;
271         char buffer[PATH_MAX];
272
273         length = strlen(path);
274         if (length >= sizeof(buffer)) {
275                 ERROR("path too long %lu [%.99s...]", (unsigned long)length, path);
276                 return -1;
277         }
278
279         memcpy(buffer, path, length + 1);
280         return adddirs(buffer, length, declare_set, call_set, failstops);
281 }
282
283 int afb_api_so_add_path(const char *path, struct afb_apiset *declare_set, struct afb_apiset * call_set, int failstops)
284 {
285         struct stat st;
286         int rc;
287
288         rc = stat(path, &st);
289         if (rc < 0)
290                 ERROR("Invalid binding path [%s]: %m", path);
291         else if (S_ISDIR(st.st_mode))
292                 rc = afb_api_so_add_directory(path, declare_set, call_set, failstops);
293         else if (strstr(path, ".so"))
294                 rc = load_binding(path, 0, declare_set, call_set);
295         else
296                 INFO("not a binding [%s], skipped", path);
297         return rc;
298 }
299
300 int afb_api_so_add_pathset(const char *pathset, struct afb_apiset *declare_set, struct afb_apiset * call_set, int failstops)
301 {
302         static char sep[] = ":";
303         char *ps, *p;
304         int rc;
305
306         ps = strdupa(pathset);
307         for (;;) {
308                 p = strsep(&ps, sep);
309                 if (!p)
310                         return 0;
311                 rc = afb_api_so_add_path(p, declare_set, call_set, failstops);
312                 if (rc < 0)
313                         return rc;
314         }
315 }
316
317 int afb_api_so_add_pathset_fails(const char *pathset, struct afb_apiset *declare_set, struct afb_apiset * call_set)
318 {
319         return afb_api_so_add_pathset(pathset, declare_set, call_set, 1);
320 }
321
322 int afb_api_so_add_pathset_nofails(const char *pathset, struct afb_apiset *declare_set, struct afb_apiset * call_set)
323 {
324         return afb_api_so_add_pathset(pathset, declare_set, call_set, 0);
325 }
326
327 #endif