XRootD
Loading...
Searching...
No Matches
XrdClHttpFilesystem.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 XrdClHttp 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
21#include "XrdClHttpFactory.hh"
23#include "XrdClHttpOps.hh"
24#include "XrdClHttpResponses.hh"
26
31
32#include <algorithm>
33#include <cerrno>
34#include <condition_variable>
35#include <chrono>
36#include <exception>
37#include <memory>
38#include <mutex>
39#include <optional>
40#include <string>
41
42using namespace XrdClHttp;
43using namespace std::string_literals;
44
45namespace
46{
47
48// Blocks the calling thread until the operation it is given to completes.
49class SyncResponseHandler final : public XrdCl::ResponseHandler
50{
51public:
52 // The operation gives the ownership of both objects to this handler. The
53 // result of the operation comes from the operation itself, thus this
54 // handler reads neither of them and releases them immediately.
55 void HandleResponse(XrdCl::XRootDStatus *status,
56 XrdCl::AnyObject *response) override
57 {
58 if (status != nullptr)
59 {
60 const std::unique_ptr<XrdCl::XRootDStatus> owned_status{status};
61 }
62
63 if (response != nullptr)
64 {
65 const std::unique_ptr<XrdCl::AnyObject> owned_response{response};
66 }
67
68 {
69 const std::lock_guard<std::mutex> lock{mutex};
70 is_ready = true;
71 }
72
73 is_ready_changed.notify_all();
74 }
75
76 // Return false when the wait takes more than the timeout.
77 bool wait(std::chrono::seconds timeout = std::chrono::seconds(0))
78 {
79 std::unique_lock<std::mutex> lock{mutex};
80
81 bool ready = true;
82
83 if (timeout.count() > 0)
84 ready = is_ready_changed.wait_for(lock, timeout, [this]{ return is_ready; });
85 else
86 is_ready_changed.wait(lock, [this]{ return is_ready; });
87
88 is_ready = false;
89
90 return ready;
91 }
92
93private:
94 std::mutex mutex{};
95 std::condition_variable is_ready_changed{};
96 bool is_ready{false};
97};
98
99// Adds fixed headers, such as the Authorization header of one endpoint, to
100// the headers of a request.
101class ExtraHeaderCallout final : public XrdClHttp::HeaderCallout
102{
103public:
104 explicit ExtraHeaderCallout(HeaderList extra) : extra(std::move(extra)) {}
105
106 std::shared_ptr<HeaderList> GetHeaders(const std::string &,
107 const std::string &,
108 const HeaderList &headers) override
109 {
110 auto result = std::make_shared<HeaderList>(headers);
111 result->insert(result->end(), extra.begin(), extra.end());
112 return result;
113 }
114
115private:
116 const HeaderList extra;
117};
118
119}
120
121Filesystem::Filesystem(const std::string &url, std::shared_ptr<HandlerQueue> queue, XrdCl::Log *log)
122 : m_queue(queue),
123 m_logger(log),
124 m_url(url)
125{
126 m_logger->Debug(kLogXrdClHttp, "Constructing filesystem object with base URL %s", url.c_str());
127 // When constructed from the root protocol handler, we've observed it include the
128 // path here (the code paths appear to be slightly different from http://). Strip
129 // it out so it's not included twice later.
130 m_url.SetPath("/");
132 m_url.SetParams(map);
133}
134
136
138Filesystem::QueueOperation(std::unique_ptr<CurlOperation> operation,
139 const char *description)
140{
141 try
142 {
143 m_queue->Produce(std::move(operation));
144 }
145 catch(const std::exception &ex)
146 {
147 m_logger->Warning(kLogXrdClHttp,
148 "Failed to add %s to queue: %s", description, ex.what());
150 EIO, ex.what());
151 }
152 return XrdCl::XRootDStatus();
153}
154
155XrdCl::XRootDStatus
156Filesystem::DirList(const std::string &path,
158 XrdCl::ResponseHandler *handler,
159 time_t timeout )
160{
162
163 auto full_url = GetCurrentURL(path);
164
165 m_logger->Debug(kLogXrdClHttp, "Filesystem::DirList path %s", path.c_str());
166 auto listdirOp = std::make_unique<XrdClHttp::CurlListdirOp>(
167 handler, full_url,
168 m_url.GetHostName() + ":" + std::to_string(m_url.GetPort()),
169 SendResponseInfo(), ts, m_logger,
170 GetConnCallout(), m_header_callout.load(std::memory_order_acquire));
171 return QueueOperation(std::move(listdirOp), "directory list operation");
172}
173
175Filesystem::GetConnCallout() const {
176 std::string pointer_str;
177 if (!GetProperty("XrdClConnectionCallout", pointer_str) && pointer_str.empty()) {
178 return nullptr;
179 }
180 long long pointer;
181 try {
182 pointer = std::stoll(pointer_str, nullptr, 16);
183 } catch (...) {
184 return nullptr;
185 }
186 if (!pointer) {
187 return nullptr;
188 }
189 return reinterpret_cast<CreateConnCalloutType>(pointer);
190}
191
192bool
193Filesystem::GetProperty(const std::string &name,
194 std::string &value) const
195{
196 std::shared_lock lock(m_properties_mutex);
197
198 const auto p = m_properties.find(name);
199 if (p == std::end(m_properties)) {
200 return false;
201 }
202
203 value = p->second;
204 return true;
205}
206
207// Trivial implementation of the "locate" call
208//
209// On Linux, this is invoked by the XrdCl client prior to directory listings.
210// Given there's no concept of multiple locations currently, we just return
211// the original host and port as the available "location".
213Filesystem::Locate( const std::string &path,
215 XrdCl::ResponseHandler *handler,
216 time_t timeout )
217{
218 if (!handler) return XrdCl::XRootDStatus();
219
220 auto locateInfo = std::make_unique<XrdCl::LocationInfo>();
221 locateInfo->Add(XrdCl::LocationInfo::Location(m_url.GetHostName() + ":" + std::to_string(m_url.GetPort()), XrdCl::LocationInfo::ServerOnline, XrdCl::LocationInfo::Read));
222
223 auto obj = std::make_unique<XrdCl::AnyObject>();
224 obj->Set(locateInfo.release());
225 handler->HandleResponse(new XrdCl::XRootDStatus(), obj.release());
226
227 return XrdCl::XRootDStatus();
228}
229
233 XrdCl::ResponseHandler *handler,
234 time_t timeout)
235{
237
238 auto full_url = GetCurrentURL(path);
239 m_logger->Debug(kLogXrdClHttp, "Filesystem::MkDir path %s", full_url.c_str());
240
241 auto mkdirOp = std::make_unique<CurlMkcolOp>(
242 handler, full_url, ts, m_logger, SendResponseInfo(), GetConnCallout(),
243 m_header_callout.load(std::memory_order_acquire));
244 return QueueOperation(std::move(mkdirOp), "filesystem mkdir operation");
245}
246
248 const std::vector<std::string> &fileList,
250 uint8_t priority,
251 XrdCl::ResponseHandler *handler,
252 time_t timeout)
253{
254 (void)priority;
255
256 if(fileList.empty())
257 {
259 EINVAL, "missing prepare file list");
260 }
261
263 auto tapeOp = std::make_unique<CurlTapePrepareOp>(
264 handler, m_url.GetURL(), fileList, flags, ts, m_logger,
265 GetConnCallout(),
266 m_header_callout.load(std::memory_order_acquire));
267 return QueueOperation(std::move(tapeOp), "Tape prepare operation");
268}
269
271 const XrdCl::Buffer &arg,
272 XrdCl::ResponseHandler *handler,
273 time_t timeout)
274{
276 std::unique_ptr<CurlOperation> operation;
277 const char *description = nullptr;
278
279 switch(queryCode)
280 {
283 operation = std::make_unique<CurlTapeQueryOp>(
284 handler, m_url.GetURL(), queryCode, arg, ts, m_logger,
285 GetConnCallout(),
286 m_header_callout.load(std::memory_order_acquire));
287 description = "Tape query operation";
288 break;
290 {
291 const auto url = GetCurrentURL(arg.ToString());
292 m_logger->Debug(kLogXrdClHttp,
293 "XrdClHttp::Filesystem::Query checksum path %s", url.c_str());
294
295 XrdClHttp::ChecksumType preferred =
297 XrdCl::URL urlObj;
298 urlObj.FromString(url);
299 const auto iter = urlObj.GetParams().find("cks.type");
300 if(iter != urlObj.GetParams().end())
301 {
302 preferred = XrdClHttp::GetTypeFromString(iter->second);
303 if(preferred == XrdClHttp::ChecksumType::kUnknown)
304 {
305 m_logger->Error(kLogXrdClHttp,
306 "Unknown checksum type %s", iter->second.c_str());
308 XrdCl::errInvalidArgs, EINVAL,
309 "unknown checksum type '" + iter->second + "'");
310 }
311 }
312 operation = std::make_unique<CurlChecksumOp>(
313 handler, url, preferred, ts, m_logger, SendResponseInfo(),
314 GetConnCallout(),
315 m_header_callout.load(std::memory_order_acquire));
316 description = "checksum operation";
317 break;
318 }
320 {
321 const std::string path = arg.ToString();
322 m_logger->Debug(kLogXrdClHttp,
323 "XrdClHttp::Filesystem::Query xattr full_url %s, path %s",
324 m_url.GetURL().c_str(), path.c_str());
325 operation = std::make_unique<CurlQueryOp>(
326 handler, path, ts, m_logger, SendResponseInfo(),
327 GetConnCallout(), queryCode,
328 m_header_callout.load(std::memory_order_acquire));
329 description = "xattr query operation";
330 break;
331 }
332 default:
333 return XrdCl::XRootDStatus(
335 }
336
337 return QueueOperation(std::move(operation), description);
338}
339
341Filesystem::Rm(const std::string &path,
342 XrdCl::ResponseHandler *handler,
343 time_t timeout)
344{
346
347 auto full_url = GetCurrentURL(path);
348 m_logger->Debug(kLogXrdClHttp, "Filesystem::Rm path %s", full_url.c_str());
349
350 auto deleteOp = std::make_unique<CurlDeleteOp>(
351 handler, full_url, ts, m_logger, SendResponseInfo(),
352 GetConnCallout(), m_header_callout.load(std::memory_order_acquire));
353 return QueueOperation(std::move(deleteOp), "filesystem delete operation");
354}
355
357Filesystem::RmDir(const std::string &path,
358 XrdCl::ResponseHandler *handler,
359 time_t timeout)
360{
361 return Rm(path, handler, timeout);
362}
363
364bool
365Filesystem::SetProperty(const std::string &name,
366 const std::string &value)
367{
368 if (name == "XrdClHttpHeaderCallout") {
369 long long pointer;
370 try {
371 pointer = std::stoll(value, nullptr, 16);
372 } catch (...) {
373 pointer = 0;
374 }
375 if (!pointer) {
376 pointer = 0;
377 }
378 m_header_callout.store(reinterpret_cast<XrdClHttp::HeaderCallout*>(pointer), std::memory_order_release);
379 }
380
381 std::unique_lock lock(m_properties_mutex);
382 m_properties[name] = value;
383 return true;
384}
385
387Filesystem::Stat(const std::string &path,
388 XrdCl::ResponseHandler *handler,
389 time_t timeout)
390{
392
393 auto full_url = GetCurrentURL(path);
394 m_logger->Debug(kLogXrdClHttp, "Filesystem::Stat path %s", full_url.c_str());
395
396 auto statOp = std::make_unique<CurlStatOp>(
397 handler, full_url, ts, m_logger, SendResponseInfo(),
398 GetConnCallout(), m_header_callout.load(std::memory_order_acquire));
399 return QueueOperation(std::move(statOp), "filesystem stat operation");
400}
401
402// Returns true if the URL uses the http or the https protocol. A third party
403// copy is possible only between these two protocols.
404static bool is_http_url( const std::string &url )
405{
406 const std::string protocol = XrdCl::URL(url).GetProtocol();
407 return protocol == "http" || protocol == "https" || protocol == "dav" || protocol == "davs";
408}
409
410// The operations a third party copy runs. The unit tests replace them by
411// declaring XrdClHttp::StatOp and XrdClHttp::CopyOp before they include this
412// file, because the lookup of the unqualified name stops at namespace
413// XrdClHttp.
414namespace XrdClHttp
415{
417 {
420 }
421}
422
424
425// Inside namespace XrdClHttp for the same name lookup of StatOp as above.
426namespace XrdClHttp
427{
428// Stat the URL with the given extra headers. Returns the size, or nothing when
429// the stat fails, does not finish in time, or cannot be queued.
430static std::optional<std::size_t> stat_size(HandlerQueue &queue,
431 const std::string &url,
432 const CurlCopyOp::Headers &hdrs,
433 time_t timeout,
434 XrdCl::Log *log,
435 const char *what)
436{
437 try
438 {
439 const auto callout = std::make_shared<ExtraHeaderCallout>(hdrs);
440 const auto rh = std::make_shared<SyncResponseHandler>();
441 const auto op = std::make_shared<StatOp>(rh.get(), url, timespec{timeout, 0}, log, true, nullptr, callout.get());
442
443 // The queue may run the operation after this function returns, thus
444 // the operation keeps its handler and header callout alive.
445 queue.Produce(std::shared_ptr<StatOp>(op.get(), [callout, rh, op](auto){}));
446
447 if (!rh->wait(std::chrono::seconds(timeout)))
448 log->Warning(kLogXrdClHttp, "Failed to stat the %s: Operation timed out", what);
449 else if (!op->IsDone() || op->HasFailed())
450 log->Debug(kLogXrdClHttp, "Failed to stat the %s", what);
451 else
452 return static_cast<std::size_t>(op->GetStatInfo().first);
453 }
454 catch (const std::exception &e) {
455 log->Warning(kLogXrdClHttp, "Failed to stat the %s - %s", what, e.what());
456 }
457
458 return std::nullopt;
459}
460}
461
463 const std::string &dest,
464 const XrdCl::PropertyList *properties,
465 XrdCl::ProgressHandler *progress_handler,
466 time_t timeout )
467{
469 log->Debug(kLogXrdClHttp, "XrdClHttp::ThirdPartyCopy src %s dst %s", source.c_str(), dest.c_str());
470
471 if (!is_http_url(source) || !is_http_url(dest))
472 {
473 log->Error(kLogXrdClHttp, "Third party copy can only be done between http(s) protocols");
475 "Third party copy can only be done between http(s) protocols");
476 }
477
478 TpcMode mode = TpcMode::Pull;
479 int number_of_streams = 1;
480 time_t init_timeout = 0;
481 time_t tpc_timeout = 0;
482 std::string token_file;
483 bool force_overwrite = false;
484
485 if (properties)
486 {
487 mode = properties->Get<std::string>("thirdPartyMode") != "push" ? TpcMode::Pull : TpcMode::Push;
488 properties->Get("initTimeout", init_timeout);
489 properties->Get("tpcTimeout", tpc_timeout);
490 token_file = properties->Get<std::string>("thirdPartyTokenFile");
491 properties->Get("force", force_overwrite);
492 }
493
494 // The tpcTimeout property takes precedence over the timeout argument.
495 if (tpc_timeout == 0)
496 tpc_timeout = timeout;
497
498 if (auto env = XrdCl::DefaultEnv::GetEnv(); env)
499 env->GetInt("SubStreamsPerChannel", number_of_streams);
500
501 CurlCopyOp::Headers headers;
502 CurlCopyOp::Headers src_hdrs;
503 CurlCopyOp::Headers dst_hdrs;
504
505 if (!token_file.empty() && !ParseTokenFile(token_file, src_hdrs, dst_hdrs))
507 "Failed to parse the token file '" + token_file + "'");
508
509 const auto is_unsecure = [] (const CurlCopyOp::Headers &hdrs, const std::string &url)
510 {
511 return XrdCl::URL(url).GetProtocol() != "https"s &&
512 std::any_of(hdrs.begin(), hdrs.end(),
513 [] (const auto &header) { return header.first == "Authorization"s; });
514 };
515
516 if (is_unsecure(src_hdrs, source) || is_unsecure(dst_hdrs, dest))
517 {
518 log->Error(kLogXrdClHttp, "Refusing to send an Authorization header over an unencrypted http URL");
520 "Refusing to send an Authorization header over an unencrypted http URL");
521 }
522
523 // In push mode the source writes the destination with a PUT, which may
524 // replace an existing file whatever the Overwrite header says. Thus check
525 // the destination first, as the destination server may not honor it.
526 if (mode == TpcMode::Push && !force_overwrite &&
527 stat_size(*m_queue, dest, dst_hdrs, init_timeout, log, "destination"))
528 {
529 log->Error(kLogXrdClHttp, "The destination %s exists", dest.c_str());
531 "The destination exists; use --force to replace it");
532 }
533
534 headers.emplace_back("X-Number-Of-Streams"s, std::to_string(number_of_streams));
535
536 // The destination headers reach the destination in both modes: directly
537 // in pull mode, with the TransferHeader prefix in push mode.
538 dst_hdrs.emplace_back("Overwrite"s, force_overwrite ? "T"s : "F"s);
539
540 std::size_t size = 0;
541
542 if (progress_handler)
543 {
544 size = stat_size(*m_queue, source, src_hdrs, init_timeout, log, "source").value_or(0);
545 progress_handler->HandleProgress(0, size);
546 }
547
548 const auto rh = std::make_shared<SyncResponseHandler>();
549 const auto op = std::make_shared<CopyOp>(rh.get(), source, src_hdrs, dest, dst_hdrs, headers,
550 mode, timespec{tpc_timeout, 0}, log, nullptr);
551 op->SetProgressHandler(progress_handler);
552
553 try
554 {
555 m_queue->Produce(std::shared_ptr<CopyOp>(op.get(), [rh, op](auto){}));
556 }
557 catch (const std::exception &e) {
558 log->Warning(kLogXrdClHttp, "Failed to add copy op to queue - %s", e.what());
560 }
561
562 if (!rh->wait(std::chrono::seconds(tpc_timeout)))
563 return XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errOperationExpired, 0, "Operation expired: Operation timed out"s);
564
565 if (op->IsDone() && !op->IsSentSuccessfully())
566 return XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errPipelineFailed, 0, op->GetSendingFailureMessage());
567
568 if (progress_handler && size > 0)
569 progress_handler->HandleProgress(size, size);
570
571 return XrdCl::XRootDStatus();
572}
573
574bool Filesystem::SendResponseInfo() const {
575 std::string val;
576 return GetProperty(ResponseInfoProperty, val) && val == "true";
577}
578
579std::string Filesystem::GetCurrentURL(const std::string &path) const {
580
581 // Compute the URL without trailing slash.
582 auto prefix = m_url.GetURL();
583 std::string_view prefix_view = prefix;
584 while (!prefix_view.empty() && prefix_view[prefix_view.size() - 1] == '/')
585 prefix_view = prefix_view.substr(0, prefix_view.size() - 1);
586
587 // Compute the target path without the '/' prefix
588 std::string_view path_view = path;
589 while (!path_view.empty() && path_view[0] == '/')
590 path_view = path_view.substr(1);
591 auto retval = std::string(prefix_view) + "/" + std::string(path_view);
592
593 // Add in the query parameters, if relevant.
594 {
595 std::shared_lock lock(m_properties_mutex);
596 auto iter = m_properties.find("XrdClHttpQueryParam");
597 if (iter != m_properties.end() && !iter->second.empty()) {
598 retval += ((retval.find('?') == std::string::npos) ? '?' : ':') + iter->second;
599 }
600 }
601 return retval;
602}
@ kXR_ItExists
static bool is_http_url(const std::string &url)
#define ResponseInfoProperty
std::vector< std::pair< std::string, std::string > > Headers
static struct timespec GetHeaderTimeoutWithDefault(time_t oper_timeout)
virtual XrdCl::XRootDStatus MkDir(const std::string &path, XrdCl::MkDirFlags::Flags flags, XrdCl::Access::Mode mode, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual bool SetProperty(const std::string &name, const std::string &value) override
virtual XrdCl::XRootDStatus Prepare(const std::vector< std::string > &fileList, XrdCl::PrepareFlags::Flags flags, uint8_t priority, XrdCl::ResponseHandler *handler, time_t timeout) override
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 ThirdPartyCopy(const std::string &source, const std::string &dest, const XrdCl::PropertyList *properties, XrdCl::ProgressHandler *progress_handler, time_t timeout=0) 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
Filesystem(const std::string &, std::shared_ptr< HandlerQueue > queue, XrdCl::Log *log)
void Produce(std::shared_ptr< CurlOperation > handler)
Binary blob representation.
std::string ToString() const
Convert the buffer to a string.
static Log * GetLog()
Get default log.
static Env * GetEnv()
Get default client environment.
@ Read
read access is allowed
@ ServerOnline
server node where the file is online
Handle diagnostics.
Definition XrdClLog.hh:101
void Error(uint64_t topic, const char *format,...)
Report an error.
Definition XrdClLog.cc:231
void Warning(uint64_t topic, const char *format,...)
Report a warning.
Definition XrdClLog.cc:248
void Debug(uint64_t topic, const char *format,...)
Print a debug message.
Definition XrdClLog.cc:282
Handle the progress of an asynchronous operation.
virtual void HandleProgress(std::size_t processed, std::size_t total=0)=0
A key-value pair map storing both keys and values as strings.
bool Get(const std::string &name, Item &item) const
Handle an async response.
virtual void HandleResponse(XRootDStatus *status, AnyObject *response)
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
std::string GetURL() const
Get the URL.
Definition XrdClURL.hh:86
const ParamsMap & GetParams() const
Get the URL params.
Definition XrdClURL.hh:244
const std::string & GetProtocol() const
Get the protocol.
Definition XrdClURL.hh:118
std::vector< std::pair< std::string, std::string > > HeaderList
ConnectionCallout *(*)(const std::string &, const ResponseInfo &) CreateConnCalloutType
ChecksumType GetTypeFromString(const std::string &str)
const uint64_t kLogXrdClHttp
static std::optional< std::size_t > stat_size(HandlerQueue &queue, const std::string &url, const CurlCopyOp::Headers &hdrs, time_t timeout, XrdCl::Log *log, const char *what)
bool ParseTokenFile(const std::string &token_file, CurlCopyOp::Headers &src_hdrs, CurlCopyOp::Headers &dst_hdrs)
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 errInternal
Internal error.
const uint16_t errOSError
const uint16_t errPipelineFailed
Pipeline failed and operation couldn't be executed.
const uint16_t errInvalidArgs
const uint16_t errAuthFailed
Flags
Open flags, may be or'd when appropriate.
Code
XRootD query request codes.
@ XAttr
Query file extended attributes.
@ Opaque
Implementation dependent.
@ Checksum
Query file checksum.
@ Prepare
Query prepare status.