forked from premiere/premiere-libtorrent
1456 lines
45 KiB
C++
1456 lines
45 KiB
C++
/*
|
|
|
|
Copyright (c) 2008, Arvid Norberg
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in
|
|
the documentation and/or other materials provided with the distribution.
|
|
* Neither the name of the author nor the names of its
|
|
contributors may be used to endorse or promote products derived
|
|
from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
#include "test.hpp"
|
|
#include "setup_transfer.hpp"
|
|
#include "test_utils.hpp"
|
|
#include "settings.hpp"
|
|
|
|
#include "libtorrent/storage.hpp"
|
|
#include "libtorrent/file_pool.hpp"
|
|
#include "libtorrent/hasher.hpp"
|
|
#include "libtorrent/session.hpp"
|
|
#include "libtorrent/alert_types.hpp"
|
|
#include "libtorrent/aux_/session_impl.hpp"
|
|
#include "libtorrent/create_torrent.hpp"
|
|
#include "libtorrent/torrent_info.hpp"
|
|
#include "libtorrent/read_resume_data.hpp"
|
|
|
|
#include <iostream>
|
|
#include <fstream>
|
|
|
|
using namespace std::placeholders;
|
|
using namespace libtorrent;
|
|
namespace lt = libtorrent;
|
|
|
|
const int piece_size = 16 * 1024 * 16;
|
|
const int half = piece_size / 2;
|
|
|
|
void signal_bool(bool* b, char const* string)
|
|
{
|
|
*b = true;
|
|
std::cerr << time_now_string() << " " << string << std::endl;
|
|
}
|
|
|
|
void on_read_piece(int ret, disk_io_job const& j, char const* data, int size)
|
|
{
|
|
std::cerr << time_now_string() << " on_read_piece piece: " << j.piece << std::endl;
|
|
TEST_EQUAL(ret, size);
|
|
if (ret > 0) TEST_CHECK(std::equal(j.buffer.disk_block, j.buffer.disk_block + ret, data));
|
|
}
|
|
|
|
void on_check_resume_data(status_t const status, storage_error const& error, bool* done)
|
|
{
|
|
std::cerr << time_now_string() << " on_check_resume_data ret: "
|
|
<< static_cast<int>(status);
|
|
switch (status)
|
|
{
|
|
case status_t::no_error:
|
|
std::cerr << time_now_string() << " success" << std::endl;
|
|
break;
|
|
case status_t::fatal_disk_error:
|
|
std::cerr << time_now_string() << " disk error: " << error.ec.message()
|
|
<< " file: " << error.file() << std::endl;
|
|
break;
|
|
case status_t::need_full_check:
|
|
std::cerr << time_now_string() << " need full check" << std::endl;
|
|
break;
|
|
case status_t::file_exist:
|
|
std::cerr << time_now_string() << " file exist" << std::endl;
|
|
break;
|
|
}
|
|
std::cerr << std::endl;
|
|
*done = true;
|
|
}
|
|
|
|
void print_error(char const* call, int ret, storage_error const& ec)
|
|
{
|
|
std::printf("%s: %s() returned: %d error: \"%s\" in file: %d operation: %d\n"
|
|
, time_now_string(), call, ret, ec.ec.message().c_str()
|
|
, static_cast<int>(ec.file()), ec.operation);
|
|
}
|
|
|
|
void run_until(io_service& ios, bool const& done)
|
|
{
|
|
while (!done)
|
|
{
|
|
ios.reset();
|
|
error_code ec;
|
|
ios.run_one(ec);
|
|
if (ec)
|
|
{
|
|
std::cerr << "run_one: " << ec.message().c_str() << std::endl;
|
|
return;
|
|
}
|
|
std::cerr << time_now_string() << " done: " << done << std::endl;
|
|
}
|
|
}
|
|
|
|
void nop() {}
|
|
|
|
std::shared_ptr<torrent_info> setup_torrent_info(file_storage& fs
|
|
, std::vector<char>& buf)
|
|
{
|
|
fs.add_file(combine_path("temp_storage", "test1.tmp"), 8);
|
|
fs.add_file(combine_path("temp_storage", combine_path("folder1", "test2.tmp")), 8);
|
|
fs.add_file(combine_path("temp_storage", combine_path("folder2", "test3.tmp")), 0);
|
|
fs.add_file(combine_path("temp_storage", combine_path("_folder3", "test4.tmp")), 0);
|
|
fs.add_file(combine_path("temp_storage", combine_path("_folder3", combine_path("subfolder", "test5.tmp"))), 8);
|
|
libtorrent::create_torrent t(fs, 4, -1, 0);
|
|
|
|
char buf_[4] = {0, 0, 0, 0};
|
|
sha1_hash h = hasher(buf_).final();
|
|
for (piece_index_t i(0); i < piece_index_t(6); ++i) t.set_hash(i, h);
|
|
|
|
bencode(std::back_inserter(buf), t.generate());
|
|
error_code ec;
|
|
|
|
auto info = std::make_shared<torrent_info>(&buf[0]
|
|
, int(buf.size()), std::ref(ec), 0);
|
|
|
|
if (ec)
|
|
{
|
|
std::printf("torrent_info constructor failed: %s\n"
|
|
, ec.message().c_str());
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
std::shared_ptr<default_storage> setup_torrent(file_storage& fs
|
|
, file_pool& fp
|
|
, std::vector<char>& buf
|
|
, std::string const& test_path
|
|
, aux::session_settings& set)
|
|
{
|
|
std::shared_ptr<torrent_info> info = setup_torrent_info(fs, buf);
|
|
|
|
storage_params p;
|
|
p.files = &fs;
|
|
p.pool = &fp;
|
|
p.path = test_path;
|
|
p.mode = storage_mode_allocate;
|
|
std::shared_ptr<default_storage> s(new default_storage(p));
|
|
s->m_settings = &set;
|
|
|
|
// allocate the files and create the directories
|
|
storage_error se;
|
|
s->initialize(se);
|
|
if (se)
|
|
{
|
|
TEST_ERROR(se.ec.message().c_str());
|
|
std::printf("default_storage::initialize %s: %d\n"
|
|
, se.ec.message().c_str(), static_cast<int>(se.file()));
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
std::vector<char> new_piece(int size)
|
|
{
|
|
std::vector<char> ret(size);
|
|
std::generate(ret.begin(), ret.end(), random_byte);
|
|
return ret;
|
|
}
|
|
|
|
void run_storage_tests(std::shared_ptr<torrent_info> info
|
|
, file_storage& fs
|
|
, std::string const& test_path
|
|
, libtorrent::storage_mode_t storage_mode
|
|
, bool unbuffered)
|
|
{
|
|
TORRENT_ASSERT(fs.num_files() > 0);
|
|
error_code ec;
|
|
create_directory(combine_path(test_path, "temp_storage"), ec);
|
|
if (ec) std::cerr << "create_directory '" << combine_path(test_path, "temp_storage")
|
|
<< "': " << ec.message() << std::endl;
|
|
remove_all(combine_path(test_path, "temp_storage2"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "temp_storage2")
|
|
<< "': " << ec.message() << std::endl;
|
|
remove_all(combine_path(test_path, "part0"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "part0")
|
|
<< "': " << ec.message() << std::endl;
|
|
|
|
int num_pieces = fs.num_pieces();
|
|
TEST_CHECK(info->num_pieces() == num_pieces);
|
|
|
|
std::vector<char> piece0 = new_piece(piece_size);
|
|
std::vector<char> piece1 = new_piece(piece_size);
|
|
std::vector<char> piece2 = new_piece(piece_size);
|
|
|
|
aux::session_settings set;
|
|
set.set_int(settings_pack::disk_io_write_mode
|
|
, unbuffered ? settings_pack::disable_os_cache
|
|
: settings_pack::enable_os_cache);
|
|
set.set_int(settings_pack::disk_io_read_mode
|
|
, unbuffered ? settings_pack::disable_os_cache
|
|
: settings_pack::enable_os_cache);
|
|
|
|
char* piece = static_cast<char*>(malloc(piece_size));
|
|
|
|
{ // avoid having two storages use the same files
|
|
file_pool fp;
|
|
boost::asio::io_service ios;
|
|
disk_buffer_pool dp(16 * 1024, ios, std::bind(&nop));
|
|
storage_params p;
|
|
p.path = test_path;
|
|
p.files = &fs;
|
|
p.pool = &fp;
|
|
p.mode = storage_mode;
|
|
std::unique_ptr<storage_interface> s(new default_storage(p));
|
|
s->m_settings = &set;
|
|
|
|
storage_error ec;
|
|
s->initialize(ec);
|
|
TEST_CHECK(!ec);
|
|
if (ec) print_error("initialize", 0, ec);
|
|
|
|
int ret = 0;
|
|
|
|
// write piece 1 (in slot 0)
|
|
file::iovec_t iov = { piece1.data(), half};
|
|
ret = s->writev(iov, piece_index_t(0), 0, 0, ec);
|
|
if (ret != half) print_error("writev", ret, ec);
|
|
|
|
iov.iov_base = piece1.data() + half;
|
|
iov.iov_len = half;
|
|
ret = s->writev(iov, piece_index_t(0), half, 0, ec);
|
|
if (ret != half) print_error("writev", ret, ec);
|
|
|
|
// test unaligned read (where the bytes are aligned)
|
|
iov.iov_base = piece + 3;
|
|
iov.iov_len = piece_size - 9;
|
|
ret = s->readv(iov, piece_index_t(0), 3, 0, ec);
|
|
if (ret != piece_size - 9) print_error("readv",ret, ec);
|
|
TEST_CHECK(std::equal(piece+3, piece + piece_size-9, piece1.data()+3));
|
|
|
|
// test unaligned read (where the bytes are not aligned)
|
|
iov.iov_base = piece;
|
|
iov.iov_len = piece_size - 9;
|
|
ret = s->readv(iov, piece_index_t(0), 3, 0, ec);
|
|
TEST_CHECK(ret == piece_size - 9);
|
|
if (ret != piece_size - 9) print_error("readv", ret, ec);
|
|
TEST_CHECK(std::equal(piece, piece + piece_size-9, piece1.data()+3));
|
|
|
|
// verify piece 1
|
|
iov.iov_base = piece;
|
|
iov.iov_len = piece_size;
|
|
ret = s->readv(iov, piece_index_t(0), 0, 0, ec);
|
|
TEST_CHECK(ret == piece_size);
|
|
if (ret != piece_size) print_error("readv", ret, ec);
|
|
TEST_CHECK(std::equal(piece, piece + piece_size, piece1.data()));
|
|
|
|
// do the same with piece 0 and 2 (in slot 1 and 2)
|
|
iov.iov_base = piece0.data();
|
|
iov.iov_len = piece_size;
|
|
ret = s->writev(iov, piece_index_t(1), 0, 0, ec);
|
|
if (ret != piece_size) print_error("writev", ret, ec);
|
|
|
|
iov.iov_base = piece2.data();
|
|
iov.iov_len = piece_size;
|
|
ret = s->writev(iov, piece_index_t(2), 0, 0, ec);
|
|
if (ret != piece_size) print_error("writev", ret, ec);
|
|
|
|
// verify piece 0 and 2
|
|
iov.iov_base = piece;
|
|
iov.iov_len = piece_size;
|
|
ret = s->readv(iov, piece_index_t(1), 0, 0, ec);
|
|
if (ret != piece_size) print_error("readv", ret, ec);
|
|
TEST_CHECK(std::equal(piece, piece + piece_size, piece0.data()));
|
|
|
|
iov.iov_base = piece;
|
|
iov.iov_len = piece_size;
|
|
ret = s->readv(iov, piece_index_t(2), 0, 0, ec);
|
|
if (ret != piece_size) print_error("readv", ret, ec);
|
|
TEST_CHECK(std::equal(piece, piece + piece_size, piece2.data()));
|
|
|
|
s->release_files(ec);
|
|
}
|
|
|
|
free(piece);
|
|
}
|
|
|
|
void test_remove(std::string const& test_path, bool unbuffered)
|
|
{
|
|
error_code ec;
|
|
remove_all(combine_path(test_path, "temp_storage"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "temp_storage")
|
|
<< "': " << ec.message() << std::endl;
|
|
TEST_CHECK(!exists(combine_path(test_path, "temp_storage")));
|
|
|
|
file_storage fs;
|
|
std::vector<char> buf;
|
|
file_pool fp;
|
|
io_service ios;
|
|
disk_buffer_pool dp(16 * 1024, ios, std::bind(&nop));
|
|
|
|
aux::session_settings set;
|
|
set.set_int(settings_pack::disk_io_write_mode
|
|
, unbuffered ? settings_pack::disable_os_cache
|
|
: settings_pack::enable_os_cache);
|
|
set.set_int(settings_pack::disk_io_read_mode
|
|
, unbuffered ? settings_pack::disable_os_cache
|
|
: settings_pack::enable_os_cache);
|
|
|
|
std::shared_ptr<default_storage> s = setup_torrent(fs, fp, buf, test_path, set);
|
|
|
|
// directories are not created up-front, unless they contain
|
|
// an empty file (all of which are created up-front, along with
|
|
// all required directories)
|
|
// files are created on first write
|
|
TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("_folder3", combine_path("subfolder", "test5.tmp"))))));
|
|
|
|
// this directory and file is created up-front because it's an empty file
|
|
TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("folder2", "test3.tmp")))));
|
|
|
|
// this isn't
|
|
TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("folder1", "test2.tmp")))));
|
|
|
|
file::iovec_t b = {&buf[0], 4};
|
|
storage_error se;
|
|
s->writev(b, piece_index_t(2), 0, 0, se);
|
|
|
|
TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("folder1", "test2.tmp")))));
|
|
TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("_folder3", combine_path("subfolder", "test5.tmp"))))));
|
|
file_status st;
|
|
stat_file(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("folder1", "test2.tmp"))), &st, ec);
|
|
TEST_EQUAL(st.file_size, 8);
|
|
|
|
s->writev(b, piece_index_t(4), 0, 0, se);
|
|
|
|
TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("_folder3", combine_path("subfolder", "test5.tmp"))))));
|
|
stat_file(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("_folder3", "test5.tmp"))), &st, ec);
|
|
TEST_EQUAL(st.file_size, 8);
|
|
|
|
s->delete_files(session::delete_files, se);
|
|
if (se) print_error("delete_files", 0, se);
|
|
|
|
if (se)
|
|
{
|
|
TEST_ERROR(se.ec.message().c_str());
|
|
std::printf("default_storage::delete_files %s: %d\n"
|
|
, se.ec.message().c_str(), static_cast<int>(se.file()));
|
|
}
|
|
|
|
TEST_CHECK(!exists(combine_path(test_path, "temp_storage")));
|
|
}
|
|
|
|
void test_rename(std::string const& test_path)
|
|
{
|
|
error_code ec;
|
|
remove_all(combine_path(test_path, "temp_storage"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "temp_storage")
|
|
<< "': " << ec.message() << std::endl;
|
|
TEST_CHECK(!exists(combine_path(test_path, "temp_storage")));
|
|
|
|
file_storage fs;
|
|
std::vector<char> buf;
|
|
file_pool fp;
|
|
io_service ios;
|
|
disk_buffer_pool dp(16 * 1024, ios, std::bind(&nop));
|
|
aux::session_settings set;
|
|
|
|
std::shared_ptr<default_storage> s = setup_torrent(fs, fp, buf, test_path
|
|
, set);
|
|
|
|
// directories are not created up-front, unless they contain
|
|
// an empty file
|
|
std::string first_file = fs.file_path(file_index_t(0));
|
|
for (file_index_t i(0); i < fs.end_file(); ++i)
|
|
{
|
|
TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage"
|
|
, fs.file_path(i)))));
|
|
}
|
|
|
|
storage_error se;
|
|
s->rename_file(file_index_t(0), "new_filename", se);
|
|
if (se.ec)
|
|
{
|
|
std::printf("default_storage::rename_file failed: %s\n"
|
|
, se.ec.message().c_str());
|
|
}
|
|
TEST_CHECK(!se.ec);
|
|
|
|
TEST_EQUAL(s->files().file_path(file_index_t(0)), "new_filename");
|
|
}
|
|
|
|
void test_check_files(std::string const& test_path
|
|
, libtorrent::storage_mode_t storage_mode
|
|
, bool unbuffered)
|
|
{
|
|
std::shared_ptr<torrent_info> info;
|
|
|
|
error_code ec;
|
|
const int piece_size = 16 * 1024;
|
|
remove_all(combine_path(test_path, "temp_storage"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "temp_storage")
|
|
<< "': " << ec.message() << std::endl;
|
|
file_storage fs;
|
|
fs.add_file("temp_storage/test1.tmp", piece_size);
|
|
fs.add_file("temp_storage/test2.tmp", piece_size * 2);
|
|
fs.add_file("temp_storage/test3.tmp", piece_size);
|
|
|
|
std::vector<char> piece0 = new_piece(piece_size);
|
|
std::vector<char> piece2 = new_piece(piece_size);
|
|
|
|
libtorrent::create_torrent t(fs, piece_size, -1, 0);
|
|
t.set_hash(piece_index_t(0), hasher(piece0).final());
|
|
t.set_hash(piece_index_t(1), sha1_hash(nullptr));
|
|
t.set_hash(piece_index_t(2), sha1_hash(nullptr));
|
|
t.set_hash(piece_index_t(3), hasher(piece2).final());
|
|
|
|
create_directory(combine_path(test_path, "temp_storage"), ec);
|
|
if (ec) std::cerr << "create_directory: " << ec.message() << std::endl;
|
|
|
|
std::ofstream f;
|
|
f.open(combine_path(test_path, combine_path("temp_storage", "test1.tmp")).c_str()
|
|
, std::ios::trunc | std::ios::binary);
|
|
f.write(piece0.data(), piece_size);
|
|
f.close();
|
|
f.open(combine_path(test_path, combine_path("temp_storage", "test3.tmp")).c_str()
|
|
, std::ios::trunc | std::ios::binary);
|
|
f.write(piece2.data(), piece_size);
|
|
f.close();
|
|
|
|
std::vector<char> buf;
|
|
bencode(std::back_inserter(buf), t.generate());
|
|
info = std::make_shared<torrent_info>(&buf[0], int(buf.size()), std::ref(ec), 0);
|
|
|
|
aux::session_settings set;
|
|
file_pool fp;
|
|
boost::asio::io_service ios;
|
|
counters cnt;
|
|
disk_io_thread io(ios, cnt);
|
|
settings_pack sett;
|
|
sett.set_int(settings_pack::aio_threads, 1);
|
|
// TODO: this should probably be optional
|
|
alert_manager dummy2(0, 0);
|
|
io.set_settings(&sett, dummy2);
|
|
|
|
disk_buffer_pool dp(16 * 1024, ios, std::bind(&nop));
|
|
storage_params p;
|
|
p.files = &fs;
|
|
p.path = test_path;
|
|
p.pool = &fp;
|
|
p.mode = storage_mode;
|
|
|
|
std::shared_ptr<storage_interface> pm = std::make_shared<default_storage>(p);
|
|
pm->set_files(&fs);
|
|
std::mutex lock;
|
|
|
|
bool done = false;
|
|
add_torrent_params frd;
|
|
aux::vector<std::string, file_index_t> links;
|
|
io.async_check_files(pm.get(), &frd, links
|
|
, std::bind(&on_check_resume_data, _1, _2, &done));
|
|
io.submit_jobs();
|
|
ios.reset();
|
|
run_until(ios, done);
|
|
|
|
io.abort(true);
|
|
}
|
|
|
|
// TODO: 2 split this test up into smaller parts
|
|
void run_test(bool unbuffered)
|
|
{
|
|
std::string test_path = current_working_directory();
|
|
std::cerr << "\n=== " << test_path << " ===\n" << std::endl;
|
|
|
|
std::shared_ptr<torrent_info> info;
|
|
|
|
std::vector<char> piece0 = new_piece(piece_size);
|
|
std::vector<char> piece1 = new_piece(piece_size);
|
|
std::vector<char> piece2 = new_piece(piece_size);
|
|
std::vector<char> piece3 = new_piece(piece_size);
|
|
|
|
{
|
|
error_code ec;
|
|
remove_all(combine_path(test_path, "temp_storage"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "temp_storage")
|
|
<< "': " << ec.message() << std::endl;
|
|
file_storage fs;
|
|
fs.add_file("temp_storage/test1.tmp", 17);
|
|
fs.add_file("temp_storage/test2.tmp", 612);
|
|
fs.add_file("temp_storage/test3.tmp", 0);
|
|
fs.add_file("temp_storage/test4.tmp", 0);
|
|
fs.add_file("temp_storage/test5.tmp", 3253);
|
|
fs.add_file("temp_storage/test6.tmp", 841);
|
|
const int last_file_size = 4 * piece_size - int(fs.total_size());
|
|
fs.add_file("temp_storage/test7.tmp", last_file_size);
|
|
|
|
// File layout
|
|
// +-+--+++-------+-------+----------------------------------------------------------------------------------------+
|
|
// |1| 2||| file5 | file6 | file7 |
|
|
// +-+--+++-------+-------+----------------------------------------------------------------------------------------+
|
|
// | | | | |
|
|
// | piece 0 | piece 1 | piece 2 | piece 3 |
|
|
|
|
libtorrent::create_torrent t(fs, piece_size, -1, 0);
|
|
TEST_CHECK(t.num_pieces() == 4);
|
|
t.set_hash(piece_index_t(0), hasher(piece0).final());
|
|
t.set_hash(piece_index_t(1), hasher(piece1).final());
|
|
t.set_hash(piece_index_t(2), hasher(piece2).final());
|
|
t.set_hash(piece_index_t(3), hasher(piece3).final());
|
|
|
|
std::vector<char> buf;
|
|
bencode(std::back_inserter(buf), t.generate());
|
|
info = std::make_shared<torrent_info>(&buf[0], int(buf.size()), std::ref(ec), 0);
|
|
std::cerr << "=== test 1 === " << (unbuffered?"unbuffered":"buffered") << std::endl;
|
|
|
|
// run_storage_tests writes piece 0, 1 and 2. not 3
|
|
run_storage_tests(info, fs, test_path, storage_mode_sparse, unbuffered);
|
|
|
|
// make sure the files have the correct size
|
|
std::string base = combine_path(test_path, "temp_storage");
|
|
std::printf("base = \"%s\"\n", base.c_str());
|
|
TEST_EQUAL(file_size(combine_path(base, "test1.tmp")), 17);
|
|
TEST_EQUAL(file_size(combine_path(base, "test2.tmp")), 612);
|
|
|
|
// these files should have been allocated as 0 size
|
|
TEST_CHECK(exists(combine_path(base, "test3.tmp")));
|
|
TEST_CHECK(exists(combine_path(base, "test4.tmp")));
|
|
TEST_CHECK(file_size(combine_path(base, "test3.tmp")) == 0);
|
|
TEST_CHECK(file_size(combine_path(base, "test4.tmp")) == 0);
|
|
|
|
TEST_EQUAL(file_size(combine_path(base, "test5.tmp")), 3253);
|
|
TEST_EQUAL(file_size(combine_path(base, "test6.tmp")), 841);
|
|
std::printf("file: %d expected: %d last_file_size: %d, piece_size: %d\n"
|
|
, int(file_size(combine_path(base, "test7.tmp")))
|
|
, int(last_file_size - piece_size), last_file_size, piece_size);
|
|
TEST_EQUAL(file_size(combine_path(base, "test7.tmp")), last_file_size - piece_size);
|
|
remove_all(combine_path(test_path, "temp_storage"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "temp_storage")
|
|
<< "': " << ec.message() << std::endl;
|
|
}
|
|
|
|
// ==============================================
|
|
|
|
{
|
|
error_code ec;
|
|
file_storage fs;
|
|
fs.add_file(combine_path("temp_storage", "test1.tmp"), 3 * piece_size);
|
|
libtorrent::create_torrent t(fs, piece_size, -1, 0);
|
|
TEST_CHECK(fs.file_path(file_index_t(0)) == combine_path("temp_storage", "test1.tmp"));
|
|
t.set_hash(piece_index_t(0), hasher(piece0).final());
|
|
t.set_hash(piece_index_t(1), hasher(piece1).final());
|
|
t.set_hash(piece_index_t(2), hasher(piece2).final());
|
|
|
|
std::vector<char> buf;
|
|
bencode(std::back_inserter(buf), t.generate());
|
|
info = std::make_shared<torrent_info>(&buf[0], int(buf.size()), std::ref(ec), 0);
|
|
|
|
std::cerr << "=== test 3 ===" << std::endl;
|
|
|
|
run_storage_tests(info, fs, test_path, storage_mode_sparse, unbuffered);
|
|
|
|
TEST_EQUAL(file_size(combine_path(test_path, combine_path("temp_storage", "test1.tmp"))), piece_size * 3);
|
|
remove_all(combine_path(test_path, "temp_storage"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "temp_storage")
|
|
<< "': " << ec.message() << std::endl;
|
|
|
|
// ==============================================
|
|
|
|
std::cerr << "=== test 4 ===" << std::endl;
|
|
|
|
run_storage_tests(info, fs, test_path, storage_mode_allocate, unbuffered);
|
|
|
|
std::cerr << file_size(combine_path(test_path, combine_path("temp_storage", "test1.tmp"))) << std::endl;
|
|
TEST_EQUAL(file_size(combine_path(test_path, combine_path("temp_storage", "test1.tmp"))), 3 * piece_size);
|
|
|
|
remove_all(combine_path(test_path, "temp_storage"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "temp_storage")
|
|
<< "': " << ec.message() << std::endl;
|
|
|
|
}
|
|
|
|
// ==============================================
|
|
|
|
std::cerr << "=== test 5 ===" << std::endl;
|
|
test_remove(test_path, unbuffered);
|
|
|
|
// ==============================================
|
|
|
|
std::cerr << "=== test 6 ===" << std::endl;
|
|
test_check_files(test_path, storage_mode_sparse, unbuffered);
|
|
test_check_files(test_path, storage_mode_sparse, unbuffered);
|
|
|
|
std::cerr << "=== test 7 ===" << std::endl;
|
|
test_rename(test_path);
|
|
}
|
|
|
|
void test_fastresume(bool const test_deprecated)
|
|
{
|
|
std::string test_path = current_working_directory();
|
|
error_code ec;
|
|
std::cout << "\n\n=== test fastresume ===" << std::endl;
|
|
remove_all(combine_path(test_path, "tmp1"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "tmp1")
|
|
<< "': " << ec.message() << std::endl;
|
|
create_directory(combine_path(test_path, "tmp1"), ec);
|
|
if (ec) std::cerr << "create_directory '" << combine_path(test_path, "tmp1")
|
|
<< "': " << ec.message() << std::endl;
|
|
std::ofstream file(combine_path(test_path, "tmp1/temporary").c_str());
|
|
std::shared_ptr<torrent_info> t = ::create_torrent(&file);
|
|
file.close();
|
|
TEST_CHECK(exists(combine_path(test_path, "tmp1/temporary")));
|
|
if (!exists(combine_path(test_path, "tmp1/temporary")))
|
|
return;
|
|
|
|
entry resume;
|
|
{
|
|
settings_pack pack = settings();
|
|
lt::session ses(pack);
|
|
|
|
error_code ec;
|
|
|
|
add_torrent_params p;
|
|
p.ti = std::make_shared<torrent_info>(std::cref(*t));
|
|
p.save_path = combine_path(test_path, "tmp1");
|
|
p.storage_mode = storage_mode_sparse;
|
|
torrent_handle h = ses.add_torrent(p, ec);
|
|
TEST_CHECK(exists(combine_path(p.save_path, "temporary")));
|
|
if (!exists(combine_path(p.save_path, "temporary")))
|
|
return;
|
|
|
|
torrent_status s;
|
|
for (int i = 0; i < 50; ++i)
|
|
{
|
|
print_alerts(ses, "ses");
|
|
s = h.status();
|
|
if (s.progress == 1.0f)
|
|
{
|
|
std::cout << "progress: 1.0f" << std::endl;
|
|
break;
|
|
}
|
|
std::this_thread::sleep_for(lt::milliseconds(100));
|
|
}
|
|
|
|
// the whole point of the test is to have a resume
|
|
// data which expects the file to exist in full. If
|
|
// we failed to do that, we might as well abort
|
|
TEST_EQUAL(s.progress, 1.0f);
|
|
if (s.progress != 1.0f)
|
|
return;
|
|
|
|
h.save_resume_data();
|
|
alert const* ra = wait_for_alert(ses, save_resume_data_alert::alert_type);
|
|
TEST_CHECK(ra);
|
|
if (ra) resume = *alert_cast<save_resume_data_alert>(ra)->resume_data;
|
|
ses.remove_torrent(h, lt::session::delete_files);
|
|
alert const* da = wait_for_alert(ses, torrent_deleted_alert::alert_type);
|
|
TEST_CHECK(da);
|
|
}
|
|
TEST_CHECK(!exists(combine_path(test_path, combine_path("tmp1", "temporary"))));
|
|
if (exists(combine_path(test_path, combine_path("tmp1", "temporary"))))
|
|
return;
|
|
|
|
std::cerr << resume.to_string() << "\n";
|
|
|
|
// make sure the fast resume check fails! since we removed the file
|
|
{
|
|
settings_pack pack = settings();
|
|
lt::session ses(pack);
|
|
|
|
std::vector<char> resume_data;
|
|
bencode(std::back_inserter(resume_data), resume);
|
|
|
|
add_torrent_params p;
|
|
#ifndef TORRENT_NO_DEPRECATE
|
|
if (test_deprecated)
|
|
{
|
|
p.resume_data = resume_data;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
p = read_resume_data(&resume_data[0], int(resume_data.size()), ec);
|
|
TEST_CHECK(!ec);
|
|
}
|
|
|
|
p.flags &= ~add_torrent_params::flag_paused;
|
|
p.flags &= ~add_torrent_params::flag_auto_managed;
|
|
p.ti = std::make_shared<torrent_info>(std::cref(*t));
|
|
p.save_path = combine_path(test_path, "tmp1");
|
|
p.storage_mode = storage_mode_sparse;
|
|
torrent_handle h = ses.add_torrent(p, ec);
|
|
|
|
std::printf("expecting fastresume to be rejected becase the files were removed");
|
|
alert const* a = wait_for_alert(ses, fastresume_rejected_alert::alert_type
|
|
, "ses");
|
|
// we expect the fast resume to be rejected because the files were removed
|
|
TEST_CHECK(alert_cast<fastresume_rejected_alert>(a) != nullptr);
|
|
}
|
|
remove_all(combine_path(test_path, "tmp1"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "tmp1")
|
|
<< "': " << ec.message() << std::endl;
|
|
}
|
|
|
|
TORRENT_TEST(fastresume)
|
|
{
|
|
test_fastresume(false);
|
|
}
|
|
|
|
#ifndef TORRENT_NO_DEPRECATE
|
|
TORRENT_TEST(fastresume_deprecated)
|
|
{
|
|
test_fastresume(true);
|
|
}
|
|
#endif
|
|
|
|
|
|
bool got_file_rename_alert(alert const* a)
|
|
{
|
|
return alert_cast<libtorrent::file_renamed_alert>(a)
|
|
|| alert_cast<libtorrent::file_rename_failed_alert>(a);
|
|
}
|
|
|
|
TORRENT_TEST(rename_file)
|
|
{
|
|
std::vector<char> buf;
|
|
file_storage fs;
|
|
std::shared_ptr<torrent_info> info = setup_torrent_info(fs, buf);
|
|
|
|
const int mask = alert::all_categories
|
|
& ~(alert::performance_warning
|
|
| alert::stats_notification);
|
|
|
|
settings_pack pack;
|
|
pack.set_bool(settings_pack::enable_lsd, false);
|
|
pack.set_bool(settings_pack::enable_natpmp, false);
|
|
pack.set_bool(settings_pack::enable_upnp, false);
|
|
pack.set_bool(settings_pack::enable_dht, false);
|
|
pack.set_int(settings_pack::alert_mask, mask);
|
|
pack.set_bool(settings_pack::disable_hash_checks, true);
|
|
lt::session ses(pack);
|
|
|
|
add_torrent_params p;
|
|
p.ti = info;
|
|
p.save_path = ".";
|
|
error_code ec;
|
|
torrent_handle h = ses.add_torrent(p, ec);
|
|
|
|
// make it a seed
|
|
std::vector<char> tmp(info->piece_length());
|
|
for (piece_index_t i(0); i < fs.end_piece(); ++i)
|
|
h.add_piece(i, &tmp[0]);
|
|
|
|
// wait for the files to have been written
|
|
alert const* pf = wait_for_alert(ses, piece_finished_alert::alert_type, "ses", info->num_pieces());
|
|
TEST_CHECK(pf);
|
|
|
|
// now rename them. This is the test
|
|
for (file_index_t i(0); i < fs.end_file(); ++i)
|
|
{
|
|
std::string name = fs.file_path(i);
|
|
h.rename_file(i, "__" + name);
|
|
}
|
|
|
|
// wait fir the files to have been renamed
|
|
alert const* fra = wait_for_alert(ses, file_renamed_alert::alert_type, "ses", info->num_files());
|
|
TEST_CHECK(fra);
|
|
|
|
TEST_CHECK(exists("__" + info->name()));
|
|
|
|
h.save_resume_data();
|
|
alert const* ra = wait_for_alert(ses, save_resume_data_alert::alert_type);
|
|
TEST_CHECK(ra);
|
|
if (!ra) return;
|
|
entry resume = *alert_cast<save_resume_data_alert>(ra)->resume_data;
|
|
|
|
std::cerr << resume.to_string() << "\n";
|
|
|
|
entry::list_type files = resume.dict().find("mapped_files")->second.list();
|
|
for (entry::list_type::iterator i = files.begin(); i != files.end(); ++i)
|
|
{
|
|
TEST_CHECK(i->string().substr(0, 2) == "__");
|
|
}
|
|
}
|
|
|
|
void test_rename_file_fastresume(bool test_deprecated)
|
|
{
|
|
std::string test_path = current_working_directory();
|
|
error_code ec;
|
|
std::cout << "\n\n=== test rename file in fastresume ===" << std::endl;
|
|
remove_all(combine_path(test_path, "tmp2"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "tmp2")
|
|
<< "': " << ec.message() << std::endl;
|
|
create_directory(combine_path(test_path, "tmp2"), ec);
|
|
if (ec) std::cerr << "create_directory: " << ec.message() << std::endl;
|
|
std::ofstream file(combine_path(test_path, "tmp2/temporary").c_str());
|
|
std::shared_ptr<torrent_info> t = ::create_torrent(&file);
|
|
file.close();
|
|
TEST_CHECK(exists(combine_path(test_path, "tmp2/temporary")));
|
|
|
|
entry resume;
|
|
{
|
|
settings_pack pack = settings();
|
|
lt::session ses(pack);
|
|
|
|
add_torrent_params p;
|
|
p.ti = std::make_shared<torrent_info>(std::cref(*t));
|
|
p.save_path = combine_path(test_path, "tmp2");
|
|
p.storage_mode = storage_mode_sparse;
|
|
torrent_handle h = ses.add_torrent(p, ec);
|
|
|
|
h.rename_file(file_index_t(0), "testing_renamed_files");
|
|
std::cout << "renaming file" << std::endl;
|
|
bool renamed = false;
|
|
for (int i = 0; i < 30; ++i)
|
|
{
|
|
if (print_alerts(ses, "ses", true, true, true, &got_file_rename_alert)) renamed = true;
|
|
torrent_status s = h.status();
|
|
if (s.state == torrent_status::seeding && renamed) break;
|
|
std::this_thread::sleep_for(lt::milliseconds(100));
|
|
}
|
|
std::cout << "stop loop" << std::endl;
|
|
torrent_status s = h.status();
|
|
TEST_CHECK(s.state == torrent_status::seeding);
|
|
|
|
h.save_resume_data();
|
|
alert const* ra = wait_for_alert(ses, save_resume_data_alert::alert_type);
|
|
TEST_CHECK(ra);
|
|
if (ra) resume = *alert_cast<save_resume_data_alert>(ra)->resume_data;
|
|
ses.remove_torrent(h);
|
|
}
|
|
TEST_CHECK(!exists(combine_path(test_path, "tmp2/temporary")));
|
|
TEST_CHECK(exists(combine_path(test_path, "tmp2/testing_renamed_files")));
|
|
TEST_CHECK(resume.dict().find("mapped_files") != resume.dict().end());
|
|
|
|
std::cerr << resume.to_string() << "\n";
|
|
|
|
// make sure the fast resume check succeeds, even though we renamed the file
|
|
{
|
|
settings_pack pack = settings();
|
|
lt::session ses(pack);
|
|
|
|
add_torrent_params p;
|
|
std::vector<char> resume_data;
|
|
bencode(std::back_inserter(resume_data), resume);
|
|
|
|
#ifndef TORRENT_NO_DEPRECATE
|
|
if (test_deprecated)
|
|
{
|
|
p.resume_data = resume_data;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
p = read_resume_data(&resume_data[0], int(resume_data.size()), ec);
|
|
TEST_CHECK(!ec);
|
|
}
|
|
p.ti = std::make_shared<torrent_info>(std::cref(*t));
|
|
p.save_path = combine_path(test_path, "tmp2");
|
|
p.storage_mode = storage_mode_sparse;
|
|
torrent_handle h = ses.add_torrent(p, ec);
|
|
|
|
torrent_status stat;
|
|
for (int i = 0; i < 50; ++i)
|
|
{
|
|
stat = h.status();
|
|
print_alerts(ses, "ses");
|
|
if (stat.state == torrent_status::seeding)
|
|
break;
|
|
std::this_thread::sleep_for(lt::milliseconds(100));
|
|
}
|
|
TEST_CHECK(stat.state == torrent_status::seeding);
|
|
|
|
h.save_resume_data();
|
|
alert const* ra = wait_for_alert(ses, save_resume_data_alert::alert_type);
|
|
TEST_CHECK(ra);
|
|
if (ra) resume = *alert_cast<save_resume_data_alert>(ra)->resume_data;
|
|
ses.remove_torrent(h);
|
|
}
|
|
TEST_CHECK(resume.dict().find("mapped_files") != resume.dict().end());
|
|
|
|
std::cerr << resume.to_string() << "\n";
|
|
|
|
remove_all(combine_path(test_path, "tmp2"), ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
std::cerr << "remove_all '" << combine_path(test_path, "tmp2")
|
|
<< "': " << ec.message() << std::endl;
|
|
}
|
|
|
|
TORRENT_TEST(rename_file_fastresume)
|
|
{
|
|
test_rename_file_fastresume(false);
|
|
}
|
|
|
|
#ifndef TORRENT_NO_DEPRECATE
|
|
TORRENT_TEST(rename_file_fastresume_deprecated)
|
|
{
|
|
test_rename_file_fastresume(true);
|
|
}
|
|
#endif
|
|
|
|
void alloc_iov(file::iovec_t* iov, int num_bufs)
|
|
{
|
|
for (int i = 0; i < num_bufs; ++i)
|
|
{
|
|
iov[i].iov_base = malloc(num_bufs * (i + 1));
|
|
iov[i].iov_len = num_bufs * (i + 1);
|
|
}
|
|
}
|
|
|
|
void fill_pattern(file::iovec_t* iov, int num_bufs)
|
|
{
|
|
int counter = 0;
|
|
for (int i = 0; i < num_bufs; ++i)
|
|
{
|
|
unsigned char* buf = (unsigned char*)iov[i].iov_base;
|
|
for (int k = 0; k < int(iov[i].iov_len); ++k)
|
|
{
|
|
buf[k] = counter & 0xff;
|
|
++counter;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool check_pattern(std::vector<char> const& buf, int counter)
|
|
{
|
|
unsigned char* p = (unsigned char*)&buf[0];
|
|
for (int k = 0; k < int(buf.size()); ++k)
|
|
{
|
|
if (p[k] != (counter & 0xff)) return false;
|
|
++counter;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void fill_pattern2(file::iovec_t* iov, int num_bufs)
|
|
{
|
|
for (int i = 0; i < num_bufs; ++i)
|
|
{
|
|
unsigned char* buf = (unsigned char*)iov[i].iov_base;
|
|
memset(buf, 0xfe, int(iov[i].iov_len));
|
|
}
|
|
}
|
|
|
|
void free_iov(file::iovec_t* iov, int num_bufs)
|
|
{
|
|
for (int i = 0; i < num_bufs; ++i)
|
|
{
|
|
free(iov[i].iov_base);
|
|
iov[i].iov_len = 0;
|
|
iov[i].iov_base = nullptr;
|
|
}
|
|
}
|
|
|
|
TORRENT_TEST(iovec_copy_bufs)
|
|
{
|
|
file::iovec_t iov1[10];
|
|
file::iovec_t iov2[10];
|
|
|
|
alloc_iov(iov1, 10);
|
|
fill_pattern(iov1, 10);
|
|
|
|
TEST_CHECK(bufs_size({iov1, 10}) >= 106);
|
|
|
|
// copy exactly 106 bytes from iov1 to iov2
|
|
int num_bufs = copy_bufs(iov1, 106, iov2);
|
|
|
|
// verify that the first 100 bytes is pattern 1
|
|
// and that the remaining bytes are pattern 2
|
|
|
|
int counter = 0;
|
|
for (int i = 0; i < num_bufs; ++i)
|
|
{
|
|
unsigned char* buf = (unsigned char*)iov2[i].iov_base;
|
|
for (int k = 0; k < int(iov2[i].iov_len); ++k)
|
|
{
|
|
TEST_EQUAL(int(buf[k]), (counter & 0xff));
|
|
++counter;
|
|
}
|
|
}
|
|
TEST_EQUAL(counter, 106);
|
|
|
|
free_iov(iov1, 10);
|
|
}
|
|
|
|
TORRENT_TEST(iovec_clear_bufs)
|
|
{
|
|
file::iovec_t iov[10];
|
|
alloc_iov(iov, 10);
|
|
fill_pattern(iov, 10);
|
|
|
|
clear_bufs({iov, 10});
|
|
for (int i = 0; i < 10; ++i)
|
|
{
|
|
unsigned char* buf = (unsigned char*)iov[i].iov_base;
|
|
for (int k = 0; k < int(iov[i].iov_len); ++k)
|
|
{
|
|
TEST_EQUAL(int(buf[k]), 0);
|
|
}
|
|
}
|
|
free_iov(iov, 10);
|
|
}
|
|
|
|
TORRENT_TEST(iovec_bufs_size)
|
|
{
|
|
file::iovec_t iov[10];
|
|
|
|
for (int i = 1; i < 10; ++i)
|
|
{
|
|
alloc_iov(iov, i);
|
|
|
|
int expected_size = 0;
|
|
for (int k = 0; k < i; ++k) expected_size += i * (k + 1);
|
|
TEST_EQUAL(bufs_size({iov, size_t(i)}), expected_size);
|
|
|
|
free_iov(iov, i);
|
|
}
|
|
}
|
|
|
|
TORRENT_TEST(iovec_advance_bufs)
|
|
{
|
|
file::iovec_t iov1[10];
|
|
file::iovec_t iov2[10];
|
|
alloc_iov(iov1, 10);
|
|
fill_pattern(iov1, 10);
|
|
|
|
memcpy(iov2, iov1, sizeof(iov1));
|
|
|
|
span<file::iovec_t> iov = iov2;
|
|
|
|
// advance iov 13 bytes. Make sure what's left fits pattern 1 shifted
|
|
// 13 bytes
|
|
iov = advance_bufs(iov, 13);
|
|
|
|
// make sure what's in
|
|
int counter = 13;
|
|
for (auto buf : iov)
|
|
{
|
|
unsigned char* buf_base = (unsigned char*)buf.iov_base;
|
|
for (int k = 0; k < int(buf.iov_len); ++k)
|
|
{
|
|
TEST_EQUAL(int(buf_base[k]), (counter & 0xff));
|
|
++counter;
|
|
}
|
|
}
|
|
|
|
free_iov(iov1, 10);
|
|
}
|
|
|
|
TORRENT_TEST(unbuffered) { run_test(true); }
|
|
TORRENT_TEST(buffered) { run_test(false); }
|
|
|
|
file_storage make_fs()
|
|
{
|
|
file_storage fs;
|
|
fs.add_file(combine_path("readwritev", "1"), 3);
|
|
fs.add_file(combine_path("readwritev", "2"), 9);
|
|
fs.add_file(combine_path("readwritev", "3"), 81);
|
|
fs.add_file(combine_path("readwritev", "4"), 6561);
|
|
fs.set_piece_length(0x1000);
|
|
fs.set_num_pieces(int((fs.total_size() + 0xfff) / 0x1000));
|
|
return fs;
|
|
}
|
|
|
|
struct test_fileop : libtorrent::fileop
|
|
{
|
|
explicit test_fileop(int stripe_size) : m_stripe_size(stripe_size) {}
|
|
|
|
int file_op(file_index_t const file_index, std::int64_t const file_offset
|
|
, span<file::iovec_t const> bufs, storage_error& ec) override
|
|
{
|
|
size_t offset = size_t(file_offset);
|
|
if (file_index >= m_file_data.end_index())
|
|
{
|
|
m_file_data.resize(static_cast<int>(file_index) + 1);
|
|
}
|
|
|
|
const int write_size = std::min(m_stripe_size, bufs_size(bufs));
|
|
|
|
std::vector<char>& file = m_file_data[file_index];
|
|
|
|
if (offset + write_size > file.size())
|
|
{
|
|
file.resize(offset + write_size);
|
|
}
|
|
|
|
int left = write_size;
|
|
while (left > 0)
|
|
{
|
|
const int copy_size = (std::min)(left, int(bufs.front().iov_len));
|
|
memcpy(&file[offset], bufs.front().iov_base, copy_size);
|
|
bufs = bufs.subspan(1);
|
|
offset += copy_size;
|
|
left -= copy_size;
|
|
}
|
|
return write_size;
|
|
}
|
|
|
|
int m_stripe_size;
|
|
aux::vector<std::vector<char>, file_index_t> m_file_data;
|
|
};
|
|
|
|
struct test_read_fileop : fileop
|
|
{
|
|
// EOF after size bytes read
|
|
explicit test_read_fileop(int size) : m_size(size), m_counter(0) {}
|
|
|
|
int file_op(file_index_t const file_index, std::int64_t const file_offset
|
|
, span<file::iovec_t const> bufs, storage_error& ec) override
|
|
{
|
|
int local_size = std::min(m_size, bufs_size(bufs));
|
|
const int read = local_size;
|
|
while (local_size > 0)
|
|
{
|
|
unsigned char* p = (unsigned char*)bufs.front().iov_base;
|
|
const int len = (std::min)(int(bufs.front().iov_len), local_size);
|
|
for (int i = 0; i < len; ++i)
|
|
{
|
|
p[i] = m_counter & 0xff;
|
|
++m_counter;
|
|
}
|
|
local_size -= len;
|
|
m_size -= len;
|
|
bufs = bufs.subspan(1);
|
|
}
|
|
return read;
|
|
}
|
|
|
|
int m_size;
|
|
int m_counter;
|
|
};
|
|
|
|
struct test_error_fileop : fileop
|
|
{
|
|
// EOF after size bytes read
|
|
explicit test_error_fileop(file_index_t error_file)
|
|
: m_error_file(error_file) {}
|
|
|
|
int file_op(file_index_t const file_index, std::int64_t const file_offset
|
|
, span<file::iovec_t const> bufs, storage_error& ec) override
|
|
{
|
|
if (m_error_file == file_index)
|
|
{
|
|
ec.file(file_index);
|
|
ec.ec.assign(boost::system::errc::permission_denied
|
|
, boost::system::generic_category());
|
|
ec.operation = storage_error::read;
|
|
return -1;
|
|
}
|
|
return bufs_size(bufs);
|
|
}
|
|
|
|
file_index_t m_error_file;
|
|
};
|
|
|
|
int count_bufs(file::iovec_t const* bufs, int bytes)
|
|
{
|
|
int size = 0;
|
|
int count = 1;
|
|
if (bytes == 0) return 0;
|
|
for (file::iovec_t const* i = bufs;; ++i, ++count)
|
|
{
|
|
size += int(i->iov_len);
|
|
if (size >= bytes) return count;
|
|
}
|
|
}
|
|
|
|
TORRENT_TEST(readwritev_stripe_1)
|
|
{
|
|
const int num_bufs = 30;
|
|
file::iovec_t iov[num_bufs];
|
|
|
|
alloc_iov(iov, num_bufs);
|
|
fill_pattern(iov, num_bufs);
|
|
|
|
file_storage fs = make_fs();
|
|
test_fileop fop(1);
|
|
storage_error ec;
|
|
|
|
TEST_CHECK(bufs_size({iov, size_t(num_bufs)}) >= fs.total_size());
|
|
|
|
file::iovec_t iov2[num_bufs];
|
|
copy_bufs(iov, int(fs.total_size()), iov2);
|
|
int num_bufs2 = count_bufs(iov2, int(fs.total_size()));
|
|
TEST_CHECK(num_bufs2 <= num_bufs);
|
|
|
|
int ret = readwritev(fs, {iov2, size_t(num_bufs2)}, piece_index_t(0), 0, fop, ec);
|
|
|
|
TEST_EQUAL(ret, fs.total_size());
|
|
TEST_EQUAL(fop.m_file_data.size(), 4);
|
|
TEST_EQUAL(fop.m_file_data[file_index_t(0)].size(), 3);
|
|
TEST_EQUAL(fop.m_file_data[file_index_t(1)].size(), 9);
|
|
TEST_EQUAL(fop.m_file_data[file_index_t(2)].size(), 81);
|
|
TEST_EQUAL(fop.m_file_data[file_index_t(3)].size(), 6561);
|
|
|
|
TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(0)], 0));
|
|
TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(1)], 3));
|
|
TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(2)], 3 + 9));
|
|
TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(3)], 3 + 9 + 81));
|
|
|
|
free_iov(iov, num_bufs);
|
|
}
|
|
|
|
TORRENT_TEST(readwritev_single_buffer)
|
|
{
|
|
file_storage fs = make_fs();
|
|
test_fileop fop(10000000);
|
|
storage_error ec;
|
|
|
|
std::vector<char> buf(size_t(fs.total_size()));
|
|
file::iovec_t iov = { &buf[0], buf.size() };
|
|
fill_pattern(&iov, 1);
|
|
|
|
int ret = readwritev(fs, iov, piece_index_t(0), 0, fop, ec);
|
|
|
|
TEST_EQUAL(ret, fs.total_size());
|
|
TEST_EQUAL(fop.m_file_data.size(), 4);
|
|
TEST_EQUAL(fop.m_file_data[file_index_t(0)].size(), 3);
|
|
TEST_EQUAL(fop.m_file_data[file_index_t(1)].size(), 9);
|
|
TEST_EQUAL(fop.m_file_data[file_index_t(2)].size(), 81);
|
|
TEST_EQUAL(fop.m_file_data[file_index_t(3)].size(), 6561);
|
|
|
|
TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(0)], 0));
|
|
TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(1)], 3));
|
|
TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(2)], 3 + 9));
|
|
TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(3)], 3 + 9 + 81));
|
|
}
|
|
|
|
TORRENT_TEST(readwritev_read)
|
|
{
|
|
file_storage fs = make_fs();
|
|
test_read_fileop fop(10000000);
|
|
storage_error ec;
|
|
|
|
std::vector<char> buf(size_t(fs.total_size()));
|
|
file::iovec_t iov = { &buf[0], buf.size() };
|
|
|
|
// read everything
|
|
int ret = readwritev(fs, iov, piece_index_t(0), 0, fop, ec);
|
|
|
|
TEST_EQUAL(ret, fs.total_size());
|
|
TEST_CHECK(check_pattern(buf, 0));
|
|
}
|
|
|
|
TORRENT_TEST(readwritev_read_short)
|
|
{
|
|
file_storage fs = make_fs();
|
|
test_read_fileop fop(100);
|
|
storage_error ec;
|
|
|
|
std::vector<char> buf(size_t(fs.total_size()));
|
|
file::iovec_t iov = { &buf[0]
|
|
, static_cast<size_t>(fs.total_size()) };
|
|
|
|
// read everything
|
|
int ret = readwritev(fs, iov, piece_index_t(0), 0, fop, ec);
|
|
|
|
TEST_EQUAL(static_cast<int>(ec.file()), 3);
|
|
|
|
TEST_EQUAL(ret, 100);
|
|
buf.resize(100);
|
|
TEST_CHECK(check_pattern(buf, 0));
|
|
}
|
|
|
|
TORRENT_TEST(readwritev_error)
|
|
{
|
|
file_storage fs = make_fs();
|
|
test_error_fileop fop(file_index_t(2));
|
|
storage_error ec;
|
|
|
|
std::vector<char> buf(size_t(fs.total_size()));
|
|
file::iovec_t iov = { &buf[0]
|
|
, static_cast<size_t>(fs.total_size()) };
|
|
|
|
// read everything
|
|
int ret = readwritev(fs, iov, piece_index_t(0), 0, fop, ec);
|
|
|
|
TEST_EQUAL(ret, -1);
|
|
TEST_EQUAL(static_cast<int>(ec.file()), 2);
|
|
TEST_EQUAL(ec.operation, storage_error::read);
|
|
TEST_EQUAL(ec.ec, boost::system::errc::permission_denied);
|
|
std::printf("error: %s\n", ec.ec.message().c_str());
|
|
}
|
|
|
|
TORRENT_TEST(readwritev_zero_size_files)
|
|
{
|
|
file_storage fs;
|
|
fs.add_file(combine_path("readwritev", "1"), 3);
|
|
fs.add_file(combine_path("readwritev", "2"), 0);
|
|
fs.add_file(combine_path("readwritev", "3"), 81);
|
|
fs.add_file(combine_path("readwritev", "4"), 0);
|
|
fs.add_file(combine_path("readwritev", "5"), 6561);
|
|
fs.set_piece_length(0x1000);
|
|
fs.set_num_pieces(int((fs.total_size() + 0xfff) / 0x1000));
|
|
test_read_fileop fop(10000000);
|
|
storage_error ec;
|
|
|
|
std::vector<char> buf(size_t(fs.total_size()));
|
|
file::iovec_t iov = { &buf[0]
|
|
, static_cast<size_t>(fs.total_size()) };
|
|
|
|
// read everything
|
|
int ret = readwritev(fs, iov, piece_index_t(0), 0, fop, ec);
|
|
|
|
TEST_EQUAL(ret, fs.total_size());
|
|
TEST_CHECK(check_pattern(buf, 0));
|
|
}
|
|
|
|
void delete_dirs(std::string path)
|
|
{
|
|
error_code ec;
|
|
remove_all(path, ec);
|
|
if (ec && ec != boost::system::errc::no_such_file_or_directory)
|
|
{
|
|
std::printf("remove_all \"%s\": %s\n"
|
|
, path.c_str(), ec.message().c_str());
|
|
}
|
|
TEST_CHECK(!exists(path));
|
|
}
|
|
|
|
TORRENT_TEST(move_storage_into_self)
|
|
{
|
|
std::string const save_path = current_working_directory();
|
|
delete_dirs(combine_path(save_path, "temp_storage"));
|
|
|
|
aux::session_settings set;
|
|
file_storage fs;
|
|
std::vector<char> buf;
|
|
file_pool fp;
|
|
io_service ios;
|
|
disk_buffer_pool dp(16 * 1024, ios, std::bind(&nop));
|
|
std::shared_ptr<default_storage> s = setup_torrent(fs, fp, buf, save_path, set);
|
|
|
|
file::iovec_t const b = {&buf[0], 4};
|
|
storage_error se;
|
|
s->writev(b, piece_index_t(2), 0, 0, se);
|
|
|
|
std::string const test_path = combine_path(save_path, combine_path("temp_storage", "folder1"));
|
|
s->move_storage(test_path, 0, se);
|
|
TEST_EQUAL(se.ec, boost::system::errc::success);
|
|
|
|
TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("folder1", "test2.tmp")))));
|
|
|
|
// these directories and files are created up-front because they are empty files
|
|
TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("folder2", "test3.tmp")))));
|
|
TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("_folder3", "test4.tmp")))));
|
|
}
|
|
|
|
TORRENT_TEST(storage_paths_string_pooling)
|
|
{
|
|
file_storage file_storage;
|
|
file_storage.add_file(combine_path("test_storage", "root.txt"), 0x4000);
|
|
file_storage.add_file(combine_path("test_storage", combine_path("sub", "test1.txt")), 0x4000);
|
|
file_storage.add_file(combine_path("test_storage", combine_path("sub", "test2.txt")), 0x4000);
|
|
file_storage.add_file(combine_path("test_storage", combine_path("sub", "test3.txt")), 0x4000);
|
|
|
|
// "sub" paths should point to same string item, so paths.size() must not grow
|
|
TEST_CHECK(file_storage.paths().size() <= 2);
|
|
}
|
|
|
|
TORRENT_TEST(dont_move_intermingled_files)
|
|
{
|
|
std::string const save_path = combine_path(current_working_directory(), "save_path_1");
|
|
delete_dirs(combine_path(save_path, "temp_storage"));
|
|
|
|
std::string test_path = combine_path(current_working_directory(), "save_path_2");
|
|
delete_dirs(combine_path(test_path, "temp_storage"));
|
|
|
|
aux::session_settings set;
|
|
file_storage fs;
|
|
std::vector<char> buf;
|
|
file_pool fp;
|
|
io_service ios;
|
|
disk_buffer_pool dp(16 * 1024, ios, std::bind(&nop));
|
|
std::shared_ptr<default_storage> s = setup_torrent(fs, fp, buf, save_path, set);
|
|
|
|
file::iovec_t b = {&buf[0], 4};
|
|
storage_error se;
|
|
s->writev(b, piece_index_t(2), 0, 0, se);
|
|
|
|
error_code ec;
|
|
create_directory(combine_path(save_path, combine_path("temp_storage"
|
|
, combine_path("_folder3", "alien_folder1"))), ec);
|
|
TEST_EQUAL(ec, boost::system::errc::success);
|
|
file f;
|
|
f.open(combine_path(save_path, combine_path("temp_storage", "alien1.tmp"))
|
|
, file::write_only, ec);
|
|
f.close();
|
|
TEST_EQUAL(ec, boost::system::errc::success);
|
|
f.open(combine_path(save_path, combine_path("temp_storage"
|
|
, combine_path("folder1", "alien2.tmp"))), file::write_only, ec);
|
|
f.close();
|
|
TEST_EQUAL(ec, boost::system::errc::success);
|
|
|
|
s->move_storage(test_path, 0, se);
|
|
TEST_EQUAL(se.ec, boost::system::errc::success);
|
|
|
|
// torrent files moved to new place
|
|
TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("folder1", "test2.tmp")))));
|
|
// these directories and files are created up-front because they are empty files
|
|
TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("folder2", "test3.tmp")))));
|
|
TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("_folder3", "test4.tmp")))));
|
|
|
|
// intermingled files and directories are still in old place
|
|
TEST_CHECK(exists(combine_path(save_path, combine_path("temp_storage"
|
|
, "alien1.tmp"))));
|
|
TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage"
|
|
, "alien1.tmp"))));
|
|
TEST_CHECK(exists(combine_path(save_path, combine_path("temp_storage"
|
|
, combine_path("folder1", "alien2.tmp")))));
|
|
TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("folder1", "alien2.tmp")))));
|
|
TEST_CHECK(exists(combine_path(save_path, combine_path("temp_storage"
|
|
, combine_path("_folder3", "alien_folder1")))));
|
|
TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage"
|
|
, combine_path("_folder3", "alien_folder1")))));
|
|
}
|