/src/libtorrent/src/part_file.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | |
3 | | Copyright (c) 2014-2020, Arvid Norberg |
4 | | Copyright (c) 2016-2018, Alden Torres |
5 | | Copyright (c) 2016-2017, Steven Siloti |
6 | | Copyright (c) 2018, d-komarov |
7 | | All rights reserved. |
8 | | |
9 | | Redistribution and use in source and binary forms, with or without |
10 | | modification, are permitted provided that the following conditions |
11 | | are met: |
12 | | |
13 | | * Redistributions of source code must retain the above copyright |
14 | | notice, this list of conditions and the following disclaimer. |
15 | | * Redistributions in binary form must reproduce the above copyright |
16 | | notice, this list of conditions and the following disclaimer in |
17 | | the documentation and/or other materials provided with the distribution. |
18 | | * Neither the name of the author nor the names of its |
19 | | contributors may be used to endorse or promote products derived |
20 | | from this software without specific prior written permission. |
21 | | |
22 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
23 | | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
24 | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
25 | | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
26 | | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
27 | | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
28 | | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
29 | | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
30 | | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
31 | | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
32 | | POSSIBILITY OF SUCH DAMAGE. |
33 | | |
34 | | */ |
35 | | |
36 | | |
37 | | /* |
38 | | |
39 | | The part_file file format is an array of piece sized blocks with |
40 | | a simple header. For a given number of pieces, the header has a |
41 | | fixed size. The header size is rounded up to an even multiple of |
42 | | 1024, in an attempt at improving disk I/O performance by aligning |
43 | | reads and writes to clusters on the drive. This is the file header |
44 | | format. All values are stored big endian on disk. |
45 | | |
46 | | |
47 | | // the size of the torrent (and can be used to calculate the size |
48 | | // of the file header) |
49 | | uint32_t num_pieces; |
50 | | |
51 | | // the number of bytes in each piece. This determines the size of |
52 | | // each slot in the part file. This is typically an even power of 2, |
53 | | // but it is not guaranteed to be. |
54 | | uint32_t piece_size; |
55 | | |
56 | | // this is an array specifying which slots a particular piece resides in, |
57 | | // A value of 0xffffffff (-1 if you will) means the piece is not in the part_file |
58 | | // Any other value means the piece resides in the slot with that index |
59 | | uint32_t piece[num_pieces]; |
60 | | |
61 | | // unused, n is defined as the number to align the size of this |
62 | | // header to an even multiple of 1024 bytes. |
63 | | uint8_t padding[n]; |
64 | | |
65 | | */ |
66 | | |
67 | | #include "libtorrent/part_file.hpp" |
68 | | #include "libtorrent/io.hpp" |
69 | | #include "libtorrent/assert.hpp" |
70 | | #include "libtorrent/aux_/vector.hpp" |
71 | | #include "libtorrent/aux_/path.hpp" |
72 | | #include "libtorrent/aux_/storage_utils.hpp" // for iovec_t |
73 | | |
74 | | #include <functional> // for std::function |
75 | | #include <cstdint> |
76 | | |
77 | | namespace { |
78 | | |
79 | | // round up to even kilobyte |
80 | | int round_up(int n) |
81 | 0 | { return (n + 1023) & ~0x3ff; } |
82 | | } |
83 | | |
84 | | namespace libtorrent { |
85 | | |
86 | | part_file::part_file(std::string path, std::string name |
87 | | , int const num_pieces, int const piece_size) |
88 | 0 | : m_path(std::move(path)) |
89 | 0 | , m_name(std::move(name)) |
90 | 0 | , m_max_pieces(num_pieces) |
91 | 0 | , m_piece_size(piece_size) |
92 | 0 | , m_header_size(round_up((2 + num_pieces) * 4)) |
93 | 0 | { |
94 | 0 | TORRENT_ASSERT(num_pieces > 0); |
95 | 0 | TORRENT_ASSERT(m_piece_size > 0); |
96 | |
|
97 | 0 | error_code ec; |
98 | 0 | auto f = open_file(aux::open_mode::read_only, ec); |
99 | 0 | if (ec) return; |
100 | | |
101 | | // parse header |
102 | 0 | std::vector<char> header(static_cast<std::size_t>(m_header_size)); |
103 | 0 | int const n = int(aux::pread_all(f.fd(), header, 0, ec)); |
104 | 0 | if (ec) return; |
105 | | |
106 | | // we don't have a full header. consider the file empty |
107 | 0 | if (n < m_header_size) return; |
108 | 0 | using namespace libtorrent::aux; |
109 | |
|
110 | 0 | char* ptr = header.data(); |
111 | | // we have a header. Parse it |
112 | 0 | int const num_pieces_ = int(read_uint32(ptr)); |
113 | 0 | int const piece_size_ = int(read_uint32(ptr)); |
114 | | |
115 | | // if there is a mismatch in number of pieces or piece size |
116 | | // consider the file empty and overwrite anything in there |
117 | 0 | if (num_pieces != num_pieces_ || m_piece_size != piece_size_) return; |
118 | | |
119 | | // this is used to determine which slots are free, and how many |
120 | | // slots are allocated |
121 | 0 | aux::vector<bool, slot_index_t> free_slots; |
122 | 0 | free_slots.resize(num_pieces, true); |
123 | |
|
124 | 0 | for (piece_index_t i = piece_index_t(0); i < piece_index_t(num_pieces); ++i) |
125 | 0 | { |
126 | 0 | slot_index_t const slot(read_int32(ptr)); |
127 | 0 | if (static_cast<int>(slot) < 0) continue; |
128 | | |
129 | | // invalid part-file |
130 | 0 | TORRENT_ASSERT(slot < slot_index_t(num_pieces)); |
131 | 0 | if (slot >= slot_index_t(num_pieces)) continue; |
132 | | |
133 | 0 | if (slot >= m_num_allocated) |
134 | 0 | m_num_allocated = next(slot); |
135 | |
|
136 | 0 | free_slots[slot] = false; |
137 | 0 | m_piece_map[i] = slot; |
138 | 0 | } |
139 | | |
140 | | // now, populate the free_list with the "holes" |
141 | 0 | for (slot_index_t i(0); i < m_num_allocated; ++i) |
142 | 0 | { |
143 | 0 | if (free_slots[i]) m_free_slots.push_back(i); |
144 | 0 | } |
145 | 0 | } |
146 | | |
147 | | part_file::~part_file() |
148 | 0 | { |
149 | 0 | error_code ec; |
150 | 0 | flush_metadata_impl(ec); |
151 | 0 | } |
152 | | |
153 | | slot_index_t part_file::allocate_slot(piece_index_t const piece) |
154 | 0 | { |
155 | | // the mutex is assumed to be held here, since this is a private function |
156 | |
|
157 | 0 | TORRENT_ASSERT(m_piece_map.find(piece) == m_piece_map.end()); |
158 | 0 | slot_index_t slot(-1); |
159 | 0 | if (!m_free_slots.empty()) |
160 | 0 | { |
161 | 0 | slot = m_free_slots.front(); |
162 | 0 | m_free_slots.erase(m_free_slots.begin()); |
163 | 0 | } |
164 | 0 | else |
165 | 0 | { |
166 | 0 | slot = m_num_allocated; |
167 | 0 | ++m_num_allocated; |
168 | 0 | } |
169 | |
|
170 | 0 | m_piece_map[piece] = slot; |
171 | 0 | m_dirty_metadata = true; |
172 | 0 | return slot; |
173 | 0 | } |
174 | | |
175 | | int part_file::write(span<char> buf, piece_index_t const piece |
176 | | , int const offset, error_code& ec) |
177 | 0 | { |
178 | 0 | TORRENT_ASSERT(offset >= 0); |
179 | 0 | TORRENT_ASSERT(int(buf.size()) + offset <= m_piece_size); |
180 | 0 | std::unique_lock<std::mutex> l(m_mutex); |
181 | |
|
182 | 0 | auto f = open_file(aux::open_mode::write | aux::open_mode::hidden, ec); |
183 | 0 | if (ec) return -1; |
184 | | |
185 | 0 | auto const i = m_piece_map.find(piece); |
186 | 0 | slot_index_t const slot = (i == m_piece_map.end()) |
187 | 0 | ? allocate_slot(piece) : i->second; |
188 | |
|
189 | 0 | l.unlock(); |
190 | |
|
191 | 0 | return int(aux::pwrite_all(f.fd(), buf, slot_offset(slot) + offset, ec)); |
192 | 0 | } |
193 | | |
194 | | int part_file::read(span<char> buf |
195 | | , piece_index_t const piece |
196 | | , int const offset, error_code& ec) |
197 | 0 | { |
198 | 0 | TORRENT_ASSERT(offset >= 0); |
199 | 0 | TORRENT_ASSERT(int(buf.size()) + offset <= m_piece_size); |
200 | 0 | std::unique_lock<std::mutex> l(m_mutex); |
201 | |
|
202 | 0 | auto const i = m_piece_map.find(piece); |
203 | 0 | if (i == m_piece_map.end()) |
204 | 0 | { |
205 | 0 | ec = make_error_code(boost::system::errc::no_such_file_or_directory); |
206 | 0 | return -1; |
207 | 0 | } |
208 | | |
209 | 0 | slot_index_t const slot = i->second; |
210 | 0 | l.unlock(); |
211 | |
|
212 | 0 | auto f = open_file(aux::open_mode::read_only | aux::open_mode::hidden, ec); |
213 | 0 | if (ec) return -1; |
214 | | |
215 | 0 | return int(aux::pread_all(f.fd(), buf, slot_offset(slot) + offset, ec)); |
216 | 0 | } |
217 | | |
218 | | int part_file::hash(hasher& ph |
219 | | , std::ptrdiff_t const len |
220 | | , piece_index_t const piece |
221 | | , int const offset, error_code& ec) |
222 | 0 | { |
223 | 0 | return do_hash(ph, len, piece, offset, ec); |
224 | 0 | } |
225 | | |
226 | | int part_file::hash2(hasher256& ph |
227 | | , std::ptrdiff_t const len |
228 | | , piece_index_t const piece |
229 | | , int const offset, error_code& ec) |
230 | 0 | { |
231 | 0 | return do_hash(ph, len, piece, offset, ec); |
232 | 0 | } |
233 | | |
234 | | template <typename Hasher> |
235 | | int part_file::do_hash(Hasher& ph |
236 | | , std::ptrdiff_t const len |
237 | | , piece_index_t const piece |
238 | | , int const offset, error_code& ec) |
239 | 0 | { |
240 | 0 | TORRENT_ASSERT(offset >= 0); |
241 | 0 | TORRENT_ASSERT(len >= 0); |
242 | 0 | TORRENT_ASSERT(int(len) + offset <= m_piece_size); |
243 | 0 | std::unique_lock<std::mutex> l(m_mutex); |
244 | |
|
245 | 0 | auto const i = m_piece_map.find(piece); |
246 | 0 | if (i == m_piece_map.end()) |
247 | 0 | { |
248 | 0 | ec = error_code(boost::system::errc::no_such_file_or_directory |
249 | 0 | , boost::system::generic_category()); |
250 | 0 | return -1; |
251 | 0 | } |
252 | | |
253 | 0 | slot_index_t const slot = i->second; |
254 | 0 | auto f = open_file(aux::open_mode::read_only | aux::open_mode::hidden, ec); |
255 | 0 | if (ec) return -1; |
256 | | |
257 | 0 | l.unlock(); |
258 | |
|
259 | 0 | std::vector<char> buffer(static_cast<std::size_t>(len)); |
260 | 0 | std::int64_t const slot_offset = std::int64_t(m_header_size) + std::int64_t(static_cast<int>(slot)) * m_piece_size; |
261 | 0 | int const ret = int(aux::pread_all(f.fd(), buffer, slot_offset + offset, ec)); |
262 | 0 | ph.update(buffer); |
263 | 0 | return ret; |
264 | 0 | } Unexecuted instantiation: int libtorrent::part_file::do_hash<libtorrent::lcrypto::hasher>(libtorrent::lcrypto::hasher&, long, libtorrent::aux::strong_typedef<int, libtorrent::aux::piece_index_tag, void>, int, boost::system::error_code&) Unexecuted instantiation: int libtorrent::part_file::do_hash<libtorrent::lcrypto::hasher256>(libtorrent::lcrypto::hasher256&, long, libtorrent::aux::strong_typedef<int, libtorrent::aux::piece_index_tag, void>, int, boost::system::error_code&) |
265 | | |
266 | 0 | aux::file_handle part_file::open_file(aux::open_mode_t const mode, error_code& ec) try |
267 | 0 | { |
268 | 0 | std::string const fn = combine_path(m_path, m_name); |
269 | 0 | try { |
270 | 0 | return aux::file_handle(fn, 0, mode); |
271 | 0 | } |
272 | 0 | catch (storage_error const& e) |
273 | 0 | { |
274 | 0 | if ((mode & aux::open_mode::write) |
275 | 0 | && e.ec == boost::system::errc::no_such_file_or_directory) |
276 | 0 | { |
277 | | // this means the directory the file is in doesn't exist. |
278 | | // so create it |
279 | 0 | ec.clear(); |
280 | 0 | create_directories(m_path, ec); |
281 | 0 | if (ec) return {}; |
282 | 0 | return aux::file_handle(fn, 0, mode); |
283 | 0 | } |
284 | 0 | return {}; |
285 | 0 | } |
286 | 0 | } |
287 | 0 | catch (storage_error const& e) |
288 | 0 | { |
289 | 0 | ec = e.ec; |
290 | 0 | return {}; |
291 | 0 | } |
292 | | |
293 | | void part_file::free_piece(piece_index_t const piece) |
294 | 0 | { |
295 | 0 | std::lock_guard<std::mutex> l(m_mutex); |
296 | |
|
297 | 0 | auto const i = m_piece_map.find(piece); |
298 | 0 | if (i == m_piece_map.end()) return; |
299 | | |
300 | | // TODO: what do we do if someone is currently reading from the disk |
301 | | // from this piece? does it matter? Since we won't actively erase the |
302 | | // data from disk, but it may be overwritten soon, it's probably not that |
303 | | // big of a deal |
304 | | |
305 | 0 | m_free_slots.push_back(i->second); |
306 | 0 | m_piece_map.erase(i); |
307 | 0 | m_dirty_metadata = true; |
308 | 0 | } |
309 | | |
310 | | void part_file::move_partfile(std::string const& path, error_code& ec) |
311 | 0 | { |
312 | 0 | std::lock_guard<std::mutex> l(m_mutex); |
313 | |
|
314 | 0 | flush_metadata_impl(ec); |
315 | 0 | if (ec) return; |
316 | | |
317 | 0 | if (!m_piece_map.empty()) |
318 | 0 | { |
319 | 0 | std::string old_path = combine_path(m_path, m_name); |
320 | 0 | std::string new_path = combine_path(path, m_name); |
321 | |
|
322 | 0 | rename(old_path, new_path, ec); |
323 | 0 | if (ec == boost::system::errc::no_such_file_or_directory) |
324 | 0 | ec.clear(); |
325 | |
|
326 | 0 | if (ec) |
327 | 0 | { |
328 | 0 | storage_error se; |
329 | 0 | aux::copy_file(old_path, new_path, se); |
330 | 0 | ec = se.ec; |
331 | 0 | if (ec) return; |
332 | 0 | remove(old_path, ec); |
333 | 0 | } |
334 | 0 | } |
335 | 0 | m_path = path; |
336 | 0 | } |
337 | | |
338 | | void part_file::export_file(std::function<void(std::int64_t, span<char>)> f |
339 | | , std::int64_t const offset, std::int64_t size, error_code& ec) |
340 | 0 | { |
341 | 0 | std::unique_lock<std::mutex> l(m_mutex); |
342 | | |
343 | | // there's nothing stored in the part_file. Nothing to do |
344 | 0 | if (m_piece_map.empty()) return; |
345 | | |
346 | 0 | piece_index_t piece(int(offset / m_piece_size)); |
347 | 0 | piece_index_t const end = piece_index_t(int(((offset + size) + m_piece_size - 1) / m_piece_size)); |
348 | |
|
349 | 0 | std::unique_ptr<char[]> buf; |
350 | |
|
351 | 0 | std::int64_t piece_offset = offset - std::int64_t(static_cast<int>(piece)) |
352 | 0 | * m_piece_size; |
353 | 0 | std::int64_t file_offset = 0; |
354 | 0 | auto file = open_file(aux::open_mode::read_only, ec); |
355 | 0 | if (ec) return; |
356 | | |
357 | 0 | for (; piece < end; ++piece) |
358 | 0 | { |
359 | 0 | auto const i = m_piece_map.find(piece); |
360 | 0 | int const block_to_copy = int(std::min(m_piece_size - piece_offset, size)); |
361 | 0 | if (i != m_piece_map.end()) |
362 | 0 | { |
363 | 0 | slot_index_t const slot = i->second; |
364 | |
|
365 | 0 | if (!buf) buf.reset(new char[std::size_t(m_piece_size)]); |
366 | | |
367 | | // don't hold the lock during disk I/O |
368 | 0 | l.unlock(); |
369 | |
|
370 | 0 | span<char> v = {buf.get(), block_to_copy}; |
371 | 0 | auto bytes_read = aux::pread_all(file.fd(), v, slot_offset(slot) + piece_offset, ec); |
372 | 0 | v = v.first(static_cast<std::ptrdiff_t>(bytes_read)); |
373 | 0 | if (ec || v.empty()) return; |
374 | | |
375 | 0 | f(file_offset, {buf.get(), block_to_copy}); |
376 | | |
377 | | // we're done with the disk I/O, grab the lock again to update |
378 | | // the slot map |
379 | 0 | l.lock(); |
380 | |
|
381 | 0 | if (block_to_copy == m_piece_size) |
382 | 0 | { |
383 | | // since we released the lock, it's technically possible that |
384 | | // another thread removed this slot map entry, and invalidated |
385 | | // our iterator. Now that we hold the lock again, perform |
386 | | // another lookup to be sure. |
387 | 0 | auto const j = m_piece_map.find(piece); |
388 | 0 | if (j != m_piece_map.end()) |
389 | 0 | { |
390 | | // if the slot moved, that's really suspicious |
391 | 0 | TORRENT_ASSERT(j->second == slot); |
392 | 0 | m_free_slots.push_back(j->second); |
393 | 0 | m_piece_map.erase(j); |
394 | 0 | m_dirty_metadata = true; |
395 | 0 | } |
396 | 0 | } |
397 | 0 | } |
398 | 0 | file_offset += block_to_copy; |
399 | 0 | piece_offset = 0; |
400 | 0 | size -= block_to_copy; |
401 | 0 | } |
402 | 0 | } |
403 | | |
404 | | void part_file::flush_metadata(error_code& ec) |
405 | 0 | { |
406 | 0 | std::lock_guard<std::mutex> l(m_mutex); |
407 | |
|
408 | 0 | flush_metadata_impl(ec); |
409 | 0 | } |
410 | | |
411 | | // TODO: instead of rebuilding the whole file header |
412 | | // and flushing it, update the slot entries as we go |
413 | | void part_file::flush_metadata_impl(error_code& ec) |
414 | 0 | { |
415 | | // do we need to flush the metadata? |
416 | 0 | if (m_dirty_metadata == false) return; |
417 | | |
418 | 0 | if (m_piece_map.empty()) |
419 | 0 | { |
420 | | // if we don't have any pieces left in the |
421 | | // part file, remove it |
422 | 0 | std::string const p = combine_path(m_path, m_name); |
423 | 0 | remove(p, ec); |
424 | |
|
425 | 0 | if (ec == boost::system::errc::no_such_file_or_directory) |
426 | 0 | ec.clear(); |
427 | 0 | return; |
428 | 0 | } |
429 | | |
430 | 0 | auto f = open_file(aux::open_mode::write | aux::open_mode::hidden, ec); |
431 | 0 | if (ec) return; |
432 | | |
433 | 0 | std::vector<char> header(static_cast<std::size_t>(m_header_size)); |
434 | |
|
435 | 0 | using namespace libtorrent::aux; |
436 | |
|
437 | 0 | char* ptr = header.data(); |
438 | 0 | write_uint32(m_max_pieces, ptr); |
439 | 0 | write_uint32(m_piece_size, ptr); |
440 | |
|
441 | 0 | for (piece_index_t piece(0); piece < piece_index_t(m_max_pieces); ++piece) |
442 | 0 | { |
443 | 0 | auto const i = m_piece_map.find(piece); |
444 | 0 | slot_index_t const slot(i == m_piece_map.end() |
445 | 0 | ? slot_index_t(-1) : i->second); |
446 | 0 | write_int32(static_cast<int>(slot), ptr); |
447 | 0 | } |
448 | 0 | std::memset(ptr, 0, std::size_t(m_header_size - (ptr - header.data()))); |
449 | 0 | aux::pwrite_all(f.fd(), header, 0, ec); |
450 | 0 | if (ec) return; |
451 | 0 | m_dirty_metadata = false; |
452 | 0 | } |
453 | | } |