subversion-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kot...@apache.org
Subject svn commit: r1859732 - /subversion/trunk/subversion/libsvn_subr/io.c
Date Wed, 22 May 2019 15:26:42 GMT
Author: kotkov
Date: Wed May 22 15:26:41 2019
New Revision: 1859732

URL: http://svn.apache.org/viewvc?rev=1859732&view=rev
Log:
io: Implement the functions that check for node existence using the native
Win32 APIs on Windows:

  svn_io_check_path()
  svn_io_check_resolved_path()
  svn_io_check_special_path()

This changeset aims for two distinct things:

1) First of all, starting from r1833621, these functions were patched to stop
   checking symlinks on Windows.  The root cause for this change has been an
   incorrect implementation of stat in APR that didn't properly distinguish
   between various types of reparse points, some of which are not symlinks.

   Providing the custom implementation allows us to remove the hack and
   properly handle such reparse points irrespectively of the APR version
   in use.

   Additional details on the core of the issue can be found here:
   - https://github.com/golang/go/issues/23684
   - https://github.com/dotnet/corefx/issues/24250

2) These APIs are used in various performance-critical code paths such as
   in the core part of `svn st`.

   On Win32 the necessary answers for these particular functions can be
   obtained with a single call to GetFileAttributes(), which is much faster
   than using the generic stat implementations from APR 1.6.x and 1.7.x
   (I believe that it would be even slower in the latter case).

   A couple of quick tests show about 20%-25% improvement in the speed of
   `svn st` for a large working copy.  The improvement may be more significant
   with indexers or virus scanners, as just asking for file attributes may
   completely avoid opening a file (by retrieving the result through the
   fast I/O QueryOpen).


* subversion/libsvn_subr/io.c
  (FILE_ATTRIBUTE_TAG_INFO, FileAttributeTagInfo): Add these definitions
   for old versions of Windows SDK.
  (typedef GetFileInformationByHandleEx_t,
   get_file_information_by_handle_ex_proc): New.
  (win_init_dynamic_imports): Import `GetFileInformationByHandleEx()`.
  (win32_get_file_information_by_handle): New helper function.
  (io_win_check_path): New helper with the Win32 implementation required
   for the "check path" functions.
  (svn_io_check_path,
   svn_io_check_resolved_path,
   svn_io_check_special_path): Invoke the new helper.
  (io_check_path): Undo the workaround from r1833621 that stopped passing
   APR_FINFO_LINK when performing a stat.

Modified:
    subversion/trunk/subversion/libsvn_subr/io.c

Modified: subversion/trunk/subversion/libsvn_subr/io.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_subr/io.c?rev=1859732&r1=1859731&r2=1859732&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_subr/io.c (original)
+++ subversion/trunk/subversion/libsvn_subr/io.c Wed May 22 15:26:41 2019
@@ -155,8 +155,14 @@ typedef struct _FILE_DISPOSITION_INFO {
   BOOL DeleteFile;
 } FILE_DISPOSITION_INFO, *PFILE_DISPOSITION_INFO;
 
+typedef struct _FILE_ATTRIBUTE_TAG_INFO {
+  DWORD FileAttributes;
+  DWORD ReparseTag;
+} FILE_ATTRIBUTE_TAG_INFO, *PFILE_ATTRIBUTE_TAG_INFO;
+
 #define FileRenameInfo 3
 #define FileDispositionInfo 4
+#define FileAttributeTagInfo 9
 #endif /* WIN32 < Vista */
 
 /* One-time initialization of the late bound Windows API functions. */
@@ -169,19 +175,30 @@ typedef DWORD (WINAPI *GETFINALPATHNAMEB
                DWORD cchFilePath,
                DWORD dwFlags);
 
+typedef BOOL (WINAPI *GetFileInformationByHandleEx_t)(HANDLE hFile,
+                                                      int FileInformationClass,
+                                                      LPVOID lpFileInformation,
+                                                      DWORD dwBufferSize);
+
 typedef BOOL (WINAPI *SetFileInformationByHandle_t)(HANDLE hFile,
                                                     int FileInformationClass,
                                                     LPVOID lpFileInformation,
                                                     DWORD dwBufferSize);
 
 static GETFINALPATHNAMEBYHANDLE get_final_path_name_by_handle_proc = NULL;
+static GetFileInformationByHandleEx_t get_file_information_by_handle_ex_proc = NULL;
 static SetFileInformationByHandle_t set_file_information_by_handle_proc = NULL;
 
-/* Forward declaration. */
+/* Forward declarations. */
 static svn_error_t * io_win_read_link(svn_string_t **dest,
                                       const char *path,
                                       apr_pool_t *pool);
 
+static svn_error_t * io_win_check_path(svn_node_kind_t *kind_p,
+                                       svn_boolean_t *is_symlink_p,
+                                       const char *path,
+                                       apr_pool_t *pool);
+
 #endif
 
 /* Forward declaration */
@@ -342,13 +359,7 @@ io_check_path(const char *path,
   /* Not using svn_io_stat() here because we want to check the
      apr_err return explicitly. */
   SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
-#ifdef WIN32
-  /* on Windows, svn does not handle reparse points or hard links.
-     So ignore the 'resolve_symlinks' flag. */
-  flags = APR_FINFO_MIN;
-#else
   flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK);
-#endif
   apr_err = apr_stat(&finfo, path_apr, flags, pool);
 
   if (APR_STATUS_IS_ENOENT(apr_err))
@@ -410,8 +421,12 @@ svn_io_check_resolved_path(const char *p
                            svn_node_kind_t *kind,
                            apr_pool_t *pool)
 {
+#if WIN32
+  return io_win_check_path(kind, NULL, path, pool);
+#else
   svn_boolean_t ignored;
   return io_check_path(path, TRUE, &ignored, kind, pool);
+#endif
 }
 
 svn_error_t *
@@ -419,8 +434,19 @@ svn_io_check_path(const char *path,
                   svn_node_kind_t *kind,
                   apr_pool_t *pool)
 {
+#if WIN32
+  svn_boolean_t is_symlink;
+
+  SVN_ERR(io_win_check_path(kind, &is_symlink, path, pool));
+
+  if (is_symlink)
+    *kind = svn_node_file;
+
+  return SVN_NO_ERROR;
+#else
   svn_boolean_t ignored;
   return io_check_path(path, FALSE, &ignored, kind, pool);
+#endif
 }
 
 svn_error_t *
@@ -429,7 +455,23 @@ svn_io_check_special_path(const char *pa
                           svn_boolean_t *is_special,
                           apr_pool_t *pool)
 {
+#ifdef WIN32
+  svn_boolean_t is_symlink;
+
+  SVN_ERR(io_win_check_path(kind, &is_symlink, path, pool));
+
+  if (is_symlink)
+    {
+      *is_special = TRUE;
+      *kind = svn_node_file;
+    }
+  else
+    *is_special = FALSE;
+
+  return SVN_NO_ERROR;
+#else
   return io_check_path(path, FALSE, is_special, kind, pool);
+#endif
 }
 
 struct temp_file_cleanup_s
@@ -1950,6 +1992,9 @@ static svn_error_t *win_init_dynamic_imp
       get_final_path_name_by_handle_proc = (GETFINALPATHNAMEBYHANDLE)
         GetProcAddress(kernel32, "GetFinalPathNameByHandleW");
 
+      get_file_information_by_handle_ex_proc = (GetFileInformationByHandleEx_t)
+        GetProcAddress(kernel32, "GetFileInformationByHandleEx");
+
       set_file_information_by_handle_proc = (SetFileInformationByHandle_t)
         GetProcAddress(kernel32, "SetFileInformationByHandle");
     }
@@ -2026,6 +2071,33 @@ static svn_error_t * io_win_read_link(sv
       }
 }
 
+/* Wrapper around Windows API function GetFileInformationByHandleEx() that
+ * returns APR status instead of boolean flag. */
+static apr_status_t
+win32_get_file_information_by_handle(HANDLE hFile,
+                                     int FileInformationClass,
+                                     LPVOID lpFileInformation,
+                                     DWORD dwBufferSize)
+{
+  svn_error_clear(svn_atomic__init_once(&win_dynamic_imports_state,
+                                        win_init_dynamic_imports,
+                                        NULL, NULL));
+
+  if (!get_file_information_by_handle_ex_proc)
+    {
+      return SVN_ERR_UNSUPPORTED_FEATURE;
+    }
+
+  if (!get_file_information_by_handle_ex_proc(hFile, FileInformationClass,
+                                              lpFileInformation,
+                                              dwBufferSize))
+    {
+      return apr_get_os_error();
+    }
+
+  return APR_SUCCESS;
+}
+
 /* Wrapper around Windows API function SetFileInformationByHandle() that
  * returns APR status instead of boolean flag. */
 static apr_status_t
@@ -2053,6 +2125,105 @@ win32_set_file_information_by_handle(HAN
   return APR_SUCCESS;
 }
 
+/* Fast Win32-specific helper for svn_io_check_path() and related functions
+ * that only requires a single GetFileAttributesW() / IRP_MJ_QUERY_OPEN.
+ */
+static svn_error_t * io_win_check_path(svn_node_kind_t *kind_p,
+                                       svn_boolean_t *is_symlink_p,
+                                       const char *path,
+                                       apr_pool_t *pool)
+{
+  DWORD attrs;
+  const WCHAR *wpath;
+  apr_status_t status;
+
+  if (path[0] == '\0')
+    path = ".";
+
+  SVN_ERR(svn_io__utf8_to_unicode_longpath(&wpath, path, pool));
+
+  attrs = GetFileAttributesW(wpath);
+  if (attrs == INVALID_FILE_ATTRIBUTES)
+    {
+      status = apr_get_os_error();
+      if (APR_STATUS_IS_ENOENT(status) || SVN__APR_STATUS_IS_ENOTDIR(status))
+        {
+          *kind_p = svn_node_none;
+          if (is_symlink_p)
+            *is_symlink_p = FALSE;
+          return SVN_NO_ERROR;
+        }
+      else
+        {
+          return svn_error_wrap_apr(status, _("Can't stat '%s'"),
+                                    svn_dirent_local_style(path, pool));
+        }
+    }
+
+  if (attrs & FILE_ATTRIBUTE_DIRECTORY)
+    *kind_p = svn_node_dir;
+  else
+    *kind_p = svn_node_file;
+
+  /* If this is a reparse point, and if we've been asked to check whether
+     we are dealing with a symlink, then open the file and check that.
+
+     Otherwise, it's either definitely not a symlink or the caller
+     doesn't care about this distinction.
+   */
+  if (is_symlink_p && (attrs & FILE_ATTRIBUTE_REPARSE_POINT))
+    {
+      const WCHAR *wfname;
+      HANDLE hFile;
+      FILE_ATTRIBUTE_TAG_INFO taginfo = { 0 };
+
+      SVN_ERR(svn_io__utf8_to_unicode_longpath(&wfname, path, pool));
+
+      hFile = CreateFileW(wfname, FILE_READ_ATTRIBUTES,
+                          FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                          NULL, OPEN_EXISTING,
+                          FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
+                          NULL);
+      if (hFile == INVALID_HANDLE_VALUE)
+        {
+          status = apr_get_os_error();
+          if (APR_STATUS_IS_ENOENT(status) || SVN__APR_STATUS_IS_ENOTDIR(status))
+            {
+              *kind_p = svn_node_none;
+              *is_symlink_p = FALSE;
+              return SVN_NO_ERROR;
+            }
+          else
+            {
+              return svn_error_wrap_apr(status, _("Can't stat '%s'"),
+                                        svn_dirent_local_style(path, pool));
+            }
+        }
+
+      status = win32_get_file_information_by_handle(hFile, FileAttributeTagInfo,
+                                                    &taginfo, sizeof(taginfo));
+      CloseHandle(hFile);
+
+      if (status)
+        return svn_error_wrap_apr(status, _("Can't stat '%s'"),
+                                  svn_dirent_local_style(path, pool));
+
+      /* The surrogate bit in the reparse tag specifies if "the file or directory
+         represents another named entity in the system" which is used to determine
+         if this reparse point behaves like a symlink.
+
+         https://docs.microsoft.com/en-us/windows/desktop/fileio/reparse-point-tags
+       */
+      *is_symlink_p = IsReparseTagNameSurrogate(taginfo.ReparseTag);
+    }
+  else if (is_symlink_p)
+    {
+      *is_symlink_p = FALSE;
+    }
+
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_io__win_delete_file_on_close(apr_file_t *file,
                                  const char *path,



Mime
View raw message