[TFile] Create dir if necessary
pr.log
This pull request changes the behavior of the create and recreate functions of the TFile class. When a new ROOT file is created, the underlying path may not exist. This commit modifies the logic of TFile.cxx such that the underlying path is explicitly created. It works only if TFile.cxx is compiled with the C++17 standard, otherwise the relevant code is excluded by the means of the preprocessor.
Checklist:
- [x] tested changes locally
- [ ] updated the docs (if necessary)
Quick test
I prepared a small program in C++ (requires C++17) to quickly test the pull request (see at the bottom of the message). In log, with the patched ROOT version the output looks like thit:
Attempt to (re)createa/b/c/test.root
Ok.
while with the ROOT version 6.28 the output looks like this:
Attempt to (re)createa/b/c/test.root
SysError in <TFile::TFile>: file XXXXXX/ftest/XXYqNIoC/a/b/c/test.root can not be opened No such file or directory
Fail.
The program:
#include "TFile.h"
#include <filesystem>
#include <cstdlib>
#include <iostream>
namespace fs = std::filesystem;
char tmpname[] = "XXXXXXXX";
char E[] = "test.root";
char F[] = "a/b/c/test.root";
char H[] = "a/b/d/atest.root";
void ok(char *fname)
{
if (fs::exists(fs::path(fname)))
std::cout << "Ok." << std::endl;
else
std::cout << "Fail." << std::endl;
}
int main()
{
char *tmpnm = mkdtemp(tmpname);
if (tmpnm == NULL) {
std::cout << "Error while creating tmp dir" << std::endl;
return -1;
}
const auto tmp = fs::path(tmpnm);
const auto prev = fs::current_path();
fs::current_path(tmp);
std::cout << "Attempt to (re)create" << E << std::endl;
TFile e(E, "recreate"); e.Close(); ok(E);
std::cout << "Attempt to (re)create" << F << std::endl;
TFile f(F, "recreate"); f.Close(); ok(F);
std::cout << "Attempt to open" << F << std::endl;
TFile g(F); g.Close(); ok(F);
std::cout << "Attempt to open" << H << std::endl;
TFile h(H); h.Close(); ok(H);
std::cout << "List temporary directory tree:" << std::endl;
std::system("tree");
fs::current_path(prev);
fs::remove_all(tmp);
if (not fs::exists(tmp))
std::cout << "Temporary directory deleted" << std::endl;
else
std::cerr << "Failed to delete temporary directory" << std::endl;
return 0;
}
Test Results
9 files 9 suites 1d 19h 53m 37s :stopwatch: 2 577 tests 2 565 :white_check_mark: 0 :zzz: 12 :x: 22 465 runs 22 364 :white_check_mark: 0 :zzz: 101 :x:
For more details on these failures, see this check.
Results for commit aa2bb05f.
Having this feature on by default seems to allow for some error case to (silently) succeed. For example:
mkdir where_the_data_goes
...
f = TFile::Open("the_data_goes_here/filename.root"); // Unintentional misspelling.
In related use case the linux utility mkdir offer both to create or not create the necessary intermediary directories but the default is to not create them (i.e. creation is requested explicitly with the option -p).
In addition ROOT already provided easy access to the directory creation via TSystem::mkdir:
int TSystem::mkdir(const char *name, bool recursive = false);
std::filesystem also does via 2 distinct function (one recursive, one not): create_directories and create_directory
So in your example you could use:
fs::create_directories(fs::path(F).parent_path());
TFile f(F, "recreate"); f.Close(); ok(F);
Externally creating the dictionary might be the right thing but at any rate if we want to introduce this feature in TFile itself it would need to:
- become optional (i.e. add a new keyword in the option)
- be implemented for all the plugins (at least by adding a failure if the creation is requested, the directory is not existing and the creation is not implemented (i.e. the server might not support directory creation).
Thanks, Philippe.
PS/Side-note: ROOT Is now-adays build only with C++17 and later.