Skip to content

Commit

Permalink
MDEV-24184 InnoDB RENAME TABLE recovery failure if names are reused
Browse files Browse the repository at this point in the history
fil_op_replay_rename(): Remove.

fil_rename_tablespace_check(): Remove a parameter is_discarded=false.

recv_sys_t::parse(): Instead of applying FILE_RENAME operations,
buffer the operations in renamed_spaces.

recv_sys_t::apply(): In the last_batch, apply renamed_spaces.
  • Loading branch information
vlad-lesin committed Mar 15, 2021
1 parent 031b3df commit 8cbada8
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 122 deletions.
2 changes: 1 addition & 1 deletion mysql-test/suite/innodb/r/log_file_name.result
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ AND support IN ('YES', 'DEFAULT', 'ENABLED');
ENGINE SUPPORT COMMENT TRANSACTIONS XA SAVEPOINTS
FOUND 1 /\[Note\] InnoDB: Header page consists of zero bytes in datafile: .*u1.ibd/ in mysqld.1.err
FOUND 1 /InnoDB: At LSN: \d+: unable to open file .*u[1-5].ibd for tablespace/ in mysqld.1.err
FOUND 1 /\[ERROR\] InnoDB: Cannot rename '.*u5.ibd' to '.*u6.ibd' because the target file exists/ in mysqld.1.err
FOUND 1 /\[ERROR\] InnoDB: Cannot replay rename of tablespace \d+ from '.*u4.ibd' to '.*u6.ibd' because the target file exists/ in mysqld.1.err
# restart: --innodb-force-recovery=1
FOUND 1 /\[Note\] InnoDB: Header page consists of zero bytes in datafile: .*u1.ibd/ in mysqld.1.err
FOUND 1 /InnoDB: At LSN: \d+: unable to open file .*u[1-5].ibd for tablespace/ in mysqld.1.err
Expand Down
3 changes: 2 additions & 1 deletion mysql-test/suite/innodb/t/log_file_name.test
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ call mtr.add_suppression("InnoDB: Failed to find tablespace for table .* in the
call mtr.add_suppression("InnoDB: Plugin initialization aborted");
call mtr.add_suppression("Plugin 'InnoDB' \(init function returned error\|registration as a STORAGE ENGINE failed\)");
call mtr.add_suppression("InnoDB: Table test/u[123] in the InnoDB data dictionary has tablespace id [1-9][0-9]*, but tablespace with that id or name does not exist\\. Have you deleted or moved \\.ibd files\\?");
call mtr.add_suppression("InnoDB: Cannot replay rename of tablespace.*");
FLUSH TABLES;
--enable_query_log

Expand Down Expand Up @@ -246,7 +247,7 @@ let SEARCH_PATTERN= \[Note\] InnoDB: Header page consists of zero bytes in dataf
let SEARCH_PATTERN= InnoDB: At LSN: \d+: unable to open file .*u[1-5].ibd for tablespace;
--source include/search_pattern_in_file.inc

let SEARCH_PATTERN= \[ERROR\] InnoDB: Cannot rename '.*u5.ibd' to '.*u6.ibd' because the target file exists;
let SEARCH_PATTERN= \[ERROR\] InnoDB: Cannot replay rename of tablespace \d+ from '.*u4.ibd' to '.*u6.ibd' because the target file exists;
--source include/search_pattern_in_file.inc

--remove_file $MYSQLD_DATADIR/test/u6.ibd
Expand Down
104 changes: 2 additions & 102 deletions storage/innobase/fil/fil0fil.cc
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,6 @@ bool fil_space_t::try_to_close(bool print_info)
return false;
}

/** Test if a tablespace file can be renamed to a new filepath by checking
if that the old filepath exists and the new filepath does not exist.
@param[in] old_path old filepath
@param[in] new_path new filepath
@param[in] is_discarded whether the tablespace is discarded
@param[in] replace_new whether to ignore the existence of new_path
@return innodb error code */
static dberr_t
fil_rename_tablespace_check(
const char* old_path,
const char* new_path,
bool is_discarded,
bool replace_new = false);
/** Rename a single-table tablespace.
The tablespace must exist in the memory cache.
@param[in] id tablespace identifier
Expand Down Expand Up @@ -1584,89 +1571,6 @@ fil_name_write(
mtr->log_file_op(FILE_MODIFY, space_id, name);
}

/** Replay a file rename operation if possible.
@param[in] space_id tablespace identifier
@param[in] name old file name
@param[in] new_name new file name
@return whether the operation was successfully applied
(the name did not exist, or new_name did not exist and
name was successfully renamed to new_name) */
bool
fil_op_replay_rename(
ulint space_id,
const char* name,
const char* new_name)
{
/* In order to replay the rename, the following must hold:
* The new name is not already used.
* A tablespace exists with the old name.
* The space ID for that tablepace matches this log entry.
This will prevent unintended renames during recovery. */
fil_space_t* space = fil_space_get(space_id);

if (space == NULL) {
return(true);
}

const bool name_match
= strcmp(name, UT_LIST_GET_FIRST(space->chain)->name) == 0;

if (!name_match) {
return(true);
}

/* Create the database directory for the new name, if
it does not exist yet */

const char* namend = strrchr(new_name, OS_PATH_SEPARATOR);
ut_a(namend != NULL);

char* dir = static_cast<char*>(
ut_malloc_nokey(ulint(namend - new_name) + 1));

memcpy(dir, new_name, ulint(namend - new_name));
dir[namend - new_name] = '\0';

bool success = os_file_create_directory(dir, false);
ut_a(success);

ulint dirlen = 0;

if (const char* dirend = strrchr(dir, OS_PATH_SEPARATOR)) {
dirlen = ulint(dirend - dir) + 1;
}

ut_free(dir);

/* New path must not exist. */
dberr_t err = fil_rename_tablespace_check(
name, new_name, false);
if (err != DB_SUCCESS) {
ib::error() << " Cannot replay file rename."
" Remove either file and try again.";
return(false);
}

char* new_table = mem_strdupl(
new_name + dirlen,
strlen(new_name + dirlen)
- 4 /* remove ".ibd" */);

ut_ad(new_table[ulint(namend - new_name) - dirlen]
== OS_PATH_SEPARATOR);
#if OS_PATH_SEPARATOR != '/'
new_table[namend - new_name - dirlen] = '/';
#endif

if (!fil_rename_tablespace(
space_id, name, new_table, new_name)) {
ut_error;
}

ut_free(new_table);
return(true);
}

/** Check for pending operations.
@param[in] space tablespace
@param[in] count number of attempts so far
Expand Down Expand Up @@ -2093,22 +1997,18 @@ fil_make_filepath(
if that the old filepath exists and the new filepath does not exist.
@param[in] old_path old filepath
@param[in] new_path new filepath
@param[in] is_discarded whether the tablespace is discarded
@param[in] replace_new whether to ignore the existence of new_path
@return innodb error code */
static dberr_t
fil_rename_tablespace_check(
const char* old_path,
const char* new_path,
bool is_discarded,
bool replace_new)
{
bool exists = false;
os_file_type_t ftype;

if (!is_discarded
&& os_file_status(old_path, &exists, &ftype)
&& !exists) {
if (os_file_status(old_path, &exists, &ftype) && !exists) {
ib::error() << "Cannot rename '" << old_path
<< "' to '" << new_path
<< "' because the source file"
Expand Down Expand Up @@ -2168,7 +2068,7 @@ dberr_t fil_space_t::rename(const char* name, const char* path, bool log,

if (log) {
dberr_t err = fil_rename_tablespace_check(
chain.start->name, path, false, replace);
chain.start->name, path, replace);
if (err != DB_SUCCESS) {
return(err);
}
Expand Down
14 changes: 0 additions & 14 deletions storage/innobase/include/fil0fil.h
Original file line number Diff line number Diff line change
Expand Up @@ -1525,20 +1525,6 @@ fil_write_flushed_lsn(
lsn_t lsn)
MY_ATTRIBUTE((warn_unused_result));

/** Replay a file rename operation if possible.
@param[in] space_id tablespace identifier
@param[in] name old file name
@param[in] new_name new file name
@return whether the operation was successfully applied
(the name did not exist, or new_name did not exist and
name was successfully renamed to new_name) */
bool
fil_op_replay_rename(
ulint space_id,
const char* name,
const char* new_name)
MY_ATTRIBUTE((warn_unused_result));

/** Delete a tablespace and associated .ibd file.
@param[in] id tablespace identifier
@param[in] if_exists whether to ignore missing tablespace
Expand Down
72 changes: 68 additions & 4 deletions storage/innobase/log/log0recv.cc
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,9 @@ typedef std::map<

static recv_spaces_t recv_spaces;

/** The last parsed FILE_RENAME records */
static std::map<uint32_t,std::string> renamed_spaces;

/** Report an operation to create, delete, or rename a file during backup.
@param[in] space_id tablespace identifier
@param[in] create whether the file is being created
Expand Down Expand Up @@ -942,6 +945,7 @@ void recv_sys_t::close()
}

recv_spaces.clear();
renamed_spaces.clear();
mlog_init.clear();

close_files();
Expand Down Expand Up @@ -2201,11 +2205,15 @@ bool recv_sys_t::parse(lsn_t checkpoint_lsn, store_t *store, bool apply)
l, static_cast<ulint>(fnend - fn),
reinterpret_cast<const byte*>(fn2),
fn2 ? static_cast<ulint>(fn2end - fn2) : 0);

if (!fn2 || !apply);
else if (!fil_op_replay_rename(space_id, fn, fn2))
found_corrupt_fs= true;
const_cast<char&>(fn[rlen])= saved_end;

if (fn2 && apply)
{
const size_t len= fn2end - fn2;
auto r= renamed_spaces.emplace(space_id, std::string{fn2, len});
if (!r.second)
r.first->second= std::string{fn2, len};
}
if (UNIV_UNLIKELY(found_corrupt_fs))
return true;
}
Expand Down Expand Up @@ -2754,6 +2762,62 @@ void recv_sys_t::apply(bool last_batch)
buf_pool_invalidate();
mysql_mutex_lock(&log_sys.mutex);
}
#if 1 /* Mariabackup FIXME: Remove or adjust rename_table_in_prepare() */
else if (srv_operation != SRV_OPERATION_NORMAL);
#endif
else
{
/* In the last batch, we will apply any rename operations. */
for (auto r : renamed_spaces)
{
const uint32_t id= r.first;
fil_space_t *space= fil_space_t::get(id);
if (!space)
continue;
ut_ad(UT_LIST_GET_LEN(space->chain) == 1);
const char *old= space->chain.start->name;
if (r.second != old)
{
bool exists;
os_file_type_t ftype;
const char *new_name= r.second.c_str();
if (!os_file_status(new_name, &exists, &ftype) || exists)
{
ib::error() << "Cannot replay rename of tablespace " << id
<< " from '" << old << "' to '" << r.second <<
(exists ? "' because the target file exists" : "'");
found_corrupt_fs= true;
}
else
{
size_t base= r.second.rfind(OS_PATH_SEPARATOR);
ut_ad(base != std::string::npos);
size_t start= r.second.rfind(OS_PATH_SEPARATOR, base - 1);
if (start == std::string::npos)
start= 0;
else
++start;
/* Keep only databasename/tablename without .ibd suffix */
std::string space_name(r.second, start, r.second.size() - start - 4);
ut_ad(space_name[base - start] == OS_PATH_SEPARATOR);
#if OS_PATH_SEPARATOR != '/'
space_name[base - start]= '/';
#endif
mysql_mutex_lock(&log_sys.mutex);
if (dberr_t err= space->rename(space_name.c_str(), r.second.c_str(),
false))
{
ib::error() << "Cannot replay rename of tablespace " << id
<< " to '" << r.second << "': " << err;
found_corrupt_fs= true;
}
mysql_mutex_unlock(&log_sys.mutex);
}
}
space->release();
}
renamed_spaces.clear();
}

mutex_enter(&mutex);

Expand Down

0 comments on commit 8cbada8

Please sign in to comment.