Coverage Report

Created: 2025-08-29 06:41

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