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