/*****************************************************************************

Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2009, Percona Inc.
Copyright (c) 2013, 2023, MariaDB Corporation.

Portions of this file contain modifications contributed and copyrighted
by Percona Inc.. Those modifications are
gratefully acknowledged and are described briefly in the InnoDB
documentation. The contributions by Percona Inc. are incorporated with
their permission, and subject to the conditions contained in the file
COPYING.Percona.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA

*****************************************************************************/

/********************************************************************//**
@file srv/srv0start.cc
Starts the InnoDB database server

Created 2/16/1996 Heikki Tuuri
*************************************************************************/

#include "my_global.h"

#include "mysqld.h"
#include "mysql/psi/mysql_stage.h"
#include "mysql/psi/psi.h"

#include "row0ftsort.h"
#include "ut0mem.h"
#include "mem0mem.h"
#include "data0data.h"
#include "data0type.h"
#include "dict0dict.h"
#include "buf0buf.h"
#include "buf0dblwr.h"
#include "buf0dump.h"
#include "os0file.h"
#include "fil0fil.h"
#include "fil0crypt.h"
#include "fsp0fsp.h"
#include "rem0rec.h"
#include "mtr0mtr.h"
#include "log0crypt.h"
#include "log0recv.h"
#include "page0page.h"
#include "page0cur.h"
#include "trx0trx.h"
#include "trx0sys.h"
#include "btr0btr.h"
#include "btr0cur.h"
#include "rem0rec.h"
#include "srv0start.h"
#include "srv0srv.h"
#include "mysql/service_wsrep.h" /* wsrep_recovery */
#include "trx0rseg.h"
#include "buf0flu.h"
#include "buf0rea.h"
#include "dict0boot.h"
#include "dict0load.h"
#include "dict0stats_bg.h"
#include "que0que.h"
#include "lock0lock.h"
#include "trx0roll.h"
#include "trx0purge.h"
#include "lock0lock.h"
#include "pars0pars.h"
#include "btr0sea.h"
#include "rem0cmp.h"
#include "dict0crea.h"
#include "row0ins.h"
#include "row0sel.h"
#include "row0upd.h"
#include "row0row.h"
#include "row0mysql.h"
#include "btr0pcur.h"
#include "ibuf0ibuf.h"
#include "zlib.h"
#include "log.h"

/** Log sequence number at shutdown */
lsn_t	srv_shutdown_lsn;

/** TRUE if a raw partition is in use */
ibool	srv_start_raw_disk_in_use;

/** UNDO tablespaces starts with space id. */
uint32_t srv_undo_space_id_start;

/** TRUE if the server is being started, before rolling back any
incomplete transactions */
bool	srv_startup_is_before_trx_rollback_phase;
/** TRUE if the server is being started */
bool	srv_is_being_started;
/** TRUE if the server was successfully started */
bool	srv_was_started;
/** whether srv_start() has been called */
static bool		srv_start_has_been_called;

/** Whether any undo log records can be generated */
bool	srv_undo_sources;

/** innodb_encrypt_log */
my_bool srv_encrypt_log;

#ifdef UNIV_DEBUG
/** InnoDB system tablespace to set during recovery */
uint	srv_sys_space_size_debug;
/** whether redo log file have been created at startup */
bool	srv_log_file_created;
#endif /* UNIV_DEBUG */

/** whether some background threads that create redo log have been started */
static bool srv_started_redo;

/** At a shutdown this value climbs from SRV_SHUTDOWN_NONE to
SRV_SHUTDOWN_CLEANUP and then to SRV_SHUTDOWN_LAST_PHASE, and so on */
enum srv_shutdown_t	srv_shutdown_state = SRV_SHUTDOWN_NONE;

/** Name of srv_monitor_file */
static char*	srv_monitor_file_name;
std::unique_ptr<tpool::timer> srv_master_timer;

/** */
#define SRV_MAX_N_PENDING_SYNC_IOS	100

#ifdef UNIV_PFS_THREAD
/* Keys to register InnoDB threads with performance schema */
mysql_pfs_key_t	thread_pool_thread_key;
#endif /* UNIV_PFS_THREAD */

#ifdef HAVE_PSI_STAGE_INTERFACE
/** Array of all InnoDB stage events for monitoring activities via
performance schema. */
static PSI_stage_info*	srv_stages[] =
{
	&srv_stage_alter_table_end,
	&srv_stage_alter_table_insert,
	&srv_stage_alter_table_log_index,
	&srv_stage_alter_table_log_table,
	&srv_stage_alter_table_merge_sort,
	&srv_stage_alter_table_read_pk_internal_sort,
	&srv_stage_buffer_pool_load,
};
#endif /* HAVE_PSI_STAGE_INTERFACE */

/** Delete any garbage log files */
static void delete_log_files()
{
  for (size_t i= 1; i < 102; i++)
    delete_log_file(std::to_string(i).c_str());
}

/** Creates log file.
@param create_new_db   whether the database is being initialized
@param lsn             log sequence number
@param logfile0        name of the log file
@return DB_SUCCESS or error code */
static dberr_t create_log_file(bool create_new_db, lsn_t lsn)
{
	ut_ad(!srv_read_only_mode);

	/* We will retain ib_logfile0 until we have written a new logically
	empty log as ib_logfile101 and atomically renamed it to
	ib_logfile0 in log_t::resize_rename(). */
	delete_log_files();

	ut_ad(!os_aio_pending_reads());
	ut_d(mysql_mutex_lock(&buf_pool.flush_list_mutex));
	ut_ad(!buf_pool.get_oldest_modification(0));
	ut_d(mysql_mutex_unlock(&buf_pool.flush_list_mutex));
	/* os_aio_pending_writes() may hold here if some
	write_io_callback() did not release the slot yet.  However,
	the page write itself must have completed, because the
	buf_pool.flush_list is empty. In debug builds, we wait for
	this to happen, hoping to get a hung process if this
	assumption does not hold. */
	ut_d(os_aio_wait_until_no_pending_writes(false));

	log_sys.latch.wr_lock(SRW_LOCK_CALL);
	log_sys.set_capacity();

	std::string logfile0{get_log_file_path("ib_logfile101")};
	bool ret;
	os_file_t file{
          os_file_create_func(logfile0.c_str(),
                              OS_FILE_CREATE,
                              OS_LOG_FILE, false, &ret)
        };

	if (!ret) {
		sql_print_error("InnoDB: Cannot create %.*s",
				int(logfile0.size()), logfile0.data());
err_exit:
		log_sys.latch.wr_unlock();
		return DB_ERROR;
	}

	ret = os_file_set_size(logfile0.c_str(), file, srv_log_file_size);
	if (!ret) {
		ib::error() << "Cannot set log file " << logfile0
			    << " size to " << ib::bytes_iec{srv_log_file_size};
close_and_exit:
		os_file_close_func(file);
		goto err_exit;
	}

	log_sys.set_latest_format(srv_encrypt_log);
	if (!log_sys.attach(file, srv_log_file_size)) {
		goto close_and_exit;
	}

	mysql_mutex_lock(&recv_sys.mutex);
	const bool all_opened = fil_system.sys_space->open(create_new_db);
	mysql_mutex_unlock(&recv_sys.mutex);

	if (!all_opened) {
		goto err_exit;
	}

	/* Create a log checkpoint. */
	if (log_sys.is_encrypted() && !log_crypt_init()) {
		goto err_exit;
	}
	ut_d(recv_no_log_write = false);
	log_sys.create(lsn);

	ut_ad(srv_startup_is_before_trx_rollback_phase);
	if (create_new_db) {
		srv_startup_is_before_trx_rollback_phase = false;
	}

	/* Enable log_checkpoint() in buf_flush_page_cleaner().
	If we are upgrading or resizing the log at startup, we must not
	write any FILE_MODIFY or FILE_CHECKPOINT records to the old log file
	because the ib_logfile0 could be in an old format that we can only
	check for emptiness, or the write could lead to an overflow.

	At this point, we are not holding recv_sys.mutex, but there are no
	pending page reads, and buf_pool.flush_list is empty. Therefore,
	we can clear the flag without risking any race condition with
	buf_page_t::read_complete(). */
	recv_sys.recovery_on = false;
	log_sys.latch.wr_unlock();

	log_make_checkpoint();
	log_buffer_flush_to_disk();

	return DB_SUCCESS;
}

/** Rename the redo log file after resizing.
@return whether an error occurred */
bool log_t::resize_rename() noexcept
{
  std::string old_name{get_log_file_path("ib_logfile101")};
  std::string new_name{get_log_file_path()};

  if (IF_WIN(MoveFileEx(old_name.c_str(), new_name.c_str(),
                        MOVEFILE_REPLACE_EXISTING),
             !rename(old_name.c_str(), new_name.c_str())))
    return false;

  sql_print_error("InnoDB: Failed to rename log from %.*s to %.*s (error %d)",
                  int(old_name.size()), old_name.data(),
                  int(new_name.size()), new_name.data(),
                  IF_WIN(int(GetLastError()), errno));
  return true;
}

/** Create an undo tablespace file
@param[in] name	 file name
@return DB_SUCCESS or error code */
static dberr_t srv_undo_tablespace_create(const char* name)
{
	pfs_os_file_t	fh;
	bool		ret;
	dberr_t		err = DB_SUCCESS;

	os_file_create_subdirs_if_needed(name);

	fh = os_file_create(
		innodb_data_file_key,
		name,
		srv_read_only_mode ? OS_FILE_OPEN : OS_FILE_CREATE,
		OS_DATA_FILE, srv_read_only_mode, &ret);

	if (!ret) {
		if (os_file_get_last_error(false) != OS_FILE_ALREADY_EXISTS
#ifdef _AIX
			/* AIX 5.1 after security patch ML7 may have
			errno set to 0 here, which causes our function
			to return 100; work around that AIX problem */
		    && os_file_get_last_error(false) != 100
#endif
		) {
			ib::error() << "Can't create UNDO tablespace "
				<< name;
		}
		err = DB_ERROR;
	} else if (srv_read_only_mode) {
		ib::info() << name << " opened in read-only mode";
	} else {
		/* We created the data file and now write it full of zeros */

		ib::info() << "Data file " << name << " did not exist: new to"
			" be created";

		ib::info() << "Setting file " << name << " size to "
			<< ib::bytes_iec{SRV_UNDO_TABLESPACE_SIZE_IN_PAGES
					 << srv_page_size_shift};

		ib::info() << "Database physically writes the file full: "
			<< "wait...";

		if (!os_file_set_size(name, fh, os_offset_t
				      {SRV_UNDO_TABLESPACE_SIZE_IN_PAGES}
				      << srv_page_size_shift)) {
			ib::error() << "Unable to allocate " << name;
			err = DB_ERROR;
		}

		os_file_close(fh);
	}

	return(err);
}

inline dberr_t trx_sys_t::reset_page(mtr_t *mtr)
{
  dberr_t err= DB_SUCCESS;
  buf_block_t *sys_header= buf_page_get_gen(
    page_id_t(TRX_SYS_SPACE, TRX_SYS_PAGE_NO), 0, RW_X_LATCH, nullptr,
    BUF_GET, mtr, &err);

  if (!sys_header) return err;

  if (mach_read_from_4(sys_header->page.frame + TRX_SYS +
                       TRX_SYS_FSEG_HEADER) != TRX_SYS_SPACE)
    return DB_CORRUPTION;

  /* Store the TRX_SYS_FSEG_HEADER page, offset */
  char fseg_addr[6];
  memcpy(fseg_addr,
         sys_header->page.frame + TRX_SYS + TRX_SYS_FSEG_HEADER + 4, 6);

  const bool dblwr_enabled=
    mach_read_from_4(TRX_SYS_DOUBLEWRITE_MAGIC + TRX_SYS_DOUBLEWRITE +
                     sys_header->page.frame)
    == TRX_SYS_DOUBLEWRITE_MAGIC_N;

  char doublewrite[TRX_SYS_DOUBLEWRITE_BLOCK2 + 4];
  memcpy(doublewrite, TRX_SYS_DOUBLEWRITE + sys_header->page.frame,
         sizeof doublewrite);

  fsp_init_file_page(fil_system.sys_space, sys_header, mtr);

  mtr->write<2>(*sys_header, FIL_PAGE_TYPE + sys_header->page.frame,
                FIL_PAGE_TYPE_TRX_SYS);

  DBUG_EXECUTE_IF("sys_fseg_header_fail",
                  {
                    fseg_addr[4]= 0;
                    fseg_addr[5]= 0;
                  });

  /** Write the TRX_SYS_FSEG_HEADER only if it is not zero-filled */
  if (!memcmp(fseg_addr + 4, "00", 2))
    mtr->memcpy(*sys_header,
                sys_header->page.frame + TRX_SYS + TRX_SYS_FSEG_HEADER + 4,
                fseg_addr, 6);
  mtr->write<4>(*sys_header,
                TRX_SYS + TRX_SYS_RSEGS + TRX_SYS_RSEG_PAGE_NO +
                sys_header->page.frame, FSP_FIRST_RSEG_PAGE_NO);
  mtr->memset(sys_header,
             TRX_SYS + TRX_SYS_RSEGS + TRX_SYS_RSEG_SLOT_SIZE,
	     254 * TRX_SYS_RSEG_SLOT_SIZE, 0xff);

  static_assert(TRX_SYS_RSEG_SLOT_SIZE == 8, "");

  if (dblwr_enabled)
  {
    mtr->memcpy(
      *sys_header, sys_header->page.frame + TRX_SYS_DOUBLEWRITE,
      doublewrite, sizeof doublewrite);
    mtr->memmove(
      *sys_header,
      TRX_SYS_DOUBLEWRITE + FSEG_HEADER_SIZE + TRX_SYS_DOUBLEWRITE_REPEAT,
      TRX_SYS_DOUBLEWRITE + FSEG_HEADER_SIZE, 12);
    memcpy(
      sys_header->page.frame + TRX_SYS_DOUBLEWRITE
      + FSEG_HEADER_SIZE + TRX_SYS_DOUBLEWRITE_REPEAT,
      sys_header->page.frame + TRX_SYS_DOUBLEWRITE + FSEG_HEADER_SIZE, 12);
    mtr->write<4>(
      *sys_header,
      TRX_SYS_DOUBLEWRITE + TRX_SYS_DOUBLEWRITE_SPACE_ID_STORED +
      sys_header->page.frame,
      TRX_SYS_DOUBLEWRITE_SPACE_ID_STORED_N);
  }

  return DB_SUCCESS;
}

/** Delete the old undo tablespaces present in the undo log directory */
static dberr_t srv_undo_delete_old_tablespaces()
{
  /* Delete the old undo tablespaces*/
  for (uint32_t i= 0; i < srv_undo_tablespaces_open; ++i)
    fil_close_tablespace(srv_undo_space_id_start + i);

  DBUG_EXECUTE_IF("after_deleting_old_undo_abort", return DB_ERROR;);

  /* Do checkpoint to get rid of old undo log tablespaces redo logs */
  log_make_checkpoint();

  DBUG_EXECUTE_IF("after_deleting_old_undo_success", return DB_ERROR;);

  return DB_SUCCESS;
}

/** Recreate the undo log tablespaces */
ATTRIBUTE_COLD static dberr_t srv_undo_tablespaces_reinit()
{
  mtr_t mtr{nullptr};
  dberr_t err;
  buf_block_t *first_rseg_hdr;
  uint32_t latest_space_id;

  mtr.start();

  buf_block_t *dict_hdr= buf_page_get_gen(
    page_id_t(DICT_HDR_SPACE, DICT_HDR_PAGE_NO), 0, RW_X_LATCH,
    nullptr, BUF_GET, &mtr, &err);

  if (!dict_hdr)
    goto func_exit;

  /* Assign the new space id for the first undo tablespace */
  latest_space_id= mach_read_from_4(
    DICT_HDR + DICT_HDR_MAX_SPACE_ID + dict_hdr->page.frame);

  if (latest_space_id + srv_undo_tablespaces > SRV_SPACE_ID_UPPER_BOUND)
  {
    err= DB_ERROR;
    sql_print_error("InnoDB: Running out of tablespace id");
    goto func_exit;
  }

  first_rseg_hdr=
    buf_page_get_gen(trx_sys.rseg_array[0].page_id(), 0, RW_X_LATCH,
                     nullptr, BUF_GET, &mtr, &err);
  if (!first_rseg_hdr)
    goto func_exit;

  if (UNIV_UNLIKELY(mach_read_from_4(TRX_RSEG + TRX_RSEG_FORMAT +
                                     first_rseg_hdr->page.frame)))
    trx_rseg_format_upgrade(first_rseg_hdr, &mtr);

  mtr.write<8,mtr_t::MAYBE_NOP>(*first_rseg_hdr,
                                TRX_RSEG + TRX_RSEG_MAX_TRX_ID +
                                first_rseg_hdr->page.frame,
                                trx_sys.get_max_trx_id() - 1);

  /* Reset TRX_SYS page */
  err= trx_sys.reset_page(&mtr);

  if (err)
    goto func_exit;

  if (srv_undo_tablespaces_open == 0)
  {
    /* Free the system rollback segment */
    for (ulint i= 1; i < TRX_SYS_N_RSEGS; i++)
    {
      trx_rseg_t *rseg= &trx_sys.rseg_array[i];
      if (rseg->space != fil_system.sys_space)
        continue;
      buf_block_t *block= buf_page_get_gen(
        rseg->page_id(), 0, RW_X_LATCH, nullptr, BUF_GET, &mtr);
      if (!block) break;
      while (!fseg_free_step(block, TRX_RSEG + TRX_RSEG_FSEG_HEADER, &mtr));
    }
  }

  for (ulint rseg_id= 1; rseg_id < TRX_SYS_N_RSEGS; rseg_id++)
  {
    trx_rseg_t *rseg= &trx_sys.rseg_array[rseg_id];
    rseg->destroy();
    rseg->init(nullptr, FIL_NULL);
  }

  if (*trx_sys.recovered_binlog_filename
#ifdef WITH_WSREP
      || !trx_sys.recovered_wsrep_xid.is_null()
#endif /* WITH_WSREP */
     )
  {
    /* Update binlog offset, binlog file name & wsrep xid in
    system tablespace rollback segment */
    if (*trx_sys.recovered_binlog_filename)
    {
      ut_d(const size_t len = strlen(trx_sys.recovered_binlog_filename) + 1);
      ut_ad(len > 1);
      ut_ad(len <= TRX_RSEG_BINLOG_NAME_LEN);
      trx_rseg_update_binlog_offset(
        first_rseg_hdr, trx_sys.recovered_binlog_filename,
        trx_sys.recovered_binlog_offset, &mtr);
    }

#ifdef WITH_WSREP
    if (!trx_sys.recovered_wsrep_xid.is_null())
      trx_rseg_update_wsrep_checkpoint(
        first_rseg_hdr, &trx_sys.recovered_wsrep_xid, &mtr);
#endif /* WITH_WSREP */
  }

  dict_hdr->page.fix();

  mtr.commit();

  DBUG_EXECUTE_IF("after_rseg_reset_abort",
                  log_write_up_to(mtr.commit_lsn(), true);
                  dict_hdr->page.unfix();
                  return DB_ERROR;);

  sql_print_information(
    "InnoDB: Reinitializing innodb_undo_tablespaces= %u from %u",
    srv_undo_tablespaces, srv_undo_tablespaces_open);

  /* Delete the old undo tablespaces */
  err= srv_undo_delete_old_tablespaces();
  if (err)
  {
    dict_hdr->page.unfix();
    return err;
  }

  mtr.start();

  dict_hdr->page.lock.x_lock();
  mtr.memo_push(dict_hdr, MTR_MEMO_PAGE_X_FIX);

  if (srv_undo_tablespaces == 0)
  {
    srv_undo_space_id_start= 0;
    srv_undo_tablespaces_open= 0;
    goto func_exit;
  }

  srv_undo_space_id_start= latest_space_id;
  if (fil_assign_new_space_id(&srv_undo_space_id_start))
    mtr.write<4>(*dict_hdr, DICT_HDR + DICT_HDR_MAX_SPACE_ID
                 + dict_hdr->page.frame, srv_undo_space_id_start);

  /* Re-create the new undo tablespaces */
  err= srv_undo_tablespaces_init(true, &mtr);
func_exit:
  mtr.commit();

  DBUG_EXECUTE_IF("after_reinit_undo_abort",
                  log_write_up_to(mtr.commit_lsn(), true);
                  err= DB_ERROR;);

  if (err == DB_SUCCESS)
  {
    /* Usually, recovery must work no matter when
    log_checkpoints are triggered. This is a special case,
    because this code is executed as part of InnoDB startup.
    Backup requires that the server has been started up,
    backup should never observe the log records that
    were written in mtr and also srv_undo_tablespaces_init()
    initializes the undo tablespace start id based on page0
    content before reading the redo log */
    log_make_checkpoint();

    DBUG_EXECUTE_IF("after_reinit_undo_success", err= DB_ERROR;);
    srv_undo_tablespaces_active= srv_undo_tablespaces;
  }
  return err;
}

/** Reinitialize the undo tablespaces when there is no undo log
left to purge/rollback and validate the number of undo opened
undo tablespace and user given undo tablespace
@return DB_SUCCESS if it is valid */
static dberr_t srv_undo_tablespaces_reinitialize()
{

  /* Re-create the undo tablespaces if it has no undo logs
  left to purge/rollback */
  if (srv_undo_tablespaces != srv_undo_tablespaces_open &&
      trx_sys.is_undo_empty())
    return srv_undo_tablespaces_reinit();

  /* If the user says that there are fewer than what we find we
  tolerate that discrepancy but not the inverse. Because there could
  be unused undo tablespaces for future use. */

  if (srv_undo_tablespaces != srv_undo_tablespaces_open)
  {
    sql_print_warning("InnoDB: Cannot change innodb_undo_tablespaces=%u "
                      "because previous shutdown was not with "
                      "innodb_fast_shutdown=0", srv_undo_tablespaces);
    srv_undo_tablespaces= srv_undo_tablespaces_open;
  }
  else if (srv_undo_tablespaces_open > 0)
    sql_print_information("InnoDB: Opened " UINT32PF " undo tablespaces",
                          srv_undo_tablespaces_open);

  return DB_SUCCESS;
}

/** @return the number of active undo tablespaces (except system tablespace) */
static uint32_t trx_rseg_get_n_undo_tablespaces()
{
  std::set<uint32_t> space_ids;
  mtr_t mtr{nullptr};
  mtr.start();

  if (const buf_block_t *sys_header=
      recv_sys.recover({TRX_SYS_SPACE, TRX_SYS_PAGE_NO}, &mtr, nullptr))
    for (ulint rseg_id= 0; rseg_id < TRX_SYS_N_RSEGS; rseg_id++)
      if (trx_sysf_rseg_get_page_no(sys_header, rseg_id) != FIL_NULL)
	if (uint32_t space= trx_sysf_rseg_get_space(sys_header, rseg_id))
	  space_ids.insert(space);
  mtr.commit();
  return static_cast<uint32_t>(space_ids.size());
}

/** Open an undo tablespace.
@param[in]	create	whether undo tablespaces are being created
@param[in]	name	tablespace file name
@param[in]	i	undo tablespace count
@return undo tablespace identifier
@retval 0   if file doesn't exist
@retval ~0U if page0 is corrupted */
static uint32_t srv_undo_tablespace_open(bool create, const char* name,
                                         uint32_t i)
{
  bool success;
  uint32_t space_id= 0;
  uint32_t fsp_flags= 0;

  if (create)
  {
    space_id= srv_undo_space_id_start + i;
    switch (srv_checksum_algorithm) {
    case SRV_CHECKSUM_ALGORITHM_FULL_CRC32:
    case SRV_CHECKSUM_ALGORITHM_STRICT_FULL_CRC32:
      fsp_flags= FSP_FLAGS_FCRC32_MASK_MARKER | FSP_FLAGS_FCRC32_PAGE_SSIZE();
      break;
    default:
      fsp_flags= FSP_FLAGS_PAGE_SSIZE();
    }
  }

  pfs_os_file_t fh= os_file_create(innodb_data_file_key, name,
                                   OS_FILE_OPEN_SILENT,
                                   OS_DATA_FILE,
                                   srv_read_only_mode, &success);

  if (!success)
    return 0;

  ulint n_retries = 5;
  os_offset_t size= os_file_get_size(fh);
  ut_a(size != os_offset_t(-1));
  page_t *apage= nullptr;
  const page_t *page= nullptr;

  if (!create)
  {
    apage= static_cast<byte*>(aligned_malloc(srv_page_size, srv_page_size));
undo_retry:
    if (os_file_read(IORequestRead, fh, apage, 0, srv_page_size, nullptr) !=
        DB_SUCCESS)
    {
err_exit:
      if (n_retries && srv_operation == SRV_OPERATION_BACKUP)
      {
        sql_print_information("InnoDB: Retrying to read undo "
                              "tablespace %s", name);
        n_retries--;
        goto undo_retry;
      }
      sql_print_error("InnoDB: Unable to read first page of file %s", name);
      aligned_free(apage);
      return ~0U;
    }

    page= apage;
    DBUG_EXECUTE_IF("undo_space_read_fail", goto err_exit;);

    uint32_t id= mach_read_from_4(FIL_PAGE_SPACE_ID + page);
    if (id == 0 || id >= SRV_SPACE_ID_UPPER_BOUND ||
        memcmp_aligned<2>(FIL_PAGE_SPACE_ID + page,
                          FSP_HEADER_OFFSET + FSP_SPACE_ID + page, 4))
    {
      sql_print_error("InnoDB: Inconsistent tablespace ID in file %s", name);
      goto err_exit;
    }

    space_id= id;
    fsp_flags= mach_read_from_4(FSP_HEADER_OFFSET + FSP_SPACE_FLAGS + page);

    if (buf_page_is_corrupted(false, page, fsp_flags))
    {
      page= recv_sys.dblwr.find_page(page_id_t{space_id, 0}, LSN_MAX);
      if (!page)
      {
        /* If the first page of the given user tablespace is not there
        in the doublewrite buffer, then the recovery is going to fail
        now. Report error only when doublewrite buffer is not empty */
        sql_print_error("InnoDB: Corrupted page "
                        "[page id: space=" UINT32PF ", page number=0]"
                        " of datafile '%s' could not be found"
                        " in the doublewrite buffer", space_id, name);
        goto err_exit;
      }

      fsp_flags= mach_read_from_4(FSP_HEADER_OFFSET + FSP_SPACE_FLAGS + page);
    }
  }

  /* Load the tablespace into InnoDB's internal data structures. */

  /* We set the biggest space id to the undo tablespace
  because InnoDB hasn't opened any other tablespace apart
  from the system tablespace. */

  fil_set_max_space_id_if_bigger(space_id);

  mysql_mutex_lock(&fil_system.mutex);
  fil_space_t *space= fil_space_t::create(space_id, fsp_flags, false, nullptr,
                                          FIL_ENCRYPTION_DEFAULT, true);
  ut_ad(space);
  fil_node_t *file= space->add(name, fh, 0, false, true);

  if (create)
  {
    space->set_sizes(SRV_UNDO_TABLESPACE_SIZE_IN_PAGES);
    space->size= file->size= uint32_t(size >> srv_page_size_shift);
  }
  else if (!file->read_page0(page, true))
  {
    os_file_close(file->handle);
    file->handle= OS_FILE_CLOSED;
    ut_a(fil_system.n_open > 0);
    fil_system.n_open--;
  }

  mysql_mutex_unlock(&fil_system.mutex);
  aligned_free(apage);
  return space_id;
}

/** Check if undo tablespaces and redo log files exist before creating a
new system tablespace
@retval DB_SUCCESS  if all undo and redo logs are not found
@retval DB_ERROR    if any undo and redo logs are found */
static
dberr_t
srv_check_undo_redo_logs_exists()
{
	bool		ret;
	os_file_t	fh;
	char	name[OS_FILE_MAX_PATH];

	/* Check if any undo tablespaces exist */
	for (ulint i = 1; i <= srv_undo_tablespaces; ++i) {

		snprintf(name, sizeof name, "%s/undo%03zu", srv_undo_dir, i);

		fh = os_file_create_func(
			name,
			OS_FILE_OPEN_RETRY_SILENT,
			OS_DATA_FILE,
			srv_read_only_mode,
			&ret);

		if (ret) {
			os_file_close_func(fh);
			ib::error()
				<< "undo tablespace '" << name << "' exists."
				" Creating system tablespace with existing undo"
				" tablespaces is not supported. Please delete"
				" all undo tablespaces before creating new"
				" system tablespace.";
			return(DB_ERROR);
		}
	}

	/* Check if redo log file exists */
	auto logfilename = get_log_file_path();

	fh = os_file_create_func(logfilename.c_str(),
				 OS_FILE_OPEN_RETRY_SILENT,
				 OS_LOG_FILE,
				 srv_read_only_mode, &ret);

	if (ret) {
		os_file_close_func(fh);
		ib::error() << "redo log file '" << logfilename
			    << "' exists. Creating system tablespace with"
			       " existing redo log file is not recommended."
			       " Please delete redo log file before"
			       " creating new system tablespace.";
		return DB_ERROR;
	}

	return(DB_SUCCESS);
}

static dberr_t srv_all_undo_tablespaces_open(bool create_new_undo,
                                             uint32_t n_undo)
{
  /* Open all the undo tablespaces that are currently in use. If we
  fail to open any of these it is a fatal error. The tablespace ids
  should be contiguous. It is a fatal error because they are required
  for recovery and are referenced by the UNDO logs (a.k.a RBS). */

  uint32_t prev_id= create_new_undo ? srv_undo_space_id_start - 1 : 0;

  for (uint32_t i= 0; i < n_undo; ++i)
  {
    char name[OS_FILE_MAX_PATH];
    snprintf(name, sizeof name, "%s/undo%03u", srv_undo_dir, i + 1);
    uint32_t space_id= srv_undo_tablespace_open(create_new_undo, name, i);
    switch (space_id) {
    case ~0U:
      return DB_CORRUPTION;
    case 0:
      if (!create_new_undo)
        goto unused_undo;
      sql_print_error("InnoDB: Unable to open create tablespace '%s'.", name);
      return DB_ERROR;
    default:
      /* Should be no gaps in undo tablespace ids. */
      ut_a(!i || prev_id + 1 == space_id);
    }

    prev_id= space_id;

    /* Note the first undo tablespace id in case of
    no active undo tablespace. */
    if (0 == srv_undo_tablespaces_open++)
      srv_undo_space_id_start= space_id;
  }

  /* Open any extra unused undo tablespaces. These must be contiguous.
  We stop at the first failure. These are undo tablespaces that are
  not in use and therefore not required by recovery. We only check
  that there are no gaps. */
unused_undo:
  for (uint32_t i= prev_id + 1; i < srv_undo_space_id_start + TRX_SYS_N_RSEGS;
       ++i)
  {
     char name[OS_FILE_MAX_PATH];
     snprintf(name, sizeof name, "%s/undo%03u", srv_undo_dir, i);
     uint32_t space_id= srv_undo_tablespace_open(false, name, i);
     if (!space_id || space_id == ~0U)
       break;
     if (0 == srv_undo_tablespaces_open++)
       srv_undo_space_id_start= space_id;
  }

  return DB_SUCCESS;
}

PRAGMA_DISABLE_CHECK_STACK_FRAME

/** Open the configured number of dedicated undo tablespaces.
@param[in]	create_new_undo	whether the undo tablespaces has to be created
@param[in,out]	mtr		mini-transaction
@return DB_SUCCESS or error code */
dberr_t srv_undo_tablespaces_init(bool create_new_undo, mtr_t *mtr)
{
  srv_undo_tablespaces_open= 0;

  ut_ad(!create_new_undo || mtr);
  ut_a(srv_undo_tablespaces <= TRX_SYS_N_RSEGS);
  ut_a(!create_new_undo || srv_operation <= SRV_OPERATION_EXPORT_RESTORED);

  if (srv_undo_tablespaces == 1)
    srv_undo_tablespaces= 0;

  /* Create the undo spaces only if we are creating a new
  instance. We don't allow creating of new undo tablespaces
  in an existing instance (yet). */
  if (create_new_undo)
  {
    DBUG_EXECUTE_IF("innodb_undo_upgrade", srv_undo_space_id_start= 3;);

    for (ulint i= 0; i < srv_undo_tablespaces; ++i)
    {
      char name[OS_FILE_MAX_PATH];
      snprintf(name, sizeof name, "%s/undo%03zu", srv_undo_dir, i + 1);
      if (dberr_t err= srv_undo_tablespace_create(name))
      {
	ib::error() << "Could not create undo tablespace '" << name << "'.";
	return err;
      }
    }
  }

  /* Get the tablespace ids of all the undo segments excluding
  the system tablespace (0). If we are creating a new instance then
  we build the undo_tablespace_ids ourselves since they don't
  already exist. */
  srv_undo_tablespaces_active= srv_undo_tablespaces;

  uint32_t n_undo= (create_new_undo || srv_operation == SRV_OPERATION_BACKUP ||
                    srv_operation == SRV_OPERATION_RESTORE_DELTA)
    ? srv_undo_tablespaces : TRX_SYS_N_RSEGS;

  mysql_mutex_lock(&recv_sys.mutex);
  dberr_t err= srv_all_undo_tablespaces_open(create_new_undo, n_undo);
  mysql_mutex_unlock(&recv_sys.mutex);

  /* Initialize srv_undo_space_id_start=0 when there are no
  dedicated undo tablespaces. */
  if (srv_undo_tablespaces_open == 0)
    srv_undo_space_id_start= 0;

  if (create_new_undo)
    for (uint32_t i= 0; err == DB_SUCCESS && i < srv_undo_tablespaces; ++i)
      err= fsp_header_init(fil_space_get(srv_undo_space_id_start + i),
                           SRV_UNDO_TABLESPACE_SIZE_IN_PAGES, mtr);

  return err;
}

PRAGMA_REENABLE_CHECK_STACK_FRAME


/** Create the temporary file tablespace.
@param[in]	create_new_db	whether we are creating a new database
@return DB_SUCCESS or error code. */
static
dberr_t
srv_open_tmp_tablespace(bool create_new_db)
{
	ulint	sum_of_new_sizes;

	/* Will try to remove if there is existing file left-over by last
	unclean shutdown */
	srv_tmp_space.set_sanity_check_status(true);
	srv_tmp_space.delete_files();
	srv_tmp_space.set_ignore_read_only(true);

	bool	create_new_temp_space;

	srv_tmp_space.set_space_id(SRV_TMP_SPACE_ID);

	dberr_t	err = srv_tmp_space.check_file_spec(
		&create_new_temp_space, 12 * 1024 * 1024);

	if (err == DB_FAIL) {
		ib::error() << "The innodb_temporary"
			" data file must be writable!";
		err = DB_ERROR;
	} else if (err != DB_SUCCESS) {
		ib::error() << "Could not create the shared innodb_temporary.";
	} else if ((err = srv_tmp_space.open_or_create(
			    true, create_new_db, &sum_of_new_sizes))
		   != DB_SUCCESS) {
		ib::error() << "Unable to create the shared innodb_temporary";
	} else if (fil_system.temp_space->open(true)) {
		/* Initialize the header page */
		mtr_t mtr{nullptr};
		mtr.start();
		mtr.set_log_mode(MTR_LOG_NO_REDO);
		err = fsp_header_init(fil_system.temp_space,
				      srv_tmp_space.get_sum_of_sizes(),
				      &mtr);
		mtr.commit();
		if (err == DB_SUCCESS) {
			err = trx_temp_rseg_create(&mtr);
		}
	} else {
		/* This file was just opened in the code above! */
		ib::error() << "The innodb_temporary"
			" data file cannot be re-opened"
			" after check_file_spec() succeeded!";
		err = DB_ERROR;
	}

	return(err);
}

/** Shutdown background threads, except the page cleaner.
@param init_abort set to true when InnoDB startup aborted */
static void srv_shutdown_threads(bool init_abort= false)
{
	ut_ad(!srv_undo_sources);
	srv_master_timer.reset();
	/* In case of InnoDB start up aborted, Don't change
	the srv_shutdown_state. Because innodb_shutdown()
	does call innodb_preshutdown() which changes the
	srv_shutdown_state back to SRV_SHUTDOWN_INITIATED */
	if (!init_abort) {
		srv_shutdown_state = SRV_SHUTDOWN_EXIT_THREADS;
	}

	if (purge_sys.enabled()) {
		srv_purge_shutdown();
	}

	if (srv_n_fil_crypt_threads) {
		fil_crypt_set_thread_cnt(0);
	}
}


/** Shut down background threads that can generate undo log. */
static void srv_shutdown_bg_undo_sources()
{
  srv_shutdown_state= SRV_SHUTDOWN_INITIATED;

  if (srv_undo_sources)
  {
    ut_ad(!srv_read_only_mode);
    fts_optimize_shutdown();
    dict_stats_shutdown();
    srv_undo_sources= false;
  }
}

#ifdef UNIV_DEBUG
# define srv_init_abort(_db_err)	\
	srv_init_abort_low(create_new_db, __FILE__, __LINE__, _db_err)
#else
# define srv_init_abort(_db_err)	\
	srv_init_abort_low(create_new_db, _db_err)
#endif /* UNIV_DEBUG */

/** Innobase start-up aborted. Perform cleanup actions.
@param[in]	create_new_db	TRUE if new db is  being created
@param[in]	file		File name
@param[in]	line		Line number
@param[in]	err		Reason for aborting InnoDB startup
@return DB_SUCCESS or error code. */
MY_ATTRIBUTE((warn_unused_result, nonnull))
static
dberr_t
srv_init_abort_low(
	bool		create_new_db,
#ifdef UNIV_DEBUG
	const char*	file,
	unsigned	line,
#endif /* UNIV_DEBUG */
	dberr_t		err)
{
	ut_ad(srv_is_being_started);

	if (create_new_db) {
		ib::error() << "Database creation was aborted"
#ifdef UNIV_DEBUG
			" at " << innobase_basename(file) << "[" << line << "]"
#endif /* UNIV_DEBUG */
			" with error " << err << ". You may need"
			" to delete the ibdata1 file before trying to start"
			" up again.";
	} else if (srv_operation == SRV_OPERATION_NORMAL) {
		ib::error() << "Plugin initialization aborted"
#ifdef UNIV_DEBUG
			" at " << innobase_basename(file) << "[" << line << "]"
#endif /* UNIV_DEBUG */
			" with error " << err;
	}

	srv_shutdown_bg_undo_sources();
	srv_shutdown_threads(true);
	return(err);
}

/** Prepare to delete the redo log file. Flush the dirty pages from all the
buffer pools.  Flush the redo log buffer to the redo log file.
@return lsn upto which data pages have been flushed. */
ATTRIBUTE_COLD static lsn_t srv_prepare_to_delete_redo_log_file() noexcept
{
  DBUG_ENTER("srv_prepare_to_delete_redo_log_file");

  ut_ad(recv_sys.recovery_on);

  /* Clean the buffer pool. */
  buf_flush_sync();

  DBUG_EXECUTE_IF("innodb_log_abort_1", DBUG_RETURN(0););
  DBUG_PRINT("ib_log", ("After innodb_log_abort_1"));

  log_sys.latch.wr_lock(SRW_LOCK_CALL);
  const bool latest_format{log_sys.is_latest()};
  lsn_t flushed_lsn{log_sys.get_flushed_lsn(std::memory_order_relaxed)};

  if (latest_format && !(log_sys.file_size & 4095) &&
      flushed_lsn != log_sys.next_checkpoint_lsn +
      (log_sys.is_encrypted()
       ? SIZE_OF_FILE_CHECKPOINT + 8
       : SIZE_OF_FILE_CHECKPOINT))
  {
#ifdef HAVE_PMEM
    if (!log_sys.is_opened())
      log_sys.buf_size= unsigned(std::min<uint64_t>(log_sys.capacity(),
                                                    log_sys.buf_size_max));
#endif
    fil_names_clear(flushed_lsn);
    flushed_lsn= log_sys.get_lsn();
  }

  {
    const char *msg;
    if (!latest_format)
    {
      msg= "Upgrading redo log: ";
same_size:
      ib::info() << msg << ib::bytes_iec(srv_log_file_size)
                 << "; LSN=" << flushed_lsn;
    }
    else if (srv_log_file_size == log_sys.file_size)
    {
      msg= srv_encrypt_log
        ? "Encrypting redo log: " : "Removing redo log encryption: ";
      goto same_size;
    }
    else
    {
      if (srv_encrypt_log == (my_bool)log_sys.is_encrypted())
        msg= srv_encrypt_log ? "Resizing encrypted" : "Resizing";
      else
        msg= srv_encrypt_log
          ? "Encrypting and resizing"
          : "Removing encryption and resizing";

      ib::info() << msg << " redo log from "
                 << ib::bytes_iec{log_sys.file_size} << " to "
                 << ib::bytes_iec{srv_log_file_size}
                 << "; LSN=" << flushed_lsn;
    }
  }

  log_sys.latch.wr_unlock();

  if (latest_format)
    log_write_up_to(flushed_lsn, false);

  ut_ad(flushed_lsn == log_get_lsn());
  ut_ad(!os_aio_pending_reads());
  ut_d(mysql_mutex_lock(&buf_pool.flush_list_mutex));
  ut_ad(!buf_pool.get_oldest_modification(0));
  ut_d(mysql_mutex_unlock(&buf_pool.flush_list_mutex));
  ut_d(os_aio_wait_until_no_pending_writes(false));

  DBUG_RETURN(flushed_lsn);
}

/** Upgrade the redo log to the latest format, or change its size
or encryption, before starting to write any log records. */
ATTRIBUTE_COLD static dberr_t srv_log_rebuild()
{
  /* Prepare to delete the old redo log file */
  const lsn_t lsn{srv_prepare_to_delete_redo_log_file()};

  DBUG_EXECUTE_IF("innodb_log_abort_1", return DB_ERROR;);
  /* Prohibit redo log writes from any other threads until creating a
  log checkpoint at the end of create_log_file(). */
  ut_d(recv_no_log_write= true);
  ut_ad(!os_aio_pending_reads());
  ut_d(mysql_mutex_lock(&buf_pool.flush_list_mutex));
  ut_ad(!buf_pool.get_oldest_modification(0));
  ut_d(mysql_mutex_unlock(&buf_pool.flush_list_mutex));
  /* os_aio_pending_writes() may hold here if some write_io_callback()
  did not release the slot yet.  However, the page write itself must
  have completed, because the buf_pool.flush_list is empty. In debug
  builds, we wait for this to happen, hoping to get a hung process if
  this assumption does not hold. */
  ut_d(os_aio_wait_until_no_pending_writes(false));

  /* Close the redo log file, so that we can replace it */
  log_sys.close_file();

  DBUG_EXECUTE_IF("innodb_log_abort_5", return DB_ERROR;);

  dberr_t err= create_log_file(false, lsn);

  if (err == DB_SUCCESS && log_sys.resize_rename())
    err = DB_ERROR;

  return err;
}

/** Rebuild the redo log if needed. */
static dberr_t srv_log_rebuild_if_needed()
{
  if (srv_force_recovery == SRV_FORCE_NO_LOG_REDO)
    /* Completely ignore the redo log. */
    return DB_SUCCESS;
  if (srv_read_only_mode)
    /* Leave the redo log alone. */
    return DB_SUCCESS;

  if (log_sys.file_size == srv_log_file_size &&
      log_sys.format ==
      (srv_encrypt_log ? log_t::FORMAT_ENC_11 : log_t::FORMAT_10_8))
  {
    /* No need to add or remove encryption, upgrade, or resize. */
    delete_log_files();
    return DB_SUCCESS;
  }

  return srv_log_rebuild();
}

ut_d(bool ibuf_upgrade_was_needed;)

ATTRIBUTE_COLD static dberr_t ibuf_log_rebuild_if_needed()
{
  ut_d(ibuf_upgrade_was_needed= true;)
  mysql_mutex_lock(&recv_sys.mutex);
  recv_sys.apply(true);
  mysql_mutex_unlock(&recv_sys.mutex);

  if (recv_sys.is_corrupt_log() || recv_sys.is_corrupt_fs())
    return DB_CORRUPTION;

  dberr_t err= srv_log_rebuild_if_needed();
  recv_sys.debug_free();
  return err;
}


inline lsn_t log_t::init_lsn() noexcept
{
  latch.wr_lock(SRW_LOCK_CALL);
  ut_ad(!write_lsn_offset);
  write_lsn_offset= 0;
  const lsn_t lsn{base_lsn.load(std::memory_order_relaxed)};
  flushed_to_disk_lsn.store(lsn, std::memory_order_relaxed);
  write_lsn= lsn;
  latch.wr_unlock();
  return lsn;
}

PRAGMA_DISABLE_CHECK_STACK_FRAME

/** Load the dictionary tables */
static dberr_t srv_load_tables(bool must_upgrade_ibuf) noexcept
{
  mem_heap_t *heap= mem_heap_create(1000);
  mtr_t mtr{nullptr};
  dict_sys.lock(SRW_LOCK_CALL);
  dberr_t err = dict_load_indexes(&mtr, dict_sys.sys_tables, false, heap,
                                  DICT_ERR_IGNORE_NONE);
  mem_heap_empty(heap);
  if ((err == DB_SUCCESS || srv_force_recovery >= SRV_FORCE_NO_DDL_UNDO) &&
      UNIV_UNLIKELY(must_upgrade_ibuf))
  {
    dict_sys.unlock();
    dict_load_tablespaces(nullptr, true);
    err= ibuf_upgrade();
    dict_sys.lock(SRW_LOCK_CALL);
  }
  if (err == DB_SUCCESS || srv_force_recovery >= SRV_FORCE_NO_DDL_UNDO)
  {
    err = dict_load_indexes(&mtr, dict_sys.sys_columns, false, heap,
                            DICT_ERR_IGNORE_NONE);
    mem_heap_empty(heap);
  }
  if (err == DB_SUCCESS || srv_force_recovery >= SRV_FORCE_NO_DDL_UNDO)
  {
    err = dict_load_indexes(&mtr, dict_sys.sys_indexes, false, heap,
                            DICT_ERR_IGNORE_NONE);
    mem_heap_empty(heap);
  }
  if (err == DB_SUCCESS || srv_force_recovery >= SRV_FORCE_NO_DDL_UNDO) {
    err = dict_load_indexes(&mtr, dict_sys.sys_fields, false, heap,
                            DICT_ERR_IGNORE_NONE);
  }
  mem_heap_free(heap);
  dict_sys.unlock();
  dict_sys.load_sys_tables();
  return err;
}

/** Start InnoDB.
@param[in]	create_new_db	whether to create a new database
@return DB_SUCCESS or error code */
dberr_t srv_start(bool create_new_db)
{
	dberr_t		err		= DB_SUCCESS;
	mtr_t		mtr{nullptr};

	ut_ad(srv_operation <= SRV_OPERATION_RESTORE_EXPORT
	      || srv_operation == SRV_OPERATION_RESTORE
	      || srv_operation == SRV_OPERATION_RESTORE_EXPORT);

	if (srv_force_recovery) {
		ib::info() << "!!! innodb_force_recovery is set to "
			<< srv_force_recovery << " !!!";
		if (srv_force_recovery == SRV_FORCE_NO_LOG_REDO) {
			srv_read_only_mode = true;
		}
	}

	if (srv_read_only_mode) {
		sql_print_information("InnoDB: Started in read only mode");
		buf_dblwr.use = buf_dblwr.USE_NO;
	}

	high_level_read_only = srv_read_only_mode
		|| srv_force_recovery >= SRV_FORCE_NO_UNDO_LOG_SCAN
		|| srv_sys_space.created_new_raw();

	srv_started_redo = false;

	compile_time_assert(sizeof(ulint) == sizeof(void*));

#ifdef UNIV_DEBUG
	ib::info() << "!!!!!!!! UNIV_DEBUG switched on !!!!!!!!!";
#endif

	ib::info() << "Compressed tables use zlib " ZLIB_VERSION
#ifdef UNIV_ZIP_DEBUG
	      " with validation"
#endif /* UNIV_ZIP_DEBUG */
	      ;
#ifdef UNIV_ZIP_COPY
	ib::info() << "and extra copying";
#endif /* UNIV_ZIP_COPY */

	/* Since InnoDB does not currently clean up all its internal data
	structures in MySQL Embedded Server Library server_end(), we
	print an error message if someone tries to start up InnoDB a
	second time during the process lifetime. */

	if (srv_start_has_been_called) {
		ib::error() << "Startup called second time"
			" during the process lifetime."
			" In the MariaDB Embedded Server Library"
			" you cannot call server_init() more than"
			" once during the process lifetime.";
	}

	srv_start_has_been_called = true;

	srv_is_being_started = true;

	/* Register performance schema stages before any real work has been
	started which may need to be instrumented. */
	mysql_stage_register("innodb", srv_stages,
			     static_cast<int>(UT_ARR_SIZE(srv_stages)));

	srv_boot();

	ib::info() << my_crc32c_implementation();

	if (!srv_read_only_mode) {
		mysql_mutex_init(srv_monitor_file_mutex_key,
				 &srv_monitor_file_mutex, nullptr);
		mysql_mutex_init(srv_misc_tmpfile_mutex_key,
				 &srv_misc_tmpfile_mutex, nullptr);
	}

	if (!srv_read_only_mode) {
		if (srv_innodb_status) {

			srv_monitor_file_name = static_cast<char*>(
				ut_malloc_nokey(
					strlen(fil_path_to_mysql_datadir)
					+ 20 + sizeof "/innodb_status."));

			sprintf(srv_monitor_file_name,
				"%s/innodb_status." ULINTPF,
				fil_path_to_mysql_datadir,
				static_cast<ulint>
				(IF_WIN(GetCurrentProcessId(), getpid())));

			srv_monitor_file = my_fopen(srv_monitor_file_name,
						    O_RDWR|O_TRUNC|O_CREAT,
						    MYF(MY_WME));

			if (!srv_monitor_file) {
				ib::error() << "Unable to create "
					<< srv_monitor_file_name << ": "
					<< strerror(errno);
				if (err == DB_SUCCESS) {
					err = DB_ERROR;
				}
			}
		} else {

			srv_monitor_file_name = NULL;
			srv_monitor_file = os_file_create_tmpfile();

			if (!srv_monitor_file && err == DB_SUCCESS) {
				err = DB_ERROR;
			}
		}

		srv_misc_tmpfile = os_file_create_tmpfile();

		if (!srv_misc_tmpfile && err == DB_SUCCESS) {
			err = DB_ERROR;
		}
	}

	if (err != DB_SUCCESS) {
		return(srv_init_abort(err));
	}

	if (os_aio_init()) {
		return(srv_init_abort(DB_ERROR));
	}

	fil_system.create(srv_file_per_table ? 50000 : 5000);

	if (buf_pool.create()) {
		return(srv_init_abort(DB_ERROR));
	}

	log_sys.create();
	recv_sys.create();
	lock_sys.create(srv_lock_table_size = 5 * buf_pool.curr_size());

	srv_startup_is_before_trx_rollback_phase = true;

	if (!srv_read_only_mode) {
		buf_flush_page_cleaner_init();
		ut_ad(buf_page_cleaner_is_active);
	}

	if (innodb_encrypt_temporary_tables && !log_crypt_init()) {
		return srv_init_abort(DB_ERROR);
	}

	/* Check if undo tablespaces and redo log files exist before creating
	a new system tablespace */
	if (create_new_db) {
		err = srv_check_undo_redo_logs_exists();
		if (err != DB_SUCCESS) {
			return(srv_init_abort(DB_ERROR));
		}
		recv_sys.debug_free();
	} else {
		err = recv_recovery_read_checkpoint();
		if (err != DB_SUCCESS) {
			return srv_init_abort(err);
		}
	}

	/* Open or create the data files. */
	ulint	sum_of_new_sizes;

	err = srv_sys_space.open_or_create(
		false, create_new_db, &sum_of_new_sizes);

	switch (err) {
	case DB_SUCCESS:
		break;
	case DB_CANNOT_OPEN_FILE:
		ib::error()
			<< "Could not open or create the system tablespace. If"
			" you tried to add new data files to the system"
			" tablespace, and it failed here, you should now"
			" edit innodb_data_file_path in my.cnf back to what"
			" it was, and remove the new ibdata files InnoDB"
			" created in this failed attempt. InnoDB only wrote"
			" those files full of zeros, but did not yet use"
			" them in any way. But be careful: do not remove"
			" old data files which contain your precious data!";
		/* fall through */
	default:
		/* Other errors might be flagged by
		Datafile::validate_first_page() */
		return srv_init_abort(err);
	}

	if (create_new_db) {
		lsn_t flushed_lsn = log_sys.init_lsn();

		err = create_log_file(true, flushed_lsn);

		if (err != DB_SUCCESS) {
			for (const Datafile &file: srv_sys_space) {
				os_file_delete(innodb_data_file_key,
					       file.filepath());
			}
			return srv_init_abort(err);
		}

		srv_undo_space_id_start = 1;
	}

	/* Open data files in the system tablespace: we keep
	them open until database shutdown */
	mysql_mutex_lock(&recv_sys.mutex);
	ut_d(fil_system.sys_space->recv_size = srv_sys_space_size_debug);
	err = fil_system.sys_space->open(create_new_db)
		? DB_SUCCESS : DB_ERROR;
	mysql_mutex_unlock(&recv_sys.mutex);


	if (err == DB_SUCCESS) {
		mtr.start();
		err= srv_undo_tablespaces_init(create_new_db, &mtr);
		mtr.commit();
	}
	else {
		err= DB_ERROR;
	}

	/* If the force recovery is set very high then we carry on regardless
	of all errors. Basically this is fingers crossed mode. */

	if (err != DB_SUCCESS
	    && srv_force_recovery < SRV_FORCE_NO_UNDO_LOG_SCAN) {

		return(srv_init_abort(err));
	}

	/* Initialize objects used by dict stats gathering thread, which
	can also be used by recovery if it tries to drop some table */
	if (!srv_read_only_mode) {
		dict_stats_init();
	}

	trx_sys.create();

	if (create_new_db) {
		ut_ad(!srv_read_only_mode);

		mtr.start();
		ut_ad(fil_system.sys_space->id == 0);
		compile_time_assert(TRX_SYS_SPACE == 0);
		err = fsp_header_init(fil_system.sys_space,
				      uint32_t(sum_of_new_sizes), &mtr);
		/* Allocate dummy change buffer pages for backward
		compatibility and to prevent a downgrade. */
		if (err != DB_SUCCESS) {
		} else if (buf_block_t *b =
			   fseg_create(fil_system.sys_space, PAGE_DATA, &mtr,
				       &err)) {
			ut_ad(b->page.id()
			      == page_id_t(0, FSP_IBUF_HEADER_PAGE_NO));
			b = fseg_alloc_free_page_general(
				b->page.frame + PAGE_DATA,
				FSP_IBUF_TREE_ROOT_PAGE_NO, FSP_UP, false,
				&mtr, &mtr, &err);
			if (b) {
				ut_ad(b->page.id() == page_id_t
				      (0, FSP_IBUF_TREE_ROOT_PAGE_NO));
				mtr.set_modified(*b);
				fsp_init_file_page(fil_system.sys_space, b,
						   &mtr);
			} else {
				ut_ad(err != DB_SUCCESS);
			}
		}
		/* To maintain backward compatibility we create only
		the first rollback segment before the double write buffer.
		All the remaining rollback segments will be created later,
		after the double write buffer has been created. */
		if (err == DB_SUCCESS) {
			err = trx_sys_create_sys_pages(&mtr);
		}
		mtr.commit();

		if (err != DB_SUCCESS) {
			return(srv_init_abort(err));
		}

		err = dict_create();

		if (err != DB_SUCCESS) {
			return(srv_init_abort(err));
		}

		buf_flush_sync();

		ut_ad(!srv_log_file_created);
		ut_d(srv_log_file_created= true);

		if (log_sys.resize_rename()) {
			return(srv_init_abort(DB_ERROR));
		}
	} else {
		/* Suppress warnings in fil_space_t::create() for files
		that are being read before dict_boot() has recovered
		DICT_HDR_MAX_SPACE_ID. */
		fil_system.space_id_reuse_warned = true;

		/* We always try to do a recovery, even if the database had
		been shut down normally: this is the normal startup path */

		err = recv_recovery_from_checkpoint_start();
		recv_sys.close_files();

		bool must_upgrade_ibuf = false;

		switch (srv_operation) {
                case SRV_OPERATION_NORMAL:
		case SRV_OPERATION_EXPORT_RESTORED:
		case SRV_OPERATION_RESTORE_EXPORT:
			if (err != DB_SUCCESS) {
				break;
			}

			err = ibuf_upgrade_needed();

			if (UNIV_UNLIKELY(err == DB_FAIL)) {
				must_upgrade_ibuf = true;
				err = ibuf_log_rebuild_if_needed();
			}

			if (err != DB_SUCCESS) {
				break;
			}

			err = dict_boot();
			/* fall through */
		case SRV_OPERATION_RESTORE:
			if (err != DB_SUCCESS) {
				break;
			}

			srv_undo_tablespaces_active
				= trx_rseg_get_n_undo_tablespaces();
			break;
		default:
			ut_ad("wrong mariabackup mode" == 0);
		}

		if (err != DB_SUCCESS) {
			return srv_init_abort(err);
		}

		if (srv_force_recovery < SRV_FORCE_NO_LOG_REDO) {
			/* Apply the hashed log records to the
			respective file pages, for the last batch of
			recv_group_scan_log_recs().
			Since it may generate huge batch of threadpool tasks,
			for read io task group, scale down thread creation rate
			by temporarily restricting tpool concurrency.
			*/
			srv_thread_pool->set_concurrency(srv_n_read_io_threads);

			mysql_mutex_lock(&recv_sys.mutex);
			recv_sys.apply(true);
			mysql_mutex_unlock(&recv_sys.mutex);

			srv_thread_pool->set_concurrency();

			if (recv_sys.is_corrupt_log()
			    || recv_sys.is_corrupt_fs()) {
				return(srv_init_abort(DB_CORRUPTION));
			}

			if (srv_operation != SRV_OPERATION_RESTORE
			    || recv_needed_recovery) {
			}

			DBUG_PRINT("ib_log", ("apply completed"));

			if (srv_operation != SRV_OPERATION_RESTORE) {
				err = srv_load_tables(must_upgrade_ibuf);
				if (err != DB_SUCCESS) {
					return srv_init_abort(err);
				}

				err = trx_lists_init_at_db_start();
				if (err != DB_SUCCESS) {
					return srv_init_abort(err);
				}

				if (recv_needed_recovery) {
					trx_sys_print_mysql_binlog_offset();
				}
			} else if (recv_needed_recovery) {
				err = trx_lists_init_at_db_start();
				if (err != DB_SUCCESS) {
					return srv_init_abort(err);
				}
				trx_sys_print_mysql_binlog_offset();
			}
		}

		fil_system.space_id_reuse_warned = false;

		if (srv_operation > SRV_OPERATION_EXPORT_RESTORED) {
			ut_ad(srv_operation == SRV_OPERATION_RESTORE_EXPORT
			      || srv_operation == SRV_OPERATION_RESTORE);
			return(err);
		}

		/* Upgrade or resize or rebuild the redo logs before
		generating any dirty pages, so that the old redo log
		file will not be written to. */

		err = srv_log_rebuild_if_needed();

		if (err != DB_SUCCESS) {
			return srv_init_abort(err);
		}

		recv_sys.debug_free();

		if (!srv_read_only_mode) {
			const uint32_t flags = FSP_FLAGS_PAGE_SSIZE();
			for (uint32_t id = srv_undo_space_id_start;
			     id <= srv_undo_tablespaces; id++) {
				if (fil_space_t* space = fil_space_get(id)) {
					fsp_flags_try_adjust(space, flags);
				}
			}

			if (sum_of_new_sizes > 0) {
				/* New data file(s) were added */
				mtr.start();
				mtr.x_lock_space(fil_system.sys_space);
				buf_block_t* block = buf_page_get(
					page_id_t(0, 0), 0,
					RW_SX_LATCH, &mtr);
				/* The first page of the system tablespace
				should already have been successfully
				accessed earlier during startup. */
				ut_a(block);
				ulint size = mach_read_from_4(
					FSP_HEADER_OFFSET + FSP_SIZE
					+ block->page.frame);
				ut_ad(size == fil_system.sys_space
				      ->size_in_header);
				size += sum_of_new_sizes;
				mtr.write<4>(*block,
					     FSP_HEADER_OFFSET + FSP_SIZE
					     + block->page.frame, size);
				fil_system.sys_space->size_in_header
					= uint32_t(size);
				mtr.commit();
				log_write_up_to(mtr.commit_lsn(), true);
			}
		}

#ifdef UNIV_DEBUG
		{
			mtr.start();
			buf_block_t* block = buf_page_get(page_id_t(0, 0), 0,
							  RW_S_LATCH, &mtr);
			ut_ad(mach_read_from_4(FSP_SIZE + FSP_HEADER_OFFSET
					       + block->page.frame)
			      == fil_system.sys_space->size_in_header);
			mtr.commit();
		}
#endif
		const ulint	tablespace_size_in_header
			= fil_system.sys_space->size_in_header;
		const ulint	sum_of_data_file_sizes
			= srv_sys_space.get_sum_of_sizes();
		/* Compare the system tablespace file size to what is
		stored in FSP_SIZE. In srv_sys_space.open_or_create()
		we already checked that the file sizes match the
		innodb_data_file_path specification. */
		if (srv_read_only_mode
		    || sum_of_data_file_sizes == tablespace_size_in_header) {
			/* Do not complain about the size. */
		} else if (!srv_sys_space.can_auto_extend_last_file()
			   || sum_of_data_file_sizes
			   < tablespace_size_in_header) {
			ib::error() << "Tablespace size stored in header is "
				<< tablespace_size_in_header
				<< " pages, but the sum of data file sizes is "
				<< sum_of_data_file_sizes << " pages";

			if (srv_force_recovery == 0
			    && sum_of_data_file_sizes
			    < tablespace_size_in_header) {
				ib::error() <<
					"Cannot start InnoDB. The tail of"
					" the system tablespace is"
					" missing. Have you edited"
					" innodb_data_file_path in my.cnf"
					" in an inappropriate way, removing"
					" data files from there?"
					" You can set innodb_force_recovery=1"
					" in my.cnf to force"
					" a startup if you are trying to"
					" recover a badly corrupt database.";

				return(srv_init_abort(DB_ERROR));
			}
		}
	}

	ut_ad(err == DB_SUCCESS);
	ut_a(sum_of_new_sizes != ULINT_UNDEFINED);

	/* Create the doublewrite buffer to a new tablespace */
	if (!srv_read_only_mode && srv_force_recovery < SRV_FORCE_NO_TRX_UNDO
	    && !buf_dblwr.create()) {
		return(srv_init_abort(DB_ERROR));
	}

	/* Recreate the undo tablespaces */
	if (!high_level_read_only) {
		err = srv_undo_tablespaces_reinitialize();
		if (err) {
			return srv_init_abort(err);
		}
	}

	srv_undo_tablespaces = srv_undo_tablespaces_open;

	/* Here the double write buffer has already been created and so
	any new rollback segments will be allocated after the double
	write buffer. The default segment should already exist.
	We create the new segments only if it's a new database or
	the database was shutdown cleanly. */

	/* Note: When creating the extra rollback segments during an upgrade
	we violate the latching order, even if the change buffer is empty.
	It cannot create a deadlock because we are still
	running in single threaded mode essentially. Only the IO threads
	should be running at this stage. */

	if (!trx_sys_create_rsegs()) {
		return(srv_init_abort(DB_ERROR));
	}

	if (!create_new_db) {
		ut_ad(high_level_read_only
		      || srv_force_recovery < SRV_FORCE_NO_UNDO_LOG_SCAN);

		if (!high_level_read_only
		    && srv_sys_space.can_auto_shrink()) {
			fsp_system_tablespace_truncate(false);
			DBUG_EXECUTE_IF("crash_after_sys_truncate",
					return srv_init_abort(DB_ERROR););
		}

		/* Validate a few system page types that were left
		uninitialized before MySQL or MariaDB 5.5. */
		if (!high_level_read_only
		    && !fil_system.sys_space->full_crc32()) {
			buf_block_t*	block;
			mtr.start();
			/* Bitmap page types will be reset in
			buf_dblwr_check_block() without redo logging. */
			block = buf_page_get(
				page_id_t(0, FSP_IBUF_HEADER_PAGE_NO),
				0, RW_X_LATCH, &mtr);
			if (UNIV_UNLIKELY(!block)) {
			corrupted_old_page:
				mtr.commit();
				return srv_init_abort(DB_CORRUPTION);
			}
			fil_block_check_type(*block, FIL_PAGE_TYPE_SYS, &mtr);
			/* Already MySQL 3.23.53 initialized
			FSP_IBUF_TREE_ROOT_PAGE_NO to
			FIL_PAGE_INDEX. No need to reset that one. */
			block = buf_page_get(
				page_id_t(TRX_SYS_SPACE, TRX_SYS_PAGE_NO),
				0, RW_X_LATCH, &mtr);
			if (UNIV_UNLIKELY(!block)) {
				goto corrupted_old_page;
			}
			fil_block_check_type(*block, FIL_PAGE_TYPE_TRX_SYS,
					     &mtr);
			block = buf_page_get(
				page_id_t(TRX_SYS_SPACE,
					  FSP_FIRST_RSEG_PAGE_NO),
				0, RW_X_LATCH, &mtr);
			if (UNIV_UNLIKELY(!block)) {
				goto corrupted_old_page;
			}
			fil_block_check_type(*block, FIL_PAGE_TYPE_SYS, &mtr);
			block = buf_page_get(
				page_id_t(TRX_SYS_SPACE, FSP_DICT_HDR_PAGE_NO),
				0, RW_X_LATCH, &mtr);
			if (UNIV_UNLIKELY(!block)) {
				goto corrupted_old_page;
			}
			fil_block_check_type(*block, FIL_PAGE_TYPE_SYS, &mtr);
			mtr.commit();
		}

		/* Roll back any recovered data dictionary
		transactions, so that the data dictionary tables will
		be free of any locks.  The data dictionary latch
		should guarantee that there is at most one data
		dictionary transaction active at a time. */
		if (!high_level_read_only
		    && srv_force_recovery <= SRV_FORCE_NO_TRX_UNDO) {
			/* If the following call is ever removed, the
			first-time ha_innobase::open() must hold (or
			acquire and release) a table lock that
			conflicts with trx_resurrect_table_locks(), to
			ensure that any recovered incomplete ALTER
			TABLE will have been rolled back. Otherwise,
			dict_table_t::instant could be cleared by
			rollback invoking
			dict_index_t::clear_instant_alter() while open
			table handles exist in client connections. */
			trx_rollback_recovered(false);
		}

		if (srv_force_recovery < SRV_FORCE_NO_UNDO_LOG_SCAN) {
			dict_load_tablespaces();
		}

		if (srv_force_recovery < SRV_FORCE_NO_TRX_UNDO
		    && !srv_read_only_mode) {
			/* Drop partially created indexes. */
			row_merge_drop_temp_indexes();
#ifdef EMBEDDED_LIBRARY
			/* Rollback incomplete non-DDL transactions */
			trx_rollback_is_active = true;
			srv_thread_pool->submit_task(&rollback_all_recovered_task);
#endif
		}
	}

	srv_startup_is_before_trx_rollback_phase = false;

	if (!srv_read_only_mode) {
		DBUG_EXECUTE_IF("innodb_skip_monitors", goto skip_monitors;);
		/* Create the task which warns of long semaphore waits */
		srv_start_periodic_timer(srv_monitor_timer, srv_monitor_task,
					 SRV_MONITOR_INTERVAL);

#ifndef DBUG_OFF
skip_monitors:
#endif
		ut_ad(srv_force_recovery >= SRV_FORCE_NO_UNDO_LOG_SCAN
		      || !purge_sys.enabled());

		if (srv_force_recovery < SRV_FORCE_NO_BACKGROUND) {
			srv_undo_sources = true;
			/* Create the dict stats gathering task */
			dict_stats_start();
			/* Create the thread that will optimize the
			FULLTEXT search index subsystem. */
			fts_optimize_init();
		}
	}

	err = dict_sys.create_or_check_sys_tables();
	switch (err) {
	case DB_SUCCESS:
		break;
	case DB_READ_ONLY:
		if (srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO) {
			break;
		}
		ib::error() << "Cannot create system tables in read-only mode";
		/* fall through */
	default:
		return(srv_init_abort(err));
	}

	if (!srv_read_only_mode
	    && srv_operation <= SRV_OPERATION_EXPORT_RESTORED) {
		/* Initialize the innodb_temporary tablespace and keep
		it open until shutdown. */
		err = srv_open_tmp_tablespace(create_new_db);

		if (err != DB_SUCCESS) {
			return(srv_init_abort(err));
		}

		if (srv_force_recovery < SRV_FORCE_NO_BACKGROUND) {
			srv_start_periodic_timer(srv_master_timer, srv_master_callback, 1000);
		}
	}

	srv_is_being_started = false;

	if (srv_print_verbose_log) {
		sql_print_information("InnoDB: "
				      "log sequence number " LSN_PF
				      "%s"
				      "; transaction id " TRX_ID_FMT,
				      recv_sys.lsn,
				      log_sys.is_mmap()
				      ? " (memory-mapped)" : "",
				      trx_sys.get_max_trx_id());
	}

	if (!srv_read_only_mode) {
		if (create_new_db) {
			srv_buffer_pool_load_at_startup = FALSE;
		}

#ifdef WITH_WSREP
		/*
		  Create the dump/load thread only when not running with
		  --wsrep-recover.
		*/
		if (!get_wsrep_recovery()) {
#endif /* WITH_WSREP */

		/* Start buffer pool dump/load task */
		buf_load_at_startup();

#ifdef WITH_WSREP
		} else {
			ib::warn() <<
				"Skipping buffer pool dump/restore during "
				"wsrep recovery.";
		}
#endif /* WITH_WSREP */

		/* Create thread(s) that handles key rotation. This is
		needed already here as log_preflush_pool_modified_pages
		will flush dirty pages and that might need e.g.
		fil_crypt_threads_cond. */
		fil_crypt_threads_init();

		srv_started_redo = true;
	}

	return(DB_SUCCESS);
}

PRAGMA_REENABLE_CHECK_STACK_FRAME

/**
  Shutdown purge to make sure that there is no possibility that we call any
  plugin code (e.g., audit) inside virtual column computation.
*/
void innodb_preshutdown()
{
  static bool first_time= true;
  if (!first_time)
    return;
  first_time= false;

  if (srv_read_only_mode)
    return;

#ifndef EMBEDDED_LIBRARY
  if (srv_force_recovery < SRV_FORCE_NO_TRX_UNDO && srv_was_started &&
      !trx_rollback_is_active && trx_sys.any_active_transaction_recovered())
  {
    /* Trx rollback thread may not be started yet, since error happened before
       innobase_tc_log_recovery_done(), thus it is started here to finish the
       normal shutdown process.
     */
    trx_rollback_is_active= true;
    srv_thread_pool->submit_task(&rollback_all_recovered_task);
  }
#endif

  if (!srv_fast_shutdown && srv_operation <= SRV_OPERATION_EXPORT_RESTORED)
    if (srv_force_recovery < SRV_FORCE_NO_TRX_UNDO && srv_was_started)
      while (trx_sys.any_active_transactions())
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
  srv_shutdown_bg_undo_sources();
  srv_purge_shutdown();

  if (srv_n_fil_crypt_threads)
    fil_crypt_set_thread_cnt(0);
}


/** Shut down InnoDB. */
void innodb_shutdown()
{
	innodb_preshutdown();
	ut_ad(!srv_undo_sources);
	switch (srv_operation) {
	case SRV_OPERATION_BACKUP:
	case SRV_OPERATION_RESTORE_DELTA:
	case SRV_OPERATION_BACKUP_NO_DEFER:
		break;
	case SRV_OPERATION_RESTORE:
	case SRV_OPERATION_RESTORE_EXPORT:
		mysql_mutex_lock(&buf_pool.flush_list_mutex);
		srv_shutdown_state = SRV_SHUTDOWN_CLEANUP;
		while (buf_page_cleaner_is_active) {
			pthread_cond_signal(&buf_pool.do_flush_list);
			my_cond_wait(&buf_pool.done_flush_list,
				     &buf_pool.flush_list_mutex.m_mutex);
		}
		mysql_mutex_unlock(&buf_pool.flush_list_mutex);
		break;
	case SRV_OPERATION_NORMAL:
	case SRV_OPERATION_EXPORT_RESTORED:
		/* Shut down the persistent files. */
		logs_empty_and_mark_files_at_shutdown();
	}

	os_aio_free();
	fil_space_t::close_all();
	/* Exit any remaining threads. */
	ut_ad(!buf_page_cleaner_is_active);
	srv_shutdown_threads();

	if (srv_monitor_file) {
		my_fclose(srv_monitor_file, MYF(MY_WME));
		srv_monitor_file = 0;
		if (srv_monitor_file_name) {
			unlink(srv_monitor_file_name);
			ut_free(srv_monitor_file_name);
		}
	}

	if (srv_misc_tmpfile) {
		my_fclose(srv_misc_tmpfile, MYF(MY_WME));
		srv_misc_tmpfile = 0;
	}

	ut_ad(dict_sys.is_initialised() || !srv_was_started);
	ut_ad(trx_sys.is_initialised() || !srv_was_started);
	ut_ad(buf_dblwr.is_created() || !srv_was_started
	      || srv_read_only_mode
	      || srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);
	ut_ad(lock_sys.is_initialised() || !srv_was_started);
	ut_ad(log_sys.is_initialised() || !srv_was_started);

	dict_stats_deinit();

	if (srv_started_redo) {
		ut_ad(!srv_read_only_mode);
		/* srv_shutdown_bg_undo_sources() already invoked
		fts_optimize_shutdown(); dict_stats_shutdown(); */

		fil_crypt_threads_cleanup();
	}

	/* This must be disabled before closing the buffer pool
	and closing the data dictionary.  */

#ifdef BTR_CUR_HASH_ADAPT
	if (dict_sys.is_initialised()) {
		btr_search.disable();
	}
#endif /* BTR_CUR_HASH_ADAPT */
	log_sys.close();
	purge_sys.close();
	trx_sys.close();
	buf_dblwr.close();
	lock_sys.close();
	trx_pool_close();

	if (!srv_read_only_mode) {
		mysql_mutex_destroy(&srv_monitor_file_mutex);
		mysql_mutex_destroy(&srv_misc_tmpfile_mutex);
	}

	dict_sys.close();
	btr_search_sys_free();
	srv_free();
	fil_system.close();
	pars_lexer_close();
	recv_sys.close();

	ut_ad(buf_pool.is_initialised() || !srv_was_started);
	buf_pool.close();

	srv_sys_space.shutdown();
	if (srv_tmp_space.get_sanity_check_status()) {
		if (fil_system.temp_space) {
			fil_system.temp_space->close();
		}
		srv_tmp_space.delete_files();
	}
	srv_tmp_space.shutdown();

	if (srv_stats.pages_page_compression_error)
		ib::warn() << "Page compression errors: "
			   << srv_stats.pages_page_compression_error;

	if (srv_was_started && srv_print_verbose_log) {
		ib::info() << "Shutdown completed; log sequence number "
			   << srv_shutdown_lsn
			   << "; transaction id " << trx_sys.get_max_trx_id();
	}
	srv_thread_pool_end();
	srv_started_redo = false;
	srv_was_started = false;
	srv_start_has_been_called = false;
}

/** Get the meta-data filename from the table name for a
single-table tablespace.
@param[in]	table		table object
@param[out]	filename	filename
@param[in]	max_len		filename max length */
void
srv_get_meta_data_filename(
	dict_table_t*	table,
	char*		filename,
	ulint		max_len)
{
	ulint		len;
	char*		path;

	/* Make sure the data_dir_path is set. */
	dict_get_and_save_data_dir_path(table);

	const char* data_dir_path = DICT_TF_HAS_DATA_DIR(table->flags)
		? table->data_dir_path : nullptr;
	ut_ad(!DICT_TF_HAS_DATA_DIR(table->flags) || data_dir_path);

	path = fil_make_filepath(data_dir_path, table->name, CFG,
				 data_dir_path != nullptr);
	ut_a(path);
	len = strlen(path);
	ut_a(max_len >= len);

	strcpy(filename, path);

	ut_free(path);
}
