Skip to content
Merged
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
3 changes: 3 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 @@ -66,6 +67,8 @@ ROOT_LINKER_LIBRARY(RIO
)

target_include_directories(RIO PRIVATE ${CMAKE_SOURCE_DIR}/core/clib/res)
# PUBLIC so that dependent libraries e.g. ROOTDataFrame may find the internal header too
target_include_directories(RIO PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/res>)
Comment thread
vepadulano marked this conversation as resolved.
target_link_libraries(RIO PUBLIC ${ROOT_ATOMIC_LIBS})
target_link_libraries(RIO PRIVATE nlohmann_json::nlohmann_json)

Expand Down
24 changes: 24 additions & 0 deletions io/io/res/ROOT/InternalIOUtils.hxx
Comment thread
vepadulano marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Author: Vincenzo Eduardo Padulano (CERN), 08/2026

#ifndef ROOT_IO_UTILS
#define ROOT_IO_UTILS

#include <optional>
#include <string>
#include <string_view>

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(std::string_view path, std::string_view xattr);

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

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

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

#ifdef R__UNIX

#include <ROOT/RLogger.hxx>

ROOT::RLogChannel &InternalLogChannel()
{
static ROOT::RLogChannel sLog("ROOT.Internal");
return sLog;
}

// 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([[maybe_unused]] std::string_view path, [[maybe_unused]] std::string_view xattr)
{
#ifdef R__UNIX
// First call to getxattr evaluates the length of the extended attribute value
if (auto len = getxattr(path.data(), xattr.data(), 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.data(), xattr.data(), xval.data(), len) == len)
return xval;
}
#endif
return std::nullopt;
}

std::optional<std::string> ROOT::Internal::GetEOSRedirectedXRootURL([[maybe_unused]] std::string_view inputPath)
{
#ifdef R__UNIX
if (inputPath.empty() || inputPath.back() == '/')
return std::nullopt;

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

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

auto baseName = inputPath.substr(inputPath.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())) {
R__LOG_WARNING(InternalLogChannel())
<< "Could not find path base name '" << baseName << "' in redirected URL '" << *xurl << "'.";
return std::nullopt;
}

// Ensure the redirected URL actually starts with the XRootD protocol string
if (ROOT::StartsWith(*xurl, "root://") || ROOT::StartsWith(*xurl, "xroot://") ||
Comment thread
vepadulano marked this conversation as resolved.
ROOT::StartsWith(*xurl, "roots://") || ROOT::StartsWith(*xurl, "xroots://"))
return xurl;
else
R__LOG_WARNING(InternalLogChannel())
<< "Redirected URL '" << *xurl << "' does not begin with any valid XRootD protocol string.";

#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))
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 @@ -3811,34 +3812,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)) {
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