ATLAS Offline Software
Loading...
Searching...
No Matches
PathResolver.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
7
8#include <cstdlib>
9#include <format>
10#include <fstream>
11#include <ranges>
12#include <stdexcept>
13#include <string_view>
14#include <memory>
15#include <mutex>
16
17#include <curl/curl.h>
18
19#include "TFile.h"
20#include "TSystem.h"
21
22
23namespace fs = std::filesystem;
24
25namespace {
26 const char path_separator = ':'; // Linux and MacOS
27 const char* const pathResolverEnvVar = "PATHRESOLVER_DEVAREARESPONSE";
28
29 // Callback function to write received data into an ofstream
30 size_t write_data(void* ptr, size_t size, size_t nmemb, void* userdata) {
31 std::ostream* stream = static_cast<std::ostream*>(userdata);
32 size_t total_size = size * nmemb;
33 stream->write(static_cast<char*>(ptr), total_size);
34 return total_size;
35 }
36 bool download_file(const std::string& url, const std::string& output_path,
37 asg::AsgMessaging& asgmsg)
38 {
39
40 // We intentionally skip curl_global_cleanup to avoid races in
41 // multithreaded use. This leaks a small amount of global state,
42 // but the OS reclaims it at process exit.
43 static std::once_flag curl_setup;
44 std::call_once(curl_setup, curl_global_init, CURL_GLOBAL_DEFAULT);
45
46 using owner_t = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>;
47 owner_t curl_owner(curl_easy_init(), curl_easy_cleanup);
48
49 auto* curl = curl_owner.get();
50
51 if (!curl) {
52 asgmsg.msg(MSG::WARNING) << "unable to setup curl" << endmsg;
53 return false;
54 }
55
56 std::ofstream file(output_path, std::ios::binary);
57 if (!file.is_open()) {
58 asgmsg.msg(MSG::WARNING) << "unable to open " << output_path << endmsg;
59 return false;
60 }
61 auto setCurlOption =[curl](auto option, const auto &value)->bool{
62 CURLcode ret = curl_easy_setopt(curl, option, value);
63 return (ret == CURLE_OK);
64 };
65 bool setupOk = setCurlOption(CURLOPT_TIMEOUT, 60L);
66 setupOk &= setCurlOption(CURLOPT_URL, url.c_str());
67 setupOk &= setCurlOption(CURLOPT_WRITEFUNCTION, write_data);
68 setupOk &= setCurlOption(CURLOPT_WRITEDATA, &file);
69 // Optional: follow redirects
70 setupOk &= setCurlOption(CURLOPT_FOLLOWLOCATION, 1L);
71 if (not setupOk) {
72 asgmsg.msg(MSG::WARNING) << "curl setup failed in PathResolver." <<endmsg;
73 return false;
74 }
75 CURLcode res = curl_easy_perform(curl);
76 if (res != CURLE_OK) {
77 asgmsg.msg(MSG::WARNING) << "error downloading file: "
78 << curl_easy_strerror(res)
79 << " (" << res << ")" << endmsg;
80 return false;
81 }
82
83 return true;
84 }
85
87 void checkForDev(asg::AsgMessaging& asgmsg,
88 const std::string& logical_file_name) {
89
90 if (asgmsg.msgLvl(MSG::DEBUG)) asgmsg.msg(MSG::DEBUG) << "Trying to locate " << logical_file_name << endmsg;
91
92 if (logical_file_name.starts_with("dev/")) {
93 const char* env = std::getenv(pathResolverEnvVar);
94 const std::string dev_area_response = env ? env : "DEFAULT";
95
96 MSG::Level level{};
97 if (dev_area_response == "SILENT") {
98 return;
99 }
100 else if (dev_area_response == "THROW") {
101 throw std::runtime_error(
102 "Loading dev area file " + logical_file_name + " is not allowed! "
103 "To override this error set the environment variable " +
104 pathResolverEnvVar + " to SILENT, DEFAULT, INFO, WARNING or ERROR");
105 }
106 else if (dev_area_response == "DEFAULT") {
107 #ifdef XAOD_ANALYSIS
108 level = MSG::WARNING;
109 #else
110 level = MSG::ERROR;
111 #endif
112 }
113 else if (dev_area_response == "INFO") level = MSG::INFO;
114 else if (dev_area_response == "WARNING") level = MSG::WARNING;
115 else if (dev_area_response == "ERROR") level = MSG::ERROR;
116 else {
117 throw std::runtime_error(std::format("{} set to '{}', not sure what to do. "
118 "Options are DEFAULT, THROW, INFO, WARNING, ERROR or SILENT",
119 pathResolverEnvVar, dev_area_response));
120 }
121
122 // Print message at appropriate level
123 asgmsg.msg(level) << "Locating dev file " << logical_file_name << ". Do not let this propagate to a release!" << endmsg;
124 }
125 }
126}
127
128
130
131#ifdef XAOD_STANDALONE
132 static thread_local asg::AsgMessaging asgMsg("PathResolver");
133#else
134 static asg::AsgMessaging asgMsg ATLAS_THREAD_SAFE ("PathResolver");
135#endif
136
137 // Set default OutputLevel unless user already set one
138 if (m_level==MSG::NIL) setOutputLevel(MSG::INFO);
139
140 return asgMsg;
141}
142
143
147bool PathResolver::PR_find( const std::string& logical_file_name, const std::string& search_list,
148 fs::file_type file_type, std::string& result ) {
149
150 // expand filename before finding
151 TString tmpString(logical_file_name);
152 gSystem->ExpandPathName(tmpString);
153
154 fs::path file(tmpString.Data());
155 fs::path locationToDownloadTo = "."; // will replace with first search location
156
157 // First always search for filename as given in local directory
158 const std::string searchPath = std::format("./{}{}", path_separator, search_list);
159
160 // iterate through search list
161 for (const auto r : searchPath | std::views::split(path_separator)) {
162 std::string_view path(r.begin(), r.end());
163 const bool is_http = path.starts_with("http//");
164 if( (is_http || path.starts_with("https//")) &&
165 file_type==fs::file_type::regular && std::getenv("PATHRESOLVER_ALLOWHTTPDOWNLOAD") ) { // only http download files, not directories
166
167 // Try to do an http download to the local location.
168 // Need to restore the proper http protocol (cannot use ":" in search paths)
169 const std::string fileToDownload = std::format("{}://{}/{}", is_http ? "http" : "https",
170 path.substr(6), file.string());
171
172 const fs::path targetPath = locationToDownloadTo / file;
173 fs::path targetDir = targetPath;
174 targetDir.remove_filename();
175 if (msgLvl(MSG::DEBUG)) msg(MSG::DEBUG) << "Attempting http download of " << fileToDownload << " to " << targetDir << endmsg;
176
177 if (!is_directory(targetDir)) {
178 if (msgLvl(MSG::DEBUG)) msg(MSG::DEBUG) << "Creating directory " << targetDir << endmsg;
179 if(!fs::create_directories(targetDir)) {
180 msg(MSG::ERROR) << "Unable to create directories to write file to " << targetDir << endmsg;
181 return false;
182 }
183 }
184
185 if (!download_file(fileToDownload, targetPath, asgMsg())) {
186 msg(MSG::WARNING) << "Unable to download file " << fileToDownload << endmsg;
187 } else {
188 if (msgLvl(MSG::DEBUG)) msg(MSG::DEBUG) << "Successfully downloaded " << fileToDownload << endmsg;
189 result = targetPath;
190 return true;
191 }
192
193 } else if (locationToDownloadTo==".") {
194 // Prefer first non-pwd location (usually local build area) for downloading to.
195 fs::path dummyFile = fs::path(path) / "._pathresolver_dummy";
196 std::ofstream ofs(dummyFile); // check if writable
197 if (ofs.is_open()) {
198 locationToDownloadTo = path;
199 ofs.close();
200 fs::remove(dummyFile);
201 }
202 }
203
204 fs::path fp = path / file;
205 try {
206 if (fs::status(fp).type() == file_type) {
207 result = fs::absolute(fp).string();
208 return true;
209 }
210 } catch (const fs::filesystem_error&) {
211 // file not accessible or does not exist
212 }
213
214 }
215
216 return false; // not found
217}
218
219
220std::string PathResolver::find_file(const std::string& logical_file_name,
221 const std::string& search_path) {
222
223#ifndef XAOD_ANALYSIS
224 if (logical_file_name.starts_with('/')) {
225 msg(MSG::ERROR) << "Use of an absolute file name: " << logical_file_name << endmsg;
226 }
227#endif
228
229 const char* path_list = std::getenv(search_path.c_str());
230 if (path_list == nullptr) {
231 msg(MSG::ERROR) << search_path << " environment variable not defined!" << endmsg;
232 return {};
233 }
234
235 return find_file_from_list(logical_file_name, path_list);
236}
237
238
239std::string PathResolver::find_file_from_list (const std::string& logical_file_name,
240 const std::string& search_list)
241{
242 std::string result;
243 PR_find (logical_file_name, search_list, fs::file_type::regular, result);
244
245 return result;
246}
247
248
249std::string PathResolver::find_directory (const std::string& logical_file_name,
250 const std::string& search_path)
251{
252 const char* path_list = std::getenv(search_path.c_str());
253 if(path_list == nullptr) {
254 msg(MSG::ERROR) << search_path << " environment variable not defined!" << endmsg;
255 return {};
256 }
257
258 return find_directory_from_list(logical_file_name, path_list);
259}
260
261
262std::string PathResolver::find_directory_from_list (const std::string& logical_file_name,
263 const std::string& search_list)
264{
265 std::string result;
266 PR_find(logical_file_name, search_list, fs::file_type::directory, result);
267
268 return result;
269}
270
271
272std::string PathResolver::find_calib_file (const std::string& logical_file_name)
273{
274 checkForDev(asgMsg(), logical_file_name);
275
276 if (logical_file_name.starts_with("root://")) {
277 //xrootd access .. try to open file ...
278 std::unique_ptr<TFile> fTmp{TFile::Open(logical_file_name.c_str())};
279 if (!fTmp || fTmp->IsZombie()) {
280 msg(MSG::WARNING) << "Could not open " << logical_file_name << endmsg;
281 return {};
282 }
283 return logical_file_name;
284 }
285
286 std::string out = PathResolver::find_file (logical_file_name, "CALIBPATH");
287 if (out.empty()) {
288 msg(MSG::WARNING) << "Could not locate " << logical_file_name << endmsg;
289 }
290 return out;
291}
292
293
294std::string PathResolver::find_calib_directory (const std::string& logical_file_name)
295{
296 checkForDev(asgMsg(), logical_file_name);
297
298 std::string out = PathResolver::find_directory (logical_file_name, "CALIBPATH");
299 if (out.empty()) {
300 msg(MSG::WARNING) << "Could not locate " << logical_file_name << endmsg;
301 }
302 return out;
303}
304
305
306void PathResolver::setOutputLevel(MSG::Level level) {
307 m_level = level;
308#ifndef XAOD_ANALYSIS
310#else
311 asgMsg().msg().setLevel(m_level);
312#endif
313}
314
315std::string PathResolverFindXMLFile (const std::string& logical_file_name)
316{
317 return PathResolver::find_file (logical_file_name, "XMLPATH");
318}
319
320std::string PathResolverFindDataFile (const std::string& logical_file_name)
321{
322 return PathResolver::find_file (logical_file_name, "DATAPATH");
323}
324
325std::string PathResolverFindCalibFile (const std::string& logical_file_name) {
326 return PathResolver::find_calib_file(logical_file_name);
327}
328
329
330std::string PathResolverFindCalibDirectory (const std::string& logical_file_name) {
331 return PathResolver::find_calib_directory(logical_file_name);
332}
333
334
336 PathResolver::setOutputLevel(MSG::Level(lvl));
337}
#define endmsg
std::pair< std::vector< unsigned int >, bool > res
static Double_t fs
std::string PathResolverFindXMLFile(const std::string &logical_file_name)
std::string PathResolverFindCalibDirectory(const std::string &logical_file_name)
std::string PathResolverFindCalibFile(const std::string &logical_file_name)
std::string PathResolverFindDataFile(const std::string &logical_file_name)
void PathResolverSetOutputLevel(int lvl)
Define macros for attributes used to control the static checker.
#define ATLAS_THREAD_SAFE
void setLevel(MSG::Level lvl)
Change the current logging level.
static std::string find_directory_from_list(const std::string &logical_file_name, const std::string &search_list)
static void setOutputLevel(MSG::Level level)
static bool msgLvl(const MSG::Level lvl)
static bool PR_find(const std::string &logical_file_name, const std::string &search_list, std::filesystem::file_type file_type, std::string &result)
Main private search method used by all public methods.
static std::atomic< MSG::Level > m_level
static std::string find_calib_file(const std::string &logical_file_name)
static std::string find_directory(const std::string &logical_file_name, const std::string &search_path)
static std::string find_file(const std::string &logical_file_name, const std::string &search_path)
static MsgStream & msg()
static std::string find_file_from_list(const std::string &logical_file_name, const std::string &search_list)
static std::string find_calib_directory(const std::string &logical_file_name)
static asg::AsgMessaging & asgMsg()
Class mimicking the AthMessaging class from the offline software.
MsgStream & msg() const
The standard message stream.
bool msgLvl(const MSG::Level lvl) const
Test the output level of the object.
int r
Definition globals.cxx:22
TFile * file