mariadb/mysys/my_delete.c
Monty 5bcb1d6532 MDEV-11412 Ensure that table is truly dropped when using DROP TABLE
The used code is largely based on code from Tencent

The problem is that in some rare cases there may be a conflict between .frm
files and the files in the storage engine. In this case the DROP TABLE
was not able to properly drop the table.

Some MariaDB/MySQL forks has solved this by adding a FORCE option to
DROP TABLE. After some discussion among MariaDB developers, we concluded
that users expects that DROP TABLE should always work, even if the
table would not be consistent. There should not be a need to use a
separate keyword to ensure that the table is really deleted.

The used solution is:
- If a .frm table doesn't exists, try dropping the table from all storage
  engines.
- If the .frm table exists but the table does not exist in the engine
  try dropping the table from all storage engines.
- Update storage engines using many table files (.CVS, MyISAM, Aria) to
  succeed with the drop even if some of the files are missing.
- Add HTON_AUTOMATIC_DELETE_TABLE to handlerton's where delete_table()
  is not needed and always succeed. This is used by ha_delete_table_force()
  to know which handlers to ignore when trying to drop a table without
  a .frm file.

The disadvantage of this solution is that a DROP TABLE on a non existing
table will be a bit slower as we have to ask all active storage engines
if they know anything about the table.

Other things:
- Added a new flag MY_IGNORE_ENOENT to my_delete() to not give an error
  if the file doesn't exist. This simplifies some of the code.
- Don't clear thd->error in ha_delete_table() if there was an active
  error. This is a bug fix.
- handler::delete_table() will not abort if first file doesn't exists.
  This is bug fix to handle the case when a drop table was aborted in
  the middle.
- Cleaned up mysql_rm_table_no_locks() to ensure that if_exists uses
  same code path as when it's not used.
- Use non_existing_Table_error() to detect if table didn't exists.
  Old code used different errors tests in different position.
- Table_triggers_list::drop_all_triggers() now drops trigger file if
  it can't be parsed instead of leaving it hanging around (bug fix)
- InnoDB doesn't anymore print error about .frm file out of sync with
  InnoDB directory if .frm file does not exists. This change was required
  to be able to try to drop an InnoDB file when .frm doesn't exists.
- Fixed bug in mi_delete_table() where the .MYD file would not be dropped
  if the .MYI file didn't exists.
- Fixed memory leak in Mroonga when deleting non existing table
- Fixed memory leak in Connect when deleting non existing table

Bugs fixed introduced by the original version of this commit:
MDEV-22826 Presence of Spider prevents tables from being force-deleted from
           other engines
2020-06-14 19:39:42 +03:00

224 lines
5.9 KiB
C

/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
#include "mysys_priv.h"
#include "mysys_err.h"
#include <my_sys.h>
#ifdef _WIN32
#include <direct.h> /* rmdir */
static int my_win_unlink(const char *name);
#endif
CREATE_NOSYMLINK_FUNCTION(
unlink_nosymlinks(const char *pathname),
unlinkat(dfd, filename, 0),
unlink(pathname)
);
int my_delete(const char *name, myf MyFlags)
{
int err;
DBUG_ENTER("my_delete");
DBUG_PRINT("my",("name %s MyFlags %lu", name, MyFlags));
#ifdef _WIN32
err = my_win_unlink(name);
#else
if (MyFlags & MY_NOSYMLINKS)
err= unlink_nosymlinks(name);
else
err= unlink(name);
#endif
if ((MyFlags & MY_IGNORE_ENOENT) && errno == ENOENT)
DBUG_RETURN(0);
if (err)
{
my_errno= errno;
if (MyFlags & (MY_FAE+MY_WME))
my_error(EE_DELETE, MYF(ME_BELL), name, errno);
}
else if ((MyFlags & MY_SYNC_DIR) && my_sync_dir_by_file(name, MyFlags))
err= -1;
DBUG_RETURN(err);
} /* my_delete */
#if defined (_WIN32)
/*
Delete file.
The function also makes best effort to minimize number of errors,
where another program (or thread in the current program) has the the same file
open.
We're using 2 tricks to prevent the errors.
1. A usual Win32's DeleteFile() can with ERROR_SHARED_VIOLATION,
because the file is opened in another application (often, antivirus or backup)
We avoid the error by using CreateFile() with FILE_FLAG_DELETE_ON_CLOSE, instead
of DeleteFile()
2. If file which is deleted (delete on close) but has not entirely gone,
because it is still opened by some app, an attempt to trcreate file with the
same name would result in yet another error. The workaround here is renaming
a file to unique name.
Symbolic link are deleted without renaming. Directories are not deleted.
*/
static int my_win_unlink(const char *name)
{
HANDLE handle= INVALID_HANDLE_VALUE;
DWORD attributes;
uint last_error;
char unique_filename[MAX_PATH + 35];
unsigned long long tsc; /* time stamp counter, for unique filename*/
DBUG_ENTER("my_win_unlink");
attributes= GetFileAttributes(name);
if (attributes == INVALID_FILE_ATTRIBUTES)
{
last_error= GetLastError();
DBUG_PRINT("error",("GetFileAttributes(%s) failed with %u\n", name, last_error));
goto error;
}
if (attributes & FILE_ATTRIBUTE_DIRECTORY)
{
DBUG_PRINT("error",("can't remove %s - it is a directory\n", name));
errno= EINVAL;
DBUG_RETURN(-1);
}
if (attributes & FILE_ATTRIBUTE_REPARSE_POINT)
{
/* Symbolic link. Delete link, the not target */
if (!DeleteFile(name))
{
last_error= GetLastError();
DBUG_PRINT("error",("DeleteFile(%s) failed with %u\n", name,last_error));
goto error;
}
DBUG_RETURN(0);
}
handle= CreateFile(name, DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
if (handle != INVALID_HANDLE_VALUE)
{
/*
We opened file without sharing flags (exclusive), no one else has this file
opened, thus it is save to close handle to remove it. No renaming is
necessary.
*/
CloseHandle(handle);
DBUG_RETURN(0);
}
/*
Can't open file exclusively, hence the file must be already opened by
someone else. Open it for delete (with all FILE_SHARE flags set),
rename to unique name, close.
*/
handle= CreateFile(name, DELETE, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
if (handle == INVALID_HANDLE_VALUE)
{
last_error= GetLastError();
DBUG_PRINT("error",
("CreateFile(%s) with FILE_FLAG_DELETE_ON_CLOSE failed with %u\n",
name,last_error));
goto error;
}
tsc= __rdtsc();
my_snprintf(unique_filename,sizeof(unique_filename),"%s.%llx.deleted",
name, tsc);
if (!MoveFile(name, unique_filename))
{
DBUG_PRINT("warning", ("moving %s to unique filename failed, error %lu\n",
name,GetLastError()));
}
CloseHandle(handle);
DBUG_RETURN(0);
error:
my_osmaperr(last_error);
DBUG_RETURN(-1);
}
#endif
/*
Remove directory recursively.
*/
int my_rmtree(const char *dir, myf MyFlags)
{
char path[FN_REFLEN];
char sep[] = { FN_LIBCHAR, 0 };
int err = 0;
uint i;
MY_DIR *dir_info = my_dir(dir, MYF(MY_DONT_SORT | MY_WANT_STAT));
if (!dir_info)
return 1;
for (i = 0; i < dir_info->number_of_files; i++)
{
FILEINFO *file = dir_info->dir_entry + i;
/* Skip "." and ".." */
if (!strcmp(file->name, ".") || !strcmp(file->name, ".."))
continue;
strxnmov(path, sizeof(path), dir, sep, file->name, NULL);
if (!MY_S_ISDIR(file->mystat->st_mode))
{
err = my_delete(path, MyFlags);
#ifdef _WIN32
/*
On Windows, check and possible reset readonly attribute.
my_delete(), or DeleteFile does not remove theses files.
*/
if (err)
{
DWORD attr = GetFileAttributes(path);
if (attr != INVALID_FILE_ATTRIBUTES &&
(attr & FILE_ATTRIBUTE_READONLY))
{
SetFileAttributes(path, attr &~FILE_ATTRIBUTE_READONLY);
err = my_delete(path, MyFlags);
}
}
#endif
}
else
err = my_rmtree(path, MyFlags);
if (err)
break;
}
my_dirend(dir_info);
if (!err)
err = rmdir(dir);
return err;
}