Chaste Commit::1fd4e48e3990e67db148bc1bc4cf6991a0049d0c
OutputFileHandler.cpp
1/*
2
3Copyright (c) 2005-2024, 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 "GetCurrentWorkingDirectory.hpp"
48#include "PetscTools.hpp"
49
50
51const std::string OutputFileHandler::SIG_FILE_NAME(".chaste_deletable_folder");
52
60void CleanFolder(const fs::path& rPath, bool isTop=true);
61
62void CleanFolder(const fs::path& rPath, bool isTop)
63{
64 assert(fs::is_directory(rPath));
65 fs::directory_iterator end_iter;
66 // First recursively remove the children
67 for (fs::directory_iterator dir_iter(rPath); dir_iter != end_iter; ++dir_iter)
68 {
69 if (fs::is_directory(dir_iter->status()))
70 {
71 CleanFolder(dir_iter->path(), false);
72 }
73 else
74 {
75 const fs::path& r_item_path(dir_iter->path());
76 if (!isTop || (r_item_path.filename().string())[0] != '.')
77 {
78 fs::remove(r_item_path);
79 }
80 }
81 }
82 // Now remove the folder itself, if not top
83 if (!isTop)
84 {
85 fs::remove(rPath);
86 }
87}
88
89
90OutputFileHandler::OutputFileHandler(const std::string& rDirectory,
91 bool cleanOutputDirectory)
92{
93 CommonConstructor(rDirectory, cleanOutputDirectory);
94}
95
96
98 bool cleanOutputDirectory)
99{
101 std::string relative_path;
102 try
103 {
104 relative_path = rDirectory.GetRelativePath(output_root);
105 }
106 catch (const Exception&)
107 {
108 EXCEPTION("The location provided to OutputFileHandler must be inside CHASTE_TEST_OUTPUT; '"
109 << rDirectory.GetAbsolutePath() << "' is not under '"
110 << output_root.GetAbsolutePath() << "'.");
111 }
112 if (*output_root.GetAbsolutePath().rbegin() != '/' && !relative_path.empty())
113 {
114 assert(*relative_path.begin() == '/');
115 relative_path.erase(0, 1); // Remove leading slash
116 }
117 CommonConstructor(relative_path, cleanOutputDirectory);
118}
119
120
121void OutputFileHandler::CommonConstructor(const std::string& rDirectory,
122 bool cleanOutputDirectory)
123{
124 // Is it a valid request for a directory?
125 if (rDirectory.find("..") != std::string::npos)
126 {
127 EXCEPTION("Will not create directory: " + rDirectory +
128 " due to it potentially being above, and cleaning, CHASTE_TEST_OUTPUT.");
129 // Note: in Boost 1.48 and above we could use 'canonical' to check this better
130 }
131 //The notion of absolute path on Windows is rather different.
132 //For example, / and /foo are not absolute paths.
133 //However, fs::path.has_root_path() captures the intended semantics here as follows
134
135 if (fs::path(rDirectory).has_root_path())
136 {
137 EXCEPTION("The constructor argument to OutputFileHandler must be a relative path; '"
138 << rDirectory << "' is absolute.");
139 }
140
142
143 // Clean the directory (default)
144 if (rDirectory != "" && cleanOutputDirectory) // Clean directory but don't ever clean CHASTE_TEST_OUTPUT at the top level
145 {
147 if (!signature_file.Exists())
148 {
149 EXCEPTION("Cannot delete " + mDirectory + " because signature file \"" + SIG_FILE_NAME + "\" is not present.");
150 }
151
152 // Are we the master process? Only the master should delete files
154 {
155 CleanFolder(mDirectory);
156 }
157 // Wait for master to finish before going on to use the directory.
158 PetscTools::Barrier("OutputFileHandler");
159 }
160}
161
163{
164 char* chaste_test_output = std::getenv("CHASTE_TEST_OUTPUT");
165 FileFinder directory_root;
166
167 // If CHASTE_TEST_OUTPUT is not set, we use the default (which is ${Chaste_BINARY_DIR}/testoutput)
168 if (chaste_test_output == nullptr || *chaste_test_output == 0)
169 {
171 }
172 else
173 {
174 directory_root.SetPath(chaste_test_output, RelativeTo::AbsoluteOrCwd);
175 }
176 // Note that FileFinder::GetAbsolutePath adds a trailing slash, but only
177 // if the directory exists at the time of the call
178 std::string chaste_test_output_directory = directory_root.GetAbsolutePath();
179 AddTrailingSlash(chaste_test_output_directory);
180 return chaste_test_output_directory;
181}
182
183std::string OutputFileHandler::MakeFoldersAndReturnFullPath(const std::string& rDirectory) const
184{
185 fs::path output_root(GetChasteTestOutputDirectory());
186 fs::path rel_path(rDirectory);
187
188 // Boost filesystem needed this, but std::filesystem is okay
189 //if (!rel_path.empty() && (*(--rel_path.end())) == ".")
190 //{
191 // // rDirectory has a trailing slash, which gives an unhelpful last component
192 // rel_path.remove_filename();
193 //}
194 if (!rel_path.empty())
195 {
196 assert( (*(--rel_path.end())) != ".");
197 }
198
199 // Make master wait (because other processes may be checking whether a directory exists)
200 PetscTools::Barrier("OutputFileHandler::MakeFoldersAndReturnFullPathBeforeCreation");
201 // Are we the master process? Only the master should make any new directories
203 {
204 try
205 {
206 // If necessary make the ChasteTestOutputDirectory - don't make it deleteable by Chaste
207 fs::create_directories(output_root); // Note that this is a no-op if the folder exists already
208
209 // Now make all the sub-folders requested one-by-one and add the .chaste_deletable_folder file to them
210 fs::path next_folder(output_root);
211 for (fs::path::iterator path_iter = rel_path.begin(); path_iter != rel_path.end(); ++path_iter)
212 {
213 next_folder /= *path_iter;
214 bool created_dir = fs::create_directory(next_folder);
215 if (created_dir)
216 {
217 // Add the Chaste signature file
218 std::ofstream sig_file(next_folder / SIG_FILE_NAME);
219 sig_file.close();
220 }
221 }
222 }
223 // LCOV_EXCL_START
224 catch (const fs::filesystem_error& e)
225 {
226 TERMINATE("Error making test output folder: " << e.what());
227 }
228 // LCOV_EXCL_STOP
229 }
230
231 // Wait for master to finish before going on to use the directory.
232 PetscTools::Barrier("OutputFileHandler::MakeFoldersAndReturnFullPath");
233
234 std::string path_with_slash = (output_root / rel_path).string();
235 AddTrailingSlash(path_with_slash);
236 return path_with_slash;
237}
238
240{
241 return mDirectory;
242}
243
245{
247 std::string relative_path = FindFile("").GetRelativePath(output_root);
248 if (!relative_path.empty() && *relative_path.rbegin() == '/')
249 {
250 relative_path.erase(--relative_path.end()); // Remove trailing slash
251 }
252 return relative_path;
253}
254
255out_stream OutputFileHandler::OpenOutputFile(const std::string& rFileName,
256 std::ios_base::openmode mode) const
257{
258 out_stream p_output_file(new std::ofstream((mDirectory+rFileName).c_str(), mode));
259 if (!p_output_file->is_open())
260 {
261 EXCEPTION("Could not open file \"" + rFileName + "\" in " + mDirectory);
262 }
263 return p_output_file;
264}
265
266out_stream OutputFileHandler::OpenOutputFile(const std::string& rFileName,
267 unsigned number,
268 const std::string& rFileFormat,
269 std::ios_base::openmode mode) const
270{
271 std::stringstream string_stream;
272 string_stream << rFileName << number << rFileFormat;
273 return OpenOutputFile(string_stream.str(), mode);
274}
275
281
282void OutputFileHandler::AddTrailingSlash(std::string& rDirectory)
283{
284 // Add a trailing slash if not already there
285 if (rDirectory!="" && !( *(rDirectory.end()-1) == '/'))
286 {
287 rDirectory = rDirectory + "/";
288 }
289}
290
292{
293 if (!rSourceFile.IsFile())
294 {
295 EXCEPTION("Can only copy single files:\n" << rSourceFile.GetAbsolutePath() << " is not a file.");
296 }
297 fs::path from_path(rSourceFile.GetAbsolutePath());
298 fs::path to_path(GetOutputDirectoryFullPath());
299 to_path /= from_path.filename();
301 {
302 try
303 {
304 fs::copy_file(from_path, to_path);
305 }
306 // LCOV_EXCL_START
307 catch (const fs::filesystem_error& e)
308 {
309 TERMINATE("Error copying file '" << rSourceFile.GetAbsolutePath() << "': " << e.what());
310 }
311 // LCOV_EXCL_STOP
312 }
313 PetscTools::Barrier("OutputFileHandler::CopyFileTo");
314 return FileFinder(to_path.string(), RelativeTo::Absolute);
315}
316
317FileFinder OutputFileHandler::FindFile(std::string leafName) const
318{
320}
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
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="")