Line data Source code
1 : // Copyright (c) 2017-2021 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_FS_H
6 : #define BITCOIN_FS_H
7 :
8 : #include <tinyformat.h>
9 :
10 : #include <cstdio>
11 : #include <filesystem> // IWYU pragma: export
12 : #include <iomanip>
13 : #include <ios>
14 : #include <ostream>
15 : #include <string>
16 : #include <system_error>
17 : #include <type_traits>
18 : #include <utility>
19 :
20 : /** Filesystem operations and types */
21 : namespace fs {
22 :
23 : using namespace std::filesystem;
24 :
25 : /**
26 : * Path class wrapper to block calls to the fs::path(std::string) implicit
27 : * constructor and the fs::path::string() method, which have unsafe and
28 : * unpredictable behavior on Windows (see implementation note in
29 : * \ref PathToString for details)
30 : */
31 : class path : public std::filesystem::path
32 : {
33 : public:
34 : using std::filesystem::path::path;
35 :
36 : // Allow path objects arguments for compatibility.
37 3567988 : path(std::filesystem::path path) : std::filesystem::path::path(std::move(path)) {}
38 10 : path& operator=(std::filesystem::path path) { std::filesystem::path::operator=(std::move(path)); return *this; }
39 1683029 : path& operator/=(const std::filesystem::path& path) { std::filesystem::path::operator/=(path); return *this; }
40 :
41 : // Allow literal string arguments, which are safe as long as the literals are ASCII.
42 110884 : path(const char* c) : std::filesystem::path(c) {}
43 9 : path& operator=(const char* c) { std::filesystem::path::operator=(c); return *this; }
44 108087 : path& operator/=(const char* c) { std::filesystem::path::operator/=(c); return *this; }
45 : path& append(const char* c) { std::filesystem::path::append(c); return *this; }
46 :
47 : // Disallow std::string arguments to avoid locale-dependent decoding on windows.
48 : path(std::string) = delete;
49 : path& operator=(std::string) = delete;
50 : path& operator/=(std::string) = delete;
51 : path& append(std::string) = delete;
52 :
53 : // Disallow std::string conversion method to avoid locale-dependent encoding on windows.
54 : std::string string() const = delete;
55 :
56 : /**
57 : * Return a UTF-8 representation of the path as a std::string, for
58 : * compatibility with code using std::string. For code using the newer
59 : * std::u8string type, it is more efficient to call the inherited
60 : * std::filesystem::path::u8string method instead.
61 : */
62 369 : std::string utf8string() const
63 : {
64 369 : const std::u8string& utf8_str{std::filesystem::path::u8string()};
65 369 : return std::string{utf8_str.begin(), utf8_str.end()};
66 369 : }
67 :
68 : // Required for path overloads in <fstream>.
69 : // See https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=96e0367ead5d8dcac3bec2865582e76e2fbab190
70 21 : path& make_preferred() { std::filesystem::path::make_preferred(); return *this; }
71 7095 : path filename() const { return std::filesystem::path::filename(); }
72 : };
73 :
74 1618528 : static inline path u8path(const std::string& utf8_str)
75 : {
76 1618528 : return std::filesystem::path(std::u8string{utf8_str.begin(), utf8_str.end()});
77 0 : }
78 :
79 : // Disallow implicit std::string conversion for absolute to avoid
80 : // locale-dependent encoding on windows.
81 22222 : static inline path absolute(const path& p)
82 : {
83 22222 : return std::filesystem::absolute(p);
84 0 : }
85 :
86 : // Disallow implicit std::string conversion for exists to avoid
87 : // locale-dependent encoding on windows.
88 30005 : static inline bool exists(const path& p)
89 : {
90 30005 : return std::filesystem::exists(p);
91 : }
92 :
93 : // Allow explicit quoted stream I/O.
94 1990 : static inline auto quoted(const std::string& s)
95 : {
96 1990 : return std::quoted(s, '"', '&');
97 : }
98 :
99 : // Allow safe path append operations.
100 1674798 : static inline path operator/(path p1, const path& p2)
101 : {
102 1674798 : p1 /= p2;
103 1674798 : return p1;
104 : }
105 100073 : static inline path operator/(path p1, const char* p2)
106 : {
107 100073 : p1 /= p2;
108 100073 : return p1;
109 : }
110 9973 : static inline path operator+(path p1, const char* p2)
111 : {
112 9973 : p1 += p2;
113 9973 : return p1;
114 : }
115 21 : static inline path operator+(path p1, path::value_type p2)
116 : {
117 21 : p1 += p2;
118 21 : return p1;
119 : }
120 :
121 : // Disallow unsafe path append operations.
122 : template<typename T> static inline path operator/(path p1, T p2) = delete;
123 : template<typename T> static inline path operator+(path p1, T p2) = delete;
124 :
125 : // Disallow implicit std::string conversion for copy_file
126 : // to avoid locale-dependent encoding on Windows.
127 125 : static inline bool copy_file(const path& from, const path& to, copy_options options)
128 : {
129 125 : return std::filesystem::copy_file(from, to, options);
130 : }
131 :
132 : /**
133 : * Convert path object to a byte string. On POSIX, paths natively are byte
134 : * strings, so this is trivial. On Windows, paths natively are Unicode, so an
135 : * encoding step is necessary. The inverse of \ref PathToString is \ref
136 : * PathFromString. The strings returned and parsed by these functions can be
137 : * used to call POSIX APIs, and for roundtrip conversion, logging, and
138 : * debugging.
139 : *
140 : * Because \ref PathToString and \ref PathFromString functions don't specify an
141 : * encoding, they are meant to be used internally, not externally. They are not
142 : * appropriate to use in applications requiring UTF-8, where
143 : * fs::path::u8string() / fs::path::utf8string() and fs::u8path() methods should be used instead. Other
144 : * applications could require still different encodings. For example, JSON, XML,
145 : * or URI applications might prefer to use higher-level escapes (\uXXXX or
146 : * &XXXX; or %XX) instead of multibyte encoding. Rust, Python, Java applications
147 : * may require encoding paths with their respective UTF-8 derivatives WTF-8,
148 : * PEP-383, and CESU-8 (see https://en.wikipedia.org/wiki/UTF-8#Derivatives).
149 : */
150 414465 : static inline std::string PathToString(const path& path)
151 : {
152 : // Implementation note: On Windows, the std::filesystem::path(string)
153 : // constructor and std::filesystem::path::string() method are not safe to
154 : // use here, because these methods encode the path using C++'s narrow
155 : // multibyte encoding, which on Windows corresponds to the current "code
156 : // page", which is unpredictable and typically not able to represent all
157 : // valid paths. So fs::path::utf8string() and
158 : // fs::u8path() functions are used instead on Windows. On
159 : // POSIX, u8string/utf8string/u8path functions are not safe to use because paths are
160 : // not always valid UTF-8, so plain string methods which do not transform
161 : // the path there are used.
162 : #ifdef WIN32
163 : return path.utf8string();
164 : #else
165 : static_assert(std::is_same<path::string_type, std::string>::value, "PathToString not implemented on this platform");
166 414465 : return path.std::filesystem::path::string();
167 : #endif
168 : }
169 :
170 : /**
171 : * Convert byte string to path object. Inverse of \ref PathToString.
172 : */
173 55222 : static inline path PathFromString(const std::string& string)
174 : {
175 : #ifdef WIN32
176 : return u8path(string);
177 : #else
178 55222 : return std::filesystem::path(string);
179 : #endif
180 0 : }
181 :
182 : /**
183 : * Create directory (and if necessary its parents), unless the leaf directory
184 : * already exists or is a symlink to an existing directory.
185 : * This is a temporary workaround for an issue in libstdc++ that has been fixed
186 : * upstream [PR101510].
187 : */
188 1625071 : static inline bool create_directories(const std::filesystem::path& p)
189 : {
190 1625071 : if (std::filesystem::is_symlink(p) && std::filesystem::is_directory(p)) {
191 87 : return false;
192 : }
193 1624984 : return std::filesystem::create_directories(p);
194 1625071 : }
195 :
196 : /**
197 : * This variant is not used. Delete it to prevent it from accidentally working
198 : * around the workaround. If it is needed, add a workaround in the same pattern
199 : * as above.
200 : */
201 : bool create_directories(const std::filesystem::path& p, std::error_code& ec) = delete;
202 :
203 : } // namespace fs
204 :
205 : /** Bridge operations to C stdio */
206 : namespace fsbridge {
207 : FILE *fopen(const fs::path& p, const char *mode);
208 :
209 : /**
210 : * Helper function for joining two paths
211 : *
212 : * @param[in] base Base path
213 : * @param[in] path Path to combine with base
214 : * @returns path unchanged if it is an absolute path, otherwise returns base joined with path. Returns base unchanged if path is empty.
215 : * @pre Base path must be absolute
216 : * @post Returned path will always be absolute
217 : */
218 : fs::path AbsPathJoin(const fs::path& base, const fs::path& path);
219 :
220 : class FileLock
221 : {
222 : public:
223 : FileLock() = delete;
224 : FileLock(const FileLock&) = delete;
225 : FileLock(FileLock&&) = delete;
226 : explicit FileLock(const fs::path& file);
227 : ~FileLock();
228 : bool TryLock();
229 17 : std::string GetReason() { return reason; }
230 :
231 : private:
232 : std::string reason;
233 : #ifndef WIN32
234 : int fd = -1;
235 : #else
236 : void* hFile = (void*)-1; // INVALID_HANDLE_VALUE
237 : #endif
238 : };
239 :
240 : std::string get_filesystem_error_message(const fs::filesystem_error& e);
241 : };
242 :
243 : // Disallow path operator<< formatting in tinyformat to avoid locale-dependent
244 : // encoding on windows.
245 : namespace tinyformat {
246 : template<> inline void formatValue(std::ostream&, const char*, const char*, int, const std::filesystem::path&) = delete;
247 : template<> inline void formatValue(std::ostream&, const char*, const char*, int, const fs::path&) = delete;
248 : } // namespace tinyformat
249 :
250 : #endif // BITCOIN_FS_H
|