/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* * Copyright 2011-2020 Couchbase, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LCB_IOPS_V12_NO_DEPRECATE 1 /* For Ruby */ #include "internal.h" #include "plugins/io/select/select_io_opts.h" #include #ifdef LCB_EMBED_PLUGIN_LIBEVENT LIBCOUCHBASE_API lcb_STATUS lcb_create_libevent_io_opts(int, lcb_io_opt_t *, void *); #endif typedef lcb_STATUS (*create_func_t)(int version, lcb_io_opt_t *io, void *cookie); #ifdef _WIN32 LIBCOUCHBASE_API lcb_STATUS lcb_iocp_new_iops(int, lcb_io_opt_t *, void *); #define DEFAULT_IOPS LCB_IO_OPS_WINIOCP #else #define DEFAULT_IOPS LCB_IO_OPS_LIBEVENT #endif typedef struct { /** The "base" name of the plugin */ const char *base; /** Corresponding type */ lcb_io_ops_type_t iotype; /** Filename */ const char *soname; /** Symbol used to initialize the plugin */ const char *symbol; /** Function to create the iops (if builtin) */ create_func_t create; /** Static buffers if reading from the environment */ char s_soname[PATH_MAX]; char s_symbol[256]; } plugin_info; #ifdef __APPLE__ #define PLUGIN_SO(NAME) "libcouchbase_" NAME ".dylib" #elif defined(_WIN32) /** Trailing period intentional. See docs for LoadLibrary */ #if (_DEBUG && _MSC_VER) #define PLUGIN_SO(NAME) "libcouchbase_" NAME "_d.dll." #else #define PLUGIN_SO(NAME) "libcouchbase_" NAME ".dll." #endif /* _DEBUG */ #else #define PLUGIN_SO(NAME) "libcouchbase_" NAME ".so" #endif #define PLUGIN_SYMBOL(NAME) "lcb_create_" NAME "_io_opts" #define BUILTIN_CORE(name, type, create) \ { \ name, type, NULL, NULL, create, {0}, \ { \ 0 \ } \ } #define BUILTIN_DL(name, type) \ { \ name, type, PLUGIN_SO(name), PLUGIN_SYMBOL(name), NULL, {0}, \ { \ 0 \ } \ } static plugin_info builtin_plugins[] = {BUILTIN_CORE("select", LCB_IO_OPS_SELECT, lcb_create_select_io_opts), BUILTIN_CORE("winsock", LCB_IO_OPS_WINSOCK, lcb_create_select_io_opts), #ifdef _WIN32 BUILTIN_CORE("iocp", LCB_IO_OPS_WINIOCP, lcb_iocp_new_iops), #endif #ifdef LCB_EMBED_PLUGIN_LIBEVENT BUILTIN_CORE("libevent", LCB_IO_OPS_LIBEVENT, lcb_create_libevent_io_opts), #else BUILTIN_DL("libevent", LCB_IO_OPS_LIBEVENT), #endif BUILTIN_DL("libev", LCB_IO_OPS_LIBEV), BUILTIN_DL("libuv", LCB_IO_OPS_LIBUV), {NULL, LCB_IO_OPS_INVALID, NULL, NULL, NULL, {0}, {0}}}; /** * Checks the environment for plugin information. * Returns: * 1 information found and valid * 0 not found * -1 error */ static int get_env_plugin_info(plugin_info *info) { plugin_info *cur = NULL; memset(info, 0, sizeof(*info)); if (!lcb_getenv_nonempty_multi(info->s_soname, sizeof(info->s_soname), "LIBCOUCHBASE_EVENT_PLUGIN_NAME", "LCB_IOPS_NAME", NULL)) { return 0; } for (cur = builtin_plugins; cur->base; cur++) { if (strlen(cur->base) != strlen(info->s_soname)) { continue; } if (strcmp(cur->base, info->s_soname) == 0) { memcpy(info, cur, sizeof(*cur)); return 1; } } if (!lcb_getenv_nonempty_multi(info->s_symbol, sizeof(info->s_symbol), "LIBCOUCHBASE_EVENT_PLUGIN_SYMBOL", "LCB_IOPS_SYMBOL", NULL)) { return -1; } info->soname = info->s_soname; info->symbol = info->s_symbol; return 1; } static plugin_info *find_plugin_info(lcb_io_ops_type_t iotype) { plugin_info *cur; if (iotype == LCB_IO_OPS_DEFAULT) { iotype = DEFAULT_IOPS; } for (cur = builtin_plugins; cur->base; cur++) { if (cur->iotype == iotype) { return cur; } } return NULL; } static void options_from_info(struct lcb_create_io_ops_st *opts, const plugin_info *info) { void *cookie; switch (opts->version) { case 0: cookie = opts->v.v0.cookie; break; case 1: cookie = opts->v.v1.cookie; break; case 2: cookie = opts->v.v2.cookie; break; default: lcb_assert("unknown options version" && 0); cookie = NULL; } if (info->create) { opts->version = 2; opts->v.v2.create = info->create; opts->v.v2.cookie = cookie; return; } opts->version = 1; opts->v.v1.sofile = info->soname; opts->v.v1.symbol = info->symbol; opts->v.v1.cookie = cookie; } static lcb_STATUS create_v2(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *options); struct plugin_st { void *dlhandle; union { create_func_t create; void *voidptr; } func; }; #ifndef _WIN32 static lcb_STATUS get_create_func(const char *image, const char *symbol, struct plugin_st *plugin, int do_warn) { void *dlhandle = dlopen(image, RTLD_NOW | RTLD_LOCAL); if (dlhandle == NULL) { if (do_warn) { fprintf(stderr, "[libcouchbase] dlopen of %s failed with '%s'\n", image, dlerror()); } return LCB_ERR_DLOPEN_FAILED; } memset(plugin, 0, sizeof(*plugin)); plugin->func.create = NULL; plugin->func.voidptr = dlsym(dlhandle, symbol); if (plugin->func.voidptr == NULL) { if (do_warn) { fprintf(stderr, "[libcouchbase] dlsym (%s) -> (%s) failed: %s\n", image, symbol, dlerror()); } dlclose(dlhandle); dlhandle = NULL; return LCB_ERR_DLSYM_FAILED; } else { plugin->dlhandle = dlhandle; } return LCB_SUCCESS; } static void close_dlhandle(void *handle) { dlclose(handle); } #else static lcb_STATUS get_create_func(const char *image, const char *symbol, struct plugin_st *plugin, int do_warn) { HMODULE hLibrary = LoadLibrary(image); FARPROC hFunction; memset(plugin, 0, sizeof(*plugin)); if (!hLibrary) { if (do_warn) { fprintf(stderr, "LoadLibrary of %s failed with code %d\n", image, (int)GetLastError()); } return LCB_ERR_DLOPEN_FAILED; } hFunction = GetProcAddress(hLibrary, symbol); if (!hFunction) { if (do_warn) { fprintf(stderr, "GetProcAddress (%s) -> (%s) failed with code %d\n", image, symbol, (int)GetLastError()); } FreeLibrary(hLibrary); return LCB_ERR_DLSYM_FAILED; } plugin->func.create = (create_func_t)hFunction; plugin->dlhandle = hLibrary; return LCB_SUCCESS; } static void close_dlhandle(void *handle) { FreeLibrary((HMODULE)handle); } #endif static int want_dl_debug = 0; /* global variable */ static lcb_STATUS create_v1(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *options); LIBCOUCHBASE_API lcb_STATUS lcb_destroy_io_ops(lcb_io_opt_t io) { if (io) { void *dlhandle = io->dlhandle; if (io->destructor) { io->destructor(io); } if (dlhandle) { close_dlhandle(dlhandle); } } return LCB_SUCCESS; } /** * Note, the 'pi' is just a context variable to ensure the pointers copied * to the options are valid. It is *not* meant to be inspected. */ static lcb_STATUS 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) { if (user) { memcpy(ours, user, sizeof(*user)); } else { memset(ours, 0, sizeof(*ours)); ours->version = 0; ours->v.v0.type = LCB_IO_OPS_DEFAULT; } if (ours->version > 0) { if (type) { *type = LCB_IO_OPS_INVALID; } /* we don't handle non-v0 options */ return LCB_SUCCESS; } if (ours->v.v0.type == LCB_IO_OPS_DEFAULT) { int rv; memset(pi, 0, sizeof(*pi)); rv = get_env_plugin_info(pi); if (rv > 0) { options_from_info(ours, pi); if (type) { *type = pi->iotype; } } else if (rv < 0) { return LCB_ERR_BAD_ENVIRONMENT; } else { plugin_info *pip = find_plugin_info(LCB_IO_OPS_DEFAULT); lcb_assert(pip); if (type) { *type = pip->iotype; } options_from_info(ours, pip); /* if the plugin is dynamically loadable, we need to * fallback to select(2) plugin in case we cannot find the * create function */ if (ours->version == 1) { struct plugin_st plugin; int want_debug; lcb_STATUS ret; if (lcb_getenv_boolean_multi("LIBCOUCHBASE_DLOPEN_DEBUG", "LCB_DLOPEN_DEBUG", NULL)) { want_debug = 1; } else { want_debug = want_dl_debug; } ret = get_create_func(ours->v.v1.sofile, ours->v.v1.symbol, &plugin, want_debug); if (ret != LCB_SUCCESS) { char path[PATH_MAX]; /* try to look up the so-file in the libdir */ snprintf(path, PATH_MAX, "%s/%s", LCB_LIBDIR, ours->v.v1.sofile); ret = get_create_func(path, ours->v.v1.symbol, &plugin, want_debug); } if (ret != LCB_SUCCESS) { if (type) { *type = LCB_IO_OPS_SELECT; } ours->version = 2; ours->v.v2.create = lcb_create_select_io_opts; ours->v.v2.cookie = NULL; } } } return LCB_SUCCESS; } else { /** Not default, ignore environment */ plugin_info *pip = find_plugin_info(ours->v.v0.type); if (!pip) { return LCB_ERR_UNSUPPORTED_OPERATION; } options_from_info(ours, pip); if (type) { *type = pip->iotype; } return LCB_SUCCESS; } } LIBCOUCHBASE_API lcb_STATUS lcb_create_io_ops(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *io_opts) { struct lcb_create_io_ops_st options; lcb_STATUS err; plugin_info pi; memset(&options, 0, sizeof(options)); err = lcb_initialize_socket_subsystem(); if (err != LCB_SUCCESS) { return err; } err = generate_options(&pi, io_opts, &options, NULL); if (err != LCB_SUCCESS) { return err; } if (options.version == 1) { err = create_v1(io, &options); } else if (options.version == 2) { err = create_v2(io, &options); } else { return LCB_ERR_UNSUPPORTED_OPERATION; } if (err != LCB_SUCCESS) { return err; } /*XXX: * This block of code here because the Ruby SDK relies on undocumented * functionality of older versions of libcouchbase in which its send/recv * functions assert that the number of IOV elements passed is always going * to be 2. * * This works around the issue by patching the send/recv functions of * the ruby implementation at load-time. * * This block of code will go away once the Ruby SDK is fixed and a released * version has been out for enough time that it won't break common existing * deployments. */ if (io_opts && io_opts->version == 1 && io_opts->v.v1.symbol != NULL) { if (strstr(io_opts->v.v1.symbol, "cb_create_ruby")) { wire_lcb_bsd_impl(*io); } } return LCB_SUCCESS; } static lcb_STATUS create_v1(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *options) { struct plugin_st plugin; int want_debug; lcb_STATUS ret; if (lcb_getenv_boolean_multi("LIBCOUCHBASE_DLOPEN_DEBUG", "LCB_DLOPEN_DEBUG", NULL)) { want_debug = 1; } else { want_debug = want_dl_debug; } ret = get_create_func(options->v.v1.sofile, options->v.v1.symbol, &plugin, want_debug); if (ret != LCB_SUCCESS) { /* try to look up the symbol in the current image */ lcb_STATUS ret2 = get_create_func(NULL, options->v.v1.symbol, &plugin, want_debug); if (ret2 != LCB_SUCCESS) { #ifndef _WIN32 char path[PATH_MAX]; /* try to look up the so-file in the libdir */ snprintf(path, PATH_MAX, "%s/%s", LCB_LIBDIR, options->v.v1.sofile); ret2 = get_create_func(path, options->v.v1.symbol, &plugin, want_debug); #endif if (ret2 != LCB_SUCCESS) { /* return original error to allow caller to fix it */ return ret; } } } ret = plugin.func.create(0, io, options->v.v1.cookie); if (ret != LCB_SUCCESS) { if (options->v.v1.sofile != NULL) { close_dlhandle(plugin.dlhandle); } return LCB_ERR_NO_MEMORY; } else { lcb_io_opt_t iop = *io; iop->dlhandle = plugin.dlhandle; /* check if plugin selected compatible version */ if (iop->version < 0 || iop->version > 3) { lcb_destroy_io_ops(iop); return LCB_ERR_PLUGIN_VERSION_MISMATCH; } } return LCB_SUCCESS; } static lcb_STATUS create_v2(lcb_io_opt_t *io, const struct lcb_create_io_ops_st *options) { lcb_STATUS ret; ret = options->v.v2.create(0, io, options->v.v2.cookie); if (ret != LCB_SUCCESS) { return ret; } else { lcb_io_opt_t iop = *io; /* check if plugin selected compatible version */ if (iop->version < 0 || iop->version > 3) { lcb_destroy_io_ops(iop); return LCB_ERR_PLUGIN_VERSION_MISMATCH; } } return LCB_SUCCESS; } lcb_STATUS lcb_iops_cntl_handler(int mode, lcb_INSTANCE *instance, int cmd, void *arg) { (void)instance; switch (cmd) { case LCB_CNTL_IOPS_DEFAULT_TYPES: { struct lcb_create_io_ops_st options; struct lcb_cntl_iops_info_st *info = arg; plugin_info pi; memset(&options, 0, sizeof(options)); if (mode != LCB_CNTL_GET) { return LCB_ERR_UNSUPPORTED_OPERATION; } if (info->version != 0) { return LCB_ERR_INVALID_ARGUMENT; } info->v.v0.os_default = DEFAULT_IOPS; return generate_options(&pi, info->v.v0.options, &options, &info->v.v0.effective); } case LCB_CNTL_IOPS_DLOPEN_DEBUG: { int *usr = arg; if (mode == LCB_CNTL_SET) { want_dl_debug = *usr; } else { *usr = want_dl_debug; } return LCB_SUCCESS; } default: return LCB_ERR_INVALID_ARGUMENT; } } /* In-library wrapper version */ LIBCOUCHBASE_API void lcb_iops_wire_bsd_impl2(lcb_bsd_procs *procs, int version) { wire_lcb_bsd_impl2(procs, version); }