Files
libtorrent/src/create_torrent.cpp

1018 lines
29 KiB
C++

/*
Copyright (c) 2008-2022, Arvid Norberg
Copyright (c) 2016-2017, 2019-2020, Alden Torres
Copyright (c) 2016, Pavel Pimenov
Copyright (c) 2017, Steven Siloti
Copyright (c) 2018, Mike Tzou
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 "libtorrent/create_torrent.hpp"
#include "libtorrent/utf8.hpp"
#include "libtorrent/mmap_disk_io.hpp" // for hasher_thread_divisor
#include "libtorrent/disk_interface.hpp"
#include "libtorrent/aux_/merkle.hpp" // for merkle_*()
#include "libtorrent/torrent_info.hpp"
#include "libtorrent/performance_counters.hpp" // for counters
#include "libtorrent/aux_/throw.hpp"
#include "libtorrent/aux_/path.hpp"
#include "libtorrent/aux_/session_settings.hpp"
#include "libtorrent/session.hpp" // for default_disk_io_constructor
#include "libtorrent/aux_/directory.hpp"
#include "libtorrent/aux_/time.hpp" // for posix_time
#include "libtorrent/disk_interface.hpp"
#include <sys/types.h>
#include <sys/stat.h>
#include <functional>
#include <memory>
using namespace std::placeholders;
namespace libtorrent {
#if TORRENT_ABI_VERSION <= 2
constexpr create_flags_t create_torrent::optimize_alignment;
#endif
#if TORRENT_ABI_VERSION == 1
constexpr create_flags_t create_torrent::optimize;
#endif
#if TORRENT_ABI_VERSION <= 2
constexpr create_flags_t create_torrent::merkle;
#endif
constexpr create_flags_t create_torrent::modification_time;
constexpr create_flags_t create_torrent::symlinks;
#if TORRENT_ABI_VERSION <= 2
constexpr create_flags_t create_torrent::mutable_torrent_support;
#endif
constexpr create_flags_t create_torrent::v2_only;
constexpr create_flags_t create_torrent::v1_only;
constexpr create_flags_t create_torrent::canonical_files;
constexpr create_flags_t create_torrent::no_attributes;
constexpr create_flags_t create_torrent::canonical_files_no_tail_padding;
constexpr create_flags_t create_torrent::allow_odd_piece_size;
namespace {
bool default_pred(std::string const&) { return true; }
bool ignore_subdir(std::string const& leaf)
{ return leaf == ".." || leaf == "."; }
#ifndef TORRENT_WINDOWS
std::string get_symlink_path_impl(char const* path)
{
constexpr int MAX_SYMLINK_PATH = 200;
char buf[MAX_SYMLINK_PATH];
std::string f = convert_to_native_path_string(path);
int char_read = int(readlink(f.c_str(), buf, MAX_SYMLINK_PATH));
if (char_read < 0) return "";
if (char_read < MAX_SYMLINK_PATH) buf[char_read] = 0;
else buf[0] = 0;
return convert_from_native_path(buf);
}
#endif
void add_files_impl(file_storage& fs, std::string const& p
, std::string const& l, std::function<bool(std::string)> const& pred
, create_flags_t const flags)
{
std::string const f = combine_path(p, l);
if (!pred(f)) return;
error_code ec;
file_status s;
stat_file(f, &s, ec, (flags & create_torrent::symlinks) ? dont_follow_links : 0);
if (ec) return;
// recurse into directories
bool recurse = (s.mode & file_status::directory) != 0;
// if the file is not a link or we're following links, and it's a directory
// only then should we recurse
#ifndef TORRENT_WINDOWS
if ((s.mode & file_status::link) && (flags & create_torrent::symlinks))
recurse = false;
#endif
if (recurse)
{
for (aux::directory i(f, ec); !i.done(); i.next(ec))
{
std::string const leaf = i.file();
if (ignore_subdir(leaf)) continue;
add_files_impl(fs, p, combine_path(l, leaf), pred, flags);
}
}
else
{
// #error use the fields from s
file_flags_t const file_flags = (flags & create_torrent::no_attributes)
? file_flags_t{}
: aux::get_file_attributes(f);
// mask all bits to check if the file is a symlink
if ((file_flags & file_storage::flag_symlink)
&& (flags & create_torrent::symlinks))
{
std::string const sym_path = aux::get_symlink_path(f);
fs.add_file(l, 0, file_flags, std::time_t(s.mtime), sym_path);
}
else
{
fs.add_file(l, s.file_size, file_flags, std::time_t(s.mtime));
}
}
}
struct hash_state
{
create_torrent& ct;
storage_holder storage;
disk_interface& iothread;
piece_index_t piece_counter;
piece_index_t completed_piece;
std::function<void(piece_index_t)> const& f;
error_code& ec;
};
void on_hash(aux::vector<sha256_hash> v2_blocks, piece_index_t const piece
, sha1_hash const& piece_hash, storage_error const& error, hash_state* st)
{
if (error)
{
// on error
st->ec = error.ec;
st->iothread.abort(true);
return;
}
if (!st->ct.is_v2_only())
st->ct.set_hash(piece, piece_hash);
if (!st->ct.is_v1_only())
{
file_index_t const current_file = st->ct.files().file_index_at_piece(piece);
if (!st->ct.files().pad_file_at(current_file))
{
piece_index_t const file_first_piece(int(st->ct.files().file_offset(current_file) / st->ct.piece_length()));
TORRENT_ASSERT(st->ct.files().file_offset(current_file) % st->ct.piece_length() == 0);
auto const file_piece_offset = piece - file_first_piece;
auto const file_size = st->ct.files().file_size(current_file);
TORRENT_ASSERT(file_size > 0);
auto const file_blocks = st->ct.files().file_num_blocks(current_file);
auto const piece_blocks = st->ct.files().blocks_in_piece2(piece);
int const num_leafs = merkle_num_leafs(file_blocks);
// If the file is smaller than one piece then the block hashes
// should be padded to the next power of two instead of the next
// piece boundary.
int const padded_leafs = file_size < st->ct.piece_length()
? num_leafs
: st->ct.piece_length() / default_block_size;
TORRENT_ASSERT(padded_leafs <= int(v2_blocks.size()));
for (auto i = piece_blocks; i < padded_leafs; ++i)
v2_blocks[i].clear();
sha256_hash const piece_root = merkle_root(
span<sha256_hash>(v2_blocks).first(padded_leafs));
st->ct.set_hash2(current_file, file_piece_offset, piece_root);
}
}
auto flags = disk_interface::sequential_access;
if (!st->ct.is_v2_only()) flags |= disk_interface::v1_hash;
st->f(st->completed_piece);
++st->completed_piece;
if (st->piece_counter < st->ct.files().end_piece())
{
span<sha256_hash> v2_span(v2_blocks);
st->iothread.async_hash(st->storage, st->piece_counter, v2_span, flags
, std::bind(&on_hash, std::move(v2_blocks), _1, _2, _3, st));
++st->piece_counter;
st->iothread.submit_jobs();
}
else if (st->completed_piece == st->ct.files().end_piece())
{
st->iothread.abort(true);
}
}
} // anonymous namespace
namespace aux {
file_flags_t get_file_attributes(std::string const& p)
{
auto const path = convert_to_native_path_string(p);
#ifdef TORRENT_WINDOWS
WIN32_FILE_ATTRIBUTE_DATA attr;
GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &attr);
if (attr.dwFileAttributes == INVALID_FILE_ATTRIBUTES) return {};
if (attr.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) return file_storage::flag_hidden;
return {};
#else
struct ::stat s{};
if (::lstat(path.c_str(), &s) < 0) return {};
file_flags_t file_attr = {};
if (s.st_mode & S_IXUSR)
file_attr |= file_storage::flag_executable;
if (S_ISLNK(s.st_mode))
file_attr |= file_storage::flag_symlink;
return file_attr;
#endif
}
std::string get_symlink_path(std::string const& p)
{
#if defined TORRENT_WINDOWS
TORRENT_UNUSED(p);
return "";
#else
return get_symlink_path_impl(p.c_str());
#endif
}
} // anonymous aux
void add_files(file_storage& fs, std::string const& file
, std::function<bool(std::string)> p, create_flags_t const flags)
{
add_files_impl(fs, parent_path(complete(file)), filename(file), p, flags);
}
void add_files(file_storage& fs, std::string const& file, create_flags_t const flags)
{
add_files_impl(fs, parent_path(complete(file)), filename(file)
, default_pred, flags);
}
namespace {
struct disk_aborter
{
explicit disk_aborter(disk_interface& dio) : m_dio(dio) {}
~disk_aborter() { m_dio.abort(true); }
disk_aborter(disk_aborter const&) = delete;
disk_aborter& operator=(disk_aborter const&) = delete;
private:
disk_interface& m_dio;
};
}
void set_piece_hashes(create_torrent& t, std::string const& p
, std::function<void(piece_index_t)> const& f, error_code& ec)
{
aux::session_settings sett;
set_piece_hashes(t, p, sett, f, ec);
}
void set_piece_hashes(create_torrent& t, std::string const& p
, settings_interface const& sett
, std::function<void(piece_index_t)> const& f, error_code& ec)
{
set_piece_hashes(t, p, sett, default_disk_io_constructor, f, ec);
}
void set_piece_hashes(create_torrent& t, std::string const& p
, settings_interface const& sett, disk_io_constructor_type disk_io
, std::function<void(piece_index_t)> const& f, error_code& ec)
{
// optimized path
#ifdef TORRENT_BUILD_SIMULATOR
sim::default_config conf;
sim::simulation sim{conf};
io_context ios{sim};
#else
io_context ios;
#endif
#if TORRENT_USE_UNC_PATHS
std::string const path = canonicalize_path(p);
#else
std::string const& path = p;
#endif
if (t.files().num_files() == 0)
{
ec = errors::no_files_in_torrent;
return;
}
if (t.files().total_size() == 0)
{
ec = errors::torrent_invalid_length;
return;
}
counters cnt;
int const num_threads = sett.get_int(settings_pack::hashing_threads);
std::unique_ptr<disk_interface> disk_thread = disk_io(ios, sett, cnt);
disk_aborter da(*disk_thread.get());
aux::vector<download_priority_t, file_index_t> priorities;
sha1_hash info_hash;
storage_params params{
t.files(),
nullptr,
path,
storage_mode_t::storage_mode_sparse,
priorities,
info_hash
};
storage_holder storage = disk_thread->new_torrent(params
, std::shared_ptr<void>());
// have 4 outstanding hash requests per thread, and no less than 1 MiB
int const jobs_per_thread = 4;
int const piece_read_ahead = std::max(num_threads * jobs_per_thread
, 1 * 1024 * 1024 / t.piece_length());
hash_state st = { t, std::move(storage), *disk_thread.get(), piece_index_t(0), piece_index_t(0), f, ec };
for (piece_index_t i(0); i < piece_index_t(piece_read_ahead); ++i)
{
aux::vector<sha256_hash> v2_blocks;
if (!t.is_v1_only())
v2_blocks.resize(t.piece_length() / default_block_size);
auto flags = disk_interface::sequential_access;
if (!t.is_v2_only()) flags |= disk_interface::v1_hash;
// the span needs to be created before the call to async_hash to ensure that
// it is constructed before the vector is moved into the bind context
span<sha256_hash> v2_span(v2_blocks);
disk_thread->async_hash(st.storage, i, v2_span, flags
, std::bind(&on_hash, std::move(v2_blocks), _1, _2, _3, &st));
++st.piece_counter;
if (st.piece_counter >= t.files().end_piece()) break;
}
disk_thread->submit_jobs();
#ifdef TORRENT_BUILD_SIMULATOR
sim.run();
#else
ios.run();
#endif
if (st.ec) {
ec = st.ec;
}
}
create_torrent::~create_torrent() = default;
create_torrent::create_torrent(file_storage& fs, int piece_size
, create_flags_t const flags)
: m_files(fs)
, m_creation_date(aux::posix_time())
, m_multifile(fs.num_files() > 1)
, m_private(false)
, m_include_mtime(bool(flags & create_torrent::modification_time))
, m_include_symlinks(bool(flags & create_torrent::symlinks))
, m_v2_only(bool(flags & create_torrent::v2_only))
, m_v1_only(bool(flags & create_torrent::v1_only))
{
// return instead of crash in release mode
if (fs.num_files() == 0 || fs.total_size() == 0) return;
if (!m_multifile && has_parent_path(m_files.file_path(file_index_t(0))))
m_multifile = true;
// a piece_size of 0 means automatic
if (piece_size == 0)
{
// size_table is computed from the following:
// target_list_size = sqrt(total_size) * 2;
// target_piece_size = total_size / (target_list_size / hash_size);
// Given hash_size = 20 bytes, target_piece_size = (16*1024 * pow(2, i))
// we can determine size_table = (total_size = pow(2 * target_piece_size / hash_size, 2))
std::array<std::int64_t, 10> const size_table{{
2684355LL // -> 16kiB
, 10737418LL // -> 32 kiB
, 42949673LL // -> 64 kiB
, 171798692LL // -> 128 kiB
, 687194767LL // -> 256 kiB
, 2748779069LL // -> 512 kiB
, 10995116278LL // -> 1 MiB
, 43980465111LL // -> 2 MiB
, 175921860444LL // -> 4 MiB
, 703687441777LL}}; // -> 8 MiB
int i = 0;
for (auto const s : size_table)
{
if (s >= fs.total_size()) break;
++i;
}
piece_size = default_block_size << i;
}
if (!(flags & v1_only))
{
// v2 torrents requires piece sizes to be at least 16 kiB
piece_size = std::max(piece_size, 16 * 1024);
// make sure the size is an even power of 2
// i.e. only a single bit is set. This is required by v2 torrents
if ((piece_size & (piece_size - 1)) != 0)
aux::throw_ex<system_error>(errors::invalid_piece_size);
}
else if ((piece_size % (16 * 1024)) != 0
&& (piece_size & (piece_size - 1)) != 0
&& !(flags & allow_odd_piece_size))
{
// v1 torrents should have piece sizes divisible by 16 kiB
aux::throw_ex<system_error>(errors::invalid_piece_size);
}
// this is an unreasonably large piece size. Some clients don't support
// pieces this large.
if (piece_size > 128 * 1024 * 1024) {
aux::throw_ex<system_error>(errors::invalid_piece_size);
}
fs.set_piece_length(piece_size);
if (!(flags & v1_only)
|| (flags & canonical_files)
|| (flags & canonical_files_no_tail_padding))
fs.canonicalize_impl(bool(flags & canonical_files_no_tail_padding));
fs.set_num_pieces(aux::calc_num_pieces(fs));
TORRENT_ASSERT(fs.piece_length() > 0);
}
create_torrent::create_torrent(torrent_info const& ti)
: m_files(ti.files())
, m_creation_date(aux::posix_time())
, m_multifile(ti.num_files() > 1)
, m_private(ti.priv())
, m_include_mtime(false)
, m_include_symlinks(false)
, m_v2_only(!ti.info_hashes().has_v1())
, m_v1_only(!ti.info_hashes().has_v2())
{
bool const make_v1 = ti.info_hashes().has_v1();
bool const make_v2 = ti.info_hashes().has_v2();
TORRENT_ASSERT_PRECOND(make_v2 || make_v1);
TORRENT_ASSERT_PRECOND(ti.is_valid());
TORRENT_ASSERT_PRECOND(ti.num_pieces() > 0);
TORRENT_ASSERT_PRECOND(ti.num_files() > 0);
TORRENT_ASSERT_PRECOND(ti.total_size() > 0);
if (!ti.is_valid()) return;
if (ti.creation_date() > 0) m_creation_date = ti.creation_date();
if (!ti.creator().empty()) set_creator(ti.creator().c_str());
if (!ti.comment().empty()) set_comment(ti.comment().c_str());
for (auto const& n : ti.nodes())
add_node(n);
for (auto const& t : ti.trackers())
add_tracker(t.url, t.tier);
for (auto const& s : ti.web_seeds())
{
if (s.type == web_seed_entry::url_seed)
add_url_seed(s.url);
else if (s.type == web_seed_entry::http_seed)
add_http_seed(s.url);
}
if (make_v1)
{
m_piece_hash.resize(m_files.num_pieces());
for (auto const i : m_files.piece_range())
set_hash(i, ti.hash_for_piece(i));
}
if (make_v2)
{
m_fileroots.resize(m_files.num_files());
m_file_piece_hash.resize(m_files.num_files());
for (auto const i : m_files.file_range())
{
// don't include merkle hash trees for pad files
if (m_files.pad_file_at(i)) continue;
if (m_files.file_size(i) == 0) continue;
auto const file_size = m_files.file_size(i);
if (file_size <= m_files.piece_length())
{
set_hash2(i, piece_index_t::diff_type{0}, m_files.root(i));
continue;
}
span<char const> pieces = ti.piece_layer(i);
piece_index_t::diff_type p{0};
for (int h = 0; h < int(pieces.size()); h += int(sha256_hash::size()))
set_hash2(i, p++, sha256_hash(pieces.data() + h));
}
}
auto const info = ti.info_section();
m_info_dict.preformatted().assign(info.data(), info.data() + info.size());
}
namespace {
bool validate_v2_hashes(file_storage const& fs
, aux::vector<aux::vector<sha256_hash, piece_index_t::diff_type>, file_index_t> const& file_piece_hash)
{
if (int(file_piece_hash.size()) != fs.num_files()) return false;
int const piece_size = fs.piece_length();
for (auto i : fs.file_range())
{
auto const& hashes = file_piece_hash[i];
// pad files are not supposed to have any hashes
if (fs.pad_file_at(i))
{
if (!hashes.empty()) return false;
continue;
}
if (int(hashes.size()) != (fs.file_size(i) + piece_size - 1) / piece_size) return false;
if (std::any_of(hashes.begin(), hashes.end(), [](sha256_hash const& h)
{ return h.is_all_zeros(); }))
{
return false;
}
}
return true;
}
bool validate_v1_hashes(file_storage const& fs
, aux::vector<sha1_hash, piece_index_t> piece_hash)
{
int const piece_size = fs.piece_length();
if (int(piece_hash.size()) != (fs.total_size() + piece_size - 1) / piece_size)
return false;
return !std::any_of(piece_hash.begin(), piece_hash.end()
, [](sha1_hash const& h) { return h.is_all_zeros(); });
}
void add_file_attrs(entry& e, file_flags_t const flags, bool const include_symlinks)
{
if (!(flags & (file_storage::flag_pad_file
| file_storage::flag_hidden
| file_storage::flag_executable
| file_storage::flag_symlink)))
{
return;
}
std::string& attr = e["attr"].string();
if (flags & file_storage::flag_pad_file) attr += 'p';
if (flags & file_storage::flag_hidden) attr += 'h';
if (flags & file_storage::flag_executable) attr += 'x';
if (include_symlinks && (flags & file_storage::flag_symlink)) attr += 'l';
}
void add_symlink_path(entry& e, std::string symlink_path)
{
entry& sympath_e = e["symlink path"];
std::string const link = lexically_relative("", symlink_path);
for (auto elems = lsplit_path(link); !elems.first.empty();
elems = lsplit_path(elems.second))
sympath_e.list().emplace_back(elems.first);
}
}
std::vector<char> create_torrent::generate_buf() const
{
// TODO: this can be optimized
std::vector<char> ret;
bencode(std::back_inserter(ret), generate());
return ret;
}
entry create_torrent::generate() const
{
if (m_files.num_files() == 0 || m_files.total_size() == 0)
aux::throw_ex<system_error>(errors::torrent_missing_file_tree);
// if all v2 hashes are set correctly, generate the v2 parts of the
// torrent
bool const make_v2 = validate_v2_hashes(m_files, m_file_piece_hash);
bool const make_v1 = validate_v1_hashes(m_files, m_piece_hash);
// if neither v1 nor v2 hashes were set, we can't create a torrent
if (!make_v1 && !make_v2)
aux::throw_ex<system_error>(errors::invalid_hash_entry);
TORRENT_ASSERT(m_files.piece_length() > 0);
entry dict;
if (!m_urls.empty()) dict["announce"] = m_urls.front().first;
if (!m_nodes.empty())
{
entry& nodes = dict["nodes"];
entry::list_type& nodes_list = nodes.list();
for (auto const& n : m_nodes)
{
entry::list_type node(2);
node[0] = n.first;
node[1] = n.second;
nodes_list.emplace_back(std::move(node));
}
}
if (m_urls.size() > 1)
{
entry::list_type trackers;
entry::list_type tier;
int current_tier = m_urls.front().second;
for (auto const& url : m_urls)
{
if (url.second != current_tier)
{
current_tier = url.second;
trackers.emplace_back(std::move(tier));
tier.clear();
}
tier.emplace_back(url.first);
}
trackers.emplace_back(std::move(tier));
dict["announce-list"] = std::move(trackers);
}
if (!m_comment.empty())
dict["comment"] = m_comment;
if (m_creation_date != 0)
dict["creation date"] = m_creation_date;
if (!m_created_by.empty())
dict["created by"] = m_created_by;
if (!m_url_seeds.empty())
{
if (m_url_seeds.size() == 1)
{
dict["url-list"] = m_url_seeds.front();
}
else
{
entry& list = dict["url-list"];
for (auto const& url : m_url_seeds)
{
list.list().emplace_back(url);
}
}
}
if (!m_http_seeds.empty())
{
if (m_http_seeds.size() == 1)
{
dict["httpseeds"] = m_http_seeds.front();
}
else
{
entry& list = dict["httpseeds"];
for (auto const& url : m_http_seeds)
{
list.list().emplace_back(url);
}
}
}
if (make_v2)
{
TORRENT_ASSERT(!m_file_piece_hash.empty());
m_fileroots.resize(m_files.num_files());
sha256_hash const pad_hash = merkle_pad(m_files.piece_length() / default_block_size, 1);
auto& file_pieces = dict["piece layers"].dict();
for (file_index_t fi : m_files.file_range())
{
if (files().file_flags(fi) & file_storage::flag_pad_file) continue;
if (files().file_size(fi) == 0) continue;
m_fileroots[fi] = merkle_root(m_file_piece_hash[fi], pad_hash);
// files that only have one piece store the piece hash as the
// root, we don't need a pieces layer entry for such files
if (m_file_piece_hash[fi].size() < 2) continue;
auto& pieces = file_pieces[m_fileroots[fi].to_string()].string();
pieces.clear();
pieces.reserve(m_file_piece_hash[fi].size() * sha256_hash::size());
for (auto& p : m_file_piece_hash[fi])
pieces.append(reinterpret_cast<const char*>(p.data()), p.size());
}
}
entry& info = dict["info"];
if (m_info_dict.type() == entry::dictionary_t
|| m_info_dict.type() == entry::preformatted_t)
{
info = m_info_dict;
return dict;
}
if (!m_collections.empty())
{
entry& list = info["collections"];
for (auto const& c : m_collections)
{
list.list().emplace_back(c);
}
}
if (!m_similar.empty())
{
entry& list = info["similar"];
for (auto const& ih : m_similar)
{
list.list().emplace_back(ih.to_string());
}
}
info["name"] = m_files.name();
if (!m_root_cert.empty())
info["ssl-cert"] = m_root_cert;
if (m_private) info["private"] = 1;
if (make_v1)
{
if (!m_multifile)
{
file_index_t const first(0);
if (m_include_mtime) info["mtime"] = m_files.mtime(first);
info["length"] = m_files.file_size(first);
file_flags_t const flags = m_files.file_flags(first);
add_file_attrs(info, flags, m_include_symlinks);
if (m_include_symlinks
&& (flags & file_storage::flag_symlink))
{
add_symlink_path(info, m_files.internal_symlink(first));
}
#if TORRENT_ABI_VERSION < 3
if (!m_filehashes.empty())
{
info["sha1"] = m_filehashes[first].to_string();
}
#endif
}
else
{
entry& files = info["files"];
for (auto const i : m_files.file_range())
{
files.list().emplace_back();
entry& file_e = files.list().back();
if (m_include_mtime && m_files.mtime(i)) file_e["mtime"] = m_files.mtime(i);
file_e["length"] = m_files.file_size(i);
TORRENT_ASSERT(has_parent_path(m_files.file_path(i)));
{
entry& path_e = file_e["path"];
std::string const p = m_files.file_path(i);
// deliberately skip the first path element, since that's the
// "name" of the torrent already
string_view path = lsplit_path(p).second;
for (auto elems = lsplit_path(path); !elems.first.empty(); elems = lsplit_path(elems.second))
path_e.list().emplace_back(elems.first);
}
file_flags_t const flags = m_files.file_flags(i);
add_file_attrs(file_e, flags, m_include_symlinks);
if (m_include_symlinks && (flags & file_storage::flag_symlink))
{
add_symlink_path(file_e, m_files.internal_symlink(i));
}
#if TORRENT_ABI_VERSION < 3
if (!m_filehashes.empty() && m_filehashes[i] != sha1_hash())
{
file_e["sha1"] = m_filehashes[i].to_string();
}
#endif
}
}
}
if (make_v2)
{
auto& tree = info["file tree"];
for (file_index_t i : m_files.file_range())
{
if (files().file_flags(i) & file_storage::flag_pad_file) continue;
entry* file_e_ptr = &tree;
{
std::string const file_path = m_files.file_path(i);
auto const split = m_multifile
? lsplit_path(file_path)
: std::pair<string_view, string_view>(file_path, file_path);
TORRENT_ASSERT(split.first == m_files.name());
for (auto e = lsplit_path(split.second);
!e.first.empty();
e = lsplit_path(e.second))
{
file_e_ptr = &(*file_e_ptr)[e.first];
if (file_e_ptr->dict().find({}) != file_e_ptr->dict().end())
{
// path conflict
// there is already a file with this name
// refuse to generate a torrent with such a conflict
aux::throw_ex<system_error>(errors::torrent_inconsistent_files);
}
}
}
if (!file_e_ptr->dict().empty())
{
// path conflict
// there is already a directory with this name
// refuse to generate a torrent with such a conflict
aux::throw_ex<system_error>(errors::torrent_inconsistent_files);
}
entry& file_e = (*file_e_ptr)[{}];
if (m_include_mtime && m_files.mtime(i)) file_e["mtime"] = m_files.mtime(i);
file_flags_t const flags = m_files.file_flags(i);
add_file_attrs(file_e, flags, m_include_symlinks);
if (m_include_symlinks && (flags & file_storage::flag_symlink))
{
add_symlink_path(file_e, m_files.internal_symlink(i));
}
else
{
if (m_files.file_size(i) > 0)
file_e["pieces root"] = m_fileroots[i];
file_e["length"] = m_files.file_size(i);
}
}
info["meta version"] = 2;
}
info["piece length"] = m_files.piece_length();
if (make_v1)
{
std::string& p = info["pieces"].string();
for (sha1_hash const& h : m_piece_hash)
p.append(h.data(), h.size());
}
return dict;
}
void create_torrent::add_tracker(string_view url, int const tier)
{
if (url.empty()) return;
using announce_entry = std::pair<std::string, int>;
auto const i = std::find_if(m_urls.begin(), m_urls.end()
, [&url](announce_entry const& ae) { return ae.first == url; });
if (i != m_urls.end()) return;
m_urls.emplace_back(url.to_string(), tier);
std::sort(m_urls.begin(), m_urls.end()
, [](announce_entry const& lhs, announce_entry const& rhs)
{ return lhs.second < rhs.second; });
}
void create_torrent::set_root_cert(string_view cert)
{
m_root_cert.assign(cert.data(), cert.size());
}
void create_torrent::add_similar_torrent(sha1_hash ih)
{
m_similar.emplace_back(ih);
}
void create_torrent::add_collection(string_view c)
{
m_collections.emplace_back(c);
}
void create_torrent::set_hash(piece_index_t index, sha1_hash const& h)
{
if (m_v2_only)
aux::throw_ex<system_error>(errors::invalid_hash_entry);
if (m_piece_hash.empty())
m_piece_hash.resize(m_files.num_pieces());
TORRENT_ASSERT_PRECOND(index >= piece_index_t(0));
TORRENT_ASSERT_PRECOND(index < m_piece_hash.end_index());
m_piece_hash[index] = h;
}
void create_torrent::set_hash2(file_index_t file, piece_index_t::diff_type piece, sha256_hash const& h)
{
TORRENT_ASSERT_PRECOND(file >= file_index_t(0));
TORRENT_ASSERT_PRECOND(file < m_files.end_file());
TORRENT_ASSERT_PRECOND(piece >= piece_index_t::diff_type(0));
TORRENT_ASSERT_PRECOND(piece < piece_index_t::diff_type(m_files.file_num_pieces(file)));
TORRENT_ASSERT_PRECOND(!m_files.pad_file_at(file));
TORRENT_ASSERT_PRECOND(!h.is_all_zeros());
TORRENT_ASSERT_PRECOND(m_files.file_num_pieces(file) > 0);
if (m_v1_only)
aux::throw_ex<system_error>(errors::invalid_hash_entry);
if (m_file_piece_hash.empty())
m_file_piece_hash.resize(m_files.num_files());
auto& fh = m_file_piece_hash[file];
if (fh.empty())
fh.resize(std::size_t(m_files.file_num_pieces(file)));
fh[piece] = h;
}
#if TORRENT_ABI_VERSION < 3
void create_torrent::set_file_hash(file_index_t index, sha1_hash const& h)
{
TORRENT_ASSERT(index >= file_index_t(0));
TORRENT_ASSERT(index < m_files.end_file());
if (m_filehashes.empty()) m_filehashes.resize(m_files.num_files());
m_filehashes[index] = h;
}
#endif
void create_torrent::add_node(std::pair<std::string, int> node)
{
m_nodes.emplace_back(std::move(node));
}
void create_torrent::add_url_seed(string_view url)
{
m_url_seeds.emplace_back(url);
}
void create_torrent::add_http_seed(string_view url)
{
m_http_seeds.emplace_back(url);
}
void create_torrent::set_comment(char const* str)
{
if (str == nullptr) m_comment.clear();
else m_comment = str;
}
void create_torrent::set_creator(char const* str)
{
if (str == nullptr) m_created_by.clear();
else m_created_by = str;
}
void create_torrent::set_creation_date(std::time_t timestamp)
{
m_creation_date = timestamp;
}
}