From 9bee2146d0ee881ed94b6387bcb1a1c12987fdf6 Mon Sep 17 00:00:00 2001 From: Andrei Kurushin Date: Fri, 17 Feb 2017 07:47:08 +0300 Subject: [PATCH] complete UNC path support (#1689) complete UNC path support. all internal file api functions handle long size paths (> MAX_PATH) and special names (CON,PRN ...) with unit test --- ChangeLog | 1 + include/libtorrent/file.hpp | 19 ++++ src/alert.cpp | 31 +++--- src/escape_string.cpp | 30 ++++-- src/file.cpp | 203 ++++++++++++++++++------------------ test/test_file.cpp | 127 ++++++++++++++++++++-- 6 files changed, 284 insertions(+), 127 deletions(-) diff --git a/ChangeLog b/ChangeLog index e04d7a4e4..bcf909216 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * complete UNC path support * add packets pool allocator * fix last_upload and last_download overflow after 9 hours in past * python binding add more add_torrent_params fields and an invalid key check diff --git a/include/libtorrent/file.hpp b/include/libtorrent/file.hpp index 8a641882c..742a9b25e 100644 --- a/include/libtorrent/file.hpp +++ b/include/libtorrent/file.hpp @@ -176,6 +176,25 @@ namespace libtorrent TORRENT_EXTRA_EXPORT std::string canonicalize_path(string_view f); #endif +// internal type alias export should be used at unit tests only + using native_path_string = +#if TORRENT_USE_WSTRING && defined TORRENT_WINDOWS + std::wstring; +#else + std::string; +#endif + +// internal export should be used at unit tests only + TORRENT_EXTRA_EXPORT native_path_string convert_to_native_path_string(std::string const& path); + +// internal export should be used at unit tests only + TORRENT_EXTRA_EXPORT std::string convert_from_native_path(char const* s); + +#if defined TORRENT_WINDOWS && TORRENT_USE_WSTRING +// internal export should be used at unit tests only + TORRENT_EXTRA_EXPORT std::string convert_from_native_path(wchar_t const* s); +#endif + // TODO: move this into a separate header file, TU pair class TORRENT_EXTRA_EXPORT directory : public boost::noncopyable { diff --git a/src/alert.cpp b/src/alert.cpp index 2c163fab6..07e8fdc84 100644 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -177,10 +177,12 @@ namespace libtorrent std::string file_completed_alert::message() const { - char msg[200 + TORRENT_MAX_PATH]; - std::snprintf(msg, sizeof(msg), "%s: file %d finished downloading" - , torrent_alert::message().c_str(), static_cast(index)); - return msg; + std::string ret { torrent_alert::message() }; + char msg[200]; + std::snprintf(msg, sizeof(msg), ": file %d finished downloading" + , static_cast(index)); + ret.append(msg); + return ret; } file_renamed_alert::file_renamed_alert(aux::stack_allocator& alloc @@ -200,10 +202,13 @@ namespace libtorrent std::string file_renamed_alert::message() const { - char msg[200 + TORRENT_MAX_PATH * 2]; - std::snprintf(msg, sizeof(msg), "%s: file %d renamed to %s" - , torrent_alert::message().c_str(), static_cast(index), new_name()); - return msg; + std::string ret { torrent_alert::message() }; + char msg[200]; + std::snprintf(msg, sizeof(msg), ": file %d renamed to " + , static_cast(index)); + ret.append(msg); + ret.append(new_name()); + return ret; } file_rename_failed_alert::file_rename_failed_alert(aux::stack_allocator& alloc @@ -217,10 +222,12 @@ namespace libtorrent std::string file_rename_failed_alert::message() const { - char ret[200 + TORRENT_MAX_PATH * 2]; - std::snprintf(ret, sizeof(ret), "%s: failed to rename file %d: %s" - , torrent_alert::message().c_str(), static_cast(index) - , convert_from_native(error.message()).c_str()); + std::string ret { torrent_alert::message() }; + char msg[200]; + std::snprintf(msg, sizeof(msg), ": failed to rename file %d: " + , static_cast(index)); + ret.append(msg); + ret.append(convert_from_native(error.message())); return ret; } diff --git a/src/escape_string.cpp b/src/escape_string.cpp index e2bdf6544..cbc292cee 100644 --- a/src/escape_string.cpp +++ b/src/escape_string.cpp @@ -231,12 +231,30 @@ namespace libtorrent if (!need_encoding(path.c_str(), int(path.size()))) return url; - char msg[TORRENT_MAX_PATH*4]; - std::snprintf(msg, sizeof(msg), "%s://%s%s%s%s%s%s", protocol.c_str(), auth.c_str() - , auth.empty() ? "" : "@", host.c_str() - , port == -1 ? "" : ":" - , port == -1 ? "" : to_string(port).data() - , escape_path(path).c_str()); + std::string msg; + std::string escaped_path { escape_path(path) }; + // reserve enough space so further append will + // only copy values to existing location + msg.reserve(protocol.size() + 3 + // protocol part + auth.size() + 1 + // auth part + host.size() + // host part + 1 + 5 + // port part + escaped_path.size()); + msg.append(protocol); + msg.append("://"); + if (!auth.empty()) + { + msg.append(auth); + msg.append("@"); + } + msg.append(host); + if (port != -1) + { + msg.append(":"); + msg.append(to_string(port).data()); + } + msg.append(escaped_path); + return msg; } diff --git a/src/file.cpp b/src/file.cpp index 9a9f5e0dd..3ecf8f3c5 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -76,6 +76,7 @@ POSSIBILITY OF SUCH DAMAGE. // for convert_to_wstring and convert_to_native #include "libtorrent/aux_/escape_string.hpp" #include "libtorrent/assert.hpp" +#include "libtorrent/aux_/throw.hpp" #include "libtorrent/aux_/disable_warnings_push.hpp" @@ -336,14 +337,19 @@ namespace libtorrent return int(size); } -#ifdef TORRENT_WINDOWS - std::string convert_separators(std::string p) + std::string convert_from_native_path(char const* s) { return convert_from_native(s); } + +#if defined TORRENT_WINDOWS && TORRENT_USE_WSTRING + std::string convert_from_native_path(wchar_t const* s) { return convert_from_wstring(s); } +#endif + + template + std::unique_ptr make_free_holder(T* ptr) { - for (int i = 0; i < int(p.size()); ++i) - if (p[i] == '/') p[i] = '\\'; - return p; + return std::unique_ptr(ptr, &std::free); } +#ifdef TORRENT_WINDOWS time_t file_time_to_posix(FILETIME f) { const std::uint64_t posix_time_offset = 11644473600LL; @@ -356,34 +362,55 @@ namespace libtorrent } #endif + native_path_string convert_to_native_path_string(std::string const& path) + { +#ifdef TORRENT_WINDOWS +#if TORRENT_USE_UNC_PATHS + std::string prepared_path; + // UNC paths must be absolute + // network paths are already UNC paths + if (path.substr(0,2) == "\\\\") + prepared_path = path; + else + { + std::string sep_path { path }; + std::replace(sep_path.begin(), sep_path.end(), '/', '\\'); + prepared_path = "\\\\?\\" + (is_complete(sep_path) ? sep_path : combine_path(current_working_directory(), sep_path)); + } +#else + std::string prepared_path { path }; + std::replace(prepared_path.begin(), prepared_path.end(), '/', '\\'); +#endif + +#if TORRENT_USE_WSTRING + return convert_to_wstring(prepared_path); +#else + return convert_to_native(prepared_path); +#endif +#else // TORRENT_WINDOWS + return convert_to_native(path); +#endif + } + void stat_file(std::string const& inf, file_status* s , error_code& ec, int const flags) { ec.clear(); + native_path_string f = convert_to_native_path_string(inf); #ifdef TORRENT_WINDOWS TORRENT_UNUSED(flags); - std::string p = convert_separators(inf); -#if TORRENT_USE_UNC_PATHS - // UNC paths must be absolute - // network paths are already UNC paths - if (inf.substr(0,2) == "\\\\") p = inf; - else p = "\\\\?\\" + (is_complete(p) ? p : combine_path(current_working_directory(), p)); -#endif - #if TORRENT_USE_WSTRING #define CreateFile_ CreateFileW - std::wstring f = convert_to_wstring(p); #else #define CreateFile_ CreateFileA - std::string f = convert_to_native(p); #endif // in order to open a directory, we need the FILE_FLAG_BACKUP_SEMANTICS HANDLE h = CreateFile_(f.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); - +#undef CreateFile_ if (h == INVALID_HANDLE_VALUE) { ec.assign(GetLastError(), system_category()); @@ -414,8 +441,6 @@ namespace libtorrent // posix version - std::string const& f = convert_to_native(inf); - struct stat ret; int retval; if (flags & dont_follow_links) @@ -448,19 +473,20 @@ namespace libtorrent { ec.clear(); + native_path_string f1 = convert_to_native_path_string(inf); + native_path_string f2 = convert_to_native_path_string(newf); + #if TORRENT_USE_WSTRING && defined TORRENT_WINDOWS - std::wstring f1 = convert_to_wstring(inf); - std::wstring f2 = convert_to_wstring(newf); - if (_wrename(f1.c_str(), f2.c_str()) < 0) +#define RenameFunction_ _wrename #else - std::string const& f1 = convert_to_native(inf); - std::string const& f2 = convert_to_native(newf); - if (::rename(f1.c_str(), f2.c_str()) < 0) +#define RenameFunction_ ::rename #endif + + if (RenameFunction_(f1.c_str(), f2.c_str()) < 0) { ec.assign(errno, generic_category()); - return; } +#undef RenameFunction_ } void create_directories(std::string const& f, error_code& ec) @@ -483,20 +509,19 @@ namespace libtorrent { ec.clear(); + native_path_string n = convert_to_native_path_string(f); #ifdef TORRENT_WINDOWS #if TORRENT_USE_WSTRING #define CreateDirectory_ CreateDirectoryW - std::wstring n = convert_to_wstring(f); #else #define CreateDirectory_ CreateDirectoryA - std::string const& n = convert_to_native(f); #endif // TORRENT_USE_WSTRING if (CreateDirectory_(n.c_str(), 0) == 0 && GetLastError() != ERROR_ALREADY_EXISTS) ec.assign(GetLastError(), system_category()); +#undef CreateDirectory_ #else - std::string n = convert_to_native(f); int ret = ::mkdir(n.c_str(), 0777); if (ret < 0 && errno != EEXIST) ec.assign(errno, system_category()); @@ -506,16 +531,14 @@ namespace libtorrent void hard_link(std::string const& file, std::string const& link , error_code& ec) { + native_path_string n_exist = convert_to_native_path_string(file); + native_path_string n_link = convert_to_native_path_string(link); #ifdef TORRENT_WINDOWS #if TORRENT_USE_WSTRING #define CreateHardLink_ CreateHardLinkW - std::wstring n_exist = convert_to_wstring(file); - std::wstring n_link = convert_to_wstring(link); #else #define CreateHardLink_ CreateHardLinkA - std::string n_exist = convert_to_native(file); - std::string n_link = convert_to_native(link); #endif BOOL ret = CreateHardLink_(n_link.c_str(), n_exist.c_str(), nullptr); if (ret) @@ -523,7 +546,7 @@ namespace libtorrent ec.clear(); return; } - +#undef CreateHardLink_ // something failed. Does the filesystem not support hard links? // TODO: 3 find out what error code is reported when the filesystem // does not support hard links. @@ -539,10 +562,6 @@ namespace libtorrent // fall back to making a copy #else - - std::string n_exist = convert_to_native(file); - std::string n_link = convert_to_native(link); - // assume posix's link() function exists int ret = ::link(n_exist.c_str(), n_link.c_str()); @@ -605,32 +624,27 @@ namespace libtorrent void copy_file(std::string const& inf, std::string const& newf, error_code& ec) { ec.clear(); + native_path_string f1 = convert_to_native_path_string(inf); + native_path_string f2 = convert_to_native_path_string(newf); + #ifdef TORRENT_WINDOWS #if TORRENT_USE_WSTRING #define CopyFile_ CopyFileW - std::wstring f1 = convert_to_wstring(inf); - std::wstring f2 = convert_to_wstring(newf); #else #define CopyFile_ CopyFileA - std::string const& f1 = convert_to_native(inf); - std::string const& f2 = convert_to_native(newf); #endif if (CopyFile_(f1.c_str(), f2.c_str(), false) == 0) ec.assign(GetLastError(), system_category()); -#elif defined __APPLE__ && defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 - std::string f1 = convert_to_native(inf); - std::string f2 = convert_to_native(newf); +#undef CopyFile_ +#elif defined __APPLE__ && defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 // this only works on 10.5 copyfile_state_t state = copyfile_state_alloc(); if (copyfile(f1.c_str(), f2.c_str(), state, COPYFILE_ALL) < 0) ec.assign(errno, system_category()); copyfile_state_free(state); #else - std::string const f1 = convert_to_native(inf); - std::string const f2 = convert_to_native(newf); - int const infd = ::open(f1.c_str(), O_RDONLY); if (infd < 0) { @@ -944,23 +958,21 @@ namespace libtorrent std::string current_working_directory() { -#if defined TORRENT_WINDOWS && !defined TORRENT_MINGW +#if defined TORRENT_WINDOWS #if TORRENT_USE_WSTRING - wchar_t cwd[TORRENT_MAX_PATH]; - _wgetcwd(cwd, sizeof(cwd) / sizeof(wchar_t)); +#define GetCurrentDir_ ::_wgetcwd #else - char cwd[TORRENT_MAX_PATH]; - _getcwd(cwd, sizeof(cwd)); +#define GetCurrentDir_ ::_getcwd #endif // TORRENT_USE_WSTRING #else - char cwd[TORRENT_MAX_PATH]; - if (getcwd(cwd, sizeof(cwd)) == nullptr) return "/"; -#endif -#if defined TORRENT_WINDOWS && !defined TORRENT_MINGW && TORRENT_USE_WSTRING - return convert_from_wstring(cwd); -#else - return convert_from_native(cwd); +#define GetCurrentDir_ ::getcwd #endif + auto cwd = GetCurrentDir_(nullptr, 0); + if (cwd == nullptr) + aux::throw_ex(error_code(errno, generic_category())); + auto holder = make_free_holder(cwd); + return convert_from_native_path(cwd); +#undef GetCurrentDir_ } #if TORRENT_USE_UNC_PATHS @@ -1057,24 +1069,21 @@ namespace libtorrent void remove(std::string const& inf, error_code& ec) { ec.clear(); + native_path_string f = convert_to_native_path_string(inf); #ifdef TORRENT_WINDOWS // windows does not allow trailing / or \ in // the path when removing files - std::string pruned; - if (inf[inf.size() - 1] == '/' - || inf[inf.size() - 1] == '\\') - pruned = inf.substr(0, inf.size() - 1); - else - pruned = inf; + while (!f.empty() && ( + f.back() == '/' || + f.back() == '\\' + )) f.pop_back(); #if TORRENT_USE_WSTRING #define DeleteFile_ DeleteFileW #define RemoveDirectory_ RemoveDirectoryW - std::wstring f = convert_to_wstring(pruned); #else #define DeleteFile_ DeleteFileA #define RemoveDirectory_ RemoveDirectoryA - std::string f = convert_to_native(pruned); #endif if (DeleteFile_(f.c_str()) == 0) { @@ -1086,8 +1095,9 @@ namespace libtorrent ec.assign(GetLastError(), system_category()); return; } +#undef DeleteFile_ +#undef RemoveDirectory_ #else // TORRENT_WINDOWS - std::string const& f = convert_to_native(inf); if (::remove(f.c_str()) < 0) { ec.assign(errno, system_category()); @@ -1149,40 +1159,43 @@ namespace libtorrent : m_done(false) { ec.clear(); + std::string p{ path }; + #ifdef TORRENT_WINDOWS - m_inode = 0; // the path passed to FindFirstFile() must be // a pattern - std::string f = convert_separators(path); - if (!f.empty() && f[f.size()-1] != '\\') f += "\\*"; - else f += "*"; + p.append((!p.empty() && p.back() != '\\') ? "\\*" : "*"); +#else + // the path passed to opendir() may not + // end with a / + if (!p.empty() && p.back() == '/') + p.pop_back(); +#endif + + native_path_string f = convert_to_native_path_string(p); + +#ifdef TORRENT_WINDOWS + m_inode = 0; + #if TORRENT_USE_WSTRING #define FindFirstFile_ FindFirstFileW - std::wstring p = convert_to_wstring(f); #else #define FindFirstFile_ FindFirstFileA - std::string p = convert_to_native(f); #endif - m_handle = FindFirstFile_(p.c_str(), &m_fd); + m_handle = FindFirstFile_(f.c_str(), &m_fd); if (m_handle == INVALID_HANDLE_VALUE) { ec.assign(GetLastError(), system_category()); m_done = true; return; } +#undef FindFirstFile_ #else std::memset(&m_dirent, 0, sizeof(dirent)); m_name[0] = 0; - // the path passed to opendir() may not - // end with a / - std::string p = path; - if (!path.empty() && path[path.size()-1] == '/') - p.resize(path.size()-1); - - p = convert_to_native(p); - m_handle = ::opendir(p.c_str()); + m_handle = ::opendir(f.c_str()); if (m_handle == nullptr) { ec.assign(errno, system_category()); @@ -1216,11 +1229,7 @@ namespace libtorrent std::string directory::file() const { #ifdef TORRENT_WINDOWS -#if TORRENT_USE_WSTRING - return convert_from_wstring(m_fd.cFileName); -#else - return convert_from_native(m_fd.cFileName); -#endif + return convert_from_native_path(m_fd.cFileName); #else return convert_from_native(m_dirent.d_name); #endif @@ -1242,6 +1251,7 @@ namespace libtorrent if (err != ERROR_NO_MORE_FILES) ec.assign(err, system_category()); } +#undef FindNextFile_ ++m_inode; #else dirent* dummy; @@ -1330,6 +1340,7 @@ namespace libtorrent bool file::open(std::string const& path, int mode, error_code& ec) { close(); + native_path_string file_path = convert_to_native_path_string(path); #ifdef TORRENT_WINDOWS @@ -1357,20 +1368,10 @@ namespace libtorrent FILE_ATTRIBUTE_HIDDEN, // hidden + executable }; - std::string p = convert_separators(path); -#if TORRENT_USE_UNC_PATHS - // UNC paths must be absolute - // network paths are already UNC paths - if (path.substr(0,2) == "\\\\") p = path; - else p = "\\\\?\\" + (is_complete(p) ? p : combine_path(current_working_directory(), p)); -#endif - #if TORRENT_USE_WSTRING #define CreateFile_ CreateFileW - std::wstring file_path = convert_to_wstring(p); #else #define CreateFile_ CreateFileA - std::string file_path = convert_to_native(p); #endif TORRENT_ASSERT((mode & rw_mask) < sizeof(mode_array)/sizeof(mode_array[0])); @@ -1390,6 +1391,8 @@ namespace libtorrent , (mode & lock_file) ? FILE_SHARE_READ : FILE_SHARE_READ | FILE_SHARE_WRITE , 0, m.create_mode, flags, 0); +#undef CreateFile_ + if (handle == INVALID_HANDLE_VALUE) { ec.assign(GetLastError(), system_category()); @@ -1436,7 +1439,7 @@ namespace libtorrent #endif ; - handle_type handle = ::open(convert_to_native(path).c_str() + handle_type handle = ::open(file_path.c_str() , mode_array[mode & rw_mask] | open_mode , permissions); @@ -1448,7 +1451,7 @@ namespace libtorrent { mode &= ~no_atime; open_mode &= ~O_NOATIME; - handle = ::open(convert_to_native(path).c_str(), mode_array[mode & rw_mask] | open_mode + handle = ::open(file_path.c_str(), mode_array[mode & rw_mask] | open_mode , permissions); } #endif diff --git a/test/test_file.cpp b/test/test_file.cpp index f4db8458f..cfb73199d 100644 --- a/test/test_file.cpp +++ b/test/test_file.cpp @@ -170,15 +170,6 @@ TORRENT_TEST(paths) TEST_EQUAL(combine_path("test1", "test2"), "test1/test2"); #endif -#if TORRENT_USE_UNC_PATHS - TEST_EQUAL(canonicalize_path("c:\\a\\..\\b"), "c:\\b"); - TEST_EQUAL(canonicalize_path("a\\..\\b"), "b"); - TEST_EQUAL(canonicalize_path("a\\..\\.\\b"), "b"); - TEST_EQUAL(canonicalize_path("\\.\\a"), "\\a"); - TEST_EQUAL(canonicalize_path("\\\\bla\\.\\a"), "\\\\bla\\a"); - TEST_EQUAL(canonicalize_path("c:\\bla\\a"), "c:\\bla\\a"); -#endif - TEST_EQUAL(extension("blah"), ""); TEST_EQUAL(extension("blah.exe"), ".exe"); TEST_EQUAL(extension("blah.foo.bar"), ".bar"); @@ -408,3 +399,121 @@ TORRENT_TEST(stat_file) TEST_CHECK(ec); TEST_EQUAL(ec, boost::system::errc::no_such_file_or_directory); } + +// specificaly UNC tests +#if TORRENT_USE_UNC_PATHS + +std::tuple fill_current_directory_caps() +{ +#ifdef TORRENT_WINDOWS + error_code ec; + DWORD dw_maximum_component_length; + DWORD dw_file_system_flags; + if (!GetVolumeInformationA(nullptr, nullptr, 0, nullptr, &dw_maximum_component_length, &dw_file_system_flags, nullptr, 0)) + { + ec.assign(GetLastError(), system_category()); + std::printf("GetVolumeInformation: [%s] %s\n" + , ec.category().name(), ec.message().c_str()); + return std::make_tuple(0, false); + } + int maximum_component_length = int(dw_maximum_component_length); + bool support_hard_links = ((dw_file_system_flags & FILE_SUPPORTS_HARD_LINKS) != 0); + return std::make_tuple(maximum_component_length, support_hard_links); +#else + return std::make_tuple(TORRENT_MAX_PATH, true); +#endif +} + +TORRENT_TEST(unc_tests) +{ + TEST_EQUAL(canonicalize_path("c:\\a\\..\\b"), "c:\\b"); + TEST_EQUAL(canonicalize_path("a\\..\\b"), "b"); + TEST_EQUAL(canonicalize_path("a\\..\\.\\b"), "b"); + TEST_EQUAL(canonicalize_path("\\.\\a"), "\\a"); + TEST_EQUAL(canonicalize_path("\\\\bla\\.\\a"), "\\\\bla\\a"); + TEST_EQUAL(canonicalize_path("c:\\bla\\a"), "c:\\bla\\a"); + + error_code ec; + + std::vector special_names + { + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", + "COM5", "COM6", "COM7", "COM8", + "COM9", "LPT1", "LPT2", "LPT3", + "LPT4", "LPT5", "LPT6", "LPT7", + "LPT8", "LPT9" + }; + + for (std::string special_name : special_names) + { + TEST_EQUAL(touch_file(special_name, 10), 0); + TEST_CHECK(lt::exists(special_name)); + lt::remove(special_name, ec); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(!lt::exists(special_name)); + } + + int maximum_component_length; + bool support_hard_links; + std::tie(maximum_component_length, support_hard_links) = fill_current_directory_caps(); + + if (maximum_component_length > 0) + { + std::string long_component_name; + long_component_name.resize(maximum_component_length); + for (int i = 0; i < maximum_component_length; ++i) + long_component_name[i] = static_cast((i % 26) + 'A'); + + std::string long_file_name1 = combine_path(long_component_name, long_component_name); + long_file_name1.back() = '1'; + std::string long_file_name2 { long_file_name1 }; + long_file_name2.back() = '2'; + + error_code ec; + + lt::create_directory(long_component_name, ec); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(lt::exists(long_component_name)); + TEST_CHECK(lt::is_directory(long_component_name, ec)); + + TEST_EQUAL(touch_file(long_file_name1, 10), 0); + TEST_CHECK(lt::exists(long_file_name1)); + + lt::rename(long_file_name1, long_file_name2, ec); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(!lt::exists(long_file_name1)); + TEST_CHECK(lt::exists(long_file_name2)); + + lt::copy_file(long_file_name2, long_file_name1, ec); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(lt::exists(long_file_name1)); + + std::set files; + + for (lt::directory i(long_component_name, ec); !i.done(); i.next(ec)) + { + std::string f = i.file(); + files.insert(f); + } + + TEST_EQUAL(files.size(), 4); + + lt::remove(long_file_name1, ec); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(!lt::exists(long_file_name1)); + + if (support_hard_links) + { + lt::hard_link(long_file_name2, long_file_name1, ec); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(lt::exists(long_file_name1)); + + lt::remove(long_file_name1, ec); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(!lt::exists(long_file_name1)); + } + } +} + +#endif