Project
Loading...
Searching...
No Matches
DLLoaderBase.h
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12// \brief A thin wrapper to manage dynamic library loading, based around boost::dll
13
14#ifndef DLLOADER_H_
15#define DLLOADER_H_
16
17#include <filesystem>
18#include <optional>
19#include <unordered_map>
20#include <memory>
21#include <mutex>
22#include <cstdlib>
23#include <typeinfo>
24#include "dlfcn.h"
25
26#if defined(__APPLE__)
27#define DLLOADER_MAC_LINUX(mac, linux) mac
28#else
29#define DLLOADER_MAC_LINUX(mac, linux) linux
30#endif
31
32#include "Framework/Logger.h"
33
34namespace o2::utils
35{
36
37// Manages dynamic loading and unloading (or rather ensuring they are not
38// unloaded for the duration of the program) of libraries and symbol lookups. It
39// ensures thread-safety through the use of a mutex and implements the Meyers
40// Singleton pattern to provide a single instance of the manager.
41template <typename DerivedType>
43{
44 public:
46 static constexpr const char* prefix = "lib"; // same prefix on mac and linux
47 static constexpr const char* suffix = DLLOADER_MAC_LINUX(".dylib", ".so");
48 };
49 enum Options {
50 none = 0,
51 global = RTLD_GLOBAL,
52 local = RTLD_LOCAL,
53 no_delete = RTLD_NODELETE,
54 no_load = RTLD_NOLOAD,
55 lazy = RTLD_LAZY,
56 };
57 using handle_t = void;
61 {
62 if (p != nullptr) {
63 dlclose(p);
64 }
65 }
66 };
67 using library_t = std::unique_ptr<handle_t, HandleDeleter>;
68
69 // Returns the singleton instance of the manager. Any function should only be
70 // accessed through an instance. This being a singleton serves two purposes:
71 // 1. Libraries are loaded only once.
72 // 2. Once loaded they are not again unloaded until the end of the program.
73 static DerivedType& Instance()
74 {
75 return DerivedType::sInstance;
76 }
77
78 // Loads a dynamic library by its name and stores its handle. Returns true
79 // if the library is successfully loaded or already loaded.
80 bool addLibrary(const std::string& library)
81 {
82 const std::lock_guard lock(mLock);
83
84 if (mLibraries.find(library) != mLibraries.end()) {
85 return true; // Library already loaded
86 }
87
88 if (mO2Path.empty()) {
89 if (const auto* path = std::getenv("O2_ROOT")) {
90 mO2Path = path;
91 } else {
92 LOGP(error, "$O2_ROOT not set!");
93 return false;
94 }
95 }
96
97 auto path = getO2Path(library);
98 if (!std::filesystem::exists(path)) {
99 LOGP(error, "Library under '{}' does not exist!", path);
100 return false;
101 }
102
103 try {
104 auto lib = std::unique_ptr<handle_t, HandleDeleter>(dlopen(path.c_str(), mLoadPolicy));
105 if (lib == nullptr) {
106 throw std::runtime_error("Library handle is nullptr!");
107 }
108 mLibraries[library] = std::move(lib);
109 LOGP(info, "Loaded dynamic library '{}' from '{}'", library, path);
110 return true;
111 } catch (std::exception& e) {
112 LOGP(error, "Failed to load library (path='{}'), failed reason: '{}'", path, e.what());
113 return false;
114 } catch (...) {
115 LOGP(error, "Failed to load library (path='{}') for unknown reason!", path.c_str());
116 return false;
117 }
118 }
119
120 // Unloads a given library returns true if this succeeded.
121 //
122 // Nota bene: Actually, we have very little control here when the unloading
123 // hapens since the linkder decides this based on if there is any reference
124 // left. And even if the reference counter goes to zero the linker is free to
125 // leave the library loaded and clean up whenever it wants.
126 bool unloadLibrary(const std::string& library)
127 {
128 const std::lock_guard lock(mLock);
129
130 if (auto it = mLibraries.find(library); it != mLibraries.end()) {
131 mLibraries.erase(it);
132 return true;
133 }
134
135 LOGP(error, "No '{}' library found, cannot unload it!", library);
136 return false;
137 }
138
139 // Resets all loaded libraries and O2Path, this invalidates all outside kept
140 // references.
141 void reset()
142 {
143 mO2Path.clear();
144 mLibraries.clear();
145 }
146
147 // Checks if a library contains a specific symbol.
148 bool hasSymbol(const std::string& library, const std::string& symbol)
149 {
150 const std::lock_guard lock(mLock);
151
152 if (mLibraries.find(library) == mLibraries.end()) {
153 // Library not loaded, attempt to load it
154 if (!addLibrary(library)) {
155 return false;
156 }
157 }
158
159 dlerror(); // clear previous error
160
161 // Checks if the symbol exists but does not load it.
162 handle_ptr_t ptr = dlsym(mLibraries[library].get(), symbol.c_str());
163 if (const auto* err = dlerror(); err != nullptr) {
164 LOGP(error, "Did not get {} from {}; error: {}", symbol, library, err);
165 }
166
167 return ptr != nullptr;
168 }
169
170 // Executes a function from a loaded library or return nullopt
171 template <typename Ret, typename... Args>
172 std::optional<Ret> executeFunction(const std::string& library, const std::string& fname, Args... args)
173 {
174 using Func_t = Ret (*)(Args...);
175
176 const std::lock_guard lock(mLock);
177
178 if (fname.empty()) {
179 LOGP(error, "Function name cannot be empty!");
180 return std::nullopt;
181 }
182
183 if (mLibraries.find(library) == mLibraries.end()) {
184 // Library not loaded, attempt to load it
185 if (!addLibrary(library)) {
186 return std::nullopt;
187 }
188 }
189
190 const auto& lib = mLibraries[library].get();
191 if (!hasSymbol(library, fname)) {
192 LOGP(error, "Library '{}' does not have a symbol '{}'", library, fname);
193 return std::nullopt;
194 }
195
196 dlerror(); // Clear previous error
197
198 auto func = (Func_t)dlsym(lib, fname.c_str());
199 if (const auto* err = dlerror(); err != nullptr) {
200 LOGP(error, "Did not get {} from {}; error: {}", fname, library, err);
201 return std::nullopt;
202 }
203
204 if (func == nullptr) {
205 LOGP(error, "Library '{}' does not have a symbol '{}' with {}", library, fname, getTypeName<Func_t>());
206 return std::nullopt;
207 }
208
209 // Execute function and return its return value
210 return func(args...);
211 }
212
213 // Wrapper for function execution which fatals if execution fails
214 template <typename Ret, typename... Args>
215 Ret executeFunctionAlias(const std::string& library, const std::string& fname, Args... args)
216 {
217 if (auto opt = executeFunction<Ret, Args...>(library, fname, args...)) {
218 return *opt;
219 }
220
221 LOGP(fatal, "Execution of '{}' from '{}' failed spectaculary!", fname, library);
222 __builtin_unreachable(); // is this safe, AFACIT only gcc and clang are supported anyway
223 }
224
225 // Delete copy and move constructors to enforce singleton pattern.
226 DLLoaderBase(const DLLoaderBase&) = delete;
230
231 protected:
232 // Constructor and destructor are protected to enforce singleton pattern.
233 DLLoaderBase() = default;
234 ~DLLoaderBase() = default;
235
236 private:
237 // Returns the full path to a O2 shared library..
238 [[nodiscard]] std::string getO2Path(const std::string& library) const
239 {
240 return mO2Path + "/lib/" + filename_decorations::prefix + library + filename_decorations::suffix;
241 }
242
243 // Returns the demangled type name of a prototype, e.g., for pretty printing.
244 template <typename ProtoType>
245 [[nodiscard]] auto getTypeName() -> std::string
246 {
247 return typeid(ProtoType).name(); // TODO
248 }
249
250 std::unordered_map<std::string, library_t> mLibraries{}; // Pointers to loaded libraries, calls `unload()' for each library, e.g., correctly destroy this object
251 std::recursive_mutex mLock{}; // While a recursive mutex is more expansive it makes locking easier
252 std::string mO2Path{}; // Holds the path O2 dynamic library determined by $O2_ROOT
253 Options mLoadPolicy{Options::lazy}; // load resolution policy
254};
255
256} // namespace o2::utils
257
258#define O2DLLoaderDef(classname) \
259 private: \
260 static classname sInstance; \
261 classname() = default; \
262 friend class o2::utils::DLLoaderBase<classname>;
263
264#define O2DLLoaderImpl(classname) classname classname::sInstance;
265
266#endif // DLLOADER_H_
#define DLLOADER_MAC_LINUX(mac, linux)
TBranch * ptr
static DerivedType & Instance()
DLLoaderBase(const DLLoaderBase &)=delete
bool unloadLibrary(const std::string &library)
bool addLibrary(const std::string &library)
std::optional< Ret > executeFunction(const std::string &library, const std::string &fname, Args... args)
bool hasSymbol(const std::string &library, const std::string &symbol)
std::unique_ptr< handle_t, HandleDeleter > library_t
DLLoaderBase & operator=(const DLLoaderBase &)=delete
DLLoaderBase & operator=(DLLoaderBase &&)=delete
Ret executeFunctionAlias(const std::string &library, const std::string &fname, Args... args)
DLLoaderBase(DLLoaderBase &&)=delete
GLenum func
Definition glcorearb.h:778
GLuint const GLchar * name
Definition glcorearb.h:781
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
static constexpr const char * prefix
static constexpr const char * suffix