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