Chaste  Release::2018.1
OutputFileHandler.cpp
1 /*
2 
3 Copyright (c) 2005-2018, 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  tmp_directory << "/tmp/" << getenv("USER") << "/testoutput/";
170  directory_root.SetPath(tmp_directory.str(), RelativeTo::AbsoluteOrCwd);
171  /* // Former behaviour: default to 'testoutput' folder within the current directory
172  * directory_root.SetPath("testoutput", RelativeTo::CWD); */
173  }
174  else
175  {
176  directory_root.SetPath(chaste_test_output, RelativeTo::AbsoluteOrCwd);
177  }
178  // Note that FileFinder::GetAbsolutePath adds a trailing slash, but only
179  // if the directory exists at the time of the call
180  std::string chaste_test_output_directory = directory_root.GetAbsolutePath();
181  AddTrailingSlash(chaste_test_output_directory);
182  return chaste_test_output_directory;
183 }
184 
185 std::string OutputFileHandler::MakeFoldersAndReturnFullPath(const std::string& rDirectory) const
186 {
187  fs::path output_root(GetChasteTestOutputDirectory());
188  fs::path rel_path(rDirectory);
189 
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_leaf();
194  }
195 
196  // Make master wait (because other processes may be checking whether a directory exists)
197  PetscTools::Barrier("OutputFileHandler::MakeFoldersAndReturnFullPathBeforeCreation");
198  // Are we the master process? Only the master should make any new directories
199  if (PetscTools::AmMaster())
200  {
201  try
202  {
203  // If necessary make the ChasteTestOutputDirectory - don't make it deleteable by Chaste
204  fs::create_directories(output_root); // Note that this is a no-op if the folder exists already
205 
206  // Now make all the sub-folders requested one-by-one and add the .chaste_deletable_folder file to them
207  fs::path next_folder(output_root);
208  for (fs::path::iterator path_iter = rel_path.begin(); path_iter != rel_path.end(); ++path_iter)
209  {
210  next_folder /= *path_iter;
211  bool created_dir = fs::create_directory(next_folder);
212  if (created_dir)
213  {
214  // Add the Chaste signature file
215  fs::ofstream sig_file(next_folder / SIG_FILE_NAME);
216  sig_file.close();
217  }
218  }
219  }
220  // LCOV_EXCL_START
221  catch (const fs::filesystem_error& e)
222  {
223  TERMINATE("Error making test output folder: " << e.what());
224  }
225  // LCOV_EXCL_STOP
226  }
227 
228  // Wait for master to finish before going on to use the directory.
229  PetscTools::Barrier("OutputFileHandler::MakeFoldersAndReturnFullPath");
230 
231  std::string path_with_slash = (output_root / rel_path).string();
232  AddTrailingSlash(path_with_slash);
233  return path_with_slash;
234 }
235 
237 {
238  return mDirectory;
239 }
240 
242 {
243  FileFinder output_root("", RelativeTo::ChasteTestOutput);
244  std::string relative_path = FindFile("").GetRelativePath(output_root);
245  if (!relative_path.empty() && *relative_path.rbegin() == '/')
246  {
247  relative_path.erase(--relative_path.end()); // Remove trailing slash
248  }
249  return relative_path;
250 }
251 
252 out_stream OutputFileHandler::OpenOutputFile(const std::string& rFileName,
253  std::ios_base::openmode mode) const
254 {
255  out_stream p_output_file(new std::ofstream((mDirectory+rFileName).c_str(), mode));
256  if (!p_output_file->is_open())
257  {
258  EXCEPTION("Could not open file \"" + rFileName + "\" in " + mDirectory);
259  }
260  return p_output_file;
261 }
262 
263 out_stream OutputFileHandler::OpenOutputFile(const std::string& rFileName,
264  unsigned number,
265  const std::string& rFileFormat,
266  std::ios_base::openmode mode) const
267 {
268  std::stringstream string_stream;
269  string_stream << rFileName << number << rFileFormat;
270  return OpenOutputFile(string_stream.str(), mode);
271 }
272 
274 {
277 }
278 
279 void OutputFileHandler::AddTrailingSlash(std::string& rDirectory)
280 {
281  // Add a trailing slash if not already there
282  if (rDirectory!="" && !( *(rDirectory.end()-1) == '/'))
283  {
284  rDirectory = rDirectory + "/";
285  }
286 }
287 
289 {
290  if (!rSourceFile.IsFile())
291  {
292  EXCEPTION("Can only copy single files:\n" << rSourceFile.GetAbsolutePath() << " is not a file.");
293  }
294  fs::path from_path(rSourceFile.GetAbsolutePath());
295  fs::path to_path(GetOutputDirectoryFullPath());
296  to_path /= from_path.leaf();
297  if (PetscTools::AmMaster())
298  {
299  try
300  {
301  fs::copy_file(from_path, to_path);
302  }
303  // LCOV_EXCL_START
304  catch (const fs::filesystem_error& e)
305  {
306  TERMINATE("Error copying file '" << rSourceFile.GetAbsolutePath() << "': " << e.what());
307  }
308  // LCOV_EXCL_STOP
309  }
310  PetscTools::Barrier("OutputFileHandler::CopyFileTo");
311  return FileFinder(to_path.string(), RelativeTo::Absolute);
312 }
313 
314 FileFinder OutputFileHandler::FindFile(std::string leafName) const
315 {
317 }
std::string MakeFoldersAndReturnFullPath(const std::string &rDirectory) const
static const std::string SIG_FILE_NAME
static void AddTrailingSlash(std::string &rDirectory)
static void Barrier(const std::string callerId="")
Definition: PetscTools.cpp:134
bool IsFile() const
Definition: FileFinder.cpp:188
FileFinder CopyFileTo(const FileFinder &rSourceFile) const
std::string GetAbsolutePath() const
Definition: FileFinder.cpp:224
#define EXCEPTION(message)
Definition: Exception.hpp:143
static bool AmMaster()
Definition: PetscTools.cpp:120
#define TERMINATE(message)
Definition: Exception.hpp:168
std::string GetOutputDirectoryFullPath() const
out_stream OpenOutputFile(const std::string &rFileName, std::ios_base::openmode mode=std::ios::out|std::ios::trunc) const
void SetArchiveDirectory() const
FileFinder FindFile(std::string leafName) const
std::string GetRelativePath() const
#define PATH_LEAF_NAME(path)
bool Exists() const
Definition: FileFinder.cpp:183
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)
std::string GetRelativePath(const FileFinder &rBasePath) const
Definition: FileFinder.cpp:263
void CommonConstructor(const std::string &rDirectory, bool cleanOutputDirectory)
OutputFileHandler(const std::string &rDirectory, bool cleanOutputDirectory=true)