/ src / util / fs.h
fs.h
  1  // Copyright (c) 2017-present The Bitcoin Core developers
  2  // Distributed under the MIT software license, see the accompanying
  3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  4  
  5  #ifndef BITCOIN_UTIL_FS_H
  6  #define BITCOIN_UTIL_FS_H
  7  
  8  // IWYU incorrectly suggests removing this header.
  9  // See https://github.com/include-what-you-use/include-what-you-use/issues/1931.
 10  #include <tinyformat.h> // IWYU pragma: keep
 11  
 12  #include <cstdio>
 13  // The `util/fs.h` header is designed to be a drop-in replacement for `filesystem`.
 14  #include <filesystem> // IWYU pragma: export
 15  #include <functional>
 16  #include <iomanip>
 17  #include <ios>
 18  #include <string>
 19  #include <string_view>
 20  #include <type_traits>
 21  #include <utility>
 22  
 23  /** Filesystem operations and types */
 24  namespace fs {
 25  
 26  using namespace std::filesystem;
 27  
 28  /**
 29   * Path class wrapper to block calls to the fs::path(std::string) implicit
 30   * constructor and the fs::path::string() method, which have unsafe and
 31   * unpredictable behavior on Windows (see implementation note in
 32   * \ref PathToString for details)
 33   */
 34  class path : public std::filesystem::path
 35  {
 36  public:
 37      using std::filesystem::path::path;
 38  
 39      // Convenience method for accessing standard path type without needing a cast.
 40      std::filesystem::path& std_path() { return *this; }
 41      const std::filesystem::path& std_path() const { return *this; }
 42  
 43      // Allow path objects arguments for compatibility.
 44      path(std::filesystem::path path) : std::filesystem::path::path(std::move(path)) {}
 45      path& operator=(std::filesystem::path path) { std::filesystem::path::operator=(std::move(path)); return *this; }
 46      path& operator/=(const std::filesystem::path& path) { std::filesystem::path::operator/=(path); return *this; }
 47  
 48      // Allow literal string arguments, which are safe as long as the literals are ASCII.
 49      path(const char* c) : std::filesystem::path(c) {}
 50      path& operator=(const char* c) { std::filesystem::path::operator=(c); return *this; }
 51      path& operator/=(const char* c) { std::filesystem::path::operator/=(c); return *this; }
 52      path& append(const char* c) { std::filesystem::path::append(c); return *this; }
 53  
 54      // Disallow std::string arguments to avoid locale-dependent decoding on windows.
 55      path(std::string) = delete;
 56      path& operator=(std::string) = delete;
 57      path& operator/=(std::string) = delete;
 58      path& append(std::string) = delete;
 59  
 60      // Disallow std::string conversion method to avoid locale-dependent encoding on windows.
 61      std::string string() const = delete;
 62  
 63      // Disallow implicit string conversion to ensure code is portable.
 64      // `string_type` may be `string` or `wstring` depending on the platform, so
 65      // using this conversion could result in code that compiles on unix but
 66      // fails to compile on windows, or vice versa.
 67      operator string_type() const = delete;
 68  
 69      /**
 70       * Return a UTF-8 representation of the path as a std::string, for
 71       * compatibility with code using std::string. For code using the newer
 72       * std::u8string type, it is more efficient to call the inherited
 73       * std::filesystem::path::u8string method instead.
 74       */
 75      std::string utf8string() const
 76      {
 77          const std::u8string& utf8_str{std::filesystem::path::u8string()};
 78          return std::string{utf8_str.begin(), utf8_str.end()};
 79      }
 80  };
 81  
 82  static inline path u8path(std::string_view utf8_str)
 83  {
 84      return std::filesystem::path(std::u8string{utf8_str.begin(), utf8_str.end()});
 85  }
 86  
 87  // Disallow implicit std::string conversion for absolute to avoid
 88  // locale-dependent encoding on windows.
 89  static inline path absolute(const path& p)
 90  {
 91      return std::filesystem::absolute(p);
 92  }
 93  
 94  // Disallow implicit std::string conversion for exists to avoid
 95  // locale-dependent encoding on windows.
 96  static inline bool exists(const path& p)
 97  {
 98      return std::filesystem::exists(p);
 99  }
100  static inline bool exists(const std::filesystem::file_status& s)
101  {
102      return std::filesystem::exists(s);
103  }
104  
105  // Allow explicit quoted stream I/O.
106  static inline auto quoted(const std::string& s)
107  {
108      return std::quoted(s, '"', '&');
109  }
110  
111  // Allow safe path append operations.
112  static inline path operator/(path p1, const path& p2)
113  {
114      p1 /= p2;
115      return p1;
116  }
117  static inline path operator/(path p1, const char* p2)
118  {
119      p1 /= p2;
120      return p1;
121  }
122  static inline path operator+(path p1, const char* p2)
123  {
124      p1 += p2;
125      return p1;
126  }
127  static inline path operator+(path p1, path::value_type p2)
128  {
129      p1 += p2;
130      return p1;
131  }
132  
133  // Disallow unsafe path append operations.
134  template<typename T> static inline path operator/(path p1, T p2) = delete;
135  template<typename T> static inline path operator+(path p1, T p2) = delete;
136  
137  // Disallow implicit std::string conversion for copy_file
138  // to avoid locale-dependent encoding on Windows.
139  static inline bool copy_file(const path& from, const path& to, copy_options options)
140  {
141      return std::filesystem::copy_file(from, to, options);
142  }
143  
144  /**
145   * Convert path object to a byte string. On POSIX, paths natively are byte
146   * strings, so this is trivial. On Windows, paths natively are Unicode, so an
147   * encoding step is necessary. The inverse of \ref PathToString is \ref
148   * PathFromString. The strings returned and parsed by these functions can be
149   * used to call POSIX APIs, and for roundtrip conversion, logging, and
150   * debugging.
151   *
152   * Because \ref PathToString and \ref PathFromString functions don't specify an
153   * encoding, they are meant to be used internally, not externally. They are not
154   * appropriate to use in applications requiring UTF-8, where
155   * fs::path::u8string() / fs::path::utf8string() and fs::u8path() methods should be used instead. Other
156   * applications could require still different encodings. For example, JSON, XML,
157   * or URI applications might prefer to use higher-level escapes (\uXXXX or
158   * &XXXX; or %XX) instead of multibyte encoding. Rust, Python, Java applications
159   * may require encoding paths with their respective UTF-8 derivatives WTF-8,
160   * PEP-383, and CESU-8 (see https://en.wikipedia.org/wiki/UTF-8#Derivatives).
161   */
162  static inline std::string PathToString(const path& path)
163  {
164      // Implementation note: On Windows, the std::filesystem::path(string)
165      // constructor and std::filesystem::path::string() method are not safe to
166      // use here, because these methods encode the path using C++'s narrow
167      // multibyte encoding, which on Windows corresponds to the current "code
168      // page", which is unpredictable and typically not able to represent all
169      // valid paths. So fs::path::utf8string() and
170      // fs::u8path() functions are used instead on Windows. On
171      // POSIX, u8string/utf8string/u8path functions are not safe to use because paths are
172      // not always valid UTF-8, so plain string methods which do not transform
173      // the path there are used.
174  #ifdef WIN32
175      return path.utf8string();
176  #else
177      static_assert(std::is_same_v<path::string_type, std::string>, "PathToString not implemented on this platform");
178      return path.std::filesystem::path::string();
179  #endif
180  }
181  
182  /**
183   * Convert byte string to path object. Inverse of \ref PathToString.
184   */
185  static inline path PathFromString(const std::string& string)
186  {
187  #ifdef WIN32
188      return u8path(string);
189  #else
190      return std::filesystem::path(string);
191  #endif
192  }
193  } // namespace fs
194  
195  /** Bridge operations to C stdio */
196  namespace fsbridge {
197      using FopenFn = std::function<FILE*(const fs::path&, const char*)>;
198      FILE *fopen(const fs::path& p, const char *mode);
199  
200      /**
201       * Helper function for joining two paths
202       *
203       * @param[in] base  Base path
204       * @param[in] path  Path to combine with base
205       * @returns path unchanged if it is an absolute path, otherwise returns base joined with path. Returns base unchanged if path is empty.
206       * @pre  Base path must be absolute
207       * @post Returned path will always be absolute
208       */
209      fs::path AbsPathJoin(const fs::path& base, const fs::path& path);
210  
211      class FileLock
212      {
213      public:
214          FileLock() = delete;
215          FileLock(const FileLock&) = delete;
216          FileLock(FileLock&&) = delete;
217          explicit FileLock(const fs::path& file);
218          ~FileLock();
219          bool TryLock();
220          std::string GetReason() { return reason; }
221  
222      private:
223          std::string reason;
224  #ifndef WIN32
225          int fd = -1;
226  #else
227          void* hFile = (void*)-1; // INVALID_HANDLE_VALUE
228  #endif
229      };
230  };
231  
232  // Disallow path operator<< formatting in tinyformat to avoid locale-dependent
233  // encoding on windows.
234  namespace tinyformat {
235  template<> inline void formatValue(std::ostream&, const char*, const char*, int, const std::filesystem::path&) = delete;
236  template<> inline void formatValue(std::ostream&, const char*, const char*, int, const fs::path&) = delete;
237  } // namespace tinyformat
238  
239  #endif // BITCOIN_UTIL_FS_H