Skip to content

Commit ab97475

Browse files
committed
[io] Centralise XRootD EOS URL redirection
The logic to extract the extended attribute 'eos.url.xroot' from a path to an EOS file on a FUSE mount is centralised in a separate header in RIO, with an internal function called GetEOSRedirectedXRootUrl. This function can be called anywhere needed in the rest of ROOT. With this commit, the following places use this functionality: - TFile::Open (done before this commit) - RLoopManager::ChangeSpec (done before this commit) - RRawFile::Create (introduced in this commit) In particular the last means introducing the redirection also for RNTupleReader, which follows a different logic to open the TFile.
1 parent 155fb39 commit ab97475

File tree

6 files changed

+107
-65
lines changed

6 files changed

+107
-65
lines changed

io/io/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ if (uring)
2121
endif ()
2222

2323
ROOT_LINKER_LIBRARY(RIO
24+
src/IOUtils.cxx
2425
src/RConcurrentHashColl.cxx
2526
src/RFile.cxx
2627
src/RRawFile.cxx
@@ -79,6 +80,7 @@ set_source_files_properties(src/RConcurrentHashColl.cxx
7980
PROPERTIES COMPILE_FLAGS -I${CMAKE_SOURCE_DIR}/core/foundation/res)
8081

8182
ROOT_GENERATE_DICTIONARY(G__RIO
83+
ROOT/IOUtils.hxx
8284
ROOT/RConcurrentHashColl.hxx
8385
ROOT/RFile.hxx
8486
ROOT/RRawFile.hxx

io/io/inc/ROOT/IOUtils.hxx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*************************************************************************
2+
* Copyright (C) 1995-2025, Rene Brun and Fons Rademakers. *
3+
* All rights reserved. *
4+
* *
5+
* For the licensing terms see $ROOTSYS/LICENSE. *
6+
* For the list of contributors see $ROOTSYS/README/CREDITS. *
7+
*************************************************************************/
8+
9+
#ifndef ROOT_IO_UTILS
10+
#define ROOT_IO_UTILS
11+
12+
#include <string>
13+
#include <optional>
14+
15+
namespace ROOT::Internal {
16+
17+
/// \brief Estimate size of extended attribute on path
18+
/// \param path Path to the file to check
19+
/// \param xattr Extended attribute to look for in the file
20+
/// \return Size of the extended attribute. Positive integer is the size of the attribute, -1 on failure.
21+
auto GetXAttrSize(std::string_view path, std::string_view xattr);
22+
23+
/// \brief Redirects the input URL to the equivalent XRootD path on EOS
24+
/// \param inputUrl The input URL to redirect
25+
/// \return The redirected URL in case of successfull redirection, empty optional otherwise
26+
std::optional<std::string> GetEOSRedirectedXRootUrl(std::string_view inputUrl);
27+
} // namespace ROOT::Internal
28+
29+
#endif

io/io/src/IOUtils.cxx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include "ROOT/IOUtils.hxx"
2+
3+
#ifdef R__UNIX
4+
#ifdef R__FBSD
5+
#include <sys/extattr.h>
6+
#else
7+
#include <sys/xattr.h>
8+
#endif
9+
10+
#ifdef R__MACOSX
11+
/* On macOS getxattr takes two extra arguments that should be set to 0 */
12+
#define getxattr(path, name, value, size) getxattr(path, name, value, size, 0u, 0)
13+
#endif
14+
15+
#ifdef R__FBSD
16+
#define getxattr(path, name, value, size) extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, value, size)
17+
#endif
18+
19+
#include "TEnv.h"
20+
#endif
21+
22+
auto ROOT::Internal::GetXAttrSize(std::string_view path, std::string_view xattr)
23+
{
24+
#ifdef R__UNIX
25+
// Assumes the inputs are null-terminated
26+
return getxattr(path.data(), attr.data(), nullptr, 0);
27+
#else
28+
(void)path;
29+
(void)xattr;
30+
return -1;
31+
#endif
32+
}
33+
34+
std::optional<std::string> ROOT::Internal::GetEOSRedirectedXRootUrl(std::string_view inputUrl)
35+
{
36+
if (inputUrl.empty())
37+
return std::nullopt;
38+
39+
#ifdef R__UNIX
40+
// Assumes the input is null-terminated.
41+
if (gEnv->GetValue("TFile.CrossProtocolRedirects", 1) == 1) {
42+
std::string_view xAttr{"eos.url.xroot"};
43+
auto len = ROOT::Internal::GetXAttrSize(inputUrl, xAttr);
44+
if (len > 0) {
45+
std::string xurl(len, 0);
46+
if (getxattr(inputUrl.data(), "eos.url.xroot", &xurl[0], len) == len) {
47+
// Sometimes the `getxattr` call may return an invalid URL due
48+
// to the POSIX attribute not being yet completely filled by EOS.
49+
if (inputUrl.back() != '/') {
50+
if (auto baseName = inputUrl.substr(inputUrl.find_last_of("/") + 1);
51+
std::equal(baseName.crbegin(), baseName.crend(), xurl.crbegin())) {
52+
return xurl;
53+
}
54+
}
55+
}
56+
}
57+
}
58+
#endif
59+
return std::nullopt;
60+
}

io/io/src/RRawFile.cxx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "TError.h"
2121
#include "TPluginManager.h"
2222
#include "TROOT.h"
23+
#include "ROOT/IOUtils.hxx"
2324

2425
#include <algorithm>
2526
#include <cctype> // for towlower
@@ -68,6 +69,8 @@ ROOT::Internal::RRawFile::Create(std::string_view url, ROptions options)
6869
#ifdef _WIN32
6970
return std::unique_ptr<RRawFile>(new RRawFileWin(url, options));
7071
#else
72+
if (auto xurl = ROOT::Internal::GetEOSRedirectedXRootUrl(url))
73+
return Create(*xurl, options);
7174
return std::unique_ptr<RRawFile>(new RRawFileUnix(url, options));
7275
#endif
7376
}

io/io/src/TFile.cxx

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ The structure of a directory is shown in TDirectoryFile::TDirectoryFile
150150
#include "TThreadSlots.h"
151151
#include "TGlobal.h"
152152
#include "ROOT/RConcurrentHashColl.hxx"
153+
#include "ROOT/IOUtils.hxx"
153154
#include <memory>
154155
#include <cinttypes>
155156

@@ -3778,34 +3779,16 @@ TFile *TFile::Open(const char *url, Option_t *options, const char *ftitle,
37783779
TString expandedUrl(url);
37793780
gSystem->ExpandPathName(expandedUrl);
37803781

3781-
#ifdef R__UNIX
3782-
// If URL is a file on an EOS FUSE mount, attempt redirection to XRootD protocol.
3783-
if (gEnv->GetValue("TFile.CrossProtocolRedirects", 1) == 1) {
3784-
TUrl fileurl(expandedUrl, /* default is file */ kTRUE);
3785-
if (strcmp(fileurl.GetProtocol(), "file") == 0) {
3786-
ssize_t len = getxattr(fileurl.GetFile(), "eos.url.xroot", nullptr, 0);
3787-
if (len > 0) {
3788-
std::string xurl(len, 0);
3789-
std::string fileNameFromUrl{fileurl.GetFile()};
3790-
if (getxattr(fileNameFromUrl.c_str(), "eos.url.xroot", &xurl[0], len) == len) {
3791-
// Sometimes the `getxattr` call may return an invalid URL due
3792-
// to the POSIX attribute not being yet completely filled by EOS.
3793-
if (auto baseName = fileNameFromUrl.substr(fileNameFromUrl.find_last_of("/") + 1);
3794-
std::equal(baseName.crbegin(), baseName.crend(), xurl.crbegin())) {
3795-
if ((f = TFile::Open(xurl.c_str(), options, ftitle, compress, netopt))) {
3796-
if (!f->IsZombie()) {
3797-
return f;
3798-
} else {
3799-
delete f;
3800-
f = nullptr;
3801-
}
3802-
}
3803-
}
3804-
}
3782+
if (auto xurl = ROOT::Internal::GetEOSRedirectedXRootUrl(expandedUrl.View())) {
3783+
if ((f = TFile::Open(xurl->c_str(), options, ftitle, compress, netopt))) {
3784+
if (!f->IsZombie()) {
3785+
return f;
3786+
} else {
3787+
delete f;
3788+
f = nullptr;
38053789
}
38063790
}
38073791
}
3808-
#endif
38093792

38103793
// If a timeout has been specified extract the value and try to apply it (it requires
38113794
// support for asynchronous open, though; the following is completely transparent if

tree/dataframe/src/RLoopManager.cxx

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,8 @@
4242
#include "ROOT/RSlotStack.hxx"
4343
#endif
4444

45-
#ifdef R__UNIX
46-
// Functions needed to perform EOS XRootD redirection in ChangeSpec
47-
#include "TEnv.h"
45+
#include "ROOT/IOUtils.hxx"
4846
#include "TSystem.h"
49-
#ifndef R__FBSD
50-
#include <sys/xattr.h>
51-
#else
52-
#include <sys/extattr.h>
53-
#endif
54-
#ifdef R__MACOSX
55-
/* On macOS getxattr takes two extra arguments that should be set to 0 */
56-
#define getxattr(path, name, value, size) getxattr(path, name, value, size, 0u, 0)
57-
#endif
58-
#ifdef R__FBSD
59-
#define getxattr(path, name, value, size) extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, value, size)
60-
#endif
61-
#endif
6247

6348
#include <algorithm>
6449
#include <atomic>
@@ -267,37 +252,20 @@ RLoopManager::RLoopManager(ROOT::RDF::Experimental::RDatasetSpec &&spec)
267252
ChangeSpec(std::move(spec));
268253
}
269254

270-
#ifdef R__UNIX
271255
namespace {
272256
std::optional<std::string> GetRedirectedSampleId(std::string_view path, std::string_view datasetName)
273257
{
274258
// Mimick the redirection done in TFile::Open to see if the path points to a FUSE-mounted EOS path.
275259
// If so, we create a redirected sample ID with the full xroot URL.
276260
TString expandedUrl(path.data());
277261
gSystem->ExpandPathName(expandedUrl);
278-
if (gEnv->GetValue("TFile.CrossProtocolRedirects", 1) == 1) {
279-
TUrl fileurl(expandedUrl, /* default is file */ kTRUE);
280-
if (strcmp(fileurl.GetProtocol(), "file") == 0) {
281-
ssize_t len = getxattr(fileurl.GetFile(), "eos.url.xroot", nullptr, 0);
282-
if (len > 0) {
283-
std::string xurl(len, 0);
284-
std::string fileNameFromUrl{fileurl.GetFile()};
285-
if (getxattr(fileNameFromUrl.c_str(), "eos.url.xroot", &xurl[0], len) == len) {
286-
// Sometimes the `getxattr` call may return an invalid URL due
287-
// to the POSIX attribute not being yet completely filled by EOS.
288-
if (auto baseName = fileNameFromUrl.substr(fileNameFromUrl.find_last_of("/") + 1);
289-
std::equal(baseName.crbegin(), baseName.crend(), xurl.crbegin())) {
290-
return xurl + '/' + datasetName.data();
291-
}
292-
}
293-
}
294-
}
295-
}
262+
TUrl fileurl(expandedUrl, /* default is file */ kTRUE);
263+
if (auto xurl = ROOT::Internal::GetEOSRedirectedXRootUrl(fileurl.GetFile()))
264+
return *xurl + '/' + datasetName.data();
296265

297266
return std::nullopt;
298267
}
299268
} // namespace
300-
#endif
301269

302270
/**
303271
* @brief Changes the internal TTree held by the RLoopManager.
@@ -348,11 +316,10 @@ void RLoopManager::ChangeSpec(ROOT::RDF::Experimental::RDatasetSpec &&spec)
348316
// is exposed to users via RSampleInfo and DefinePerSample).
349317
const auto sampleId = files[i] + '/' + trees[i];
350318
fSampleMap.insert({sampleId, &sample});
351-
#ifdef R__UNIX
319+
352320
// Also add redirected EOS xroot URL when available
353321
if (auto redirectedSampleId = GetRedirectedSampleId(files[i], trees[i]))
354322
fSampleMap.insert({redirectedSampleId.value(), &sample});
355-
#endif
356323
}
357324
}
358325
fDataSource = std::make_unique<ROOT::Internal::RDF::RTTreeDS>(std::move(chain), spec.GetFriendInfo());
@@ -370,11 +337,9 @@ void RLoopManager::ChangeSpec(ROOT::RDF::Experimental::RDatasetSpec &&spec)
370337
fileNames.push_back(files[i]);
371338
rntupleNames.insert(trees[i]);
372339

373-
#ifdef R__UNIX
374340
// Also add redirected EOS xroot URL when available
375341
if (auto redirectedSampleId = GetRedirectedSampleId(files[i], trees[i]))
376342
fSampleMap.insert({redirectedSampleId.value(), &sample});
377-
#endif
378343
}
379344
}
380345

0 commit comments

Comments
 (0)