aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libbutl/buildfile5
-rw-r--r--libbutl/filesystem.cxx124
-rw-r--r--tests/dir-iterator/testscript13
-rw-r--r--tests/link/driver.cxx48
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<bool, entry_stat>
@@ -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<uint64_t> (s.st_size)});
+ entry_stat {et, static_cast<uint64_t> (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<bool, entry_stat> pe (path_entry (ld / "f"));
assert (pe.first && pe.second.type == entry_type::regular);
}
{
+ pair<bool, entry_stat> pe (path_entry (lld / "f"));
+ assert (pe.first && pe.second.type == entry_type::regular);
+ }
+
+ {
pair<bool, entry_stat> 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<bool, entry_stat> pe (path_entry (lld));
+ assert (pe.first && pe.second.type == entry_type::symlink);
+ }
+
+ {
+ pair<bool, entry_stat> 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<bool, entry_stat> pe (path_entry (ld));
+ assert (pe.first && pe.second.type == entry_type::symlink);
+ }
+#endif
+
+ {
+ pair<bool, entry_stat> 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);