Skip to content

Commit c6f6051

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 775ebc8 commit c6f6051

File tree

6 files changed

+120
-65
lines changed

6 files changed

+120
-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/InternalIOUtils.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/InternalIOUtils.hxx
8284
ROOT/RConcurrentHashColl.hxx
8385
ROOT/RFile.hxx
8486
ROOT/RRawFile.hxx

io/io/inc/ROOT/InternalIOUtils.hxx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
// Author: Vincenzo Eduardo Padulano (CERN), 11/2025
10+
11+
#ifndef ROOT_IO_UTILS
12+
#define ROOT_IO_UTILS
13+
14+
#include <string>
15+
#include <optional>
16+
17+
namespace ROOT::Internal {
18+
19+
/// \brief Get extended attribute value from path
20+
/// \param path Path to the file to check
21+
/// \param xattr Extended attribute to evaluate
22+
/// \return The string containing the extended attribute value if found, std::nullopt otherwise
23+
std::optional<std::string> GetXAttrVal(const char *path, const char *xattr);
24+
25+
/// \brief Redirects the input URL to the equivalent XRootD path on EOS
26+
/// \param inputUrl The input URL to redirect
27+
/// \return The redirected URL in case of successful redirection, std::nullopt otherwise
28+
std::optional<std::string> GetEOSRedirectedXRootURL(const char *inputURL);
29+
} // namespace ROOT::Internal
30+
31+
#endif

io/io/src/InternalIOUtils.cxx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#include "ROOT/InternalIOUtils.hxx"
2+
3+
#include "ROOT/RConfig.hxx" // R__UNIX
4+
5+
#ifdef R__UNIX
6+
// getxattr
7+
#ifdef R__FBSD
8+
#include <sys/extattr.h>
9+
#else
10+
#include <sys/xattr.h>
11+
#endif
12+
13+
#ifdef R__MACOSX
14+
/* On macOS getxattr takes two extra arguments that should be set to 0 */
15+
#define getxattr(path, name, value, size) getxattr(path, name, value, size, 0u, 0)
16+
#endif
17+
18+
#ifdef R__FBSD
19+
#define getxattr(path, name, value, size) extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, value, size)
20+
#endif
21+
22+
#include "ROOT/StringUtils.hxx" // ROOT::StartsWith
23+
#include "TEnv.h" // TEnv::GetValue
24+
#endif
25+
26+
std::optional<std::string> ROOT::Internal::GetXAttrVal(const char *path, const char *xattr)
27+
{
28+
#ifdef R__UNIX
29+
// First call to getxattr evaluates the length of the extended attribute value
30+
if (auto len = getxattr(path, xattr, nullptr, 0); len > 0) {
31+
std::string xval{};
32+
// Second call extracts the extended attribute value, checking it's of the correct length
33+
if (getxattr(path, xattr, &xval[0], len) == len)
34+
return xval;
35+
}
36+
#else
37+
(void)path;
38+
(void)xattr;
39+
#endif
40+
return std::nullopt;
41+
}
42+
43+
std::optional<std::string> ROOT::Internal::GetEOSRedirectedXRootURL(const char *inputURL)
44+
{
45+
#ifdef R__UNIX
46+
std::string_view inputSV{inputURL};
47+
if (inputSV.empty())
48+
return std::nullopt;
49+
50+
if (gEnv->GetValue("TFile.CrossProtocolRedirects", 1) == 1) {
51+
if (auto xurl = ROOT::Internal::GetXAttrVal(inputURL, "eos.url.xroot")) {
52+
// Sometimes the `getxattr` call may return an invalid URL due
53+
// to the POSIX attribute not being yet completely filled by EOS.
54+
if (inputSV.back() != '/') {
55+
if (auto baseName = inputSV.substr(inputSV.find_last_of("/") + 1);
56+
std::equal(baseName.crbegin(), baseName.crend(), xurl->crbegin())) {
57+
// Ensure the redirected URL actually starts with the XRootD protocol string
58+
if (ROOT::StartsWith(*xurl, "root://")) {
59+
return xurl;
60+
}
61+
}
62+
}
63+
}
64+
}
65+
#else
66+
(void)inputURL;
67+
#endif
68+
return std::nullopt;
69+
}

io/io/src/RRawFile.cxx

Lines changed: 4 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/InternalIOUtils.hxx"
2324

2425
#include <algorithm>
2526
#include <cctype> // for towlower
@@ -68,6 +69,9 @@ 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+
// We're assuming the input url is null-terminated in the next call
73+
if (auto xurl = ROOT::Internal::GetEOSRedirectedXRootURL(url.data()))
74+
return Create(*xurl, options);
7175
return std::unique_ptr<RRawFile>(new RRawFileUnix(url, options));
7276
#endif
7377
}

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/InternalIOUtils.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.Data())) {
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: 6 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/InternalIOUtils.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,21 @@ 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-
}
262+
TUrl fileurl(expandedUrl, /* default is file */ kTRUE);
263+
if (strcmp(fileurl.GetProtocol(), "file") == 0) {
264+
if (auto xurl = ROOT::Internal::GetEOSRedirectedXRootURL(fileurl.GetFile()))
265+
return *xurl + '/' + datasetName.data();
295266
}
296-
297267
return std::nullopt;
298268
}
299269
} // namespace
300-
#endif
301270

302271
/**
303272
* @brief Changes the internal TTree held by the RLoopManager.
@@ -348,11 +317,10 @@ void RLoopManager::ChangeSpec(ROOT::RDF::Experimental::RDatasetSpec &&spec)
348317
// is exposed to users via RSampleInfo and DefinePerSample).
349318
const auto sampleId = files[i] + '/' + trees[i];
350319
fSampleMap.insert({sampleId, &sample});
351-
#ifdef R__UNIX
320+
352321
// Also add redirected EOS xroot URL when available
353322
if (auto redirectedSampleId = GetRedirectedSampleId(files[i], trees[i]))
354323
fSampleMap.insert({redirectedSampleId.value(), &sample});
355-
#endif
356324
}
357325
}
358326
fDataSource = std::make_unique<ROOT::Internal::RDF::RTTreeDS>(std::move(chain), spec.GetFriendInfo());
@@ -370,11 +338,9 @@ void RLoopManager::ChangeSpec(ROOT::RDF::Experimental::RDatasetSpec &&spec)
370338
fileNames.push_back(files[i]);
371339
rntupleNames.insert(trees[i]);
372340

373-
#ifdef R__UNIX
374341
// Also add redirected EOS xroot URL when available
375342
if (auto redirectedSampleId = GetRedirectedSampleId(files[i], trees[i]))
376343
fSampleMap.insert({redirectedSampleId.value(), &sample});
377-
#endif
378344
}
379345
}
380346

0 commit comments

Comments
 (0)