Skip to content

Commit d447fee

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 5319787 commit d447fee

6 files changed

Lines changed: 125 additions & 65 deletions

File tree

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 <optional>
15+
#include <string>
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: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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(len, 0);
32+
// Second call extracts the extended attribute value, checking it's of the correct length
33+
if (getxattr(path, xattr, xval.data(), 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+
if (!inputURL)
47+
return std::nullopt;
48+
49+
std::string_view inputSV{inputURL};
50+
if (inputSV.empty() || inputSV.back() == '/')
51+
return std::nullopt;
52+
53+
if (gEnv->GetValue("TFile.CrossProtocolRedirects", 1) != 1)
54+
return std::nullopt;
55+
56+
auto xurl = ROOT::Internal::GetXAttrVal(inputURL, "eos.url.xroot");
57+
if (!xurl)
58+
return std::nullopt;
59+
60+
auto baseName = inputSV.substr(inputSV.find_last_of("/") + 1);
61+
// Sometimes the `getxattr` call may return an invalid URL due
62+
// to the POSIX attribute not being yet completely filled by EOS.
63+
if (!std::equal(baseName.crbegin(), baseName.crend(), xurl->crbegin()))
64+
return std::nullopt;
65+
66+
// Ensure the redirected URL actually starts with the XRootD protocol string
67+
if (ROOT::StartsWith(*xurl, "root://") || ROOT::StartsWith(*xurl, "xroot://") ||
68+
ROOT::StartsWith(*xurl, "roots://") || ROOT::StartsWith(*xurl, "xroots://"))
69+
return xurl;
70+
#else
71+
(void)inputURL;
72+
#endif
73+
return std::nullopt;
74+
}

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
@@ -171,6 +171,7 @@ The structure of a directory is shown in TDirectoryFile::TDirectoryFile
171171
#include "TThreadSlots.h"
172172
#include "TGlobal.h"
173173
#include "ROOT/RConcurrentHashColl.hxx"
174+
#include "ROOT/InternalIOUtils.hxx"
174175
#include <memory>
175176
#include <cinttypes>
176177

@@ -3801,34 +3802,16 @@ TFile *TFile::Open(const char *url, Option_t *options, const char *ftitle,
38013802
TString expandedUrl(url);
38023803
gSystem->ExpandPathName(expandedUrl);
38033804

3804-
#ifdef R__UNIX
3805-
// If URL is a file on an EOS FUSE mount, attempt redirection to XRootD protocol.
3806-
if (gEnv->GetValue("TFile.CrossProtocolRedirects", 1) == 1) {
3807-
TUrl fileurl(expandedUrl, /* default is file */ kTRUE);
3808-
if (strcmp(fileurl.GetProtocol(), "file") == 0) {
3809-
ssize_t len = getxattr(fileurl.GetFile(), "eos.url.xroot", nullptr, 0);
3810-
if (len > 0) {
3811-
std::string xurl(len, 0);
3812-
std::string fileNameFromUrl{fileurl.GetFile()};
3813-
if (getxattr(fileNameFromUrl.c_str(), "eos.url.xroot", &xurl[0], len) == len) {
3814-
// Sometimes the `getxattr` call may return an invalid URL due
3815-
// to the POSIX attribute not being yet completely filled by EOS.
3816-
if (auto baseName = fileNameFromUrl.substr(fileNameFromUrl.find_last_of("/") + 1);
3817-
std::equal(baseName.crbegin(), baseName.crend(), xurl.crbegin())) {
3818-
if ((f = TFile::Open(xurl.c_str(), options, ftitle, compress, netopt))) {
3819-
if (!f->IsZombie()) {
3820-
return f;
3821-
} else {
3822-
delete f;
3823-
f = nullptr;
3824-
}
3825-
}
3826-
}
3827-
}
3805+
if (auto xurl = ROOT::Internal::GetEOSRedirectedXRootURL(expandedUrl.Data())) {
3806+
if ((f = TFile::Open(xurl->c_str(), options, ftitle, compress, netopt))) {
3807+
if (!f->IsZombie()) {
3808+
return f;
3809+
} else {
3810+
delete f;
3811+
f = nullptr;
38283812
}
38293813
}
38303814
}
3831-
#endif
38323815

38333816
// If a timeout has been specified extract the value and try to apply it (it requires
38343817
// 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
@@ -43,23 +43,8 @@
4343
#include "ROOT/RSlotStack.hxx"
4444
#endif
4545

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

6449
#include <algorithm>
6550
#include <atomic>
@@ -370,37 +355,21 @@ RLoopManager::RLoopManager(ROOT::RDF::Experimental::RDatasetSpec &&spec)
370355
ChangeSpec(std::move(spec));
371356
}
372357

373-
#ifdef R__UNIX
374358
namespace {
375359
std::optional<std::string> GetRedirectedSampleId(std::string_view path, std::string_view datasetName)
376360
{
377361
// Mimick the redirection done in TFile::Open to see if the path points to a FUSE-mounted EOS path.
378362
// If so, we create a redirected sample ID with the full xroot URL.
379363
TString expandedUrl(path.data());
380364
gSystem->ExpandPathName(expandedUrl);
381-
if (gEnv->GetValue("TFile.CrossProtocolRedirects", 1) == 1) {
382-
TUrl fileurl(expandedUrl, /* default is file */ kTRUE);
383-
if (strcmp(fileurl.GetProtocol(), "file") == 0) {
384-
ssize_t len = getxattr(fileurl.GetFile(), "eos.url.xroot", nullptr, 0);
385-
if (len > 0) {
386-
std::string xurl(len, 0);
387-
std::string fileNameFromUrl{fileurl.GetFile()};
388-
if (getxattr(fileNameFromUrl.c_str(), "eos.url.xroot", &xurl[0], len) == len) {
389-
// Sometimes the `getxattr` call may return an invalid URL due
390-
// to the POSIX attribute not being yet completely filled by EOS.
391-
if (auto baseName = fileNameFromUrl.substr(fileNameFromUrl.find_last_of("/") + 1);
392-
std::equal(baseName.crbegin(), baseName.crend(), xurl.crbegin())) {
393-
return xurl + '/' + datasetName.data();
394-
}
395-
}
396-
}
397-
}
365+
TUrl fileurl(expandedUrl, /* default is file */ kTRUE);
366+
if (strcmp(fileurl.GetProtocol(), "file") == 0) {
367+
if (auto xurl = ROOT::Internal::GetEOSRedirectedXRootURL(fileurl.GetFile()))
368+
return *xurl + '/' + datasetName.data();
398369
}
399-
400370
return std::nullopt;
401371
}
402372
} // namespace
403-
#endif
404373

405374
/**
406375
* @brief Changes the internal TTree held by the RLoopManager.
@@ -451,11 +420,10 @@ void RLoopManager::ChangeSpec(ROOT::RDF::Experimental::RDatasetSpec &&spec)
451420
// is exposed to users via RSampleInfo and DefinePerSample).
452421
const auto sampleId = files[i] + '/' + trees[i];
453422
fSampleMap.insert({sampleId, &sample});
454-
#ifdef R__UNIX
423+
455424
// Also add redirected EOS xroot URL when available
456425
if (auto redirectedSampleId = GetRedirectedSampleId(files[i], trees[i]))
457426
fSampleMap.insert({redirectedSampleId.value(), &sample});
458-
#endif
459427
}
460428
}
461429
fDataSource = std::make_unique<ROOT::Internal::RDF::RTTreeDS>(std::move(chain), spec.GetFriendInfo());
@@ -473,11 +441,9 @@ void RLoopManager::ChangeSpec(ROOT::RDF::Experimental::RDatasetSpec &&spec)
473441
fileNames.push_back(files[i]);
474442
rntupleNames.insert(trees[i]);
475443

476-
#ifdef R__UNIX
477444
// Also add redirected EOS xroot URL when available
478445
if (auto redirectedSampleId = GetRedirectedSampleId(files[i], trees[i]))
479446
fSampleMap.insert({redirectedSampleId.value(), &sample});
480-
#endif
481447
}
482448
}
483449

0 commit comments

Comments
 (0)