rcpputils  master
C++ API providing common utilities and data structures.
filesystem_helper.hpp
Go to the documentation of this file.
1 // Copyright (c) 2019, Open Source Robotics Foundation, Inc.
2 // All rights reserved.
3 //
4 // Software License Agreement (BSD License 2.0)
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions
8 // are met:
9 //
10 // * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 // * Redistributions in binary form must reproduce the above
13 // copyright notice, this list of conditions and the following
14 // disclaimer in the documentation and/or other materials provided
15 // with the distribution.
16 // * Neither the name of the copyright holders nor the names of its
17 // contributors may be used to endorse or promote products derived
18 // from this software without specific prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 //
33 // This file is originally from:
34 // https://github.com/ros/pluginlib/blob/1a4de29fa55173e9b897ca8ff57ebc88c047e0b3/pluginlib/include/pluginlib/impl/filesystem_helper.hpp
35 
44 #ifndef RCPPUTILS__FILESYSTEM_HELPER_HPP_
45 #define RCPPUTILS__FILESYSTEM_HELPER_HPP_
46 
47 #include <limits.h>
48 #include <sys/stat.h>
49 
50 #include <algorithm>
51 #include <cstring>
52 #include <string>
53 #include <vector>
54 
61 #ifdef _WIN32
62 # define RCPPUTILS_IMPL_OS_DIRSEP '\\'
63 #else
64 # define RCPPUTILS_IMPL_OS_DIRSEP '/'
65 #endif
66 
67 #ifdef _WIN32
68 # include <windows.h>
69 # include <direct.h>
70 # include <fileapi.h>
71 # include <io.h>
72 # define access _access_s
73 #else
74 # include <dirent.h>
75 # include <sys/types.h>
76 # include <unistd.h>
77 #endif
78 
79 #include "rcpputils/split.hpp"
80 
81 namespace rcpputils
82 {
83 namespace fs
84 {
85 
86 static constexpr const char kPreferredSeparator = RCPPUTILS_IMPL_OS_DIRSEP;
87 
94 class path
95 {
96 public:
101  : path("")
102  {}
103 
109  path(const std::string & p) // NOLINT(runtime/explicit): this is a conversion constructor
110  : path_(p), path_as_vector_(split(p, kPreferredSeparator))
111  {
112  std::replace(path_.begin(), path_.end(), '\\', kPreferredSeparator);
113  std::replace(path_.begin(), path_.end(), '/', kPreferredSeparator);
114  }
115 
119  path(const path & p) = default;
120 
127  {
128  return path_;
129  }
130 
136  bool exists() const
137  {
138  return access(path_.c_str(), 0) == 0;
139  }
140 
146  bool is_directory() const noexcept
147  {
148  struct stat stat_buffer;
149  const auto rc = stat(path_.c_str(), &stat_buffer);
150 
151  if (rc != 0) {
152  return false;
153  }
154 
155 #ifdef _WIN32
156  return (stat_buffer.st_mode & S_IFDIR) == S_IFDIR;
157 #else
158  return S_ISDIR(stat_buffer.st_mode);
159 #endif
160  }
161 
167  bool is_regular_file() const noexcept
168  {
169  struct stat stat_buffer;
170  const auto rc = stat(path_.c_str(), &stat_buffer);
171 
172  if (rc != 0) {
173  return false;
174  }
175 
176 #ifdef _WIN32
177  return (stat_buffer.st_mode & S_IFREG) == S_IFREG;
178 #else
179  return S_ISREG(stat_buffer.st_mode);
180 #endif
181  }
182 
189  uint64_t file_size() const
190  {
191  if (is_directory()) {
192  auto ec = std::make_error_code(std::errc::is_a_directory);
193  throw std::system_error{ec, "cannot get file size"};
194  }
195 
196  struct stat stat_buffer;
197  const auto rc = stat(path_.c_str(), &stat_buffer);
198 
199  if (rc != 0) {
201  errno = 0;
202  throw std::system_error{ec, "cannot get file size"};
203  } else {
204  return static_cast<uint64_t>(stat_buffer.st_size);
205  }
206  }
207 
213  bool empty() const
214  {
215  return path_.empty();
216  }
217 
223  bool is_absolute() const
224  {
225  return path_.size() > 0 &&
226  (path_.compare(0, 1, std::string(1, kPreferredSeparator)) == 0 ||
227  this->is_absolute_with_drive_letter());
228  }
229 
236  {
237  return path_as_vector_.cbegin();
238  }
239 
246  {
247  return path_as_vector_.cend();
248  }
249 
256  {
257  // Edge case: empty path
258  if (this->empty()) {
259  return path("");
260  }
261 
262  // Edge case: if path only consists of one part, then return '.' or '/'
263  // depending if the path is absolute or not
264  if (1u == path_as_vector_.size()) {
265  if (this->is_absolute()) {
266  // Windows is tricky, since an absolute path may start with 'C:\\' or '\\'
267  if (this->is_absolute_with_drive_letter()) {
268  return path(path_as_vector_[0] + kPreferredSeparator);
269  }
270  return path(std::string(1, kPreferredSeparator));
271  }
272  return path(".");
273  }
274 
275  // Edge case: with a path 'C:\\foo' we want to return 'C:\\' not 'C:'
276  // Don't drop the root directory from an absolute path on Windows starting with a letter drive
277  if (2u == path_as_vector_.size() && this->is_absolute_with_drive_letter()) {
278  return path(path_as_vector_[0] + kPreferredSeparator);
279  }
280 
281  path parent;
282  for (auto it = this->cbegin(); it != --this->cend(); ++it) {
283  if (!parent.empty() || it->empty()) {
284  parent /= *it;
285  } else {
286  parent = *it;
287  }
288  }
289  return parent;
290  }
291 
299  path filename() const
300  {
301  return path_.empty() ? path() : *--this->cend();
302  }
303 
309  path extension() const
310  {
311  const char * delimiter = ".";
312  auto split_fname = split(this->string(), *delimiter);
313  return split_fname.size() == 1 ? path("") : path("." + split_fname.back());
314  }
315 
322  path operator/(const std::string & other)
323  {
324  return this->operator/(path(other));
325  }
326 
333  path & operator/=(const std::string & other)
334  {
335  this->operator/=(path(other));
336  return *this;
337  }
338 
345  path operator/(const path & other)
346  {
347  return path(*this).operator/=(other);
348  }
349 
356  path & operator/=(const path & other)
357  {
358  if (other.is_absolute()) {
359  this->path_ = other.path_;
360  this->path_as_vector_ = other.path_as_vector_;
361  } else {
362  this->path_ += kPreferredSeparator + other.string();
363  this->path_as_vector_.insert(
364  std::end(this->path_as_vector_),
365  std::begin(other.path_as_vector_), std::end(other.path_as_vector_));
366  }
367  return *this;
368  }
369 
370 private:
372  bool is_absolute_with_drive_letter() const
373  {
374 #ifdef _WIN32
375  if (path_.empty()) {
376  return false;
377  }
378  return 0 == path_.compare(1, 2, ":\\");
379 #else
380  return false; // only Windows contains absolute paths starting with drive letters
381 #endif
382  }
383 
384  std::string path_;
385  std::vector<std::string> path_as_vector_;
386 };
387 
394 inline bool is_regular_file(const path & p) noexcept
395 {
396  return p.is_regular_file();
397 }
398 
405 inline bool is_directory(const path & p) noexcept
406 {
407  return p.is_directory();
408 }
409 
418 inline uint64_t file_size(const path & p)
419 {
420  return p.file_size();
421 }
422 
429 inline bool exists(const path & path_to_check)
430 {
431  return path_to_check.exists();
432 }
433 
434 
441 {
442 #ifdef _WIN32
443 #ifdef UNICODE
444 #error "rcpputils::fs does not support Unicode paths"
445 #endif
446  TCHAR temp_path[MAX_PATH];
447  DWORD size = GetTempPathA(MAX_PATH, temp_path);
448  if (size > MAX_PATH || size == 0) {
449  std::error_code ec(static_cast<int>(GetLastError()), std::system_category());
450  throw std::system_error(ec, "cannot get temporary directory path");
451  }
452  temp_path[size] = '\0';
453 #else
454  const char * temp_path = getenv("TMPDIR");
455  if (!temp_path) {
456  temp_path = "/tmp";
457  }
458 #endif
459  return path(temp_path);
460 }
461 
470 {
471 #ifdef _WIN32
472 #ifdef UNICODE
473 #error "rcpputils::fs does not support Unicode paths"
474 #endif
475  char cwd[MAX_PATH];
476  if (nullptr == _getcwd(cwd, MAX_PATH)) {
477 #else
478  char cwd[PATH_MAX];
479  if (nullptr == getcwd(cwd, PATH_MAX)) {
480 #endif
482  errno = 0;
483  throw std::system_error{ec, "cannot get current working directory"};
484  }
485 
486  return path(cwd);
487 }
488 
495 inline bool create_directories(const path & p)
496 {
497  path p_built;
498  int status = 0;
499 
500  for (auto it = p.cbegin(); it != p.cend() && status == 0; ++it) {
501  if (!p_built.empty() || it->empty()) {
502  p_built /= *it;
503  } else {
504  p_built = *it;
505  }
506  if (!p_built.exists()) {
507 #ifdef _WIN32
508  status = _mkdir(p_built.string().c_str());
509 #else
510  status = mkdir(p_built.string().c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
511 #endif
512  }
513  }
514  return status == 0;
515 }
516 
523 inline bool remove(const path & p)
524 {
525 #ifdef _WIN32
526  struct _stat s;
527  if (_stat(p.string().c_str(), &s) == 0) {
528  if (s.st_mode & S_IFDIR) {
529  return _rmdir(p.string().c_str()) == 0;
530  }
531  if (s.st_mode & S_IFREG) {
532  return ::remove(p.string().c_str()) == 0;
533  }
534  }
535  return false;
536 #else
537  return ::remove(p.string().c_str()) == 0;
538 #endif
539 }
540 
549 inline bool remove_all(const path & p)
550 {
551  if (!is_directory(p)) {return remove(p);}
552 
553 #ifdef _WIN32
554  // We need a string of type PCZZTSTR, which is a double null terminated char ptr
555  size_t length = p.string().size();
556  TCHAR * temp_dir = new TCHAR[length + 2];
557  memcpy(temp_dir, p.string().c_str(), length);
558  temp_dir[length] = '\0';
559  temp_dir[length + 1] = '\0'; // double null terminated
560 
561  SHFILEOPSTRUCT file_options;
562  file_options.hwnd = nullptr;
563  file_options.wFunc = FO_DELETE; // delete (recursively)
564  file_options.pFrom = temp_dir;
565  file_options.pTo = nullptr;
566  file_options.fFlags = FOF_NOCONFIRMATION | FOF_SILENT; // do not prompt user
567  file_options.fAnyOperationsAborted = FALSE;
568  file_options.lpszProgressTitle = nullptr;
569  file_options.hNameMappings = nullptr;
570 
571  auto ret = SHFileOperation(&file_options);
572  delete[] temp_dir;
573 
574  return 0 == ret && false == file_options.fAnyOperationsAborted;
575 #else
576  DIR * dir = opendir(p.string().c_str());
577  struct dirent * directory_entry;
578  while ((directory_entry = readdir(dir)) != nullptr) {
579  // Make sure to not call ".." or "." entries in directory (might delete everything)
580  if (strcmp(directory_entry->d_name, ".") != 0 && strcmp(directory_entry->d_name, "..") != 0) {
581  auto sub_path = rcpputils::fs::path(p) / directory_entry->d_name;
582  // if directory, call recursively
583  if (sub_path.is_directory() && !remove_all(sub_path)) {
584  return false;
585  // if not, call regular remove
586  } else if (!remove(sub_path)) {
587  return false;
588  }
589  }
590  }
591  closedir(dir);
592  // directory is empty now, call remove
593  remove(p);
594  return !rcpputils::fs::exists(p);
595 #endif
596 }
597 
607 inline path remove_extension(const path & file_path, int n_times = 1)
608 {
609  path new_path(file_path);
610  for (int i = 0; i < n_times; i++) {
611  const auto new_path_str = new_path.string();
612  const auto last_dot = new_path_str.find_last_of('.');
613  if (last_dot == std::string::npos) {
614  return new_path;
615  }
616  new_path = path(new_path_str.substr(0, last_dot));
617  }
618  return new_path;
619 }
620 
621 #undef RCPPUTILS_IMPL_OS_DIRSEP
622 
623 } // namespace fs
624 } // namespace rcpputils
625 
626 #endif // RCPPUTILS__FILESYSTEM_HELPER_HPP_
rcpputils::fs::path::exists
bool exists() const
Check if this path exists.
Definition: filesystem_helper.hpp:136
rcpputils::fs::is_regular_file
bool is_regular_file(const path &p) noexcept
Check if the path is a regular file.
Definition: filesystem_helper.hpp:394
rcpputils::fs::path::operator/
path operator/(const path &other)
Concatenate two paths together.
Definition: filesystem_helper.hpp:345
rcpputils::fs::remove
bool remove(const path &p)
Remove the file or directory at the path p.
Definition: filesystem_helper.hpp:523
std::string
rcpputils::fs::temp_directory_path
path temp_directory_path()
Get a path to a location in the temporary directory, if it's available.
Definition: filesystem_helper.hpp:440
rcpputils::fs::file_size
uint64_t file_size(const path &p)
Get the file size of the path.
Definition: filesystem_helper.hpp:418
std::system_error
rcpputils
Definition: asserts.hpp:37
std::vector
std::string::size
T size(T... args)
RCPPUTILS_IMPL_OS_DIRSEP
#define RCPPUTILS_IMPL_OS_DIRSEP
Definition: filesystem_helper.hpp:64
rcpputils::fs::current_path
path current_path()
Return current working directory.
Definition: filesystem_helper.hpp:469
split.hpp
Split string by provided delimiter.
rcpputils::fs::path::filename
path filename() const
Get the last element in this path.
Definition: filesystem_helper.hpp:299
rcpputils::fs::path::parent_path
path parent_path() const
Get the parent directory of this path.
Definition: filesystem_helper.hpp:255
std::replace
T replace(T... args)
std::error_code
rcpputils::fs::path::operator/=
path & operator/=(const path &other)
Append a string component to this path.
Definition: filesystem_helper.hpp:356
rcpputils::fs::path::cend
std::vector< std::string >::const_iterator cend() const
Definition: filesystem_helper.hpp:245
rcpputils::fs::path::empty
bool empty() const
Check if the path is empty.
Definition: filesystem_helper.hpp:213
std::system_category
T system_category(T... args)
rcpputils::fs::path::is_absolute
bool is_absolute() const
Check if the path is an absolute path.
Definition: filesystem_helper.hpp:223
rcpputils::fs::remove_extension
path remove_extension(const path &file_path, int n_times=1)
Remove extension(s) from a path.
Definition: filesystem_helper.hpp:607
rcpputils::fs::path::path
path(const std::string &p)
Conversion constructor from a std::string path.
Definition: filesystem_helper.hpp:109
rcpputils::fs::path::file_size
uint64_t file_size() const
Return the size of the file in bytes.
Definition: filesystem_helper.hpp:189
rcpputils::split
void split(const std::string &input, char delim, InsertIterator &it, bool skip_empty=false)
Split a specified input into tokens using a delimiter and a type erased insert iterator.
Definition: split.hpp:68
std::string::c_str
T c_str(T... args)
std::string::compare
T compare(T... args)
std::string::find_last_of
T find_last_of(T... args)
rcpputils::fs::is_directory
bool is_directory(const path &p) noexcept
Check if the path is a directory.
Definition: filesystem_helper.hpp:405
rcpputils::fs::path::string
std::string string() const
Get the path delimited using this system's path separator.
Definition: filesystem_helper.hpp:126
rcpputils::fs::path::operator/
path operator/(const std::string &other)
Concatenate a path and a string into a single path.
Definition: filesystem_helper.hpp:322
rcpputils::fs::path
Drop-in replacement for std::filesystem::path.
Definition: filesystem_helper.hpp:94
rcpputils::fs::path::cbegin
std::vector< std::string >::const_iterator cbegin() const
Const iterator to first element of this path.
Definition: filesystem_helper.hpp:235
rcpputils::fs::path::extension
path extension() const
Get a relative path to the component including and following the last '.'.
Definition: filesystem_helper.hpp:309
rcpputils::fs::exists
bool exists(const path &path_to_check)
Check if a path exists.
Definition: filesystem_helper.hpp:429
std::string::begin
T begin(T... args)
std::vector::insert
T insert(T... args)
rcpputils::fs::create_directories
bool create_directories(const path &p)
Create a directory with the given path p.
Definition: filesystem_helper.hpp:495
rcpputils::fs::path::is_directory
bool is_directory() const noexcept
Check if the path exists and it is a directory.
Definition: filesystem_helper.hpp:146
std::string::empty
T empty(T... args)
std::string::end
T end(T... args)
rcpputils::fs::path::path
path()
Constructs an empty path.
Definition: filesystem_helper.hpp:100
rcpputils::fs::remove_all
bool remove_all(const path &p)
Remove the directory at the path p and its content.
Definition: filesystem_helper.hpp:549
rcpputils::fs::path::operator/=
path & operator/=(const std::string &other)
Append a string component to this path.
Definition: filesystem_helper.hpp:333
rcpputils::fs::path::is_regular_file
bool is_regular_file() const noexcept
Check if the path is a regular file.
Definition: filesystem_helper.hpp:167