Chaste Commit::1fd4e48e3990e67db148bc1bc4cf6991a0049d0c
FileFinder.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 <algorithm>
37#include <cassert>
38
39#include "FileFinder.hpp"
40#include "ChasteBuildRoot.hpp"
41#include "Exception.hpp"
42#include "GetCurrentWorkingDirectory.hpp"
43#include "OutputFileHandler.hpp"
44#include "PosixPathFixer.hpp"
45#include "Warnings.hpp"
46
47bool FileFinder::msFaking = false;
48
50
51std::string FileFinder::msFakePath = "";
52
53#define UNSET_PATH "UNSET!"
54
67#define CONVERT_ERROR(code) \
68 try \
69 { \
70 code; \
71 } \
72 catch (const fs::filesystem_error& e) \
73 { \
74 EXCEPTION(e.what()); \
75 }
76
78 : mAbsPath(UNSET_PATH)
79{
80}
81
82FileFinder::FileFinder(const std::string& rRelativePath, RelativeTo::Value relativeTo)
83{
84 SetPath(rRelativePath, relativeTo);
85}
86
87FileFinder::FileFinder(const std::string& rLeafName, const FileFinder& rParentOrSibling)
88{
89 SetPath(rLeafName, rParentOrSibling);
90}
91
92FileFinder::FileFinder(const fs::path& rPath)
93{
94 SetPath(fs::absolute(rPath).string(), RelativeTo::Absolute);
95}
96
100
101void FileFinder::SetPath(const std::string& rRelativePath, RelativeTo::Value relativeTo)
102{
103 switch (relativeTo)
104 {
106 mAbsPath = ChasteSourceRootDir() + rRelativePath;
107 break;
108
110 mAbsPath = ChasteBuildRootDir() + rRelativePath;
111 break;
112
115 break;
116
117 case RelativeTo::CWD:
118 mAbsPath = GetCurrentWorkingDirectory() + "/" + rRelativePath;
119 break;
120
122 mAbsPath = rRelativePath;
123 break;
124
126 if (FileFinder::IsAbsolutePath(rRelativePath))
127 {
128 mAbsPath = rRelativePath;
129 }
130 else
131 {
132 mAbsPath = GetCurrentWorkingDirectory() + "/" + rRelativePath;
133 }
134 break;
135
136 default:
137 // Getting here is impossible
139 break;
140 }
141
142 if (msFaking && msFakeWhat == relativeTo)
143 {
144 // Fake the resulting path
145 mAbsPath = msFakePath + "/" + rRelativePath;
146 }
147
148 // Remove any trailing /
149 std::string::iterator it = mAbsPath.end();
150 while (it != mAbsPath.begin() && *(--it) == '/')
151 {
152 // Iterator was decremented in the while test
153 }
154 // it now points at the last non-slash character, if any
155 if (it != mAbsPath.end() && (++it) != mAbsPath.end())
156 {
157 mAbsPath.erase(it, mAbsPath.end());
158 }
159}
160
161void FileFinder::SetPath(const std::string& rLeafName, const FileFinder& rParentOrSibling)
162{
163 if (!rParentOrSibling.Exists())
164 {
165 EXCEPTION("Reference path '" << rParentOrSibling.GetAbsolutePath() << "' does not exist.");
166 }
167 if (rParentOrSibling.IsDir())
168 {
169 SetPath(rParentOrSibling.GetAbsolutePath() + rLeafName, RelativeTo::Absolute);
170 }
171 else
172 {
173 SetPath(rParentOrSibling.GetParent().GetAbsolutePath() + rLeafName, RelativeTo::Absolute);
174 }
175}
176
178{
179 return mAbsPath != UNSET_PATH;
180}
181
183{
184 return fs::exists(mAbsPath);
185}
186
188{
189 return fs::is_regular_file(mAbsPath);
190}
191
193{
194 return fs::is_directory(mAbsPath);
195}
196
198{
199 bool empty = true;
200 if (IsFile())
201 {
202 empty = (fs::file_size(mAbsPath) == 0u);
203 }
204 else if (IsDir())
205 {
206 fs::directory_iterator end_iter;
207 for (fs::directory_iterator dir_iter(mAbsPath); dir_iter != end_iter; ++dir_iter)
208 {
209 if ((dir_iter->path().filename().string()).substr(0, 1) != ".")
210 {
211 empty = false;
212 break;
213 }
214 }
215 }
216 else
217 {
218 EXCEPTION("The path '" << mAbsPath << "' does not exist.");
219 }
220 return empty;
221}
222
224{
225 if (IsDir())
226 {
227 return mAbsPath + '/';
228 }
229 return mAbsPath;
230}
231
232bool FileFinder::IsNewerThan(const FileFinder& rOtherEntity) const
233{
234 assert(Exists());
235 assert(rOtherEntity.Exists());
236 return fs::last_write_time(mAbsPath) > fs::last_write_time(rOtherEntity.mAbsPath);
237}
238
239std::string FileFinder::GetLeafName() const
240{
241 return fs::path(mAbsPath).filename().string();
242}
243
245{
246 return fs::path(mAbsPath).stem().string();
247}
248
249std::string FileFinder::GetExtension() const
250{
251 return fs::path(mAbsPath).extension().string();
252}
253
255{
256 fs::path our_path(mAbsPath);
257 // Assertion will only happen if constructed with an empty string and absolute path.
258 // (Arguably a user could do this, but not accidentally.)
259 assert(!our_path.parent_path().empty());
260 return FileFinder(our_path.parent_path().string(),
262}
263
264std::string FileFinder::GetRelativePath(const FileFinder& rBasePath) const
265{
266 const std::string base_path = rBasePath.GetAbsolutePath();
267 const std::string our_path = GetAbsolutePath();
268 if (our_path.substr(0, base_path.length()) != base_path)
269 {
270 EXCEPTION("The path '" << our_path << "' is not relative to '" << base_path << "'.");
271 }
272 return our_path.substr(base_path.length());
273}
274
280void RecursiveCopy(const fs::path& rFromPath, const fs::path& rToPath);
281
282void RecursiveCopy(const fs::path& rFromPath, const fs::path& rToPath)
283{
284 fs::path dest = rToPath;
285 // If rToPath is a folder, then we're copying to the source name *inside* this folder
286 if (fs::is_directory(dest))
287 {
288 dest /= rFromPath.filename();
289 }
290 // If the source is a folder, it's complicated
291 if (fs::is_directory(rFromPath))
292 {
293 // Create the destination folder
294 EXCEPT_IF(fs::exists(dest));
295 fs::create_directory(dest);
296 // Recursively copy our contents
297 fs::directory_iterator end_iter;
298 for (fs::directory_iterator dir_iter(rFromPath); dir_iter != end_iter; ++dir_iter)
299 {
300 RecursiveCopy(dir_iter->path(), dest);
301 }
302 }
303 else
304 {
305 fs::copy_file(rFromPath, dest); // Just copy!
306 }
307}
308
310{
311 if (!Exists())
312 {
313 EXCEPTION("Cannot copy '" << mAbsPath << "' as it does not exist.");
314 }
315 fs::path from_path(mAbsPath);
316 fs::path to_path(rDest.mAbsPath);
317 if (rDest.IsDir())
318 {
319 to_path /= from_path.filename();
320 }
321 if (fs::exists(to_path))
322 {
323 if (IsFile())
324 {
325 CONVERT_ERROR(fs::remove(to_path));
326 }
327 else
328 {
329 EXCEPTION("Cannot copy '" << mAbsPath << "' to '" << to_path << "' as it would overwrite an existing file.");
330 }
331 }
332 CONVERT_ERROR(RecursiveCopy(from_path, to_path));
333 return FileFinder(to_path);
334}
335
340void RemoveAll(const fs::path& rPath);
341
342void 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
357void 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_filename();
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
422{
424}
425
427{
428 PrivateRemove(true);
429}
430
431std::vector<FileFinder> FileFinder::FindMatches(const std::string& rPattern) const
432{
433 // Check for error/warning cases
434 if (!IsDir())
435 {
436 EXCEPTION("Cannot search for matching files in '" << mAbsPath << "' as it is not a directory.");
437 }
438 size_t len = rPattern.length();
439 size_t inner_star_pos = rPattern.find('*', 1);
440 if (inner_star_pos != std::string::npos && inner_star_pos < len - 1)
441 {
442 WARNING("A '*' only has special meaning at the start or end of a pattern.");
443 }
444
445 // Note initial or trailing *, and use of ?
446 std::string pattern(rPattern);
447 bool star_fini = false;
448 if (!pattern.empty() && *(pattern.rbegin()) == '*')
449 {
450 star_fini = true;
451 pattern = pattern.substr(0, len - 1);
452 len--;
453 }
454 bool star_init = false;
455 if (!pattern.empty() && pattern[0] == '*')
456 {
457 star_init = true;
458 pattern = pattern.substr(1);
459 len--;
460 }
461 bool has_query = (pattern.find('?') != std::string::npos);
462 // Disallow a harder case to match
463 if (star_init && star_fini && has_query)
464 {
465 EXCEPTION("The '*' wildcard may not be used at both the start and end of the pattern if the '?' wildcard is also used.");
466 }
467
468 // Search the folder
469 std::vector<FileFinder> results;
470 if (!rPattern.empty())
471 {
472 fs::directory_iterator end_iter;
473 fs::path our_path(mAbsPath);
474 for (fs::directory_iterator dir_iter(our_path); dir_iter != end_iter; ++dir_iter)
475 {
476 std::string leafname = dir_iter->path().filename().string();
477 size_t leaf_len = leafname.length();
478 if (leafname[0] != '.' // Don't include hidden files
479 && leaf_len >= len) // Ignore stuff that can't match
480 {
481 if (!has_query) // Easier case
482 {
483 size_t pos = leafname.find(pattern);
484 if ((star_init || pos == 0) && (star_fini || pos + len == leaf_len))
485 {
486 results.push_back(FileFinder(our_path / leafname));
487 }
488 }
489 else
490 {
491 std::string match;
492 if (star_init)
493 {
494 // Match against last len chars
495 match = leafname.substr(leaf_len - len);
496 }
497 else
498 {
499 // Match against first len chars
500 match = leafname.substr(0, len);
501 }
502 bool ok = true;
503 for (std::string::const_iterator it_p = pattern.begin(), it_m = match.begin();
504 it_p != pattern.end();
505 ++it_p, ++it_m)
506 {
507 if (*it_p != '?' && *it_p != *it_m)
508 {
509 ok = false;
510 break;
511 }
512 }
513 if (ok)
514 {
515 results.push_back(FileFinder(our_path / leafname));
516 }
517 }
518 }
519 }
520 }
521
522 std::sort(results.begin(), results.end());
523 return results;
524}
525
526bool FileFinder::IsAbsolutePath(const std::string& rPath)
527{
528 return fs::path(rPath).is_absolute();
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
553bool FileFinder::operator<(const FileFinder& otherFinder) const
554{
555 return (mAbsPath < otherFinder.GetAbsolutePath());
556}
557
558void FileFinder::FakePath(RelativeTo::Value fakeWhat, const std::string& rFakePath)
559{
560 msFakeWhat = fakeWhat;
561 msFakePath = rFakePath;
562 msFaking = true;
563}
564
566{
567 msFaking = false;
568}
const char * ChasteSourceRootDir()
const char * ChasteBuildRootDir()
#define EXCEPTION(message)
#define EXCEPT_IF(test)
#define NEVER_REACHED
static std::string ToPosix(const fs::path path)
void Remove() const
std::string GetLeafNameNoExtension() const
std::string mAbsPath
std::string GetRelativePath(const FileFinder &rBasePath) const
bool IsEmpty() const
bool IsNewerThan(const FileFinder &rOtherEntity) const
static void ReplaceSpacesWithUnderscores(std::string &rPath)
std::string GetAbsolutePath() const
static bool IsAbsolutePath(const std::string &rPath)
static bool msFaking
void PrivateRemove(bool dangerous=false) const
void DangerousRemove() const
std::string GetExtension() const
std::vector< FileFinder > FindMatches(const std::string &rPattern) const
FileFinder GetParent() const
static void FakePath(RelativeTo::Value fakeWhat, const std::string &rFakePath)
static RelativeTo::Value msFakeWhat
bool IsFile() const
static void ReplaceUnderscoresWithSpaces(std::string &rPath)
bool IsDir() const
bool operator<(const FileFinder &otherFinder) const
std::string GetLeafName() const
virtual void SetPath(const std::string &rPath, RelativeTo::Value relativeTo)
bool Exists() const
static std::string msFakePath
virtual ~FileFinder()
FileFinder CopyTo(const FileFinder &rDest) const
bool IsPathSet() const
static void StopFaking()
static std::string GetChasteTestOutputDirectory()
static const std::string SIG_FILE_NAME