2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
6 #include "DynamicBackendUtils.hpp"
8 #include <boost/filesystem.hpp>
9 #include <boost/algorithm/string.hpp>
10 #include <boost/log/trivial.hpp>
17 void* DynamicBackendUtils::OpenHandle(const std::string& sharedObjectPath)
19 if (sharedObjectPath.empty())
21 throw RuntimeException("OpenHandle error: shared object path must not be empty");
24 void* sharedObjectHandle = dlopen(sharedObjectPath.c_str(), RTLD_LAZY);
25 if (!sharedObjectHandle)
27 throw RuntimeException(boost::str(boost::format("OpenHandle error: %1%") % GetDlError()));
30 return sharedObjectHandle;
33 void DynamicBackendUtils::CloseHandle(const void* sharedObjectHandle)
35 if (!sharedObjectHandle)
40 dlclose(const_cast<void*>(sharedObjectHandle));
43 bool DynamicBackendUtils::IsBackendCompatible(const BackendVersion &backendVersion)
45 BackendVersion backendApiVersion = IBackendInternal::GetApiVersion();
47 return IsBackendCompatibleImpl(backendApiVersion, backendVersion);
50 bool DynamicBackendUtils::IsBackendCompatibleImpl(const BackendVersion &backendApiVersion,
51 const BackendVersion &backendVersion)
53 return backendVersion.m_Major == backendApiVersion.m_Major &&
54 backendVersion.m_Minor <= backendApiVersion.m_Minor;
57 std::string DynamicBackendUtils::GetDlError()
59 const char* errorMessage = dlerror();
65 return std::string(errorMessage);
68 std::vector<std::string> DynamicBackendUtils::GetBackendPaths(const std::string& overrideBackendPath)
70 // Check if a path where to dynamically load the backends from is given
71 if (!overrideBackendPath.empty())
73 if (!IsPathValid(overrideBackendPath))
75 BOOST_LOG_TRIVIAL(warning) << "WARNING: The given override path for dynamic backends \""
76 << overrideBackendPath << "\" is not valid";
81 return std::vector<std::string>{ overrideBackendPath };
84 // Expects a colon-separated list: DYNAMIC_BACKEND_PATHS="PATH_1:PATH_2:...:PATH_N"
85 const std::string backendPaths = DYNAMIC_BACKEND_PATHS;
87 return GetBackendPathsImpl(backendPaths);
90 std::vector<std::string> DynamicBackendUtils::GetBackendPathsImpl(const std::string& backendPaths)
92 std::unordered_set<std::string> uniqueBackendPaths;
93 std::vector<std::string> tempBackendPaths;
94 std::vector<std::string> validBackendPaths;
96 // Split the given list of paths
97 boost::split(tempBackendPaths, backendPaths, boost::is_any_of(":"));
99 for (const std::string& path : tempBackendPaths)
101 // Check whether the path is valid
102 if (!IsPathValid(path))
107 // Check whether the path is a duplicate
108 auto it = uniqueBackendPaths.find(path);
109 if (it != uniqueBackendPaths.end())
111 // The path is a duplicate
115 // Add the path to the set of unique paths
116 uniqueBackendPaths.insert(path);
118 // Add the path to the list of valid paths
119 validBackendPaths.push_back(path);
122 return validBackendPaths;
125 bool DynamicBackendUtils::IsPathValid(const std::string& path)
129 BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path is empty";
133 boost::filesystem::path boostPath(path);
135 if (!boost::filesystem::exists(boostPath))
137 BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path \"" << path << "\" does not exist";
141 if (!boost::filesystem::is_directory(boostPath))
143 BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path \"" << path << "\" is not a directory";
147 if (!boostPath.is_absolute())
149 BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path \"" << path << "\" is not absolute";
156 std::vector<std::string> DynamicBackendUtils::GetSharedObjects(const std::vector<std::string>& backendPaths)
158 std::unordered_set<std::string> uniqueSharedObjects;
159 std::vector<std::string> sharedObjects;
161 for (const std::string& backendPath : backendPaths)
163 using namespace boost::filesystem;
165 // Check if the path is valid. In case of error, IsValidPath will log an error message
166 if (!IsPathValid(backendPath))
171 // Get all the files in the current path in alphabetical order
172 std::vector<path> backendPathFiles;
173 std::copy(directory_iterator(backendPath), directory_iterator(), std::back_inserter(backendPathFiles));
174 std::sort(backendPathFiles.begin(), backendPathFiles.end());
176 // Go through all the files in the current backend path
177 for (const path& backendPathFile : backendPathFiles)
179 // Get only the name of the file (without the full path)
180 std::string filename = backendPathFile.filename().string();
182 if (filename.empty())
191 // Get the canonical path for the current file, it will throw if for example the file is a
192 // symlink that cannot be resolved
193 canonicalPath = canonical(backendPathFile);
195 catch (const filesystem_error& e)
197 BOOST_LOG_TRIVIAL(warning) << "GetSharedObjects warning: " << e.what();
199 if (canonicalPath.empty())
201 // No such file or perhaps a symlink that couldn't be resolved
205 // Check if the current filename matches the expected naming convention
206 // The expected format is: <vendor>_<name>_backend.so[<version>]
207 // e.g. "Arm_GpuAcc_backend.so" or "Arm_GpuAcc_backend.so.1.2"
208 const std::regex dynamicBackendRegex("^[a-zA-Z0-9]+_[a-zA-Z0-9]+_backend.so(\\.[0-9]+)*$");
210 bool filenameMatch = false;
213 // Match the filename to the expected naming scheme
214 filenameMatch = std::regex_match(filename, dynamicBackendRegex);
216 catch (const std::exception& e)
218 BOOST_LOG_TRIVIAL(warning) << "GetSharedObjects warning: " << e.what();
222 // Filename does not match the expected naming scheme (or an error has occurred)
226 // Append the valid canonical path to the output list only if it's not a duplicate
227 std::string validCanonicalPath = canonicalPath.string();
228 auto it = uniqueSharedObjects.find(validCanonicalPath);
229 if (it == uniqueSharedObjects.end())
231 // Not a duplicate, append the canonical path to the output list
232 sharedObjects.push_back(validCanonicalPath);
234 // Add the canonical path to the collection of unique shared objects
235 uniqueSharedObjects.insert(validCanonicalPath);
240 return sharedObjects;
243 std::vector<DynamicBackendPtr> DynamicBackendUtils::CreateDynamicBackends(const std::vector<std::string>& sharedObjects)
245 // Create a list of dynamic backends
246 std::vector<DynamicBackendPtr> dynamicBackends;
247 for (const std::string& sharedObject : sharedObjects)
249 // Create a handle to the shared object
250 void* sharedObjectHandle = nullptr;
253 sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObject);
255 catch (const RuntimeException& e)
257 BOOST_LOG_TRIVIAL(warning) << "Cannot create a handle to the shared object file \""
258 << sharedObject << "\": " << e.what();
261 if (!sharedObjectHandle)
263 BOOST_LOG_TRIVIAL(warning) << "Invalid handle to the shared object file \"" << sharedObject << "\"";
268 // Create a dynamic backend object
269 DynamicBackendPtr dynamicBackend;
272 dynamicBackend.reset(new DynamicBackend(sharedObjectHandle));
274 catch (const Exception& e)
276 BOOST_LOG_TRIVIAL(warning) << "Cannot create a valid dynamic backend from the shared object file \""
277 << sharedObject << "\": " << e.what();
282 BOOST_LOG_TRIVIAL(warning) << "Invalid dynamic backend object for the shared object file \""
283 << sharedObject << "\"";
288 // Append the newly created dynamic backend to the list
289 dynamicBackends.push_back(std::move(dynamicBackend));
292 return dynamicBackends;