36 std::string urlquote(
const std::string input) {
38 output.reserve(3 * input.size());
39 for (
char val : input) {
40 if ((val >= 48 && val <= 57) ||
41 (val >= 65 && val <= 90) ||
42 (val >= 97 && val <= 122) ||
43 (val == 95 || val == 46 || val == 45 || val == 126 ||
48 output +=
"%" + std::to_string(val);
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] ==
'/') {
59 while (idx < path.size() && path[idx] ==
'/') idx++;
60 result.append(path.data() + idx, path.size() - idx);
72 m_header_callout(header_callout),
109 m_existence_check(existence_check),
111 m_header_callout(header_callout),
113 m_host(
Factory::ExtractHostname(url)),
124 bool m_existence_check;
141 m_expiry(time(NULL) + (timeout ? timeout : 30)),
150 bool m_started_close{
false};
151 std::unique_ptr<XrdCl::File> m_file;
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);
162 if (m_handler) {
return m_handler->HandleResponse(status.release(), response_holder.release());}
167 if (m_handler) {
return m_handler->HandleResponse(status.release(), response_holder.release());}
173 std::string https_url, err_msg;
174 const auto s3_url = JoinUrl(m_s3_url, m_path);
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) +
"/";
186 auto expiry = time(NULL) + m_timeout;
191 new DirListResponseHandler(
192 true, https_url, m_header_callout,
new StatHandlerDirectory(m_handler), expiry, m_logger
197 if (m_handler)
return m_handler->HandleResponse(
new XrdCl::XRootDStatus(st), response_holder.release());
204 std::unique_ptr<StatHandlerDirectory>
self(
this);
210 if (!status || !status->
IsOK()) {
211 return m_handler->HandleResponse(status, response);
217 m_handler->HandleResponse(status, obj);
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);
228 if (!status || !status->
IsOK()) {
229 return m_handler->HandleResponse(status.release(), response.release());
233 m_logger.Error(
kLogXrdClS3,
"Directory listing returned without any response object.");
238 response->
Get(buffer);
240 m_logger.Error(
kLogXrdClS3,
"Directory listing response object was not a buffer.");
247 doc.Parse(buffer_str.c_str());
249 std::string errMsg =
"Error when parsing S3 endpoint's listing response: " + std::string(doc.ErrorDesc());
254 auto elem = doc.RootElement();
255 if (strcmp(elem->Value(),
"ListBucketResult")) {
257 "S3 ListBucket response is not rooted with ListBucketResult element"),
nullptr);
295 bool isTruncated =
false;
297 bool found_sentinel =
false;
298 for (
auto child = elem->FirstChildElement();
child !=
nullptr;
300 if (!strcmp(
child->Value(),
"IsTruncated")) {
301 auto text =
child->GetText();
302 if (!strcasecmp(text,
"true")) {
304 }
else if (!strcasecmp(text,
"false")) {
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);
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;
322 "nobody", 4096, flags, 0)));
326 }
else if (!strcmp(
child->Value(),
"Contents")) {
327 std::string_view keyStr;
329 bool goodSize =
false;
330 auto key =
child->FirstChildElement(
"Key");
331 if (key !=
nullptr) {
332 auto keyChar = key->GetText();
333 if (keyChar !=
nullptr) {
337 auto last_slash = keyStr.rfind(
'/');
338 if (last_slash != std::string_view::npos) {
340 found_sentinel =
true;
341 if (m_existence_check)
break;
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()) {
355 auto lastModifiedElem =
child->FirstChildElement(
"LastModified");
356 time_t lastModified = 0;
357 if (lastModifiedElem !=
nullptr) {
358 auto lastModifiedChar = lastModifiedElem->GetText();
359 if (lastModifiedChar !=
nullptr) {
362 if (strptime(lastModifiedChar,
"%Y-%m-%dT%H:%M:%S", &tm) !=
nullptr) {
364 lastModified = mktime(&tm);
368 if (goodSize && !keyStr.empty()) {
369 uint32_t flags = XrdCl::StatInfo::Flags::IsReadable;
373 "nobody", size, flags, lastModified)));
375 }
else if (!strcmp(
child->Value(),
"NextContinuationToken")) {
376 auto ctChar =
child->GetText();
386 if (!isTruncated || (m_existence_check && (dirlist->GetSize() || found_sentinel))) {
388 if (!found_sentinel && !dirlist->GetSize()) {
389 m_handler->HandleResponse(
396 object->Set(dirlist.release());
397 m_handler->HandleResponse(
404 auto url = m_url +
"&continuation-token=" + urlquote(ct);
407 time_t now = time(NULL);
408 if (now >= m_expiry) {
409 m_handler->HandleResponse(
416 auto st =
DownloadUrl(url, m_header_callout,
this, m_expiry - now);
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);
430 if (!status || !status->
IsOK() || m_started_close) {
431 if (m_handler) m_handler->HandleResponse(status.release(), response.release());
435 time_t now = time(NULL);
436 if (now >= m_expiry) {
437 m_handler->HandleResponse(
444 m_started_close =
true;
445 auto st = m_file->Close(
this, m_expiry - now);
447 if (m_handler) m_handler->HandleResponse(status.release(), response.release());
475 std::string https_url, err_msg;
476 const auto s3_url = JoinUrl(m_url.
GetURL(), path);
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) +
"/";
487 auto expiry = time(NULL) + timeout;
492 new DirListResponseHandler(
493 false, https_url, &m_header_callout, handler, expiry, *m_logger
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;
506 auto loc = https_url.find(
'/', 8);
507 if (loc == std::string::npos) {
510 auto endpoint = https_url.substr(0, loc);
512 std::shared_lock lock(m_handles_mutex);
513 auto iter = m_handles.find(endpoint);
514 if (iter != m_handles.end()) {
522 std::unique_lock lock(m_handles_mutex);
523 auto iter = m_handles.find(endpoint);
524 if (iter != m_handles.end()) {
528 std::stringstream ss;
529 ss << std::hex << reinterpret_cast<long long>(&m_header_callout);
530 if (!fs->SetProperty(
"XrdClHttpHeaderCallout", ss.str())) {
534 m_handles[endpoint] = fs;
541 std::string &value)
const
543 std::unique_lock lock(m_properties_mutex);
544 const auto p = m_properties.find(name);
545 if (p == std::end(m_properties)) {
560 auto [st, fs] = GetFSHandle(cleaned_path);
564 return fs->Locate(cleaned_path, flags, handler, timeout);
575 if (sentinel.empty()) {
579 auto loc = input_path.find(
'?');
580 auto path = input_path.substr(0, loc);
581 if (!path.empty() && path[path.size() - 1] !=
'/') path +=
"/";
583 if (loc != std::string::npos) {
584 path += input_path.substr(loc);
588 std::string https_url, err_msg;
589 const auto s3_url = JoinUrl(m_url.
GetURL(), path);
596 if (!status.
IsOK()) {
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);
610 MkdirHandler *mkdirHandler =
new MkdirHandler(http_file, handler, timeout);
625 auto [st, fs] = GetFSHandle(cleaned_path);
631 return fs->Query(queryCode, cleanedArg, handler, timeout);
641 auto [st, fs] = GetFSHandle(cleaned_path);
645 return fs->Rm(cleaned_path, handler, timeout);
654 if (sentinel.empty()) {
658 auto loc = input_path.find(
'?');
659 auto path = input_path.substr(0, loc);
660 if (!path.empty() && path[path.size() - 1] !=
'/') path +=
"/";
662 if (loc != std::string::npos) {
663 path += input_path.substr(loc);
665 return Rm(path, handler, timeout);
671 const std::string &value)
673 std::unique_lock lock(m_properties_mutex);
674 m_properties[name] = value;
684 auto [st, fs] = GetFSHandle(cleaned_path);
688 return fs->Stat(cleaned_path,
new StatHandler(cleaned_path, m_url.
GetURL(), &m_header_callout, handler, timeout, *m_logger), timeout);
691 std::shared_ptr<XrdClHttp::HeaderCallout::HeaderList>
692 Filesystem::S3HeaderCallout::GetHeaders(
const std::string &verb,
693 const std::string &url,
696 std::string auth_token, err_msg;
697 std::shared_ptr<HeaderList> header_list(
new HeaderList(headers));
699 header_list->emplace_back(
"Authorization", auth_token);
701 m_parent.m_logger->Error(
kLogXrdClS3,
"Failed to generate V4 signature: %s", err_msg.c_str());
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.
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.
Send file/filesystem queries to an XRootD cluster.
XRootDStatus Open(const std::string &url, OpenFlags::Flags flags, Access::Mode mode, ResponseHandler *handler, time_t timeout=0) XRD_WARN_UNUSED_RESULT
bool SetProperty(const std::string &name, const std::string &value)
void Debug(uint64_t topic, const char *format,...)
Print a debug message.
Handle an async response.
virtual void HandleResponse(XRootDStatus *status, AnyObject *response)
@ IsDir
This is a directory.
std::map< std::string, std::string > ParamsMap
bool FromString(const std::string &url)
Parse a string and fill the URL fields.
void SetParams(const std::string ¶ms)
Set params.
std::string GetURL() const
Get the URL.
void SetPath(const std::string &path)
Set the 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.