diff --git a/io/io/CMakeLists.txt b/io/io/CMakeLists.txt index 4318f1ee8af5e..eb1be8fe8e8d1 100644 --- a/io/io/CMakeLists.txt +++ b/io/io/CMakeLists.txt @@ -21,6 +21,7 @@ if (uring) endif () ROOT_LINKER_LIBRARY(RIO + src/InternalIOUtils.cxx src/RConcurrentHashColl.cxx src/RFile.cxx src/RRawFile.cxx @@ -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 diff --git a/io/io/inc/ROOT/InternalIOUtils.hxx b/io/io/inc/ROOT/InternalIOUtils.hxx new file mode 100644 index 0000000000000..b19aec7c32a44 --- /dev/null +++ b/io/io/inc/ROOT/InternalIOUtils.hxx @@ -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 +#include + +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 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 GetEOSRedirectedXRootURL(const char *inputURL); +} // namespace ROOT::Internal + +#endif diff --git a/io/io/src/InternalIOUtils.cxx b/io/io/src/InternalIOUtils.cxx new file mode 100644 index 0000000000000..78595f480bca7 --- /dev/null +++ b/io/io/src/InternalIOUtils.cxx @@ -0,0 +1,74 @@ +#include "ROOT/InternalIOUtils.hxx" + +#include "ROOT/RConfig.hxx" // R__UNIX + +#ifdef R__UNIX +// getxattr +#ifdef R__FBSD +#include +#else +#include +#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 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 ROOT::Internal::GetEOSRedirectedXRootURL(const char *inputURL) +{ +#ifdef R__UNIX + if (!inputURL) + return std::nullopt; + + std::string_view inputSV{inputURL}; + if (inputSV.empty() || inputSV.back() == '/') + 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; +} diff --git a/io/io/src/RRawFile.cxx b/io/io/src/RRawFile.cxx index 1304f6b8dda72..30d17649a9c18 100644 --- a/io/io/src/RRawFile.cxx +++ b/io/io/src/RRawFile.cxx @@ -20,6 +20,7 @@ #include "TError.h" #include "TPluginManager.h" #include "TROOT.h" +#include "ROOT/InternalIOUtils.hxx" #include #include // for towlower @@ -68,6 +69,9 @@ ROOT::Internal::RRawFile::Create(std::string_view url, ROptions options) #ifdef _WIN32 return std::unique_ptr(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(new RRawFileUnix(url, options)); #endif } diff --git a/io/io/src/TFile.cxx b/io/io/src/TFile.cxx index 4d71c78283592..f38f64f26a416 100644 --- a/io/io/src/TFile.cxx +++ b/io/io/src/TFile.cxx @@ -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 #include @@ -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 diff --git a/tree/dataframe/src/RLoopManager.cxx b/tree/dataframe/src/RLoopManager.cxx index 67e1e9e4157b4..6394e022257c2 100644 --- a/tree/dataframe/src/RLoopManager.cxx +++ b/tree/dataframe/src/RLoopManager.cxx @@ -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 -#else -#include -#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 #include @@ -370,7 +355,6 @@ RLoopManager::RLoopManager(ROOT::RDF::Experimental::RDatasetSpec &&spec) ChangeSpec(std::move(spec)); } -#ifdef R__UNIX namespace { std::optional GetRedirectedSampleId(std::string_view path, std::string_view datasetName) { @@ -378,29 +362,14 @@ std::optional GetRedirectedSampleId(std::string_view path, std::str // 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. @@ -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(std::move(chain), spec.GetFriendInfo()); @@ -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 } }