#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#39750 | Vladislav Vaintroub | 20 Nov |