/src/libtorrent/src/storage_utils.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | |
3 | | Copyright (c) 2023, Vladimir Golovnev |
4 | | Copyright (c) 2016-2022, Arvid Norberg |
5 | | Copyright (c) 2017-2018, Alden Torres |
6 | | Copyright (c) 2017-2018, Steven Siloti |
7 | | Copyright (c) 2018, Pavel Pimenov |
8 | | Copyright (c) 2019, Mike Tzou |
9 | | All rights reserved. |
10 | | |
11 | | Redistribution and use in source and binary forms, with or without |
12 | | modification, are permitted provided that the following conditions |
13 | | are met: |
14 | | |
15 | | * Redistributions of source code must retain the above copyright |
16 | | notice, this list of conditions and the following disclaimer. |
17 | | * Redistributions in binary form must reproduce the above copyright |
18 | | notice, this list of conditions and the following disclaimer in |
19 | | the documentation and/or other materials provided with the distribution. |
20 | | * Neither the name of the author nor the names of its |
21 | | contributors may be used to endorse or promote products derived |
22 | | from this software without specific prior written permission. |
23 | | |
24 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
25 | | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
26 | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
27 | | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
28 | | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
29 | | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
30 | | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
31 | | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
32 | | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
33 | | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
34 | | POSSIBILITY OF SUCH DAMAGE. |
35 | | |
36 | | */ |
37 | | |
38 | | #include "libtorrent/aux_/storage_utils.hpp" |
39 | | #include "libtorrent/file_storage.hpp" |
40 | | #include "libtorrent/aux_/alloca.hpp" |
41 | | #include "libtorrent/aux_/path.hpp" |
42 | | #include "libtorrent/session.hpp" // for session::delete_files |
43 | | #include "libtorrent/stat_cache.hpp" |
44 | | #include "libtorrent/add_torrent_params.hpp" |
45 | | #include "libtorrent/torrent_status.hpp" |
46 | | #include "libtorrent/error_code.hpp" |
47 | | #include "libtorrent/string_view.hpp" |
48 | | #include "libtorrent/hasher.hpp" |
49 | | |
50 | | #if TORRENT_HAS_SYMLINK |
51 | | #include <unistd.h> // for symlink() |
52 | | #endif |
53 | | |
54 | | #include <set> |
55 | | |
56 | | namespace libtorrent { namespace aux { |
57 | | |
58 | | // much of what needs to be done when reading and writing is buffer |
59 | | // management and piece to file mapping. Most of that is the same for reading |
60 | | // and writing. This function is a template, and the fileop decides what to |
61 | | // do with the file and the buffers. |
62 | | int readwrite(file_storage const& files, span<char> buf |
63 | | , piece_index_t const piece, const int offset |
64 | | , storage_error& ec, fileop op) |
65 | 0 | { |
66 | 0 | TORRENT_ASSERT(piece >= piece_index_t(0)); |
67 | 0 | TORRENT_ASSERT(piece < files.end_piece()); |
68 | 0 | TORRENT_ASSERT(offset >= 0); |
69 | 0 | TORRENT_ASSERT(buf.size() > 0); |
70 | |
|
71 | 0 | TORRENT_ASSERT(buf.size() > 0); |
72 | 0 | TORRENT_ASSERT(static_cast<int>(piece) * static_cast<std::int64_t>(files.piece_length()) |
73 | 0 | + offset + buf.size() <= files.total_size()); |
74 | | |
75 | | // find the file iterator and file offset |
76 | 0 | std::int64_t const torrent_offset = static_cast<int>(piece) * std::int64_t(files.piece_length()) + offset; |
77 | 0 | file_index_t file_index = files.file_index_at_offset(torrent_offset); |
78 | 0 | TORRENT_ASSERT(torrent_offset >= files.file_offset(file_index)); |
79 | 0 | TORRENT_ASSERT(torrent_offset < files.file_offset(file_index) + files.file_size(file_index)); |
80 | 0 | std::int64_t file_offset = torrent_offset - files.file_offset(file_index); |
81 | |
|
82 | 0 | int ret = 0; |
83 | |
|
84 | 0 | while (buf.size() > 0) |
85 | 0 | { |
86 | | // the number of bytes left to read in the current file (specified by |
87 | | // file_index). This is the minimum of (file_size - file_offset) and |
88 | | // buf.size(). |
89 | 0 | int file_bytes_left = int(buf.size()); |
90 | 0 | if (file_offset + file_bytes_left > files.file_size(file_index)) |
91 | 0 | file_bytes_left = std::max(static_cast<int>(files.file_size(file_index) - file_offset), 0); |
92 | | |
93 | | // there are no bytes left in this file, move to the next one |
94 | | // this loop skips over empty files |
95 | 0 | if (file_bytes_left == 0) |
96 | 0 | { |
97 | 0 | do |
98 | 0 | { |
99 | 0 | ++file_index; |
100 | 0 | file_offset = 0; |
101 | 0 | TORRENT_ASSERT(file_index < files.end_file()); |
102 | | |
103 | | // this should not happen. buf.size() should be clamped by the total |
104 | | // size of the torrent, so we should never run off the end of it |
105 | 0 | if (file_index >= files.end_file()) return ret; |
106 | | |
107 | | // skip empty files |
108 | 0 | } |
109 | 0 | while (files.file_size(file_index) == 0); |
110 | | |
111 | 0 | file_bytes_left = int(buf.size()); |
112 | 0 | if (file_offset + file_bytes_left > files.file_size(file_index)) |
113 | 0 | file_bytes_left = std::max(static_cast<int>(files.file_size(file_index) - file_offset), 0); |
114 | 0 | TORRENT_ASSERT(file_bytes_left > 0); |
115 | 0 | } |
116 | | |
117 | 0 | int const bytes_transferred = op(file_index, file_offset |
118 | 0 | , buf.first(file_bytes_left), ec); |
119 | 0 | TORRENT_ASSERT(bytes_transferred <= file_bytes_left); |
120 | 0 | if (ec) |
121 | 0 | { |
122 | 0 | ec.file(file_index); |
123 | 0 | return ret; |
124 | 0 | } |
125 | | |
126 | 0 | buf = buf.subspan(bytes_transferred); |
127 | 0 | file_offset += bytes_transferred; |
128 | 0 | ret += bytes_transferred; |
129 | | |
130 | | // if the file operation returned 0, we've hit end-of-file. We're done |
131 | 0 | if (bytes_transferred == 0 && file_bytes_left > 0 ) |
132 | 0 | { |
133 | | // fill in this information in case the caller wants to treat |
134 | | // a short-read as an error |
135 | 0 | ec.operation = operation_t::file_read; |
136 | 0 | ec.ec = boost::asio::error::eof; |
137 | 0 | ec.file(file_index); |
138 | 0 | } |
139 | 0 | } |
140 | 0 | return ret; |
141 | 0 | } |
142 | | |
143 | | std::pair<status_t, std::string> move_storage(file_storage const& f |
144 | | , std::string save_path |
145 | | , std::string const& destination_save_path |
146 | | , std::function<void(std::string const&, error_code&)> const& move_partfile |
147 | | , move_flags_t const flags, storage_error& ec) |
148 | 0 | { |
149 | 0 | status_t ret = status_t::no_error; |
150 | 0 | std::string const new_save_path = complete(destination_save_path); |
151 | | |
152 | | // check to see if any of the files exist |
153 | 0 | if (flags == move_flags_t::fail_if_exist) |
154 | 0 | { |
155 | 0 | file_status s; |
156 | 0 | error_code err; |
157 | 0 | stat_file(new_save_path, &s, err); |
158 | 0 | if (err != boost::system::errc::no_such_file_or_directory) |
159 | 0 | { |
160 | | // the directory exists, check all the files |
161 | 0 | for (auto const i : f.file_range()) |
162 | 0 | { |
163 | | // files moved out to absolute paths are ignored |
164 | 0 | if (f.file_absolute_path(i)) continue; |
165 | | |
166 | 0 | stat_file(f.file_path(i, new_save_path), &s, err); |
167 | 0 | if (err != boost::system::errc::no_such_file_or_directory) |
168 | 0 | { |
169 | 0 | ec.ec = err; |
170 | 0 | ec.file(i); |
171 | 0 | ec.operation = operation_t::file_stat; |
172 | 0 | return { status_t::file_exist, save_path }; |
173 | 0 | } |
174 | 0 | } |
175 | 0 | } |
176 | 0 | } |
177 | | |
178 | 0 | { |
179 | 0 | file_status s; |
180 | 0 | error_code err; |
181 | 0 | stat_file(new_save_path, &s, err); |
182 | 0 | if (err == boost::system::errc::no_such_file_or_directory) |
183 | 0 | { |
184 | 0 | err.clear(); |
185 | 0 | create_directories(new_save_path, err); |
186 | 0 | if (err) |
187 | 0 | { |
188 | 0 | ec.ec = err; |
189 | 0 | ec.file(file_index_t(-1)); |
190 | 0 | ec.operation = operation_t::mkdir; |
191 | 0 | return { status_t::fatal_disk_error, save_path }; |
192 | 0 | } |
193 | 0 | } |
194 | 0 | else if (err) |
195 | 0 | { |
196 | 0 | ec.ec = err; |
197 | 0 | ec.file(file_index_t(-1)); |
198 | 0 | ec.operation = operation_t::file_stat; |
199 | 0 | return { status_t::fatal_disk_error, save_path }; |
200 | 0 | } |
201 | 0 | } |
202 | | |
203 | 0 | if (flags == move_flags_t::reset_save_path) |
204 | 0 | return { status_t::need_full_check, new_save_path }; |
205 | | |
206 | 0 | if (flags == move_flags_t::reset_save_path_unchecked) |
207 | 0 | return { status_t::no_error, new_save_path }; |
208 | | |
209 | | // indices of all files we ended up copying. These need to be deleted |
210 | | // later |
211 | 0 | aux::vector<bool, file_index_t> copied_files(std::size_t(f.num_files()), false); |
212 | | |
213 | | // track how far we got in case of an error |
214 | 0 | file_index_t file_index{}; |
215 | 0 | for (auto const i : f.file_range()) |
216 | 0 | { |
217 | | // files moved out to absolute paths are not moved |
218 | 0 | if (f.file_absolute_path(i)) continue; |
219 | | |
220 | 0 | std::string const old_path = combine_path(save_path, f.file_path(i)); |
221 | 0 | std::string const new_path = combine_path(new_save_path, f.file_path(i)); |
222 | |
|
223 | 0 | error_code ignore; |
224 | 0 | if (flags == move_flags_t::dont_replace && exists(new_path, ignore)) |
225 | 0 | { |
226 | 0 | if (ret == status_t::no_error) ret = status_t::need_full_check; |
227 | 0 | continue; |
228 | 0 | } |
229 | | |
230 | | // TODO: ideally, if we end up copying files because of a move across |
231 | | // volumes, the source should not be deleted until they've all been |
232 | | // copied. That would let us rollback with higher confidence. |
233 | 0 | move_file(old_path, new_path, ec); |
234 | | |
235 | | // if the source file doesn't exist. That's not a problem |
236 | | // we just ignore that file |
237 | 0 | if (ec.ec == boost::system::errc::no_such_file_or_directory) |
238 | 0 | ec.ec.clear(); |
239 | 0 | else if (ec |
240 | 0 | && ec.ec != boost::system::errc::invalid_argument |
241 | 0 | && ec.ec != boost::system::errc::permission_denied) |
242 | 0 | { |
243 | | // moving the file failed |
244 | | // on OSX, the error when trying to rename a file across different |
245 | | // volumes is EXDEV, which will make it fall back to copying. |
246 | 0 | ec.ec.clear(); |
247 | 0 | copy_file(old_path, new_path, ec); |
248 | 0 | if (!ec) copied_files[i] = true; |
249 | 0 | } |
250 | |
|
251 | 0 | if (ec) |
252 | 0 | { |
253 | 0 | ec.file(i); |
254 | 0 | file_index = i; |
255 | 0 | break; |
256 | 0 | } |
257 | 0 | } |
258 | |
|
259 | 0 | if (!ec && move_partfile) |
260 | 0 | { |
261 | 0 | error_code e; |
262 | 0 | move_partfile(new_save_path, e); |
263 | 0 | if (e) |
264 | 0 | { |
265 | 0 | ec.ec = e; |
266 | 0 | ec.file(torrent_status::error_file_partfile); |
267 | 0 | ec.operation = operation_t::partfile_move; |
268 | 0 | } |
269 | 0 | } |
270 | |
|
271 | 0 | if (ec) |
272 | 0 | { |
273 | | // rollback |
274 | 0 | while (--file_index >= file_index_t(0)) |
275 | 0 | { |
276 | | // files moved out to absolute paths are not moved |
277 | 0 | if (f.file_absolute_path(file_index)) continue; |
278 | | |
279 | | // if we ended up copying the file, don't do anything during |
280 | | // roll-back |
281 | 0 | if (copied_files[file_index]) continue; |
282 | | |
283 | 0 | std::string const old_path = combine_path(save_path, f.file_path(file_index)); |
284 | 0 | std::string const new_path = combine_path(new_save_path, f.file_path(file_index)); |
285 | | |
286 | | // ignore errors when rolling back |
287 | 0 | storage_error ignore; |
288 | 0 | move_file(new_path, old_path, ignore); |
289 | 0 | } |
290 | |
|
291 | 0 | return { status_t::fatal_disk_error, save_path }; |
292 | 0 | } |
293 | | |
294 | | // TODO: 2 technically, this is where the transaction of moving the files |
295 | | // is completed. This is where the new save_path should be committed. If |
296 | | // there is an error in the code below, that should not prevent the new |
297 | | // save path to be set. Maybe it would make sense to make the save_path |
298 | | // an in-out parameter |
299 | | |
300 | 0 | std::set<std::string> subdirs; |
301 | 0 | for (auto const i : f.file_range()) |
302 | 0 | { |
303 | | // files moved out to absolute paths are not moved |
304 | 0 | if (f.file_absolute_path(i)) continue; |
305 | | |
306 | 0 | if (has_parent_path(f.file_path(i))) |
307 | 0 | subdirs.insert(parent_path(f.file_path(i))); |
308 | | |
309 | | // if we ended up renaming the file instead of moving it, there's no |
310 | | // need to delete the source. |
311 | 0 | if (copied_files[i] == false) continue; |
312 | | |
313 | 0 | std::string const old_path = combine_path(save_path, f.file_path(i)); |
314 | | |
315 | | // we may still have some files in old save_path |
316 | | // eg. if (flags == dont_replace && exists(new_path)) |
317 | | // ignore errors when removing |
318 | 0 | error_code ignore; |
319 | 0 | remove(old_path, ignore); |
320 | 0 | } |
321 | |
|
322 | 0 | for (std::string const& s : subdirs) |
323 | 0 | { |
324 | 0 | error_code err; |
325 | 0 | std::string subdir = combine_path(save_path, s); |
326 | |
|
327 | 0 | while (!path_equal(subdir, save_path) && !err) |
328 | 0 | { |
329 | 0 | remove(subdir, err); |
330 | 0 | subdir = parent_path(subdir); |
331 | 0 | } |
332 | 0 | } |
333 | |
|
334 | 0 | return { ret, new_save_path }; |
335 | 0 | } |
336 | | |
337 | | namespace { |
338 | | |
339 | | void delete_one_file(std::string const& p, error_code& ec) |
340 | 0 | { |
341 | 0 | remove(p, ec); |
342 | |
|
343 | 0 | if (ec == boost::system::errc::no_such_file_or_directory) |
344 | 0 | ec.clear(); |
345 | 0 | } |
346 | | |
347 | | } |
348 | | |
349 | | void delete_files(file_storage const& fs, std::string const& save_path |
350 | | , std::string const& part_file_name, remove_flags_t const options, storage_error& ec) |
351 | 0 | { |
352 | 0 | if (options & session::delete_files) |
353 | 0 | { |
354 | | // delete the files from disk |
355 | 0 | std::set<std::string> directories; |
356 | 0 | using iter_t = std::set<std::string>::iterator; |
357 | 0 | for (auto const i : fs.file_range()) |
358 | 0 | { |
359 | 0 | std::string const fp = fs.file_path(i); |
360 | 0 | bool const complete = fs.file_absolute_path(i); |
361 | 0 | std::string const p = complete ? fp : combine_path(save_path, fp); |
362 | 0 | if (!complete) |
363 | 0 | { |
364 | 0 | std::string bp = parent_path(fp); |
365 | 0 | std::pair<iter_t, bool> ret; |
366 | 0 | ret.second = true; |
367 | 0 | while (ret.second && !bp.empty()) |
368 | 0 | { |
369 | 0 | ret = directories.insert(combine_path(save_path, bp)); |
370 | 0 | bp = parent_path(bp); |
371 | 0 | } |
372 | 0 | } |
373 | 0 | delete_one_file(p, ec.ec); |
374 | 0 | if (ec) { ec.file(i); ec.operation = operation_t::file_remove; } |
375 | 0 | } |
376 | | |
377 | | // remove the directories. Reverse order to delete |
378 | | // subdirectories first |
379 | |
|
380 | 0 | for (auto i = directories.rbegin() |
381 | 0 | , end(directories.rend()); i != end; ++i) |
382 | 0 | { |
383 | 0 | error_code error; |
384 | 0 | delete_one_file(*i, error); |
385 | 0 | if (error && !ec) |
386 | 0 | { |
387 | 0 | ec.file(file_index_t(-1)); |
388 | 0 | ec.ec = error; |
389 | 0 | ec.operation = operation_t::file_remove; |
390 | 0 | } |
391 | 0 | } |
392 | 0 | } |
393 | | |
394 | | // when we're deleting "files", we also delete the part file |
395 | 0 | if ((options & session::delete_partfile) |
396 | 0 | || (options & session::delete_files)) |
397 | 0 | { |
398 | 0 | error_code error; |
399 | 0 | remove(combine_path(save_path, part_file_name), error); |
400 | 0 | if (error && error != boost::system::errc::no_such_file_or_directory) |
401 | 0 | { |
402 | 0 | ec.file(file_index_t(-1)); |
403 | 0 | ec.ec = error; |
404 | 0 | ec.operation = operation_t::file_remove; |
405 | 0 | } |
406 | 0 | } |
407 | 0 | } |
408 | | |
409 | | namespace { |
410 | | |
411 | | std::int64_t get_filesize(stat_cache& stat, file_index_t const file_index |
412 | | , file_storage const& fs, std::string const& save_path, storage_error& ec) |
413 | 0 | { |
414 | 0 | error_code error; |
415 | 0 | std::int64_t const size = stat.get_filesize(file_index, fs, save_path, error); |
416 | |
|
417 | 0 | if (size >= 0) return size; |
418 | 0 | if (error != boost::system::errc::no_such_file_or_directory) |
419 | 0 | { |
420 | 0 | ec.ec = error; |
421 | 0 | ec.file(file_index); |
422 | 0 | ec.operation = operation_t::file_stat; |
423 | 0 | } |
424 | 0 | else |
425 | 0 | { |
426 | 0 | ec.ec = errors::mismatching_file_size; |
427 | 0 | ec.file(file_index); |
428 | 0 | ec.operation = operation_t::file_stat; |
429 | 0 | } |
430 | 0 | return -1; |
431 | 0 | } |
432 | | |
433 | | } |
434 | | |
435 | | bool verify_resume_data(add_torrent_params const& rd |
436 | | , aux::vector<std::string, file_index_t> const& links |
437 | | , file_storage const& fs |
438 | | , aux::vector<download_priority_t, file_index_t> const& file_priority |
439 | | , stat_cache& stat |
440 | | , std::string const& save_path |
441 | | , storage_error& ec) |
442 | 1 | { |
443 | | #ifdef TORRENT_DISABLE_MUTABLE_TORRENTS |
444 | | TORRENT_UNUSED(links); |
445 | | #else |
446 | 1 | bool added_files = false; |
447 | 1 | if (!links.empty()) |
448 | 0 | { |
449 | 0 | TORRENT_ASSERT(int(links.size()) == fs.num_files()); |
450 | | // if this is a mutable torrent, and we need to pick up some files |
451 | | // from other torrents, do that now. Note that there is an inherent |
452 | | // race condition here. We checked if the files existed on a different |
453 | | // thread a while ago. These files may no longer exist or may have been |
454 | | // moved. If so, we just fail. The user is responsible to not touch |
455 | | // other torrents until a new mutable torrent has been completely |
456 | | // added. |
457 | 0 | for (auto const idx : fs.file_range()) |
458 | 0 | { |
459 | 0 | std::string const& s = links[idx]; |
460 | 0 | if (s.empty()) continue; |
461 | | |
462 | 0 | error_code err; |
463 | 0 | std::string file_path = fs.file_path(idx, save_path); |
464 | 0 | hard_link(s, file_path, err); |
465 | 0 | if (err == boost::system::errc::no_such_file_or_directory) |
466 | 0 | { |
467 | | // we create directories lazily, so it's possible it hasn't |
468 | | // been created yet. Create the directories now and try |
469 | | // again |
470 | 0 | create_directories(parent_path(file_path), err); |
471 | |
|
472 | 0 | if (err) |
473 | 0 | { |
474 | 0 | ec.file(idx); |
475 | 0 | ec.operation = operation_t::mkdir; |
476 | 0 | return false; |
477 | 0 | } |
478 | | |
479 | 0 | hard_link(s, file_path, err); |
480 | 0 | } |
481 | | |
482 | | // if the file already exists, that's not an error |
483 | 0 | if (err == boost::system::errc::file_exists) |
484 | 0 | continue; |
485 | | |
486 | | // TODO: 2 is this risky? The upper layer will assume we have the |
487 | | // whole file. Perhaps we should verify that at least the size |
488 | | // of the file is correct |
489 | 0 | if (err) |
490 | 0 | { |
491 | 0 | ec.ec = err; |
492 | 0 | ec.file(idx); |
493 | 0 | ec.operation = operation_t::file_hard_link; |
494 | 0 | return false; |
495 | 0 | } |
496 | 0 | added_files = true; |
497 | 0 | stat.set_dirty(idx); |
498 | 0 | } |
499 | 0 | } |
500 | 1 | #endif // TORRENT_DISABLE_MUTABLE_TORRENTS |
501 | | |
502 | 1 | bool const seed = (rd.have_pieces.size() >= fs.num_pieces() |
503 | 1 | && rd.have_pieces.all_set()) |
504 | 1 | || (rd.flags & torrent_flags::seed_mode); |
505 | | |
506 | 1 | if (seed) |
507 | 0 | { |
508 | 0 | for (file_index_t const file_index : fs.file_range()) |
509 | 0 | { |
510 | 0 | if (fs.pad_file_at(file_index)) continue; |
511 | | |
512 | | // files with priority zero may not have been saved to disk at their |
513 | | // expected location, but is likely to be in a partfile. Just exempt it |
514 | | // from checking |
515 | 0 | if (file_index < file_priority.end_index() |
516 | 0 | && file_priority[file_index] == dont_download |
517 | 0 | && !(rd.flags & torrent_flags::seed_mode)) |
518 | 0 | continue; |
519 | | |
520 | 0 | std::int64_t const size = get_filesize(stat, file_index, fs |
521 | 0 | , save_path, ec); |
522 | 0 | if (size < 0) return false; |
523 | | |
524 | 0 | if (size < fs.file_size(file_index)) |
525 | 0 | { |
526 | 0 | ec.ec = errors::mismatching_file_size; |
527 | 0 | ec.file(file_index); |
528 | 0 | ec.operation = operation_t::check_resume; |
529 | 0 | return false; |
530 | 0 | } |
531 | 0 | } |
532 | 0 | return true; |
533 | 0 | } |
534 | | |
535 | 1 | #ifndef TORRENT_DISABLE_MUTABLE_TORRENTS |
536 | | // always trigger a full recheck when we pull in files from other |
537 | | // torrents, via hard links |
538 | | // TODO: it would seem reasonable to, instead, set the have_pieces bits |
539 | | // for the pieces representing these files, and resume with the normal |
540 | | // logic |
541 | 1 | if (added_files) return false; |
542 | 1 | #endif |
543 | | |
544 | | // parse have bitmask. Verify that the files we expect to have |
545 | | // actually do exist |
546 | 1 | piece_index_t const end_piece = std::min(rd.have_pieces.end_index(), fs.end_piece()); |
547 | 1 | for (piece_index_t i(0); i < end_piece; ++i) |
548 | 0 | { |
549 | 0 | if (rd.have_pieces.get_bit(i) == false) continue; |
550 | | |
551 | 0 | std::vector<file_slice> f = fs.map_block(i, 0, 1); |
552 | 0 | TORRENT_ASSERT(!f.empty()); |
553 | |
|
554 | 0 | file_index_t const file_index = f[0].file_index; |
555 | | |
556 | | // files with priority zero may not have been saved to disk at their |
557 | | // expected location, but is likely to be in a partfile. Just exempt it |
558 | | // from checking |
559 | 0 | if (file_index < file_priority.end_index() |
560 | 0 | && file_priority[file_index] == dont_download) |
561 | 0 | continue; |
562 | | |
563 | 0 | if (fs.pad_file_at(file_index)) continue; |
564 | | |
565 | 0 | if (get_filesize(stat, file_index, fs, save_path, ec) < 0) |
566 | 0 | return false; |
567 | | |
568 | | // OK, this file existed, good. Now, skip all remaining pieces in |
569 | | // this file. We're just sanity-checking whether the files exist |
570 | | // or not. |
571 | 0 | peer_request const pr = fs.map_file(file_index |
572 | 0 | , fs.file_size(file_index) + 1, 0); |
573 | 0 | i = std::max(next(i), pr.piece); |
574 | 0 | } |
575 | 1 | return true; |
576 | 1 | } |
577 | | |
578 | | bool has_any_file( |
579 | | file_storage const& fs |
580 | | , std::string const& save_path |
581 | | , stat_cache& cache |
582 | | , storage_error& ec) |
583 | 1 | { |
584 | 1 | for (auto const i : fs.file_range()) |
585 | 1 | { |
586 | 1 | std::int64_t const sz = cache.get_filesize( |
587 | 1 | i, fs, save_path, ec.ec); |
588 | | |
589 | 1 | if (sz < 0) |
590 | 1 | { |
591 | 1 | if (ec && ec.ec != boost::system::errc::no_such_file_or_directory) |
592 | 0 | { |
593 | 0 | ec.file(i); |
594 | 0 | ec.operation = operation_t::file_stat; |
595 | 0 | cache.clear(); |
596 | 0 | return false; |
597 | 0 | } |
598 | | // some files not existing is expected and not an error |
599 | 1 | ec.ec.clear(); |
600 | 1 | } |
601 | | |
602 | 1 | if (sz > 0) return true; |
603 | 1 | } |
604 | 1 | return false; |
605 | 1 | } |
606 | | |
607 | | int read_zeroes(span<char> buf) |
608 | 0 | { |
609 | 0 | std::fill(buf.begin(), buf.end(), '\0'); |
610 | 0 | return int(buf.size()); |
611 | 0 | } |
612 | | |
613 | | int hash_zeroes(hasher& ph, std::int64_t const size) |
614 | 0 | { |
615 | 0 | std::array<char, 64> zeroes; |
616 | 0 | zeroes.fill(0); |
617 | 0 | for (std::ptrdiff_t left = std::ptrdiff_t(size); left > 0; left -= zeroes.size()) |
618 | 0 | ph.update({zeroes.data(), std::min(std::ptrdiff_t(zeroes.size()), left)}); |
619 | 0 | return int(size); |
620 | 0 | } |
621 | | |
622 | | void initialize_storage(file_storage const& fs |
623 | | , std::string const& save_path |
624 | | , stat_cache& sc |
625 | | , aux::vector<download_priority_t, file_index_t> const& file_priority |
626 | | , std::function<void(file_index_t, storage_error&)> create_file |
627 | | , std::function<void(std::string const&, std::string const&, storage_error&)> create_link |
628 | | , std::function<void(file_index_t, std::int64_t)> oversized_file |
629 | | , storage_error& ec) |
630 | 1 | { |
631 | | // create zero-sized files |
632 | 1 | for (auto const file_index : fs.file_range()) |
633 | 1 | { |
634 | | // ignore files that have priority 0 |
635 | 1 | if (file_priority.end_index() > file_index |
636 | 1 | && file_priority[file_index] == dont_download) |
637 | 0 | { |
638 | 0 | continue; |
639 | 0 | } |
640 | | |
641 | | // ignore pad files |
642 | 1 | if (fs.pad_file_at(file_index)) continue; |
643 | | |
644 | | // this is just to see if the file exists |
645 | 1 | error_code err; |
646 | 1 | auto const sz = sc.get_filesize(file_index, fs, save_path, err); |
647 | | |
648 | 1 | if (err && err != boost::system::errc::no_such_file_or_directory) |
649 | 0 | { |
650 | 0 | ec.file(file_index); |
651 | 0 | ec.operation = operation_t::file_stat; |
652 | 0 | ec.ec = err; |
653 | 0 | break; |
654 | 0 | } |
655 | | |
656 | 1 | auto const fs_file_size = fs.file_size(file_index); |
657 | 1 | if (!err && sz > fs_file_size) |
658 | 0 | { |
659 | | // this file is oversized, alert the client |
660 | 0 | oversized_file(file_index, sz); |
661 | 0 | } |
662 | | |
663 | | // if the file is empty and doesn't already exist, create it |
664 | | // deliberately don't truncate files that already exist |
665 | | // if a file is supposed to have size 0, but already exists, we will |
666 | | // never truncate it to 0. |
667 | 1 | if (fs_file_size == 0) |
668 | 0 | { |
669 | | // create symlinks |
670 | 0 | if (fs.file_flags(file_index) & file_storage::flag_symlink) |
671 | 0 | { |
672 | 0 | #if TORRENT_HAS_SYMLINK |
673 | 0 | std::string const path = fs.file_path(file_index, save_path); |
674 | | // we make the symlink target relative to the link itself |
675 | 0 | std::string const target = lexically_relative( |
676 | 0 | parent_path(fs.file_path(file_index)), fs.symlink(file_index)); |
677 | 0 | create_link(target, path, ec); |
678 | 0 | if (ec.ec) |
679 | 0 | { |
680 | 0 | ec.file(file_index); |
681 | 0 | return; |
682 | 0 | } |
683 | | #else |
684 | | TORRENT_UNUSED(create_link); |
685 | | #endif |
686 | 0 | } |
687 | 0 | else if (err == boost::system::errc::no_such_file_or_directory) |
688 | 0 | { |
689 | | // just creating the file is enough to make it zero-sized. If |
690 | | // there's a race here and some other process truncates the file, |
691 | | // it's not a problem, we won't access empty files ever again |
692 | 0 | ec.ec.clear(); |
693 | 0 | create_file(file_index, ec); |
694 | 0 | if (ec) return; |
695 | 0 | } |
696 | 0 | } |
697 | 1 | ec.ec.clear(); |
698 | 1 | } |
699 | 1 | } |
700 | | |
701 | | void create_symlink(std::string const& target, std::string const& link, storage_error& ec) |
702 | 0 | { |
703 | 0 | #if TORRENT_HAS_SYMLINK |
704 | 0 | create_directories(parent_path(link), ec.ec); |
705 | 0 | if (ec) |
706 | 0 | { |
707 | 0 | ec.ec = error_code(errno, generic_category()); |
708 | 0 | ec.operation = operation_t::mkdir; |
709 | 0 | return; |
710 | 0 | } |
711 | 0 | if (::symlink(target.c_str(), link.c_str()) != 0) |
712 | 0 | { |
713 | 0 | int const error = errno; |
714 | 0 | if (error == EEXIST) |
715 | 0 | { |
716 | | // if the file exist, it may be a symlink already. if so, |
717 | | // just verify the link target is what it's supposed to be |
718 | | // note that readlink() does not null terminate the buffer |
719 | 0 | char buffer[512]; |
720 | 0 | auto const ret = ::readlink(link.c_str(), buffer, sizeof(buffer)); |
721 | 0 | if (ret <= 0 || target != string_view(buffer, std::size_t(ret))) |
722 | 0 | { |
723 | 0 | ec.ec = error_code(error, generic_category()); |
724 | 0 | ec.operation = operation_t::symlink; |
725 | 0 | return; |
726 | 0 | } |
727 | 0 | } |
728 | 0 | else |
729 | 0 | { |
730 | 0 | ec.ec = error_code(error, generic_category()); |
731 | 0 | ec.operation = operation_t::symlink; |
732 | 0 | return; |
733 | 0 | } |
734 | 0 | } |
735 | | #else |
736 | | TORRENT_UNUSED(target); |
737 | | TORRENT_UNUSED(link); |
738 | | TORRENT_UNUSED(ec); |
739 | | #endif |
740 | 0 | } |
741 | | |
742 | | void move_file(std::string const& inf, std::string const& newf, storage_error& se) |
743 | 0 | { |
744 | 0 | se.ec.clear(); |
745 | |
|
746 | 0 | file_status s; |
747 | 0 | stat_file(inf, &s, se.ec); |
748 | 0 | if (se) |
749 | 0 | { |
750 | 0 | se.operation = operation_t::file_stat; |
751 | 0 | return; |
752 | 0 | } |
753 | | |
754 | 0 | if (has_parent_path(newf)) |
755 | 0 | { |
756 | 0 | create_directories(parent_path(newf), se.ec); |
757 | 0 | if (se) |
758 | 0 | { |
759 | 0 | se.operation = operation_t::mkdir; |
760 | 0 | return; |
761 | 0 | } |
762 | 0 | } |
763 | | |
764 | 0 | rename(inf, newf, se.ec); |
765 | 0 | if (se) |
766 | 0 | se.operation = operation_t::file_rename; |
767 | 0 | } |
768 | | |
769 | | }} |