From 381d5a3c5d170c7e2cbcfe231c8e6108391327d7 Mon Sep 17 00:00:00 2001 From: arvidn Date: Tue, 26 Feb 2019 14:44:16 +0100 Subject: [PATCH] add support for creating symlinks, for torrents with symlinks in them --- ChangeLog | 1 + bindings/python/src/alert.cpp | 1 + include/libtorrent/config.hpp | 8 ++++ include/libtorrent/create_torrent.hpp | 2 + include/libtorrent/operations.hpp | 1 + src/alert.cpp | 4 +- src/create_torrent.cpp | 65 ++++++++++++++------------- src/storage.cpp | 38 ++++++++++++---- test/test_torrent.cpp | 23 ++++++++++ test/test_torrents/symlink2.torrent | 1 + 10 files changed, 103 insertions(+), 41 deletions(-) create mode 100755 test/test_torrents/symlink2.torrent diff --git a/ChangeLog b/ChangeLog index 40b689895..dfdfbe3f2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * support creating symlinks, for torrents with symlinks in them * fix error in seed_mode flag * support magnet link parameters with number siffixes * consistently use "lt" namespace in examples and documentation diff --git a/bindings/python/src/alert.cpp b/bindings/python/src/alert.cpp index 2e3580299..b5e8d771f 100644 --- a/bindings/python/src/alert.cpp +++ b/bindings/python/src/alert.cpp @@ -345,6 +345,7 @@ void bind_alert() .value("partfile_read", operation_t::partfile_read) .value("partfile_write", operation_t::partfile_write) .value("hostname_lookup", operation_t::hostname_lookup) + .value("symlink", operation_t::symlink) ; def("operation_name", static_cast(<::operation_name)); diff --git a/include/libtorrent/config.hpp b/include/libtorrent/config.hpp index 3bbec20e0..d4112a9e9 100644 --- a/include/libtorrent/config.hpp +++ b/include/libtorrent/config.hpp @@ -166,6 +166,7 @@ POSSIBILITY OF SUCH DAMAGE. #endif #endif // __APPLE__ +#define TORRENT_HAS_SYMLINK 1 #define TORRENT_USE_DEV_RANDOM 1 #define TORRENT_HAVE_MMAP 1 @@ -188,6 +189,7 @@ POSSIBILITY OF SUCH DAMAGE. # define TORRENT_USE_PREAD 1 #endif +#define TORRENT_HAS_SYMLINK 1 #define TORRENT_HAVE_MMAP 1 #define TORRENT_USE_NETLINK 1 #define TORRENT_USE_IFADDRS 0 @@ -308,6 +310,7 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_HAS_SALEN 0 #define TORRENT_HAS_SEM_RELTIMEDWAIT 1 #define TORRENT_HAVE_MMAP 1 +#define TORRENT_HAS_SYMLINK 1 // ==== BEOS === #elif defined __BEOS__ || defined __HAIKU__ @@ -323,6 +326,7 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_HURD #define TORRENT_USE_IFADDRS 1 #define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SYMLINK 1 // ==== eCS(OS/2) === #elif defined __OS2__ @@ -505,6 +509,10 @@ constexpr std::size_t TORRENT_WRITE_HANDLER_MAX_SIZE = 342; # endif #endif +#ifndef TORRENT_HAS_SYMLINK +#define TORRENT_HAS_SYMLINK 0 +#endif + // debug builds have asserts enabled by default, release // builds have asserts if they are explicitly enabled by // the release_asserts macro. diff --git a/include/libtorrent/create_torrent.hpp b/include/libtorrent/create_torrent.hpp index ad1cd9411..6ace097bb 100644 --- a/include/libtorrent/create_torrent.hpp +++ b/include/libtorrent/create_torrent.hpp @@ -465,6 +465,8 @@ namespace detail { } #endif // TORRENT_ABI_VERSION + TORRENT_EXTRA_EXPORT file_flags_t get_file_attributes(std::string const& p); + TORRENT_EXTRA_EXPORT std::string get_symlink_path(std::string const& p); } #endif diff --git a/include/libtorrent/operations.hpp b/include/libtorrent/operations.hpp index 808ae0e88..027e401bb 100644 --- a/include/libtorrent/operations.hpp +++ b/include/libtorrent/operations.hpp @@ -134,6 +134,7 @@ namespace libtorrent { partfile_read, partfile_write, hostname_lookup, + symlink, }; // maps an operation id (from peer_error_alert and peer_disconnected_alert) diff --git a/src/alert.cpp b/src/alert.cpp index 3955fba48..cef0a290d 100644 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -894,6 +894,7 @@ namespace { case o::partfile_read: return -1; case o::partfile_write: return -1; case o::hostname_lookup: return -1; + case o::symlink: return -1; }; return -1; } @@ -1558,7 +1559,8 @@ namespace { "partfile_move", "partfile_read", "partfile_write", - "hostname_lookup" + "hostname_lookup", + "symlink" }; int const idx = static_cast(op); diff --git a/src/create_torrent.cpp b/src/create_torrent.cpp index 5305345cf..c11250716 100644 --- a/src/create_torrent.cpp +++ b/src/create_torrent.cpp @@ -66,28 +66,6 @@ namespace { bool ignore_subdir(std::string const& leaf) { return leaf == ".." || leaf == "."; } - 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 - } - #ifndef TORRENT_WINDOWS std::string get_symlink_path_impl(char const* path) { @@ -103,16 +81,6 @@ namespace { } #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 - } - void add_files_impl(file_storage& fs, std::string const& p , std::string const& l, std::function const& pred , create_flags_t const flags) @@ -202,6 +170,39 @@ namespace { } // anonymous namespace + 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 + } + + #if TORRENT_ABI_VERSION == 1 void add_files(file_storage& fs, std::wstring const& wfile diff --git a/src/storage.cpp b/src/storage.cpp index c19f4a13d..578982372 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -62,6 +62,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #endif +#if TORRENT_HAS_SYMLINK +#include // for symlink() +#endif + #include "libtorrent/aux_/disable_warnings_pop.hpp" #include "libtorrent/storage.hpp" @@ -294,6 +298,7 @@ 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 @@ -301,8 +306,7 @@ namespace libtorrent { if (fs.file_size(file_index) == 0 && err == boost::system::errc::no_such_file_or_directory) { - std::string file_path = fs.file_path(file_index, m_save_path); - std::string dir = parent_path(file_path); + std::string dir = parent_path(fs.file_path(file_index, m_save_path)); if (dir != last_path) { @@ -317,12 +321,30 @@ namespace libtorrent { } } ec.ec.clear(); - // just creating the file is enough to make it zero-sized. If - // there's a race here and some other process truncates the file, - // it's not a problem, we won't access empty files ever again - file_handle f = open_file(file_index, open_mode::read_write - | open_mode::random_access, ec); - if (ec) return; + +#if TORRENT_HAS_SYMLINK + // 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) + { + ec.ec = error_code(errno, generic_category()); + ec.file(file_index); + ec.operation = operation_t::symlink; + break; + } + } + else +#endif + { + // just creating the file is enough to make it zero-sized. If + // there's a race here and some other process truncates the file, + // it's not a problem, we won't access empty files ever again + file_handle f = open_file(file_index, open_mode::read_write + | open_mode::random_access, ec); + if (ec) return; + } } ec.ec.clear(); } diff --git a/test/test_torrent.cpp b/test/test_torrent.cpp index c239122d1..b8b4f3cdb 100644 --- a/test/test_torrent.cpp +++ b/test/test_torrent.cpp @@ -739,3 +739,26 @@ TORRENT_TEST(test_calc_bytes_all_pieces_two_pad) auto const fs = test_fs(); TEST_EQUAL(calc_bytes(fs, piece_count{fs.num_pieces(), 2, true}), fs.total_size() - 2 * 0x4000); } + +#if TORRENT_HAS_SYMLINK +TORRENT_TEST(symlinks_restore) +{ + // downloading test torrent with symlinks + std::string const work_dir = current_working_directory(); + lt::add_torrent_params p; + p.ti = std::make_shared(combine_path( + combine_path(parent_path(work_dir), "test_torrents"), "symlink2.torrent")); + p.flags &= ~lt::torrent_flags::paused; + p.save_path = work_dir; + settings_pack pack = settings(); + pack.set_int(libtorrent::settings_pack::alert_mask, libtorrent::alert::status_notification | libtorrent::alert::error_notification); + lt::session ses(std::move(pack)); + ses.add_torrent(p); + + wait_for_alert(ses, torrent_checked_alert::alert_type, "torrent_checked_alert"); + + std::string const f = combine_path(combine_path(work_dir, "Some.framework"), "SDL2"); + TEST_CHECK(get_file_attributes(f) & file_storage::flag_symlink); + TEST_CHECK(get_symlink_path(f) == "Versions/A/SDL2"); +} +#endif diff --git a/test/test_torrents/symlink2.torrent b/test/test_torrents/symlink2.torrent new file mode 100755 index 000000000..c54ef8989 --- /dev/null +++ b/test/test_torrents/symlink2.torrent @@ -0,0 +1 @@ +d7:comment13:Symlinks test4:infod5:filesld4:attr2:xl6:lengthi0e4:pathl4:SDL2e12:symlink pathl8:Versions1:A4:SDL2eed6:lengthi2514e4:pathl8:Versions1:A14:_CodeSignature13:CodeResourceseed6:lengthi1310e4:pathl8:Versions1:A9:Resources10:Info.plisteed6:lengthi0e4:pathl8:Versions1:A4:SDL2eed4:attr2:xl6:lengthi0e4:pathl8:Versions7:Currente12:symlink pathl1:Aeee4:name14:Some.framework12:piece lengthi16384e6:pieces20:¤ÎIÝ¡Ú‹