Chaste Commit::fa89f2b838c1edb21a1eaec92ee3a2eacc9255dd
OutputFileHandler.cpp
1/*
2
3Copyright (c) 2005-2026, University of Oxford.
4All rights reserved.
5
6University of Oxford means the Chancellor, Masters and Scholars of the
7University of Oxford, having an administrative office at Wellington
8Square, Oxford OX1 2JD, UK.
9
10This file is part of Chaste.
11
12Redistribution and use in source and binary forms, with or without
13modification, are permitted provided that the following conditions are met:
14 * Redistributions of source code must retain the above copyright notice,
15 this list of conditions and the following disclaimer.
16 * Redistributions in binary form must reproduce the above copyright notice,
17 this list of conditions and the following disclaimer in the documentation
18 and/or other materials provided with the distribution.
19 * Neither the name of the University of Oxford nor the names of its
20 contributors may be used to endorse or promote products derived from this
21 software without specific prior written permission.
22
23THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
29GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
34*/
35
36#include "OutputFileHandler.hpp"
37
38#include <cstdlib>
39#include <fstream>
40#include <sstream>
41#include <cassert>
42
43#include "ArchiveLocationInfo.hpp"
44#include "ChasteBuildRoot.hpp"
45#include "Exception.hpp"
46#include "FileFinder.hpp"
47#include "FilesystemPermissions.hpp"
48#include "GetCurrentWorkingDirectory.hpp"
49#include "PetscTools.hpp"
50
51
52const std::string OutputFileHandler::SIG_FILE_NAME(".chaste_deletable_folder");
53
61void CleanFolder(const fs::path& rPath, bool isTop=true);
62
63void CleanFolder(const fs::path& rPath, bool isTop)
64{
65 assert(fs::is_directory(rPath));
66 fs::directory_iterator end_iter;
67 // First recursively remove the children
68 for (fs::directory_iterator dir_iter(rPath); dir_iter != end_iter; ++dir_iter)
69 {
70 if (fs::is_directory(dir_iter->status()))
71 {
72 CleanFolder(dir_iter->path(), false);
73 }
74 else
75 {
76 const fs::path& r_item_path(dir_iter->path());
77 if (!isTop || (r_item_path.filename().string())[0] != '.')
78 {
79 fs::remove(r_item_path);
80 }
81 }
82 }
83 // Now remove the folder itself, if not top
84 if (!isTop)
85 {
86 fs::remove(rPath);
87 }
88}
89
90
91OutputFileHandler::OutputFileHandler(const std::string& rDirectory,
92 bool cleanOutputDirectory)
93{
94 CommonConstructor(rDirectory, cleanOutputDirectory);
95}
96
97
99 bool cleanOutputDirectory)
100{
102 std::string relative_path;
103 try
104 {
105 relative_path = rDirectory.GetRelativePath(output_root);
106 }
107 catch (const Exception&)
108 {
109 EXCEPTION("The location provided to OutputFileHandler must be inside CHASTE_TEST_OUTPUT; '"
110 << rDirectory.GetAbsolutePath() << "' is not under '"
111 << output_root.GetAbsolutePath() << "'.");
112 }
113 if (*output_root.GetAbsolutePath().rbegin() != '/' && !relative_path.empty())
114 {
115 assert(*relative_path.begin() == '/');
116 relative_path.erase(0, 1); // Remove leading slash
117 }
118 CommonConstructor(relative_path, cleanOutputDirectory);
119}
120
121
122void OutputFileHandler::CommonConstructor(const std::string& rDirectory,
123 bool cleanOutputDirectory)
124{
125 // Is it a valid request for a directory?
126 if (rDirectory.find("..") != std::string::npos)
127 {
128 EXCEPTION("Will not create directory: " + rDirectory +
129 " due to it potentially being above, and cleaning, CHASTE_TEST_OUTPUT.");
130 // Note: in Boost 1.48 and above we could use 'canonical' to check this better
131 }
132 //The notion of absolute path on Windows is rather different.
133 //For example, / and /foo are not absolute paths.
134 //However, fs::path.has_root_path() captures the intended semantics here as follows
135
136 if (fs::path(rDirectory).has_root_path())
137 {
138 EXCEPTION("The constructor argument to OutputFileHandler must be a relative path; '"
139 << rDirectory << "' is absolute.");
140 }
141
143
144 // Clean the directory (default)
145 if (rDirectory != "" && cleanOutputDirectory) // Clean directory but don't ever clean CHASTE_TEST_OUTPUT at the top level
146 {
148 if (!signature_file.Exists())
149 {
150 EXCEPTION("Cannot delete " + mDirectory + " because signature file \"" + SIG_FILE_NAME + "\" is not present.");
151 }
152
153 // Are we the master process? Only the master should delete files
155 {
156 CleanFolder(mDirectory);
157 }
158 // Wait for master to finish before going on to use the directory.
159 PetscTools::Barrier("OutputFileHandler");
160 }
161}
162
164{
165 char* chaste_test_output = std::getenv("CHASTE_TEST_OUTPUT");
166 FileFinder directory_root;
167
168 // If CHASTE_TEST_OUTPUT is not set, we use the default (which is ${Chaste_BINARY_DIR}/testoutput)
169 if (chaste_test_output == nullptr || *chaste_test_output == 0)
170 {
172 }
173 else
174 {
175 directory_root.SetPath(chaste_test_output, RelativeTo::AbsoluteOrCwd);
176 }
177 // Note that FileFinder::GetAbsolutePath adds a trailing slash, but only
178 // if the directory exists at the time of the call
179 std::string chaste_test_output_directory = directory_root.GetAbsolutePath();
180 AddTrailingSlash(chaste_test_output_directory);
181 return chaste_test_output_directory;
182}
183
184std::string OutputFileHandler::MakeFoldersAndReturnFullPath(const std::string& rDirectory) const
185{
186 fs::path output_root(GetChasteTestOutputDirectory());
187 fs::path rel_path(rDirectory);
188
189 // Boost filesystem needed this, but std::filesystem is okay
190 //if (!rel_path.empty() && (*(--rel_path.end())) == ".")
191 //{
192 // // rDirectory has a trailing slash, which gives an unhelpful last component
193 // rel_path.remove_filename();
194 //}
195 if (!rel_path.empty())
196 {
197 assert( (*(--rel_path.end())) != ".");
198 }
199
200 // Make master wait (because other processes may be checking whether a directory exists)
201 PetscTools::Barrier("OutputFileHandler::MakeFoldersAndReturnFullPathBeforeCreation");
202 // Are we the master process? Only the master should make any new directories
204 {
205 try
206 {
207 // If necessary make the ChasteTestOutputDirectory - don't make it deleteable by Chaste
208 FilesystemPermissions::CreateDirectoriesWithPermissions(output_root); // Note that this is a no-op if the folder exists already
209
210 // Now make all the sub-folders requested one-by-one and add the .chaste_deletable_folder file to them
211 fs::path next_folder(output_root);
212 for (fs::path::iterator path_iter = rel_path.begin(); path_iter != rel_path.end(); ++path_iter)
213 {
214 next_folder /= *path_iter;
215 bool created_dir = FilesystemPermissions::CreateDirectoryWithPermissions(next_folder);
216 if (created_dir)
217 {
218 // Add the Chaste signature file
219 fs::path sig_file_path(next_folder / SIG_FILE_NAME);
220 std::ofstream sig_file(sig_file_path);
221 sig_file.close();
223 }
224 }
225 }
226 // LCOV_EXCL_START
227 catch (const fs::filesystem_error& e)
228 {
229 TERMINATE("Error making test output folder: " << e.what());
230 }
231 // LCOV_EXCL_STOP
232 }
233
234 // Wait for master to finish before going on to use the directory.
235 PetscTools::Barrier("OutputFileHandler::MakeFoldersAndReturnFullPath");
236
237 std::string path_with_slash = (output_root / rel_path).string();
238 AddTrailingSlash(path_with_slash);
239 return path_with_slash;
240}
241
243{
244 return mDirectory;
245}
246
248{
250 std::string relative_path = FindFile("").GetRelativePath(output_root);
251 if (!relative_path.empty() && *relative_path.rbegin() == '/')
252 {
253 relative_path.erase(--relative_path.end()); // Remove trailing slash
254 }
255 return relative_path;
256}
257
258out_stream OutputFileHandler::OpenOutputFile(const std::string& rFileName,
259 std::ios_base::openmode mode) const
260{
261 // A leading '/' in rFileName would make operator/= treat it as an absolute path and
262 // discard mDirectory, so strip any root component before appending.
263 fs::path file_name(rFileName);
264 if (file_name.has_root_path())
265 {
266 file_name = file_name.relative_path();
267 }
268 fs::path output_file = fs::path(mDirectory) / file_name;
269
270 out_stream p_output_file(new std::ofstream(output_file, mode));
271 if (!p_output_file->is_open())
272 {
273 EXCEPTION("Could not open file \"" + rFileName + "\" in " + mDirectory);
274 }
276 return p_output_file;
277}
278
279out_stream OutputFileHandler::OpenOutputFile(const std::string& rFileName,
280 unsigned number,
281 const std::string& rFileFormat,
282 std::ios_base::openmode mode) const
283{
284 std::stringstream string_stream;
285 string_stream << rFileName << number << rFileFormat;
286 return OpenOutputFile(string_stream.str(), mode);
287}
288
294
295void OutputFileHandler::AddTrailingSlash(std::string& rDirectory)
296{
297 // Add a trailing slash if not already there
298 if (rDirectory!="" && !( *(rDirectory.end()-1) == '/'))
299 {
300 rDirectory = rDirectory + "/";
301 }
302}
303
305{
306 if (!rSourceFile.IsFile())
307 {
308 EXCEPTION("Can only copy single files:\n" << rSourceFile.GetAbsolutePath() << " is not a file.");
309 }
310 fs::path from_path(rSourceFile.GetAbsolutePath());
311 fs::path to_path(GetOutputDirectoryFullPath());
312 to_path /= from_path.filename();
314 {
315 try
316 {
318 }
319 // LCOV_EXCL_START
320 catch (const fs::filesystem_error& e)
321 {
322 TERMINATE("Error copying file '" << rSourceFile.GetAbsolutePath() << "': " << e.what());
323 }
324 // LCOV_EXCL_STOP
325 }
326 PetscTools::Barrier("OutputFileHandler::CopyFileTo");
327 return FileFinder(to_path.string(), RelativeTo::Absolute);
328}
329
330FileFinder OutputFileHandler::FindFile(std::string leafName) const
331{
333}
std::string DefaultChasteTestOutput()
#define TERMINATE(message)
#define EXCEPTION(message)
static void SetArchiveDirectory(const FileFinder &rDirectory)
std::string GetRelativePath(const FileFinder &rBasePath) const
std::string GetAbsolutePath() const
bool IsFile() const
virtual void SetPath(const std::string &rPath, RelativeTo::Value relativeTo)
bool Exists() const
static bool CreateDirectoriesWithPermissions(const fs::path &rPath)
static void CopyFileWithPermissions(const fs::path &rFromPath, const fs::path &rToPath)
static bool CreateDirectoryWithPermissions(const fs::path &rPath)
static void SetFilePermissions(const fs::path &rPath)
FileFinder CopyFileTo(const FileFinder &rSourceFile) const
static void AddTrailingSlash(std::string &rDirectory)
static std::string GetChasteTestOutputDirectory()
static const std::string SIG_FILE_NAME
std::string GetOutputDirectoryFullPath() const
void SetArchiveDirectory() const
FileFinder FindFile(std::string leafName) const
std::string MakeFoldersAndReturnFullPath(const std::string &rDirectory) const
out_stream OpenOutputFile(const std::string &rFileName, std::ios_base::openmode mode=std::ios::out|std::ios::trunc) const
void CommonConstructor(const std::string &rDirectory, bool cleanOutputDirectory)
std::string mDirectory
The directory to store output files in (always ends in "/")
std::string GetRelativePath() const
OutputFileHandler(const std::string &rDirectory, bool cleanOutputDirectory=true)
static bool AmMaster()
static void Barrier(const std::string callerId="")