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