XRootD
XrdClS3File.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 
21 #include "XrdClS3Factory.hh"
22 #include "XrdClS3File.hh"
23 
24 #include <XrdCl/XrdClLog.hh>
25 
26 using namespace XrdClS3;
27 
28 namespace {
29 
30 class OpenResponseHandler : public XrdCl::ResponseHandler {
31 public:
32  OpenResponseHandler(bool *is_opened, XrdCl::ResponseHandler *handler)
33  : m_is_opened(is_opened),
34  m_handler(handler)
35  {
36  }
37 
38  virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) {
39  // Delete the handler; since we're injecting results a File object, no one owns us
40  std::unique_ptr<OpenResponseHandler> owner(this);
41 
42  if (status && status->IsOK()) {
43  if (m_is_opened) *m_is_opened = true;
44  }
45  if (m_handler) m_handler->HandleResponse(status, response);
46  else delete response;
47  }
48 
49 private:
50  bool *m_is_opened;
51 
52  // A reference to the handler we are wrapping. Note we don't own the handler
53  // so this is not a unique_ptr.
54  XrdCl::ResponseHandler *m_handler;
55 };
56 
57 class CloseResponseHandler : public XrdCl::ResponseHandler {
58 public:
59  CloseResponseHandler(bool *is_opened, XrdCl::ResponseHandler *handler)
60  : m_is_opened(is_opened),
61  m_handler(handler)
62  {
63  }
64 
65  virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) {
66  std::unique_ptr<CloseResponseHandler> owner(this);
67 
68  if (status && status->IsOK()) {
69  if (m_is_opened) *m_is_opened = false;
70  }
71  if (m_handler) m_handler->HandleResponse(status, response);
72  else delete response;
73  }
74 
75 private:
76  bool *m_is_opened;
77 
78  // A reference to the handler we are wrapping. Note we don't own the handler
79  // so this is not a unique_ptr.
80  XrdCl::ResponseHandler *m_handler;
81 };
82 
83 } // namespace
84 
85 File::File(XrdCl::Log *log) :
86  m_logger(log),
87  m_wrapped_file()
88 {
89 }
90 
91 File::~File() noexcept {}
92 
95  time_t timeout)
96 {
97  return m_wrapped_file->Close(new CloseResponseHandler(&m_is_opened, handler), timeout);
98 }
99 
100 bool
101 File::IsOpen() const
102 {
103  return m_is_opened;
104 }
105 
106 bool
107 File::GetProperty(const std::string &name,
108  std::string &value) const
109 {
110  std::unique_lock lock(m_properties_mutex);
111  const auto p = m_properties.find(name);
112  if (p == std::end(m_properties)) {
113  return false;
114  }
115 
116  value = p->second;
117  return true;
118 }
119 
120 std::tuple<XrdCl::XRootDStatus, std::string, XrdCl::File*>
121 File::GetFileHandle(const std::string &s3_url) {
122  if (m_wrapped_file) {
123  return std::make_tuple(XrdCl::XRootDStatus{}, m_url, m_wrapped_file.get());
124  }
125 
126  std::string s3_noslash_url;
127  auto schema_loc = s3_url.find("://");
128  if (schema_loc != std::string::npos) {
129  auto path_loc = s3_url.find('/', schema_loc + 3);
130  if (path_loc != std::string::npos) {
131  if (s3_url.size() >= path_loc && s3_url[path_loc + 1] == '/') {
132  s3_noslash_url = s3_url.substr(0, path_loc) + s3_url.substr(path_loc + 1);
133  }
134  }
135  }
136 
137  std::string https_url, err_msg;
138  if (!Factory::GenerateHttpUrl(s3_noslash_url.empty() ? s3_url : s3_noslash_url, https_url, nullptr, err_msg)) {
139  return std::make_tuple(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidAddr, 0, err_msg), "", nullptr);
140  }
141  auto loc = https_url.find('/', 8); // strlen("https://") -> 8
142  if (loc == std::string::npos) {
143  return std::make_tuple(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidAddr, 0, "Invalid generated URL"), "", nullptr);
144  }
145 
146  XrdCl::URL url;
147  if (!url.FromString(https_url)) {
148  return std::make_tuple(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidAddr, 0, "Invalid generated XrdCl URL"), "", nullptr);
149  }
150  m_url = https_url;
151  std::unique_ptr<XrdCl::File> wrapped_file(new XrdCl::File());
152  // Hack - we need to set a few properties on the file object before the open occurs.
153  // However, the "real" (plugin) file object is not created until the open call.
154  // This forces the plugin object to be created, so we can set the properties and Open later.
155  auto status = wrapped_file->Open(url.GetURL(), XrdCl::OpenFlags::Compress, XrdCl::Access::None, nullptr, time_t(0));
156  if (!status.IsOK()) {
157  return std::make_tuple(status, "", nullptr);
158  }
159 
160  std::stringstream ss;
161  ss << std::hex << reinterpret_cast<long long>(&m_header_callout);
162  if (!wrapped_file->SetProperty("XrdClHttpHeaderCallout", ss.str())) {
163  return std::make_tuple(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInvalidAddr, 0, "Failed to setup header callout"), "", nullptr);
164  }
165  m_wrapped_file.reset(wrapped_file.release());
166 
167  return std::make_tuple(XrdCl::XRootDStatus{}, https_url, m_wrapped_file.get());
168 }
169 
171 File::Open(const std::string &url,
173  XrdCl::Access::Mode mode,
174  XrdCl::ResponseHandler *handler,
175  time_t timeout)
176 {
177  if (IsOpen()) {
178  m_logger->Error(kLogXrdClS3, "URL %s already open", url.c_str());
180  }
181 
182  auto [st, https_url, fs] = GetFileHandle(url);
183  if (!st.IsOK()) {
184  return st;
185  }
186 
187  return fs->Open(https_url, flags, mode, new OpenResponseHandler(&m_is_opened, handler), timeout);
188 }
189 
191 File::PgRead(uint64_t offset,
192  uint32_t size,
193  void *buffer,
194  XrdCl::ResponseHandler *handler,
195  time_t timeout)
196 {
197  return m_wrapped_file->PgRead(offset, size, buffer, handler, timeout);
198 }
199 
201 File::Read(uint64_t offset,
202  uint32_t size,
203  void *buffer,
204  XrdCl::ResponseHandler *handler,
205  time_t timeout)
206 {
207 
208  return m_wrapped_file->Read(offset, size, buffer, handler, timeout);
209 }
210 
211 bool
212 File::SetProperty(const std::string &name,
213  const std::string &value)
214 {
215  std::unique_lock lock(m_properties_mutex);
216  m_properties[name] = value;
217  return true;
218 }
219 
221 File::Stat(bool force,
222  XrdCl::ResponseHandler *handler,
223  time_t timeout)
224 {
225  return m_wrapped_file->Stat(force, handler, timeout);
226 }
227 
229 File::VectorRead(const XrdCl::ChunkList &chunks,
230  void *buffer,
231  XrdCl::ResponseHandler *handler,
232  time_t timeout )
233 {
234  return m_wrapped_file->VectorRead(chunks, buffer, handler, timeout);
235 }
236 
238 File::Write(uint64_t offset,
239  uint32_t size,
240  const void *buffer,
241  XrdCl::ResponseHandler *handler,
242  time_t timeout)
243 {
244  return m_wrapped_file->Write(offset, size, buffer, handler, timeout);
245 }
246 
248 File::Write(uint64_t offset,
249  XrdCl::Buffer &&buffer,
250  XrdCl::ResponseHandler *handler,
251  time_t timeout)
252 {
253  return m_wrapped_file->Write(offset, std::move(buffer), handler, timeout);
254 }
255 
256 std::shared_ptr<XrdClHttp::HeaderCallout::HeaderList>
257 File::S3HeaderCallout::GetHeaders(const std::string &verb,
258  const std::string &url,
260 {
261  std::string auth_token, err_msg;
262  std::shared_ptr<HeaderList> header_list(new HeaderList(headers));
263  if (Factory::GenerateV4Signature(url, verb, *header_list, auth_token, err_msg)) {
264  header_list->emplace_back("Authorization", auth_token);
265  } else {
266  m_parent.m_logger->Error(kLogXrdClS3, "Failed to generate V4 signature: %s", err_msg.c_str());
267  return nullptr;
268  }
269  return header_list;
270 }
std::vector< std::pair< std::string, std::string > > HeaderList
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)
virtual XrdCl::XRootDStatus Stat(bool force, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual bool IsOpen() const override
virtual XrdCl::XRootDStatus Open(const std::string &url, XrdCl::OpenFlags::Flags flags, XrdCl::Access::Mode mode, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual XrdCl::XRootDStatus Read(uint64_t offset, uint32_t size, void *buffer, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual XrdCl::XRootDStatus PgRead(uint64_t offset, uint32_t size, void *buffer, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual XrdCl::XRootDStatus Write(uint64_t offset, uint32_t size, const void *buffer, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual XrdCl::XRootDStatus VectorRead(const XrdCl::ChunkList &chunks, void *buffer, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual XrdCl::XRootDStatus Close(XrdCl::ResponseHandler *handler, time_t timeout) override
virtual bool GetProperty(const std::string &name, std::string &value) const override
virtual bool SetProperty(const std::string &name, const std::string &value) override
File(XrdCl::Log *log)
Binary blob representation.
Definition: XrdClBuffer.hh:34
A file.
Definition: XrdClFile.hh:52
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.
URL representation.
Definition: XrdClURL.hh:31
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
const uint64_t kLogXrdClS3
const uint16_t errInvalidAddr
Definition: XrdClStatus.hh:71
const uint16_t stError
An error occurred that could potentially be retried.
Definition: XrdClStatus.hh:32
const uint16_t errInvalidOp
Definition: XrdClStatus.hh:51
std::vector< ChunkInfo > ChunkList
List of chunks.
Mode
Access mode.
Flags
Open flags, may be or'd when appropriate.
bool IsOK() const
We're fine.
Definition: XrdClStatus.hh:124