]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Fix fs::hard_link_count behaviour on MinGW [PR113663]
authorLennox Shou Hao Ho <lennoxhoe@gmail.com>
Mon, 29 Jul 2024 20:09:27 +0000 (21:09 +0100)
committerJonathan Wakely <redi@gcc.gnu.org>
Tue, 30 Jul 2024 11:55:10 +0000 (12:55 +0100)
std::filesystem::hard_link_count() always returns 1 on
mingw-w64ucrt-11.0.1-r3 on Windows 10 19045

hard_link_count() queries _wstat64() on MinGW-w64
The MSFT documentation claims _wstat64() will always return 1 *non*-NTFS volumes
https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2013/14h5k7ff(v=vs.120)

My tests suggest that is not always true -
hard_link_count()/_wstat64() still returns 1 on NTFS.
GetFileInformationByHandle does return the correct result of 2.
Please see the PR for a minimal repro.

This patch changes the Windows implementation to always call
GetFileInformationByHandle.

PR libstdc++/113663

libstdc++-v3/ChangeLog:

* src/c++17/fs_ops.cc (fs::equivalent): Moved helper class
auto_handle to anonymous namespace as auto_win_file_handle.
(fs::hard_link_count): Changed Windows implementation to use
information provided by GetFileInformationByHandle which is more
reliable.
* testsuite/27_io/filesystem/operations/hard_link_count.cc: New
test.

Signed-off-by: "Lennox" Shou Hao Ho <lennoxhoe@gmail.com>
Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
libstdc++-v3/src/c++17/fs_ops.cc
libstdc++-v3/testsuite/27_io/filesystem/operations/hard_link_count.cc [new file with mode: 0644]

index 07bc2a0fa88d2b1b93520dadfeb5430163655e8a..81227c49dfde96317e1356097c92e58fdcd41ba6 100644 (file)
@@ -822,6 +822,34 @@ fs::equivalent(const path& p1, const path& p2)
   return result;
 }
 
+#if _GLIBCXX_FILESYSTEM_IS_WINDOWS
+namespace
+{
+  // An RAII type that opens a handle for an existing file.
+  struct auto_win_file_handle
+  {
+    explicit
+    auto_win_file_handle(const fs::path& p_)
+    : handle(CreateFileW(p_.c_str(), 0,
+                        FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+                        0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0))
+    { }
+
+    ~auto_win_file_handle()
+    { if (*this) CloseHandle(handle); }
+
+    explicit operator bool() const
+    { return handle != INVALID_HANDLE_VALUE; }
+
+    bool get_info()
+    { return GetFileInformationByHandle(handle, &info); }
+
+    HANDLE handle;
+    BY_HANDLE_FILE_INFORMATION info;
+  };
+}
+#endif
+
 bool
 fs::equivalent(const path& p1, const path& p2, error_code& ec) noexcept
 {
@@ -858,27 +886,8 @@ fs::equivalent(const path& p1, const path& p2, error_code& ec) noexcept
       if (st1.st_mode != st2.st_mode || st1.st_dev != st2.st_dev)
        return false;
 
-      struct auto_handle {
-       explicit auto_handle(const path& p_)
-       : handle(CreateFileW(p_.c_str(), 0,
-             FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
-             0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0))
-       { }
-
-       ~auto_handle()
-       { if (*this) CloseHandle(handle); }
-
-       explicit operator bool() const
-       { return handle != INVALID_HANDLE_VALUE; }
-
-       bool get_info()
-       { return GetFileInformationByHandle(handle, &info); }
-
-       HANDLE handle;
-       BY_HANDLE_FILE_INFORMATION info;
-      };
-      auto_handle h1(p1);
-      auto_handle h2(p2);
+      auto_win_file_handle h1(p1);
+      auto_win_file_handle h2(p2);
       if (!h1 || !h2)
        {
          if (!h1 && !h2)
@@ -982,7 +991,13 @@ fs::hard_link_count(const path& p)
 std::uintmax_t
 fs::hard_link_count(const path& p, error_code& ec) noexcept
 {
-#ifdef _GLIBCXX_HAVE_SYS_STAT_H
+#if _GLIBCXX_FILESYSTEM_IS_WINDOWS
+  auto_win_file_handle h(p);
+  if (h && h.get_info())
+    return static_cast<uintmax_t>(h.info.nNumberOfLinks);
+  ec = __last_system_error();
+  return static_cast<uintmax_t>(-1);
+#elif defined _GLIBCXX_HAVE_SYS_STAT_H
   return do_stat(p, ec, std::mem_fn(&stat_type::st_nlink),
                 static_cast<uintmax_t>(-1));
 #else
diff --git a/libstdc++-v3/testsuite/27_io/filesystem/operations/hard_link_count.cc b/libstdc++-v3/testsuite/27_io/filesystem/operations/hard_link_count.cc
new file mode 100644 (file)
index 0000000..8b2fb4f
--- /dev/null
@@ -0,0 +1,37 @@
+// { dg-do run { target c++17 } }
+// { dg-require-filesystem-ts "" }
+
+#include <filesystem>
+#include <testsuite_hooks.h>
+#include <testsuite_fs.h>
+
+namespace fs = std::filesystem;
+
+void test01()
+{
+  // PR libstdc++/113663
+
+  fs::path p1 = __gnu_test::nonexistent_path();
+  VERIFY( !fs::exists(p1) );
+
+  __gnu_test::scoped_file f1(p1);
+  VERIFY( fs::exists(p1) );
+
+  VERIFY( fs::hard_link_count(p1) == 1 );
+
+  fs::path p2 = __gnu_test::nonexistent_path();
+  VERIFY( !fs::exists(p2) );
+
+  fs::create_hard_link(p1, p2);
+  __gnu_test::scoped_file f2(p2, __gnu_test::scoped_file::adopt_file);
+  VERIFY( fs::exists(p2) );
+
+  VERIFY( fs::hard_link_count(p1) == 2 );
+  VERIFY( fs::hard_link_count(p2) == 2 );
+}
+
+int
+main()
+{
+  test01();
+}