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