diff --git a/ChangeLog b/ChangeLog index 4825dd886..ae623cc4c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,5 @@ + * fix loading resume data when in seed mode * fix part-file creation race condition * fix issue with initializing settings on session construction * fix issue with receiving interested before metadata diff --git a/docs/hunspell/libtorrent.dic b/docs/hunspell/libtorrent.dic index f2c4007c8..1fa52c510 100644 --- a/docs/hunspell/libtorrent.dic +++ b/docs/hunspell/libtorrent.dic @@ -84,6 +84,8 @@ pread preadv pwrite pwritev +readv +writev ftruncate iovec uint8 diff --git a/include/libtorrent/storage.hpp b/include/libtorrent/storage.hpp index 8f08c6529..6ff23c368 100644 --- a/include/libtorrent/storage.hpp +++ b/include/libtorrent/storage.hpp @@ -209,10 +209,24 @@ namespace libtorrent storage_interface(): m_settings(0) {} - // This function is called when the storage is to be initialized. The - // default storage will create directories and empty files at this point. - // If ``allocate_files`` is true, it will also ``ftruncate`` all files to - // their target size. + // This function is called when the *storage* on disk is to be + // initialized. The default storage will create directories and empty + // files at this point. If ``allocate_files`` is true, it will also + // ``ftruncate`` all files to their target size. + // + // This function may be called multiple time on a single instance. When a + // torrent is force-rechecked, the storage is re-initialized to trigger + // the re-check from scratch. + // + // The function is not necessarily called before other member functions. + // For instance has_any_files() and verify_resume_data() are + // called early to determine whether we may have to check all files or + // not. If we're doing a full check of the files every piece will be + // hashed, causing readv() to be called as well. + // + // Any required internals that need initialization should be done in the + // constructor. This function is called before the torrent starts to + // download. // // If an error occurs, ``storage_error`` should be set to reflect it. virtual void initialize(storage_error& ec) = 0; diff --git a/src/torrent.cpp b/src/torrent.cpp index 78a64265f..82cef522c 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -1998,85 +1998,84 @@ namespace libtorrent if (m_seed_mode) { m_have_all = true; - m_ses.get_io_service().post(boost::bind(&torrent::files_checked, shared_from_this())); - m_resume_data.reset(); update_gauge(); update_state_list(); - return; + } + else + { + int num_pad_files = 0; + TORRENT_ASSERT(block_size() > 0); + file_storage const& fs = m_torrent_file->files(); + for (int i = 0; i < fs.num_files(); ++i) + { + if (!fs.pad_file_at(i) || fs.file_size(i) == 0) continue; + + if (fs.pad_file_at(i)) ++num_pad_files; + + m_padding += boost::uint32_t(fs.file_size(i)); + + // TODO: instead of creating the picker up front here, + // maybe this whole section should move to need_picker() + need_picker(); + + peer_request pr = m_torrent_file->map_file(i, 0, fs.file_size(i)); + int off = pr.start & (block_size()-1); + if (off != 0) { pr.length -= block_size() - off; pr.start += block_size() - off; } + TORRENT_ASSERT((pr.start & (block_size()-1)) == 0); + + int block = block_size(); + int blocks_per_piece = m_torrent_file->piece_length() / block; + piece_block pb(pr.piece, pr.start / block); + for (; pr.length >= block; pr.length -= block, ++pb.block_index) + { + if (int(pb.block_index) == blocks_per_piece) { pb.block_index = 0; ++pb.piece_index; } + m_picker->mark_as_finished(pb, 0); + } + // ugly edge case where padfiles are not used they way they're + // supposed to be. i.e. added back-to back or at the end + if (pb.block_index == blocks_per_piece) { pb.block_index = 0; ++pb.piece_index; } + if (pr.length > 0 && ((i+1 != fs.num_files() && fs.pad_file_at(i+1)) + || i + 1 == fs.num_files())) + { + m_picker->mark_as_finished(pb, 0); + } + } + + if (m_padding > 0) + { + // if we marked an entire piece as finished, we actually + // need to consider it finished + + std::vector dq + = m_picker->get_download_queue(); + + std::vector have_pieces; + + for (std::vector::const_iterator i + = dq.begin(); i != dq.end(); ++i) + { + int num_blocks = m_picker->blocks_in_piece(i->index); + if (i->finished < num_blocks) continue; + have_pieces.push_back(i->index); + } + + for (std::vector::iterator i = have_pieces.begin(); + i != have_pieces.end(); ++i) + { + picker().piece_passed(*i); + TORRENT_ASSERT(picker().have_piece(*i)); + we_have(*i); + } + } + + if (!need_loaded()) return; + + if (num_pad_files > 0) + m_picker->set_num_pad_files(num_pad_files); } set_state(torrent_status::checking_resume_data); - int num_pad_files = 0; - TORRENT_ASSERT(block_size() > 0); - file_storage const& fs = m_torrent_file->files(); - for (int i = 0; i < fs.num_files(); ++i) - { - if (!fs.pad_file_at(i) || fs.file_size(i) == 0) continue; - - if (fs.pad_file_at(i)) ++num_pad_files; - - m_padding += boost::uint32_t(fs.file_size(i)); - - // TODO: instead of creating the picker up front here, - // maybe this whole section should move to need_picker() - need_picker(); - - peer_request pr = m_torrent_file->map_file(i, 0, fs.file_size(i)); - int off = pr.start & (block_size()-1); - if (off != 0) { pr.length -= block_size() - off; pr.start += block_size() - off; } - TORRENT_ASSERT((pr.start & (block_size()-1)) == 0); - - int block = block_size(); - int blocks_per_piece = m_torrent_file->piece_length() / block; - piece_block pb(pr.piece, pr.start / block); - for (; pr.length >= block; pr.length -= block, ++pb.block_index) - { - if (int(pb.block_index) == blocks_per_piece) { pb.block_index = 0; ++pb.piece_index; } - m_picker->mark_as_finished(pb, 0); - } - // ugly edge case where padfiles are not used they way they're - // supposed to be. i.e. added back-to back or at the end - if (pb.block_index == blocks_per_piece) { pb.block_index = 0; ++pb.piece_index; } - if (pr.length > 0 && ((i+1 != fs.num_files() && fs.pad_file_at(i+1)) - || i + 1 == fs.num_files())) - { - m_picker->mark_as_finished(pb, 0); - } - } - - if (m_padding > 0) - { - // if we marked an entire piece as finished, we actually - // need to consider it finished - - std::vector dq - = m_picker->get_download_queue(); - - std::vector have_pieces; - - for (std::vector::const_iterator i - = dq.begin(); i != dq.end(); ++i) - { - int num_blocks = m_picker->blocks_in_piece(i->index); - if (i->finished < num_blocks) continue; - have_pieces.push_back(i->index); - } - - for (std::vector::iterator i = have_pieces.begin(); - i != have_pieces.end(); ++i) - { - picker().piece_passed(*i); - TORRENT_ASSERT(picker().have_piece(*i)); - we_have(*i); - } - } - - if (!need_loaded()) return; - - if (num_pad_files > 0) - m_picker->set_num_pad_files(num_pad_files); - std::vector links; #ifndef TORRENT_DISABLE_MUTABLE_TORRENTS if (!m_torrent_file->similar_torrents().empty() @@ -2350,7 +2349,7 @@ namespace libtorrent // want anything in this function to affect the state of // m_need_save_resume_data, so we save it in a local variable and reset // it at the end of the function. - bool need_save_resume_data = m_need_save_resume_data; + bool const need_save_resume_data = m_need_save_resume_data; dec_refcount("check_fastresume"); TORRENT_ASSERT(is_single_thread()); @@ -2497,7 +2496,14 @@ namespace libtorrent // that when the resume data check fails. For instance, if the resume data // is incorrect, but we don't have any files, we skip the check and initialize // the storage to not have anything. - if (j->ret == 0) + if (m_seed_mode) + { + m_have_all = true; + files_checked(); + update_gauge(); + update_state_list(); + } + else if (j->ret == 0) { // there are either no files for this torrent // or the resume_data was accepted diff --git a/test/test_fast_extension.cpp b/test/test_fast_extension.cpp index 61e8ceeb2..ce12643a9 100644 --- a/test/test_fast_extension.cpp +++ b/test/test_fast_extension.cpp @@ -441,10 +441,7 @@ boost::shared_ptr setup_peer(tcp::socket& s, sha1_hash& ih if (th) *th = ret; // wait for the torrent to be ready - if ((flags & add_torrent_params::flag_seed_mode) == 0) - { - wait_for_downloading(*ses, "ses"); - } + wait_for_downloading(*ses, "ses"); if (incoming) { diff --git a/test/test_priority.cpp b/test/test_priority.cpp index aab445029..06366a6ed 100644 --- a/test/test_priority.cpp +++ b/test/test_priority.cpp @@ -165,6 +165,7 @@ void test_transfer(settings_pack const& sett) } TEST_CHECK(st1.state == torrent_status::seeding + || st1.state == torrent_status::checking_resume_data || st1.state == torrent_status::checking_files); TEST_CHECK(st2.state == torrent_status::downloading || st2.state == torrent_status::checking_resume_data); diff --git a/test/test_remap_files.cpp b/test/test_remap_files.cpp index e7eed2238..e327c48f1 100644 --- a/test/test_remap_files.cpp +++ b/test/test_remap_files.cpp @@ -173,6 +173,7 @@ void test_remap_files_gather(storage_mode_t storage_mode = storage_mode_sparse) } TEST_CHECK(st1.state == torrent_status::seeding + || st1.state == torrent_status::checking_resume_data || st1.state == torrent_status::checking_files); TEST_CHECK(st2.state == torrent_status::downloading || st2.state == torrent_status::checking_resume_data); @@ -317,6 +318,7 @@ void test_remap_files_scatter(storage_mode_t storage_mode = storage_mode_sparse) } TEST_CHECK(st1.state == torrent_status::seeding + || st1.state == torrent_status::checking_resume_data || st1.state == torrent_status::checking_files); TEST_CHECK(st2.state == torrent_status::downloading || st2.state == torrent_status::checking_resume_data); diff --git a/test/test_remove_torrent.cpp b/test/test_remove_torrent.cpp index a68e3fd45..6228deaa7 100644 --- a/test/test_remove_torrent.cpp +++ b/test/test_remove_torrent.cpp @@ -118,12 +118,13 @@ void test_remove_torrent(int const remove_options if (st2.is_finished) break; TEST_CHECK(st1.state == torrent_status::seeding + || st1.state == torrent_status::checking_resume_data || st1.state == torrent_status::checking_files); TEST_CHECK(st2.state == torrent_status::downloading || st2.state == torrent_status::checking_resume_data); - // if nothing is being transferred after 2 seconds, we're failing the test - if (st1.upload_payload_rate == 0 && i > 20) + // if nothing is being transferred after 3 seconds, we're failing the test + if (st1.upload_payload_rate == 0 && i > 30) { TEST_ERROR("no transfer"); return; diff --git a/test/test_resume.cpp b/test/test_resume.cpp index d8e1acc7b..62841ed70 100644 --- a/test/test_resume.cpp +++ b/test/test_resume.cpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert_types.hpp" #include "libtorrent/entry.hpp" #include "libtorrent/bencode.hpp" +#include "libtorrent/peer_info.hpp" #include @@ -186,10 +187,15 @@ void default_tests(torrent_status const& s) TEST_CHECK(s.time_since_upload < 1351 + 10); TEST_CHECK(s.active_time < 1339 + 10); - TEST_EQUAL(s.finished_time, 1352); - TEST_EQUAL(s.seeding_time, 1340); - TEST_EQUAL(s.added_time, 1347); - TEST_EQUAL(s.completed_time, 1348); + TEST_CHECK(s.finished_time >= 1352); + TEST_CHECK(s.seeding_time >= 1340); + TEST_CHECK(s.added_time >= 1347); + TEST_CHECK(s.completed_time >= 1348); + + TEST_CHECK(s.finished_time < 1352 + 5); + TEST_CHECK(s.seeding_time < 1340 + 5); + TEST_CHECK(s.added_time < 1347 + 5); + TEST_CHECK(s.completed_time < 1348 + 5); } void test_file_sizes(bool allocate) @@ -494,6 +500,39 @@ TORRENT_TEST(seed_mode_preserve) test_seed_mode(false, false, false); } +TORRENT_TEST(seed_mode_load_peers) +{ + lt::session ses(settings()); + boost::shared_ptr ti = generate_torrent(); + add_torrent_params p; + p.ti = ti; + p.save_path = "."; + + entry rd; + + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + rd["info-hash"] = ti->info_hash().to_string(); + rd["blocks per piece"] = std::max(1, ti->piece_length() / 0x4000); + + rd["pieces"] = std::string(ti->num_pieces(), '\x01'); + rd["piece_priority"] = std::string(ti->num_pieces(), '\x01'); + rd["seed_mode"] = 1; + rd["peers"] = "\x01\x02\x03\x04\x30\x39"; + + bencode(back_inserter(p.resume_data), rd); + + torrent_handle h = ses.add_torrent(p); + + wait_for_alert(ses, torrent_checked_alert::alert_type, "seed_mode_load_peers"); + + std::vector peers; + h.get_full_peer_list(peers); + + TEST_EQUAL(peers.size(), 1); + TEST_CHECK(peers[0].ip == tcp::endpoint(address::from_string("1.2.3.4"), 12345)); +} + TORRENT_TEST(resume_save_load) { lt::session ses(settings());