Skip to content

Commit ad01cd3

Browse files
authored
Merge pull request #106 from whwjiang/feature_globus
Full Globus Support
2 parents 08a8edc + dff80fd commit ad01cd3

16 files changed

+976
-32
lines changed

CMakeLists.txt

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,24 @@ find_package( Filesystem REQUIRED )
4444
# Similar setup for libatomic; required only on 32-bit systems
4545
find_package( Atomic REQUIRED )
4646

47+
# Add nlohmann/json dependency (use system if available; otherwise, fetch it)
48+
find_package(nlohmann_json 3.11.2 QUIET)
49+
if(NOT nlohmann_json_FOUND)
50+
include(FetchContent)
51+
# Allow a locally-found tarball; if missing, fall back to upstream URL
52+
set(JSON_URL "${CMAKE_CURRENT_SOURCE_DIR}/nlohmann-json-3.11.2.tar.xz")
53+
if(NOT EXISTS "${JSON_URL}")
54+
set(JSON_URL "https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz")
55+
endif()
56+
cmake_policy(SET CMP0135 NEW)
57+
FetchContent_Declare(
58+
nlohmann_json
59+
URL "${JSON_URL}"
60+
URL_HASH SHA256=8c4b26bf4b422252e13f332bc5e388ec0ab5c3443d24399acb675e68278d341f
61+
)
62+
FetchContent_MakeAvailable(nlohmann_json)
63+
endif()
64+
4765
if( NOT XROOTD_EXTERNAL_TINYXML2 )
4866
include(FetchContent)
4967
# Allow a locally-found tinyxml2 tarball to be used; provides the ability for packagers
@@ -116,25 +134,38 @@ target_link_libraries( XrdOssFilterObj ${XRootD_UTILS_LIBRARIES} ${XRootD_SERVER
116134
add_library( XrdOssFilter MODULE "$<TARGET_OBJECTS:XrdOssFilterObj>" )
117135
target_link_libraries( XrdOssFilter XrdOssFilterObj )
118136

137+
######################
138+
## libXrdOssGlobus ##
139+
######################
140+
add_library(XrdOssGlobusObj OBJECT src/HTTPCommands.cc src/GlobusFile.cc src/GlobusFileSystem.cc src/GlobusDirectory.cc src/logging.cc src/stl_string_utils.cc src/shortfile.cc src/TokenFile.cc src/CurlUtil.cc)
141+
set_target_properties(XrdOssGlobusObj PROPERTIES POSITION_INDEPENDENT_CODE ON)
142+
target_include_directories(XrdOssGlobusObj PRIVATE ${XRootD_INCLUDE_DIRS})
143+
target_link_libraries(XrdOssGlobusObj ${XRootD_UTILS_LIBRARIES} ${XRootD_SERVER_LIBRARIES} nlohmann_json::nlohmann_json CURL::libcurl OpenSSL::Crypto Threads::Threads std::filesystem)
144+
145+
add_library(XrdOssGlobus MODULE "$<TARGET_OBJECTS:XrdOssGlobusObj>")
146+
target_link_libraries(XrdOssGlobus XrdOssGlobusObj)
147+
119148
# Customize module's suffix and, on Linux, hide unnecessary symbols
120149
if( APPLE )
121150
set_target_properties( XrdS3 PROPERTIES OUTPUT_NAME "XrdS3-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" )
122151
set_target_properties( XrdHTTPServer PROPERTIES OUTPUT_NAME "XrdHTTPServer-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" )
123152
set_target_properties( XrdOssS3 PROPERTIES OUTPUT_NAME "XrdOssS3-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" )
124153
set_target_properties( XrdOssHttp PROPERTIES OUTPUT_NAME "XrdOssHttp-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" )
125154
set_target_properties( XrdOssFilter PROPERTIES OUTPUT_NAME "XrdOssFilter-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" )
155+
set_target_properties( XrdOssGlobus PROPERTIES OUTPUT_NAME "XrdOssGlobus-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" )
126156
else()
127157
set_target_properties( XrdS3 PROPERTIES OUTPUT_NAME "XrdS3-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/configs/export-lib-symbols" )
128158
set_target_properties( XrdHTTPServer PROPERTIES OUTPUT_NAME "XrdHTTPServer-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/configs/export-lib-symbols" )
129159
set_target_properties( XrdOssS3 PROPERTIES OUTPUT_NAME "XrdOssS3-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/configs/export-lib-symbols" )
130160
set_target_properties( XrdOssHttp PROPERTIES OUTPUT_NAME "XrdOssHttp-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/configs/export-lib-symbols" )
131161
set_target_properties( XrdOssFilter PROPERTIES OUTPUT_NAME "XrdOssFilter-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/configs/export-lib-symbols" )
162+
set_target_properties( XrdOssGlobus PROPERTIES OUTPUT_NAME "XrdOssGlobus-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/configs/export-lib-symbols" )
132163
endif()
133164

134165
include(GNUInstallDirs)
135166

136167
install(
137-
TARGETS XrdS3 XrdHTTPServer XrdOssS3 XrdOssHttp XrdOssFilter
168+
TARGETS XrdS3 XrdHTTPServer XrdOssS3 XrdOssHttp XrdOssFilter XrdOssGlobus
138169
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
139170
)
140171

@@ -152,6 +183,10 @@ if( BUILD_TESTING )
152183
target_link_libraries( XrdOssFilterTesting XrdOssFilterObj )
153184
target_include_directories( XrdOssFilterTesting INTERFACE ${XRootD_INCLUDE_DIRS} )
154185

186+
add_library(XrdOssGlobusTesting SHARED "$<TARGET_OBJECTS:XrdOssGlobusObj>")
187+
target_link_libraries(XrdOssGlobusTesting XrdOssGlobusObj)
188+
target_include_directories(XrdOssGlobusTesting INTERFACE ${XRootD_INCLUDE_DIRS})
189+
155190
find_program(GoWrk go-wrk HINTS "$ENV{HOME}/go/bin")
156191
if( NOT GoWrk )
157192
# Try installing the go-wrk variable to generate a reasonable stress test

rpm/xrootd-s3-http.spec

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Name: xrootd-s3-http
2-
Version: 0.4.1
2+
Version: 0.5.0
33
Release: 1%{?dist}
44
Summary: S3/HTTP filesystem plugins for xrootd
55

@@ -42,12 +42,17 @@ cmake --build redhat-linux-build --verbose
4242
%{_libdir}/libXrdHTTPServer-5.so
4343
%{_libdir}/libXrdS3-5.so
4444
%{_libdir}/libXrdOssHttp-5.so
45+
%{_libdir}/libXrdOssGlobus-5.so
4546
%{_libdir}/libXrdOssS3-5.so
4647
%{_libdir}/libXrdOssFilter-5.so
4748
%doc README.md
4849
%license LICENSE
4950

5051
%changelog
52+
* Wed Jul 30 2025 William Jiang <whjiang@wisc.edu> - 0.5.0-1
53+
- Add support for Globus endpoints (reads, writes, listings)
54+
- Add support for HTTP writes
55+
5156
* Fri May 30 2025 Brian Bockelman <bbockelman@morgridge.org> - 0.4.1-1
5257
- Fix stall timeouts which would never fire.
5358
- Fix bug where S3 rate limiting would result in corrupt data being sent back to the client.

src/GlobusDirectory.cc

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/***************************************************************
2+
*
3+
* Copyright (C) 2025, Pelican Project, Morgridge Institute for Research
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License"); you
6+
* may not use this file except in compliance with the License. You may
7+
* obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
***************************************************************/
18+
19+
#include "GlobusDirectory.hh"
20+
#include "GlobusFileSystem.hh"
21+
#include "HTTPCommands.hh"
22+
#include "logging.hh"
23+
#include "stl_string_utils.hh"
24+
25+
#include <XrdOss/XrdOssWrapper.hh>
26+
#include <XrdSys/XrdSysError.hh>
27+
28+
#include <nlohmann/json.hpp>
29+
30+
#include <sstream>
31+
32+
using json = nlohmann::json;
33+
34+
time_t parseTimestamp(const std::string &last_modified) {
35+
if (!last_modified.empty()) {
36+
struct tm tm_time = {};
37+
if (strptime(last_modified.c_str(), "%Y-%m-%d %H:%M:%S", &tm_time) !=
38+
nullptr) {
39+
return mktime(&tm_time);
40+
}
41+
}
42+
return 0;
43+
}
44+
45+
void GlobusDirectory::Reset() {
46+
m_opened = false;
47+
m_idx = 0;
48+
m_objInfo.clear();
49+
m_directories.clear();
50+
m_stat_buf = nullptr;
51+
m_object = "";
52+
}
53+
54+
int GlobusDirectory::ListGlobusDir() {
55+
m_log.Log(XrdHTTPServer::Debug, "GlobusDirectory::ListGlobusDir",
56+
"Listing directory:", m_object.c_str());
57+
HTTPDownload listCommand(m_fs.getLsUrl(), m_object, m_log,
58+
m_fs.getTransferToken());
59+
60+
if (!listCommand.SendRequest(0, 0)) {
61+
return HTTPRequest::HandleHTTPError(
62+
listCommand, m_log, "Globus directory listing", m_object.c_str());
63+
}
64+
65+
std::string response = listCommand.getResultString();
66+
67+
try {
68+
auto json = json::parse(response);
69+
70+
if (json.contains("DATA") && json["DATA"].is_array()) {
71+
const auto &data = json["DATA"];
72+
for (const auto &item : data) {
73+
if (item.contains("name") && item.contains("size") &&
74+
item.contains("type")) {
75+
GlobusObjectInfo obj;
76+
obj.m_key = item["name"].get<std::string>();
77+
obj.m_size = item["size"].get<size_t>();
78+
79+
if (item.contains("last_modified")) {
80+
obj.m_last_modified =
81+
item["last_modified"].get<std::string>();
82+
}
83+
84+
if (item["type"].get<std::string>() == "file") {
85+
m_objInfo.push_back(obj);
86+
} else if (item["type"].get<std::string>() == "dir") {
87+
std::string dirName = obj.m_key;
88+
if (dirName.back() != '/') {
89+
dirName += "/";
90+
}
91+
obj.m_key = dirName;
92+
m_directories.push_back(obj);
93+
}
94+
}
95+
}
96+
}
97+
98+
} catch (const json::exception &e) {
99+
m_log.Log(XrdHTTPServer::Warning, "GlobusDirectory::ListGlobusDir",
100+
"Failed to parse JSON response:", e.what());
101+
return -EIO;
102+
}
103+
104+
m_idx = 0;
105+
m_opened = true;
106+
107+
return 0;
108+
}
109+
110+
int GlobusDirectory::Opendir(const char *path, XrdOucEnv &env) {
111+
if (m_opened) {
112+
return -EBADF;
113+
}
114+
Reset();
115+
116+
std::string realPath = path;
117+
if (realPath.back() != '/') {
118+
realPath = realPath + "/";
119+
}
120+
121+
std::string storagePrefix = m_fs.getStoragePrefix();
122+
std::string object;
123+
124+
if (realPath.find(storagePrefix) == 0) {
125+
object = realPath.substr(storagePrefix.length());
126+
} else {
127+
object = realPath;
128+
}
129+
130+
if (!object.empty() && object[0] == '/') {
131+
object = object.substr(1);
132+
}
133+
134+
m_object = object;
135+
136+
return ListGlobusDir();
137+
}
138+
139+
int GlobusDirectory::Readdir(char *buff, int blen) {
140+
if (!m_opened) {
141+
return -EBADF;
142+
}
143+
144+
if (m_stat_buf) {
145+
memset(m_stat_buf, '\0', sizeof(struct stat));
146+
}
147+
148+
// m_idx encodes the location inside the current directory.
149+
// - m_idx in [0, m_objInfo.size) means return a "file" from the object
150+
// list.
151+
// - m_idx == m_objInfo.size means return the first entry in the directories
152+
// list.
153+
// - m_idx in (m_directories.size, -1] means return an entry from the
154+
// directories list.
155+
// - m_idx == -m_directories.size means that all the path elements have been
156+
// consumed.
157+
auto idx = m_idx;
158+
if (m_objInfo.empty() && m_directories.empty()) {
159+
*buff = '\0';
160+
return XrdOssOK;
161+
} else if (idx >= 0 && idx < static_cast<ssize_t>(m_objInfo.size())) {
162+
// Return a file entry
163+
m_idx++;
164+
std::string full_name = m_objInfo[idx].m_key;
165+
auto lastSlashIdx = full_name.rfind("/");
166+
if (lastSlashIdx != std::string::npos) {
167+
full_name.erase(0, lastSlashIdx + 1);
168+
}
169+
170+
strncpy(buff, full_name.c_str(), blen);
171+
if (buff[blen - 1] != '\0') {
172+
buff[blen - 1] = '\0';
173+
return -ENOMEM;
174+
}
175+
176+
if (m_stat_buf) {
177+
m_stat_buf->st_mode = 0x0600 | S_IFREG;
178+
m_stat_buf->st_nlink = 1;
179+
m_stat_buf->st_size = m_objInfo[idx].m_size;
180+
time_t timestamp = parseTimestamp(m_objInfo[idx].m_last_modified);
181+
if (timestamp != 0) {
182+
m_stat_buf->st_mtime = timestamp;
183+
m_stat_buf->st_atime = timestamp;
184+
m_stat_buf->st_ctime = timestamp;
185+
}
186+
}
187+
} else if (idx < 0 && -idx == static_cast<ssize_t>(m_directories.size())) {
188+
// All items have been consumed
189+
*buff = '\0';
190+
return XrdOssOK;
191+
} else if (idx == static_cast<ssize_t>(m_objInfo.size()) ||
192+
-idx < static_cast<ssize_t>(m_directories.size())) {
193+
// Handle directory entries
194+
if (m_directories.empty()) {
195+
*buff = '\0';
196+
return XrdOssOK;
197+
}
198+
if (idx == static_cast<ssize_t>(m_objInfo.size())) {
199+
m_idx = -1;
200+
idx = 0;
201+
} else {
202+
idx = -m_idx;
203+
m_idx--;
204+
}
205+
std::string full_name = m_directories[idx].m_key;
206+
if (!full_name.empty() && full_name.back() == '/') {
207+
full_name.pop_back();
208+
}
209+
210+
strncpy(buff, full_name.c_str(), blen);
211+
if (buff[blen - 1] != '\0') {
212+
buff[blen - 1] = '\0';
213+
return -ENOMEM;
214+
}
215+
216+
if (m_stat_buf) {
217+
m_stat_buf->st_mode = 0x0700 | S_IFDIR;
218+
m_stat_buf->st_nlink = 2;
219+
m_stat_buf->st_size = 4096;
220+
time_t timestamp =
221+
parseTimestamp(m_directories[idx].m_last_modified);
222+
if (timestamp != 0) {
223+
m_stat_buf->st_mtime = timestamp;
224+
m_stat_buf->st_atime = timestamp;
225+
m_stat_buf->st_ctime = timestamp;
226+
}
227+
}
228+
} else {
229+
return -EBADF;
230+
}
231+
232+
if (m_stat_buf) {
233+
m_stat_buf->st_uid = 1;
234+
m_stat_buf->st_gid = 1;
235+
if (m_stat_buf->st_mtime == 0) {
236+
m_stat_buf->st_mtime = m_stat_buf->st_ctime = m_stat_buf->st_atime =
237+
0;
238+
}
239+
m_stat_buf->st_dev = 0;
240+
m_stat_buf->st_ino = 1;
241+
}
242+
return XrdOssOK;
243+
}
244+
245+
int GlobusDirectory::StatRet(struct stat *buf) {
246+
if (!m_opened) {
247+
return -EBADF;
248+
}
249+
250+
m_stat_buf = buf;
251+
return XrdOssOK;
252+
}
253+
254+
int GlobusDirectory::Close(long long *retsz) {
255+
if (!m_opened) {
256+
return -EBADF;
257+
}
258+
Reset();
259+
return XrdOssOK;
260+
}

0 commit comments

Comments
 (0)