/src/libtorrent/src/path.cpp
Line | Count | Source |
1 | | /* |
2 | | |
3 | | Copyright (c) 2017-2019, Alden Torres |
4 | | Copyright (c) 2017-2021, Arvid Norberg |
5 | | Copyright (c) 2017, Steven Siloti |
6 | | Copyright (c) 2020, Kacper Michajłow |
7 | | Copyright (c) 2020, Tiger Wang |
8 | | All rights reserved. |
9 | | |
10 | | Redistribution and use in source and binary forms, with or without |
11 | | modification, are permitted provided that the following conditions |
12 | | are met: |
13 | | |
14 | | * Redistributions of source code must retain the above copyright |
15 | | notice, this list of conditions and the following disclaimer. |
16 | | * Redistributions in binary form must reproduce the above copyright |
17 | | notice, this list of conditions and the following disclaimer in |
18 | | the documentation and/or other materials provided with the distribution. |
19 | | * Neither the name of the author nor the names of its |
20 | | contributors may be used to endorse or promote products derived |
21 | | from this software without specific prior written permission. |
22 | | |
23 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
24 | | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
25 | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
26 | | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
27 | | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
28 | | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
29 | | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
30 | | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
31 | | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
32 | | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
33 | | POSSIBILITY OF SUCH DAMAGE. |
34 | | |
35 | | */ |
36 | | |
37 | | #include "libtorrent/aux_/disable_warnings_push.hpp" |
38 | | |
39 | | #ifdef __GNUC__ |
40 | | #pragma GCC diagnostic push |
41 | | #pragma GCC diagnostic ignored "-Wunused-macros" |
42 | | #endif |
43 | | |
44 | | #ifdef __clang__ |
45 | | #pragma clang diagnostic push |
46 | | #pragma clang diagnostic ignored "-Wunknown-pragmas" |
47 | | #pragma clang diagnostic ignored "-Wunused-macros" |
48 | | #pragma clang diagnostic ignored "-Wreserved-id-macro" |
49 | | #endif |
50 | | |
51 | | // on mingw this is necessary to enable 64-bit time_t, specifically used for |
52 | | // the stat struct. Without this, modification times returned by stat may be |
53 | | // incorrect and consistently fail resume data |
54 | | #ifndef __MINGW_USE_VC2005_COMPAT |
55 | | # define __MINGW_USE_VC2005_COMPAT |
56 | | #endif |
57 | | |
58 | | #ifdef __clang__ |
59 | | #pragma clang diagnostic pop |
60 | | #endif |
61 | | |
62 | | #ifdef __GNUC__ |
63 | | #pragma GCC diagnostic pop |
64 | | #endif |
65 | | |
66 | | #include "libtorrent/aux_/disable_warnings_pop.hpp" |
67 | | |
68 | | #include "libtorrent/config.hpp" |
69 | | #include "libtorrent/aux_/alloca.hpp" |
70 | | #include "libtorrent/aux_/path.hpp" |
71 | | #include "libtorrent/aux_/directory.hpp" |
72 | | #include "libtorrent/string_util.hpp" |
73 | | #include <cstring> |
74 | | |
75 | | #include "libtorrent/aux_/escape_string.hpp" // for convert_to_native |
76 | | #include "libtorrent/assert.hpp" |
77 | | #include "libtorrent/aux_/throw.hpp" |
78 | | |
79 | | #include "libtorrent/aux_/disable_warnings_push.hpp" |
80 | | |
81 | | #include <sys/stat.h> |
82 | | #include <climits> // for IOV_MAX |
83 | | |
84 | | #ifdef TORRENT_WINDOWS |
85 | | // windows part |
86 | | |
87 | | #include "libtorrent/utf8.hpp" |
88 | | #include "libtorrent/aux_/win_util.hpp" |
89 | | |
90 | | #include "libtorrent/aux_/windows.hpp" |
91 | | #include <winioctl.h> |
92 | | #ifndef TORRENT_MINGW |
93 | | #include <direct.h> // for _getcwd, _mkdir |
94 | | #else |
95 | | #include <dirent.h> |
96 | | #endif |
97 | | #include <sys/types.h> |
98 | | #else |
99 | | // posix part |
100 | | |
101 | | #include <unistd.h> |
102 | | #include <sys/types.h> |
103 | | #include <cerrno> |
104 | | #include <dirent.h> |
105 | | #include <sys/ioctl.h> |
106 | | |
107 | | #endif // posix part |
108 | | |
109 | | #include "libtorrent/aux_/disable_warnings_pop.hpp" |
110 | | #include "libtorrent/aux_/storage_utils.hpp" // copy_file |
111 | | |
112 | | namespace libtorrent { |
113 | | |
114 | | #if defined TORRENT_WINDOWS |
115 | | std::string convert_from_native_path(wchar_t const* s) |
116 | | { |
117 | | if (s[0] == L'\\' && s[1] == L'\\' && s[2] == L'?' && s[3] == L'\\') s += 4; |
118 | | return convert_from_wstring(s); |
119 | | } |
120 | | #else |
121 | 0 | std::string convert_from_native_path(char const* s) { return convert_from_native(s); } |
122 | | #endif |
123 | | |
124 | | namespace { |
125 | | struct free_function |
126 | | { |
127 | 0 | void operator()(void* ptr) const noexcept { std::free(ptr); } |
128 | | }; |
129 | | |
130 | | template <typename T> |
131 | | std::unique_ptr<T, free_function> make_free_holder(T* ptr) |
132 | 0 | { |
133 | 0 | return std::unique_ptr<T, free_function>(ptr, free_function{}); |
134 | 0 | } |
135 | | |
136 | | #ifdef TORRENT_WINDOWS |
137 | | time_t file_time_to_posix(FILETIME f) |
138 | | { |
139 | | const std::uint64_t posix_time_offset = 11644473600LL; |
140 | | std::uint64_t ft = (std::uint64_t(f.dwHighDateTime) << 32) |
141 | | | f.dwLowDateTime; |
142 | | |
143 | | // windows filetime is specified in 100 nanoseconds resolution. |
144 | | // convert to seconds |
145 | | return time_t(ft / 10000000 - posix_time_offset); |
146 | | } |
147 | | |
148 | | void fill_file_status(file_status & s, LARGE_INTEGER file_size, DWORD file_attributes, FILETIME creation_time, FILETIME last_access, FILETIME last_write) |
149 | | { |
150 | | s.file_size = file_size.QuadPart; |
151 | | s.ctime = file_time_to_posix(creation_time); |
152 | | s.atime = file_time_to_posix(last_access); |
153 | | s.mtime = file_time_to_posix(last_write); |
154 | | |
155 | | s.mode = (file_attributes & FILE_ATTRIBUTE_DIRECTORY) |
156 | | ? file_status::directory |
157 | | : (file_attributes & FILE_ATTRIBUTE_DEVICE) |
158 | | ? file_status::character_special : file_status::regular_file; |
159 | | } |
160 | | |
161 | | void fill_file_status(file_status & s, DWORD file_size_low, DWORD file_size_high, DWORD file_attributes, FILETIME creation_time, FILETIME last_access, FILETIME last_write) |
162 | | { |
163 | | LARGE_INTEGER file_size; |
164 | | file_size.HighPart = static_cast<LONG>(file_size_high); |
165 | | file_size.LowPart = file_size_low; |
166 | | |
167 | | fill_file_status(s, file_size, file_attributes, creation_time, last_access, last_write); |
168 | | } |
169 | | |
170 | | #ifdef TORRENT_WINRT |
171 | | FILETIME to_file_time(LARGE_INTEGER i) |
172 | | { |
173 | | FILETIME time; |
174 | | time.dwHighDateTime = i.HighPart; |
175 | | time.dwLowDateTime = i.LowPart; |
176 | | |
177 | | return time; |
178 | | } |
179 | | |
180 | | void fill_file_status(file_status & s, LARGE_INTEGER file_size, DWORD file_attributes, LARGE_INTEGER creation_time, LARGE_INTEGER last_access, LARGE_INTEGER last_write) |
181 | | { |
182 | | fill_file_status(s, file_size, file_attributes, to_file_time(creation_time), to_file_time(last_access), to_file_time(last_write)); |
183 | | } |
184 | | #endif |
185 | | #endif |
186 | | } // anonymous namespace |
187 | | |
188 | | native_path_string convert_to_native_path_string(std::string const& path) |
189 | 0 | { |
190 | | #ifdef TORRENT_WINDOWS |
191 | | #if TORRENT_USE_UNC_PATHS |
192 | | // UNC paths must be absolute |
193 | | // network paths are already UNC paths |
194 | | std::string prepared_path = complete(path); |
195 | | if (prepared_path.substr(0,2) != "\\\\") |
196 | | prepared_path = "\\\\?\\" + prepared_path; |
197 | | std::replace(prepared_path.begin(), prepared_path.end(), '/', '\\'); |
198 | | |
199 | | return convert_to_wstring(prepared_path); |
200 | | #else |
201 | | return convert_to_wstring(path); |
202 | | #endif |
203 | | #else // TORRENT_WINDOWS |
204 | 0 | return convert_to_native(path); |
205 | 0 | #endif |
206 | 0 | } |
207 | | |
208 | | void stat_file(std::string const& inf, file_status* s |
209 | | , error_code& ec, int const flags) |
210 | 0 | { |
211 | 0 | ec.clear(); |
212 | 0 | native_path_string f = convert_to_native_path_string(inf); |
213 | | #ifdef TORRENT_WINDOWS |
214 | | |
215 | | WIN32_FILE_ATTRIBUTE_DATA data; |
216 | | if (!GetFileAttributesExW(f.c_str(), GetFileExInfoStandard, &data)) |
217 | | { |
218 | | ec.assign(GetLastError(), system_category()); |
219 | | TORRENT_ASSERT(ec); |
220 | | return; |
221 | | } |
222 | | |
223 | | // Fallback to GetFileInformationByHandle for symlinks |
224 | | if (!(flags & dont_follow_links) && (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) |
225 | | { |
226 | | #ifdef TORRENT_WINRT |
227 | | |
228 | | CREATEFILE2_EXTENDED_PARAMETERS Extended |
229 | | { |
230 | | sizeof(CREATEFILE2_EXTENDED_PARAMETERS), |
231 | | 0, // no file attributes |
232 | | FILE_FLAG_BACKUP_SEMANTICS // in order to open a directory, we need the FILE_FLAG_BACKUP_SEMANTICS |
233 | | }; |
234 | | |
235 | | const auto h = CreateFile2(f.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ |
236 | | | FILE_SHARE_WRITE, OPEN_EXISTING, &Extended); |
237 | | |
238 | | if (h == INVALID_HANDLE_VALUE) |
239 | | { |
240 | | ec.assign(GetLastError(), system_category()); |
241 | | TORRENT_ASSERT(ec); |
242 | | return; |
243 | | } |
244 | | |
245 | | FILE_BASIC_INFO Basic; |
246 | | FILE_STANDARD_INFO Standard; |
247 | | |
248 | | if ( |
249 | | !GetFileInformationByHandleEx(h, FILE_INFO_BY_HANDLE_CLASS::FileBasicInfo, &Basic, sizeof(FILE_BASIC_INFO)) || |
250 | | !GetFileInformationByHandleEx(h, FILE_INFO_BY_HANDLE_CLASS::FileStandardInfo, &Standard, sizeof(FILE_STANDARD_INFO)) |
251 | | ) |
252 | | { |
253 | | ec.assign(GetLastError(), system_category()); |
254 | | TORRENT_ASSERT(ec); |
255 | | CloseHandle(h); |
256 | | return; |
257 | | } |
258 | | CloseHandle(h); |
259 | | |
260 | | fill_file_status(*s, Standard.EndOfFile, Basic.FileAttributes, Basic.CreationTime, Basic.LastAccessTime, Basic.LastWriteTime); |
261 | | return; |
262 | | |
263 | | #else |
264 | | |
265 | | // in order to open a directory, we need the FILE_FLAG_BACKUP_SEMANTICS |
266 | | HANDLE h = CreateFileW(f.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ |
267 | | | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); |
268 | | if (h == INVALID_HANDLE_VALUE) |
269 | | { |
270 | | ec.assign(GetLastError(), system_category()); |
271 | | TORRENT_ASSERT(ec); |
272 | | return; |
273 | | } |
274 | | |
275 | | BY_HANDLE_FILE_INFORMATION handle_data; |
276 | | if (!GetFileInformationByHandle(h, &handle_data)) |
277 | | { |
278 | | ec.assign(GetLastError(), system_category()); |
279 | | TORRENT_ASSERT(ec); |
280 | | CloseHandle(h); |
281 | | return; |
282 | | } |
283 | | CloseHandle(h); |
284 | | |
285 | | fill_file_status(*s, handle_data.nFileSizeLow, handle_data.nFileSizeHigh, handle_data.dwFileAttributes, handle_data.ftCreationTime, handle_data.ftLastAccessTime, handle_data.ftLastWriteTime); |
286 | | return; |
287 | | |
288 | | #endif |
289 | | } |
290 | | |
291 | | fill_file_status(*s, data.nFileSizeLow, data.nFileSizeHigh, data.dwFileAttributes, data.ftCreationTime, data.ftLastAccessTime, data.ftLastWriteTime); |
292 | | |
293 | | #else |
294 | | |
295 | | // posix version |
296 | |
|
297 | 0 | struct ::stat ret{}; |
298 | 0 | int retval; |
299 | 0 | if (flags & dont_follow_links) |
300 | 0 | retval = ::lstat(f.c_str(), &ret); |
301 | 0 | else |
302 | 0 | retval = ::stat(f.c_str(), &ret); |
303 | 0 | if (retval < 0) |
304 | 0 | { |
305 | 0 | ec.assign(errno, system_category()); |
306 | 0 | return; |
307 | 0 | } |
308 | | |
309 | | // make sure the _FILE_OFFSET_BITS define worked |
310 | | // on this platform. It's supposed to make file |
311 | | // related functions support 64-bit offsets. |
312 | 0 | static_assert(sizeof(ret.st_size) >= 8, "64 bit file operations are required"); |
313 | |
|
314 | 0 | s->file_size = ret.st_size; |
315 | 0 | s->atime = std::uint64_t(ret.st_atime); |
316 | 0 | s->mtime = std::uint64_t(ret.st_mtime); |
317 | 0 | s->ctime = std::uint64_t(ret.st_ctime); |
318 | |
|
319 | 0 | s->mode = (S_ISREG(ret.st_mode) ? file_status::regular_file : 0) |
320 | 0 | | (S_ISDIR(ret.st_mode) ? file_status::directory : 0) |
321 | 0 | | (S_ISLNK(ret.st_mode) ? file_status::link : 0) |
322 | 0 | | (S_ISFIFO(ret.st_mode) ? file_status::fifo : 0) |
323 | 0 | | (S_ISCHR(ret.st_mode) ? file_status::character_special : 0) |
324 | 0 | | (S_ISBLK(ret.st_mode) ? file_status::block_special : 0) |
325 | 0 | | (S_ISSOCK(ret.st_mode) ? file_status::socket : 0); |
326 | |
|
327 | 0 | #endif // TORRENT_WINDOWS |
328 | 0 | } |
329 | | |
330 | | void rename(std::string const& inf, std::string const& newf, error_code& ec) |
331 | 0 | { |
332 | 0 | ec.clear(); |
333 | |
|
334 | 0 | native_path_string f1 = convert_to_native_path_string(inf); |
335 | 0 | native_path_string f2 = convert_to_native_path_string(newf); |
336 | |
|
337 | 0 | if (f1 == f2) return; |
338 | | |
339 | | #if defined TORRENT_WINDOWS |
340 | | #define RenameFunction_ ::_wrename |
341 | | #else |
342 | 0 | #define RenameFunction_ ::rename |
343 | 0 | #endif |
344 | | |
345 | 0 | if (RenameFunction_(f1.c_str(), f2.c_str()) < 0) |
346 | 0 | { |
347 | 0 | ec.assign(errno, generic_category()); |
348 | 0 | } |
349 | 0 | #undef RenameFunction_ |
350 | 0 | } |
351 | | |
352 | | void create_directories(std::string const& f, error_code& ec) |
353 | 0 | { |
354 | 0 | ec.clear(); |
355 | 0 | if (is_directory(f, ec)) return; |
356 | 0 | if (ec != boost::system::errc::no_such_file_or_directory) |
357 | 0 | return; |
358 | 0 | ec.clear(); |
359 | 0 | if (is_root_path(f)) |
360 | 0 | { |
361 | | // this is just to set ec correctly, in case this root path isn't |
362 | | // mounted |
363 | 0 | file_status s; |
364 | 0 | stat_file(f, &s, ec); |
365 | 0 | return; |
366 | 0 | } |
367 | 0 | if (has_parent_path(f)) |
368 | 0 | { |
369 | 0 | create_directories(parent_path(f), ec); |
370 | 0 | if (ec) return; |
371 | 0 | } |
372 | 0 | create_directory(f, ec); |
373 | 0 | } |
374 | | |
375 | | void create_directory(std::string const& f, error_code& ec) |
376 | 0 | { |
377 | 0 | ec.clear(); |
378 | |
|
379 | 0 | native_path_string n = convert_to_native_path_string(f); |
380 | | #ifdef TORRENT_WINDOWS |
381 | | if (CreateDirectoryW(n.c_str(), nullptr) == 0 |
382 | | && GetLastError() != ERROR_ALREADY_EXISTS) |
383 | | ec.assign(GetLastError(), system_category()); |
384 | | #else |
385 | 0 | int ret = ::mkdir(n.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); |
386 | 0 | if (ret < 0 && errno != EEXIST) |
387 | 0 | ec.assign(errno, system_category()); |
388 | 0 | #endif |
389 | 0 | } |
390 | | |
391 | | void hard_link(std::string const& file, std::string const& link |
392 | | , error_code& ec) |
393 | 0 | { |
394 | 0 | native_path_string n_exist = convert_to_native_path_string(file); |
395 | 0 | native_path_string n_link = convert_to_native_path_string(link); |
396 | | #ifdef TORRENT_WINDOWS |
397 | | #ifndef TORRENT_WINRT |
398 | | |
399 | | BOOL ret = CreateHardLinkW(n_link.c_str(), n_exist.c_str(), nullptr); |
400 | | if (ret) |
401 | | { |
402 | | ec.clear(); |
403 | | return; |
404 | | } |
405 | | // something failed. Does the filesystem not support hard links? |
406 | | DWORD const error = GetLastError(); |
407 | | if (error != ERROR_INVALID_FUNCTION) |
408 | | { |
409 | | // it's possible CreateHardLink will copy the file internally too, |
410 | | // if the filesystem does not support it. |
411 | | ec.assign(GetLastError(), system_category()); |
412 | | return; |
413 | | } |
414 | | |
415 | | // fall back to making a copy |
416 | | #endif |
417 | | #else |
418 | | // assume posix's link() function exists |
419 | 0 | int ret = ::link(n_exist.c_str(), n_link.c_str()); |
420 | |
|
421 | 0 | if (ret == 0) |
422 | 0 | { |
423 | 0 | ec.clear(); |
424 | 0 | return; |
425 | 0 | } |
426 | | |
427 | | // most errors are passed through, except for the ones that indicate that |
428 | | // hard links are not supported and require a copy. |
429 | | // TODO: 2 test this on a FAT volume to see what error we get! |
430 | 0 | if (errno != EMLINK |
431 | 0 | && errno != EXDEV |
432 | | #ifdef TORRENT_BEOS |
433 | | // haiku returns EPERM when the filesystem doesn't support hard link |
434 | | && errno != EPERM |
435 | | #endif |
436 | 0 | ) |
437 | 0 | { |
438 | | // some error happened, report up to the caller |
439 | 0 | ec.assign(errno, system_category()); |
440 | 0 | return; |
441 | 0 | } |
442 | | |
443 | | // fall back to making a copy |
444 | | |
445 | 0 | #endif |
446 | | |
447 | | // if we get here, we should copy the file |
448 | 0 | storage_error se; |
449 | 0 | aux::copy_file(file, link, se); |
450 | 0 | ec = se.ec; |
451 | 0 | } |
452 | | |
453 | | bool is_directory(std::string const& f, error_code& ec) |
454 | 0 | { |
455 | 0 | ec.clear(); |
456 | 0 | error_code e; |
457 | 0 | file_status s; |
458 | 0 | stat_file(f, &s, e); |
459 | 0 | if (!e && s.mode & file_status::directory) return true; |
460 | 0 | ec = e; |
461 | 0 | return false; |
462 | 0 | } |
463 | | |
464 | | std::string extension(std::string const& f) |
465 | 0 | { |
466 | 0 | for (int i = int(f.size()) - 1; i >= 0; --i) |
467 | 0 | { |
468 | 0 | auto const idx = static_cast<std::size_t>(i); |
469 | 0 | if (f[idx] == '/') break; |
470 | | #ifdef TORRENT_WINDOWS |
471 | | if (f[idx] == '\\') break; |
472 | | #endif |
473 | 0 | if (f[idx] != '.') continue; |
474 | 0 | return f.substr(idx); |
475 | 0 | } |
476 | 0 | return ""; |
477 | 0 | } |
478 | | |
479 | | std::string remove_extension(std::string const& f) |
480 | 0 | { |
481 | 0 | char const* slash = std::strrchr(f.c_str(), '/'); |
482 | | #ifdef TORRENT_WINDOWS |
483 | | slash = std::max((char const*)std::strrchr(f.c_str(), '\\'), slash); |
484 | | #endif |
485 | 0 | char const* ext = std::strrchr(f.c_str(), '.'); |
486 | | // if we don't have an extension, just return f |
487 | 0 | if (ext == nullptr || ext == &f[0] || (slash != nullptr && ext < slash)) return f; |
488 | 0 | return f.substr(0, aux::numeric_cast<std::size_t>(ext - &f[0])); |
489 | 0 | } |
490 | | |
491 | | bool is_root_path(std::string const& f) |
492 | 0 | { |
493 | 0 | if (f.empty()) return false; |
494 | | |
495 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
496 | | // match \\ form |
497 | | if (f == "\\\\") return true; |
498 | | int i = 0; |
499 | | // match the xx:\ or xx:/ form |
500 | | while (f[i] && is_alpha(f[i])) ++i; |
501 | | if (i == int(f.size()-2) && f[i] == ':' && (f[i+1] == '\\' || f[i+1] == '/')) |
502 | | return true; |
503 | | // match network paths \\computer_name\ form |
504 | | if (f.size() > 2 && f[0] == '\\' && f[1] == '\\') |
505 | | { |
506 | | // we don't care about the last character, since it's OK for it |
507 | | // to be a slash or a back slash |
508 | | bool found = false; |
509 | | for (int j = 2; j < int(f.size()) - 1; ++j) |
510 | | { |
511 | | if (f[j] != '\\' && f[j] != '/') continue; |
512 | | // there is a directory separator in here, |
513 | | // i.e. this is not the root |
514 | | found = true; |
515 | | break; |
516 | | } |
517 | | if (!found) return true; |
518 | | } |
519 | | #else |
520 | | // as well as parent_path("/") should be "/". |
521 | 0 | if (f == "/") return true; |
522 | 0 | #endif |
523 | 0 | return false; |
524 | 0 | } |
525 | | |
526 | | bool path_equal(std::string const& lhs, std::string const& rhs) |
527 | 0 | { |
528 | 0 | std::string::size_type const lhs_size = !lhs.empty() |
529 | 0 | && (lhs[lhs.size()-1] == '/' |
530 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
531 | | || lhs[lhs.size()-1] == '\\' |
532 | | #endif |
533 | 0 | ) ? lhs.size() - 1 : lhs.size(); |
534 | |
|
535 | 0 | std::string::size_type const rhs_size = !rhs.empty() |
536 | 0 | && (rhs[rhs.size()-1] == '/' |
537 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
538 | | || rhs[rhs.size()-1] == '\\' |
539 | | #endif |
540 | 0 | ) ? rhs.size() - 1 : rhs.size(); |
541 | 0 | return lhs.compare(0, lhs_size, rhs, 0, rhs_size) == 0; |
542 | 0 | } |
543 | | |
544 | | // <0: lhs < rhs |
545 | | // 0: lhs == rhs |
546 | | // >0: lhs > rhs |
547 | | int path_compare(string_view const lhs, string_view const lfile |
548 | | , string_view const rhs, string_view const rfile) |
549 | 0 | { |
550 | 0 | for (auto lhs_elems = lsplit_path(lhs), rhs_elems = lsplit_path(rhs); |
551 | 0 | !lhs_elems.first.empty() || !rhs_elems.first.empty(); |
552 | 0 | lhs_elems = lsplit_path(lhs_elems.second), rhs_elems = lsplit_path(rhs_elems.second)) |
553 | 0 | { |
554 | 0 | if (lhs_elems.first.empty() || rhs_elems.first.empty()) |
555 | 0 | { |
556 | 0 | if (lhs_elems.first.empty()) lhs_elems.first = lfile; |
557 | 0 | if (rhs_elems.first.empty()) rhs_elems.first = rfile; |
558 | 0 | return lhs_elems.first.compare(rhs_elems.first); |
559 | 0 | } |
560 | | |
561 | 0 | int const ret = lhs_elems.first.compare(rhs_elems.first); |
562 | 0 | if (ret != 0) return ret; |
563 | 0 | } |
564 | 0 | return 0; |
565 | 0 | } |
566 | | |
567 | | bool has_parent_path(std::string const& f) |
568 | 0 | { |
569 | 0 | if (f.empty()) return false; |
570 | 0 | if (is_root_path(f)) return false; |
571 | | |
572 | 0 | int len = int(f.size()) - 1; |
573 | | // if the last character is / or \ ignore it |
574 | 0 | if (f[std::size_t(len)] == '/' || f[std::size_t(len)] == '\\') --len; |
575 | 0 | while (len >= 0) |
576 | 0 | { |
577 | 0 | if (f[std::size_t(len)] == '/' || f[std::size_t(len)] == '\\') |
578 | 0 | break; |
579 | 0 | --len; |
580 | 0 | } |
581 | |
|
582 | 0 | return len >= 0; |
583 | 0 | } |
584 | | |
585 | | std::string parent_path(std::string const& f) |
586 | 0 | { |
587 | 0 | if (f.empty()) return f; |
588 | | |
589 | | #ifdef TORRENT_WINDOWS |
590 | | if (f == "\\\\") return ""; |
591 | | #endif |
592 | 0 | if (f == "/") return ""; |
593 | | |
594 | 0 | int len = int(f.size()); |
595 | | // if the last character is / or \ ignore it |
596 | 0 | if (f[std::size_t(len - 1)] == '/' || f[std::size_t(len - 1)] == '\\') --len; |
597 | 0 | while (len > 0) |
598 | 0 | { |
599 | 0 | --len; |
600 | | #ifdef TORRENT_WINDOWS |
601 | | if (f[std::size_t(len)] == '/' || f[std::size_t(len)] == '\\') break; |
602 | | #else |
603 | 0 | if (f[std::size_t(len)] == '/') break; |
604 | 0 | #endif |
605 | 0 | } |
606 | |
|
607 | 0 | if (f[std::size_t(len)] == '/' || f[std::size_t(len)] == '\\') ++len; |
608 | 0 | return std::string(f.c_str(), std::size_t(len)); |
609 | 0 | } |
610 | | |
611 | | std::string filename(std::string const& f) |
612 | 0 | { |
613 | 0 | if (f.empty()) return ""; |
614 | 0 | char const* first = f.c_str(); |
615 | 0 | char const* sep = std::strrchr(first, '/'); |
616 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
617 | | char const* altsep = std::strrchr(first, '\\'); |
618 | | if (sep == nullptr || altsep > sep) sep = altsep; |
619 | | #endif |
620 | 0 | if (sep == nullptr) return f; |
621 | | |
622 | 0 | if (sep - first == int(f.size()) - 1) |
623 | 0 | { |
624 | | // if the last character is a / (or \) |
625 | | // ignore it |
626 | 0 | int len = 0; |
627 | 0 | while (sep > first) |
628 | 0 | { |
629 | 0 | --sep; |
630 | 0 | if (*sep == '/' |
631 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
632 | | || *sep == '\\' |
633 | | #endif |
634 | 0 | ) |
635 | 0 | return std::string(sep + 1, std::size_t(len)); |
636 | 0 | ++len; |
637 | 0 | } |
638 | 0 | return std::string(first, std::size_t(len)); |
639 | |
|
640 | 0 | } |
641 | 0 | return std::string(sep + 1); |
642 | 0 | } |
643 | | |
644 | | void append_path(std::string& branch, string_view leaf) |
645 | 0 | { |
646 | 0 | TORRENT_ASSERT(!is_complete(leaf)); |
647 | 0 | if (branch.empty() || branch == ".") |
648 | 0 | { |
649 | 0 | branch.assign(leaf.data(), leaf.size()); |
650 | 0 | return; |
651 | 0 | } |
652 | 0 | if (leaf.empty()) return; |
653 | | |
654 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
655 | | #define TORRENT_SEPARATOR_CHAR '\\' |
656 | | bool const need_sep = branch[branch.size()-1] != '\\' |
657 | | && branch[branch.size()-1] != '/'; |
658 | | #else |
659 | 0 | #define TORRENT_SEPARATOR_CHAR '/' |
660 | 0 | bool const need_sep = branch[branch.size()-1] != '/'; |
661 | 0 | #endif |
662 | |
|
663 | 0 | if (need_sep) branch += TORRENT_SEPARATOR_CHAR; |
664 | 0 | branch.append(leaf.data(), leaf.size()); |
665 | 0 | } |
666 | | |
667 | | std::string combine_path(string_view lhs, string_view rhs) |
668 | 0 | { |
669 | 0 | TORRENT_ASSERT(!is_complete(rhs)); |
670 | 0 | if (lhs.empty() || lhs == ".") return rhs.to_string(); |
671 | 0 | if (rhs.empty() || rhs == ".") return lhs.to_string(); |
672 | | |
673 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
674 | | #define TORRENT_SEPARATOR "\\" |
675 | | bool const need_sep = lhs[lhs.size() - 1] != '\\' && lhs[lhs.size() - 1] != '/'; |
676 | | #else |
677 | 0 | #define TORRENT_SEPARATOR "/" |
678 | 0 | bool const need_sep = lhs[lhs.size() - 1] != '/'; |
679 | 0 | #endif |
680 | 0 | std::string ret; |
681 | 0 | std::size_t target_size = lhs.size() + rhs.size() + 2; |
682 | 0 | ret.resize(target_size); |
683 | 0 | target_size = aux::numeric_cast<std::size_t>(std::snprintf(&ret[0], target_size, "%*s%s%*s" |
684 | 0 | , int(lhs.size()), lhs.data() |
685 | 0 | , (need_sep ? TORRENT_SEPARATOR : "") |
686 | 0 | , int(rhs.size()), rhs.data())); |
687 | 0 | ret.resize(target_size); |
688 | 0 | return ret; |
689 | 0 | } |
690 | | |
691 | | std::string lexically_relative(string_view base, string_view target) |
692 | 0 | { |
693 | | // first, strip trailing directory separators |
694 | 0 | if (!base.empty() && base.back() == TORRENT_SEPARATOR_CHAR) |
695 | 0 | base.remove_suffix(1); |
696 | 0 | if (!target.empty() && target.back() == TORRENT_SEPARATOR_CHAR) |
697 | 0 | target.remove_suffix(1); |
698 | | |
699 | | // strip common path elements |
700 | 0 | for (;;) |
701 | 0 | { |
702 | 0 | if (base.empty()) break; |
703 | 0 | string_view prev_base = base; |
704 | 0 | string_view prev_target = target; |
705 | |
|
706 | 0 | string_view base_element; |
707 | 0 | string_view target_element; |
708 | 0 | std::tie(base_element, base) = split_string(base, TORRENT_SEPARATOR_CHAR); |
709 | 0 | std::tie(target_element, target) = split_string(target, TORRENT_SEPARATOR_CHAR); |
710 | 0 | if (base_element == target_element) continue; |
711 | | |
712 | 0 | base = prev_base; |
713 | 0 | target = prev_target; |
714 | 0 | break; |
715 | 0 | } |
716 | | |
717 | | // count number of path elements left in base, and prepend that number of |
718 | | // "../" to target |
719 | | |
720 | | // base always points to a directory. There's an implied directory |
721 | | // separator at the end of it |
722 | 0 | int const num_steps = static_cast<int>(std::count( |
723 | 0 | base.begin(), base.end(), TORRENT_SEPARATOR_CHAR)) + (base.empty() ? 0 : 1); |
724 | 0 | std::string ret; |
725 | 0 | for (int i = 0; i < num_steps; ++i) |
726 | 0 | ret += ".." TORRENT_SEPARATOR; |
727 | |
|
728 | 0 | ret += target.to_string(); |
729 | 0 | return ret; |
730 | 0 | } |
731 | | |
732 | | std::string current_working_directory() |
733 | 0 | { |
734 | | #if defined TORRENT_WINDOWS |
735 | | #define GetCurrentDir_ ::_wgetcwd |
736 | | #else |
737 | 0 | #define GetCurrentDir_ ::getcwd |
738 | 0 | #endif |
739 | 0 | auto cwd = GetCurrentDir_(nullptr, 0); |
740 | 0 | if (cwd == nullptr) |
741 | 0 | aux::throw_ex<system_error>(error_code(errno, generic_category())); |
742 | 0 | auto holder = make_free_holder(cwd); |
743 | 0 | return convert_from_native_path(cwd); |
744 | 0 | #undef GetCurrentDir_ |
745 | 0 | } |
746 | | |
747 | | #if TORRENT_USE_UNC_PATHS |
748 | | std::string canonicalize_path(string_view f) |
749 | | { |
750 | | std::string ret; |
751 | | ret.resize(f.size()); |
752 | | char* write_cur = &ret[0]; |
753 | | char* last_write_sep = write_cur; |
754 | | |
755 | | char const* read_cur = f.data(); |
756 | | char const* last_read_sep = read_cur; |
757 | | |
758 | | // the last_*_sep pointers point to one past |
759 | | // the last path separator encountered and is |
760 | | // initialized to the first character in the path |
761 | | for (int i = 0; i < int(f.size()); ++i) |
762 | | { |
763 | | if (*read_cur != '\\') |
764 | | { |
765 | | *write_cur++ = *read_cur++; |
766 | | continue; |
767 | | } |
768 | | int element_len = int(read_cur - last_read_sep); |
769 | | if (element_len == 1 && std::memcmp(last_read_sep, ".", 1) == 0) |
770 | | { |
771 | | --write_cur; |
772 | | ++read_cur; |
773 | | last_read_sep = read_cur; |
774 | | continue; |
775 | | } |
776 | | if (element_len == 2 && std::memcmp(last_read_sep, "..", 2) == 0) |
777 | | { |
778 | | // find the previous path separator |
779 | | if (last_write_sep > &ret[0]) |
780 | | { |
781 | | --last_write_sep; |
782 | | while (last_write_sep > &ret[0] |
783 | | && last_write_sep[-1] != '\\') |
784 | | --last_write_sep; |
785 | | } |
786 | | write_cur = last_write_sep; |
787 | | // find the previous path separator |
788 | | if (last_write_sep > &ret[0]) |
789 | | { |
790 | | --last_write_sep; |
791 | | while (last_write_sep > &ret[0] |
792 | | && last_write_sep[-1] != '\\') |
793 | | --last_write_sep; |
794 | | } |
795 | | ++read_cur; |
796 | | last_read_sep = read_cur; |
797 | | continue; |
798 | | } |
799 | | *write_cur++ = *read_cur++; |
800 | | last_write_sep = write_cur; |
801 | | last_read_sep = read_cur; |
802 | | } |
803 | | // terminate destination string |
804 | | *write_cur = 0; |
805 | | ret.resize(write_cur - &ret[0]); |
806 | | return ret; |
807 | | } |
808 | | #endif |
809 | | bool exists(std::string const& f, error_code& ec) |
810 | 0 | { |
811 | 0 | file_status s; |
812 | 0 | stat_file(f, &s, ec); |
813 | 0 | if (ec) |
814 | 0 | { |
815 | | // if the filename is too long, the file also cannot exist |
816 | 0 | if (ec == boost::system::errc::no_such_file_or_directory |
817 | 0 | || ec == boost::system::errc::filename_too_long) |
818 | 0 | ec.clear(); |
819 | 0 | return false; |
820 | 0 | } |
821 | 0 | return true; |
822 | 0 | } |
823 | | |
824 | | void remove(std::string const& inf, error_code& ec) |
825 | 0 | { |
826 | 0 | ec.clear(); |
827 | 0 | native_path_string f = convert_to_native_path_string(inf); |
828 | |
|
829 | | #ifdef TORRENT_WINDOWS |
830 | | // windows does not allow trailing / or \ in |
831 | | // the path when removing files |
832 | | while (!f.empty() && ( |
833 | | f.back() == '/' || |
834 | | f.back() == '\\' |
835 | | )) f.pop_back(); |
836 | | |
837 | | if (DeleteFileW(f.c_str()) == 0) |
838 | | { |
839 | | if (GetLastError() == ERROR_ACCESS_DENIED) |
840 | | { |
841 | | if (RemoveDirectoryW(f.c_str()) != 0) |
842 | | return; |
843 | | } |
844 | | ec.assign(GetLastError(), system_category()); |
845 | | return; |
846 | | } |
847 | | #else // TORRENT_WINDOWS |
848 | 0 | if (::remove(f.c_str()) < 0) |
849 | 0 | { |
850 | 0 | ec.assign(errno, system_category()); |
851 | 0 | return; |
852 | 0 | } |
853 | 0 | #endif // TORRENT_WINDOWS |
854 | 0 | } |
855 | | |
856 | | void remove_all(std::string const& f, error_code& ec) |
857 | 0 | { |
858 | 0 | ec.clear(); |
859 | |
|
860 | 0 | file_status s; |
861 | 0 | stat_file(f, &s, ec); |
862 | 0 | if (ec) return; |
863 | | |
864 | 0 | if (s.mode & file_status::directory) |
865 | 0 | { |
866 | 0 | for (aux::directory i(f, ec); !i.done(); i.next(ec)) |
867 | 0 | { |
868 | 0 | if (ec) return; |
869 | 0 | std::string p = i.file(); |
870 | 0 | if (p == "." || p == "..") continue; |
871 | 0 | remove_all(combine_path(f, p), ec); |
872 | 0 | if (ec) return; |
873 | 0 | } |
874 | 0 | } |
875 | 0 | remove(f, ec); |
876 | 0 | } |
877 | | |
878 | | std::pair<string_view, string_view> rsplit_path(string_view p) |
879 | 0 | { |
880 | 0 | if (p.empty()) return {{}, {}}; |
881 | 0 | if (p.back() == TORRENT_SEPARATOR_CHAR) p.remove_suffix(1); |
882 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
883 | | else if (p.back() == '/') p.remove_suffix(1); |
884 | | #endif |
885 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
886 | | auto const sep = p.find_last_of("/\\"); |
887 | | #else |
888 | 0 | auto const sep = p.find_last_of(TORRENT_SEPARATOR_CHAR); |
889 | 0 | #endif |
890 | 0 | if (sep == string_view::npos) return {{}, p}; |
891 | 0 | return { p.substr(0, sep), p.substr(sep + 1) }; |
892 | 0 | } |
893 | | |
894 | | std::pair<string_view, string_view> lsplit_path(string_view p) |
895 | 0 | { |
896 | 0 | if (p.empty()) return {{}, {}}; |
897 | | // for absolute paths, skip the initial "/" |
898 | 0 | if (p.front() == TORRENT_SEPARATOR_CHAR) p.remove_prefix(1); |
899 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
900 | | else if (p.front() == '/') p.remove_prefix(1); |
901 | | #endif |
902 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
903 | | auto const sep = p.find_first_of("/\\"); |
904 | | #else |
905 | 0 | auto const sep = p.find_first_of(TORRENT_SEPARATOR_CHAR); |
906 | 0 | #endif |
907 | 0 | if (sep == string_view::npos) return {p, {}}; |
908 | 0 | return { p.substr(0, sep), p.substr(sep + 1) }; |
909 | 0 | } |
910 | | |
911 | | std::pair<string_view, string_view> lsplit_path(string_view p, std::size_t pos) |
912 | 0 | { |
913 | 0 | if (p.empty()) return {{}, {}}; |
914 | | // for absolute paths, skip the initial "/" |
915 | 0 | if (p.front() == TORRENT_SEPARATOR_CHAR |
916 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
917 | | || p.front() == '/' |
918 | | #endif |
919 | 0 | ) |
920 | 0 | { p.remove_prefix(1); if (pos > 0) --pos; } |
921 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
922 | | auto const sep = find_first_of(p, "/\\", std::string::size_type(pos)); |
923 | | #else |
924 | 0 | auto const sep = find_first_of(p, TORRENT_SEPARATOR_CHAR, std::string::size_type(pos)); |
925 | 0 | #endif |
926 | 0 | if (sep == string_view::npos) return {p, {}}; |
927 | 0 | return { p.substr(0, sep), p.substr(sep + 1) }; |
928 | 0 | } |
929 | | |
930 | | std::string complete(string_view f) |
931 | 0 | { |
932 | 0 | if (is_complete(f)) return f.to_string(); |
933 | 0 | auto parts = lsplit_path(f); |
934 | 0 | if (parts.first == ".") f = parts.second; |
935 | 0 | return combine_path(current_working_directory(), f); |
936 | 0 | } |
937 | | |
938 | | bool is_complete(string_view f) |
939 | 0 | { |
940 | 0 | if (f.empty()) return false; |
941 | | #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) |
942 | | int i = 0; |
943 | | // match the xx:\ or xx:/ form |
944 | | while (f[i] && is_alpha(f[i])) ++i; |
945 | | if (i < int(f.size()-1) && f[i] == ':' && (f[i+1] == '\\' || f[i+1] == '/')) |
946 | | return true; |
947 | | |
948 | | // match the \\ form |
949 | | if (int(f.size()) >= 2 && f[0] == '\\' && f[1] == '\\') |
950 | | return true; |
951 | | return false; |
952 | | #else |
953 | 0 | if (f[0] == '/') return true; |
954 | 0 | return false; |
955 | 0 | #endif |
956 | 0 | } |
957 | | } |