update symlinks to conform to BEP 47
This commit is contained in:
parent
5f85e40193
commit
57cd2882d4
|
@ -1,3 +1,4 @@
|
|||
* update symlinks to conform to BEP 47
|
||||
* fix python bindings for peer_info
|
||||
* support creating symlinks, for torrents with symlinks in them
|
||||
* fix error in seed_mode flag
|
||||
|
|
|
@ -156,6 +156,8 @@ namespace libtorrent {
|
|||
, string_view rhs);
|
||||
TORRENT_EXTRA_EXPORT void append_path(std::string& branch
|
||||
, string_view leaf);
|
||||
TORRENT_EXTRA_EXPORT std::string lexically_relative(string_view base
|
||||
, string_view target);
|
||||
|
||||
// internal used by create_torrent.hpp
|
||||
TORRENT_EXTRA_EXPORT std::string complete(string_view f);
|
||||
|
|
|
@ -37,6 +37,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <ctime>
|
||||
#include <cstdint>
|
||||
|
||||
|
@ -527,6 +528,8 @@ namespace libtorrent {
|
|||
// offset to add to any pointers to make them point into the new buffer
|
||||
void apply_pointer_offset(std::ptrdiff_t off);
|
||||
|
||||
void sanitize_symlinks();
|
||||
|
||||
private:
|
||||
|
||||
file_index_t last_file() const noexcept;
|
||||
|
@ -566,7 +569,7 @@ namespace libtorrent {
|
|||
// for files that are symlinks, the symlink
|
||||
// path_index in the internal_file_entry indexes
|
||||
// this vector of strings
|
||||
aux::vector<std::string, file_index_t> m_symlinks;
|
||||
std::vector<std::string> m_symlinks;
|
||||
|
||||
// the modification times of each file. This vector
|
||||
// is empty if no file have a modification time.
|
||||
|
|
|
@ -120,7 +120,7 @@ namespace {
|
|||
if ((file_flags & file_storage::flag_symlink)
|
||||
&& (flags & create_torrent::symlinks))
|
||||
{
|
||||
std::string sym_path = aux::get_symlink_path(f);
|
||||
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
|
||||
|
|
|
@ -667,7 +667,7 @@ namespace {
|
|||
TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file());
|
||||
internal_file_entry const& fe = m_files[index];
|
||||
TORRENT_ASSERT(fe.symlink_index < int(m_symlinks.size()));
|
||||
return m_symlinks[file_index_t(fe.symlink_index)];
|
||||
return m_symlinks[fe.symlink_index];
|
||||
}
|
||||
|
||||
std::time_t file_storage::mtime(file_index_t const index) const
|
||||
|
@ -884,7 +884,7 @@ namespace {
|
|||
std::string const& file_storage::symlink(internal_file_entry const& fe) const
|
||||
{
|
||||
TORRENT_ASSERT_PRECOND(fe.symlink_index < int(m_symlinks.size()));
|
||||
return m_symlinks[file_index_t(fe.symlink_index)];
|
||||
return m_symlinks[fe.symlink_index];
|
||||
}
|
||||
|
||||
std::time_t file_storage::mtime(internal_file_entry const& fe) const
|
||||
|
@ -1116,6 +1116,83 @@ namespace {
|
|||
if (index != cur_index) reorder_file(index, cur_index);
|
||||
}
|
||||
|
||||
void file_storage::sanitize_symlinks()
|
||||
{
|
||||
// symlinks are unusual, this function is optimized assuming there are no
|
||||
// symbolic links in the torrent. If we find one symbolic link, we'll
|
||||
// build the hash table of files it's allowed to refer to, but don't pay
|
||||
// that price up-front.
|
||||
std::unordered_map<std::string, file_index_t> file_map;
|
||||
bool file_map_initialized = false;
|
||||
|
||||
for (auto const i : file_range())
|
||||
{
|
||||
if (!(file_flags(i) & file_storage::flag_symlink)) continue;
|
||||
|
||||
if (!file_map_initialized)
|
||||
{
|
||||
for (auto const j : file_range()) file_map[file_path(j)] = j;
|
||||
file_map_initialized = true;
|
||||
}
|
||||
|
||||
internal_file_entry const& fe = m_files[i];
|
||||
TORRENT_ASSERT(fe.symlink_index < int(m_symlinks.size()));
|
||||
|
||||
// symlink targets are only allowed to point to files or directories in
|
||||
// this torrent.
|
||||
{
|
||||
std::string target = symlink(i);
|
||||
|
||||
// if it points to a directory, that's OK
|
||||
auto it = std::find(m_paths.begin(), m_paths.end(), target);
|
||||
if (it != m_paths.end())
|
||||
{
|
||||
m_symlinks[fe.symlink_index] = combine_path(name(), *it);
|
||||
continue;
|
||||
}
|
||||
|
||||
target = combine_path(name(), target);
|
||||
|
||||
auto const idx = file_map.find(target);
|
||||
if (idx != file_map.end())
|
||||
{
|
||||
m_symlinks[fe.symlink_index] = target;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// this symlink target points to a file that's not part of this torrent
|
||||
// file structure. That's not allowed by the spec.
|
||||
|
||||
// for backwards compatibility, allow paths relative to the link as
|
||||
// well
|
||||
if (fe.path_index >= 0)
|
||||
{
|
||||
std::string target = m_paths[fe.path_index];
|
||||
append_path(target, symlink(i));
|
||||
// if it points to a directory, that's OK
|
||||
auto it = std::find(m_paths.begin(), m_paths.end(), target);
|
||||
if (it != m_paths.end())
|
||||
{
|
||||
m_symlinks[fe.symlink_index] = combine_path(name(), *it);
|
||||
continue;
|
||||
}
|
||||
|
||||
target = combine_path(name(), target);
|
||||
auto const idx = file_map.find(target);
|
||||
if (idx != file_map.end())
|
||||
{
|
||||
m_symlinks[fe.symlink_index] = target;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// this symlink is invalid, make it point to itself
|
||||
m_symlinks[fe.symlink_index] = file_path(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace aux {
|
||||
|
||||
std::tuple<piece_index_t, piece_index_t>
|
||||
|
|
41
src/path.cpp
41
src/path.cpp
|
@ -737,6 +737,47 @@ namespace {
|
|||
return ret;
|
||||
}
|
||||
|
||||
std::string lexically_relative(string_view base, string_view target)
|
||||
{
|
||||
// first, strip trailing directory separators
|
||||
if (!base.empty() && base.back() == TORRENT_SEPARATOR_CHAR)
|
||||
base.remove_suffix(1);
|
||||
if (!target.empty() && target.back() == TORRENT_SEPARATOR_CHAR)
|
||||
target.remove_suffix(1);
|
||||
|
||||
// strip common path elements
|
||||
for (;;)
|
||||
{
|
||||
if (base.empty()) break;
|
||||
string_view prev_base = base;
|
||||
string_view prev_target = target;
|
||||
|
||||
string_view base_element;
|
||||
string_view target_element;
|
||||
std::tie(base_element, base) = split_string(base, TORRENT_SEPARATOR_CHAR);
|
||||
std::tie(target_element, target) = split_string(target, TORRENT_SEPARATOR_CHAR);
|
||||
if (base_element == target_element) continue;
|
||||
|
||||
base = prev_base;
|
||||
target = prev_target;
|
||||
break;
|
||||
}
|
||||
|
||||
// count number of path elements left in base, and prepend that number of
|
||||
// "../" to target
|
||||
|
||||
// base alwaus points to a directory. There's an implied directory
|
||||
// separator at the end of it
|
||||
int const num_steps = static_cast<int>(std::count(
|
||||
base.begin(), base.end(), TORRENT_SEPARATOR_CHAR)) + (base.empty() ? 0 : 1);
|
||||
std::string ret;
|
||||
for (int i = 0; i < num_steps; ++i)
|
||||
ret += ".." TORRENT_SEPARATOR;
|
||||
|
||||
ret += target.to_string();
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string current_working_directory()
|
||||
{
|
||||
#if defined TORRENT_WINDOWS
|
||||
|
|
|
@ -78,6 +78,16 @@ namespace libtorrent {
|
|||
std::int64_t stat_cache::get_filesize(file_index_t const i, file_storage const& fs
|
||||
, std::string const& save_path, error_code& ec)
|
||||
{
|
||||
// always pretend symlinks don't exist, to trigger special logic for
|
||||
// creating and possibly validating them. There's a risk we'll and up in a
|
||||
// cycle of references here otherwise.
|
||||
// Should stat_file() be changed to use lstat()?
|
||||
if (fs.file_flags(i) & file_storage::flag_symlink)
|
||||
{
|
||||
ec.assign(boost::system::errc::no_such_file_or_directory, boost::system::system_category());
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> l(m_mutex);
|
||||
TORRENT_ASSERT(i < fs.end_file());
|
||||
if (i >= m_stat_cache.end_index()) m_stat_cache.resize(static_cast<int>(i) + 1
|
||||
|
|
|
@ -298,7 +298,6 @@ namespace libtorrent {
|
|||
break;
|
||||
}
|
||||
|
||||
|
||||
// if the file is empty and doesn't already exist, create it
|
||||
// deliberately don't truncate files that already exist
|
||||
// if a file is supposed to have size 0, but already exists, we will
|
||||
|
@ -326,13 +325,35 @@ namespace libtorrent {
|
|||
// create symlinks
|
||||
if (fs.file_flags(file_index) & file_storage::flag_symlink)
|
||||
{
|
||||
if (::symlink(fs.symlink(file_index).c_str()
|
||||
, fs.file_path(file_index, m_save_path).c_str()) != 0)
|
||||
// we make the symlink target relative to the link itself
|
||||
std::string const target = lexically_relative(
|
||||
parent_path(fs.file_path(file_index)), fs.symlink(file_index));
|
||||
std::string const link = fs.file_path(file_index, m_save_path);
|
||||
if (::symlink(target.c_str(), link.c_str()) != 0)
|
||||
{
|
||||
ec.ec = error_code(errno, generic_category());
|
||||
ec.file(file_index);
|
||||
ec.operation = operation_t::symlink;
|
||||
break;
|
||||
int const error = errno;
|
||||
if (error == EEXIST)
|
||||
{
|
||||
// if the file exist, it may be a symlink already. if so,
|
||||
// just verify the link target is what it's supposed to be
|
||||
// note that readlink() does not null terminate the buffer
|
||||
char buffer[512];
|
||||
auto const ret = ::readlink(link.c_str(), buffer, sizeof(buffer));
|
||||
if (ret <= 0 || target != string_view(buffer, std::size_t(ret)))
|
||||
{
|
||||
ec.ec = error_code(error, generic_category());
|
||||
ec.file(file_index);
|
||||
ec.operation = operation_t::symlink;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ec.ec = error_code(error, generic_category());
|
||||
ec.file(file_index);
|
||||
ec.operation = operation_t::symlink;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -488,9 +488,12 @@ namespace {
|
|||
sanitize_append_path_element(symlink_path, pe);
|
||||
}
|
||||
}
|
||||
// symlink targets are validated later, as it may point to a file or
|
||||
// directory we haven't parsed yet
|
||||
}
|
||||
else
|
||||
{
|
||||
// technically this is an invalid torrent. "symlink path" must exist
|
||||
file_flags &= ~file_storage::flag_symlink;
|
||||
}
|
||||
|
||||
|
@ -526,6 +529,8 @@ namespace {
|
|||
, info_ptr_diff, false, pad_file_cnt, ec))
|
||||
return false;
|
||||
}
|
||||
// this rewrites invalid symlinks to point to themselves
|
||||
target.sanitize_symlinks();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1006,6 +1011,7 @@ namespace {
|
|||
return false;
|
||||
}
|
||||
|
||||
files.sanitize_symlinks();
|
||||
m_flags &= ~multifile;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -415,6 +415,46 @@ TORRENT_TEST(stat_file)
|
|||
TEST_EQUAL(ec, boost::system::errc::no_such_file_or_directory);
|
||||
}
|
||||
|
||||
TORRENT_TEST(relative_path)
|
||||
{
|
||||
#ifdef TORRENT_WINDOWS
|
||||
#define S "\\"
|
||||
#else
|
||||
#define S "/"
|
||||
#endif
|
||||
TEST_EQUAL(lexically_relative("A" S "B" S "C", "A" S "C" S "B")
|
||||
, ".." S ".." S "C" S "B");
|
||||
|
||||
TEST_EQUAL(lexically_relative("A" S "B" S "C" S, "A" S "C" S "B")
|
||||
, ".." S ".." S "C" S "B");
|
||||
|
||||
TEST_EQUAL(lexically_relative("A" S "B" S "C" S, "A" S "C" S "B" S)
|
||||
, ".." S ".." S "C" S "B");
|
||||
|
||||
TEST_EQUAL(lexically_relative("A" S "B" S "C", "A" S "B" S "B")
|
||||
, ".." S "B");
|
||||
|
||||
TEST_EQUAL(lexically_relative("A" S "B" S "C", "A" S "B" S "C")
|
||||
, "");
|
||||
|
||||
TEST_EQUAL(lexically_relative("A" S "B", "A" S "B")
|
||||
, "");
|
||||
|
||||
TEST_EQUAL(lexically_relative("A" S "B", "A" S "B" S "C")
|
||||
, "C");
|
||||
|
||||
TEST_EQUAL(lexically_relative("A" S, "A" S)
|
||||
, "");
|
||||
|
||||
TEST_EQUAL(lexically_relative("", "A" S "B" S "C")
|
||||
, "A" S "B" S "C");
|
||||
|
||||
TEST_EQUAL(lexically_relative("A" S "B" S "C", "")
|
||||
, ".." S ".." S ".." S);
|
||||
|
||||
TEST_EQUAL(lexically_relative("", ""), "");
|
||||
}
|
||||
|
||||
// UNC tests
|
||||
#if TORRENT_USE_UNC_PATHS
|
||||
|
||||
|
@ -570,4 +610,5 @@ TORRENT_TEST(unc_paths)
|
|||
remove(reserved_name, ec);
|
||||
TEST_CHECK(!ec);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -126,6 +126,7 @@ static test_torrent_t test_torrents[] =
|
|||
{ "invalid_name2.torrent" },
|
||||
{ "invalid_name3.torrent" },
|
||||
{ "symlink1.torrent" },
|
||||
{ "symlink2.torrent" },
|
||||
{ "unordered.torrent" },
|
||||
{ "symlink_zero_size.torrent" },
|
||||
{ "pad_file_no_path.torrent" },
|
||||
|
@ -795,6 +796,17 @@ TORRENT_TEST(parse_torrents)
|
|||
TEST_EQUAL(ti->name(), "foobar ");
|
||||
#endif
|
||||
}
|
||||
else if (t.file == "symlink1.torrent"_sv)
|
||||
{
|
||||
TEST_EQUAL(ti->num_files(), 2);
|
||||
TEST_EQUAL(ti->files().symlink(file_index_t{1}), "temp" SEPARATOR "a" SEPARATOR "b" SEPARATOR "bar");
|
||||
}
|
||||
else if (t.file == "symlink2.torrent"_sv)
|
||||
{
|
||||
TEST_EQUAL(ti->num_files(), 5);
|
||||
TEST_EQUAL(ti->files().symlink(file_index_t{0}), "Some.framework" SEPARATOR "Versions" SEPARATOR "A" SEPARATOR "SDL2");
|
||||
TEST_EQUAL(ti->files().symlink(file_index_t{4}), "Some.framework" SEPARATOR "Versions" SEPARATOR "A");
|
||||
}
|
||||
else if (t.file == "slash_path.torrent"_sv)
|
||||
{
|
||||
TEST_EQUAL(ti->num_files(), 1);
|
||||
|
@ -813,7 +825,7 @@ TORRENT_TEST(parse_torrents)
|
|||
else if (t.file == "symlink_zero_size.torrent"_sv)
|
||||
{
|
||||
TEST_EQUAL(ti->num_files(), 2);
|
||||
TEST_EQUAL(ti->files().symlink(file_index_t(1)), combine_path("foo", "bar"));
|
||||
TEST_EQUAL(ti->files().symlink(file_index_t(1)), "temp" SEPARATOR "a" SEPARATOR "b" SEPARATOR "bar");
|
||||
}
|
||||
else if (t.file == "pad_file_no_path.torrent"_sv)
|
||||
{
|
||||
|
|
|
@ -1 +1 @@
|
|||
d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl1:a1:b3:bareed4:attr1:l6:lengthi425e4:pathl1:a1:b3:fooe12:symlink pathl3:foo3:bareee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee
|
||||
d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl1:a1:b3:bareed4:attr1:l6:lengthi425e4:pathl1:a1:b3:fooe12:symlink pathl1:a1:b3:bareee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee
|
||||
|
|
|
@ -1 +1 @@
|
|||
d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl1:a1:b3:bareed4:attr1:l4:pathl1:a1:b3:fooe12:symlink pathl3:foo3:bareee4:name4:temp12:piece lengthi16384e6:pieces20:aaaaaaaaaaaaaaaaaaaaee
|
||||
d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl1:a1:b3:bareed4:attr1:l4:pathl1:a1:b3:fooe12:symlink pathl1:a1:b3:bareee4:name4:temp12:piece lengthi16384e6:pieces20:aaaaaaaaaaaaaaaaaaaaee
|
||||
|
|
Loading…
Reference in New Issue