diff options
Diffstat (limited to 'src/osd/ClassHandler.cc')
-rw-r--r-- | src/osd/ClassHandler.cc | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/src/osd/ClassHandler.cc b/src/osd/ClassHandler.cc new file mode 100644 index 000000000..d1e726408 --- /dev/null +++ b/src/osd/ClassHandler.cc @@ -0,0 +1,350 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "include/types.h" +#include "ClassHandler.h" +#include "common/errno.h" +#include "common/ceph_context.h" +#include "include/dlfcn_compat.h" + +#include <map> + +#if defined(__FreeBSD__) +#include <sys/param.h> +#endif + +#include "common/config.h" +#include "common/debug.h" + +#define dout_subsys ceph_subsys_osd +#undef dout_prefix +#define dout_prefix *_dout + + +#define CLS_PREFIX "libcls_" +#define CLS_SUFFIX SHARED_LIB_SUFFIX + +using std::map; +using std::set; +using std::string; + +using ceph::bufferlist; + + +int ClassHandler::open_class(const string& cname, ClassData **pcls) +{ + std::lock_guard lock(mutex); + ClassData *cls = _get_class(cname, true); + if (!cls) + return -EPERM; + if (cls->status != ClassData::CLASS_OPEN) { + int r = _load_class(cls); + if (r) + return r; + } + *pcls = cls; + return 0; +} + +int ClassHandler::open_all_classes() +{ + ldout(cct, 10) << __func__ << dendl; + DIR *dir = ::opendir(cct->_conf->osd_class_dir.c_str()); + if (!dir) + return -errno; + + struct dirent *pde = nullptr; + int r = 0; + while ((pde = ::readdir(dir))) { + if (pde->d_name[0] == '.') + continue; + if (strlen(pde->d_name) > sizeof(CLS_PREFIX) - 1 + sizeof(CLS_SUFFIX) - 1 && + strncmp(pde->d_name, CLS_PREFIX, sizeof(CLS_PREFIX) - 1) == 0 && + strcmp(pde->d_name + strlen(pde->d_name) - (sizeof(CLS_SUFFIX) - 1), CLS_SUFFIX) == 0) { + char cname[PATH_MAX + 1]; + strncpy(cname, pde->d_name + sizeof(CLS_PREFIX) - 1, sizeof(cname) -1); + cname[strlen(cname) - (sizeof(CLS_SUFFIX) - 1)] = '\0'; + ldout(cct, 10) << __func__ << " found " << cname << dendl; + ClassData *cls; + // skip classes that aren't in 'osd class load list' + r = open_class(cname, &cls); + if (r < 0 && r != -EPERM) + goto out; + } + } + out: + closedir(dir); + return r; +} + +void ClassHandler::shutdown() +{ + for (auto& cls : classes) { + if (cls.second.handle) { + dlclose(cls.second.handle); + } + } + classes.clear(); +} + +/* + * Check if @cname is in the whitespace delimited list @list, or the @list + * contains the wildcard "*". + * + * This is expensive but doesn't consume memory for an index, and is performed + * only once when a class is loaded. + */ +bool ClassHandler::in_class_list(const std::string& cname, + const std::string& list) +{ + std::istringstream ss(list); + std::istream_iterator<std::string> begin{ss}; + std::istream_iterator<std::string> end{}; + + const std::vector<std::string> targets{cname, "*"}; + + auto it = std::find_first_of(begin, end, + targets.begin(), targets.end()); + + return it != end; +} + +ClassHandler::ClassData *ClassHandler::_get_class(const string& cname, + bool check_allowed) +{ + ClassData *cls; + map<string, ClassData>::iterator iter = classes.find(cname); + + if (iter != classes.end()) { + cls = &iter->second; + } else { + if (check_allowed && !in_class_list(cname, cct->_conf->osd_class_load_list)) { + ldout(cct, 0) << "_get_class not permitted to load " << cname << dendl; + return NULL; + } + cls = &classes[cname]; + ldout(cct, 10) << "_get_class adding new class name " << cname << " " << cls << dendl; + cls->name = cname; + cls->handler = this; + cls->allowed = in_class_list(cname, cct->_conf->osd_class_default_list); + } + return cls; +} + +int ClassHandler::_load_class(ClassData *cls) +{ + // already open + if (cls->status == ClassData::CLASS_OPEN) + return 0; + + if (cls->status == ClassData::CLASS_UNKNOWN || + cls->status == ClassData::CLASS_MISSING) { + char fname[PATH_MAX]; + snprintf(fname, sizeof(fname), "%s/" CLS_PREFIX "%s" CLS_SUFFIX, + cct->_conf->osd_class_dir.c_str(), + cls->name.c_str()); + ldout(cct, 10) << "_load_class " << cls->name << " from " << fname << dendl; + + cls->handle = dlopen(fname, RTLD_NOW); + if (!cls->handle) { + struct stat st; + int r = ::stat(fname, &st); + if (r < 0) { + r = -errno; + ldout(cct, 0) << __func__ << " could not stat class " << fname + << ": " << cpp_strerror(r) << dendl; + } else { + ldout(cct, 0) << "_load_class could not open class " << fname + << " (dlopen failed): " << dlerror() << dendl; + r = -EIO; + } + cls->status = ClassData::CLASS_MISSING; + return r; + } + + cls_deps_t *(*cls_deps)(); + cls_deps = (cls_deps_t *(*)())dlsym(cls->handle, "class_deps"); + if (cls_deps) { + cls_deps_t *deps = cls_deps(); + while (deps) { + if (!deps->name) + break; + ClassData *cls_dep = _get_class(deps->name, false); + cls->dependencies.insert(cls_dep); + if (cls_dep->status != ClassData::CLASS_OPEN) + cls->missing_dependencies.insert(cls_dep); + deps++; + } + } + } + + // resolve dependencies + set<ClassData*>::iterator p = cls->missing_dependencies.begin(); + while (p != cls->missing_dependencies.end()) { + ClassData *dc = *p; + int r = _load_class(dc); + if (r < 0) { + cls->status = ClassData::CLASS_MISSING_DEPS; + return r; + } + + ldout(cct, 10) << "_load_class " << cls->name << " satisfied dependency " << dc->name << dendl; + cls->missing_dependencies.erase(p++); + } + + // initialize + void (*cls_init)() = (void (*)())dlsym(cls->handle, "__cls_init"); + if (cls_init) { + cls->status = ClassData::CLASS_INITIALIZING; + cls_init(); + } + + ldout(cct, 10) << "_load_class " << cls->name << " success" << dendl; + cls->status = ClassData::CLASS_OPEN; + return 0; +} + + + +ClassHandler::ClassData *ClassHandler::register_class(const char *cname) +{ + ceph_assert(ceph_mutex_is_locked(mutex)); + + ClassData *cls = _get_class(cname, false); + ldout(cct, 10) << "register_class " << cname << " status " << cls->status << dendl; + + if (cls->status != ClassData::CLASS_INITIALIZING) { + ldout(cct, 0) << "class " << cname << " isn't loaded; is the class registering under the wrong name?" << dendl; + return NULL; + } + return cls; +} + +void ClassHandler::unregister_class(ClassHandler::ClassData *cls) +{ + /* FIXME: do we really need this one? */ +} + +ClassHandler::ClassMethod *ClassHandler::ClassData::register_method(const char *mname, + int flags, + cls_method_call_t func) +{ + /* no need for locking, called under the class_init mutex */ + if (!flags) { + lderr(handler->cct) << "register_method " << name << "." << mname + << " flags " << flags << " " << (void*)func + << " FAILED -- flags must be non-zero" << dendl; + return NULL; + } + ldout(handler->cct, 10) << "register_method " << name << "." << mname << " flags " << flags << " " << (void*)func << dendl; + [[maybe_unused]] auto [method, added] = methods_map.try_emplace(mname, mname, func, flags, this); + return &method->second; +} + +ClassHandler::ClassMethod *ClassHandler::ClassData::register_cxx_method(const char *mname, + int flags, + cls_method_cxx_call_t func) +{ + /* no need for locking, called under the class_init mutex */ + ldout(handler->cct, 10) << "register_cxx_method " << name << "." << mname << " flags " << flags << " " << (void*)func << dendl; + [[maybe_unused]] auto [method, added] = methods_map.try_emplace(mname, mname, func, flags, this); + return &method->second; +} + +ClassHandler::ClassFilter *ClassHandler::ClassData::register_cxx_filter( + const std::string &filter_name, + cls_cxx_filter_factory_t fn) +{ + ClassFilter &filter = filters_map[filter_name]; + filter.fn = fn; + filter.name = filter_name; + filter.cls = this; + return &filter; +} + +ClassHandler::ClassMethod *ClassHandler::ClassData::_get_method( + const std::string& mname) +{ + if (auto iter = methods_map.find(mname); iter != methods_map.end()) { + return &(iter->second); + } else { + return nullptr; + } +} + +int ClassHandler::ClassData::get_method_flags(const std::string& mname) +{ + std::lock_guard l(handler->mutex); + ClassMethod *method = _get_method(mname); + if (!method) + return -ENOENT; + return method->flags; +} + +void ClassHandler::ClassData::unregister_method(ClassHandler::ClassMethod *method) +{ + /* no need for locking, called under the class_init mutex */ + map<string, ClassMethod>::iterator iter = methods_map.find(method->name); + if (iter == methods_map.end()) + return; + methods_map.erase(iter); +} + +void ClassHandler::ClassMethod::unregister() +{ + cls->unregister_method(this); +} + +void ClassHandler::ClassData::unregister_filter(ClassHandler::ClassFilter *filter) +{ + /* no need for locking, called under the class_init mutex */ + map<string, ClassFilter>::iterator iter = filters_map.find(filter->name); + if (iter == filters_map.end()) + return; + filters_map.erase(iter); +} + +void ClassHandler::ClassFilter::unregister() +{ + cls->unregister_filter(this); +} + +int ClassHandler::ClassMethod::exec(cls_method_context_t ctx, bufferlist& indata, bufferlist& outdata) +{ + int ret = 0; + std::visit([&](auto method) { + using method_t = decltype(method); + if constexpr (std::is_same_v<method_t, cls_method_cxx_call_t>) { + // C++ call version + ret = method(ctx, &indata, &outdata); + } else if constexpr (std::is_same_v<method_t, cls_method_call_t>) { + // C version + char *out = nullptr; + int olen = 0; + ret = method(ctx, indata.c_str(), indata.length(), &out, &olen); + if (out) { + // assume *out was allocated via cls_alloc (which calls malloc!) + ceph::buffer::ptr bp = ceph::buffer::claim_malloc(olen, out); + outdata.push_back(bp); + } + } else { + static_assert(std::is_same_v<method_t, void>); + } + }, func); + return ret; +} + +ClassHandler& ClassHandler::get_instance() +{ +#ifdef WITH_SEASTAR + // the context is being used solely for: + // 1. random number generation (cls_gen_random_bytes) + // 2. accessing the configuration + // 3. logging + static CephContext cct; + static ClassHandler single(&cct); +#else + static ClassHandler single(g_ceph_context); +#endif // WITH_SEASTAR + return single; +} |