XRootD
Loading...
Searching...
No Matches
XrdClS3DownloadHandler.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 "XrdClS3Filesystem.hh"
23
26#include <XrdCl/XrdClFile.hh>
27
28#include <charconv>
29
30using namespace XrdClS3;
31
32namespace {
33
34class S3DownloadHandler : public XrdCl::ResponseHandler {
35public:
36 S3DownloadHandler(std::unique_ptr<XrdCl::File> file, XrdCl::ResponseHandler *handler, time_t timeout)
37 : m_expiry(time(NULL) + timeout), m_file(std::move(file)), m_handler(handler), m_buffer(new XrdCl::Buffer(kReadSize))
38 {
39 if (timeout == 0) {
41 XrdCl::DefaultEnv::GetEnv()->GetInt( "RequestTimeout", val );
42 m_expiry += val;
43 }
44 }
45
46 virtual ~S3DownloadHandler() noexcept = default;
47
48 virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) override;
49
50private:
51 time_t m_expiry; // Expiration time for the download operation
52 std::unique_ptr<XrdCl::File> m_file; // File we are reading from
53 XrdCl::ResponseHandler *m_handler; // Handler to call with the final result buffer (or failure).
54 std::unique_ptr<XrdCl::Buffer> m_buffer; // Buffer to hold the data read from the file
55 static constexpr size_t kReadSize = 32 * 1024; // Size of each read operation (32 KB)
56
57 std::pair<time_t, bool> GetTimeout() const {
58 // Calculate the timeout based on the current time and the expiry time
59 time_t now = time(NULL);
60 if (now >= m_expiry) {
61 return {0, false}; // No time left, return 0 timeout
62 }
63 return {m_expiry - now, true};
64 }
65
66 class ReadHandler : public XrdCl::ResponseHandler {
67 public:
68 ReadHandler(std::unique_ptr<S3DownloadHandler> parent) : m_parent(std::move(parent)) {}
69 virtual ~ReadHandler() noexcept = default;
70
71 virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) override;
72
73 // Release ownership of the parent handler back to the caller
74 std::unique_ptr<S3DownloadHandler> TakeParent() { return std::move(m_parent); }
75 private:
76 std::unique_ptr<S3DownloadHandler> m_parent; // Pointer to the parent handler to access its members
77 };
78
79 class CloseHandler : public XrdCl::ResponseHandler {
80 public:
81 CloseHandler(std::unique_ptr<S3DownloadHandler> parent, std::unique_ptr<XrdCl::XRootDStatus> status) : m_parent(std::move(parent)), m_read_status(std::move(status)) {}
82 virtual ~CloseHandler() noexcept = default;
83
84 virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) override;
85
86 private:
87 std::unique_ptr<S3DownloadHandler> m_parent; // Pointer to the parent handler to access its members
88 std::unique_ptr<XrdCl::XRootDStatus> m_read_status; // Status from the read operation; if nullptr, the read was successful
89 };
90};
91
92void
93S3DownloadHandler::HandleResponse(XrdCl::XRootDStatus *status_raw, XrdCl::AnyObject *response_raw)
94{
95 std::unique_ptr<S3DownloadHandler> self(this);
96 std::unique_ptr<XrdCl::XRootDStatus> status(status_raw);
97 std::unique_ptr<XrdCl::AnyObject> response(response_raw);
98
99 // If the open failed, we pass the status up the chain.
100 if (!status || !status->IsOK()) {
101 if (m_handler) m_handler->HandleResponse(status.release(), response.release());
102 return;
103 }
104 auto [timeout, ok] = GetTimeout();
105 if (!ok) {
106 // If we have no time left, we cannot proceed with the read.
107 if (m_handler) {
108 m_handler->HandleResponse(new XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errOperationExpired, 0, "Download operation timed out"), nullptr);
109 }
110 return;
111 }
112
113 // Open succeeded, so we can now read the file.
114 std::unique_ptr<ReadHandler> readHandler(new ReadHandler(std::move(self)));
115 auto st = m_file->Read(0, S3DownloadHandler::kReadSize, m_buffer->GetBufferAtCursor(), readHandler.get(), timeout);
116
117 if (!st.IsOK()) {
118 // The read request failed; take ownership of 'this' back
119 self = readHandler->TakeParent();
120 // We close the file and return the error
121 std::unique_ptr<CloseHandler> closeHandler(new CloseHandler(std::move(self), std::unique_ptr<XrdCl::XRootDStatus>(new XrdCl::XRootDStatus(st))));
122 auto close_st = m_file->Close(closeHandler.get(), timeout);
123 if (close_st.IsOK()) {
124 closeHandler.release(); // The close handler now owns itself
125 } else {
126 if (m_handler) {
127 m_handler->HandleResponse(new XrdCl::XRootDStatus(close_st), nullptr);
128 }
129 }
130 return;
131 }
132
133 // if read succeeds relinquish ownership
134 readHandler.release(); // Read now owns the handler
135}
136
137void
138S3DownloadHandler::ReadHandler::HandleResponse(XrdCl::XRootDStatus *status_raw, XrdCl::AnyObject *response_raw) {
139 std::unique_ptr<ReadHandler> self(this);
140 std::unique_ptr<XrdCl::XRootDStatus> status(status_raw);
141 std::unique_ptr<XrdCl::AnyObject> response(response_raw);
142
143 auto [timeout, ok] = m_parent->GetTimeout();
144 if (!ok) {
145 // If we have no time left, we cannot proceed with the read.
146 if (m_parent->m_handler) {
147 m_parent->m_handler->HandleResponse(new XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errOperationExpired, 0, "Download operation timed out"), nullptr);
148 }
149 return;
150 }
151
152 if (!status || !status->IsOK()) {
153 auto parent = m_parent.get();
154 std::unique_ptr<CloseHandler> closeHandler(new CloseHandler(std::move(m_parent), std::move(status)));
155 auto st = parent->m_file->Close(closeHandler.get(), timeout);
156 if (st.IsOK()) {
157 closeHandler.release();
158 } else if (parent->m_handler) {
159 parent->m_handler->HandleResponse(new XrdCl::XRootDStatus(st), nullptr);
160 }
161 return;
162 }
163
164 XrdCl::ChunkInfo *chunkInfo = nullptr;
165 response->Get(chunkInfo);
166 if (!chunkInfo) {
167 // If we didn't get a chunk, we can close the file and return.
168 auto parent = m_parent.get();
169 std::unique_ptr<CloseHandler> closeHandler(new CloseHandler(std::move(m_parent),
170 std::unique_ptr<XrdCl::XRootDStatus>(new XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errInternal, 0, "No chunk info received"))));
171 auto st = parent->m_file->Close(closeHandler.get(), timeout);
172 if (st.IsOK()) {
173 closeHandler.release();
174 } else if (parent->m_handler) {
175 parent->m_handler->HandleResponse(new XrdCl::XRootDStatus(st), nullptr);
176 }
177 return;
178 }
179
180 // If we got a chunk but the length is zero, that is the end of the file;
181 // we can close the file and return.
182 if (chunkInfo->GetLength() == 0) {
183 m_parent->m_buffer->ReAllocate(m_parent->m_buffer->GetCursor());
184 auto parent = m_parent.get();
185 std::unique_ptr<CloseHandler> closeHandler(new CloseHandler(std::move(m_parent), nullptr));
186 auto st = parent->m_file->Close(closeHandler.get(), timeout);
187 if (st.IsOK()) {
188 closeHandler.release();
189 } else if (parent->m_handler) {
190 parent->m_handler->HandleResponse(new XrdCl::XRootDStatus(st), nullptr);
191 }
192 return;
193 }
194
195 // Read was successful; read additional data if available.
196 m_parent->m_buffer->AdvanceCursor(chunkInfo->GetLength());
197 m_parent->m_buffer->ReAllocate(m_parent->m_buffer->GetCursor() + S3DownloadHandler::kReadSize);
198 // Pass to Read a non-owning pointer
199 auto st = m_parent->m_file->Read(m_parent->m_buffer->GetCursor(), kReadSize, m_parent->m_buffer->GetBufferAtCursor(), self.get(), timeout);
200 if (!st.IsOK()) {
201 // If the read request failed, close or delete the parent handler.
202 // We still own the handler, pass it to close handler
203 auto parent = m_parent.get();
204 std::unique_ptr<CloseHandler> closeHandler(new CloseHandler(std::move(m_parent), nullptr));
205 auto close_st = parent->m_file->Close(closeHandler.get(), timeout);
206 if (close_st.IsOK()) {
207 closeHandler.release();
208 } else if (parent->m_handler) {
209 parent->m_handler->HandleResponse(new XrdCl::XRootDStatus(close_st), nullptr);
210 }
211 return;
212 }
213
214 // Release ownership if Read returns successfully
215 self.release();
216}
217
218void
219S3DownloadHandler::CloseHandler::HandleResponse(XrdCl::XRootDStatus *status_raw, XrdCl::AnyObject *response_raw) {
220 std::unique_ptr<CloseHandler> self(this);
221 std::unique_ptr<XrdCl::XRootDStatus> status(status_raw);
222 std::unique_ptr<XrdCl::AnyObject> response(response_raw);
223
224 // If there was a read error, then we report that to the handler and ignore the close status.
225 if (m_read_status) {
226 // If we had a read status, we pass it up the chain.
227 if (m_parent->m_handler) {
228 m_parent->m_handler->HandleResponse(m_read_status.release(), nullptr);
229 }
230 return;
231 }
232
233 if (!status || !status->IsOK()) {
234 if (m_parent->m_handler) {
235 m_parent->m_handler->HandleResponse(status.release(), nullptr);
236 }
237 return;
238 }
239
240 // If the close was successful, we can pass the buffer to the handler.
241 response.reset(new XrdCl::AnyObject());
242 response->Set(m_parent->m_buffer.release(), true); // Take ownership of the buffer
243 if (m_parent->m_handler) {
244 m_parent->m_handler->HandleResponse(status.release(), response.release());
245 }
246}
247
248} // namespace
249
250XrdCl::XRootDStatus
251XrdClS3::DownloadUrl(const std::string &url, XrdClHttp::HeaderCallout *header_callout, XrdCl::ResponseHandler *handler, time_t timeout)
252{
253 std::unique_ptr<XrdCl::File> http_file(new XrdCl::File(url));
254
255 if (header_callout) {
256 auto callout_loc = reinterpret_cast<long long>(header_callout);
257 size_t buf_size = 16;
258 char callout_buf[buf_size];
259 std::to_chars_result result = std::to_chars(callout_buf, callout_buf + buf_size - 1, callout_loc, 16);
260 if (result.ec == std::errc{}) {
261 std::string callout_str(callout_buf, result.ptr - callout_buf);
262 http_file->SetProperty("XrdClHttpHeaderCallout", callout_str);
263 }
264 }
265 http_file->SetProperty("XrdClHttpFullDownload", "true");
266
267 auto http_file_raw = http_file.get();
268 S3DownloadHandler *downloadHandler = new S3DownloadHandler(std::move(http_file), handler, timeout);
269
270 return http_file_raw->Open(url, XrdCl::OpenFlags::Read, XrdCl::Access::None, downloadHandler, timeout);
271}
static void parent()
XrdOucString File
static Env * GetEnv()
Get default client environment.
bool GetInt(const std::string &key, int &value)
Definition XrdClEnv.cc:115
A file.
Definition XrdClFile.hh:52
Handle an async response.
XrdCl::XRootDStatus DownloadUrl(const std::string &url, XrdClHttp::HeaderCallout *header_callout, XrdCl::ResponseHandler *handler, time_t timeout)
const uint16_t errOperationExpired
const uint16_t stError
An error occurred that could potentially be retried.
const uint16_t errInternal
Internal error.
const int DefaultRequestTimeout
uint32_t GetLength() const
Get the data length.
@ Read
Open only for reading.