1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2011-2020 Couchbase, Inc.
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 LCB_IOPS_V12_NO_DEPRECATE 1 /* For Ruby */
19 
20 #include "internal.h"
21 #include "plugins/io/select/select_io_opts.h"
22 #include <libcouchbase/plugins/io/bsdio-inl.c>
23 
24 #ifdef LCB_EMBED_PLUGIN_LIBEVENT
25 LIBCOUCHBASE_API lcb_STATUS lcb_create_libevent_io_opts(int, lcb_io_opt_t *, void *);
26 #endif
27 
28 typedef lcb_STATUS (*create_func_t)(int version, lcb_io_opt_t *io, void *cookie);
29 
30 #ifdef _WIN32
31 LIBCOUCHBASE_API
32 lcb_STATUS lcb_iocp_new_iops(int, lcb_io_opt_t *, void *);
33 #define DEFAULT_IOPS LCB_IO_OPS_WINIOCP
34 #else
35 #define DEFAULT_IOPS LCB_IO_OPS_LIBEVENT
36 #endif
37 
38 typedef struct {
39     /** The "base" name of the plugin */
40     const char *base;
41 
42     /** Corresponding type */
43     lcb_io_ops_type_t iotype;
44 
45     /** Filename */
46     const char *soname;
47 
48     /** Symbol used to initialize the plugin */
49     const char *symbol;
50 
51     /** Function to create the iops (if builtin) */
52     create_func_t create;
53 
54     /** Static buffers if reading from the environment */
55     char s_soname[PATH_MAX];
56     char s_symbol[256];
57 } plugin_info;
58 
59 #ifdef __APPLE__
60 #define PLUGIN_SO(NAME) "libcouchbase_" NAME ".dylib"
61 #elif defined(_WIN32)
62 /** Trailing period intentional. See docs for LoadLibrary */
63 #if (_DEBUG && _MSC_VER)
64 #define PLUGIN_SO(NAME) "libcouchbase_" NAME "_d.dll."
65 #else
66 #define PLUGIN_SO(NAME) "libcouchbase_" NAME ".dll."
67 #endif /* _DEBUG */
68 #else
69 #define PLUGIN_SO(NAME) "libcouchbase_" NAME ".so"
70 #endif
71 
72 #define PLUGIN_SYMBOL(NAME) "lcb_create_" NAME "_io_opts"
73 
74 #define BUILTIN_CORE(name, type, create)                                                                               \
75     {                                                                                                                  \
76         name, type, NULL, NULL, create, {0},                                                                           \
77         {                                                                                                              \
78             0                                                                                                          \
79         }                                                                                                              \
80     }
81 
82 #define BUILTIN_DL(name, type)                                                                                         \
83     {                                                                                                                  \
84         name, type, PLUGIN_SO(name), PLUGIN_SYMBOL(name), NULL, {0},                                                   \
85         {                                                                                                              \
86             0                                                                                                          \
87         }                                                                                                              \
88     }
89 
90 static plugin_info builtin_plugins[] = {BUILTIN_CORE("select", LCB_IO_OPS_SELECT, lcb_create_select_io_opts),
91                                         BUILTIN_CORE("winsock", LCB_IO_OPS_WINSOCK, lcb_create_select_io_opts),
92 
93 #ifdef _WIN32
94                                         BUILTIN_CORE("iocp", LCB_IO_OPS_WINIOCP, lcb_iocp_new_iops),
95 #endif
96 
97 #ifdef LCB_EMBED_PLUGIN_LIBEVENT
98                                         BUILTIN_CORE("libevent", LCB_IO_OPS_LIBEVENT, lcb_create_libevent_io_opts),
99 #else
100                                         BUILTIN_DL("libevent", LCB_IO_OPS_LIBEVENT),
101 #endif
102 
103                                         BUILTIN_DL("libev", LCB_IO_OPS_LIBEV),
104                                         BUILTIN_DL("libuv", LCB_IO_OPS_LIBUV),
105 
106                                         {NULL, LCB_IO_OPS_INVALID, NULL, NULL, NULL, {0}, {0}}};
107 
108 /**
109  * Checks the environment for plugin information.
110  * Returns:
111  *   1  information found and valid
112  *   0  not found
113  *   -1 error
114  */
get_env_plugin_info(plugin_info *info)115 static int get_env_plugin_info(plugin_info *info)
116 {
117 
118     plugin_info *cur = NULL;
119     memset(info, 0, sizeof(*info));
120 
121     if (!lcb_getenv_nonempty_multi(info->s_soname, sizeof(info->s_soname), "LIBCOUCHBASE_EVENT_PLUGIN_NAME",
122                                    "LCB_IOPS_NAME", NULL)) {
123         return 0;
124     }
125 
126     for (cur = builtin_plugins; cur->base; cur++) {
127         if (strlen(cur->base) != strlen(info->s_soname)) {
128             continue;
129         }
130 
131         if (strcmp(cur->base, info->s_soname) == 0) {
132             memcpy(info, cur, sizeof(*cur));
133             return 1;
134         }
135     }
136 
137     if (!lcb_getenv_nonempty_multi(info->s_symbol, sizeof(info->s_symbol), "LIBCOUCHBASE_EVENT_PLUGIN_SYMBOL",
138                                    "LCB_IOPS_SYMBOL", NULL)) {
139         return -1;
140     }
141 
142     info->soname = info->s_soname;
143     info->symbol = info->s_symbol;
144     return 1;
145 }
146 
find_plugin_info(lcb_io_ops_type_t iotype)147 static plugin_info *find_plugin_info(lcb_io_ops_type_t iotype)
148 {
149     plugin_info *cur;
150 
151     if (iotype == LCB_IO_OPS_DEFAULT) {
152         iotype = DEFAULT_IOPS;
153     }
154 
155     for (cur = builtin_plugins; cur->base; cur++) {
156         if (cur->iotype == iotype) {
157             return cur;
158         }
159     }
160     return NULL;
161 }
162 
options_from_info(struct lcb_create_io_ops_st *opts, const plugin_info *info)163 static void options_from_info(struct lcb_create_io_ops_st *opts, const plugin_info *info)
164 {
165     void *cookie;
166 
167     switch (opts->version) {
168         case 0:
169             cookie = opts->v.v0.cookie;
170             break;
171         case 1:
172             cookie = opts->v.v1.cookie;
173             break;
174         case 2:
175             cookie = opts->v.v2.cookie;
176             break;
177         default:
178             lcb_assert("unknown options version" && 0);
179             cookie = NULL;
180     }
181 
182     if (info->create) {
183         opts->version = 2;
184         opts->v.v2.create = info->create;
185         opts->v.v2.cookie = cookie;
186         return;
187     }
188 
189     opts->version = 1;
190     opts->v.v1.sofile = info->soname;
191     opts->v.v1.symbol = info->symbol;
192     opts->v.v1.cookie = cookie;
193 }
194 
195 static lcb_STATUS create_v2(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *options);
196 
197 struct plugin_st {
198     void *dlhandle;
199     union {
200         create_func_t create;
201         void *voidptr;
202     } func;
203 };
204 
205 #ifndef _WIN32
get_create_func(const char *image, const char *symbol, struct plugin_st *plugin, int do_warn)206 static lcb_STATUS get_create_func(const char *image, const char *symbol, struct plugin_st *plugin, int do_warn)
207 {
208     void *dlhandle = dlopen(image, RTLD_NOW | RTLD_LOCAL);
209     if (dlhandle == NULL) {
210         if (do_warn) {
211             fprintf(stderr, "[libcouchbase] dlopen of %s failed with '%s'\n", image, dlerror());
212         }
213         return LCB_ERR_DLOPEN_FAILED;
214     }
215 
216     memset(plugin, 0, sizeof(*plugin));
217     plugin->func.create = NULL;
218     plugin->func.voidptr = dlsym(dlhandle, symbol);
219 
220     if (plugin->func.voidptr == NULL) {
221         if (do_warn) {
222             fprintf(stderr, "[libcouchbase] dlsym (%s) -> (%s) failed: %s\n", image, symbol, dlerror());
223         }
224         dlclose(dlhandle);
225         dlhandle = NULL;
226         return LCB_ERR_DLSYM_FAILED;
227 
228     } else {
229         plugin->dlhandle = dlhandle;
230     }
231     return LCB_SUCCESS;
232 }
233 
close_dlhandle(void *handle)234 static void close_dlhandle(void *handle)
235 {
236     dlclose(handle);
237 }
238 #else
get_create_func(const char *image, const char *symbol, struct plugin_st *plugin, int do_warn)239 static lcb_STATUS get_create_func(const char *image, const char *symbol, struct plugin_st *plugin, int do_warn)
240 {
241     HMODULE hLibrary = LoadLibrary(image);
242     FARPROC hFunction;
243 
244     memset(plugin, 0, sizeof(*plugin));
245 
246     if (!hLibrary) {
247         if (do_warn) {
248             fprintf(stderr, "LoadLibrary of %s failed with code %d\n", image, (int)GetLastError());
249         }
250         return LCB_ERR_DLOPEN_FAILED;
251     }
252 
253     hFunction = GetProcAddress(hLibrary, symbol);
254     if (!hFunction) {
255         if (do_warn) {
256             fprintf(stderr, "GetProcAddress (%s) -> (%s) failed with code %d\n", image, symbol, (int)GetLastError());
257         }
258         FreeLibrary(hLibrary);
259         return LCB_ERR_DLSYM_FAILED;
260     }
261 
262     plugin->func.create = (create_func_t)hFunction;
263     plugin->dlhandle = hLibrary;
264     return LCB_SUCCESS;
265 }
266 
close_dlhandle(void *handle)267 static void close_dlhandle(void *handle)
268 {
269     FreeLibrary((HMODULE)handle);
270 }
271 #endif
272 
273 static int want_dl_debug = 0; /* global variable */
274 static lcb_STATUS create_v1(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *options);
275 
276 LIBCOUCHBASE_API
lcb_destroy_io_ops(lcb_io_opt_t io)277 lcb_STATUS lcb_destroy_io_ops(lcb_io_opt_t io)
278 {
279     if (io) {
280         void *dlhandle = io->dlhandle;
281         if (io->destructor) {
282             io->destructor(io);
283         }
284         if (dlhandle) {
285             close_dlhandle(dlhandle);
286         }
287     }
288 
289     return LCB_SUCCESS;
290 }
291 
292 /**
293  * Note, the 'pi' is just a context variable to ensure the pointers copied
294  * to the options are valid. It is *not* meant to be inspected.
295  */
generate_options(plugin_info *pi, const struct lcb_create_io_ops_st *user, struct lcb_create_io_ops_st *ours, lcb_io_ops_type_t *type)296 static lcb_STATUS generate_options(plugin_info *pi, const struct lcb_create_io_ops_st *user,
297                                    struct lcb_create_io_ops_st *ours, lcb_io_ops_type_t *type)
298 {
299     if (user) {
300         memcpy(ours, user, sizeof(*user));
301 
302     } else {
303         memset(ours, 0, sizeof(*ours));
304         ours->version = 0;
305         ours->v.v0.type = LCB_IO_OPS_DEFAULT;
306     }
307 
308     if (ours->version > 0) {
309         if (type) {
310             *type = LCB_IO_OPS_INVALID;
311         }
312         /* we don't handle non-v0 options */
313         return LCB_SUCCESS;
314     }
315 
316     if (ours->v.v0.type == LCB_IO_OPS_DEFAULT) {
317         int rv;
318         memset(pi, 0, sizeof(*pi));
319 
320         rv = get_env_plugin_info(pi);
321         if (rv > 0) {
322             options_from_info(ours, pi);
323 
324             if (type) {
325                 *type = pi->iotype;
326             }
327 
328         } else if (rv < 0) {
329             return LCB_ERR_BAD_ENVIRONMENT;
330 
331         } else {
332             plugin_info *pip = find_plugin_info(LCB_IO_OPS_DEFAULT);
333             lcb_assert(pip);
334 
335             if (type) {
336                 *type = pip->iotype;
337             }
338 
339             options_from_info(ours, pip);
340 
341             /* if the plugin is dynamically loadable, we need to
342              * fallback to select(2) plugin in case we cannot find the
343              * create function */
344             if (ours->version == 1) {
345                 struct plugin_st plugin;
346                 int want_debug;
347                 lcb_STATUS ret;
348 
349                 if (lcb_getenv_boolean_multi("LIBCOUCHBASE_DLOPEN_DEBUG", "LCB_DLOPEN_DEBUG", NULL)) {
350                     want_debug = 1;
351                 } else {
352                     want_debug = want_dl_debug;
353                 }
354                 ret = get_create_func(ours->v.v1.sofile, ours->v.v1.symbol, &plugin, want_debug);
355                 if (ret != LCB_SUCCESS) {
356                     char path[PATH_MAX];
357                     /* try to look up the so-file in the libdir */
358                     snprintf(path, PATH_MAX, "%s/%s", LCB_LIBDIR, ours->v.v1.sofile);
359                     ret = get_create_func(path, ours->v.v1.symbol, &plugin, want_debug);
360                 }
361                 if (ret != LCB_SUCCESS) {
362                     if (type) {
363                         *type = LCB_IO_OPS_SELECT;
364                     }
365                     ours->version = 2;
366                     ours->v.v2.create = lcb_create_select_io_opts;
367                     ours->v.v2.cookie = NULL;
368                 }
369             }
370         }
371         return LCB_SUCCESS;
372 
373     } else {
374         /** Not default, ignore environment */
375         plugin_info *pip = find_plugin_info(ours->v.v0.type);
376         if (!pip) {
377             return LCB_ERR_UNSUPPORTED_OPERATION;
378         }
379         options_from_info(ours, pip);
380         if (type) {
381             *type = pip->iotype;
382         }
383         return LCB_SUCCESS;
384     }
385 }
386 
387 LIBCOUCHBASE_API
lcb_create_io_ops(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *io_opts)388 lcb_STATUS lcb_create_io_ops(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *io_opts)
389 {
390 
391     struct lcb_create_io_ops_st options;
392     lcb_STATUS err;
393     plugin_info pi;
394     memset(&options, 0, sizeof(options));
395 
396     err = lcb_initialize_socket_subsystem();
397     if (err != LCB_SUCCESS) {
398         return err;
399     }
400 
401     err = generate_options(&pi, io_opts, &options, NULL);
402     if (err != LCB_SUCCESS) {
403         return err;
404     }
405 
406     if (options.version == 1) {
407         err = create_v1(io, &options);
408     } else if (options.version == 2) {
409         err = create_v2(io, &options);
410     } else {
411         return LCB_ERR_UNSUPPORTED_OPERATION;
412     }
413 
414     if (err != LCB_SUCCESS) {
415         return err;
416     }
417     /*XXX:
418      * This block of code here because the Ruby SDK relies on undocumented
419      * functionality of older versions of libcouchbase in which its send/recv
420      * functions assert that the number of IOV elements passed is always going
421      * to be 2.
422      *
423      * This works around the issue by patching the send/recv functions of
424      * the ruby implementation at load-time.
425      *
426      * This block of code will go away once the Ruby SDK is fixed and a released
427      * version has been out for enough time that it won't break common existing
428      * deployments.
429      */
430     if (io_opts && io_opts->version == 1 && io_opts->v.v1.symbol != NULL) {
431         if (strstr(io_opts->v.v1.symbol, "cb_create_ruby")) {
432             wire_lcb_bsd_impl(*io);
433         }
434     }
435     return LCB_SUCCESS;
436 }
437 
create_v1(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *options)438 static lcb_STATUS create_v1(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *options)
439 {
440     struct plugin_st plugin;
441     int want_debug;
442     lcb_STATUS ret;
443 
444     if (lcb_getenv_boolean_multi("LIBCOUCHBASE_DLOPEN_DEBUG", "LCB_DLOPEN_DEBUG", NULL)) {
445         want_debug = 1;
446     } else {
447         want_debug = want_dl_debug;
448     }
449     ret = get_create_func(options->v.v1.sofile, options->v.v1.symbol, &plugin, want_debug);
450     if (ret != LCB_SUCCESS) {
451         /* try to look up the symbol in the current image */
452         lcb_STATUS ret2 = get_create_func(NULL, options->v.v1.symbol, &plugin, want_debug);
453         if (ret2 != LCB_SUCCESS) {
454 #ifndef _WIN32
455             char path[PATH_MAX];
456             /* try to look up the so-file in the libdir */
457             snprintf(path, PATH_MAX, "%s/%s", LCB_LIBDIR, options->v.v1.sofile);
458             ret2 = get_create_func(path, options->v.v1.symbol, &plugin, want_debug);
459 #endif
460             if (ret2 != LCB_SUCCESS) {
461                 /* return original error to allow caller to fix it */
462                 return ret;
463             }
464         }
465     }
466 
467     ret = plugin.func.create(0, io, options->v.v1.cookie);
468     if (ret != LCB_SUCCESS) {
469         if (options->v.v1.sofile != NULL) {
470             close_dlhandle(plugin.dlhandle);
471         }
472         return LCB_ERR_NO_MEMORY;
473     } else {
474         lcb_io_opt_t iop = *io;
475         iop->dlhandle = plugin.dlhandle;
476         /* check if plugin selected compatible version */
477         if (iop->version < 0 || iop->version > 3) {
478             lcb_destroy_io_ops(iop);
479             return LCB_ERR_PLUGIN_VERSION_MISMATCH;
480         }
481     }
482 
483     return LCB_SUCCESS;
484 }
485 
create_v2(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *options)486 static lcb_STATUS create_v2(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *options)
487 {
488     lcb_STATUS ret;
489 
490     ret = options->v.v2.create(0, io, options->v.v2.cookie);
491     if (ret != LCB_SUCCESS) {
492         return ret;
493     } else {
494         lcb_io_opt_t iop = *io;
495         /* check if plugin selected compatible version */
496         if (iop->version < 0 || iop->version > 3) {
497             lcb_destroy_io_ops(iop);
498             return LCB_ERR_PLUGIN_VERSION_MISMATCH;
499         }
500     }
501 
502     return LCB_SUCCESS;
503 }
504 
lcb_iops_cntl_handler(int mode, lcb_INSTANCE *instance, int cmd, void *arg)505 lcb_STATUS lcb_iops_cntl_handler(int mode, lcb_INSTANCE *instance, int cmd, void *arg)
506 {
507     (void)instance;
508 
509     switch (cmd) {
510         case LCB_CNTL_IOPS_DEFAULT_TYPES: {
511             struct lcb_create_io_ops_st options;
512             struct lcb_cntl_iops_info_st *info = arg;
513             plugin_info pi;
514 
515             memset(&options, 0, sizeof(options));
516             if (mode != LCB_CNTL_GET) {
517                 return LCB_ERR_UNSUPPORTED_OPERATION;
518             }
519 
520             if (info->version != 0) {
521                 return LCB_ERR_INVALID_ARGUMENT;
522             }
523 
524             info->v.v0.os_default = DEFAULT_IOPS;
525 
526             return generate_options(&pi, info->v.v0.options, &options, &info->v.v0.effective);
527         }
528 
529         case LCB_CNTL_IOPS_DLOPEN_DEBUG: {
530             int *usr = arg;
531             if (mode == LCB_CNTL_SET) {
532                 want_dl_debug = *usr;
533             } else {
534                 *usr = want_dl_debug;
535             }
536             return LCB_SUCCESS;
537         }
538 
539         default:
540             return LCB_ERR_INVALID_ARGUMENT;
541     }
542 }
543 
544 /* In-library wrapper version */
545 LIBCOUCHBASE_API
lcb_iops_wire_bsd_impl2(lcb_bsd_procs *procs, int version)546 void lcb_iops_wire_bsd_impl2(lcb_bsd_procs *procs, int version)
547 {
548     wire_lcb_bsd_impl2(procs, version);
549 }
550