From a115ab36aaefa18981bcb6a7703f0870c95b0333 Mon Sep 17 00:00:00 2001 From: Andrey Semashev Date: Wed, 17 Nov 2021 20:29:44 +0300 Subject: [PATCH] Added support for removing read-only files on Windows. Reworked remove() operation to separate POSIX and Windows implementations. On Windows, if the file to be removed is read-only, try to reset the read-only attribute before deleting the file. If deleting fails (other than because the file is already deleted), try to restore the read-only attribute. As a side effect, we were able to remove an implementation detail value from the file_type enum that was used by the old remove() implementation. Added a test for remove() on a read-only file on Windows. Also added tests for remove_all(), including for cases with symlinks, hardlinks and read-only files. Also, corrected mklink /J argument in tests. The command accepts /j (lowercase) to the same effect, but the formal help lists /J (uppercase) to create junctions. Related to https://github.com/boostorg/filesystem/issues/216. --- doc/release_history.html | 1 + include/boost/filesystem/file_status.hpp | 4 +- src/operations.cpp | 366 ++++++++++++------ test/Jamfile.v2 | 14 + .../99_canonical_with_junction_point.cpp | 2 +- test/operations_test.cpp | 193 ++++++++- test/windows_attributes.cpp | 4 +- 7 files changed, 453 insertions(+), 131 deletions(-) diff --git a/doc/release_history.html b/doc/release_history.html index 6710cd73c..4a1050228 100644 --- a/doc/release_history.html +++ b/doc/release_history.html @@ -49,6 +49,7 @@

1.78.0

  • Optimized overloads of path::assign, path::append, path::concat and the corresponding operators to avoid unnecessary path copying and reduce the amount of code redundancy.
  • On POSIX systems, fixed absolute(p, base) returning a path with root name base.root_name() if p starts with a root directory. In such a case p is already an absolute path and should be returned as is.
  • create_directories no longer reports an error if the input path consists entirely of dot (".") and dot-dot ("..") elements. The implementation is no longer using recursion internally and therefore is better protected from stack overflow on extremely long paths.
  • +
  • On Windows, remove now supports deleting read-only files. The operation will attempt to reset the read-only attribute prior to removal. Note that this introduces a possibility of the read-only attribute being left unset, if the operation fails and the original value of the attribute fails to be restored. This also affects remove_all. (#216)
  • Fixed a linking error about unresolved references to Boost.ContainerHash functions when user's code includes boost/filesystem/path.hpp but not boost/container_hash/hash.hpp and the compiler is set to preserve unused inline functions. (#215)
  • diff --git a/include/boost/filesystem/file_status.hpp b/include/boost/filesystem/file_status.hpp index cedee8562..484efdaa2 100644 --- a/include/boost/filesystem/file_status.hpp +++ b/include/boost/filesystem/file_status.hpp @@ -46,10 +46,8 @@ enum file_type fifo_file, socket_file, reparse_file, // Windows: FILE_ATTRIBUTE_REPARSE_POINT that is not a symlink - type_unknown, // file does exist, but isn't one of the above types or + type_unknown // file does exist, but isn't one of the above types or // we don't have strong enough permission to find its type - - _detail_directory_symlink // internal use only; never exposed to users }; //--------------------------------------------------------------------------------------// diff --git a/src/operations.cpp b/src/operations.cpp index 66c13601f..e8b8490b2 100644 --- a/src/operations.cpp +++ b/src/operations.cpp @@ -295,8 +295,6 @@ union reparse_data_buffer #define BOOST_SET_CURRENT_DIRECTORY(P) (::chdir(P) == 0) #define BOOST_CREATE_HARD_LINK(F, T) (::link(T, F) == 0) -#define BOOST_REMOVE_DIRECTORY(P) (::rmdir(P) == 0) -#define BOOST_DELETE_FILE(P) (::unlink(P) == 0) #define BOOST_MOVE_FILE(OLD, NEW) (::rename(OLD, NEW) == 0) #define BOOST_RESIZE_FILE(P, SZ) (::truncate(P, SZ) == 0) @@ -304,11 +302,8 @@ union reparse_data_buffer #define BOOST_SET_CURRENT_DIRECTORY(P) (::SetCurrentDirectoryW(P) != 0) #define BOOST_CREATE_HARD_LINK(F, T) (create_hard_link_api(F, T, 0) != 0) -#define BOOST_REMOVE_DIRECTORY(P) (::RemoveDirectoryW(P) != 0) -#define BOOST_DELETE_FILE(P) (::DeleteFileW(P) != 0) #define BOOST_MOVE_FILE(OLD, NEW) (::MoveFileExW(OLD, NEW, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) #define BOOST_RESIZE_FILE(P, SZ) (resize_file_api(P, SZ) != 0) -#define BOOST_READ_SYMLINK(P, T) #endif @@ -355,8 +350,6 @@ BOOST_CONSTEXPR_OR_CONST unsigned int symloop_max = #endif ; -fs::file_type query_file_type(path const& p, error_code* ec); - // general helpers -----------------------------------------------------------------// bool is_empty_directory(path const& p, error_code* ec) @@ -368,81 +361,6 @@ bool is_empty_directory(path const& p, error_code* ec) bool not_found_error(int errval) BOOST_NOEXCEPT; // forward declaration -// only called if directory exists -inline bool remove_directory(path const& p) // true if succeeds or not found -{ - return BOOST_REMOVE_DIRECTORY(p.c_str()) || not_found_error(BOOST_ERRNO); // mitigate possible file system race. See #11166 -} - -// only called if file exists -inline bool remove_file(path const& p) // true if succeeds or not found -{ - return BOOST_DELETE_FILE(p.c_str()) || not_found_error(BOOST_ERRNO); // mitigate possible file system race. See #11166 -} - -// called by remove and remove_all_aux -// return true if file removed, false if not removed -bool remove_file_or_directory(path const& p, fs::file_type type, error_code* ec) -{ - if (type == fs::file_not_found) - { - if (ec) - ec->clear(); - return false; - } - - if (type == fs::directory_file -#ifdef BOOST_WINDOWS_API - || type == fs::_detail_directory_symlink -#endif - ) - { - if (error(!remove_directory(p) ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::remove")) - return false; - } - else - { - if (error(!remove_file(p) ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::remove")) - return false; - } - return true; -} - -uintmax_t remove_all_aux(path const& p, fs::file_type type, error_code* ec) -{ - uintmax_t count = 0u; - - if (type == fs::directory_file) // but not a directory symlink - { - fs::directory_iterator itr; - fs::detail::directory_iterator_construct(itr, p, static_cast< unsigned int >(directory_options::none), ec); - if (ec && *ec) - return count; - - const fs::directory_iterator end_dit; - while (itr != end_dit) - { - fs::file_type tmp_type = query_file_type(itr->path(), ec); - if (ec && *ec) - return count; - - count += remove_all_aux(itr->path(), tmp_type, ec); - if (ec && *ec) - return count; - - fs::detail::directory_iterator_increment(itr, ec); - if (ec && *ec) - return count; - } - } - - remove_file_or_directory(p, type, ec); - if (ec && *ec) - return count; - - return ++count; -} - #ifdef BOOST_POSIX_API //--------------------------------------------------------------------------------------// @@ -977,9 +895,101 @@ const syscall_initializer syscall_init; #endif // defined(linux) || defined(__linux) || defined(__linux__) -inline fs::file_type query_file_type(path const& p, error_code* ec) +//! remove() implementation +inline bool remove_impl(path const& p, fs::file_type type, error_code* ec) +{ + if (type == fs::file_not_found) + return false; + + int res; + if (type == fs::directory_file) + res = ::rmdir(p.c_str()); + else + res = ::unlink(p.c_str()); + + if (res != 0) + { + int err = errno; + if (BOOST_UNLIKELY(!not_found_error(err))) + emit_error(err, p, ec, "boost::filesystem::remove"); + + return false; + } + + return true; +} + +//! remove() implementation +inline bool remove_impl(path const& p, error_code* ec) +{ + // Since POSIX remove() is specified to work with either files or directories, in a + // perfect world it could just be called. But some important real-world operating + // systems (Windows, Mac OS, for example) don't implement the POSIX spec. So + // we have to distinguish between files and directories and call corresponding APIs + // to remove them. + + error_code local_ec; + fs::file_type type = fs::detail::symlink_status(p, &local_ec).type(); + if (BOOST_UNLIKELY(type == fs::status_error)) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove", p, local_ec)); + + *ec = local_ec; + return false; + } + + return fs::detail::remove_impl(p, type, ec); +} + +//! remove_all() implementation +uintmax_t remove_all_impl(path const& p, error_code* ec) { - return fs::detail::symlink_status(p, ec).type(); + fs::file_type type; + { + error_code local_ec; + type = fs::detail::symlink_status(p, &local_ec).type(); + + if (type == fs::file_not_found) + return 0u; + + if (BOOST_UNLIKELY(type == fs::status_error)) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove_all", p, local_ec)); + + *ec = local_ec; + return 0u; + } + } + + uintmax_t count = 0u; + + if (type == fs::directory_file) // but not a directory symlink + { + fs::directory_iterator itr; + fs::detail::directory_iterator_construct(itr, p, static_cast< unsigned int >(directory_options::none), ec); + if (ec && *ec) + return count; + + const fs::directory_iterator end_dit; + while (itr != end_dit) + { + count += fs::detail::remove_all_impl(itr->path(), ec); + if (ec && *ec) + return count; + + fs::detail::directory_iterator_increment(itr, ec); + if (ec && *ec) + return count; + } + } + + count += fs::detail::remove_impl(p, type, ec); + if (ec && *ec) + return count; + + return count; } #else // defined(BOOST_POSIX_API) @@ -1085,16 +1095,14 @@ bool is_reparse_point_a_symlink(path const& p) || buf->rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT; // aka "directory junction" or "junction" } -inline std::size_t get_full_path_name( - path const& src, std::size_t len, wchar_t* buf, wchar_t** p) +inline std::size_t get_full_path_name(path const& src, std::size_t len, wchar_t* buf, wchar_t** p) { - return static_cast< std::size_t >( - ::GetFullPathNameW(src.c_str(), static_cast< DWORD >(len), buf, p)); + return static_cast< std::size_t >(::GetFullPathNameW(src.c_str(), static_cast< DWORD >(len), buf, p)); } -fs::file_status process_status_failure(path const& p, error_code* ec) +inline fs::file_status process_status_failure(path const& p, error_code* ec) { - int errval(::GetLastError()); + int errval = ::GetLastError(); if (ec) // always report errval, even though some ec->assign(errval, system_category()); // errval values are not status_errors @@ -1106,32 +1114,163 @@ fs::file_status process_status_failure(path const& p, error_code* ec) { return fs::file_status(fs::type_unknown); } + if (!ec) BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::status", p, error_code(errval, system_category()))); + return fs::file_status(fs::status_error); } -// differs from symlink_status() in that directory symlinks are reported as -// _detail_directory_symlink, as required on Windows by remove() and its helpers. -fs::file_type query_file_type(path const& p, error_code* ec) +//! remove() implementation +inline bool remove_impl(path const& p, DWORD attrs, error_code* ec) { - DWORD attr(::GetFileAttributesW(p.c_str())); - if (attr == 0xFFFFFFFF) + // The following is similar to symlink_status(), except that it distinguishes between symlinks + // to directories and to files, and also preserves the full file attributes, which we'll need below. + bool is_directory; + if (BOOST_UNLIKELY(attrs == INVALID_FILE_ATTRIBUTES)) { - return process_status_failure(p, ec).type(); + error_code local_ec; + file_type type = process_status_failure(p, &local_ec).type(); + + if (type == fs::file_not_found) + return false; + + if (BOOST_UNLIKELY(type == fs::status_error)) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove", p, local_ec)); + + *ec = local_ec; + return false; + } + + is_directory = type == fs::directory_file; + } + else if (attrs & FILE_ATTRIBUTE_REPARSE_POINT) + { + is_directory = (attrs & FILE_ATTRIBUTE_DIRECTORY) && is_reparse_point_a_symlink(p); + } + else + { + is_directory = (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0; } - if (ec) - ec->clear(); + if (is_directory) + { + BOOL res = ::RemoveDirectoryW(p.c_str()); + if (BOOST_UNLIKELY(!res)) + { + DWORD err = ::GetLastError(); + if (!not_found_error(err)) + emit_error(err, p, ec, "boost::filesystem::remove"); - if (attr & FILE_ATTRIBUTE_REPARSE_POINT) + return false; + } + } + else { - if (is_reparse_point_a_symlink(p)) - return (attr & FILE_ATTRIBUTE_DIRECTORY) ? fs::_detail_directory_symlink : fs::symlink_file; - return fs::reparse_file; + const bool is_read_only = (attrs & FILE_ATTRIBUTE_READONLY) != 0; + if (is_read_only) + { + // DeleteFileW does not allow to remove a read-only file, so we have to drop the attribute + DWORD new_attrs = attrs & ~FILE_ATTRIBUTE_READONLY; + BOOL res = ::SetFileAttributesW(p.c_str(), new_attrs); + if (BOOST_UNLIKELY(!res)) + { + DWORD err = ::GetLastError(); + if (!not_found_error(err)) + emit_error(err, p, ec, "boost::filesystem::remove"); + + return false; + } + } + + BOOL res = ::DeleteFileW(p.c_str()); + if (BOOST_UNLIKELY(!res)) + { + DWORD err = ::GetLastError(); + if (!not_found_error(err)) + { + if (is_read_only) + { + // Try to restore the read-only attribute + ::SetFileAttributesW(p.c_str(), attrs); + } + + emit_error(err, p, ec, "boost::filesystem::remove"); + } + + return false; + } + } + + return true; +} + +//! remove() implementation +inline bool remove_impl(path const& p, error_code* ec) +{ + return remove_impl(p, ::GetFileAttributesW(p.c_str()), ec); +} + +//! remove_all() implementation +uintmax_t remove_all_impl(path const& p, error_code* ec) +{ + const DWORD attrs = ::GetFileAttributesW(p.c_str()); + bool recurse; + if (BOOST_UNLIKELY(attrs == INVALID_FILE_ATTRIBUTES)) + { + error_code local_ec; + file_type type = process_status_failure(p, &local_ec).type(); + + if (type == fs::file_not_found) + return 0u; + + if (BOOST_UNLIKELY(type == fs::status_error)) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove_all", p, local_ec)); + + *ec = local_ec; + return 0u; + } + + // Some unknown file type + recurse = false; + } + else + { + // Recurse into directories, but not into junctions or directory symlinks + recurse = (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0 && (attrs & FILE_ATTRIBUTE_REPARSE_POINT) == 0; + } + + uintmax_t count = 0u; + + if (recurse) + { + fs::directory_iterator itr; + fs::detail::directory_iterator_construct(itr, p, static_cast< unsigned int >(directory_options::none), ec); + if (ec && *ec) + return count; + + const fs::directory_iterator end_dit; + while (itr != end_dit) + { + count += remove_all_impl(itr->path(), ec); + if (ec && *ec) + return count; + + fs::detail::directory_iterator_increment(itr, ec); + if (ec && *ec) + return count; + } } - return (attr & FILE_ATTRIBUTE_DIRECTORY) ? fs::directory_file : fs::regular_file; + count += remove_impl(p, attrs, ec); + if (ec && *ec) + return count; + + return count; } inline BOOL resize_file_api(const wchar_t* p, uintmax_t size) @@ -3074,28 +3213,19 @@ path relative(path const& p, path const& base, error_code* ec) BOOST_FILESYSTEM_DECL bool remove(path const& p, error_code* ec) { - error_code tmp_ec; - file_type type = query_file_type(p, &tmp_ec); - if (error(type == status_error ? tmp_ec.value() : 0, p, ec, "boost::filesystem::remove")) - return false; + if (ec) + ec->clear(); - // Since POSIX remove() is specified to work with either files or directories, in a - // perfect world it could just be called. But some important real-world operating - // systems (Windows, Mac OS X, for example) don't implement the POSIX spec. So - // remove_file_or_directory() is always called to keep it simple. - return remove_file_or_directory(p, type, ec); + return detail::remove_impl(p, ec); } BOOST_FILESYSTEM_DECL uintmax_t remove_all(path const& p, error_code* ec) { - error_code tmp_ec; - file_type type = query_file_type(p, &tmp_ec); - if (error(type == status_error ? tmp_ec.value() : 0, p, ec, "boost::filesystem::remove_all")) - return 0; + if (ec) + ec->clear(); - return (type != status_error && type != file_not_found) // exists - ? remove_all_aux(p, type, ec) : 0; + return detail::remove_all_impl(p, ec); } BOOST_FILESYSTEM_DECL diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 87bd4fa9f..8dccb9fad 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -25,6 +25,20 @@ rule check-mklink ( properties * ) if [ MATCH (MKLINK) : $(output[1]) ] { result = BOOST_FILESYSTEM_HAS_MKLINK ; + + if ! $(.annouced-mklink) + { + ECHO " - Boost.Filesystem: mklink found" ; + .annouced-mklink = 1 ; + } + } + else + { + if ! $(.annouced-mklink) + { + ECHO " - Boost.Filesystem: mklink not found" ; + .annouced-mklink = 1 ; + } } } diff --git a/test/issues/99_canonical_with_junction_point.cpp b/test/issues/99_canonical_with_junction_point.cpp index e723cbd4b..38e3bc078 100644 --- a/test/issues/99_canonical_with_junction_point.cpp +++ b/test/issues/99_canonical_with_junction_point.cpp @@ -46,7 +46,7 @@ int main() const fs::path subDir = "sub"; fs::create_directories(real / subDir); fs::current_path(tmp.path); - BOOST_TEST(std::system("mklink /j junction real") == 0); + BOOST_TEST(std::system("mklink /J junction real") == 0); BOOST_TEST(fs::exists(junction)); // Due to a bug there was a dependency on the current path so try the below for all: diff --git a/test/operations_test.cpp b/test/operations_test.cpp index 612f9bebb..eb4816b42 100644 --- a/test/operations_test.cpp +++ b/test/operations_test.cpp @@ -82,6 +82,24 @@ inline void unsetenv_(const char* name) SetEnvironmentVariableW(convert(name).c_str(), 0); } +//! Sets read-only attribute on a file +inline void set_read_only(fs::path const& p) +{ + DWORD attrs = GetFileAttributesW(p.c_str()); + if (attrs == INVALID_FILE_ATTRIBUTES) + { + DWORD err = GetLastError(); + throw fs::filesystem_error("operations_test set_read_only: failed to get file attributes", p, error_code(err, system_category())); + } + + attrs |= FILE_ATTRIBUTE_READONLY; + if (!SetFileAttributesW(p.c_str(), attrs)) + { + DWORD err = GetLastError(); + throw fs::filesystem_error("operations_test set_read_only: failed to set file attributes", p, error_code(err, system_category())); + } +} + #else #include // sleep @@ -149,7 +167,6 @@ bool throws_fs_error(F func, errno_t en, int line) { func(); } - catch (const fs::filesystem_error& ex) { if (report_throws) @@ -266,8 +283,7 @@ class renamer // else if (s.type() == fs::fifo_file) { os << "fifo_file"; } // else if (s.type() == fs::socket_file) { os << "socket_file"; } // else if (s.type() == fs::reparse_file) { os << "reparse_file"; } -// else if (s.type() == fs::type_unknown) { os << "type_unknown"; } -// else { os << "_detail_directory_symlink"; } +// else { os << "type_unknown"; } // return os; //} @@ -1346,6 +1362,17 @@ void remove_tests(const fs::path& dirx) BOOST_TEST(!fs::remove("no-such-file")); BOOST_TEST(!fs::remove("no-such-directory/no-such-file")); +#if defined(BOOST_WINDOWS_API) + // remove() read-only file + BOOST_TEST(!fs::exists(f1x)); + create_file(f1x, ""); + BOOST_TEST(fs::exists(f1x)); + BOOST_TEST(!fs::is_directory(f1x)); + set_read_only(f1x); + BOOST_TEST(fs::remove(f1x)); + BOOST_TEST(!fs::exists(f1x)); +#endif // defined(BOOST_WINDOWS_API) + // remove() directory fs::path d1x = dirx / "shortlife_dir"; BOOST_TEST(!fs::exists(d1x)); @@ -1423,6 +1450,156 @@ void remove_symlink_tests() BOOST_TEST(!fs::exists(f1x)); } +// remove_all_tests ----------------------------------------------------------------// + +void remove_all_tests(const fs::path& dirx) +{ + cout << "remove_all_tests..." << endl; + + // remove_all() file + { + fs::path f1x = dirx / "shortlife"; + BOOST_TEST(!fs::exists(f1x)); + create_file(f1x, ""); + BOOST_TEST(fs::exists(f1x)); + BOOST_TEST(!fs::is_directory(f1x)); + BOOST_TEST_EQ(fs::remove_all(f1x), 1u); + BOOST_TEST(!fs::exists(f1x)); + BOOST_TEST_EQ(fs::remove_all("no-such-file"), 0u); + BOOST_TEST_EQ(fs::remove_all("no-such-directory/no-such-file"), 0u); + } + + // remove_all() directory tree + { + unsigned int created_count = 0u; + fs::path d1x = dirx / "shortlife_dir"; + BOOST_TEST(!fs::exists(d1x)); + fs::create_directory(d1x); + ++created_count; + BOOST_TEST(fs::exists(d1x)); + BOOST_TEST(fs::is_directory(d1x)); + + fs::path d2x = d1x / "nested_dir"; + BOOST_TEST(!fs::exists(d2x)); + fs::create_directory(d2x); + ++created_count; + BOOST_TEST(fs::exists(d2x)); + BOOST_TEST(fs::is_directory(d2x)); + + fs::path f1x = d1x / "shortlife"; + BOOST_TEST(!fs::exists(f1x)); + create_file(f1x, ""); + ++created_count; + BOOST_TEST(fs::exists(f1x)); + BOOST_TEST(!fs::is_directory(f1x)); + +#if defined(BOOST_WINDOWS_API) + // read-only file + fs::path f2x = d1x / "shortlife_ro"; + BOOST_TEST(!fs::exists(f2x)); + create_file(f2x, ""); + ++created_count; + BOOST_TEST(fs::exists(f2x)); + BOOST_TEST(!fs::is_directory(f2x)); + set_read_only(f2x); +#endif // defined(BOOST_WINDOWS_API) + + boost::uintmax_t removed_count = fs::remove_all(d1x); + BOOST_TEST_EQ(removed_count, created_count); + + BOOST_TEST(!fs::exists(d1x)); + } +} + +// remove_all_symlink_tests --------------------------------------------------------// + +void remove_all_symlink_tests(const fs::path& dirx) +{ + cout << "remove_all_symlink_tests..." << endl; + + // External directory tree + fs::path d1x = dirx / "shortlife_dir1"; + BOOST_TEST(!fs::exists(d1x)); + fs::create_directory(d1x); + BOOST_TEST(fs::exists(d1x)); + BOOST_TEST(fs::is_directory(d1x)); + + fs::path f1x = d1x / "shortlife1"; + BOOST_TEST(!fs::exists(f1x)); + create_file(f1x, ""); + BOOST_TEST(fs::exists(f1x)); + BOOST_TEST(!fs::is_directory(f1x)); + + fs::path f2x = d1x / "shortlife2"; + BOOST_TEST(!fs::exists(f2x)); + create_file(f2x, ""); + BOOST_TEST(fs::exists(f2x)); + BOOST_TEST(!fs::is_directory(f2x)); + + // remove_all() directory tree that has symlinks to external directories + unsigned int created_count = 0u; + fs::path d2x = dirx / "shortlife_dir2"; + BOOST_TEST(!fs::exists(d2x)); + fs::create_directory(d2x); + ++created_count; + BOOST_TEST(fs::exists(d2x)); + BOOST_TEST(fs::is_directory(d2x)); + + fs::path f3x = d2x / "shortlife"; + BOOST_TEST(!fs::exists(f3x)); + create_file(f3x, ""); + ++created_count; + BOOST_TEST(fs::exists(f3x)); + BOOST_TEST(!fs::is_directory(f3x)); + + fs::path d3x = d2x / "symlink_dir"; + BOOST_TEST(!fs::exists(d3x)); + fs::create_directory_symlink(d1x, d3x); + ++created_count; + BOOST_TEST(fs::exists(d3x)); + BOOST_TEST(fs::is_symlink(d3x)); + +#if defined(BOOST_FILESYSTEM_HAS_MKLINK) + fs::path junc = d2x / "junc"; + fs::path cur_path(fs::current_path()); + fs::current_path(d2x); + BOOST_TEST(std::system("mklink /J junc ..\\shortlife_dir1") == 0); + fs::current_path(cur_path); + ++created_count; + BOOST_TEST(fs::exists(junc)); +#endif + + fs::path f4x = d2x / "symlink"; + BOOST_TEST(!fs::exists(f4x)); + fs::create_symlink(f1x, f4x); + ++created_count; + BOOST_TEST(fs::exists(f4x)); + BOOST_TEST(fs::is_symlink(f4x)); + + fs::path f5x = d2x / "hardlink"; + BOOST_TEST(!fs::exists(f5x)); + fs::create_hard_link(f2x, f5x); + ++created_count; + BOOST_TEST(fs::exists(f5x)); + BOOST_TEST(!fs::is_directory(f5x)); + + boost::uintmax_t removed_count = fs::remove_all(d2x); + BOOST_TEST_EQ(removed_count, created_count); + + BOOST_TEST(!fs::exists(d2x)); + + // Check that external directory and file are intact + BOOST_TEST(fs::exists(d1x)); + BOOST_TEST(fs::is_directory(d1x)); + BOOST_TEST(fs::exists(f1x)); + BOOST_TEST(!fs::is_directory(f1x)); + BOOST_TEST(fs::exists(f2x)); + BOOST_TEST(!fs::is_directory(f2x)); + + // Cleanup + fs::remove_all(d1x); +} + // absolute_tests -----------------------------------------------------------------// void absolute_tests() @@ -2085,7 +2262,7 @@ void platform_specific_tests() // // Directory junctions are very similar to symlinks, but have some performance // and other advantages over symlinks. They can be created from the command line - // with "mklink /j junction-name target-path". + // with "mklink /J junction-name target-path". { cout << " directory junction tests..." << endl; @@ -2106,7 +2283,7 @@ void platform_specific_tests() fs::path cur_path(fs::current_path()); fs::current_path(dir); //cout << " current_path() is " << fs::current_path() << endl; - BOOST_TEST(std::system("mklink /j junc d1") == 0); + BOOST_TEST(std::system("mklink /J junc d1") == 0); //std::system("dir"); fs::current_path(cur_path); //cout << " current_path() is " << fs::current_path() << endl; @@ -2578,8 +2755,12 @@ int cpp_main(int argc, char* argv[]) recursive_iterator_status_tests(); // lots of cases by now, so a good time to test rename_tests(); remove_tests(dir); + remove_all_tests(dir); if (create_symlink_ok) // only if symlinks supported + { remove_symlink_tests(); + remove_all_symlink_tests(dir); + } creation_time_tests(dir); write_time_tests(dir); temp_directory_path_tests(); @@ -2602,4 +2783,4 @@ int cpp_main(int argc, char* argv[]) cout << "returning from main()" << endl; return ::boost::report_errors(); -} // main +} diff --git a/test/windows_attributes.cpp b/test/windows_attributes.cpp index 2e80a96fa..cdca5355e 100644 --- a/test/windows_attributes.cpp +++ b/test/windows_attributes.cpp @@ -96,10 +96,8 @@ int cpp_main(int argc, char* argv[]) "fifo_file", "socket_file", "reparse_file", // Windows: FILE_ATTRIBUTE_REPARSE_POINT that is not a symlink - "type_unknown", // file does exist", but isn't one of the above types or + "type_unknown" // file does exist", but isn't one of the above types or // we don't have strong enough permission to find its type - - "_detail_directory_symlink" // internal use only; never exposed to users }; std::cout << "boost::filesystem::status().type() is " << types[stat.type()] << std::endl;