From cc8c13b8435f34ef8901bb968c6998587ba9a19b Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sat, 16 Feb 2019 00:05:47 +0300 Subject: Fix non-detecting dangling junctions if built with mingw gcc --- libbutl/buildfile | 5 -- libbutl/filesystem.cxx | 124 ++++++++++++++++++++++++++---------------- tests/dir-iterator/testscript | 13 ++++- tests/link/driver.cxx | 48 ++++++++++++++++ 4 files changed, 136 insertions(+), 54 deletions(-) diff --git a/libbutl/buildfile b/libbutl/buildfile index 2be258a..5374533 100644 --- a/libbutl/buildfile +++ b/libbutl/buildfile @@ -49,11 +49,6 @@ if $windows else cxx.libs += -lpthread -#@@ MOD VC bogus warning if module and dll-exported function called within DLL. -# -if ($cxx.features.modules && $cxx.class == 'msvc') - cxx.loptions += /ignore:4217 - # Include the generated version header into the distribution (so that we don't # pick up an installed one) and don't remove it when cleaning in src (so that # clean results in a state identical to distributed). diff --git a/libbutl/filesystem.cxx b/libbutl/filesystem.cxx index 1ebe379..f78c876 100644 --- a/libbutl/filesystem.cxx +++ b/libbutl/filesystem.cxx @@ -130,6 +130,17 @@ namespace butl #else static inline bool + error_file_not_found (DWORD ec) noexcept + { + return ec == ERROR_FILE_NOT_FOUND || + ec == ERROR_PATH_NOT_FOUND || + ec == ERROR_INVALID_NAME || + ec == ERROR_INVALID_DRIVE || + ec == ERROR_BAD_PATHNAME || + ec == ERROR_BAD_NETPATH; + } + + static inline bool junction (DWORD a) noexcept { return a != INVALID_FILE_ATTRIBUTES && @@ -143,21 +154,40 @@ namespace butl return junction (GetFileAttributesA (p.string ().c_str ())); } - static inline entry_type - type (const struct __stat64& s) noexcept + // Return true if the junction exists and is referencing an existing + // directory. Assume that the path references a junction. Underlying OS + // errors are reported by throwing std::system_error, unless ignore_error + // is true. + // + static bool + junction_target_exists (const char* p, bool ignore_error) { - // Note that we currently support only directory symlinks (see mksymlink() - // for details). - // - if (S_ISREG (s.st_mode)) - return entry_type::regular; - else if (S_ISDIR (s.st_mode)) - return entry_type::directory; - // - //else if (S_ISLNK (s.st_mode)) - // return entry_type::symlink; - else - return entry_type::unknown; + HANDLE h (CreateFile (p, + FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL)); + + if (h == INVALID_HANDLE_VALUE) + { + DWORD ec; + + if (ignore_error || error_file_not_found (ec = GetLastError ())) + return false; + + throw_system_error (ec); + } + + CloseHandle (h); + return true; + } + + static inline bool + junction_target_exists (const path& p, bool ignore_error) + { + return junction_target_exists (p.string ().c_str (), ignore_error); } pair @@ -177,32 +207,49 @@ namespace butl p = d.c_str (); } - // Note that _stat64() follows junctions and fails for dangling ones, so - // we check if the entry is a junction prior to calling _stat64(). + // Note that VC's implementations of _stat64() follows junctions and fails + // for dangling ones. MinGW GCC's implementation returns the information + // about the junction itself. That's why we handle junctions specially, + // not relying on _stat64(). // - if (!fl) - { - DWORD a (GetFileAttributesA (p)); - if (a == INVALID_FILE_ATTRIBUTES) // Presumably not exists. - return make_pair (false, entry_stat {entry_type::unknown, 0}); + DWORD a (GetFileAttributesA (p)); + if (a == INVALID_FILE_ATTRIBUTES) // Presumably not exists. + return make_pair (false, entry_stat {entry_type::unknown, 0}); - if (junction (a)) + if (junction (a)) + { + if (!fl) return make_pair (true, entry_stat {entry_type::symlink, 0}); + + return junction_target_exists (p, ie) + ? make_pair (true, entry_stat {entry_type::directory, 0}) + : make_pair (false, entry_stat {entry_type::unknown, 0}); } + entry_type et (entry_type::unknown); struct __stat64 s; // For 64-bit size. if (_stat64 (p, &s) != 0) { if (errno == ENOENT || errno == ENOTDIR || ie) - return make_pair (false, entry_stat {entry_type::unknown, 0}); + return make_pair (false, entry_stat {et, 0}); else throw_generic_error (errno); } + // Note that we currently support only directory symlinks (see mksymlink() + // for details). + // + if (S_ISREG (s.st_mode)) + et = entry_type::regular; + else if (S_ISDIR (s.st_mode)) + et = entry_type::directory; + // + //else if (S_ISLNK (s.st_mode)) + // et = entry_type::symlink; + return make_pair (true, - entry_stat {type (s), - static_cast (s.st_size)}); + entry_stat {et, static_cast (s.st_size)}); } #endif @@ -914,12 +961,7 @@ namespace butl { DWORD ec (GetLastError ()); - if (ec == ERROR_FILE_NOT_FOUND || - ec == ERROR_PATH_NOT_FOUND || - ec == ERROR_INVALID_NAME || - ec == ERROR_INVALID_DRIVE || - ec == ERROR_BAD_PATHNAME || - ec == ERROR_BAD_NETPATH) + if (error_file_not_found (ec)) return {timestamp_nonexistent, timestamp_nonexistent}; throw_system_error (ec); @@ -1434,21 +1476,11 @@ namespace butl // If requested, we ignore dangling symlinks, skipping ones with // non-existing or inaccessible targets. // - if (ignore_dangling_ && e_.ltype () == entry_type::symlink) - { - struct __stat64 s; - path pe (e_.base () / e_.path ()); - - if (_stat64 (pe.string ().c_str (), &s) != 0) - { - if (errno == ENOENT || errno == ENOTDIR || errno == EACCES) - continue; - - throw_generic_error (errno); - } - - e_.lt_ = type (s); // While at it, set the target type. - } + if (ignore_dangling_ && + e_.ltype () == entry_type::symlink && + !junction_target_exists (e_.base () / e_.path (), + true /* ignore_error */)) + continue; } else if (errno == ENOENT) { diff --git a/tests/dir-iterator/testscript b/tests/dir-iterator/testscript index 956eacb..370fd2f 100644 --- a/tests/dir-iterator/testscript +++ b/tests/dir-iterator/testscript @@ -36,14 +36,21 @@ else { +mkdir a +mkdir --no-cleanup a/b - +ln -s a/b a/l + +ln -s a/b a/bl +rmdir a/b +touch a/c + +mkdir a/d + +ln -s a/d a/dl + # On Wine dangling symlinks are not visible (see mksymlink() for details). # - #$* ../a >! 2>! != 0 : keep + #$* ../a >! 2>! != 0 : keep - $* -i ../a >'reg c' : skip + : skip + : + $* -i ../a >>~%EOO% + %(reg c|dir d|sym dir dl)%{3} + EOO } diff --git a/tests/link/driver.cxx b/tests/link/driver.cxx index 76cdbfc..6489d8d 100644 --- a/tests/link/driver.cxx +++ b/tests/link/driver.cxx @@ -179,12 +179,22 @@ main () dir_path ld (td / dir_path ("dslink")); assert (link_dir (dp, ld, false /* hard */, true /* check_content */)); + // Create the symlink to a directory symlink using an absolute path. + // + dir_path lld (td / dir_path ("dslinkslink")); + assert (link_dir (ld, lld, false /* hard */, true /* check_content */)); + { pair pe (path_entry (ld / "f")); assert (pe.first && pe.second.type == entry_type::regular); } { + pair pe (path_entry (lld / "f")); + assert (pe.first && pe.second.type == entry_type::regular); + } + + { pair pe (path_entry (ld)); assert (pe.first && pe.second.type == entry_type::symlink); } @@ -194,16 +204,31 @@ main () assert (pe.first && pe.second.type == entry_type::directory); } + { + pair pe (path_entry (lld)); + assert (pe.first && pe.second.type == entry_type::symlink); + } + + { + pair pe (path_entry (lld, true /* follow_symlinks */)); + assert (pe.first && pe.second.type == entry_type::directory); + } + for (const dir_entry& de: dir_iterator (td, false /* ignore_dangling */)) { assert (de.path () != path ("dslink") || (de.type () == entry_type::directory && de.ltype () == entry_type::symlink)); + + assert (de.path () != path ("dslinkslink") || + (de.type () == entry_type::directory && + de.ltype () == entry_type::symlink)); } // Remove the directory symlink and make sure the target's content still // exists. // + assert (try_rmsymlink (lld) == rmfile_status::success); assert (try_rmsymlink (ld) == rmfile_status::success); { @@ -221,6 +246,29 @@ main () assert (link_dir (dn, td / dir_path ("rdslink"), false, true)); #endif + // Delete the junction target and verify the junction entry status. + // + assert (link_dir (dp, ld, false /* hard */, true /* check_content */)); + rmdir_r (dp); + + // On Wine dangling junctions are not visible. That's why we also re-create + // the target before the junction removal. + // +#if 0 + { + pair pe (path_entry (ld)); + assert (pe.first && pe.second.type == entry_type::symlink); + } +#endif + + { + pair pe (path_entry (ld, true /* follow_symlinks */)); + assert (!pe.first); + } + + assert (try_mkdir (dp) == mkdir_status::success); + assert (try_rmsymlink (ld) == rmfile_status::success); + try { rmdir_r (td); -- cgit v1.1