Coverage Report

Created: 2025-06-12 06:24

/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
}