XRootD
Loading...
Searching...
No Matches
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
32using namespace XrdClS3;
33
34namespace {
35
36// Helper function to URL-quote a string.
37std::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
55class StatHandler : public XrdCl::ResponseHandler {
56public:
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
68private:
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.
81class StatHandlerDirectory : public XrdCl::ResponseHandler {
82public:
83 StatHandlerDirectory(XrdCl::ResponseHandler *handler) :
84 m_handler(handler)
85 {}
86
87 virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) override;
88
89private:
90 XrdCl::ResponseHandler *m_handler{nullptr};
91};
92
93// Response handler for the S3 directory listing GET operation.
94class DirListResponseHandler : public XrdCl::ResponseHandler {
95public:
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
108private:
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"
126class MkdirHandler : public XrdCl::ResponseHandler {
127public:
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
136private:
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
143void
144StatHandler::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
190void
191StatHandlerDirectory::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
208void
209DirListResponseHandler::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 |
307 dirlist->Add(
308 new XrdCl::DirectoryList::ListEntry(
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(
359 new XrdCl::DirectoryList::ListEntry(
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(
378 new XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errErrorResponse, kXR_NotFound),
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
411void
412MkdirHandler::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
443Filesystem::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
458Filesystem::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
487std::pair<XrdCl::XRootDStatus, XrdCl::FileSystem*>
488Filesystem::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
527bool
528Filesystem::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
542Filesystem::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
556Filesystem::MkDir(const std::string &input_path,
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
619Filesystem::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
632Filesystem::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
652bool
653Filesystem::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
662Filesystem::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
674std::shared_ptr<XrdClHttp::HeaderCallout::HeaderList>
675Filesystem::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
std::vector< std::pair< std::string, std::string > > HeaderList
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)
static const std::string & GetMkdirSentinel()
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.
void FromString(const std::string str)
Fill the buffer from a string.
const char * GetBuffer(uint32_t offset=0) const
Get the message buffer.
uint32_t GetSize() const
Get the size of the message.
std::string ToString() const
Convert the buffer to a string.
void Add(ListEntry *entry)
Add an entry to the list - takes ownership.
uint32_t GetSize() const
Get the size of the listing.
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
XRootDStatus Close(ResponseHandler *handler, time_t timeout=0) XRD_WARN_UNUSED_RESULT
Definition XrdClFile.cc:210
bool SetProperty(const std::string &name, const std::string &value)
Definition XrdClFile.cc:983
Handle diagnostics.
Definition XrdClLog.hh:101
void Error(uint64_t topic, const char *format,...)
Report an error.
Definition XrdClLog.cc:231
Handle an async response.
virtual void HandleResponse(XRootDStatus *status, AnyObject *response)
@ IsReadable
Read access is allowed.
@ IsDir
This is a directory.
@ XBitSet
Executable/searchable bit set.
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
std::string GetURL() const
Get the URL.
Definition XrdClURL.hh:86
static std::string JoinUrl(const std::string &base, const std::string &path)
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
const uint16_t errErrorResponse
const uint16_t errOperationExpired
const uint16_t errNotImplemented
Operation is not implemented.
const uint16_t stError
An error occurred that could potentially be retried.
const uint16_t errInvalidResponse
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.