ATLAS Offline Software
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 
23 namespace fs = std::filesystem;
24 
25 namespace {
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  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 #ifdef XAOD_STANDALONE
131  static thread_local asg::AsgMessaging asgMsg("PathResolver");
132 #else
133  static asg::AsgMessaging asgMsg ATLAS_THREAD_SAFE ("PathResolver");
134 #endif
135 #ifndef XAOD_ANALYSIS
138 #else
139  asgMsg.msg().setLevel(m_level);
140 #endif
141  return asgMsg;
142 }
143 
144 
148 bool PathResolver::PR_find( const std::string& logical_file_name, const std::string& search_list,
149  fs::file_type file_type, std::string& result ) {
150 
151  // expand filename before finding
152  TString tmpString(logical_file_name);
153  gSystem->ExpandPathName(tmpString);
154 
155  fs::path file(tmpString.Data());
156  fs::path locationToDownloadTo = "."; // will replace with first search location
157 
158  // First always search for filename as given in local directory
159  const std::string searchPath = std::format("./{}{}", path_separator, search_list);
160 
161  // iterate through search list
162  for (const auto& r : searchPath | std::views::split(path_separator)) {
163  std::string_view path(r.begin(), r.end());
164  const bool is_http = path.starts_with("http//");
165  if( (is_http || path.starts_with("https//")) &&
166  file_type==fs::file_type::regular && std::getenv("PATHRESOLVER_ALLOWHTTPDOWNLOAD") ) { // only http download files, not directories
167 
168  // Try to do an http download to the local location.
169  // Need to restore the proper http protocol (cannot use ":" in search paths)
170  const std::string fileToDownload = std::format("{}://{}/{}", is_http ? "http" : "https",
171  path.substr(6), file.string());
172 
173  const fs::path targetPath = locationToDownloadTo / file;
174  fs::path targetDir = targetPath;
175  targetDir.remove_filename();
176  msg(MSG::DEBUG) << "Attempting http download of " << fileToDownload << " to " << targetDir << endmsg;
177 
178  if (!is_directory(targetDir)) {
179  msg(MSG::DEBUG) << "Creating directory " << targetDir << endmsg;
180  if(!fs::create_directories(targetDir)) {
181  msg(MSG::ERROR) << "Unable to create directories to write file to " << targetDir << endmsg;
182  return false;
183  }
184  }
185 
186  if (!download_file(fileToDownload, targetPath, asgMsg())) {
187  msg(MSG::WARNING) << "Unable to download file " << fileToDownload << endmsg;
188  } else {
189  msg(MSG::DEBUG) << "Successfully downloaded " << fileToDownload << endmsg;
190  result = targetPath;
191  return true;
192  }
193 
194  } else if (locationToDownloadTo==".") {
195  // Prefer first non-pwd location (usually local build area) for downloading to.
196  fs::path dummyFile = fs::path(path) / "._pathresolver_dummy";
197  std::ofstream ofs(dummyFile); // check if writable
198  if (ofs.is_open()) {
199  locationToDownloadTo = path;
200  ofs.close();
201  fs::remove(dummyFile);
202  }
203  }
204 
205  fs::path fp = path / file;
206  try {
207  if (fs::status(fp).type() == file_type) {
208  result = fs::absolute(fp).string();
209  return true;
210  }
211  } catch (const fs::filesystem_error&) {
212  // file not accessible or does not exist
213  }
214 
215  }
216 
217  return false; // not found
218 }
219 
220 
221 std::string PathResolver::find_file(const std::string& logical_file_name,
222  const std::string& search_path) {
223 
224 #ifndef XAOD_ANALYSIS
225  if (logical_file_name.starts_with('/')) {
226  msg(MSG::ERROR) << "Use of an absolute file name: " << logical_file_name << endmsg;
227  }
228 #endif
229 
230  const char* path_list = std::getenv(search_path.c_str());
231  if (path_list == nullptr) {
232  msg(MSG::ERROR) << search_path << " environment variable not defined!" << endmsg;
233  return {};
234  }
235 
236  return find_file_from_list(logical_file_name, path_list);
237 }
238 
239 
240 std::string PathResolver::find_file_from_list (const std::string& logical_file_name,
241  const std::string& search_list)
242 {
243  std::string result;
244  PR_find (logical_file_name, search_list, fs::file_type::regular, result);
245 
246  return result;
247 }
248 
249 
250 std::string PathResolver::find_directory (const std::string& logical_file_name,
251  const std::string& search_path)
252 {
253  const char* path_list = std::getenv(search_path.c_str());
254  if(path_list == nullptr) {
255  msg(MSG::ERROR) << search_path << " environment variable not defined!" << endmsg;
256  return {};
257  }
258 
259  return find_directory_from_list(logical_file_name, path_list);
260 }
261 
262 
263 std::string PathResolver::find_directory_from_list (const std::string& logical_file_name,
264  const std::string& search_list)
265 {
266  std::string result;
267  PR_find(logical_file_name, search_list, fs::file_type::directory, result);
268 
269  return result;
270 }
271 
272 
273 std::string PathResolver::find_calib_file (const std::string& logical_file_name)
274 {
275  checkForDev(asgMsg(), logical_file_name);
276 
277  if (logical_file_name.starts_with("root://")) {
278  //xrootd access .. try to open file ...
279  std::unique_ptr<TFile> fTmp{TFile::Open(logical_file_name.c_str())};
280  if (!fTmp || fTmp->IsZombie()) {
281  msg(MSG::WARNING) << "Could not open " << logical_file_name << endmsg;
282  return {};
283  }
284  return logical_file_name;
285  }
286 
287  std::string out = PathResolver::find_file (logical_file_name, "CALIBPATH");
288  if (out.empty()) {
289  msg(MSG::WARNING) << "Could not locate " << logical_file_name << endmsg;
290  }
291  return out;
292 }
293 
294 
295 std::string PathResolver::find_calib_directory (const std::string& logical_file_name)
296 {
297  checkForDev(asgMsg(), logical_file_name);
298 
299  std::string out = PathResolver::find_directory (logical_file_name, "CALIBPATH");
300  if (out.empty()) {
301  msg(MSG::WARNING) << "Could not locate " << logical_file_name << endmsg;
302  }
303  return out;
304 }
305 
306 
308  m_level = level;
309 }
310 
311 std::string PathResolverFindXMLFile (const std::string& logical_file_name)
312 {
313  return PathResolver::find_file (logical_file_name, "XMLPATH");
314 }
315 
316 std::string PathResolverFindDataFile (const std::string& logical_file_name)
317 {
318  return PathResolver::find_file (logical_file_name, "DATAPATH");
319 }
320 
321 std::string PathResolverFindCalibFile (const std::string& logical_file_name) {
322  return PathResolver::find_calib_file(logical_file_name);
323 }
324 
325 
326 std::string PathResolverFindCalibDirectory (const std::string& logical_file_name) {
327  return PathResolver::find_calib_directory(logical_file_name);
328 }
329 
330 
333 }
PathResolver::find_calib_file
static std::string find_calib_file(const std::string &logical_file_name)
Definition: PathResolver.cxx:273
beamspotman.r
def r
Definition: beamspotman.py:672
PathResolver::asgMsg
static asg::AsgMessaging & asgMsg()
Definition: PathResolver.cxx:129
get_generator_info.result
result
Definition: get_generator_info.py:21
athena.path
path
python interpreter configuration --------------------------------------—
Definition: athena.py:128
vtune_athena.format
format
Definition: vtune_athena.py:14
PathResolver::find_directory
static std::string find_directory(const std::string &logical_file_name, const std::string &search_path)
Definition: PathResolver.cxx:250
pool_uuid.regular
bool regular
Definition: pool_uuid.py:103
PathResolver::find_calib_directory
static std::string find_calib_directory(const std::string &logical_file_name)
Definition: PathResolver.cxx:295
plotting.yearwise_luminosity.absolute
absolute
Definition: yearwise_luminosity.py:29
python.AthDsoLogger.out
out
Definition: AthDsoLogger.py:70
PathResolver::find_file_from_list
static std::string find_file_from_list(const std::string &logical_file_name, const std::string &search_list)
Definition: PathResolver.cxx:240
athena.value
value
Definition: athena.py:124
dbg::ptr
void * ptr(T *p)
Definition: SGImplSvc.cxx:74
physics_parameters.url
string url
Definition: physics_parameters.py:27
AthenaPoolTestWrite.stream
string stream
Definition: AthenaPoolTestWrite.py:12
python.CaloAddPedShiftConfig.type
type
Definition: CaloAddPedShiftConfig.py:42
python.iconfTool.models.loaders.level
level
Definition: loaders.py:20
PixelModuleFeMask_create_db.remove
string remove
Definition: PixelModuleFeMask_create_db.py:83
AthMessaging::setLevel
void setLevel(MSG::Level lvl)
Change the current logging level.
Definition: AthMessaging.cxx:28
python.setupRTTAlg.size
int size
Definition: setupRTTAlg.py:39
TrigConf::MSGTC::Level
Level
Definition: Trigger/TrigConfiguration/TrigConfBase/TrigConfBase/MsgStream.h:21
PathResolver::msg
static MsgStream & msg()
Definition: PathResolver.h:73
DeMoScan.directory
string directory
Definition: DeMoScan.py:78
PathResolver::PR_find
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.
Definition: PathResolver.cxx:148
trigmenu_modify_prescale_json.fp
fp
Definition: trigmenu_modify_prescale_json.py:53
endmsg
#define endmsg
Definition: AnalysisConfig_Ntuple.cxx:63
PathResolver::find_directory_from_list
static std::string find_directory_from_list(const std::string &logical_file_name, const std::string &search_list)
Definition: PathResolver.cxx:263
PathResolverFindXMLFile
std::string PathResolverFindXMLFile(const std::string &logical_file_name)
Definition: PathResolver.cxx:311
res
std::pair< std::vector< unsigned int >, bool > res
Definition: JetGroupProductTest.cxx:11
asg::AsgMessaging::msg
MsgStream & msg() const
The standard message stream.
Definition: AsgMessaging.cxx:49
file
TFile * file
Definition: tile_monitor.h:29
PathResolverFindDataFile
std::string PathResolverFindDataFile(const std::string &logical_file_name)
Definition: PathResolver.cxx:316
PathResolver::m_level
static std::atomic< MSG::Level > m_level
Definition: PathResolver.h:71
PathResolver.h
asg::AsgMessaging
Class mimicking the AthMessaging class from the offline software.
Definition: AsgMessaging.h:40
PathResolverSetOutputLevel
void PathResolverSetOutputLevel(int lvl)
Definition: PathResolver.cxx:331
PathResolverFindCalibDirectory
std::string PathResolverFindCalibDirectory(const std::string &logical_file_name)
Definition: PathResolver.cxx:326
SCT_ConditionsAlgorithms::CoveritySafe::getenv
std::string getenv(const std::string &variableName)
get an environment variable
Definition: SCT_ConditionsUtilities.cxx:17
python.Constants.INFO
int INFO
Definition: Control/AthenaCommon/python/Constants.py:15
athena.path_list
list path_list
Definition: athena.py:93
PathResolver::find_file
static std::string find_file(const std::string &logical_file_name, const std::string &search_path)
Definition: PathResolver.cxx:221
DEBUG
#define DEBUG
Definition: page_access.h:11
Herwig7_QED_EvtGen_ll.fs
dictionary fs
Definition: Herwig7_QED_EvtGen_ll.py:17
PathResolverFindCalibFile
std::string PathResolverFindCalibFile(const std::string &logical_file_name)
Definition: PathResolver.cxx:321
PathResolver::setOutputLevel
static void setOutputLevel(MSG::Level level)
Definition: PathResolver.cxx:307
merge.status
status
Definition: merge.py:16
ATLAS_THREAD_SAFE
#define ATLAS_THREAD_SAFE
Definition: checker_macros.h:211
python.DataFormatRates.env
env
Definition: DataFormatRates.py:32
checker_macros.h
Define macros for attributes used to control the static checker.
Trk::split
@ split
Definition: LayerMaterialProperties.h:38
python.SystemOfUnits.L
float L
Definition: SystemOfUnits.py:92