/** * @file lib_common.cpp * @author Martin Pulec */ /* * Copyright (c) 2012-2023 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 "utils/color_out.h" 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) { const size_t len = strlen(pathelem) + 1 + strlen(binarynames[i]) + 1; char * candidate = (char *)calloc(1, len); snprintf(candidate, len, "%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 { // case-independent (ci) compare_less binary function struct nocase_compare { 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 auto& get_libmap(){ /* This is needed because register_library() may be called before global * static members are initialized (it is __attribute__((constructor))) */ static map> libraries; return libraries; } void register_library(const char *name, const void *data, enum library_class cls, int abi_version, int hidden) { auto& map = get_libmap()[cls]; if (map.find(name) != map.end()) { LOG(LOG_LEVEL_ERROR) << "Module \"" << name << "\" (class " << cls << ") multiple initialization!\n"; } map[name] = {data, abi_version, static_cast(hidden)}; } const void *load_library(const char *name, enum library_class cls, int abi_version) { auto it_cls = get_libmap().find(cls); if (it_cls != get_libmap().end()) { auto it_module = it_cls->second.find(name); if (it_module != it_cls->second.end()) { const auto& mod_pair = it_module->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) { col() << "\t" << SBOLD(item.first.c_str()) << "\n"; } } /** * @retval false if there occurs some problem opening one or more modules, true otherwise */ bool list_all_modules() { bool ret = true; auto& libraries = get_libmap(); 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) { col() << "\t" << SBOLD(item.first) << "\n"; } } cout << "\n"; } if (!lib_errors.empty()) { ret = false; col() << SBOLD(SRED("Errors:")) << "\n"; for (auto && item : lib_errors) { cout << "\t" << SRED(item.first) << "\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& libraries = get_libmap(); 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; }