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