diff options
Diffstat (limited to 'storage/innobase/fil/fil0fil.cc')
-rw-r--r-- | storage/innobase/fil/fil0fil.cc | 2946 |
1 files changed, 2016 insertions, 930 deletions
diff --git a/storage/innobase/fil/fil0fil.cc b/storage/innobase/fil/fil0fil.cc index 839199cfd8e..a89875352c6 100644 --- a/storage/innobase/fil/fil0fil.cc +++ b/storage/innobase/fil/fil0fil.cc @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 1995, 2011, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 1995, 2012, 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 @@ -25,6 +25,9 @@ Created 10/25/1995 Heikki Tuuri #include "fil0fil.h" +#include <debug_sync.h> +#include <my_dbug.h> + #include "mem0mem.h" #include "hash0hash.h" #include "os0file.h" @@ -41,7 +44,7 @@ Created 10/25/1995 Heikki Tuuri #include "page0page.h" #include "page0zip.h" #include "trx0sys.h" -#include "buf0rea.h" +#include "row0mysql.h" #ifndef UNIV_HOTBACKUP # include "buf0lru.h" # include "ibuf0ibuf.h" @@ -138,7 +141,7 @@ UNIV_INTERN mysql_pfs_key_t fil_space_latch_key; #endif /* UNIV_PFS_RWLOCK */ /** File node of a tablespace or the log data space */ -struct fil_node_struct { +struct fil_node_t { fil_space_t* space; /*!< backpointer to the space where this node belongs */ char* name; /*!< path to the file */ @@ -172,11 +175,11 @@ struct fil_node_struct { ulint magic_n;/*!< FIL_NODE_MAGIC_N */ }; -/** Value of fil_node_struct::magic_n */ +/** Value of fil_node_t::magic_n */ #define FIL_NODE_MAGIC_N 89389 /** Tablespace or log data space: let us call them by a common name space */ -struct fil_space_struct { +struct fil_space_t { char* name; /*!< space name = the path to the first file in it */ ulint id; /*!< space id */ @@ -215,7 +218,8 @@ struct fil_space_struct { last incomplete megabytes in data files may be ignored if space == 0 */ ulint flags; /*!< tablespace flags; see - fsp_flags_validate(), fsp_flags_get_zip_size() */ + fsp_flags_is_valid(), + fsp_flags_get_zip_size() */ ulint n_reserved_extents; /*!< number of reserved free extents for ongoing operations like B-tree page split */ @@ -238,26 +242,23 @@ struct fil_space_struct { UT_LIST_NODE_T(fil_space_t) unflushed_spaces; /*!< list of spaces with at least one unflushed file we have written to */ - ibool is_in_unflushed_spaces; /*!< TRUE if this space is - currently in unflushed_spaces */ + bool is_in_unflushed_spaces; + /*!< true if this space is currently in + unflushed_spaces */ UT_LIST_NODE_T(fil_space_t) space_list; /*!< list of all spaces */ ulint magic_n;/*!< FIL_SPACE_MAGIC_N */ }; -/** Value of fil_space_struct::magic_n */ +/** Value of fil_space_t::magic_n */ #define FIL_SPACE_MAGIC_N 89472 -/** The tablespace memory cache */ -typedef struct fil_system_struct fil_system_t; - /** The tablespace memory cache; also the totality of logs (the log data space) is stored here; below we talk about tablespaces, but also the ib_logfiles form a 'space' and it is handled here */ - -struct fil_system_struct { +struct fil_system_t { #ifndef UNIV_HOTBACKUP - mutex_t mutex; /*!< The mutex protecting the cache */ + ib_mutex_t mutex; /*!< The mutex protecting the cache */ #endif /* !UNIV_HOTBACKUP */ hash_table_t* spaces; /*!< The hash table of spaces in the system; they are hashed on the space @@ -313,7 +314,17 @@ initialized. */ static fil_system_t* fil_system = NULL; /** Determine if (i) is a user tablespace id or not. */ -# define fil_is_user_tablespace_id(i) ((i) > srv_undo_tablespaces) +# define fil_is_user_tablespace_id(i) ((i) > srv_undo_tablespaces_open) + +/** Determine if user has explicitly disabled fsync(). */ +#ifndef __WIN__ +# define fil_buffering_disabled(s) \ + ((s)->purpose == FIL_TABLESPACE \ + && srv_unix_file_flush_method \ + == SRV_UNIX_O_DIRECT_NO_FSYNC) +#else /* __WIN__ */ +# define fil_buffering_disabled(s) (0) +#endif /* __WIN__ */ #ifdef UNIV_DEBUG /** Try fil_validate() every this many times */ @@ -384,16 +395,6 @@ fil_node_complete_io( the node as modified if type == OS_FILE_WRITE */ /*******************************************************************//** -Checks if a single-table tablespace for a given table name exists in the -tablespace memory cache. -@return space id, ULINT_UNDEFINED if not found */ -static -ulint -fil_get_space_id_for_table( -/*=======================*/ - const char* name); /*!< in: table name in the standard - 'databasename/tablename' format */ -/*******************************************************************//** Frees a space object from the tablespace memory cache. Closes the files in the chain but does not delete them. There must not be any pending i/o's or flushes on the files. @@ -412,7 +413,7 @@ calculating the byte offset within a space. @return DB_SUCCESS, or DB_TABLESPACE_DELETED if we are trying to do i/o on a tablespace which does not exist */ UNIV_INLINE -ulint +dberr_t fil_read( /*=====*/ ibool sync, /*!< in: TRUE if synchronous aio is desired */ @@ -441,7 +442,7 @@ calculating the byte offset within a space. @return DB_SUCCESS, or DB_TABLESPACE_DELETED if we are trying to do i/o on a tablespace which does not exist */ UNIV_INLINE -ulint +dberr_t fil_write( /*======*/ ibool sync, /*!< in: TRUE if synchronous aio is desired */ @@ -459,6 +460,8 @@ fil_write( void* message) /*!< in: message for aio handler if non-sync aio used, else ignored */ { + ut_ad(!srv_read_only_mode); + return(fil_io(OS_FILE_WRITE, sync, space_id, zip_size, block_offset, byte_offset, len, buf, message)); } @@ -592,9 +595,9 @@ fil_space_get_type( /**********************************************************************//** Checks if all the file nodes in a space are flushed. The caller must hold the fil_system mutex. -@return TRUE if all are flushed */ +@return true if all are flushed */ static -ibool +bool fil_space_is_flushed( /*=================*/ fil_space_t* space) /*!< in: space */ @@ -608,19 +611,21 @@ fil_space_is_flushed( while (node) { if (node->modification_counter > node->flush_counter) { - return(FALSE); + ut_ad(!fil_buffering_disabled(space)); + return(false); } node = UT_LIST_GET_NEXT(chain, node); } - return(TRUE); + return(true); } /*******************************************************************//** -Appends a new file to the chain of files of a space. File must be closed. */ +Appends a new file to the chain of files of a space. File must be closed. +@return pointer to the file name, or NULL on error */ UNIV_INTERN -void +char* fil_node_create( /*============*/ const char* name, /*!< in: file name (file must be closed) */ @@ -663,7 +668,7 @@ fil_node_create( mutex_exit(&fil_system->mutex); - return; + return(NULL); } space->size += size; @@ -678,6 +683,8 @@ fil_node_create( } mutex_exit(&fil_system->mutex); + + return(node->name); } /********************************************************************//** @@ -718,7 +725,7 @@ fil_node_open_file( OS_FILE_READ_ONLY, &success); if (!success) { /* The following call prints an error message */ - os_file_get_last_error(TRUE); + os_file_get_last_error(true); ut_print_timestamp(stderr); @@ -798,9 +805,9 @@ fil_node_open_file( != page_size)) { fprintf(stderr, "InnoDB: Error: tablespace file %s" - " has page size %lx\n" + " has page size 0x%lx\n" "InnoDB: but the data dictionary" - " expects page size %lx!\n", + " expects page size 0x%lx!\n", node->name, flags, fsp_flags_get_page_size(space->flags)); @@ -809,9 +816,9 @@ fil_node_open_file( if (UNIV_UNLIKELY(space->flags != flags)) { fprintf(stderr, - "InnoDB: Error: table flags are %lx" + "InnoDB: Error: table flags are 0x%lx" " in the data dictionary\n" - "InnoDB: but the flags in file %s are %lx!\n", + "InnoDB: but the flags in file %s are 0x%lx!\n", space->flags, node->name, flags); ut_error; @@ -971,6 +978,7 @@ fil_try_to_close_file_in_LRU( ", because mod_count %ld != fl_count %ld\n", (long) node->modification_counter, (long) node->flush_counter); + } if (node->being_extended) { @@ -1143,10 +1151,15 @@ fil_node_free( node->modification_counter = node->flush_counter; - if (space->is_in_unflushed_spaces - && fil_space_is_flushed(space)) { + if (fil_buffering_disabled(space)) { + + ut_ad(!space->is_in_unflushed_spaces); + ut_ad(fil_space_is_flushed(space)); - space->is_in_unflushed_spaces = FALSE; + } else if (space->is_in_unflushed_spaces + && fil_space_is_flushed(space)) { + + space->is_in_unflushed_spaces = false; UT_LIST_REMOVE(unflushed_spaces, system->unflushed_spaces, @@ -1215,82 +1228,50 @@ fil_space_create( { fil_space_t* space; - fsp_flags_validate(flags); - -try_again: - /*printf( - "InnoDB: Adding tablespace %lu of name %s, purpose %lu\n", id, name, - purpose);*/ + DBUG_EXECUTE_IF("fil_space_create_failure", return(false);); ut_a(fil_system); - ut_a(name); + ut_a(fsp_flags_is_valid(flags)); - mutex_enter(&fil_system->mutex); + /* Look for a matching tablespace and if found free it. */ + do { + mutex_enter(&fil_system->mutex); - space = fil_space_get_by_name(name); + space = fil_space_get_by_name(name); - if (UNIV_LIKELY_NULL(space)) { - ibool success; - ulint namesake_id; + if (space != 0) { + ib_logf(IB_LOG_LEVEL_WARN, + "Tablespace '%s' exists in the cache " + "with id %lu", name, (ulong) id); - ut_print_timestamp(stderr); - fprintf(stderr, - " InnoDB: Warning: trying to init to the" - " tablespace memory cache\n" - "InnoDB: a tablespace %lu of name ", (ulong) id); - ut_print_filename(stderr, name); - fprintf(stderr, ",\n" - "InnoDB: but a tablespace %lu of the same name\n" - "InnoDB: already exists in the" - " tablespace memory cache!\n", - (ulong) space->id); + if (id == 0 || purpose != FIL_TABLESPACE) { - if (id == 0 || purpose != FIL_TABLESPACE) { + mutex_exit(&fil_system->mutex); - mutex_exit(&fil_system->mutex); + return(FALSE); + } - return(FALSE); - } + ib_logf(IB_LOG_LEVEL_WARN, + "Freeing existing tablespace '%s' entry " + "from the cache with id %lu", + name, (ulong) id); - fprintf(stderr, - "InnoDB: We assume that InnoDB did a crash recovery," - " and you had\n" - "InnoDB: an .ibd file for which the table" - " did not exist in the\n" - "InnoDB: InnoDB internal data dictionary in the" - " ibdata files.\n" - "InnoDB: We assume that you later removed the" - " .ibd and .frm files,\n" - "InnoDB: and are now trying to recreate the table." - " We now remove the\n" - "InnoDB: conflicting tablespace object" - " from the memory cache and try\n" - "InnoDB: the init again.\n"); - - namesake_id = space->id; - - success = fil_space_free(namesake_id, FALSE); - ut_a(success); + ibool success = fil_space_free(space->id, FALSE); + ut_a(success); - mutex_exit(&fil_system->mutex); + mutex_exit(&fil_system->mutex); + } - goto try_again; - } + } while (space != 0); space = fil_space_get_by_id(id); - if (UNIV_LIKELY_NULL(space)) { - fprintf(stderr, - "InnoDB: Error: trying to add tablespace %lu" - " of name ", (ulong) id); - ut_print_filename(stderr, name); - fprintf(stderr, "\n" - "InnoDB: to the tablespace memory cache," - " but tablespace\n" - "InnoDB: %lu of name ", (ulong) space->id); - ut_print_filename(stderr, space->name); - fputs(" already exists in the tablespace\n" - "InnoDB: memory cache!\n", stderr); + if (space != 0) { + ib_logf(IB_LOG_LEVEL_ERROR, + "Trying to add tablespace '%s' with id %lu " + "to the tablespace memory cache, but tablespace '%s' " + "with id %lu already exists in the cache!", + name, (ulong) id, space->name, (ulong) space->id); mutex_exit(&fil_system->mutex); @@ -1306,15 +1287,15 @@ try_again: space->tablespace_version = fil_system->tablespace_version; space->mark = FALSE; - if (UNIV_LIKELY(purpose == FIL_TABLESPACE && !recv_recovery_on) - && UNIV_UNLIKELY(id > fil_system->max_assigned_id)) { + if (purpose == FIL_TABLESPACE && !recv_recovery_on + && id > fil_system->max_assigned_id) { + if (!fil_system->space_id_reuse_warned) { fil_system->space_id_reuse_warned = TRUE; - ut_print_timestamp(stderr); - fprintf(stderr, - " InnoDB: Warning: allocated tablespace %lu," - " old maximum was %lu\n", + ib_logf(IB_LOG_LEVEL_WARN, + "Allocated tablespace %lu, old maximum " + "was %lu", (ulong) id, (ulong) fil_system->max_assigned_id); } @@ -1333,7 +1314,7 @@ try_again: HASH_INSERT(fil_space_t, name_hash, fil_system->name_hash, ut_fold_string(name), space); - space->is_in_unflushed_spaces = FALSE; + space->is_in_unflushed_spaces = false; UT_LIST_ADD_LAST(space_list, fil_system->space_list, space); @@ -1418,7 +1399,6 @@ fil_space_free( { fil_space_t* space; fil_space_t* fnamespace; - fil_node_t* fil_node; ut_ad(mutex_own(&fil_system->mutex)); @@ -1444,7 +1424,9 @@ fil_space_free( ut_fold_string(space->name), space); if (space->is_in_unflushed_spaces) { - space->is_in_unflushed_spaces = FALSE; + + ut_ad(!fil_buffering_disabled(space)); + space->is_in_unflushed_spaces = false; UT_LIST_REMOVE(unflushed_spaces, fil_system->unflushed_spaces, space); @@ -1455,12 +1437,11 @@ fil_space_free( ut_a(space->magic_n == FIL_SPACE_MAGIC_N); ut_a(0 == space->n_pending_flushes); - fil_node = UT_LIST_GET_FIRST(space->chain); + for (fil_node_t* fil_node = UT_LIST_GET_FIRST(space->chain); + fil_node != NULL; + fil_node = UT_LIST_GET_FIRST(space->chain)) { - while (fil_node != NULL) { fil_node_free(fil_node, fil_system, space); - - fil_node = UT_LIST_GET_FIRST(space->chain); } ut_a(0 == UT_LIST_GET_LEN(space->chain)); @@ -1478,53 +1459,32 @@ fil_space_free( } /*******************************************************************//** -Returns the size of the space in pages. The tablespace must be cached in the -memory cache. -@return space size, 0 if space not found */ -UNIV_INTERN -ulint -fil_space_get_size( -/*===============*/ +Returns a pointer to the file_space_t that is in the memory cache +associated with a space id. The caller must lock fil_system->mutex. +@return file_space_t pointer, NULL if space not found */ +UNIV_INLINE +fil_space_t* +fil_space_get_space( +/*================*/ ulint id) /*!< in: space id */ { - fil_node_t* node; fil_space_t* space; - ulint size; + fil_node_t* node; ut_ad(fil_system); - mutex_enter(&fil_system->mutex); - space = fil_space_get_by_id(id); - if (space == NULL) { - mutex_exit(&fil_system->mutex); - - return(0); + return(NULL); } if (space->size == 0 && space->purpose == FIL_TABLESPACE) { ut_a(id != 0); + /* The following code must change when InnoDB supports + multiple datafiles per tablespace. */ ut_a(1 == UT_LIST_GET_LEN(space->chain)); - mutex_exit(&fil_system->mutex); - - /* It is possible that the space gets evicted at this point - before the fil_mutex_enter_and_prepare_for_io() acquires - the fil_system->mutex. Check for this after completing the - call to fil_mutex_enter_and_prepare_for_io(). */ - fil_mutex_enter_and_prepare_for_io(id); - - /* We are still holding the fil_system->mutex. Check if - the space is still in memory cache. */ - space = fil_space_get_by_id(id); - - if (space == NULL) { - mutex_exit(&fil_system->mutex); - return(0); - } - node = UT_LIST_GET_FIRST(space->chain); /* It must be a single-table tablespace and we have not opened @@ -1535,7 +1495,69 @@ fil_space_get_size( fil_node_complete_io(node, fil_system, OS_FILE_READ); } - size = space->size; + return(space); +} + +/*******************************************************************//** +Returns the path from the first fil_node_t found for the space ID sent. +The caller is responsible for freeing the memory allocated here for the +value returned. +@return own: A copy of fil_node_t::path, NULL if space ID is zero +or not found. */ +UNIV_INTERN +char* +fil_space_get_first_path( +/*=====================*/ + ulint id) /*!< in: space id */ +{ + fil_space_t* space; + fil_node_t* node; + char* path; + + ut_ad(fil_system); + ut_a(id); + + fil_mutex_enter_and_prepare_for_io(id); + + space = fil_space_get_space(id); + + if (space == NULL) { + mutex_exit(&fil_system->mutex); + + return(NULL); + } + + ut_ad(mutex_own(&fil_system->mutex)); + + node = UT_LIST_GET_FIRST(space->chain); + + path = mem_strdup(node->name); + + mutex_exit(&fil_system->mutex); + + return(path); +} + +/*******************************************************************//** +Returns the size of the space in pages. The tablespace must be cached in the +memory cache. +@return space size, 0 if space not found */ +UNIV_INTERN +ulint +fil_space_get_size( +/*===============*/ + ulint id) /*!< in: space id */ +{ + fil_space_t* space; + ulint size; + + ut_ad(fil_system); + + fil_mutex_enter_and_prepare_for_io(id); + + space = fil_space_get_space(id); + + size = space ? space->size : 0; mutex_exit(&fil_system->mutex); @@ -1552,19 +1574,18 @@ fil_space_get_flags( /*================*/ ulint id) /*!< in: space id */ { - fil_node_t* node; fil_space_t* space; ulint flags; ut_ad(fil_system); - if (UNIV_UNLIKELY(!id)) { + if (!id) { return(0); } - mutex_enter(&fil_system->mutex); + fil_mutex_enter_and_prepare_for_io(id); - space = fil_space_get_by_id(id); + space = fil_space_get_space(id); if (space == NULL) { mutex_exit(&fil_system->mutex); @@ -1572,38 +1593,6 @@ fil_space_get_flags( return(ULINT_UNDEFINED); } - if (space->size == 0 && space->purpose == FIL_TABLESPACE) { - ut_a(id != 0); - - ut_a(1 == UT_LIST_GET_LEN(space->chain)); - - mutex_exit(&fil_system->mutex); - - /* It is possible that the space gets evicted at this point - before the fil_mutex_enter_and_prepare_for_io() acquires - the fil_system->mutex. Check for this after completing the - call to fil_mutex_enter_and_prepare_for_io(). */ - fil_mutex_enter_and_prepare_for_io(id); - - /* We are still holding the fil_system->mutex. Check if - the space is still in memory cache. */ - space = fil_space_get_by_id(id); - - if (space == NULL) { - mutex_exit(&fil_system->mutex); - return(0); - } - - node = UT_LIST_GET_FIRST(space->chain); - - /* It must be a single-table tablespace and we have not opened - the file yet; the following calls will open it and update the - size fields */ - - fil_node_prepare_for_io(node, fil_system, space); - fil_node_complete_io(node, fil_system, OS_FILE_READ); - } - flags = space->flags; mutex_exit(&fil_system->mutex); @@ -1778,6 +1767,49 @@ fil_close_all_files(void) } /*******************************************************************//** +Closes the redo log files. There must not be any pending i/o's or not +flushed modifications in the files. */ +UNIV_INTERN +void +fil_close_log_files( +/*================*/ + bool free) /*!< in: whether to free the memory object */ +{ + fil_space_t* space; + + mutex_enter(&fil_system->mutex); + + space = UT_LIST_GET_FIRST(fil_system->space_list); + + while (space != NULL) { + fil_node_t* node; + fil_space_t* prev_space = space; + + if (space->purpose != FIL_LOG) { + space = UT_LIST_GET_NEXT(space_list, space); + continue; + } + + for (node = UT_LIST_GET_FIRST(space->chain); + node != NULL; + node = UT_LIST_GET_NEXT(chain, node)) { + + if (node->open) { + fil_node_close_file(node, fil_system); + } + } + + space = UT_LIST_GET_NEXT(space_list, space); + + if (free) { + fil_space_free(prev_space->id, FALSE); + } + } + + mutex_exit(&fil_system->mutex); +} + +/*******************************************************************//** Sets the max tablespace id counter if the given number is bigger than the previous value. */ UNIV_INTERN @@ -1807,8 +1839,8 @@ fil_set_max_space_id_if_bigger( Writes the flushed lsn and the latest archived log number to the page header of the first page of a data file of the system tablespace (space 0), which is uncompressed. */ -static -ulint +static __attribute__((warn_unused_result)) +dberr_t fil_write_lsn_and_arch_no_to_file( /*==============================*/ ulint space, /*!< in: space to write to */ @@ -1820,19 +1852,23 @@ fil_write_lsn_and_arch_no_to_file( { byte* buf1; byte* buf; + dberr_t err; buf1 = static_cast<byte*>(mem_alloc(2 * UNIV_PAGE_SIZE)); buf = static_cast<byte*>(ut_align(buf1, UNIV_PAGE_SIZE)); - fil_read(TRUE, space, 0, sum_of_sizes, 0, UNIV_PAGE_SIZE, buf, NULL); + err = fil_read(TRUE, space, 0, sum_of_sizes, 0, + UNIV_PAGE_SIZE, buf, NULL); + if (err == DB_SUCCESS) { + mach_write_to_8(buf + FIL_PAGE_FILE_FLUSH_LSN, lsn); - mach_write_to_8(buf + FIL_PAGE_FILE_FLUSH_LSN, lsn); - - fil_write(TRUE, space, 0, sum_of_sizes, 0, UNIV_PAGE_SIZE, buf, NULL); + err = fil_write(TRUE, space, 0, sum_of_sizes, 0, + UNIV_PAGE_SIZE, buf, NULL); + } mem_free(buf1); - return(DB_SUCCESS); + return(err); } /****************************************************************//** @@ -1840,7 +1876,7 @@ Writes the flushed lsn and the latest archived log number to the page header of the first page of each data file in the system tablespace. @return DB_SUCCESS or error number */ UNIV_INTERN -ulint +dberr_t fil_write_flushed_lsn_to_data_files( /*================================*/ lsn_t lsn, /*!< in: lsn to write */ @@ -1848,7 +1884,7 @@ fil_write_flushed_lsn_to_data_files( { fil_space_t* space; fil_node_t* node; - ulint err; + dberr_t err; mutex_enter(&fil_system->mutex); @@ -1864,7 +1900,6 @@ fil_write_flushed_lsn_to_data_files( if (space->purpose == FIL_TABLESPACE && !fil_is_user_tablespace_id(space->id)) { - ulint sum_of_sizes = 0; for (node = UT_LIST_GET_FIRST(space->chain); @@ -1906,6 +1941,7 @@ fil_read_first_page( parameters below already contain sensible data */ ulint* flags, /*!< out: tablespace flags */ + ulint* space_id, /*!< out: tablespace ID */ #ifdef UNIV_LOG_ARCHIVE ulint* min_arch_log_no, /*!< out: min of archived log numbers in data files */ @@ -1931,7 +1967,9 @@ fil_read_first_page( *flags = fsp_header_get_flags(page); - flushed_lsn = mach_read_from_8(page+ FIL_PAGE_FILE_FLUSH_LSN); + *space_id = fsp_header_get_space_id(page); + + flushed_lsn = mach_read_from_8(page + FIL_PAGE_FILE_FLUSH_LSN); ut_free(buf); @@ -2136,6 +2174,12 @@ created does not exist, then we create the directory, too. Note that ibbackup --apply-log sets fil_path_to_mysql_datadir to point to the datadir that we should use in replaying the file operations. + +InnoDB recovery does not replay these fully since it always sets the space id +to zero. But ibbackup does replay them. TODO: If remote tablespaces are used, +ibbackup will only create tables in the default directory since MLOG_FILE_CREATE +and MLOG_FILE_CREATE2 only know the tablename, not the path. + @return end of log record, or NULL if the record was not completely contained between ptr and end_ptr */ UNIV_INTERN @@ -2231,7 +2275,9 @@ fil_op_log_parse_or_replay( switch (type) { case MLOG_FILE_DELETE: if (fil_tablespace_exists_in_mem(space_id)) { - ut_a(fil_delete_tablespace(space_id)); + dberr_t err = fil_delete_tablespace( + space_id, BUF_REMOVE_FLUSH_NO_WRITE); + ut_a(err == DB_SUCCESS); } break; @@ -2252,10 +2298,10 @@ fil_op_log_parse_or_replay( if (fil_get_space_id_for_table(new_name) == ULINT_UNDEFINED) { - /* We do not care of the old name, that is - why we pass NULL as the first argument */ + /* We do not care about the old name, that + is why we pass NULL as the first argument. */ if (!fil_rename_tablespace(NULL, space_id, - new_name)) { + new_name, NULL)) { ut_error; } } @@ -2273,12 +2319,14 @@ fil_op_log_parse_or_replay( } else if (log_flags & MLOG_FILE_FLAG_TEMP) { /* Temporary table, do nothing */ } else { + const char* path = NULL; + /* Create the database directory for name, if it does not exist yet */ fil_create_directory_for_tablename(name); if (fil_create_new_single_table_tablespace( - space_id, name, FALSE, flags, + space_id, name, path, flags, DICT_TF2_USE_TABLESPACE, FIL_IBD_FILE_INITIAL_SIZE) != DB_SUCCESS) { ut_error; @@ -2295,118 +2343,271 @@ fil_op_log_parse_or_replay( } /*******************************************************************//** -Deletes a single-table tablespace. The tablespace must be cached in the -memory cache. -@return TRUE if success */ -UNIV_INTERN -ibool -fil_delete_tablespace( -/*==================*/ - ulint id) /*!< in: space id */ +Allocates a file name for the EXPORT/IMPORT config file name. The +string must be freed by caller with mem_free(). +@return own: file name */ +static +char* +fil_make_cfg_name( +/*==============*/ + const char* filepath) /*!< in: .ibd file name */ { - ibool success; - fil_space_t* space; - fil_node_t* node; - ulint count = 0; - char* path; + char* cfg_name; - ut_a(id != 0); -stop_new_ops: - mutex_enter(&fil_system->mutex); + /* Create a temporary file path by replacing the .ibd suffix + with .cfg. */ - space = fil_space_get_by_id(id); + ut_ad(strlen(filepath) > 4); - if (space != NULL) { - space->stop_new_ops = TRUE; + cfg_name = mem_strdup(filepath); + ut_snprintf(cfg_name + strlen(cfg_name) - 3, 4, "cfg"); + return(cfg_name); +} - if (space->n_pending_ops == 0) { - mutex_exit(&fil_system->mutex); +/*******************************************************************//** +Check for change buffer merges. +@return 0 if no merges else count + 1. */ +static +ulint +fil_ibuf_check_pending_ops( +/*=======================*/ + fil_space_t* space, /*!< in/out: Tablespace to check */ + ulint count) /*!< in: number of attempts so far */ +{ + ut_ad(mutex_own(&fil_system->mutex)); - count = 0; + if (space != 0 && space->n_pending_ops != 0) { - goto try_again; - } else { - if (count > 5000) { - ut_print_timestamp(stderr); - fputs(" InnoDB: Warning: trying to" - " delete tablespace ", stderr); - ut_print_filename(stderr, space->name); - fprintf(stderr, ",\n" - "InnoDB: but there are %lu pending" - " operations (most likely ibuf merges)" - " on it.\n" - "InnoDB: Loop %lu.\n", - (ulong) space->n_pending_ops, - (ulong) count); - } + if (count > 5000) { + ib_logf(IB_LOG_LEVEL_WARN, + "Trying to close/delete tablespace " + "'%s' but there are %lu pending change " + "buffer merges on it.", + space->name, + (ulong) space->n_pending_ops); + } - mutex_exit(&fil_system->mutex); + return(count + 1); + } - os_thread_sleep(20000); - count++; + return(0); +} + +/*******************************************************************//** +Check for pending IO. +@return 0 if no pending else count + 1. */ +static +ulint +fil_check_pending_io( +/*=================*/ + fil_space_t* space, /*!< in/out: Tablespace to check */ + fil_node_t** node, /*!< out: Node in space list */ + ulint count) /*!< in: number of attempts so far */ +{ + ut_ad(mutex_own(&fil_system->mutex)); + ut_a(space->n_pending_ops == 0); - goto stop_new_ops; + /* The following code must change when InnoDB supports + multiple datafiles per tablespace. */ + ut_a(UT_LIST_GET_LEN(space->chain) == 1); + + *node = UT_LIST_GET_FIRST(space->chain); + + if (space->n_pending_flushes > 0 || (*node)->n_pending > 0) { + + ut_a(!(*node)->being_extended); + + if (count > 1000) { + ib_logf(IB_LOG_LEVEL_WARN, + "Trying to close/delete tablespace '%s' " + "but there are %lu flushes " + " and %lu pending i/o's on it.", + space->name, + (ulong) space->n_pending_flushes, + (ulong) (*node)->n_pending); } + + return(count + 1); } - mutex_exit(&fil_system->mutex); - count = 0; + return(0); +} + +/*******************************************************************//** +Check pending operations on a tablespace. +@return DB_SUCCESS or error failure. */ +static +dberr_t +fil_check_pending_operations( +/*=========================*/ + ulint id, /*!< in: space id */ + fil_space_t** space, /*!< out: tablespace instance in memory */ + char** path) /*!< out/own: tablespace path */ +{ + ulint count = 0; + + ut_a(id != TRX_SYS_SPACE); + ut_ad(space); + + *space = 0; -try_again: mutex_enter(&fil_system->mutex); + fil_space_t* sp = fil_space_get_by_id(id); + if (sp) { + sp->stop_new_ops = TRUE; + } + mutex_exit(&fil_system->mutex); - space = fil_space_get_by_id(id); + /* Check for pending change buffer merges. */ - if (space == NULL) { - ut_print_timestamp(stderr); - fprintf(stderr, - " InnoDB: Error: cannot delete tablespace %lu\n" - "InnoDB: because it is not found in the" - " tablespace memory cache.\n", - (ulong) id); + do { + mutex_enter(&fil_system->mutex); + + sp = fil_space_get_by_id(id); + + count = fil_ibuf_check_pending_ops(sp, count); mutex_exit(&fil_system->mutex); - return(FALSE); - } + if (count > 0) { + os_thread_sleep(20000); + } - ut_a(space->stop_new_ops); - ut_a(space->n_pending_ops == 0); + } while (count > 0); - /* TODO: The following code must change when InnoDB supports - multiple datafiles per tablespace. */ - ut_a(UT_LIST_GET_LEN(space->chain) == 1); + /* Check for pending IO. */ - node = UT_LIST_GET_FIRST(space->chain); + *path = 0; - if (space->n_pending_flushes > 0 || node->n_pending > 0 - || node->being_extended) { - if (count > 1000) { - ut_print_timestamp(stderr); - fputs(" InnoDB: Warning: trying to" - " delete tablespace ", stderr); - ut_print_filename(stderr, space->name); - fprintf(stderr, ",\n" - "InnoDB: but there are %lu flushes" - " and %lu pending i/o's on it\n" - "InnoDB: Or it is being extended\n" - "InnoDB: Loop %lu.\n", - (ulong) space->n_pending_flushes, - (ulong) node->n_pending, - (ulong) count); + do { + mutex_enter(&fil_system->mutex); + + sp = fil_space_get_by_id(id); + + if (sp == NULL) { + mutex_exit(&fil_system->mutex); + return(DB_TABLESPACE_NOT_FOUND); } + + fil_node_t* node; + + count = fil_check_pending_io(sp, &node, count); + + if (count == 0) { + *path = mem_strdup(node->name); + } + mutex_exit(&fil_system->mutex); - os_thread_sleep(20000); - count++; + if (count > 0) { + os_thread_sleep(20000); + } + + } while (count > 0); + + ut_ad(sp); + + *space = sp; + return(DB_SUCCESS); +} + +/*******************************************************************//** +Closes a single-table tablespace. The tablespace must be cached in the +memory cache. Free all pages used by the tablespace. +@return DB_SUCCESS or error */ +UNIV_INTERN +dberr_t +fil_close_tablespace( +/*=================*/ + trx_t* trx, /*!< in/out: Transaction covering the close */ + ulint id) /*!< in: space id */ +{ + char* path = 0; + fil_space_t* space = 0; + + ut_a(id != TRX_SYS_SPACE); - goto try_again; + dberr_t err = fil_check_pending_operations(id, &space, &path); + + if (err != DB_SUCCESS) { + return(err); } - path = mem_strdup(node->name); + ut_a(space); + ut_a(path != 0); + + rw_lock_x_lock(&space->latch); + +#ifndef UNIV_HOTBACKUP + /* Invalidate in the buffer pool all pages belonging to the + tablespace. Since we have set space->stop_new_ops = TRUE, readahead + or ibuf merge can no longer read more pages of this tablespace to the + buffer pool. Thus we can clean the tablespace out of the buffer pool + completely and permanently. The flag stop_new_ops also prevents + fil_flush() from being applied to this tablespace. */ + + buf_LRU_flush_or_remove_pages(id, BUF_REMOVE_FLUSH_WRITE, trx); +#endif + mutex_enter(&fil_system->mutex); + + /* If the free is successful, the X lock will be released before + the space memory data structure is freed. */ + + if (!fil_space_free(id, TRUE)) { + rw_lock_x_unlock(&space->latch); + err = DB_TABLESPACE_NOT_FOUND; + } else { + err = DB_SUCCESS; + } mutex_exit(&fil_system->mutex); + /* If it is a delete then also delete any generated files, otherwise + when we drop the database the remove directory will fail. */ + + char* cfg_name = fil_make_cfg_name(path); + + os_file_delete_if_exists(cfg_name); + + mem_free(path); + mem_free(cfg_name); + + return(err); +} + +/*******************************************************************//** +Deletes a single-table tablespace. The tablespace must be cached in the +memory cache. +@return DB_SUCCESS or error */ +UNIV_INTERN +dberr_t +fil_delete_tablespace( +/*==================*/ + ulint id, /*!< in: space id */ + buf_remove_t buf_remove) /*!< in: specify the action to take + on the tables pages in the buffer + pool */ +{ + char* path = 0; + fil_space_t* space = 0; + + ut_a(id != TRX_SYS_SPACE); + + dberr_t err = fil_check_pending_operations(id, &space, &path); + + if (err != DB_SUCCESS) { + + ib_logf(IB_LOG_LEVEL_ERROR, + "Cannot delete tablespace %lu because it is not " + "found in the tablespace memory cache.", + (ulong) id); + + return(err); + } + + ut_a(space); + ut_a(path != 0); + /* Important: We rely on the data dictionary mutex to ensure that a race is not possible here. It should serialize the tablespace drop/free. We acquire an X latch only to avoid a race condition @@ -2441,9 +2642,22 @@ try_again: To deal with potential read requests by checking the ::stop_new_ops flag in fil_io() */ - buf_LRU_invalidate_tablespace(id); -#endif - /* printf("Deleting tablespace %s id %lu\n", space->name, id); */ + buf_LRU_flush_or_remove_pages(id, buf_remove, 0); + +#endif /* !UNIV_HOTBACKUP */ + + /* If it is a delete then also delete any generated files, otherwise + when we drop the database the remove directory will fail. */ + { + char* cfg_name = fil_make_cfg_name(path); + os_file_delete_if_exists(cfg_name); + mem_free(cfg_name); + } + + /* Delete the link file pointing to the ibd file we are deleting. */ + if (FSP_FLAGS_HAS_DATA_DIR(space->flags)) { + fil_delete_link_file(space->name); + } mutex_enter(&fil_system->mutex); @@ -2452,25 +2666,27 @@ try_again: if (fil_space_get_by_id(id)) { ut_a(space->n_pending_ops == 0); ut_a(UT_LIST_GET_LEN(space->chain) == 1); - node = UT_LIST_GET_FIRST(space->chain); + fil_node_t* node = UT_LIST_GET_FIRST(space->chain); ut_a(node->n_pending == 0); } - success = fil_space_free(id, TRUE); + if (!fil_space_free(id, TRUE)) { + err = DB_TABLESPACE_NOT_FOUND; + } mutex_exit(&fil_system->mutex); - if (success) { - success = os_file_delete(path); - - if (!success) { - success = os_file_delete_if_exists(path); - } - } else { + if (err != DB_SUCCESS) { rw_lock_x_unlock(&space->latch); + } else if (!os_file_delete(path) && !os_file_delete_if_exists(path)) { + + /* Note: This is because we have removed the + tablespace instance from the cache. */ + + err = DB_IO_ERROR; } - if (success) { + if (err == DB_SUCCESS) { #ifndef UNIV_HOTBACKUP /* Write a log record about the deletion of the .ibd file, so that ibbackup can replay it in the @@ -2485,14 +2701,12 @@ try_again: fil_op_write_log(MLOG_FILE_DELETE, id, 0, 0, path, NULL, &mtr); mtr_commit(&mtr); #endif - mem_free(path); - - return(TRUE); + err = DB_SUCCESS; } mem_free(path); - return(FALSE); + return(err); } /*******************************************************************//** @@ -2524,36 +2738,49 @@ fil_tablespace_is_being_deleted( /*******************************************************************//** Discards a single-table tablespace. The tablespace must be cached in the memory cache. Discarding is like deleting a tablespace, but -1) we do not drop the table from the data dictionary; -2) we remove all insert buffer entries for the tablespace immediately; in DROP -TABLE they are only removed gradually in the background; -3) when the user does IMPORT TABLESPACE, the tablespace will have the same id -as it originally had. -@return TRUE if success */ + + 1. We do not drop the table from the data dictionary; + + 2. We remove all insert buffer entries for the tablespace immediately; + in DROP TABLE they are only removed gradually in the background; + + 3. Free all the pages in use by the tablespace. +@return DB_SUCCESS or error */ UNIV_INTERN -ibool +dberr_t fil_discard_tablespace( /*===================*/ ulint id) /*!< in: space id */ { - ibool success; + dberr_t err; - success = fil_delete_tablespace(id); + switch (err = fil_delete_tablespace(id, BUF_REMOVE_ALL_NO_WRITE)) { + case DB_SUCCESS: + break; - if (!success) { - fprintf(stderr, - "InnoDB: Warning: cannot delete tablespace %lu" - " in DISCARD TABLESPACE.\n" - "InnoDB: But let us remove the" - " insert buffer entries for this tablespace.\n", - (ulong) id); + case DB_IO_ERROR: + ib_logf(IB_LOG_LEVEL_WARN, + "While deleting tablespace %lu in DISCARD TABLESPACE." + " File rename/delete failed: %s", + (ulong) id, ut_strerr(err)); + break; + + case DB_TABLESPACE_NOT_FOUND: + ib_logf(IB_LOG_LEVEL_WARN, + "Cannot delete tablespace %lu in DISCARD " + "TABLESPACE. %s", + (ulong) id, ut_strerr(err)); + break; + + default: + ut_error; } /* Remove all insert buffer entries for the tablespace */ ibuf_delete_for_discarded_space(id); - return(success); + return(err); } #endif /* !UNIV_HOTBACKUP */ @@ -2609,30 +2836,27 @@ fil_rename_tablespace_in_mem( Allocates a file name for a single-table tablespace. The string must be freed by caller with mem_free(). @return own: file name */ -static +UNIV_INTERN char* fil_make_ibd_name( /*==============*/ - const char* name, /*!< in: table name or a dir path of a - TEMPORARY table */ - ibool is_temp) /*!< in: TRUE if it is a dir path */ + const char* name, /*!< in: table name or a dir path */ + bool is_full_path) /*!< in: TRUE if it is a dir path */ { char* filename; ulint namelen = strlen(name); ulint dirlen = strlen(fil_path_to_mysql_datadir); + ulint pathlen = dirlen + namelen + sizeof "/.ibd"; - filename = static_cast<char*>( - mem_alloc(namelen + dirlen + sizeof "/.ibd")); + filename = static_cast<char*>(mem_alloc(pathlen)); - if (is_temp) { + if (is_full_path) { memcpy(filename, name, namelen); memcpy(filename + namelen, ".ibd", sizeof ".ibd"); } else { - memcpy(filename, fil_path_to_mysql_datadir, dirlen); - filename[dirlen] = '/'; + ut_snprintf(filename, pathlen, "%s/%s.ibd", + fil_path_to_mysql_datadir, name); - memcpy(filename + dirlen + 1, name, namelen); - memcpy(filename + dirlen + namelen + 1, ".ibd", sizeof ".ibd"); } srv_normalize_path_for_win(filename); @@ -2641,6 +2865,31 @@ fil_make_ibd_name( } /*******************************************************************//** +Allocates a file name for a tablespace ISL file (InnoDB Symbolic Link). +The string must be freed by caller with mem_free(). +@return own: file name */ +UNIV_INTERN +char* +fil_make_isl_name( +/*==============*/ + const char* name) /*!< in: table name */ +{ + char* filename; + ulint namelen = strlen(name); + ulint dirlen = strlen(fil_path_to_mysql_datadir); + ulint pathlen = dirlen + namelen + sizeof "/.isl"; + + filename = static_cast<char*>(mem_alloc(pathlen)); + + ut_snprintf(filename, pathlen, "%s/%s.isl", + fil_path_to_mysql_datadir, name); + + srv_normalize_path_for_win(filename); + + return(filename); +} + +/*******************************************************************//** Renames a single-table tablespace. The tablespace must be cached in the tablespace memory cache. @return TRUE if success */ @@ -2648,14 +2897,19 @@ UNIV_INTERN ibool fil_rename_tablespace( /*==================*/ - const char* old_name_in, /*!< in: old table name in the standard - databasename/tablename format of - InnoDB, or NULL if we do the rename - based on the space id only */ + const char* old_name_in, /*!< in: old table name in the + standard databasename/tablename + format of InnoDB, or NULL if we + do the rename based on the space + id only */ ulint id, /*!< in: space id */ - const char* new_name) /*!< in: new table name in the standard - databasename/tablename format - of InnoDB */ + const char* new_name, /*!< in: new table name in the + standard databasename/tablename + format of InnoDB */ + const char* new_path_in) /*!< in: new full datafile path + if the tablespace is remotely + located, or NULL if it is located + in the normal data directory. */ { ibool success; fil_space_t* space; @@ -2685,14 +2939,14 @@ retry: space = fil_space_get_by_id(id); + DBUG_EXECUTE_IF("fil_rename_tablespace_failure_1", space = NULL; ); + if (space == NULL) { - fprintf(stderr, - "InnoDB: Error: cannot find space id %lu" - " in the tablespace memory cache\n" - "InnoDB: though the table ", (ulong) id); - ut_print_filename(stderr, - old_name_in ? old_name_in : not_given); - fputs(" in a rename operation should have that id\n", stderr); + ib_logf(IB_LOG_LEVEL_ERROR, + "Cannot find space id %lu in the tablespace " + "memory cache, though the table '%s' in a " + "rename operation should have that id.", + (ulong) id, old_name_in ? old_name_in : not_given); mutex_exit(&fil_system->mutex); return(FALSE); @@ -2711,10 +2965,13 @@ retry: space->stop_ios = TRUE; + /* The following code must change when InnoDB supports + multiple datafiles per tablespace. */ ut_a(UT_LIST_GET_LEN(space->chain) == 1); node = UT_LIST_GET_FIRST(space->chain); - if (node->n_pending > 0 || node->n_pending_flushes > 0 + if (node->n_pending > 0 + || node->n_pending_flushes > 0 || node->being_extended) { /* There are pending i/o's or flushes or the file is currently being extended, sleep for a while and @@ -2747,24 +3004,31 @@ retry: if (old_name_in) { old_name = mem_strdup(old_name_in); - old_path = fil_make_ibd_name(old_name, FALSE); - ut_a(strcmp(space->name, old_name) == 0); - ut_a(strcmp(node->name, old_path) == 0); } else { old_name = mem_strdup(space->name); - old_path = mem_strdup(node->name); } + old_path = mem_strdup(node->name); /* Rename the tablespace and the node in the memory cache */ - new_path = fil_make_ibd_name(new_name, FALSE); + new_path = new_path_in ? mem_strdup(new_path_in) + : fil_make_ibd_name(new_name, false); + success = fil_rename_tablespace_in_mem( space, node, new_name, new_path); if (success) { + + DBUG_EXECUTE_IF("fil_rename_tablespace_failure_2", + goto skip_second_rename; ); + success = os_file_rename( innodb_file_data_key, old_path, new_path); + DBUG_EXECUTE_IF("fil_rename_tablespace_failure_2", +skip_second_rename: + success = FALSE; ); + if (!success) { /* We have to revert the changes we made to the tablespace memory cache */ @@ -2788,7 +3052,7 @@ retry: &mtr); mtr_commit(&mtr); } -#endif +#endif /* !UNIV_HOTBACKUP */ mem_free(new_path); mem_free(old_path); @@ -2798,23 +3062,202 @@ retry: } /*******************************************************************//** +Creates a new InnoDB Symbolic Link (ISL) file. It is always created +under the 'datadir' of MySQL. The datadir is the directory of a +running mysqld program. We can refer to it by simply using the path '.'. +@return DB_SUCCESS or error code */ +UNIV_INTERN +dberr_t +fil_create_link_file( +/*=================*/ + const char* tablename, /*!< in: tablename */ + const char* filepath) /*!< in: pathname of tablespace */ +{ + os_file_t file; + ibool success; + dberr_t err = DB_SUCCESS; + char* link_filepath; + char* prev_filepath = fil_read_link_file(tablename); + + ut_ad(!srv_read_only_mode); + + if (prev_filepath) { + /* Truncate will call this with an existing + link file which contains the same filepath. */ + if (0 == strcmp(prev_filepath, filepath)) { + mem_free(prev_filepath); + return(DB_SUCCESS); + } + mem_free(prev_filepath); + } + + link_filepath = fil_make_isl_name(tablename); + + file = os_file_create_simple_no_error_handling( + innodb_file_data_key, link_filepath, + OS_FILE_CREATE, OS_FILE_READ_WRITE, &success); + + if (!success) { + /* The following call will print an error message */ + ulint error = os_file_get_last_error(true); + + ut_print_timestamp(stderr); + fputs(" InnoDB: Cannot create file ", stderr); + ut_print_filename(stderr, link_filepath); + fputs(".\n", stderr); + + if (error == OS_FILE_ALREADY_EXISTS) { + fputs("InnoDB: The link file: ", stderr); + ut_print_filename(stderr, filepath); + fputs(" already exists.\n", stderr); + err = DB_TABLESPACE_EXISTS; + + } else if (error == OS_FILE_DISK_FULL) { + err = DB_OUT_OF_FILE_SPACE; + + } else { + err = DB_ERROR; + } + + /* file is not open, no need to close it. */ + mem_free(link_filepath); + return(err); + } + + if (!os_file_write(link_filepath, file, filepath, 0, + strlen(filepath))) { + err = DB_ERROR; + } + + /* Close the file, we only need it at startup */ + os_file_close(file); + + mem_free(link_filepath); + + return(err); +} + +/*******************************************************************//** +Deletes an InnoDB Symbolic Link (ISL) file. */ +UNIV_INTERN +void +fil_delete_link_file( +/*=================*/ + const char* tablename) /*!< in: name of table */ +{ + char* link_filepath = fil_make_isl_name(tablename); + + os_file_delete_if_exists(link_filepath); + + mem_free(link_filepath); +} + +/*******************************************************************//** +Reads an InnoDB Symbolic Link (ISL) file. +It is always created under the 'datadir' of MySQL. The name is of the +form {databasename}/{tablename}. and the isl file is expected to be in a +'{databasename}' directory called '{tablename}.isl'. The caller must free +the memory of the null-terminated path returned if it is not null. +@return own: filepath found in link file, NULL if not found. */ +UNIV_INTERN +char* +fil_read_link_file( +/*===============*/ + const char* name) /*!< in: tablespace name */ +{ + char* filepath = NULL; + char* link_filepath; + FILE* file = NULL; + + /* The .isl file is in the 'normal' tablespace location. */ + link_filepath = fil_make_isl_name(name); + + file = fopen(link_filepath, "r+b"); + + mem_free(link_filepath); + + if (file) { + filepath = static_cast<char*>(mem_alloc(OS_FILE_MAX_PATH)); + + os_file_read_string(file, filepath, OS_FILE_MAX_PATH); + fclose(file); + + if (strlen(filepath)) { + /* Trim whitespace from end of filepath */ + ulint lastch = strlen(filepath) - 1; + while (lastch > 4 && filepath[lastch] <= 0x20) { + filepath[lastch--] = 0x00; + } + srv_normalize_path_for_win(filepath); + } + } + + return(filepath); +} + +/*******************************************************************//** +Opens a handle to the file linked to in an InnoDB Symbolic Link file. +@return TRUE if remote linked tablespace file is found and opened. */ +UNIV_INTERN +ibool +fil_open_linked_file( +/*===============*/ + const char* tablename, /*!< in: database/tablename */ + char** remote_filepath,/*!< out: remote filepath */ + os_file_t* remote_file) /*!< out: remote file handle */ + +{ + ibool success; + + *remote_filepath = fil_read_link_file(tablename); + if (*remote_filepath == NULL) { + return(FALSE); + } + + /* The filepath provided is different from what was + found in the link file. */ + *remote_file = os_file_create_simple_no_error_handling( + innodb_file_data_key, *remote_filepath, + OS_FILE_OPEN, OS_FILE_READ_ONLY, + &success); + + if (!success) { + char* link_filepath = fil_make_isl_name(tablename); + + /* The following call prints an error message */ + os_file_get_last_error(true); + + ib_logf(IB_LOG_LEVEL_ERROR, + "A link file was found named '%s' " + "but the linked tablespace '%s' " + "could not be opened.", + link_filepath, *remote_filepath); + + mem_free(link_filepath); + mem_free(*remote_filepath); + *remote_filepath = NULL; + } + + return(success); +} + +/*******************************************************************//** Creates a new single-table tablespace to a database directory of MySQL. Database directories are under the 'datadir' of MySQL. The datadir is the directory of a running mysqld program. We can refer to it by simply the path '.'. Tables created with CREATE TEMPORARY TABLE we place in the temp dir of the mysqld server. + @return DB_SUCCESS or error code */ UNIV_INTERN -ulint +dberr_t fil_create_new_single_table_tablespace( /*===================================*/ ulint space_id, /*!< in: space id */ const char* tablename, /*!< in: the table name in the usual databasename/tablename format - of InnoDB, or a dir path to a temp - table */ - ibool is_temp, /*!< in: TRUE if a table created with - CREATE TEMPORARY TABLE */ + of InnoDB */ + const char* dir_path, /*!< in: NULL or a dir path */ ulint flags, /*!< in: tablespace flags */ ulint flags2, /*!< in: table flags2 */ ulint size) /*!< in: the initial size of the @@ -2823,18 +3266,40 @@ fil_create_new_single_table_tablespace( { os_file_t file; ibool ret; - ulint err; + dberr_t err; byte* buf2; byte* page; char* path; ibool success; + /* TRUE if a table is created with CREATE TEMPORARY TABLE */ + bool is_temp = !!(flags2 & DICT_TF2_TEMPORARY); + bool has_data_dir = FSP_FLAGS_HAS_DATA_DIR(flags); ut_a(space_id > 0); + ut_ad(!srv_read_only_mode); ut_a(space_id < SRV_LOG_SPACE_FIRST_ID); ut_a(size >= FIL_IBD_FILE_INITIAL_SIZE); - fsp_flags_validate(flags); + ut_a(fsp_flags_is_valid(flags)); - path = fil_make_ibd_name(tablename, is_temp); + if (is_temp) { + /* Temporary table filepath */ + ut_ad(dir_path); + path = fil_make_ibd_name(dir_path, true); + } else if (has_data_dir) { + ut_ad(dir_path); + path = os_file_make_remote_pathname(dir_path, tablename, "ibd"); + + /* Since this tablespace file will be created in a + remote directory, let's create the subdirectories + in the path, if they are not there already. */ + success = os_file_create_subdirs_if_needed(path); + if (!success) { + err = DB_ERROR; + goto error_exit_3; + } + } else { + path = fil_make_ibd_name(tablename, false); + } file = os_file_create( innodb_file_data_key, path, @@ -2844,58 +3309,44 @@ fil_create_new_single_table_tablespace( &ret); if (ret == FALSE) { - ut_print_timestamp(stderr); - fputs(" InnoDB: Error creating file ", stderr); - ut_print_filename(stderr, path); - fputs(".\n", stderr); - /* The following call will print an error message */ - - err = os_file_get_last_error(TRUE); - - if (err == OS_FILE_ALREADY_EXISTS) { - fputs("InnoDB: The file already exists though" - " the corresponding table did not\n" - "InnoDB: exist in the InnoDB data dictionary." - " Have you moved InnoDB\n" - "InnoDB: .ibd files around without using the" - " SQL commands\n" - "InnoDB: DISCARD TABLESPACE and" - " IMPORT TABLESPACE, or did\n" - "InnoDB: mysqld crash in the middle of" - " CREATE TABLE? You can\n" - "InnoDB: resolve the problem by" - " removing the file ", stderr); - ut_print_filename(stderr, path); - fputs("\n" - "InnoDB: under the 'datadir' of MySQL.\n", - stderr); - - mem_free(path); - return(DB_TABLESPACE_ALREADY_EXISTS); + ulint error = os_file_get_last_error(true); + + ib_logf(IB_LOG_LEVEL_ERROR, + "Cannot create file '%s'\n", path); + + if (error == OS_FILE_ALREADY_EXISTS) { + ib_logf(IB_LOG_LEVEL_ERROR, + "The file '%s' already exists though the " + "corresponding table did not exist " + "in the InnoDB data dictionary. " + "Have you moved InnoDB .ibd files " + "around without using the SQL commands " + "DISCARD TABLESPACE and IMPORT TABLESPACE, " + "or did mysqld crash in the middle of " + "CREATE TABLE? " + "You can resolve the problem by removing " + "the file '%s' under the 'datadir' of MySQL.", + path, path); + + err = DB_TABLESPACE_EXISTS; + goto error_exit_3; } - if (err == OS_FILE_DISK_FULL) { - - mem_free(path); - return(DB_OUT_OF_FILE_SPACE); + if (error == OS_FILE_DISK_FULL) { + err = DB_OUT_OF_FILE_SPACE; + goto error_exit_3; } - mem_free(path); - return(DB_ERROR); + err = DB_ERROR; + goto error_exit_3; } ret = os_file_set_size(path, file, size * UNIV_PAGE_SIZE); if (!ret) { err = DB_OUT_OF_FILE_SPACE; -error_exit: - os_file_close(file); -error_exit2: - os_file_delete(path); - - mem_free(path); - return(err); + goto error_exit_2; } /* printf("Creating tablespace %s id %lu\n", path, space_id); */ @@ -2944,356 +3395,486 @@ error_exit2: ut_free(buf2); if (!ret) { - fputs("InnoDB: Error: could not write the first page" - " to tablespace ", stderr); - ut_print_filename(stderr, path); - putc('\n', stderr); + ib_logf(IB_LOG_LEVEL_ERROR, + "Could not write the first page to tablespace " + "'%s'", path); + err = DB_ERROR; - goto error_exit; + goto error_exit_2; } ret = os_file_flush(file); if (!ret) { - fputs("InnoDB: Error: file flush of tablespace ", stderr); - ut_print_filename(stderr, path); - fputs(" failed\n", stderr); + ib_logf(IB_LOG_LEVEL_ERROR, + "File flush of tablespace '%s' failed", path); err = DB_ERROR; - goto error_exit; + goto error_exit_2; } - os_file_close(file); + if (has_data_dir) { + /* Now that the IBD file is created, make the ISL file. */ + err = fil_create_link_file(tablename, path); + if (err != DB_SUCCESS) { + goto error_exit_2; + } + } success = fil_space_create(tablename, space_id, flags, FIL_TABLESPACE); - - if (!success) { + if (!success || !fil_node_create(path, size, space_id, FALSE)) { err = DB_ERROR; - goto error_exit2; + goto error_exit_1; } - fil_node_create(path, size, space_id, FALSE); - #ifndef UNIV_HOTBACKUP { mtr_t mtr; + ulint mlog_file_flag = 0; + + if (is_temp) { + mlog_file_flag |= MLOG_FILE_FLAG_TEMP; + } mtr_start(&mtr); fil_op_write_log(flags ? MLOG_FILE_CREATE2 : MLOG_FILE_CREATE, - space_id, - is_temp ? MLOG_FILE_FLAG_TEMP : 0, - flags, + space_id, mlog_file_flag, flags, tablename, NULL, &mtr); mtr_commit(&mtr); } #endif + err = DB_SUCCESS; + + /* Error code is set. Cleanup the various variables used. + These labels reflect the order in which variables are assigned or + actions are done. */ +error_exit_1: + if (has_data_dir && err != DB_SUCCESS) { + fil_delete_link_file(tablename); + } +error_exit_2: + os_file_close(file); + if (err != DB_SUCCESS) { + os_file_delete(path); + } +error_exit_3: mem_free(path); - return(DB_SUCCESS); + + return(err); } #ifndef UNIV_HOTBACKUP /********************************************************************//** -It is possible, though very improbable, that the lsn's in the tablespace to be -imported have risen above the current system lsn, if a lengthy purge, ibuf -merge, or rollback was performed on a backup taken with ibbackup. If that is -the case, reset page lsn's in the file. We assume that mysqld was shut down -after it performed these cleanup operations on the .ibd file, so that it at -the shutdown stamped the latest lsn to the FIL_PAGE_FILE_FLUSH_LSN in the -first page of the .ibd file, and we can determine whether we need to reset the -lsn's just by looking at that flush lsn. -@return TRUE if success */ -UNIV_INTERN -ibool -fil_reset_too_high_lsns( -/*====================*/ - const char* name, /*!< in: table name in the - databasename/tablename format */ - lsn_t current_lsn) /*!< in: reset lsn's if the lsn stamped - to FIL_PAGE_FILE_FLUSH_LSN in the - first page is too high */ +Report information about a bad tablespace. */ +static +void +fil_report_bad_tablespace( +/*======================*/ + char* filepath, /*!< in: filepath */ + ulint found_id, /*!< in: found space ID */ + ulint found_flags, /*!< in: found flags */ + ulint expected_id, /*!< in: expected space id */ + ulint expected_flags) /*!< in: expected flags */ { - os_file_t file; - char* filepath; - byte* page; - byte* buf2; - lsn_t flush_lsn; - ulint space_id; - os_offset_t file_size; - os_offset_t offset; - ulint zip_size; - ibool success; - page_zip_des_t page_zip; - - filepath = fil_make_ibd_name(name, FALSE); - - file = os_file_create_simple_no_error_handling( - innodb_file_data_key, filepath, OS_FILE_OPEN, - OS_FILE_READ_WRITE, &success); - if (!success) { - /* The following call prints an error message */ - os_file_get_last_error(TRUE); - - ut_print_timestamp(stderr); + ib_logf(IB_LOG_LEVEL_ERROR, + "In file '%s', tablespace id and flags are %lu and %lu, " + "but in the InnoDB data dictionary they are %lu and %lu. " + "Have you moved InnoDB .ibd files around without using the " + "commands DISCARD TABLESPACE and IMPORT TABLESPACE? " + "Please refer to " + REFMAN "innodb-troubleshooting-datadict.html " + "for how to resolve the issue.", + filepath, (ulong) found_id, (ulong) found_flags, + (ulong) expected_id, (ulong) expected_flags); +} - fputs(" InnoDB: Error: trying to open a table," - " but could not\n" - "InnoDB: open the tablespace file ", stderr); - ut_print_filename(stderr, filepath); - fputs("!\n", stderr); - mem_free(filepath); +struct fsp_open_info { + ibool success; /*!< Has the tablespace been opened? */ + ibool valid; /*!< Is the tablespace valid? */ + os_file_t file; /*!< File handle */ + char* filepath; /*!< File path to open */ + lsn_t lsn; /*!< Flushed LSN from header page */ + ulint id; /*!< Space ID */ + ulint flags; /*!< Tablespace flags */ +#ifdef UNIV_LOG_ARCHIVE + ulint arch_log_no; /*!< latest archived log file number */ +#endif /* UNIV_LOG_ARCHIVE */ +}; - return(FALSE); - } +/********************************************************************//** +Tries to open a single-table tablespace and optionally checks that the +space id in it is correct. If this does not succeed, print an error message +to the .err log. This function is used to open a tablespace when we start +mysqld after the dictionary has been booted, and also in IMPORT TABLESPACE. - /* Read the first page of the tablespace */ +NOTE that we assume this operation is used either at the database startup +or under the protection of the dictionary mutex, so that two users cannot +race here. This operation does not leave the file associated with the +tablespace open, but closes it after we have looked at the space id in it. - buf2 = static_cast<byte*>(ut_malloc(3 * UNIV_PAGE_SIZE)); - /* Align the memory for file i/o if we might have O_DIRECT set */ - page = static_cast<byte*>(ut_align(buf2, UNIV_PAGE_SIZE)); +If the validate boolean is set, we read the first page of the file and +check that the space id in the file is what we expect. We assume that +this function runs much faster if no check is made, since accessing the +file inode probably is much faster (the OS caches them) than accessing +the first page of the file. This boolean may be initially FALSE, but if +a remote tablespace is found it will be changed to true. - success = os_file_read(file, page, 0, UNIV_PAGE_SIZE); - if (!success) { +If the fix_dict boolean is set, then it is safe to use an internal SQL +statement to update the dictionary tables if they are incorrect. - goto func_exit; +@return DB_SUCCESS or error code */ +UNIV_INTERN +dberr_t +fil_open_single_table_tablespace( +/*=============================*/ + bool validate, /*!< in: Do we validate tablespace? */ + bool fix_dict, /*!< in: Can we fix the dictionary? */ + ulint id, /*!< in: space id */ + ulint flags, /*!< in: tablespace flags */ + const char* tablename, /*!< in: table name in the + databasename/tablename format */ + const char* path_in) /*!< in: tablespace filepath */ +{ + dberr_t err = DB_SUCCESS; + bool dict_filepath_same_as_default = false; + bool link_file_found = false; + bool link_file_is_bad = false; + fsp_open_info def; + fsp_open_info dict; + fsp_open_info remote; + ulint tablespaces_found = 0; + ulint valid_tablespaces_found = 0; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(!fix_dict || rw_lock_own(&dict_operation_lock, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + ut_ad(!fix_dict || mutex_own(&(dict_sys->mutex))); + + if (!fsp_flags_is_valid(flags)) { + return(DB_CORRUPTION); + } + + /* If the tablespace was relocated, we do not + compare the DATA_DIR flag */ + ulint mod_flags = flags & ~FSP_FLAGS_MASK_DATA_DIR; + + memset(&def, 0, sizeof(def)); + memset(&dict, 0, sizeof(dict)); + memset(&remote, 0, sizeof(remote)); + + /* Discover the correct filepath. We will always look for an ibd + in the default location. If it is remote, it should not be here. */ + def.filepath = fil_make_ibd_name(tablename, false); + + /* The path_in was read from SYS_DATAFILES. */ + if (path_in) { + if (strcmp(def.filepath, path_in)) { + dict.filepath = mem_strdup(path_in); + /* possibility of multiple files. */ + validate = true; + } else { + dict_filepath_same_as_default = true; + } } - /* We have to read the file flush lsn from the header of the file */ - - flush_lsn = mach_read_from_8(page + FIL_PAGE_FILE_FLUSH_LSN); + link_file_found = fil_open_linked_file( + tablename, &remote.filepath, &remote.file); + remote.success = link_file_found; + if (remote.success) { + /* possibility of multiple files. */ + validate = true; + tablespaces_found++; + + /* A link file was found. MySQL does not allow a DATA + DIRECTORY to be be the same as the default filepath. */ + ut_a(strcmp(def.filepath, remote.filepath)); + + /* If there was a filepath found in SYS_DATAFILES, + we hope it was the same as this remote.filepath found + in the ISL file. */ + if (dict.filepath + && (0 == strcmp(dict.filepath, remote.filepath))) { + remote.success = FALSE; + os_file_close(remote.file); + mem_free(remote.filepath); + remote.filepath = NULL; + tablespaces_found--; + } + } - if (current_lsn >= flush_lsn) { - /* Ok */ - success = TRUE; + /* Attempt to open the tablespace at other possible filepaths. */ + if (dict.filepath) { + dict.file = os_file_create_simple_no_error_handling( + innodb_file_data_key, dict.filepath, OS_FILE_OPEN, + OS_FILE_READ_ONLY, &dict.success); + if (dict.success) { + /* possibility of multiple files. */ + validate = true; + tablespaces_found++; + } + } - goto func_exit; + /* Always look for a file at the default location. */ + ut_a(def.filepath); + def.file = os_file_create_simple_no_error_handling( + innodb_file_data_key, def.filepath, OS_FILE_OPEN, + OS_FILE_READ_ONLY, &def.success); + if (def.success) { + tablespaces_found++; } - space_id = fsp_header_get_space_id(page); - zip_size = fsp_header_get_zip_size(page); + /* We have now checked all possible tablespace locations and + have a count of how many we found. If things are normal, we + only found 1. */ + if (!validate && tablespaces_found == 1) { + goto skip_validate; + } - page_zip_des_init(&page_zip); - page_zip_set_size(&page_zip, zip_size); - if (zip_size) { - page_zip.data = page + UNIV_PAGE_SIZE; + /* Read the first page of the datadir tablespace, if found. */ + if (def.success) { + fil_read_first_page( + def.file, FALSE, &def.flags, &def.id, +#ifdef UNIV_LOG_ARCHIVE + &space_arch_log_no, &space_arch_log_no, +#endif /* UNIV_LOG_ARCHIVE */ + &def.lsn, &def.lsn); + + /* Validate this single-table-tablespace with SYS_TABLES, + but do not compare the DATA_DIR flag, in case the + tablespace was relocated. */ + ulint mod_def_flags = def.flags & ~FSP_FLAGS_MASK_DATA_DIR; + if (def.id == id && mod_def_flags == mod_flags) { + valid_tablespaces_found++; + def.valid = TRUE; + } else { + /* Do not use this tablespace. */ + fil_report_bad_tablespace( + def.filepath, def.id, + def.flags, id, flags); + } } - ut_print_timestamp(stderr); - fprintf(stderr, - " InnoDB: Flush lsn in the tablespace file %lu" - " to be imported\n" - "InnoDB: is " LSN_PF ", which exceeds current" - " system lsn " LSN_PF ".\n" - "InnoDB: We reset the lsn's in the file ", - (ulong) space_id, - flush_lsn, current_lsn); - ut_print_filename(stderr, filepath); - fputs(".\n", stderr); - - ut_a(ut_is_2pow(zip_size)); - ut_a(zip_size <= UNIV_ZIP_SIZE_MAX); - - /* Loop through all the pages in the tablespace and reset the lsn and - the page checksum if necessary */ - - file_size = os_file_get_size(file); - ut_a(file_size != (os_offset_t) -1); + /* Read the first page of the remote tablespace */ + if (remote.success) { + fil_read_first_page( + remote.file, FALSE, &remote.flags, &remote.id, +#ifdef UNIV_LOG_ARCHIVE + &remote.arch_log_no, &remote.arch_log_no, +#endif /* UNIV_LOG_ARCHIVE */ + &remote.lsn, &remote.lsn); + + /* Validate this single-table-tablespace with SYS_TABLES, + but do not compare the DATA_DIR flag, in case the + tablespace was relocated. */ + ulint mod_remote_flags = remote.flags & ~FSP_FLAGS_MASK_DATA_DIR; + if (remote.id == id && mod_remote_flags == mod_flags) { + valid_tablespaces_found++; + remote.valid = TRUE; + } else { + /* Do not use this linked tablespace. */ + fil_report_bad_tablespace( + remote.filepath, remote.id, + remote.flags, id, flags); + link_file_is_bad = true; + } + } - for (offset = 0; offset < file_size; - offset += zip_size ? zip_size : UNIV_PAGE_SIZE) { - success = os_file_read(file, page, offset, - zip_size ? zip_size : UNIV_PAGE_SIZE); - if (!success) { + /* Read the first page of the datadir tablespace, if found. */ + if (dict.success) { + fil_read_first_page( + dict.file, FALSE, &dict.flags, &dict.id, +#ifdef UNIV_LOG_ARCHIVE + &dict.arch_log_no, &dict.arch_log_no, +#endif /* UNIV_LOG_ARCHIVE */ + &dict.lsn, &dict.lsn); + + /* Validate this single-table-tablespace with SYS_TABLES, + but do not compare the DATA_DIR flag, in case the + tablespace was relocated. */ + ulint mod_dict_flags = dict.flags & ~FSP_FLAGS_MASK_DATA_DIR; + if (dict.id == id && mod_dict_flags == mod_flags) { + valid_tablespaces_found++; + dict.valid = TRUE; + } else { + /* Do not use this tablespace. */ + fil_report_bad_tablespace( + dict.filepath, dict.id, + dict.flags, id, flags); + } + } - goto func_exit; + /* Make sense of these three possible locations. + First, bail out if no tablespace files were found. */ + if (valid_tablespaces_found == 0) { + /* The following call prints an error message */ + os_file_get_last_error(true); + + ib_logf(IB_LOG_LEVEL_ERROR, + "Could not find a valid tablespace file for '%s'. " + "See " REFMAN "innodb-troubleshooting-datadict.html " + "for how to resolve the issue.", + tablename); + + err = DB_CORRUPTION; + + goto cleanup_and_exit; + } + + /* Do not open any tablespaces if more than one tablespace with + the correct space ID and flags were found. */ + if (tablespaces_found > 1) { + ib_logf(IB_LOG_LEVEL_ERROR, + "A tablespace for %s has been found in " + "multiple places;", tablename); + if (def.success) { + ib_logf(IB_LOG_LEVEL_ERROR, + "Default location; %s, LSN=" LSN_PF + ", Space ID=%lu, Flags=%lu", + def.filepath, def.lsn, + (ulong) def.id, (ulong) def.flags); + } + if (remote.success) { + ib_logf(IB_LOG_LEVEL_ERROR, + "Remote location; %s, LSN=" LSN_PF + ", Space ID=%lu, Flags=%lu", + remote.filepath, remote.lsn, + (ulong) remote.id, (ulong) remote.flags); } - if (mach_read_from_8(page + FIL_PAGE_LSN) > current_lsn) { - /* We have to reset the lsn */ - - if (zip_size) { - memcpy(page_zip.data, page, zip_size); - buf_flush_init_for_writing( - page, &page_zip, current_lsn); - success = os_file_write( - filepath, file, page_zip.data, - offset, zip_size); + if (dict.success) { + ib_logf(IB_LOG_LEVEL_ERROR, + "Dictionary location; %s, LSN=" LSN_PF + ", Space ID=%lu, Flags=%lu", + dict.filepath, dict.lsn, + (ulong) dict.id, (ulong) dict.flags); + } + + /* Force-recovery will allow some tablespaces to be + skipped by REDO if there was more than one file found. + Unlike during the REDO phase of recovery, we now know + if the tablespace is valid according to the dictionary, + which was not available then. So if we did not force + recovery and there is only one good tablespace, ignore + any bad tablespaces. */ + if (valid_tablespaces_found > 1 || srv_force_recovery > 0) { + ib_logf(IB_LOG_LEVEL_ERROR, + "Will not open the tablespace for '%s'", + tablename); + + if (def.success != def.valid + || dict.success != dict.valid + || remote.success != remote.valid) { + err = DB_CORRUPTION; } else { - buf_flush_init_for_writing( - page, NULL, current_lsn); - success = os_file_write( - filepath, file, page, - offset, UNIV_PAGE_SIZE); + err = DB_ERROR; } + goto cleanup_and_exit; + } - if (!success) { + /* There is only one valid tablespace found and we did + not use srv_force_recovery during REDO. Use this one + tablespace and clean up invalid tablespace pointers */ + if (def.success && !def.valid) { + def.success = false; + os_file_close(def.file); + tablespaces_found--; + } + if (dict.success && !dict.valid) { + dict.success = false; + os_file_close(dict.file); + /* Leave dict.filepath so that SYS_DATAFILES + can be corrected below. */ + tablespaces_found--; + } + if (remote.success && !remote.valid) { + remote.success = false; + os_file_close(remote.file); + mem_free(remote.filepath); + remote.filepath = NULL; + tablespaces_found--; + } + } - goto func_exit; + /* At this point, there should be only one filepath. */ + ut_a(tablespaces_found == 1); + ut_a(valid_tablespaces_found == 1); + + /* Only fix the dictionary at startup when there is only one thread. + Calls to dict_load_table() can be done while holding other latches. */ + if (!fix_dict) { + goto skip_validate; + } + + /* We may need to change what is stored in SYS_DATAFILES or + SYS_TABLESPACES or adjust the link file. + Since a failure to update SYS_TABLESPACES or SYS_DATAFILES does + not prevent opening and using the single_table_tablespace either + this time or the next, we do not check the return code or fail + to open the tablespace. But dict_update_filepath() will issue a + warning to the log. */ + if (dict.filepath) { + if (remote.success) { + dict_update_filepath(id, remote.filepath); + } else if (def.success) { + dict_update_filepath(id, def.filepath); + if (link_file_is_bad) { + fil_delete_link_file(tablename); } + } else if (!link_file_found || link_file_is_bad) { + ut_ad(dict.success); + /* Fix the link file if we got our filepath + from the dictionary but a link file did not + exist or it did not point to a valid file. */ + fil_delete_link_file(tablename); + fil_create_link_file(tablename, dict.filepath); } - } - success = os_file_flush(file); - if (!success) { + } else if (remote.success && dict_filepath_same_as_default) { + dict_update_filepath(id, remote.filepath); - goto func_exit; + } else if (remote.success && path_in == NULL) { + /* SYS_DATAFILES record for this space ID was not found. */ + dict_insert_tablespace_and_filepath( + id, tablename, remote.filepath, flags); } - /* We now update the flush_lsn stamp at the start of the file */ - success = os_file_read(file, page, 0, - zip_size ? zip_size : UNIV_PAGE_SIZE); - if (!success) { +skip_validate: + if (err != DB_SUCCESS) { + ; // Don't load the tablespace into the cache + } else if (!fil_space_create(tablename, id, flags, FIL_TABLESPACE)) { + err = DB_ERROR; + } else { + /* We do not measure the size of the file, that is why + we pass the 0 below */ - goto func_exit; + if (!fil_node_create(remote.success ? remote.filepath : + dict.success ? dict.filepath : + def.filepath, 0, id, FALSE)) { + err = DB_ERROR; + } } - mach_write_to_8(page + FIL_PAGE_FILE_FLUSH_LSN, current_lsn); - - success = os_file_write(filepath, file, page, 0, - zip_size ? zip_size : UNIV_PAGE_SIZE); - if (!success) { - - goto func_exit; +cleanup_and_exit: + if (remote.success) { + os_file_close(remote.file); } - success = os_file_flush(file); -func_exit: - os_file_close(file); - ut_free(buf2); - mem_free(filepath); - - return(success); -} - -/********************************************************************//** -Tries to open a single-table tablespace and optionally checks the space id is -right in it. If does not succeed, prints an error message to the .err log. This -function is used to open a tablespace when we start up mysqld, and also in -IMPORT TABLESPACE. -NOTE that we assume this operation is used either at the database startup -or under the protection of the dictionary mutex, so that two users cannot -race here. This operation does not leave the file associated with the -tablespace open, but closes it after we have looked at the space id in it. -@return TRUE if success */ -UNIV_INTERN -ibool -fil_open_single_table_tablespace( -/*=============================*/ - ibool check_space_id, /*!< in: should we check that the space - id in the file is right; we assume - that this function runs much faster - if no check is made, since accessing - the file inode probably is much - faster (the OS caches them) than - accessing the first page of the file */ - ulint id, /*!< in: space id */ - ulint flags, /*!< in: tablespace flags */ - const char* tablename) /*!< in: table name in the - databasename/tablename format */ -{ - os_file_t file; - char* filepath; - ibool success; - byte* buf2; - byte* page; - ulint space_id; - ulint space_flags; - - filepath = fil_make_ibd_name(tablename, FALSE); - - fsp_flags_validate(flags); - - file = os_file_create_simple_no_error_handling( - innodb_file_data_key, filepath, OS_FILE_OPEN, - OS_FILE_READ_ONLY, &success); - if (!success) { - /* The following call prints an error message */ - os_file_get_last_error(TRUE); - - ut_print_timestamp(stderr); - - fputs(" InnoDB: Error: trying to open a table," - " but could not\n" - "InnoDB: open the tablespace file ", stderr); - ut_print_filename(stderr, filepath); - fputs("!\n" - "InnoDB: Have you moved InnoDB .ibd files around" - " without using the\n" - "InnoDB: commands DISCARD TABLESPACE and" - " IMPORT TABLESPACE?\n" - "InnoDB: It is also possible that this is" - " a temporary table #sql...,\n" - "InnoDB: and MySQL removed the .ibd file for this.\n" - "InnoDB: Please refer to\n" - "InnoDB: " REFMAN - "innodb-troubleshooting-datadict.html\n" - "InnoDB: for how to resolve the issue.\n", stderr); - - mem_free(filepath); - - return(FALSE); + if (remote.filepath) { + mem_free(remote.filepath); } - - if (!check_space_id) { - space_id = id; - - goto skip_check; + if (dict.success) { + os_file_close(dict.file); } - - /* Read the first page of the tablespace */ - - buf2 = static_cast<byte*>(ut_malloc(2 * UNIV_PAGE_SIZE)); - /* Align the memory for file i/o if we might have O_DIRECT set */ - page = static_cast<byte*>(ut_align(buf2, UNIV_PAGE_SIZE)); - - success = os_file_read(file, page, 0, UNIV_PAGE_SIZE); - - /* We have to read the tablespace id and flags from the file. */ - - space_id = fsp_header_get_space_id(page); - space_flags = fsp_header_get_flags(page); - - ut_free(buf2); - - if (UNIV_UNLIKELY(space_id != id || space_flags != flags)) { - ut_print_timestamp(stderr); - - fputs(" InnoDB: Error: tablespace id and flags in file ", - stderr); - ut_print_filename(stderr, filepath); - fprintf(stderr, " are %lu and %lu, but in the InnoDB\n" - "InnoDB: data dictionary they are %lu and %lu.\n" - "InnoDB: Have you moved InnoDB .ibd files" - " around without using the\n" - "InnoDB: commands DISCARD TABLESPACE and" - " IMPORT TABLESPACE?\n" - "InnoDB: Please refer to\n" - "InnoDB: " REFMAN "innodb-troubleshooting-datadict.html\n" - "InnoDB: for how to resolve the issue.\n", - (ulong) space_id, (ulong) space_flags, - (ulong) id, (ulong) flags); - - success = FALSE; - - goto func_exit; + if (dict.filepath) { + mem_free(dict.filepath); } - -skip_check: - success = fil_space_create(tablename, space_id, flags, FIL_TABLESPACE); - - if (!success) { - goto func_exit; + if (def.success) { + os_file_close(def.file); } + mem_free(def.filepath); - /* We do not measure the size of the file, that is why we pass the 0 - below */ - - fil_node_create(filepath, 0, space_id, FALSE); -func_exit: - os_file_close(file); - mem_free(filepath); - - return(success); + return(err); } #endif /* !UNIV_HOTBACKUP */ @@ -3316,13 +3897,64 @@ fil_make_ibbackup_old_name( memcpy(path, name, len); memcpy(path + len, suffix, (sizeof suffix) - 1); - ut_sprintf_timestamp_without_extra_chars(path + len + sizeof suffix); + ut_sprintf_timestamp_without_extra_chars( + path + len + ((sizeof suffix) - 1)); return(path); } #endif /* UNIV_HOTBACKUP */ /********************************************************************//** Opens an .ibd file and adds the associated single-table tablespace to the +InnoDB fil0fil.cc data structures. +Set fsp->success to TRUE if tablespace is valid, FALSE if not. */ +static +void +fil_validate_single_table_tablespace( +/*=================================*/ + const char* tablename, /*!< in: database/tablename */ + fsp_open_info* fsp) /*!< in/out: tablespace info */ +{ + fil_read_first_page( + fsp->file, FALSE, &fsp->flags, &fsp->id, +#ifdef UNIV_LOG_ARCHIVE + &fsp->arch_log_no, &fsp->arch_log_no, +#endif /* UNIV_LOG_ARCHIVE */ + &fsp->lsn, &fsp->lsn); + + if (fsp->id == ULINT_UNDEFINED || fsp->id == 0) { + fprintf(stderr, + " InnoDB: Error: Tablespace is not sensible;" + " Table: %s Space ID: %lu Filepath: %s\n", + tablename, (ulong) fsp->id, fsp->filepath); + fsp->success = FALSE; + return; + } + + mutex_enter(&fil_system->mutex); + fil_space_t* space = fil_space_get_by_id(fsp->id); + mutex_exit(&fil_system->mutex); + if (space != NULL) { + char* prev_filepath = fil_space_get_first_path(fsp->id); + + ib_logf(IB_LOG_LEVEL_ERROR, + "Attempted to open a previously opened tablespace. " + "Previous tablespace %s uses space ID: %lu at " + "filepath: %s. Cannot open tablespace %s which uses " + "space ID: %lu at filepath: %s", + space->name, (ulong) space->id, prev_filepath, + tablename, (ulong) fsp->id, fsp->filepath); + + mem_free(prev_filepath); + fsp->success = FALSE; + return; + } + + fsp->success = TRUE; +} + + +/********************************************************************//** +Opens an .ibd file and adds the associated single-table tablespace to the InnoDB fil0fil.cc data structures. */ static void @@ -3330,34 +3962,49 @@ fil_load_single_table_tablespace( /*=============================*/ const char* dbname, /*!< in: database name */ const char* filename) /*!< in: file name (not a path), - including the .ibd extension */ + including the .ibd or .isl extension */ { - os_file_t file; - char* filepath; char* tablename; - ibool success; - byte* buf2; - byte* page; - ulint space_id; - ulint flags; + ulint tablename_len; + ulint dbname_len = strlen(dbname); + ulint filename_len = strlen(filename); + fsp_open_info def; + fsp_open_info remote; os_offset_t size; #ifdef UNIV_HOTBACKUP fil_space_t* space; #endif - filepath = static_cast<char*>( - mem_alloc( - strlen(dbname) - + strlen(filename) - + strlen(fil_path_to_mysql_datadir) + 3)); - sprintf(filepath, "%s/%s/%s", fil_path_to_mysql_datadir, dbname, - filename); - srv_normalize_path_for_win(filepath); + memset(&def, 0, sizeof(def)); + memset(&remote, 0, sizeof(remote)); + /* The caller assured that the extension is ".ibd" or ".isl". */ + ut_ad(0 == memcmp(filename + filename_len - 4, ".ibd", 4) + || 0 == memcmp(filename + filename_len - 4, ".isl", 4)); + + /* Build up the tablename in the standard form database/table. */ tablename = static_cast<char*>( - mem_alloc(strlen(dbname) + strlen(filename) + 2)); + mem_alloc(dbname_len + filename_len + 2)); sprintf(tablename, "%s/%s", dbname, filename); - tablename[strlen(tablename) - strlen(".ibd")] = 0; + tablename_len = strlen(tablename) - strlen(".ibd"); + tablename[tablename_len] = '\0'; + + /* There may be both .ibd and .isl file in the directory. + And it is possible that the .isl file refers to a different + .ibd file. If so, we open and compare them the first time + one of them is sent to this function. So if this table has + already been loaded, there is nothing to do.*/ + mutex_enter(&fil_system->mutex); + if (fil_space_get_by_name(tablename)) { + mem_free(tablename); + mutex_exit(&fil_system->mutex); + return; + } + mutex_exit(&fil_system->mutex); + + /* Build up the filepath of the .ibd tablespace in the datadir. + This must be freed independent of def.success. */ + def.filepath = fil_make_ibd_name(tablename, false); #ifdef __WIN__ # ifndef UNIV_HOTBACKUP @@ -3367,31 +4014,56 @@ fil_load_single_table_tablespace( file path to lower case, so that we are consistent with InnoDB's internal data dictionary. */ - dict_casedn_str(filepath); + dict_casedn_str(def.filepath); # endif /* !UNIV_HOTBACKUP */ #endif - file = os_file_create_simple_no_error_handling( - innodb_file_data_key, filepath, OS_FILE_OPEN, - OS_FILE_READ_ONLY, &success); - if (!success) { - /* The following call prints an error message */ - os_file_get_last_error(TRUE); + /* Check for a link file which locates a remote tablespace. */ + remote.success = fil_open_linked_file( + tablename, &remote.filepath, &remote.file); + + /* Read the first page of the remote tablespace */ + if (remote.success) { + fil_validate_single_table_tablespace(tablename, &remote); + if (!remote.success) { + os_file_close(remote.file); + mem_free(remote.filepath); + } + } + + + /* Try to open the tablespace in the datadir. */ + def.file = os_file_create_simple_no_error_handling( + innodb_file_data_key, def.filepath, OS_FILE_OPEN, + OS_FILE_READ_ONLY, &def.success); + + /* Read the first page of the remote tablespace */ + if (def.success) { + fil_validate_single_table_tablespace(tablename, &def); + if (!def.success) { + os_file_close(def.file); + } + } + + if (!def.success && !remote.success) { + /* The following call prints an error message */ + os_file_get_last_error(true); + fprintf(stderr, + "InnoDB: Error: could not open single-table" + " tablespace file %s\n", def.filepath); +no_good_file: fprintf(stderr, - "InnoDB: Error: could not open single-table tablespace" - " file\n" - "InnoDB: %s!\n" "InnoDB: We do not continue the crash recovery," " because the table may become\n" - "InnoDB: corrupt if we cannot apply the log records" - " in the InnoDB log to it.\n" + "InnoDB: corrupt if we cannot apply the log" + " records in the InnoDB log to it.\n" "InnoDB: To fix the problem and start mysqld:\n" "InnoDB: 1) If there is a permission problem" " in the file and mysqld cannot\n" "InnoDB: open the file, you should" " modify the permissions.\n" - "InnoDB: 2) If the table is not needed, or you can" - " restore it from a backup,\n" + "InnoDB: 2) If the table is not needed, or you" + " can restore it from a backup,\n" "InnoDB: then you can remove the .ibd file," " and InnoDB will do a normal\n" "InnoDB: crash recovery and ignore that table.\n" @@ -3400,123 +4072,84 @@ fil_load_single_table_tablespace( "InnoDB: the .ibd file, you can set" " innodb_force_recovery > 0 in my.cnf\n" "InnoDB: and force InnoDB to continue crash" - " recovery here.\n", filepath); - + " recovery here.\n"); +will_not_choose: mem_free(tablename); - mem_free(filepath); - - if (srv_force_recovery > 0) { - fprintf(stderr, - "InnoDB: innodb_force_recovery" - " was set to %lu. Continuing crash recovery\n" - "InnoDB: even though we cannot access" - " the .ibd file of this table.\n", - srv_force_recovery); - return; + if (remote.success) { + mem_free(remote.filepath); } - - exit(1); - } - - size = os_file_get_size(file); - - if (UNIV_UNLIKELY(size == (os_offset_t) -1)) { - /* The following call prints an error message */ - os_file_get_last_error(TRUE); - - fprintf(stderr, - "InnoDB: Error: could not measure the size" - " of single-table tablespace file\n" - "InnoDB: %s!\n" - "InnoDB: We do not continue crash recovery," - " because the table will become\n" - "InnoDB: corrupt if we cannot apply the log records" - " in the InnoDB log to it.\n" - "InnoDB: To fix the problem and start mysqld:\n" - "InnoDB: 1) If there is a permission problem" - " in the file and mysqld cannot\n" - "InnoDB: access the file, you should" - " modify the permissions.\n" - "InnoDB: 2) If the table is not needed," - " or you can restore it from a backup,\n" - "InnoDB: then you can remove the .ibd file," - " and InnoDB will do a normal\n" - "InnoDB: crash recovery and ignore that table.\n" - "InnoDB: 3) If the file system or the disk is broken," - " and you cannot remove\n" - "InnoDB: the .ibd file, you can set" - " innodb_force_recovery > 0 in my.cnf\n" - "InnoDB: and force InnoDB to continue" - " crash recovery here.\n", filepath); - - os_file_close(file); - mem_free(tablename); - mem_free(filepath); + mem_free(def.filepath); if (srv_force_recovery > 0) { - fprintf(stderr, - "InnoDB: innodb_force_recovery" - " was set to %lu. Continuing crash recovery\n" - "InnoDB: even though we cannot access" - " the .ibd file of this table.\n", + ib_logf(IB_LOG_LEVEL_INFO, + "innodb_force_recovery was set to %lu. " + "Continuing crash recovery even though we " + "cannot access the .ibd file of this table.", srv_force_recovery); return; } + /* If debug code, cause a core dump and call stack. For + release builds just exit and rely on the messages above. */ + ut_ad(0); exit(1); } - /* TODO: What to do in other cases where we cannot access an .ibd - file during a crash recovery? */ + if (def.success && remote.success) { + ib_logf(IB_LOG_LEVEL_ERROR, + "Tablespaces for %s have been found in two places;\n" + "Location 1: SpaceID: %lu LSN: %lu File: %s\n" + "Location 2: SpaceID: %lu LSN: %lu File: %s\n" + "You must delete one of them.", + tablename, (ulong) def.id, (ulong) def.lsn, + def.filepath, (ulong) remote.id, (ulong) remote.lsn, + remote.filepath); - /* Every .ibd file is created >= 4 pages in size. Smaller files - cannot be ok. */ + def.success = FALSE; + os_file_close(def.file); + os_file_close(remote.file); + goto will_not_choose; + } -#ifndef UNIV_HOTBACKUP - if (size < FIL_IBD_FILE_INITIAL_SIZE * UNIV_PAGE_SIZE) { - fprintf(stderr, - "InnoDB: Error: the size of single-table" - " tablespace file %s\n" - "InnoDB: is only " UINT64PF - ", should be at least %lu!\n", - filepath, - size, (ulong) (4 * UNIV_PAGE_SIZE)); - os_file_close(file); - mem_free(tablename); - mem_free(filepath); + /* At this point, only one tablespace is open */ + ut_a(def.success == !remote.success); - return; - } -#endif - /* Read the first page of the tablespace if the size is big enough */ + fsp_open_info* fsp = def.success ? &def : &remote; - buf2 = static_cast<byte*>(ut_malloc(2 * UNIV_PAGE_SIZE)); - /* Align the memory for file i/o if we might have O_DIRECT set */ - page = static_cast<byte*>(ut_align(buf2, UNIV_PAGE_SIZE)); + /* Get and test the file size. */ + size = os_file_get_size(fsp->file); - if (size >= FIL_IBD_FILE_INITIAL_SIZE * UNIV_PAGE_SIZE) { - success = os_file_read(file, page, 0, UNIV_PAGE_SIZE); + if (size == (os_offset_t) -1) { + /* The following call prints an error message */ + os_file_get_last_error(true); - /* We have to read the tablespace id from the file */ + ib_logf(IB_LOG_LEVEL_ERROR, + "could not measure the size of single-table " + "tablespace file %s", fsp->filepath); - space_id = fsp_header_get_space_id(page); - flags = fsp_header_get_flags(page); - } else { - space_id = ULINT_UNDEFINED; - flags = 0; + os_file_close(fsp->file); + goto no_good_file; } + /* Every .ibd file is created >= 4 pages in size. Smaller files + cannot be ok. */ + ulong minimum_size = FIL_IBD_FILE_INITIAL_SIZE * UNIV_PAGE_SIZE; + if (size < minimum_size) { #ifndef UNIV_HOTBACKUP - if (space_id == ULINT_UNDEFINED || space_id == 0) { - fprintf(stderr, - "InnoDB: Error: tablespace id %lu in file %s" - " is not sensible\n", - (ulong) space_id, - filepath); - goto func_exit; - } + ib_logf(IB_LOG_LEVEL_ERROR, + "The size of single-table tablespace file %s " + "is only " UINT64PF ", should be at least %lu!", + fsp->filepath, size, minimum_size); + os_file_close(fsp->file); + goto no_good_file; #else - if (space_id == ULINT_UNDEFINED || space_id == 0) { + fsp->id = ULINT_UNDEFINED; + fsp->flags = 0; +#endif /* !UNIV_HOTBACKUP */ + } + +#ifdef UNIV_HOTBACKUP + if (fsp->id == ULINT_UNDEFINED || fsp->id == 0) { char* new_path; fprintf(stderr, @@ -3528,18 +4161,19 @@ fil_load_single_table_tablespace( " is not sensible.\n" "InnoDB: This can happen in an ibbackup run," " and is not dangerous.\n", - filepath, space_id, filepath, size); - os_file_close(file); + fsp->filepath, fsp->id, fsp->filepath, size); + os_file_close(fsp->file); - new_path = fil_make_ibbackup_old_name(filepath); - ut_a(os_file_rename(innodb_file_data_key, filepath, new_path)); + new_path = fil_make_ibbackup_old_name(fsp->filepath); + + bool success = os_file_rename( + innodb_file_data_key, fsp->filepath, new_path)); + + ut_a(success); - ut_free(buf2); - mem_free(tablename); - mem_free(filepath); mem_free(new_path); - return; + goto func_exit_after_close; } /* A backup may contain the same space several times, if the space got @@ -3551,7 +4185,7 @@ fil_load_single_table_tablespace( mutex_enter(&fil_system->mutex); - space = fil_space_get_by_id(space_id); + space = fil_space_get_by_id(fsp->id); if (space) { char* new_path; @@ -3563,52 +4197,64 @@ fil_load_single_table_tablespace( "InnoDB: was scanned earlier. This can happen" " if you have renamed tables\n" "InnoDB: during an ibbackup run.\n", - filepath, space_id, filepath, + fsp->filepath, fsp->id, fsp->filepath, space->name); - os_file_close(file); + os_file_close(fsp->file); - new_path = fil_make_ibbackup_old_name(filepath); + new_path = fil_make_ibbackup_old_name(fsp->filepath); mutex_exit(&fil_system->mutex); - ut_a(os_file_rename(innodb_file_data_key, filepath, new_path)); + bool success = os_file_rename( + innodb_file_data_key, fsp->filepath, new_path); + + ut_a(success); - ut_free(buf2); - mem_free(tablename); - mem_free(filepath); mem_free(new_path); - return; + goto func_exit_after_close; } mutex_exit(&fil_system->mutex); -#endif - success = fil_space_create(tablename, space_id, flags, FIL_TABLESPACE); - - if (!success) { +#endif /* UNIV_HOTBACKUP */ + ibool file_space_create_success = fil_space_create( + tablename, fsp->id, fsp->flags, FIL_TABLESPACE); + if (!file_space_create_success) { if (srv_force_recovery > 0) { fprintf(stderr, - "InnoDB: innodb_force_recovery" - " was set to %lu. Continuing crash recovery\n" - "InnoDB: even though the tablespace creation" - " of this table failed.\n", + "InnoDB: innodb_force_recovery was set" + " to %lu. Continuing crash recovery\n" + "InnoDB: even though the tablespace" + " creation of this table failed.\n", srv_force_recovery); goto func_exit; } - exit(1); + /* Exit here with a core dump, stack, etc. */ + ut_a(file_space_create_success); } /* We do not use the size information we have about the file, because the rounding formula for extents and pages is somewhat complex; we let fil_node_open() do that task. */ - fil_node_create(filepath, 0, space_id, FALSE); + if (!fil_node_create(fsp->filepath, 0, fsp->id, FALSE)) { + ut_error; + } + func_exit: - os_file_close(file); - ut_free(buf2); + os_file_close(fsp->file); + +#ifdef UNIV_HOTBACKUP +func_exit_after_close: +#else + ut_ad(!mutex_own(&fil_system->mutex)); +#endif mem_free(tablename); - mem_free(filepath); + if (remote.success) { + mem_free(remote.filepath); + } + mem_free(def.filepath); } /***********************************************************************//** @@ -3621,29 +4267,25 @@ static int fil_file_readdir_next_file( /*=======================*/ - ulint* err, /*!< out: this is set to DB_ERROR if an error + dberr_t* err, /*!< out: this is set to DB_ERROR if an error was encountered, otherwise not changed */ const char* dirname,/*!< in: directory name or path */ os_file_dir_t dir, /*!< in: directory stream */ - os_file_stat_t* info) /*!< in/out: buffer where the info is returned */ + os_file_stat_t* info) /*!< in/out: buffer where the + info is returned */ { - ulint i; - int ret; - - for (i = 0; i < 100; i++) { - ret = os_file_readdir_next_file(dirname, dir, info); + for (ulint i = 0; i < 100; i++) { + int ret = os_file_readdir_next_file(dirname, dir, info); if (ret != -1) { return(ret); } - fprintf(stderr, - "InnoDB: Error: os_file_readdir_next_file()" - " returned -1 in\n" - "InnoDB: directory %s\n" - "InnoDB: Crash recovery may have failed" - " for some .ibd files!\n", dirname); + ib_logf(IB_LOG_LEVEL_ERROR, + "os_file_readdir_next_file() returned -1 in " + "directory %s, crash recovery may have failed " + "for some .ibd files!", dirname); *err = DB_ERROR; } @@ -3660,7 +4302,7 @@ in the doublewrite buffer, also to know where to apply log records where the space id is != 0. @return DB_SUCCESS or error number */ UNIV_INTERN -ulint +dberr_t fil_load_single_table_tablespaces(void) /*===================================*/ { @@ -3671,7 +4313,7 @@ fil_load_single_table_tablespaces(void) os_file_dir_t dbdir; os_file_stat_t dbinfo; os_file_stat_t fileinfo; - ulint err = DB_SUCCESS; + dberr_t err = DB_SUCCESS; /* The datadir of MySQL is always the default directory of mysqld */ @@ -3720,7 +4362,6 @@ fil_load_single_table_tablespaces(void) dbdir = os_file_opendir(dbpath, FALSE); if (dbdir != NULL) { - /* printf("Opened dir %s\n", dbinfo.name); */ /* We found a database directory; loop through it, looking for possible .ibd files in it */ @@ -3728,8 +4369,6 @@ fil_load_single_table_tablespaces(void) ret = fil_file_readdir_next_file(&err, dbpath, dbdir, &fileinfo); while (ret == 0) { - /* printf( - " Looking at file %s\n", fileinfo.name); */ if (fileinfo.type == OS_FILE_TYPE_DIR) { @@ -3738,11 +4377,14 @@ fil_load_single_table_tablespaces(void) /* We found a symlink or a file */ if (strlen(fileinfo.name) > 4 - && 0 == strcmp(fileinfo.name + && (0 == strcmp(fileinfo.name + + strlen(fileinfo.name) - 4, + ".ibd") + || 0 == strcmp(fileinfo.name + strlen(fileinfo.name) - 4, - ".ibd")) { - /* The name ends in .ibd; try opening - the file */ + ".isl"))) { + /* The name ends in .ibd or .isl; + try opening the file */ fil_load_single_table_tablespace( dbinfo.name, fileinfo.name); } @@ -3842,6 +4484,29 @@ fil_tablespace_exists_in_mem( } /*******************************************************************//** +Report that a tablespace for a table was not found. */ +static +void +fil_report_missing_tablespace( +/*===========================*/ + const char* name, /*!< in: table name */ + ulint space_id) /*!< in: table's space id */ +{ + char index_name[MAX_FULL_NAME_LEN + 1]; + + innobase_format_name(index_name, sizeof(index_name), name, TRUE); + + ib_logf(IB_LOG_LEVEL_ERROR, + "Table %s in the InnoDB data dictionary has tablespace id %lu, " + "but tablespace with that id or name does not exist. Have " + "you deleted or moved .ibd files? This may also be a table " + "created with CREATE TEMPORARY TABLE whose .ibd and .frm " + "files MySQL automatically removed, but the table still " + "exists in the InnoDB internal data dictionary.", + name, space_id); +} + +/*******************************************************************//** Returns TRUE if a matching tablespace exists in the InnoDB tablespace memory cache. Note that if we have not done a crash recovery at the database startup, there may be many tablespaces which are not yet in the memory cache. @@ -3851,19 +4516,25 @@ ibool fil_space_for_table_exists_in_mem( /*==============================*/ ulint id, /*!< in: space id */ - const char* name, /*!< in: table name in the standard - 'databasename/tablename' format */ + const char* name, /*!< in: table name used in + fil_space_create(). Either the + standard 'dbname/tablename' format + or table->dir_path_of_temp_table */ ibool mark_space, /*!< in: in crash recovery, at database startup we mark all spaces which have an associated table in the InnoDB data dictionary, so that we can print a warning about orphaned tablespaces */ - ibool print_error_if_does_not_exist) + ibool print_error_if_does_not_exist, /*!< in: print detailed error information to the .err log if a matching tablespace is not found from memory */ + bool adjust_space, /*!< in: whether to adjust space id + when find table space mismatch */ + mem_heap_t* heap, /*!< in: heap memory */ + table_id_t table_id) /*!< in: table id */ { fil_space_t* fnamespace; fil_space_t* space; @@ -3892,6 +4563,47 @@ fil_space_for_table_exists_in_mem( return(TRUE); } + /* Info from "fnamespace" comes from the ibd file itself, it can + be different from data obtained from System tables since it is + not transactional. If adjust_space is set, and the mismatching + space are between a user table and its temp table, we shall + adjust the ibd file name according to system table info */ + if (adjust_space + && space != NULL + && row_is_mysql_tmp_table_name(space->name) + && !row_is_mysql_tmp_table_name(name)) { + + mutex_exit(&fil_system->mutex); + + DBUG_EXECUTE_IF("ib_crash_before_adjust_fil_space", + DBUG_SUICIDE();); + + if (fnamespace) { + char* tmp_name; + + tmp_name = dict_mem_create_temporary_tablename( + heap, name, table_id); + + fil_rename_tablespace(fnamespace->name, fnamespace->id, + tmp_name, NULL); + } + + DBUG_EXECUTE_IF("ib_crash_after_adjust_one_fil_space", + DBUG_SUICIDE();); + + fil_rename_tablespace(space->name, id, name, NULL); + + DBUG_EXECUTE_IF("ib_crash_after_adjust_fil_space", + DBUG_SUICIDE();); + + mutex_enter(&fil_system->mutex); + fnamespace = fil_space_get_by_name(name); + ut_ad(space == fnamespace); + mutex_exit(&fil_system->mutex); + + return(TRUE); + } + if (!print_error_if_does_not_exist) { mutex_exit(&fil_system->mutex); @@ -3901,22 +4613,9 @@ fil_space_for_table_exists_in_mem( if (space == NULL) { if (fnamespace == NULL) { - ut_print_timestamp(stderr); - fputs(" InnoDB: Error: table ", stderr); - ut_print_filename(stderr, name); - fprintf(stderr, "\n" - "InnoDB: in InnoDB data dictionary" - " has tablespace id %lu,\n" - "InnoDB: but tablespace with that id" - " or name does not exist. Have\n" - "InnoDB: you deleted or moved .ibd files?\n" - "InnoDB: This may also be a table created with" - " CREATE TEMPORARY TABLE\n" - "InnoDB: whose .ibd and .frm files" - " MySQL automatically removed, but the\n" - "InnoDB: table still exists in the" - " InnoDB internal data dictionary.\n", - (ulong) id); + if (print_error_if_does_not_exist) { + fil_report_missing_tablespace(name, id); + } } else { ut_print_timestamp(stderr); fputs(" InnoDB: Error: table ", stderr); @@ -3975,7 +4674,7 @@ error_exit: Checks if a single-table tablespace for a given table name exists in the tablespace memory cache. @return space id, ULINT_UNDEFINED if not found */ -static +UNIV_INTERN ulint fil_get_space_id_for_table( /*=======================*/ @@ -4030,6 +4729,8 @@ fil_extend_space_to_desired_size( ulint pages_added; ibool success; + ut_ad(!srv_read_only_mode); + retry: pages_added = 0; success = TRUE; @@ -4081,20 +4782,6 @@ retry: start_page_no = space->size; file_start_page_no = space->size - node->size; -#ifdef HAVE_POSIX_FALLOCATE - if (srv_use_posix_fallocate) { - success = os_file_set_size(node->name, node->handle, - size_after_extend * page_size); - mutex_enter(&fil_system->mutex); - if (success) { - node->size += (size_after_extend - start_page_no); - space->size += (size_after_extend - start_page_no); - os_has_said_disk_full = FALSE; - } - goto complete_io; - } -#endif - /* Extend at most 64 pages at a time */ buf_size = ut_min(64, size_after_extend - start_page_no) * page_size; buf2 = static_cast<byte*>(mem_alloc(buf_size + page_size)); @@ -4118,7 +4805,7 @@ retry: node->name, node->handle, buf, offset, page_size * n_pages, NULL, NULL); -#endif +#endif /* UNIV_HOTBACKUP */ if (success) { os_has_said_disk_full = FALSE; } else { @@ -4150,10 +4837,6 @@ retry: node->size += pages_added; node->being_extended = FALSE; -#ifdef HAVE_POSIX_FALLOCATE -complete_io: -#endif - fil_node_complete_io(node, fil_system, OS_FILE_WRITE); *actual_size = space->size; @@ -4195,7 +4878,7 @@ fil_extend_tablespaces_to_stored_len(void) byte* buf; ulint actual_size; ulint size_in_header; - ulint error; + dberr_t error; ibool success; buf = mem_alloc(UNIV_PAGE_SIZE); @@ -4229,7 +4912,7 @@ fil_extend_tablespaces_to_stored_len(void) "InnoDB: Check that you have free disk space" " and retry!\n", space->name, size_in_header, actual_size); - exit(1); + ut_a(success); } mutex_enter(&fil_system->mutex); @@ -4399,12 +5082,21 @@ fil_node_complete_io( node->n_pending--; if (type == OS_FILE_WRITE) { + ut_ad(!srv_read_only_mode); system->modification_counter++; node->modification_counter = system->modification_counter; - if (!node->space->is_in_unflushed_spaces) { + if (fil_buffering_disabled(node->space)) { + + /* We don't need to keep track of unflushed + changes as user has explicitly disabled + buffering. */ + ut_ad(!node->space->is_in_unflushed_spaces); + node->flush_counter = node->modification_counter; - node->space->is_in_unflushed_spaces = TRUE; + } else if (!node->space->is_in_unflushed_spaces) { + + node->space->is_in_unflushed_spaces = true; UT_LIST_ADD_FIRST(unflushed_spaces, system->unflushed_spaces, node->space); @@ -4451,7 +5143,7 @@ Reads or writes data. This operation is asynchronous (aio). @return DB_SUCCESS, or DB_TABLESPACE_DELETED if we are trying to do i/o on a tablespace which does not exist */ UNIV_INTERN -ulint +dberr_t fil_io( /*===*/ ulint type, /*!< in: OS_FILE_READ or OS_FILE_WRITE, @@ -4514,9 +5206,11 @@ fil_io( #ifndef UNIV_HOTBACKUP # ifndef UNIV_LOG_DEBUG /* ibuf bitmap pages must be read in the sync aio mode: */ - ut_ad(recv_no_ibuf_operations || (type == OS_FILE_WRITE) + ut_ad(recv_no_ibuf_operations + || type == OS_FILE_WRITE || !ibuf_bitmap_page(zip_size, block_offset) - || sync || is_log); + || sync + || is_log); # endif /* UNIV_LOG_DEBUG */ if (sync) { mode = OS_AIO_SYNC; @@ -4535,9 +5229,10 @@ fil_io( #endif /* !UNIV_HOTBACKUP */ if (type == OS_FILE_READ) { - srv_data_read+= len; + srv_stats.data_read.add(len); } else if (type == OS_FILE_WRITE) { - srv_data_written+= len; + ut_ad(!srv_read_only_mode); + srv_stats.data_written.add(len); } /* Reserve the fil_system mutex and make sure that we can open at @@ -4549,48 +5244,43 @@ fil_io( /* If we are deleting a tablespace we don't allow any read operations on that. However, we do allow write operations. */ - if (!space || (type == OS_FILE_READ && space->stop_new_ops)) { + if (space == 0 || (type == OS_FILE_READ && space->stop_new_ops)) { mutex_exit(&fil_system->mutex); - ut_print_timestamp(stderr); - fprintf(stderr, - " InnoDB: Error: trying to do i/o" - " to a tablespace which does not exist.\n" - "InnoDB: i/o type %lu, space id %lu," - " page no. %lu, i/o length %lu bytes\n", + ib_logf(IB_LOG_LEVEL_ERROR, + "Trying to do i/o to a tablespace which does " + "not exist. i/o type %lu, space id %lu, " + "page no. %lu, i/o length %lu bytes", (ulong) type, (ulong) space_id, (ulong) block_offset, (ulong) len); return(DB_TABLESPACE_DELETED); } - ut_ad((mode != OS_AIO_IBUF) || (space->purpose == FIL_TABLESPACE)); + ut_ad(mode != OS_AIO_IBUF || space->purpose == FIL_TABLESPACE); node = UT_LIST_GET_FIRST(space->chain); for (;;) { - if (UNIV_UNLIKELY(node == NULL)) { + if (node == NULL) { if (ignore_nonexistent_pages) { mutex_exit(&fil_system->mutex); return(DB_ERROR); } - /* else */ fil_report_invalid_page_access( block_offset, space_id, space->name, byte_offset, len, type); ut_error; - } - if (fil_is_user_tablespace_id(space->id) && node->size == 0) { + } else if (fil_is_user_tablespace_id(space->id) + && node->size == 0) { + /* We do not know the size of a single-table tablespace before we open the file */ - break; - } - - if (node->size > block_offset) { + } else if (node->size > block_offset) { /* Found! */ break; } else { @@ -4652,6 +5342,7 @@ fil_io( if (type == OS_FILE_READ) { ret = os_file_read(node->handle, buf, offset, len); } else { + ut_ad(!srv_read_only_mode); ret = os_file_write(node->name, node->handle, buf, offset, len); } @@ -4659,7 +5350,7 @@ fil_io( /* Queue the aio request */ ret = os_aio(type, mode | wake_later, node->name, node->handle, buf, offset, len, node, message); -#endif +#endif /* UNIV_HOTBACKUP */ ut_a(ret); if (mode == OS_AIO_SYNC) { @@ -4701,24 +5392,24 @@ fil_aio_wait( if (srv_use_native_aio) { srv_set_io_thread_op_info(segment, "native aio handle"); #ifdef WIN_ASYNC_IO - ret = os_aio_windows_handle(segment, 0, &fil_node, - &message, &type); + ret = os_aio_windows_handle( + segment, 0, &fil_node, &message, &type); #elif defined(LINUX_NATIVE_AIO) - ret = os_aio_linux_handle(segment, &fil_node, - &message, &type); + ret = os_aio_linux_handle( + segment, &fil_node, &message, &type); #else ut_error; ret = 0; /* Eliminate compiler warning */ -#endif +#endif /* WIN_ASYNC_IO */ } else { srv_set_io_thread_op_info(segment, "simulated aio handle"); - ret = os_aio_simulated_handle(segment, &fil_node, - &message, &type); + ret = os_aio_simulated_handle( + segment, &fil_node, &message, &type); } ut_a(ret); - if (UNIV_UNLIKELY(fil_node == NULL)) { + if (fil_node == NULL) { ut_ad(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS); return; } @@ -4774,6 +5465,28 @@ fil_flush( return; } + if (fil_buffering_disabled(space)) { + + /* No need to flush. User has explicitly disabled + buffering. */ + ut_ad(!space->is_in_unflushed_spaces); + ut_ad(fil_space_is_flushed(space)); + ut_ad(space->n_pending_flushes == 0); + +#ifdef UNIV_DEBUG + for (node = UT_LIST_GET_FIRST(space->chain); + node != NULL; + node = UT_LIST_GET_NEXT(chain, node)) { + ut_ad(node->modification_counter + == node->flush_counter); + ut_ad(node->n_pending_flushes == 0); + } +#endif /* UNIV_DEBUG */ + + mutex_exit(&fil_system->mutex); + return; + } + space->n_pending_flushes++; /*!< prevent dropping of the space while we are flushing */ node = UT_LIST_GET_FIRST(space->chain); @@ -4797,7 +5510,7 @@ fil_flush( goto skip_flush; } -#endif +#endif /* __WIN__ */ retry: if (node->n_pending_flushes > 0) { /* We want to avoid calling os_file_flush() on @@ -4840,7 +5553,7 @@ skip_flush: if (space->is_in_unflushed_spaces && fil_space_is_flushed(space)) { - space->is_in_unflushed_spaces = FALSE; + space->is_in_unflushed_spaces = false; UT_LIST_REMOVE( unflushed_spaces, @@ -5078,6 +5791,379 @@ fil_close(void) fil_system = NULL; } +/********************************************************************//** +Initializes a buffer control block when the buf_pool is created. */ +static +void +fil_buf_block_init( +/*===============*/ + buf_block_t* block, /*!< in: pointer to control block */ + byte* frame) /*!< in: pointer to buffer frame */ +{ + UNIV_MEM_DESC(frame, UNIV_PAGE_SIZE); + + block->frame = frame; + + block->page.io_fix = BUF_IO_NONE; + /* There are assertions that check for this. */ + block->page.buf_fix_count = 1; + block->page.state = BUF_BLOCK_READY_FOR_USE; + + page_zip_des_init(&block->page.zip); +} + +struct fil_iterator_t { + os_file_t file; /*!< File handle */ + const char* filepath; /*!< File path name */ + os_offset_t start; /*!< From where to start */ + os_offset_t end; /*!< Where to stop */ + os_offset_t file_size; /*!< File size in bytes */ + ulint page_size; /*!< Page size */ + ulint n_io_buffers; /*!< Number of pages to use + for IO */ + byte* io_buffer; /*!< Buffer to use for IO */ +}; + +/********************************************************************//** +TODO: This can be made parallel trivially by chunking up the file and creating +a callback per thread. . Main benefit will be to use multiple CPUs for +checksums and compressed tables. We have to do compressed tables block by +block right now. Secondly we need to decompress/compress and copy too much +of data. These are CPU intensive. + +Iterate over all the pages in the tablespace. +@param iter - Tablespace iterator +@param block - block to use for IO +@param callback - Callback to inspect and update page contents +@retval DB_SUCCESS or error code */ +static +dberr_t +fil_iterate( +/*========*/ + const fil_iterator_t& iter, + buf_block_t* block, + PageCallback& callback) +{ + os_offset_t offset; + ulint page_no = 0; + ulint space_id = callback.get_space_id(); + ulint n_bytes = iter.n_io_buffers * iter.page_size; + + ut_ad(!srv_read_only_mode); + + /* TODO: For compressed tables we do a lot of useless + copying for non-index pages. Unfortunately, it is + required by buf_zip_decompress() */ + + for (offset = iter.start; offset < iter.end; offset += n_bytes) { + + byte* io_buffer = iter.io_buffer; + + block->frame = io_buffer; + + if (callback.get_zip_size() > 0) { + page_zip_des_init(&block->page.zip); + page_zip_set_size(&block->page.zip, iter.page_size); + block->page.zip.data = block->frame + UNIV_PAGE_SIZE; + ut_d(block->page.zip.m_external = true); + ut_ad(iter.page_size == callback.get_zip_size()); + + /* Zip IO is done in the compressed page buffer. */ + io_buffer = block->page.zip.data; + } else { + io_buffer = iter.io_buffer; + } + + /* We have to read the exact number of bytes. Otherwise the + InnoDB IO functions croak on failed reads. */ + + n_bytes = static_cast<ulint>( + ut_min(static_cast<os_offset_t>(n_bytes), + iter.end - offset)); + + ut_ad(n_bytes > 0); + ut_ad(!(n_bytes % iter.page_size)); + + if (!os_file_read(iter.file, io_buffer, offset, + (ulint) n_bytes)) { + + ib_logf(IB_LOG_LEVEL_ERROR, "os_file_read() failed"); + + return(DB_IO_ERROR); + } + + bool updated = false; + os_offset_t page_off = offset; + ulint n_pages_read = (ulint) n_bytes / iter.page_size; + + for (ulint i = 0; i < n_pages_read; ++i) { + + buf_block_set_file_page(block, space_id, page_no++); + + dberr_t err; + + if ((err = callback(page_off, block)) != DB_SUCCESS) { + + return(err); + + } else if (!updated) { + updated = buf_block_get_state(block) + == BUF_BLOCK_FILE_PAGE; + } + + buf_block_set_state(block, BUF_BLOCK_NOT_USED); + buf_block_set_state(block, BUF_BLOCK_READY_FOR_USE); + + page_off += iter.page_size; + block->frame += iter.page_size; + } + + /* A page was updated in the set, write back to disk. */ + if (updated + && !os_file_write( + iter.filepath, iter.file, io_buffer, + offset, (ulint) n_bytes)) { + + ib_logf(IB_LOG_LEVEL_ERROR, "os_file_write() failed"); + + return(DB_IO_ERROR); + } + } + + return(DB_SUCCESS); +} + +/********************************************************************//** +Iterate over all the pages in the tablespace. +@param table - the table definiton in the server +@param n_io_buffers - number of blocks to read and write together +@param callback - functor that will do the page updates +@return DB_SUCCESS or error code */ +UNIV_INTERN +dberr_t +fil_tablespace_iterate( +/*===================*/ + dict_table_t* table, + ulint n_io_buffers, + PageCallback& callback) +{ + dberr_t err; + os_file_t file; + char* filepath; + + ut_a(n_io_buffers > 0); + ut_ad(!srv_read_only_mode); + + DBUG_EXECUTE_IF("ib_import_trigger_corruption_1", + return(DB_CORRUPTION);); + + if (DICT_TF_HAS_DATA_DIR(table->flags)) { + dict_get_and_save_data_dir_path(table, false); + ut_a(table->data_dir_path); + + filepath = os_file_make_remote_pathname( + table->data_dir_path, table->name, "ibd"); + } else { + filepath = fil_make_ibd_name(table->name, false); + } + + { + ibool success; + + file = os_file_create_simple_no_error_handling( + innodb_file_data_key, filepath, + OS_FILE_OPEN, OS_FILE_READ_WRITE, &success); + + DBUG_EXECUTE_IF("fil_tablespace_iterate_failure", + { + static bool once; + + if (!once || ut_rnd_interval(0, 10) == 5) { + once = true; + success = FALSE; + os_file_close(file); + } + }); + + if (!success) { + /* The following call prints an error message */ + os_file_get_last_error(true); + + ib_logf(IB_LOG_LEVEL_ERROR, + "Trying to import a tablespace, but could not " + "open the tablespace file %s", filepath); + + mem_free(filepath); + + return(DB_TABLESPACE_NOT_FOUND); + + } else { + err = DB_SUCCESS; + } + } + + callback.set_file(filepath, file); + + os_offset_t file_size = os_file_get_size(file); + ut_a(file_size != (os_offset_t) -1); + + /* The block we will use for every physical page */ + buf_block_t block; + + memset(&block, 0x0, sizeof(block)); + + /* Allocate a page to read in the tablespace header, so that we + can determine the page size and zip_size (if it is compressed). + We allocate an extra page in case it is a compressed table. One + page is to ensure alignement. */ + + void* page_ptr = mem_alloc(3 * UNIV_PAGE_SIZE); + byte* page = static_cast<byte*>(ut_align(page_ptr, UNIV_PAGE_SIZE)); + + fil_buf_block_init(&block, page); + + /* Read the first page and determine the page and zip size. */ + + if (!os_file_read(file, page, 0, UNIV_PAGE_SIZE)) { + + err = DB_IO_ERROR; + + } else if ((err = callback.init(file_size, &block)) == DB_SUCCESS) { + fil_iterator_t iter; + + iter.file = file; + iter.start = 0; + iter.end = file_size; + iter.filepath = filepath; + iter.file_size = file_size; + iter.n_io_buffers = n_io_buffers; + iter.page_size = callback.get_page_size(); + + /* Compressed pages can't be optimised for block IO for now. + We do the IMPORT page by page. */ + + if (callback.get_zip_size() > 0) { + iter.n_io_buffers = 1; + ut_a(iter.page_size == callback.get_zip_size()); + } + + /** Add an extra page for compressed page scratch area. */ + + void* io_buffer = mem_alloc( + (2 + iter.n_io_buffers) * UNIV_PAGE_SIZE); + + iter.io_buffer = static_cast<byte*>( + ut_align(io_buffer, UNIV_PAGE_SIZE)); + + err = fil_iterate(iter, &block, callback); + + mem_free(io_buffer); + } + + if (err == DB_SUCCESS) { + + ib_logf(IB_LOG_LEVEL_INFO, "Sync to disk"); + + if (!os_file_flush(file)) { + ib_logf(IB_LOG_LEVEL_INFO, "os_file_flush() failed!"); + err = DB_IO_ERROR; + } else { + ib_logf(IB_LOG_LEVEL_INFO, "Sync to disk - done!"); + } + } + + os_file_close(file); + + mem_free(page_ptr); + mem_free(filepath); + + return(err); +} + +/** +Set the tablespace compressed table size. +@return DB_SUCCESS if it is valie or DB_CORRUPTION if not */ +dberr_t +PageCallback::set_zip_size(const buf_frame_t* page) UNIV_NOTHROW +{ + m_zip_size = fsp_header_get_zip_size(page); + + if (!ut_is_2pow(m_zip_size) || m_zip_size > UNIV_ZIP_SIZE_MAX) { + return(DB_CORRUPTION); + } + + return(DB_SUCCESS); +} + +/********************************************************************//** +Delete the tablespace file and any related files like .cfg. +This should not be called for temporary tables. */ +UNIV_INTERN +void +fil_delete_file( +/*============*/ + const char* ibd_name) /*!< in: filepath of the ibd + tablespace */ +{ + /* Force a delete of any stale .ibd files that are lying around. */ + + ib_logf(IB_LOG_LEVEL_INFO, "Deleting %s", ibd_name); + + os_file_delete_if_exists(ibd_name); + + char* cfg_name = fil_make_cfg_name(ibd_name); + + os_file_delete_if_exists(cfg_name); + + mem_free(cfg_name); +} + +/** +Iterate over all the spaces in the space list and fetch the +tablespace names. It will return a copy of the name that must be +freed by the caller using: delete[]. +@return DB_SUCCESS if all OK. */ +UNIV_INTERN +dberr_t +fil_get_space_names( +/*================*/ + space_name_list_t& space_name_list) + /*!< in/out: List to append to */ +{ + fil_space_t* space; + dberr_t err = DB_SUCCESS; + + mutex_enter(&fil_system->mutex); + + for (space = UT_LIST_GET_FIRST(fil_system->space_list); + space != NULL; + space = UT_LIST_GET_NEXT(space_list, space)) { + + if (space->purpose == FIL_TABLESPACE) { + ulint len; + char* name; + + len = strlen(space->name); + name = new(std::nothrow) char[len + 1]; + + if (name == 0) { + /* Caller to free elements allocated so far. */ + err = DB_OUT_OF_MEMORY; + break; + } + + memcpy(name, space->name, len); + name[len] = 0; + + space_name_list.push_back(name); + } + } + + mutex_exit(&fil_system->mutex); + + return(err); +} + /****************************************************************//** Generate redo logs for swapping two .ibd files */ UNIV_INTERN |