Chaste  Release::2019.1
OutputFileHandler.cpp
1 /*
2 
3 Copyright (c) 2005-2019, University of Oxford.
4 All rights reserved.
5 
6 University of Oxford means the Chancellor, Masters and Scholars of the
7 University of Oxford, having an administrative office at Wellington
8 Square, Oxford OX1 2JD, UK.
9 
10 This file is part of Chaste.
11 
12 Redistribution and use in source and binary forms, with or without
13 modification, 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 
23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
29 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32 OF 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 
42 #include "ArchiveLocationInfo.hpp"
43 #include "BoostFilesystem.hpp"
44 #include "Exception.hpp"
45 #include "FileFinder.hpp"
46 #include "GetCurrentWorkingDirectory.hpp"
47 #include "PetscTools.hpp"
48 
49 
50 const std::string OutputFileHandler::SIG_FILE_NAME(".chaste_deletable_folder");
51 
59 void CleanFolder(const fs::path& rPath, bool isTop=true);
60 
61 void 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 || PATH_LEAF_NAME(r_item_path)[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 
89 OutputFileHandler::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 
120 void 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
152  if (PetscTools::AmMaster())
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 SCons 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/";
172  }
173  else
174  {
175  // No $USER in environment (which may be the case in Docker)
176  tmp_directory << "/tmp/chaste/testoutput/"; // LCOV_EXCL_LINE
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 
193 std::string OutputFileHandler::MakeFoldersAndReturnFullPath(const std::string& rDirectory) const
194 {
195  fs::path output_root(GetChasteTestOutputDirectory());
196  fs::path rel_path(rDirectory);
197 
198  if (!rel_path.empty() && (*(--rel_path.end())) == ".")
199  {
200  // rDirectory has a trailing slash, which gives an unhelpful last component
201  rel_path.remove_leaf();
202  }
203 
204  // Make master wait (because other processes may be checking whether a directory exists)
205  PetscTools::Barrier("OutputFileHandler::MakeFoldersAndReturnFullPathBeforeCreation");
206  // Are we the master process? Only the master should make any new directories
207  if (PetscTools::AmMaster())
208  {
209  try
210  {
211  // If necessary make the ChasteTestOutputDirectory - don't make it deleteable by Chaste
212  fs::create_directories(output_root); // Note that this is a no-op if the folder exists already
213 
214  // Now make all the sub-folders requested one-by-one and add the .chaste_deletable_folder file to them
215  fs::path next_folder(output_root);
216  for (fs::path::iterator path_iter = rel_path.begin(); path_iter != rel_path.end(); ++path_iter)
217  {
218  next_folder /= *path_iter;
219  bool created_dir = fs::create_directory(next_folder);
220  if (created_dir)
221  {
222  // Add the Chaste signature file
223  fs::ofstream sig_file(next_folder / SIG_FILE_NAME);
224  sig_file.close();
225  }
226  }
227  }
228  // LCOV_EXCL_START
229  catch (const fs::filesystem_error& e)
230  {
231  TERMINATE("Error making test output folder: " << e.what());
232  }
233  // LCOV_EXCL_STOP
234  }
235 
236  // Wait for master to finish before going on to use the directory.
237  PetscTools::Barrier("OutputFileHandler::MakeFoldersAndReturnFullPath");
238 
239  std::string path_with_slash = (output_root / rel_path).string();
240  AddTrailingSlash(path_with_slash);
241  return path_with_slash;
242 }
243 
245 {
246  return mDirectory;
247 }
248 
250 {
251  FileFinder output_root("", RelativeTo::ChasteTestOutput);
252  std::string relative_path = FindFile("").GetRelativePath(output_root);
253  if (!relative_path.empty() && *relative_path.rbegin() == '/')
254  {
255  relative_path.erase(--relative_path.end()); // Remove trailing slash
256  }
257  return relative_path;
258 }
259 
260 out_stream OutputFileHandler::OpenOutputFile(const std::string& rFileName,
261  std::ios_base::openmode mode) const
262 {
263  out_stream p_output_file(new std::ofstream((mDirectory+rFileName).c_str(), mode));
264  if (!p_output_file->is_open())
265  {
266  EXCEPTION("Could not open file \"" + rFileName + "\" in " + mDirectory);
267  }
268  return p_output_file;
269 }
270 
271 out_stream OutputFileHandler::OpenOutputFile(const std::string& rFileName,
272  unsigned number,
273  const std::string& rFileFormat,
274  std::ios_base::openmode mode) const
275 {
276  std::stringstream string_stream;
277  string_stream << rFileName << number << rFileFormat;
278  return OpenOutputFile(string_stream.str(), mode);
279 }
280 
282 {
285 }
286 
287 void OutputFileHandler::AddTrailingSlash(std::string& rDirectory)
288 {
289  // Add a trailing slash if not already there
290  if (rDirectory!="" && !( *(rDirectory.end()-1) == '/'))
291  {
292  rDirectory = rDirectory + "/";
293  }
294 }
295 
297 {
298  if (!rSourceFile.IsFile())
299  {
300  EXCEPTION("Can only copy single files:\n" << rSourceFile.GetAbsolutePath() << " is not a file.");
301  }
302  fs::path from_path(rSourceFile.GetAbsolutePath());
303  fs::path to_path(GetOutputDirectoryFullPath());
304  to_path /= from_path.leaf();
305  if (PetscTools::AmMaster())
306  {
307  try
308  {
309  fs::copy_file(from_path, to_path);
310  }
311  // LCOV_EXCL_START
312  catch (const fs::filesystem_error& e)
313  {
314  TERMINATE("Error copying file '" << rSourceFile.GetAbsolutePath() << "': " << e.what());
315  }
316  // LCOV_EXCL_STOP
317  }
318  PetscTools::Barrier("OutputFileHandler::CopyFileTo");
319  return FileFinder(to_path.string(), RelativeTo::Absolute);
320 }
321 
322 FileFinder OutputFileHandler::FindFile(std::string leafName) const
323 {
325 }
static const std::string SIG_FILE_NAME
std::string GetAbsolutePath() const
Definition: FileFinder.cpp:224
std::string GetOutputDirectoryFullPath() const
static void AddTrailingSlash(std::string &rDirectory)
static void Barrier(const std::string callerId="")
Definition: PetscTools.cpp:134
std::string GetRelativePath() const
FileFinder FindFile(std::string leafName) const
bool Exists() const
Definition: FileFinder.cpp:183
#define EXCEPTION(message)
Definition: Exception.hpp:143
static bool AmMaster()
Definition: PetscTools.cpp:120
#define TERMINATE(message)
Definition: Exception.hpp:168
bool IsFile() const
Definition: FileFinder.cpp:188
std::string MakeFoldersAndReturnFullPath(const std::string &rDirectory) const
FileFinder CopyFileTo(const FileFinder &rSourceFile) const
void SetArchiveDirectory() const
out_stream OpenOutputFile(const std::string &rFileName, std::ios_base::openmode mode=std::ios::out|std::ios::trunc) const
#define PATH_LEAF_NAME(path)
std::string GetRelativePath(const FileFinder &rBasePath) const
Definition: FileFinder.cpp:263
std::string mDirectory
The directory to store output files in (always ends in "/")
virtual void SetPath(const std::string &rPath, RelativeTo::Value relativeTo)
Definition: FileFinder.cpp:102
static std::string GetChasteTestOutputDirectory()
static void SetArchiveDirectory(const FileFinder &rDirectory)
void CommonConstructor(const std::string &rDirectory, bool cleanOutputDirectory)
OutputFileHandler(const std::string &rDirectory, bool cleanOutputDirectory=true)