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
29#include <charconv>
30
31using namespace XrdClS3;
32
33namespace {
34
35// Helper function to URL-quote a string.
36std::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 '/'
55std::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
67class StatHandler : public XrdCl::ResponseHandler {
68public:
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
80private:
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.
93class StatHandlerDirectory : public XrdCl::ResponseHandler {
94public:
95 StatHandlerDirectory(XrdCl::ResponseHandler *handler) :
96 m_handler(handler)
97 {}
98
99 virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) override;
100
101private:
102 XrdCl::ResponseHandler *m_handler{nullptr};
103};
104
105// Response handler for the S3 directory listing GET operation.
106class DirListResponseHandler : public XrdCl::ResponseHandler {
107public:
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
120private:
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"
138class MkdirHandler : public XrdCl::ResponseHandler {
139public:
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
148private:
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
155void
156StatHandler::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
202void
203StatHandlerDirectory::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
220void
221DirListResponseHandler::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 |
319 dirlist->Add(
320 new XrdCl::DirectoryList::ListEntry(
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(
371 new XrdCl::DirectoryList::ListEntry(
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(
390 new XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errErrorResponse, kXR_NotFound),
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
423void
424MkdirHandler::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
455Filesystem::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
470Filesystem::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
499std::pair<XrdCl::XRootDStatus, XrdCl::FileSystem*>
500Filesystem::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
539bool
540Filesystem::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
554Filesystem::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
568Filesystem::MkDir(const std::string &input_path,
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
636Filesystem::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
649Filesystem::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
669bool
670Filesystem::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
679Filesystem::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
691std::shared_ptr<XrdClHttp::HeaderCallout::HeaderList>
692Filesystem::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
static void child()
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:62
std::string GetURL() const
Get the URL.
Definition XrdClURL.hh:86
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.