Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions io/io/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ if (uring)
endif ()

ROOT_LINKER_LIBRARY(RIO
src/InternalIOUtils.cxx
src/RConcurrentHashColl.cxx
src/RFile.cxx
src/RRawFile.cxx
Expand Down Expand Up @@ -79,6 +80,7 @@ set_source_files_properties(src/RConcurrentHashColl.cxx
PROPERTIES COMPILE_FLAGS -I${CMAKE_SOURCE_DIR}/core/foundation/res)

ROOT_GENERATE_DICTIONARY(G__RIO
ROOT/InternalIOUtils.hxx
ROOT/RConcurrentHashColl.hxx
ROOT/RFile.hxx
ROOT/RRawFile.hxx
Expand Down
31 changes: 31 additions & 0 deletions io/io/inc/ROOT/InternalIOUtils.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*************************************************************************
* Copyright (C) 1995-2025, Rene Brun and Fons Rademakers. *
* All rights reserved. *
* *
* For the licensing terms see $ROOTSYS/LICENSE. *
* For the list of contributors see $ROOTSYS/README/CREDITS. *
*************************************************************************/

// Author: Vincenzo Eduardo Padulano (CERN), 11/2025

#ifndef ROOT_IO_UTILS
#define ROOT_IO_UTILS

#include <optional>
#include <string>

namespace ROOT::Internal {

/// \brief Get extended attribute value from path
/// \param path Path to the file to check
/// \param xattr Extended attribute to evaluate
/// \return The string containing the extended attribute value if found, std::nullopt otherwise
std::optional<std::string> GetXAttrVal(const char *path, const char *xattr);

/// \brief Redirects the input URL to the equivalent XRootD path on EOS
/// \param inputUrl The input URL to redirect
/// \return The redirected URL in case of successful redirection, std::nullopt otherwise
std::optional<std::string> GetEOSRedirectedXRootURL(const char *inputURL);
} // namespace ROOT::Internal

#endif
74 changes: 74 additions & 0 deletions io/io/src/InternalIOUtils.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "ROOT/InternalIOUtils.hxx"

#include "ROOT/RConfig.hxx" // R__UNIX

#ifdef R__UNIX
// getxattr
#ifdef R__FBSD
#include <sys/extattr.h>
#else
#include <sys/xattr.h>
#endif

#ifdef R__MACOSX
/* On macOS getxattr takes two extra arguments that should be set to 0 */
#define getxattr(path, name, value, size) getxattr(path, name, value, size, 0u, 0)
#endif

#ifdef R__FBSD
#define getxattr(path, name, value, size) extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, value, size)
#endif

#include "ROOT/StringUtils.hxx" // ROOT::StartsWith
#include "TEnv.h" // TEnv::GetValue
#endif

std::optional<std::string> ROOT::Internal::GetXAttrVal(const char *path, const char *xattr)
{
#ifdef R__UNIX
// First call to getxattr evaluates the length of the extended attribute value
if (auto len = getxattr(path, xattr, nullptr, 0); len > 0) {
std::string xval(len, 0);
// Second call extracts the extended attribute value, checking it's of the correct length
if (getxattr(path, xattr, xval.data(), len) == len)
return xval;
}
#else
(void)path;
(void)xattr;
#endif
return std::nullopt;
}

std::optional<std::string> ROOT::Internal::GetEOSRedirectedXRootURL(const char *inputURL)
{
#ifdef R__UNIX
if (!inputURL)
return std::nullopt;

std::string_view inputSV{inputURL};
if (inputSV.empty() || inputSV.back() == '/')
Comment thread
vepadulano marked this conversation as resolved.
return std::nullopt;

if (gEnv->GetValue("TFile.CrossProtocolRedirects", 1) != 1)
return std::nullopt;

auto xurl = ROOT::Internal::GetXAttrVal(inputURL, "eos.url.xroot");
if (!xurl)
return std::nullopt;

auto baseName = inputSV.substr(inputSV.find_last_of("/") + 1);
// Sometimes the `getxattr` call may return an invalid URL due
// to the POSIX attribute not being yet completely filled by EOS.
if (!std::equal(baseName.crbegin(), baseName.crend(), xurl->crbegin()))
return std::nullopt;

// Ensure the redirected URL actually starts with the XRootD protocol string
if (ROOT::StartsWith(*xurl, "root://") || ROOT::StartsWith(*xurl, "xroot://") ||
ROOT::StartsWith(*xurl, "roots://") || ROOT::StartsWith(*xurl, "xroots://"))
return xurl;
#else
(void)inputURL;
#endif
return std::nullopt;
}
4 changes: 4 additions & 0 deletions io/io/src/RRawFile.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "TError.h"
#include "TPluginManager.h"
#include "TROOT.h"
#include "ROOT/InternalIOUtils.hxx"

#include <algorithm>
#include <cctype> // for towlower
Expand Down Expand Up @@ -68,6 +69,9 @@ ROOT::Internal::RRawFile::Create(std::string_view url, ROptions options)
#ifdef _WIN32
return std::unique_ptr<RRawFile>(new RRawFileWin(url, options));
#else
// We're assuming the input url is null-terminated in the next call
if (auto xurl = ROOT::Internal::GetEOSRedirectedXRootURL(url.data()))
return Create(*xurl, options);
return std::unique_ptr<RRawFile>(new RRawFileUnix(url, options));
#endif
}
Expand Down
33 changes: 8 additions & 25 deletions io/io/src/TFile.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ The structure of a directory is shown in TDirectoryFile::TDirectoryFile
#include "TThreadSlots.h"
#include "TGlobal.h"
#include "ROOT/RConcurrentHashColl.hxx"
#include "ROOT/InternalIOUtils.hxx"
#include <memory>
#include <cinttypes>

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

#ifdef R__UNIX
// If URL is a file on an EOS FUSE mount, attempt redirection to XRootD protocol.
if (gEnv->GetValue("TFile.CrossProtocolRedirects", 1) == 1) {
TUrl fileurl(expandedUrl, /* default is file */ kTRUE);
if (strcmp(fileurl.GetProtocol(), "file") == 0) {
ssize_t len = getxattr(fileurl.GetFile(), "eos.url.xroot", nullptr, 0);
if (len > 0) {
std::string xurl(len, 0);
std::string fileNameFromUrl{fileurl.GetFile()};
if (getxattr(fileNameFromUrl.c_str(), "eos.url.xroot", &xurl[0], len) == len) {
// Sometimes the `getxattr` call may return an invalid URL due
// to the POSIX attribute not being yet completely filled by EOS.
if (auto baseName = fileNameFromUrl.substr(fileNameFromUrl.find_last_of("/") + 1);
std::equal(baseName.crbegin(), baseName.crend(), xurl.crbegin())) {
if ((f = TFile::Open(xurl.c_str(), options, ftitle, compress, netopt))) {
if (!f->IsZombie()) {
return f;
} else {
delete f;
f = nullptr;
}
}
}
}
if (auto xurl = ROOT::Internal::GetEOSRedirectedXRootURL(expandedUrl.Data())) {
if ((f = TFile::Open(xurl->c_str(), options, ftitle, compress, netopt))) {
if (!f->IsZombie()) {
return f;
} else {
delete f;
f = nullptr;
}
}
}
#endif

// If a timeout has been specified extract the value and try to apply it (it requires
// support for asynchronous open, though; the following is completely transparent if
Expand Down
46 changes: 6 additions & 40 deletions tree/dataframe/src/RLoopManager.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,8 @@
#include "ROOT/RSlotStack.hxx"
#endif

#ifdef R__UNIX
// Functions needed to perform EOS XRootD redirection in ChangeSpec
#include "TEnv.h"
#include "ROOT/InternalIOUtils.hxx"
#include "TSystem.h"
#ifndef R__FBSD
#include <sys/xattr.h>
#else
#include <sys/extattr.h>
#endif
#ifdef R__MACOSX
/* On macOS getxattr takes two extra arguments that should be set to 0 */
#define getxattr(path, name, value, size) getxattr(path, name, value, size, 0u, 0)
#endif
#ifdef R__FBSD
#define getxattr(path, name, value, size) extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, value, size)
#endif
#endif

#include <algorithm>
#include <atomic>
Expand Down Expand Up @@ -370,37 +355,21 @@ RLoopManager::RLoopManager(ROOT::RDF::Experimental::RDatasetSpec &&spec)
ChangeSpec(std::move(spec));
}

#ifdef R__UNIX
namespace {
std::optional<std::string> GetRedirectedSampleId(std::string_view path, std::string_view datasetName)
{
// Mimick the redirection done in TFile::Open to see if the path points to a FUSE-mounted EOS path.
// If so, we create a redirected sample ID with the full xroot URL.
TString expandedUrl(path.data());
gSystem->ExpandPathName(expandedUrl);
if (gEnv->GetValue("TFile.CrossProtocolRedirects", 1) == 1) {
TUrl fileurl(expandedUrl, /* default is file */ kTRUE);
if (strcmp(fileurl.GetProtocol(), "file") == 0) {
ssize_t len = getxattr(fileurl.GetFile(), "eos.url.xroot", nullptr, 0);
if (len > 0) {
std::string xurl(len, 0);
std::string fileNameFromUrl{fileurl.GetFile()};
if (getxattr(fileNameFromUrl.c_str(), "eos.url.xroot", &xurl[0], len) == len) {
// Sometimes the `getxattr` call may return an invalid URL due
// to the POSIX attribute not being yet completely filled by EOS.
if (auto baseName = fileNameFromUrl.substr(fileNameFromUrl.find_last_of("/") + 1);
std::equal(baseName.crbegin(), baseName.crend(), xurl.crbegin())) {
return xurl + '/' + datasetName.data();
}
}
}
}
TUrl fileurl(expandedUrl, /* default is file */ kTRUE);
if (strcmp(fileurl.GetProtocol(), "file") == 0) {
if (auto xurl = ROOT::Internal::GetEOSRedirectedXRootURL(fileurl.GetFile()))
return *xurl + '/' + datasetName.data();
}

return std::nullopt;
}
} // namespace
#endif

/**
* @brief Changes the internal TTree held by the RLoopManager.
Expand Down Expand Up @@ -451,11 +420,10 @@ void RLoopManager::ChangeSpec(ROOT::RDF::Experimental::RDatasetSpec &&spec)
// is exposed to users via RSampleInfo and DefinePerSample).
const auto sampleId = files[i] + '/' + trees[i];
fSampleMap.insert({sampleId, &sample});
#ifdef R__UNIX

// Also add redirected EOS xroot URL when available
if (auto redirectedSampleId = GetRedirectedSampleId(files[i], trees[i]))
fSampleMap.insert({redirectedSampleId.value(), &sample});
#endif
}
}
fDataSource = std::make_unique<ROOT::Internal::RDF::RTTreeDS>(std::move(chain), spec.GetFriendInfo());
Expand All @@ -473,11 +441,9 @@ void RLoopManager::ChangeSpec(ROOT::RDF::Experimental::RDatasetSpec &&spec)
fileNames.push_back(files[i]);
rntupleNames.insert(trees[i]);

#ifdef R__UNIX
// Also add redirected EOS xroot URL when available
if (auto redirectedSampleId = GetRedirectedSampleId(files[i], trees[i]))
fSampleMap.insert({redirectedSampleId.value(), &sample});
#endif
}
}

Expand Down
Loading