Chaste  Release::2017.1
FileFinder.cpp
1 /*
2 
3 Copyright (c) 2005-2017, 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 "FileFinder.hpp"
37 
38 #include <cassert>
39 
40 #include "BoostFilesystem.hpp"
41 #include "ChasteBuildRoot.hpp"
42 #include "Exception.hpp"
43 #include "GetCurrentWorkingDirectory.hpp"
44 #include "OutputFileHandler.hpp"
45 #include "Warnings.hpp"
46 #include "PosixPathFixer.hpp"
47 
48 bool FileFinder::msFaking = false;
49 
51 
52 std::string FileFinder::msFakePath = "";
53 
54 #define UNSET_PATH "UNSET!"
55 
68 #define CONVERT_ERROR(code) \
69  try { \
70  code; \
71  } catch (const fs::filesystem_error& e) { \
72  EXCEPTION(e.what()); \
73  }
74 
76  : mAbsPath(UNSET_PATH)
77 {
78 }
79 
80 FileFinder::FileFinder(const std::string& rRelativePath, RelativeTo::Value relativeTo)
81 {
82  SetPath(rRelativePath, relativeTo);
83 }
84 
85 FileFinder::FileFinder(const std::string& rLeafName, const FileFinder& rParentOrSibling)
86 {
87  SetPath(rLeafName, rParentOrSibling);
88 }
89 
90 FileFinder::FileFinder(const fs::path& rPath)
91 {
92  SetPath(fs::complete(rPath).string(), RelativeTo::Absolute);
93 }
94 
96 {
97 }
98 
99 void FileFinder::SetPath(const std::string& rRelativePath, RelativeTo::Value relativeTo)
100 {
101  switch (relativeTo)
102  {
104  mAbsPath = ChasteSourceRootDir() + rRelativePath;
105  break;
106 
108  mAbsPath = ChasteBuildRootDir() + rRelativePath;
109  break;
110 
113  break;
114 
115  case RelativeTo::CWD:
116  mAbsPath = GetCurrentWorkingDirectory() + "/" + rRelativePath;
117  break;
118 
120  mAbsPath = rRelativePath;
121  break;
122 
124  if (FileFinder::IsAbsolutePath(rRelativePath))
125  {
126  mAbsPath = rRelativePath;
127  }
128  else
129  {
130  mAbsPath = GetCurrentWorkingDirectory() + "/" + rRelativePath;
131  }
132  break;
133 
134  default:
135  // Getting here is impossible
137  break;
138  }
139 
140  if (msFaking && msFakeWhat == relativeTo)
141  {
142  // Fake the resulting path
143  mAbsPath = msFakePath + "/" + rRelativePath;
144  }
145 
146  // Remove any trailing /
147  std::string::iterator it = mAbsPath.end();
148  while (it != mAbsPath.begin() && *(--it) == '/')
149  {
150  // Iterator was decremented in the while test
151  }
152  // it now points at the last non-slash character, if any
153  if (it != mAbsPath.end() && (++it) != mAbsPath.end())
154  {
155  mAbsPath.erase(it, mAbsPath.end());
156  }
157 }
158 
159 void FileFinder::SetPath(const std::string& rLeafName, const FileFinder& rParentOrSibling)
160 {
161  if (!rParentOrSibling.Exists())
162  {
163  EXCEPTION("Reference path '" << rParentOrSibling.GetAbsolutePath() << "' does not exist.");
164  }
165  if (rParentOrSibling.IsDir())
166  {
167  SetPath(rParentOrSibling.GetAbsolutePath() + rLeafName, RelativeTo::Absolute);
168  }
169  else
170  {
171  SetPath(rParentOrSibling.GetParent().GetAbsolutePath() + rLeafName, RelativeTo::Absolute);
172  }
173 }
174 
176 {
177  return mAbsPath != UNSET_PATH;
178 }
179 
180 bool FileFinder::Exists() const
181 {
182  return fs::exists(mAbsPath);
183 }
184 
185 bool FileFinder::IsFile() const
186 {
187  return fs::is_regular(mAbsPath);
188 }
189 
190 bool FileFinder::IsDir() const
191 {
192  return fs::is_directory(mAbsPath);
193 }
194 
196 {
197  bool empty = true;
198  if (IsFile())
199  {
200  empty = (fs::file_size(mAbsPath) == 0u);
201  }
202  else if (IsDir())
203  {
204  fs::directory_iterator end_iter;
205  for (fs::directory_iterator dir_iter(mAbsPath); dir_iter != end_iter; ++dir_iter)
206  {
207  if (PATH_LEAF_NAME(dir_iter->path()).substr(0, 1) != ".")
208  {
209  empty = false;
210  break;
211  }
212  }
213  }
214  else
215  {
216  EXCEPTION("The path '" << mAbsPath << "' does not exist.");
217  }
218  return empty;
219 }
220 
221 std::string FileFinder::GetAbsolutePath() const
222 {
223  if (IsDir())
224  {
225  return mAbsPath + '/';
226  }
227  return mAbsPath;
228 }
229 
230 bool FileFinder::IsNewerThan(const FileFinder& rOtherEntity) const
231 {
232  assert(Exists());
233  assert(rOtherEntity.Exists());
234  return fs::last_write_time(mAbsPath) > fs::last_write_time(rOtherEntity.mAbsPath);
235 }
236 
237 std::string FileFinder::GetLeafName() const
238 {
239  return PATH_LEAF_NAME(fs::path(mAbsPath));
240 }
241 
243 {
244  return fs::basename(mAbsPath);
245 }
246 
247 std::string FileFinder::GetExtension() const
248 {
249  return fs::extension(mAbsPath);
250 }
251 
253 {
254  fs::path our_path(mAbsPath);
255  EXCEPT_IF_NOT(our_path.has_branch_path());
256  return FileFinder(our_path.branch_path().string(),
258 }
259 
260 
261 std::string FileFinder::GetRelativePath(const FileFinder& rBasePath) const
262 {
263  const std::string base_path = rBasePath.GetAbsolutePath();
264  const std::string our_path = GetAbsolutePath();
265  if (our_path.substr(0, base_path.length()) != base_path)
266  {
267  EXCEPTION("The path '" << our_path << "' is not relative to '" << base_path << "'.");
268  }
269  return our_path.substr(base_path.length());
270 }
271 
272 
278 void RecursiveCopy(const fs::path& rFromPath, const fs::path& rToPath);
279 
280 void RecursiveCopy(const fs::path& rFromPath, const fs::path& rToPath)
281 {
282  fs::path dest = rToPath;
283  // If rToPath is a folder, then we're copying to the source name *inside* this folder
284  if (fs::is_directory(dest))
285  {
286  dest /= rFromPath.leaf();
287  }
288  // If the source is a folder, it's complicated
289  if (fs::is_directory(rFromPath))
290  {
291  // Create the destination folder
292  EXCEPT_IF_NOT(!fs::exists(dest));
293  fs::create_directory(dest);
294  // Recursively copy our contents
295  fs::directory_iterator end_iter;
296  for (fs::directory_iterator dir_iter(rFromPath); dir_iter != end_iter; ++dir_iter)
297  {
298  RecursiveCopy(dir_iter->path(), dest);
299  }
300  }
301  else
302  {
303  fs::copy_file(rFromPath, dest); // Just copy!
304  }
305 }
306 
307 
309 {
310  if (!Exists())
311  {
312  EXCEPTION("Cannot copy '" << mAbsPath << "' as it does not exist.");
313  }
314  fs::path from_path(mAbsPath);
315  fs::path to_path(rDest.mAbsPath);
316  if (rDest.IsDir())
317  {
318  to_path /= from_path.leaf();
319  }
320  if (fs::exists(to_path))
321  {
322  if (IsFile())
323  {
324  CONVERT_ERROR(fs::remove(to_path));
325  }
326  else
327  {
328  EXCEPTION("Cannot copy '" << mAbsPath << "' to '" << to_path << "' as it would overwrite an existing file.");
329  }
330  }
331  CONVERT_ERROR(RecursiveCopy(from_path, to_path));
332  return FileFinder(to_path);
333 }
334 
335 
340 void RemoveAll(const fs::path& rPath);
341 
342 void RemoveAll(const fs::path& rPath)
343 {
344  // First recursively remove any children
345  if (fs::is_directory(rPath))
346  {
347  fs::directory_iterator end_iter;
348  for (fs::directory_iterator dir_iter(rPath); dir_iter != end_iter; ++dir_iter)
349  {
350  RemoveAll(dir_iter->path());
351  }
352  }
353  // Now remove the item itself
354  fs::remove(rPath);
355 }
356 
357 void FileFinder::PrivateRemove(bool dangerous) const
358 {
359  // Test for bad paths
360  const std::string test_output(OutputFileHandler::GetChasteTestOutputDirectory());
361  const std::string test_output_path(ChastePosixPathFixer::ToPosix(fs::path(test_output)));
362  const std::string absolute_path(ChastePosixPathFixer::ToPosix(fs::path(GetAbsolutePath())));
363  bool in_testoutput = (absolute_path.substr(0, test_output_path.length()) == test_output_path);
364 
365  if (!in_testoutput)
366  {
367  if (dangerous)
368  {
369  const std::string source_folder(FileFinder("",RelativeTo::ChasteSourceRoot).GetAbsolutePath());
370  const std::string source_folder_path = ChastePosixPathFixer::ToPosix(fs::path(source_folder));
371  bool in_source = (absolute_path.substr(0, source_folder_path.length()) == source_folder_path);
372 
373  const std::string build_folder(FileFinder("",RelativeTo::ChasteBuildRoot).GetAbsolutePath());
374  const std::string build_folder_path = ChastePosixPathFixer::ToPosix(fs::path(build_folder));
375  bool in_build = (absolute_path.substr(0, build_folder_path.length()) == build_folder_path);
376 
377  if (!(in_source || in_build))
378  {
379  EXCEPTION("Cannot remove location '" << mAbsPath
380  << "' as it is not located within the Chaste test output folder ("
381  << test_output_path << "), the Chaste source folder ("
382  << source_folder_path <<") or the Chaste build folder ("
383  << build_folder_path <<").");
384  }
385  }
386  else
387  {
388  EXCEPTION("Cannot remove location '" << mAbsPath
389  << "' as it is not located within the Chaste test output folder ("
390  << test_output_path << ").");
391  }
392  }
393 
394  if (mAbsPath.find("..") != std::string::npos)
395  {
396  EXCEPTION("Cannot remove location '" << mAbsPath
397  << "' as it contains a dangerous path component.");
398  }
399  if (Exists())
400  {
401  if (!dangerous)
402  {
403  fs::path sig_file(mAbsPath);
404  if (IsFile())
405  {
406  // We need to look for the signature file in the parent folder
407  sig_file.remove_leaf();
408  }
410  if (!fs::exists(sig_file))
411  {
412  EXCEPTION("Cannot remove location '" << mAbsPath << "' because the signature file '"
413  << OutputFileHandler::SIG_FILE_NAME << "' is not present.");
414  }
415  }
416  // Do the removal
417  CONVERT_ERROR(RemoveAll(mAbsPath));
418  }
419 }
420 
421 void FileFinder::Remove() const
422 {
423  PrivateRemove();
424 }
425 
427 {
428  PrivateRemove(true);
429 }
430 
431 
432 std::vector<FileFinder> FileFinder::FindMatches(const std::string& rPattern) const
433 {
434  // Check for error/warning cases
435  if (!IsDir())
436  {
437  EXCEPTION("Cannot search for matching files in '" << mAbsPath << "' as it is not a directory.");
438  }
439  size_t len = rPattern.length();
440  size_t inner_star_pos = rPattern.find('*', 1);
441  if (inner_star_pos != std::string::npos && inner_star_pos < len - 1)
442  {
443  WARNING("A '*' only has special meaning at the start or end of a pattern.");
444  }
445 
446  // Note initial or trailing *, and use of ?
447  std::string pattern(rPattern);
448  bool star_fini = false;
449  if (!pattern.empty() && *(pattern.rbegin()) == '*')
450  {
451  star_fini = true;
452  pattern = pattern.substr(0, len-1);
453  len--;
454  }
455  bool star_init = false;
456  if (!pattern.empty() && pattern[0] == '*')
457  {
458  star_init = true;
459  pattern = pattern.substr(1);
460  len--;
461  }
462  bool has_query = (pattern.find('?') != std::string::npos);
463  // Disallow a harder case to match
464  if (star_init && star_fini && has_query)
465  {
466  EXCEPTION("The '*' wildcard may not be used at both the start and end of the pattern if the '?' wildcard is also used.");
467  }
468 
469  // Search the folder
470  std::vector<FileFinder> results;
471  if (!rPattern.empty())
472  {
473  fs::directory_iterator end_iter;
474  fs::path our_path(mAbsPath);
475  for (fs::directory_iterator dir_iter(our_path); dir_iter != end_iter; ++dir_iter)
476  {
477  std::string leafname = PATH_LEAF_NAME(dir_iter->path());
478  size_t leaf_len = leafname.length();
479  if (leafname[0] != '.' // Don't include hidden files
480  && leaf_len >= len) // Ignore stuff that can't match
481  {
482  if (!has_query) // Easier case
483  {
484  size_t pos = leafname.find(pattern);
485  if ((star_init || pos == 0) && (star_fini || pos + len == leaf_len))
486  {
487  results.push_back(FileFinder(our_path / leafname));
488  }
489  }
490  else
491  {
492  std::string match;
493  if (star_init)
494  {
495  // Match against last len chars
496  match = leafname.substr(leaf_len - len);
497  }
498  else
499  {
500  // Match against first len chars
501  match = leafname.substr(0, len);
502  }
503  bool ok = true;
504  for (std::string::const_iterator it_p=pattern.begin(), it_m=match.begin();
505  it_p != pattern.end();
506  ++it_p, ++it_m)
507  {
508  if (*it_p != '?' && *it_p != *it_m)
509  {
510  ok = false;
511  break;
512  }
513  }
514  if (ok)
515  {
516  results.push_back(FileFinder(our_path / leafname));
517  }
518  }
519  }
520  }
521  }
522  return results;
523 }
524 
525 
526 bool FileFinder::IsAbsolutePath(const std::string& rPath)
527 {
528  return fs::path(rPath).is_complete();
529 }
530 
532 {
533  for (std::string::iterator it = rPath.begin(); it != rPath.end(); ++it)
534  {
535  if (*it == ' ')
536  {
537  *it = '_';
538  }
539  }
540 }
541 
543 {
544  for (std::string::iterator it = rPath.begin(); it != rPath.end(); ++it)
545  {
546  if (*it == '_')
547  {
548  *it = ' ';
549  }
550  }
551 }
552 
553 void FileFinder::FakePath(RelativeTo::Value fakeWhat, const std::string& rFakePath)
554 {
555  msFakeWhat = fakeWhat;
556  msFakePath = rFakePath;
557  msFaking = true;
558 }
559 
561 {
562  msFaking = false;
563 }
std::string mAbsPath
Definition: FileFinder.hpp:297
void DangerousRemove() const
Definition: FileFinder.cpp:426
static const std::string SIG_FILE_NAME
void PrivateRemove(bool dangerous=false) const
Definition: FileFinder.cpp:357
virtual ~FileFinder()
Definition: FileFinder.cpp:95
static bool IsAbsolutePath(const std::string &rPath)
Definition: FileFinder.cpp:526
bool IsFile() const
Definition: FileFinder.cpp:185
const char * ChasteSourceRootDir()
std::string GetAbsolutePath() const
Definition: FileFinder.cpp:221
static bool msFaking
Definition: FileFinder.hpp:300
bool IsPathSet() const
Definition: FileFinder.cpp:175
#define EXCEPTION(message)
Definition: Exception.hpp:143
static void StopFaking()
Definition: FileFinder.cpp:560
FileFinder CopyTo(const FileFinder &rDest) const
Definition: FileFinder.cpp:308
static RelativeTo::Value msFakeWhat
Definition: FileFinder.hpp:303
bool IsEmpty() const
Definition: FileFinder.cpp:195
#define NEVER_REACHED
Definition: Exception.hpp:206
static std::string msFakePath
Definition: FileFinder.hpp:306
bool IsDir() const
Definition: FileFinder.cpp:190
std::string GetExtension() const
Definition: FileFinder.cpp:247
#define EXCEPT_IF_NOT(test)
Definition: Exception.hpp:158
#define PATH_LEAF_NAME(path)
static std::string ToPosix(const fs::path path)
std::vector< FileFinder > FindMatches(const std::string &rPattern) const
Definition: FileFinder.cpp:432
bool Exists() const
Definition: FileFinder.cpp:180
const char * ChasteBuildRootDir()
FileFinder GetParent() const
Definition: FileFinder.cpp:252
static void ReplaceUnderscoresWithSpaces(std::string &rPath)
Definition: FileFinder.cpp:542
std::string GetLeafNameNoExtension() const
Definition: FileFinder.cpp:242
virtual void SetPath(const std::string &rPath, RelativeTo::Value relativeTo)
Definition: FileFinder.cpp:99
static std::string GetChasteTestOutputDirectory()
std::string GetLeafName() const
Definition: FileFinder.cpp:237
static void FakePath(RelativeTo::Value fakeWhat, const std::string &rFakePath)
Definition: FileFinder.cpp:553
std::string GetRelativePath(const FileFinder &rBasePath) const
Definition: FileFinder.cpp:261
bool IsNewerThan(const FileFinder &rOtherEntity) const
Definition: FileFinder.cpp:230
static void ReplaceSpacesWithUnderscores(std::string &rPath)
Definition: FileFinder.cpp:531
void Remove() const
Definition: FileFinder.cpp:421