MySQL Lists are EOL. Please join:

List:Commits« Previous MessageNext Message »
From:Vladislav Vaintroub Date:November 20 2008 8:36am
Subject:bzr commit into mysql-5.1 branch (vvaintroub:2699) Bug#39750
View as plain text  
#At file:///G:/bzr/51-win_unlink/

 2699 Vladislav Vaintroub	2008-11-20
      Bug #39750 : INFORMATION_SCHEMA operations fail sporadically due to I/O errors
      on Windows
      
      The problem: shortly after successfull my_delete, my_open with the same name
      would fail with ERROR_ACCESS_DENIED. This is pecularity of Windows: file does not 
      disappear entirely from the filesystem until the last open handle to it is closed
      and in the time after DeleteFile and before handles to it are closed it is in a 
      sort of "zombie" state (aka DELETE PENDING)-  any further access to this file will 
      result in access denied.
      
      It does not seem possible to prevent other applications like virus scanner or 
      indexer from opening the file while we're deleting it. Also, there is a possibility 
      that another MySQL thread has opened the file while we're deleting it (as in see 
      Bug #27594)
      
      The fix is to rename file to a unique name prior to deletion. This technique was 
      used prior, but just for binlog. This patch also fixes some glitches in prior 
      implementationof rename/remove.
modified:
  include/my_sys.h
  mysys/my_delete.c
  mysys/my_redel.c
  sql/log.cc

per-file messages:
  include/my_sys.h
    * Remove nt_share_delete
    * Remove my_delete_allow_opened, use my_delete everywhere
  mysys/my_delete.c
    my_delete will always move the file to a unique name prior to deletion.
    rename file to a unique name prior to deletion. 
    
    This technique was previously used, but not universally, just for binlog. 
    Now MoveFile will always be done. This patch also fixes some glitches in 
    previous implementation:
    
    * the Move/Delete operations is now atomic, i.e if application crashes in between, 
    no file is left in the filesystem.
    * ensure we can rename even if generated tmp filename exceeds MAX_PATH 
    * ensure that the last path component of generated filename is no longer 
    than MAX_FNAME
  mysys/my_redel.c
    my_delete_allow_opened->my_delete
  sql/log.cc
    my_delete_allow_opened->my_delete
=== modified file 'include/my_sys.h'
--- a/include/my_sys.h	2008-08-23 02:47:43 +0000
+++ b/include/my_sys.h	2008-11-20 08:36:24 +0000
@@ -629,12 +629,6 @@ extern File my_sopen(const char *path, i
 extern int check_if_legal_filename(const char *path);
 extern int check_if_legal_tablename(const char *path);
 
-#if defined(__WIN__) && defined(__NT__)
-extern int nt_share_delete(const char *name,myf MyFlags);
-#define my_delete_allow_opened(fname,flags)  nt_share_delete((fname),(flags))
-#else
-#define my_delete_allow_opened(fname,flags)  my_delete((fname),(flags))
-#endif
 
 #ifndef TERMINATE
 extern void TERMINATE(FILE *file, uint flag);

=== modified file 'mysys/my_delete.c'
--- a/mysys/my_delete.c	2007-12-07 20:27:48 +0000
+++ b/mysys/my_delete.c	2008-11-20 08:36:24 +0000
@@ -17,13 +17,23 @@
 #include "mysys_err.h"
 #include <my_sys.h>
 
+#ifdef _WIN32
+static int my_win_unlink(const char *name);
+#endif
+
 int my_delete(const char *name, myf MyFlags)
 {
   int err;
   DBUG_ENTER("my_delete");
   DBUG_PRINT("my",("name %s MyFlags %d", name, MyFlags));
 
-  if ((err = unlink(name)) == -1)
+#ifdef _WIN32
+  err = my_win_unlink(name);
+#else
+  err = unlink(name);
+#endif
+
+  if(err)
   {
     my_errno=errno;
     if (MyFlags & (MY_FAE+MY_WME))
@@ -36,57 +46,164 @@ int my_delete(const char *name, myf MyFl
   DBUG_RETURN(err);
 } /* my_delete */
 
-#if defined(__WIN__) && defined(__NT__)
+
+#if defined (_WIN32)
+extern  void _dosmaperr (unsigned long oserrno);
+
+/*
+  Move file to a unique filename, helper function used by my_win_unlink
+
+  Unique filename is generated in the same  directory as original file,
+  and the name is original name plus suffix <counter>.deleted, where counter is
+  the result of QueryPerformanceCounter().
+
+  Since generated unique filename can be longer then MAX_PATH, we use unicode 
+  version of MoveFile and prepend \\?\ to the normalized filename 
+  (or \\?\UNC\ too UNC path's)
+*/
+#define SUFFIX_LEN 25 /*size of .<counter>.deleted filename part*/
+#define PREFIX_LEN 8 /* e.g \\?\UNC\ */
+
+static BOOL move_to_unique_name(const char *name)
+{
+  wchar_t unique_filename[MAX_PATH+SUFFIX_LEN+PREFIX_LEN];
+  wchar_t filename[MAX_PATH]=L"";
+  wchar_t longname[MAX_PATH];
+  wchar_t *file_part;
+  __int64 counter= 0;
+  DWORD ret;
+
+  if(mbstowcs(filename, name, MAX_PATH) >= MAX_PATH)
+    return FALSE;
+
+  ret = GetLongPathNameW(filename, longname, MAX_PATH);
+  if (ret >= MAX_PATH)
+    return FALSE;
+
+  ret = GetFullPathNameW(longname,MAX_PATH,filename,&file_part);
+  if (ret >= MAX_PATH)
+    return FALSE;
+
+  QueryPerformanceCounter((LARGE_INTEGER *)&counter);
+
+
+  if (file_part <= filename || file_part[-1] != L'\\')
+  {
+    /* No path separator, must be directory, we don't handle this */
+    return FALSE; 
+  }
+
+  /*
+   Construct unique filename.
+
+   - Prepend prefix \\?\ (or \\?\UNC\) to the filename for correct handling of names
+   that are possibly longer than MAX_PATH. See "Naming a File or Directory" in MSDN
+
+   - Append suffix <counter>.deleted to the filename. Make sure last path.component
+   does not grow to over MAX_FNAME (255) characters.Truncate original filename part,
+   if <filepart>.<counter>.deleted  would expand MAX_FNAME limit.
+  */
+
+
+  /* Temporarily separate directory and filename part, for formatting*/
+  file_part[-1]= 0;
+  if(filename[1] == L':')
+  {
+    /* It is local path, prepend \\?\ */
+    _snwprintf(unique_filename, sizeof(unique_filename)-1,
+      L"\\\\?\\%s\\%.*s.%llx.deleted",
+      filename, _MAX_FNAME - SUFFIX_LEN -1, file_part,counter);
+  }
+  else
+  {
+    /* It is UNC path, prepend \\?\UNC\ */
+    _snwprintf(unique_filename, sizeof(unique_filename)-1,
+      L"\\\\?\\UNC\\%s\\%.*s.%llx.deleted", 
+      filename+2, _MAX_FNAME - SUFFIX_LEN -1, file_part, counter);
+  }
+  /* Restore path separator */
+  file_part[-1]= L'\\';
+
+  return MoveFileW(filename, unique_filename);
+}
+
+
 /*
   Delete file which is possibly not closed.
 
-  This function is intended to be used exclusively as a temporal solution
-  for Win NT in case when it is needed to delete a not closed file (note
-  that the file must be opened everywhere with FILE_SHARE_DELETE mode).
-  Deleting not-closed files can not be supported on Win 98|ME (and because
-  of that is considered harmful).
-  
-  The function deletes the file with its preliminary renaming. This is
-  because when not-closed share-delete file is deleted it still lives on
-  a disk until it will not be closed everwhere. This may conflict with an
-  attempt to create a new file with the same name. The deleted file is
+  The function tries to delete the file with its preliminary renaming.
+  This is because when not-closed share-delete file is deleted it still lives
+  in the filesystem until every handle to it is closed. This may conflict
+  with attempts to create a new file with the same name. The deleted file is
   renamed to <name>.<num>.deleted where <name> - the initial name of the
   file, <num> - a hexadecimal number chosen to make the temporal name to
   be unique.
+
+  If renaming fails on any reason ,file is deleted normally.
+
+  Symbolic link are deleted without renaming. Directories are not deleted.
 */
-int nt_share_delete(const char *name, myf MyFlags)
+static int my_win_unlink(const char *name)
 {
-  char buf[MAX_PATH + 20];
-  ulong cnt;
-  DBUG_ENTER("nt_share_delete");
-  DBUG_PRINT("my",("name %s MyFlags %d", name, MyFlags));
+  HANDLE hFile= INVALID_HANDLE_VALUE;
+  DWORD attributes;
+  DWORD lastError;
 
-  for (cnt= GetTickCount(); cnt; cnt--)
-  {
-    sprintf(buf, "%s.%08X.deleted", name, cnt);
-    if (MoveFile(name, buf))
-      break;
-
-    if ((errno= GetLastError()) == ERROR_ALREADY_EXISTS)
-      continue;
+  DBUG_ENTER("my_win_unlink");
 
-    /* This happened during tests with MERGE tables. */
-    if (errno == ERROR_ACCESS_DENIED)
-      continue;
+  attributes= GetFileAttributes(name);
+  if (attributes == INVALID_FILE_ATTRIBUTES)
+  {
+    lastError= GetLastError();
+    DBUG_PRINT("error",("GetFileAttributes(%s) failed with %d\n", name, lastError));
+    goto error;
+  }
 
-    DBUG_PRINT("warning", ("Failed to rename %s to %s, errno: %d",
-                           name, buf, errno));
-    break;
+  if (attributes & FILE_ATTRIBUTE_DIRECTORY)
+  {
+    /* Use rmdir to delete directories*/
+    DBUG_PRINT("error",("can't remove %s - it is a directory\n", name));
+    errno= EINVAL;
+    DBUG_RETURN(-1);
   }
 
-  if (DeleteFile(buf))
+  if (attributes & FILE_ATTRIBUTE_REPARSE_POINT)
+  {
+    /* Symbolic link. Delete link, the not target */
+    if (!DeleteFile(name))
+    {
+      lastError= GetLastError();
+      DBUG_PRINT("error",("DeleteFile(%s) failed with %d\n", name,lastError));
+      goto error;
+    }
     DBUG_RETURN(0);
+  }
 
-  my_errno= GetLastError();
-  if (MyFlags & (MY_FAE+MY_WME))
-    my_error(EE_DELETE, MYF(ME_BELL + ME_WAITTANG + (MyFlags & ME_NOINPUT)),
-	       name, my_errno);
+  /*
+    Open file with DELETE_ON_CLOSE, rename to a unique name, then close.
+    Ignore rename errors- they are not fatal, file will disappear after last handle 
+    is closed.
+  */
+  hFile= CreateFile(name, DELETE, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+     NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
+  if (hFile == INVALID_HANDLE_VALUE)
+  {
+    lastError= GetLastError();
+    DBUG_PRINT("error",
+      ("CreateFile(%s) with FILE_FLAG_DELETE_ON_CLOSE failed with %d\n",
+       name,lastError));
+    goto error;
+  }
+
+  if (!move_to_unique_name(name))
+    DBUG_PRINT("warning",  ("moving %s to unique filename failed, error %d\n",
+     name,GetLastError()));
+  
+  CloseHandle(hFile);
+  DBUG_RETURN(0);
 
+error:
+  _dosmaperr(lastError);
   DBUG_RETURN(-1);
 }
 #endif

=== modified file 'mysys/my_redel.c'
--- a/mysys/my_redel.c	2007-03-29 17:01:51 +0000
+++ b/mysys/my_redel.c	2008-11-20 08:36:24 +0000
@@ -59,7 +59,7 @@ int my_redel(const char *org_name, const
 		  MyFlags))
       goto end;
   }
-  else if (my_delete_allow_opened(org_name, MyFlags))
+  else if (my_delete(org_name, MyFlags))
       goto end;
   if (my_rename(tmp_name,org_name,MyFlags))
     goto end;

=== modified file 'sql/log.cc'
--- a/sql/log.cc	2008-11-06 14:18:25 +0000
+++ b/sql/log.cc	2008-11-20 08:36:24 +0000
@@ -2841,7 +2841,7 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd)
 
   for (;;)
   {
-    if ((error= my_delete_allow_opened(linfo.log_file_name, MYF(0))) != 0)
+    if ((error= my_delete(linfo.log_file_name, MYF(0))) != 0)
     {
       if (my_errno == ENOENT) 
       {
@@ -2872,7 +2872,7 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd)
 
   /* Start logging with a new file */
   close(LOG_CLOSE_INDEX);
-  if ((error= my_delete_allow_opened(index_file_name, MYF(0))))	// Reset (open will update)
+  if ((error= my_delete(index_file_name, MYF(0))))	// Reset (open will update)
   {
     if (my_errno == ENOENT) 
     {

Thread
bzr commit into mysql-5.1 branch (vvaintroub:2699) Bug#39750Vladislav Vaintroub20 Nov