// -*- 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 #if defined(__FreeBSD__) #include #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 ".so" void ClassHandler::add_embedded_class(const string& cname) { assert(mutex.is_locked()); ClassData *cls = _get_class(cname, false); assert(cls->status == ClassData::CLASS_UNKNOWN); cls->status = ClassData::CLASS_INITIALIZING; } int ClassHandler::open_class(const string& cname, ClassData **pcls) { Mutex::Locker 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 begin{ss}; std::istream_iterator end{}; const std::vector 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::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->whitelisted = 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::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) { assert(mutex.is_locked()); 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; ClassMethod& method = methods_map[mname]; method.func = func; method.name = mname; method.flags = flags; method.cls = this; return &method; } 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; ClassMethod& method = methods_map[mname]; method.cxx_func = func; method.name = mname; method.flags = flags; method.cls = this; return &method; } 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 char *mname) { map::iterator iter = methods_map.find(mname); if (iter == methods_map.end()) return NULL; return &(iter->second); } int ClassHandler::ClassData::get_method_flags(const char *mname) { Mutex::Locker 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::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::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; if (cxx_func) { // C++ call version ret = cxx_func(ctx, &indata, &outdata); } else { // C version char *out = NULL; int olen = 0; ret = func(ctx, indata.c_str(), indata.length(), &out, &olen); if (out) { // assume *out was allocated via cls_alloc (which calls malloc!) buffer::ptr bp = buffer::claim_malloc(olen, out); outdata.push_back(bp); } } return ret; }