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