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