XRootD
XrdClS3Filesystem.cc
Go to the documentation of this file.
1 /******************************************************************************/
2 /* Copyright (C) 2025, Pelican Project, Morgridge Institute for Research */
3 /* */
4 /* This file is part of the XrdClS3 client plugin for XRootD. */
5 /* */
6 /* XRootD is free software: you can redistribute it and/or modify it under */
7 /* the terms of the GNU Lesser General Public License as published by the */
8 /* Free Software Foundation, either version 3 of the License, or (at your */
9 /* option) any later version. */
10 /* */
11 /* XRootD is distributed in the hope that it will be useful, but WITHOUT */
12 /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
13 /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
14 /* License for more details. */
15 /* */
16 /* The copyright holder's institutional names and contributor's names may not */
17 /* be used to endorse or promote products derived from this software without */
18 /* specific prior written permission of the institution or contributor. */
19 /******************************************************************************/
20 
22 #include "XrdClS3Factory.hh"
23 #include "XrdClS3Filesystem.hh"
24 
25 #include <tinyxml.h>
26 #include <XrdCl/XrdClURL.hh>
27 #include <XrdCl/XrdClLog.hh>
28 
29 #include <charconv>
30 
31 using namespace XrdClS3;
32 
33 namespace {
34 
35 // Helper function to URL-quote a string.
36 std::string urlquote(const std::string input) {
37  std::string output;
38  output.reserve(3 * input.size());
39  for (char val : input) {
40  if ((val >= 48 && val <= 57) || // Digits 0-9
41  (val >= 65 && val <= 90) || // Uppercase A-Z
42  (val >= 97 && val <= 122) || // Lowercase a-z
43  (val == 95 || val == 46 || val == 45 || val == 126 ||
44  val == 47)) // '_.-~/'
45  {
46  output += val;
47  } else {
48  output += "%" + std::to_string(val);
49  }
50  }
51  return output;
52 }
53 
54 // Helper function for joining two URLs without introducing a double '/'
55 std::string JoinUrl(const std::string & base, const std::string & path) {
56  std::string result = base;
57  if (!base.empty() && base[base.size()-1] == '/') {
58  size_t idx = 0;
59  while (idx < path.size() && path[idx] == '/') idx++;
60  result.append(path.data() + idx, path.size() - idx);
61  } else {
62  result += path;
63  }
64  return result;
65 }
66 
67 class StatHandler : public XrdCl::ResponseHandler {
68 public:
69  StatHandler(const std::string &path, const std::string &s3_url, XrdClHttp::HeaderCallout *header_callout, XrdCl::ResponseHandler *handler, time_t timeout, XrdCl::Log &log) :
70  m_timeout(timeout),
71  m_handler(handler),
72  m_header_callout(header_callout),
73  m_path(path),
74  m_s3_url(s3_url),
75  m_logger(log)
76  {}
77 
78  virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) override;
79 
80 private:
81  time_t m_timeout;
82  XrdCl::ResponseHandler *m_handler{nullptr};
83  XrdClHttp::HeaderCallout *m_header_callout{nullptr};
84  std::string m_path;
85  std::string m_s3_url;
86  XrdCl::Log &m_logger;
87 };
88 
89 // If the stat request returns a "file not found" error, then there is definitely not
90 // an object at the given path. However, it could be a directory, so we
91 // issue a directory listing request to see if it is a directory.
92 // This is the response handler for that directory listing request.
93 class StatHandlerDirectory : public XrdCl::ResponseHandler {
94 public:
95  StatHandlerDirectory(XrdCl::ResponseHandler *handler) :
96  m_handler(handler)
97  {}
98 
99  virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) override;
100 
101 private:
102  XrdCl::ResponseHandler *m_handler{nullptr};
103 };
104 
105 // Response handler for the S3 directory listing GET operation.
106 class DirListResponseHandler : public XrdCl::ResponseHandler {
107 public:
108  DirListResponseHandler(bool existence_check, const std::string &url, XrdClHttp::HeaderCallout *header_callout, XrdCl::ResponseHandler *handler, time_t expiry, XrdCl::Log &log) :
109  m_existence_check(existence_check),
110  m_expiry(expiry),
111  m_header_callout(header_callout),
112  m_url(url),
113  m_host(Factory::ExtractHostname(url)),
114  m_handler(handler),
115  m_logger(log)
116  {}
117 
118  virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) override;
119 
120 private:
121  // Sometimes we are simply looking to see if a "directory" exists; in such a case, we
122  // don't need to enumerate all the entries in the bucket and can exit early after the first subdir
123  // or file is found
124  bool m_existence_check;
125 
126  time_t m_expiry; // Expiration time for the directory listing request
127  XrdClHttp::HeaderCallout *m_header_callout{nullptr}; // Header callout for S3 signing
128  std::string m_url; // The URL of the S3 directory listing
129  std::string m_host; // The host address of the S3 endpoint
130 
131  std::unique_ptr<XrdCl::DirectoryList> dirlist{new XrdCl::DirectoryList()}; // Directory listing object to hold the results
132 
133  XrdCl::ResponseHandler *m_handler{nullptr};
134  XrdCl::Log &m_logger;
135 };
136 
137 // Handle the creation of a zero-sized file that indicates a "directory"
138 class MkdirHandler : public XrdCl::ResponseHandler {
139 public:
140  MkdirHandler(XrdCl::File *file, XrdCl::ResponseHandler *handler, time_t timeout) :
141  m_expiry(time(NULL) + (timeout ? timeout : 30)),
142  m_file(file),
143  m_handler(handler)
144  {}
145 
146  virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) override;
147 
148 private:
149  time_t m_expiry;
150  bool m_started_close{false};
151  std::unique_ptr<XrdCl::File> m_file;
152  XrdCl::ResponseHandler *m_handler{nullptr};
153 };
154 
155 void
156 StatHandler::HandleResponse(XrdCl::XRootDStatus *status_raw, XrdCl::AnyObject *response_raw) {
157  std::unique_ptr<StatHandler> self(this);
158  std::unique_ptr<XrdCl::AnyObject> response_holder(response_raw);
159  std::unique_ptr<XrdCl::XRootDStatus> status(status_raw);
160 
161  if (!status) {
162  if (m_handler) {return m_handler->HandleResponse(status.release(), response_holder.release());}
163  else return;
164  }
165 
166  if (status->IsOK() || status->errNo != kXR_NotFound) {
167  if (m_handler) {return m_handler->HandleResponse(status.release(), response_holder.release());}
168  else return;
169  }
170 
171  // We got a "file not found" type of response. In this case, we could interpret
172  // this as a directory.
173  std::string https_url, err_msg;
174  const auto s3_url = JoinUrl(m_s3_url, m_path);
175  std::string obj;
176  if (!Factory::GenerateHttpUrl(s3_url, https_url, &obj, err_msg)) {
177  if (m_handler) return m_handler->HandleResponse(new XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidAddr, 0, err_msg), nullptr);
178  else return;
179  }
180  obj = obj.substr(0, obj.find('?'));
181  auto query_loc = https_url.find('?');
182  https_url += (query_loc == std::string::npos) ? "?" : "&";
183  https_url += "list-type=2&delimiter=/&encoding-type=url";
184  https_url += "&prefix=" + urlquote(obj) + "/";
185 
186  auto expiry = time(NULL) + m_timeout;
187 
188  auto st = DownloadUrl(
189  https_url,
190  m_header_callout,
191  new DirListResponseHandler(
192  true, https_url, m_header_callout, new StatHandlerDirectory(m_handler), expiry, m_logger
193  ),
194  m_timeout
195  );
196  if (!st.IsOK()) {
197  if (m_handler) return m_handler->HandleResponse(new XrdCl::XRootDStatus(st), response_holder.release());
198  else return;
199  }
200 }
201 
202 void
203 StatHandlerDirectory::HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) {
204  std::unique_ptr<StatHandlerDirectory> self(this);
205  if (!m_handler) {
206  delete response;
207  delete status;
208  return;
209  }
210  if (!status || !status->IsOK()) {
211  return m_handler->HandleResponse(status, response);
212  }
213  auto stat_info = new XrdCl::StatInfo("nobody", 0, XrdCl::StatInfo::IsDir, 0);
214  auto obj = new XrdCl::AnyObject();
215  obj->Set(stat_info);
216  delete response;
217  m_handler->HandleResponse(status, obj);
218 }
219 
220 void
221 DirListResponseHandler::HandleResponse(XrdCl::XRootDStatus *status_raw, XrdCl::AnyObject *response_raw) {
222  std::unique_ptr<DirListResponseHandler> self(this);
223  std::unique_ptr<XrdCl::AnyObject> response(response_raw);
224  std::unique_ptr<XrdCl::XRootDStatus> status(status_raw);
225  if (!m_handler) {
226  return;
227  }
228  if (!status || !status->IsOK()) {
229  return m_handler->HandleResponse(status.release(), response.release());
230  }
231 
232  if (!response) {
233  m_logger.Error(kLogXrdClS3, "Directory listing returned without any response object.");
234  return m_handler->HandleResponse(new XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidResponse, 0, "No response object provided"), nullptr);
235  }
236 
237  XrdCl::Buffer *buffer = nullptr;
238  response->Get(buffer);
239  if (!buffer) {
240  m_logger.Error(kLogXrdClS3, "Directory listing response object was not a buffer.");
241  return m_handler->HandleResponse(new XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidResponse, 0, "No buffer in response object"), nullptr);
242  }
243 
244  // Parse the XML response from the S3 service
245  TiXmlDocument doc;
246  std::string buffer_str(buffer->GetBuffer(), buffer->GetSize());
247  doc.Parse(buffer_str.c_str());
248  if (doc.Error()) {
249  std::string errMsg = "Error when parsing S3 endpoint's listing response: " + std::string(doc.ErrorDesc());
250  m_handler->HandleResponse(new XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidResponse, 0, errMsg), nullptr);
251  return;
252  }
253 
254  auto elem = doc.RootElement();
255  if (strcmp(elem->Value(), "ListBucketResult")) {
256  m_handler->HandleResponse(new XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidResponse, 0,
257  "S3 ListBucket response is not rooted with ListBucketResult element"), nullptr);
258  return;
259  }
260 
261  // Example response from S3:
262  // <?xml version="1.0" encoding="utf-8"?>
263  // <ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
264  // <Name>genome-browser</Name>
265  // <Prefix>cells/muscle-ibm/endothelial-stromal-cells</Prefix>
266  // <KeyCount>40</KeyCount>
267  // <MaxKeys>40</MaxKeys>
268  // <NextContinuationToken>1PnsptbFFpBSb6UBNN4F/RrxtBvIHjNpdXNYlX8E7IyqXRK26w2y36KViUAbyPPsjzikVY0Zj4jMvQHRhsGWZbcKKrEVvaR0HaZDtfUXUwnc=</NextContinuationToken>
269  // <IsTruncated>false</IsTruncated>
270  // <Contents>
271  // <Key>cells/muscle-ibm/endothelial-stromal-cells/UMAP.coords.tsv.gz</Key>
272  // <LastModified>2023-08-21T11:02:53.000Z</LastModified>
273  // <ETag>"b9b0065f10cbd91c9d341acc235c63b0"</ETag>
274  // <Size>360012</Size>
275  // <StorageClass>STANDARD</StorageClass>
276  // </Contents>
277  // <Contents>
278  // <Key>cells/muscle-ibm/endothelial-stromal-cells/barcodes.tsv.gz</Key>
279  // <LastModified>2023-07-17T11:02:19.000Z</LastModified>
280  // <ETag>"048feef5d340e2dd4d2d2d495c24ad7e"</ETag>
281  // <Size>118061</Size>
282  // <StorageClass>STANDARD</StorageClass>
283  // </Contents>
284  // ... (truncated some entries for readability) ...
285  // <CommonPrefixes>
286  // <Prefix>cells/muscle-ibm/endothelial-stromal-cells/coords/</Prefix>
287  // </CommonPrefixes>
288  // <CommonPrefixes>
289  // <Prefix>cells/muscle-ibm/endothelial-stromal-cells/markers/</Prefix>
290  // </CommonPrefixes>
291  // <CommonPrefixes>
292  // <Prefix>cells/muscle-ibm/endothelial-stromal-cells/metaFields/</Prefix>
293  // </CommonPrefixes>
294  // </ListBucketResult>
295  bool isTruncated = false;
296  std::string ct;
297  bool found_sentinel = false;
298  for (auto child = elem->FirstChildElement(); child != nullptr;
299  child = child->NextSiblingElement()) {
300  if (!strcmp(child->Value(), "IsTruncated")) {
301  auto text = child->GetText();
302  if (!strcasecmp(text, "true")) {
303  isTruncated = true;
304  } else if (!strcasecmp(text, "false")) {
305  isTruncated = false;
306  }
307  } else if (!strcmp(child->Value(), "CommonPrefixes")) {
308  auto prefix = child->FirstChildElement("Prefix");
309  if (prefix != nullptr) {
310  auto prefixChar = prefix->GetText();
311  if (prefixChar != nullptr) {
312  auto prefixStr = std::string_view(prefixChar);
313  Factory::TrimView(prefixStr);
314  if (!prefixStr.empty()) {
315  if (prefixStr[prefixStr.size() - 1] == '/') prefixStr = prefixStr.substr(0, prefixStr.size() - 1);
316  uint32_t flags = XrdCl::StatInfo::Flags::IsReadable |
317  XrdCl::StatInfo::Flags::IsDir |
318  XrdCl::StatInfo::Flags::XBitSet;
319  dirlist->Add(
321  m_host, std::string(prefixStr), new XrdCl::StatInfo(
322  "nobody", 4096, flags, 0)));
323  }
324  }
325  }
326  } else if (!strcmp(child->Value(), "Contents")) {
327  std::string_view keyStr;
328  int64_t size = -1;
329  bool goodSize = false;
330  auto key = child->FirstChildElement("Key");
331  if (key != nullptr) {
332  auto keyChar = key->GetText();
333  if (keyChar != nullptr) {
334  keyStr = Factory::TrimView(keyChar);
335  }
336  }
337  auto last_slash = keyStr.rfind('/');
338  if (last_slash != std::string_view::npos) {
339  if (!Factory::GetMkdirSentinel().empty() && (keyStr.substr(last_slash) == Factory::GetMkdirSentinel())) {
340  found_sentinel = true;
341  if (m_existence_check) break;
342  else continue;
343  }
344  }
345  auto sizeElem = child->FirstChildElement("Size");
346  if (sizeElem != nullptr) {
347  auto sizeChar = sizeElem->GetText();
348  if (sizeChar != nullptr && *sizeChar) {
349  auto res = std::from_chars(sizeChar, sizeChar + strlen(sizeChar), size);
350  if (res.ec == std::errc()) {
351  goodSize = true;
352  }
353  }
354  }
355  auto lastModifiedElem = child->FirstChildElement("LastModified");
356  time_t lastModified = 0;
357  if (lastModifiedElem != nullptr) {
358  auto lastModifiedChar = lastModifiedElem->GetText();
359  if (lastModifiedChar != nullptr) {
360  struct tm tm;
361  // Example format: "2023-08-21T11:02:53.000Z"
362  if (strptime(lastModifiedChar, "%Y-%m-%dT%H:%M:%S", &tm) != nullptr) {
363  tm.tm_isdst = -1;
364  lastModified = mktime(&tm);
365  }
366  }
367  }
368  if (goodSize && !keyStr.empty()) {
369  uint32_t flags = XrdCl::StatInfo::Flags::IsReadable;
370  dirlist->Add(
372  m_host, std::string(keyStr), new XrdCl::StatInfo(
373  "nobody", size, flags, lastModified)));
374  }
375  } else if (!strcmp(child->Value(), "NextContinuationToken")) {
376  auto ctChar = child->GetText();
377  if (ctChar) {
378  ct = Factory::TrimView(ctChar);
379  }
380  }
381  }
382  // - !isTruncated indicates all object listings have been consumed.
383  // - If m_existence_check mode is set, then the caller only cares to know that this is a
384  // directory; as soon as the directory has any "contents", then it officially exists and
385  // we can return.
386  if (!isTruncated || (m_existence_check && (dirlist->GetSize() || found_sentinel))) {
387  // We interpret an "empty directory" as not existing if there's no sentinel object.
388  if (!found_sentinel && !dirlist->GetSize()) {
389  m_handler->HandleResponse(
391  nullptr
392  );
393  return;
394  }
395  auto object = new XrdCl::AnyObject();
396  object->Set(dirlist.release());
397  m_handler->HandleResponse(
398  new XrdCl::XRootDStatus{},
399  object
400  );
401  return;
402  }
403 
404  auto url = m_url + "&continuation-token=" + urlquote(ct);
405 
406  // Calculate the timeout based on the current time and the expiry time
407  time_t now = time(NULL);
408  if (now >= m_expiry) {
409  m_handler->HandleResponse(
410  new XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errOperationExpired, 0, "Request timed out"),
411  nullptr
412  );
413 
414  }
415 
416  auto st = DownloadUrl(url, m_header_callout, this, m_expiry - now);
417  if (!st.IsOK()) {
418  m_handler->HandleResponse(new XrdCl::XRootDStatus(st), nullptr);
419  return;
420  }
421 }
422 
423 void
424 MkdirHandler::HandleResponse(XrdCl::XRootDStatus *status_raw, XrdCl::AnyObject *response_raw)
425 {
426  std::unique_ptr<MkdirHandler> self(this);
427  std::unique_ptr<XrdCl::XRootDStatus> status(status_raw);
428  std::unique_ptr<XrdCl::AnyObject> response(response_raw);
429 
430  if (!status || !status->IsOK() || m_started_close) {
431  if (m_handler) m_handler->HandleResponse(status.release(), response.release());
432  return;
433  }
434 
435  time_t now = time(NULL);
436  if (now >= m_expiry) {
437  m_handler->HandleResponse(
438  new XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errOperationExpired, 0, "Request timed out"),
439  nullptr
440  );
441  }
442 
443  self.release();
444  m_started_close = true;
445  auto st = m_file->Close(this, m_expiry - now);
446  if (!st.IsOK()) {
447  if (m_handler) m_handler->HandleResponse(status.release(), response.release());
448  return;
449  }
450 }
451 
452 
453 } // namespace
454 
455 Filesystem::Filesystem(const std::string &url, XrdCl::Log *log) :
456  m_logger(log),
457  m_url(url)
458 {
459  m_url.SetPath("");
461  m_url.SetParams(map);
462 
463  m_logger->Debug(kLogXrdClS3, "S3 filesystem constructed with URL: %s.",
464  m_url.GetURL().c_str());
465 }
466 
468 
470 Filesystem::DirList(const std::string &path,
472  XrdCl::ResponseHandler *handler,
473  time_t timeout)
474 {
475  std::string https_url, err_msg;
476  const auto s3_url = JoinUrl(m_url.GetURL(), path);
477  std::string obj;
478  if (!Factory::GenerateHttpUrl(s3_url, https_url, &obj, err_msg)) {
480  }
481  obj = obj.substr(0, obj.find('?'));
482  auto query_loc = https_url.find('?');
483  https_url += (query_loc == std::string::npos) ? "?" : "&";
484  https_url += "list-type=2&delimiter=/&encoding-type=url";
485  https_url += "&prefix=" + urlquote(obj) + "/";
486 
487  auto expiry = time(NULL) + timeout;
488 
489  return DownloadUrl(
490  https_url,
491  &m_header_callout,
492  new DirListResponseHandler(
493  false, https_url, &m_header_callout, handler, expiry, *m_logger
494  ),
495  timeout
496  );
497 }
498 
499 std::pair<XrdCl::XRootDStatus, XrdCl::FileSystem*>
500 Filesystem::GetFSHandle(const std::string &path) {
501  const auto s3_url = JoinUrl(m_url.GetURL(), path);
502  std::string https_url, err_msg;
503  if (!Factory::GenerateHttpUrl(s3_url, https_url, nullptr, err_msg)) {
504  return std::make_pair(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidAddr, 0, err_msg), nullptr);
505  }
506  auto loc = https_url.find('/', 8); // strlen("https://") -> 8
507  if (loc == std::string::npos) {
508  return std::make_pair(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidAddr, 0, "Invalid generated URL"), nullptr);
509  }
510  auto endpoint = https_url.substr(0, loc);
511  {
512  std::shared_lock lock(m_handles_mutex);
513  auto iter = m_handles.find(endpoint);
514  if (iter != m_handles.end()) {
515  return std::make_pair(XrdCl::XRootDStatus{}, iter->second);
516  }
517  }
518  XrdCl::URL url;
519  if (!url.FromString(https_url)) {
520  return std::make_pair(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidAddr, 0, "Invalid generated XrdCl URL"), nullptr);
521  }
522  std::unique_lock lock(m_handles_mutex);
523  auto iter = m_handles.find(endpoint);
524  if (iter != m_handles.end()) {
525  return std::make_pair(XrdCl::XRootDStatus{}, iter->second);
526  }
527  auto fs = new XrdCl::FileSystem(url);
528  std::stringstream ss;
529  ss << std::hex << reinterpret_cast<long long>(&m_header_callout);
530  if (!fs->SetProperty("XrdClHttpHeaderCallout", ss.str())) {
531  delete fs;
532  return std::make_pair(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidAddr, 0, "Failed to setup header callout"), nullptr);
533  }
534  m_handles[endpoint] = fs;
535 
536  return std::make_pair(XrdCl::XRootDStatus{}, fs);
537 }
538 
539 bool
540 Filesystem::GetProperty(const std::string &name,
541  std::string &value) const
542 {
543  std::unique_lock lock(m_properties_mutex);
544  const auto p = m_properties.find(name);
545  if (p == std::end(m_properties)) {
546  return false;
547  }
548 
549  value = p->second;
550  return true;
551 }
552 
554 Filesystem::Locate(const std::string &path,
556  XrdCl::ResponseHandler *handler,
557  time_t timeout)
558 {
559  auto cleaned_path = Factory::CleanObjectName(path);
560  auto [st, fs] = GetFSHandle(cleaned_path);
561  if (!st.IsOK()) {
562  return st;
563  }
564  return fs->Locate(cleaned_path, flags, handler, timeout);
565 }
566 
568 Filesystem::MkDir(const std::string &input_path,
570  XrdCl::Access::Mode mode,
571  XrdCl::ResponseHandler *handler,
572  time_t timeout)
573 {
574  auto sentinel = Factory::GetMkdirSentinel();
575  if (sentinel.empty()) {
576  if (handler) handler->HandleResponse(new XrdCl::XRootDStatus{}, nullptr);
577  return {};
578  }
579  auto loc = input_path.find('?');
580  auto path = input_path.substr(0, loc);
581  if (!path.empty() && path[path.size() - 1] != '/') path += "/";
582  path += sentinel;
583  if (loc != std::string::npos) {
584  path += input_path.substr(loc);
585  }
586 
587  // Try creating a zero-sized sentinel.
588  std::string https_url, err_msg;
589  const auto s3_url = JoinUrl(m_url.GetURL(), path);
590  if (!Factory::GenerateHttpUrl(s3_url, https_url, nullptr, err_msg)) {
592  }
593 
594  XrdCl::File *http_file(new XrdCl::File());
595  auto status = http_file->Open(https_url, XrdCl::OpenFlags::Compress, XrdCl::Access::None, nullptr, time_t(0));
596  if (!status.IsOK()) {
597  delete http_file;
598  return status;
599  }
600 
601  auto callout_loc = reinterpret_cast<long long>(&m_header_callout);
602  size_t buf_size = 16;
603  char callout_buf[buf_size];
604  std::to_chars_result result = std::to_chars(callout_buf, callout_buf + buf_size - 1, callout_loc, 16);
605  if (result.ec == std::errc{}) {
606  std::string callout_str(callout_buf, result.ptr - callout_buf);
607  http_file->SetProperty("XrdClHttpHeaderCallout", callout_str);
608  }
609 
610  MkdirHandler *mkdirHandler = new MkdirHandler(http_file, handler, timeout);
611 
612  return http_file->Open(https_url, XrdCl::OpenFlags::Write, XrdCl::Access::None, mkdirHandler, timeout);
613 }
614 
617  const XrdCl::Buffer &arg,
618  XrdCl::ResponseHandler *handler,
619  time_t timeout)
620 {
621  if (queryCode != XrdCl::QueryCode::Checksum && queryCode != XrdCl::QueryCode::XAttr) {
623  }
624  auto cleaned_path = Factory::CleanObjectName(arg.ToString());
625  auto [st, fs] = GetFSHandle(cleaned_path);
626  if (!st.IsOK()) {
627  return st;
628  }
629  XrdCl::Buffer cleanedArg;
630  cleanedArg.FromString(cleaned_path);
631  return fs->Query(queryCode, cleanedArg, handler, timeout);
632 }
633 
634 
636 Filesystem::Rm(const std::string &path,
637  XrdCl::ResponseHandler *handler,
638  time_t timeout)
639 {
640  auto cleaned_path = Factory::CleanObjectName(path);
641  auto [st, fs] = GetFSHandle(cleaned_path);
642  if (!st.IsOK()) {
643  return st;
644  }
645  return fs->Rm(cleaned_path, handler, timeout);
646 }
647 
649 Filesystem::RmDir(const std::string &input_path,
650  XrdCl::ResponseHandler *handler,
651  time_t timeout)
652 {
653  auto sentinel = Factory::GetMkdirSentinel();
654  if (sentinel.empty()) {
655  if (handler) handler->HandleResponse(new XrdCl::XRootDStatus{}, nullptr);
656  return {};
657  }
658  auto loc = input_path.find('?');
659  auto path = input_path.substr(0, loc);
660  if (!path.empty() && path[path.size() - 1] != '/') path += "/";
661  path += sentinel;
662  if (loc != std::string::npos) {
663  path += input_path.substr(loc);
664  }
665  return Rm(path, handler, timeout);
666 }
667 
668 
669 bool
670 Filesystem::SetProperty(const std::string &name,
671  const std::string &value)
672 {
673  std::unique_lock lock(m_properties_mutex);
674  m_properties[name] = value;
675  return true;
676 }
677 
679 Filesystem::Stat(const std::string &path,
680  XrdCl::ResponseHandler *handler,
681  time_t timeout)
682 {
683  auto cleaned_path = Factory::CleanObjectName(path);
684  auto [st, fs] = GetFSHandle(cleaned_path);
685  if (!st.IsOK()) {
686  return st;
687  }
688  return fs->Stat(cleaned_path, new StatHandler(cleaned_path, m_url.GetURL(), &m_header_callout, handler, timeout, *m_logger), timeout);
689 }
690 
691 std::shared_ptr<XrdClHttp::HeaderCallout::HeaderList>
692 Filesystem::S3HeaderCallout::GetHeaders(const std::string &verb,
693  const std::string &url,
695 {
696  std::string auth_token, err_msg;
697  std::shared_ptr<HeaderList> header_list(new HeaderList(headers));
698  if (Factory::GenerateV4Signature(url, verb, *header_list, auth_token, err_msg)) {
699  header_list->emplace_back("Authorization", auth_token);
700  } else {
701  m_parent.m_logger->Error(kLogXrdClS3, "Failed to generate V4 signature: %s", err_msg.c_str());
702  return nullptr;
703  }
704  return header_list;
705 }
@ kXR_NotFound
Definition: XProtocol.hh:1043
static void child()
std::vector< std::pair< std::string, std::string > > HeaderList
static const std::string & GetMkdirSentinel()
static std::string CleanObjectName(const std::string &object)
static bool GenerateHttpUrl(const std::string &s3_url, std::string &https_url, std::string *obj_result, std::string &err_msg)
static bool GenerateV4Signature(const std::string &url, const std::string &verb, std::vector< std::pair< std::string, std::string >> &headers, std::string &auth_token, std::string &err_msg)
static std::string_view TrimView(const std::string_view str)
virtual XrdCl::XRootDStatus MkDir(const std::string &path, XrdCl::MkDirFlags::Flags flags, XrdCl::Access::Mode mode, XrdCl::ResponseHandler *handler, time_t timeout) override
Filesystem(const std::string &, XrdCl::Log *log)
virtual bool SetProperty(const std::string &name, const std::string &value) override
virtual XrdCl::XRootDStatus DirList(const std::string &path, XrdCl::DirListFlags::Flags flags, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual XrdCl::XRootDStatus Rm(const std::string &path, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual XrdCl::XRootDStatus Stat(const std::string &path, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual XrdCl::XRootDStatus Locate(const std::string &path, XrdCl::OpenFlags::Flags flags, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual ~Filesystem() noexcept
virtual XrdCl::XRootDStatus RmDir(const std::string &path, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual bool GetProperty(const std::string &name, std::string &value) const override
virtual XrdCl::XRootDStatus Query(XrdCl::QueryCode::Code queryCode, const XrdCl::Buffer &arg, XrdCl::ResponseHandler *handler, time_t timeout) override
void Get(Type &object)
Retrieve the object being held.
Binary blob representation.
Definition: XrdClBuffer.hh:34
void FromString(const std::string str)
Fill the buffer from a string.
Definition: XrdClBuffer.hh:205
const char * GetBuffer(uint32_t offset=0) const
Get the message buffer.
Definition: XrdClBuffer.hh:72
uint32_t GetSize() const
Get the size of the message.
Definition: XrdClBuffer.hh:132
std::string ToString() const
Convert the buffer to a string.
Definition: XrdClBuffer.hh:215
Send file/filesystem queries to an XRootD cluster.
A file.
Definition: XrdClFile.hh:52
XRootDStatus Open(const std::string &url, OpenFlags::Flags flags, Access::Mode mode, ResponseHandler *handler, time_t timeout=0) XRD_WARN_UNUSED_RESULT
Definition: XrdClFile.cc:125
bool SetProperty(const std::string &name, const std::string &value)
Definition: XrdClFile.cc:983
Handle diagnostics.
Definition: XrdClLog.hh:101
void Debug(uint64_t topic, const char *format,...)
Print a debug message.
Definition: XrdClLog.cc:282
Handle an async response.
virtual void HandleResponse(XRootDStatus *status, AnyObject *response)
Object stat info.
@ IsDir
This is a directory.
URL representation.
Definition: XrdClURL.hh:31
std::map< std::string, std::string > ParamsMap
Definition: XrdClURL.hh:33
bool FromString(const std::string &url)
Parse a string and fill the URL fields.
Definition: XrdClURL.cc:62
void SetParams(const std::string &params)
Set params.
Definition: XrdClURL.cc:402
std::string GetURL() const
Get the URL.
Definition: XrdClURL.hh:86
void SetPath(const std::string &path)
Set the path.
Definition: XrdClURL.hh:225
XrdCl::XRootDStatus DownloadUrl(const std::string &url, XrdClHttp::HeaderCallout *header_callout, XrdCl::ResponseHandler *handler, time_t timeout)
const uint64_t kLogXrdClS3
const uint16_t errInvalidAddr
Definition: XrdClStatus.hh:71
const uint16_t errErrorResponse
Definition: XrdClStatus.hh:105
const uint16_t errOperationExpired
Definition: XrdClStatus.hh:90
const uint16_t errNotImplemented
Operation is not implemented.
Definition: XrdClStatus.hh:64
const uint16_t stError
An error occurred that could potentially be retried.
Definition: XrdClStatus.hh:32
const uint16_t errInvalidResponse
Definition: XrdClStatus.hh:99
Mode
Access mode.
Flags
Open flags, may be or'd when appropriate.
@ Write
Open only for writing.
Code
XRootD query request codes.
@ XAttr
Query file extended attributes.
@ Checksum
Query file checksum.
bool IsOK() const
We're fine.
Definition: XrdClStatus.hh:124