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