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