fs.h raw

   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
 240