/** * @file lib_common.cpp * @author Martin Pulec */ /* * Copyright (c) 2012-2019 CESNET, z. s. p. o. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of CESNET nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H #include "config.h" #include "config_unix.h" #include "config_win32.h" #endif // HAVE_CONFIG_H #ifdef BUILD_LIBRARIES #include #include #include #endif #include #include #include "debug.h" #include "host.h" #include "lib_common.h" #include "rang.hpp" using namespace std; struct library_class_info_t { const char *class_name; const char *file_prefix; }; const map library_class_info = { { LIBRARY_CLASS_UNDEFINED, { "General module", "" }}, { LIBRARY_CLASS_CAPTURE_FILTER, { "Video capture filter" , "vcapfilter" }}, { LIBRARY_CLASS_AUDIO_CAPTURE, { "Audio capture device", "acap" }}, { LIBRARY_CLASS_AUDIO_PLAYBACK, { "Audio playback device", "aplay" }}, { LIBRARY_CLASS_VIDEO_CAPTURE, { "Video capture device", "vidcap" }}, { LIBRARY_CLASS_VIDEO_DISPLAY, { "Video display device", "display" }}, { LIBRARY_CLASS_AUDIO_COMPRESS, { "Audio compression", "acompress" }}, { LIBRARY_CLASS_VIDEO_DECOMPRESS, { "Video decompression", "vdecompress" }}, { LIBRARY_CLASS_VIDEO_COMPRESS, { "Video compression", "vcompress" }}, { LIBRARY_CLASS_VIDEO_POSTPROCESS, { "Video postprocess", "vo_pp" }}, { LIBRARY_CLASS_VIDEO_RXTX, { "Video RXTX", "video_rxtx" }}, }; static map lib_errors; #ifdef BUILD_LIBRARIES static void push_basename_entry(char ***binarynames, const char *bnc, size_t * templates) { char * alt_v0 = strdup(bnc); assert(alt_v0 != NULL); char * bn_v0 = basename(alt_v0); for (size_t i = 0; i < *templates && bn_v0 != NULL; ++i) { if (strcmp((*binarynames)[i], bn_v0) == 0) bn_v0 = NULL; } if (bn_v0 != NULL) { *binarynames = (char **)realloc(*binarynames, (*templates + 2)*sizeof(**binarynames)); assert(*binarynames != NULL); (*binarynames)[(*templates)++] = strdup(bn_v0); (*binarynames)[*templates] = NULL; } free(alt_v0); } static int running_from_path(char * uv_argv[]) { const char *env_path = getenv("PATH"); if (env_path == NULL) { // LOG(LOG_LEVEL_WARNING) << "Failed to determine environment PATH variable, plugins shall be searched relative to binary" << "\n"; return 0; } char *self_path = realpath("/proc/self/exe", NULL); if (self_path == NULL) { // LOG(LOG_LEVEL_WARNING) << "Failed to determine path to running process" << "\n"; return 0; } // considered binary names, currently only uv, but reserve other as well const char * binarynames_tpl[] = { "uv", "ultragrid", "uv-nightly", "ultragrid-nightly" }; size_t templates = sizeof(binarynames_tpl)/sizeof(binarynames_tpl[0]); char **binarynames = (char **)calloc(templates + 1 + 3, sizeof(*binarynames)); // pre-allocate 3 dirnames assert(binarynames != NULL); for (size_t i = 0; i < templates; ++i) binarynames[i] = strdup(binarynames_tpl[i]); push_basename_entry(&binarynames, uv_argv[0], &templates); if (strchr(uv_argv[0], '/') != NULL) { char * alt_v0 = realpath(uv_argv[0], NULL); if (alt_v0 != NULL) { push_basename_entry(&binarynames, alt_v0, &templates); free(alt_v0); } } push_basename_entry(&binarynames, self_path, &templates); char *pathdup = strdup(env_path); char *tokencont = pathdup; int rval = 0; // if path contains /, make it the first and only candidate char *next_token = NULL; while (rval == 0) { // loop through paths const char *pathelem = strtok_r(tokencont, ":", &next_token); tokencont = NULL; if (pathelem == NULL) break; for (size_t i = 0; binarynames[i] != NULL && rval == 0; ++i) { char * candidate = (char *)calloc(1, strlen(pathelem) + 1 + strlen(binarynames[i]) + 1); sprintf(candidate, "%s/%s", pathelem, binarynames[i]); char * real_candidate = realpath(candidate, NULL); if (real_candidate != NULL) { rval = strcmp(real_candidate, self_path) == 0; free(real_candidate); } free(candidate); } }; for (size_t i = 0; binarynames[i] != NULL; ++i) free(binarynames[i]); free(binarynames); free(pathdup); free(self_path); return rval; } #endif void open_all(const char *pattern, list &libs) { #ifdef BUILD_LIBRARIES char path[512]; glob_t glob_buf; /* binary not from $PATH */ if (!running_from_path(uv_argv)) { char *tmp = strdup(uv_argv[0]); char *dir = dirname(tmp); snprintf(path, sizeof(path), "%s/../lib/ultragrid/%s", dir, pattern); free(tmp); } else { snprintf(path, sizeof(path), LIB_DIR "/ultragrid/%s", pattern); } glob(path, 0, NULL, &glob_buf); for(unsigned int i = 0; i < glob_buf.gl_pathc; ++i) { void *handle = dlopen(glob_buf.gl_pathv[i], RTLD_NOW|RTLD_GLOBAL); if (!handle) { char *error = dlerror(); verbose_msg("Library %s opening warning: %s \n", glob_buf.gl_pathv[i], error); char *filename = basename(glob_buf.gl_pathv[i]); if (filename && error) { lib_errors.emplace(filename, error); } continue; } libs.push_back(handle); } globfree(&glob_buf); #else UNUSED(libs); UNUSED(pattern); #endif } struct lib_info { const void *data; int abi_version; bool hidden; }; // http://stackoverflow.com/questions/1801892/making-mapfind-operation-case-insensitive /************************************************************************/ /* Comparator for case-insensitive comparison in STL assos. containers */ /************************************************************************/ struct ci_less : std::binary_function { // case-independent (ci) compare_less binary function struct nocase_compare : public std::binary_function { bool operator() (const unsigned char& c1, const unsigned char& c2) const { return tolower (c1) < tolower (c2); } }; bool operator() (const std::string & s1, const std::string & s2) const { return std::lexicographical_compare (s1.begin (), s1.end (), // source range s2.begin (), s2.end (), // dest range nocase_compare ()); // comparison } }; static map> *libraries = nullptr; /** * The purpose of this initializor instead of ordinary static initialization is that register_video_capture_filter() * may be called before static members are initialized (it is __attribute__((constructor))) */ struct init_libraries { init_libraries() { if (libraries == nullptr) { libraries = new remove_pointer::type(); } } }; static struct init_libraries loader; void register_library(const char *name, const void *data, enum library_class cls, int abi_version, int hidden) { struct init_libraries loader; if ((*libraries)[cls].find(name) != (*libraries)[cls].find(name)) { LOG(LOG_LEVEL_ERROR) << "Module \"" << name << "\" multiple initialization!\n"; } (*libraries)[cls][name] = {data, abi_version, static_cast(hidden)}; } const void *load_library(const char *name, enum library_class cls, int abi_version) { if (libraries->find(cls) != libraries->end()) { auto it_cls = libraries->find(cls)->second; auto it_module = it_cls.find(name); if (it_module != it_cls.end()) { auto mod_pair = it_cls.find(name)->second; if (mod_pair.abi_version == abi_version) { return mod_pair.data; } else { LOG(LOG_LEVEL_WARNING) << "Module " << name << " ABI version mismatch (required " << abi_version << ", have " << mod_pair.abi_version << ")\n"; } } } // Library was not found or was not loaded due to unsatisfied // dependencies. If the latter one, display reason why dlopen() failed. if (library_class_info.find(cls) != library_class_info.end()) { string filename = "ultragrid_"; if (strlen(library_class_info.at(cls).file_prefix) > 0) { filename += library_class_info.at(cls).file_prefix; filename += "_"; } filename += name + string(".so"); if (lib_errors.find(filename) != lib_errors.end()) { LOG(LOG_LEVEL_WARNING) << filename << ": " << lib_errors.find(filename)->second << "\n"; } } return NULL; } /** * Prints list of modules of given class * @param full include hidden modules */ void list_modules(enum library_class cls, int abi_version, bool full) { const auto & class_set = get_libraries_for_class(cls, abi_version, full); for (auto && item : class_set) { cout << rang::style::bold << "\t" << item.first.c_str() << "\n" << rang::style::reset; } } /** * @retval false if there occurs some problem opening one or more modules, true otherwise */ bool list_all_modules() { bool ret = true; for (auto cls_it = library_class_info.begin(); cls_it != library_class_info.end(); ++cls_it) { cout << cls_it->second.class_name << "\n"; auto it = libraries->find(cls_it->first); if (it != libraries->end()) { for (auto && item : it->second) { cout << rang::style::bold << "\t" << item.first << "\n" << rang::style::reset; } } cout << "\n"; } if (!lib_errors.empty()) { ret = false; cout << rang::style::bold << rang::fg::red << "Errors:\n" << rang::fg::reset << rang::style::reset; for (auto && item : lib_errors) { cout << "\t" << rang::fg::red << item.first << rang::fg::reset << "\n\t\t" << item.second << "\n"; } cout << "\n"; } return ret; } map get_libraries_for_class(enum library_class cls, int abi_version, bool include_hidden) { map ret; auto it = libraries->find(cls); if (it != libraries->end()) { for (auto && item : it->second) { if (abi_version == item.second.abi_version) { if (include_hidden || !item.second.hidden) { ret[item.first] = item.second.data; } } else { LOG(LOG_LEVEL_WARNING) << "Module " << item.first << " ABI version mismatch (required " << abi_version << ", have " << item.second.abi_version << ")\n"; } } } return ret; }