Skip to content

File migrate.c

File List > cubrid > src > executables > migrate.c

Go to the documentation of this file

/*
 * Copyright 2008 Search Solution Corporation
 * Copyright 2016 CUBRID Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

/*
 * migrate.c :
 */

#ident "$Id$"

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <errno.h>
#include <assert.h>

#include "dbi.h"
#include "porting.h"
#include "utility.h"
#include "databases_file.h"
#include "boot_sr.h"
#include "log_impl.h"
#include "btree.h"
#include "schema_system_catalog_constants.h"
#include "message_catalog.h"
#include "error_manager.h"
#include "system_parameter.h"
#include "language_support.h"
#include "catalog_class.h"

#define V9_1_LEVEL (9.1f)
#define V9_2_LEVEL (9.2f)

#define FOPEN_AND_CHECK(fp, filename, mode) \
do { \
  (fp) = fopen ((filename), (mode)); \
  if ((fp) == NULL) \
    { \
      printf ("file open error: %s, %d\n", (filename), errno); \
      return ER_FAILED; \
    } \
} while (0)

#define FSEEK_AND_CHECK(fp, size, origin, filename) \
do { \
  if (fseek ((fp), (size), (origin)) < 0) \
    { \
      printf ("file seek error: %s, %d\n", (filename), errno); \
      fclose ((fp)); \
      return ER_FAILED; \
    } \
} while (0)

#define FREAD_AND_CHECK(ptr, size, n, fp, filename) \
do { \
  fread ((ptr), (size), (n), (fp)); \
  if (ferror ((fp)) != 0) \
    { \
      printf ("file fread error: %s, %d\n", (filename), errno); \
      fclose ((fp)); \
      return ER_FAILED; \
    } \
} while (0)

#define FWRITE_AND_CHECK(ptr, size, n, fp, filename) \
do { \
  fwrite ((ptr), (size), (n), (fp)); \
  if (ferror ((fp)) != 0) \
    { \
      printf ("file fwrite error: %s, %d\n", (filename), errno); \
      fclose ((fp)); \
      return ER_FAILED; \
    } \
} while (0)

#define FFLUSH_AND_CHECK(fp, filename) \
do { \
  if (fflush ((fp)) != 0) \
    { \
      printf ("file fflush error: %s, %d\n", (filename), errno); \
      fclose ((fp)); \
      return ER_FAILED; \
    } \
} while (0)

#define FCLOSE_AND_CHECK(fp, filename) \
do { \
  if (fclose ((fp)) != 0) \
    { \
      printf ("file fclose error: %s, %d\n", (filename), errno); \
      return ER_FAILED; \
    } \
} while (0)

#define UNDO_LIST_SIZE (32)

typedef struct r91_disk_var_header R91_DISK_VAR_HEADER;
struct r91_disk_var_header
{
  char magic[CUBRID_MAGIC_MAX_LENGTH];
  INT16 iopagesize;
  INT16 volid;
  DISK_VOLPURPOSE purpose;
  INT32 sect_npgs;
  INT32 total_sects;
  INT32 free_sects;
  INT32 hint_allocsect;
  INT32 total_pages;
  INT32 free_pages;
  INT32 sect_alloctb_npages;
  INT32 page_alloctb_npages;
  INT32 sect_alloctb_page1;
  INT32 page_alloctb_page1;
  INT32 sys_lastpage;
  INT64 db_creation;
  INT32 max_npages;
  INT32 dummy;
  LOG_LSA chkpt_lsa;
  HFID boot_hfid;
  INT16 offset_to_vol_fullname;
  INT16 offset_to_next_vol_fullname;
  INT16 offset_to_vol_remarks;
  char var_fields[1];
};

typedef struct
{
  char *filename;
  char *page;
  int page_size;
} VOLUME_UNDO_INFO;

static int vol_undo_list_length;
static int vol_undo_count;
static VOLUME_UNDO_INFO *vol_undo_info;

static int fix_all_volume_header (const char *db_path);
static int fix_volume_header (const char *vol_path);
static int undo_fix_volume_header (const char *vol_path, char *undo_page, int size);
static char *make_volume_header_undo_page (const char *vol_path, int size);
static void free_volume_header_undo_list (void);

static int get_active_log_vol_path (const char *db_path, char *logvol_path);
static int check_and_fix_compat_level (const char *db_name, const char *vol_path);
static int get_db_path (const char *db_name, char *db_full_path);
static int fix_codeset_in_active_log (const char *db_path, INTL_CODESET codeset);

static int
get_active_log_vol_path (const char *db_path, char *logvol_path)
{
  char vol_info_path[PATH_MAX], vol_path[PATH_MAX];
  FILE *vol_info_fp = NULL;
  int volid = NULL_VOLID;
  char scan_format[32];

  fileio_make_volume_info_name (vol_info_path, db_path);

  FOPEN_AND_CHECK (vol_info_fp, vol_info_path, "r");

  sprintf (scan_format, "%%d %%%ds", (int) (sizeof (vol_path) - 1));
  while (true)
    {
      if (fscanf (vol_info_fp, scan_format, &volid, vol_path) != 2)
    {
      break;
    }

      if (volid != LOG_DBLOG_ACTIVE_VOLID)
    {
      continue;
    }

      strcpy (logvol_path, vol_path);
      break;
    }

  FCLOSE_AND_CHECK (vol_info_fp, vol_info_path);
  return NO_ERROR;
}

static int
fix_codeset_in_active_log (const char *db_path, INTL_CODESET codeset)
{
  FILE *fp = NULL;
  char vol_path[PATH_MAX];
  LOG_HEADER *hdr;
  LOG_PAGE *hdr_page;
  char log_io_page[IO_MAX_PAGE_SIZE];

  if (get_active_log_vol_path (db_path, vol_path) != NO_ERROR)
    {
      printf ("Can't find log active volume path.\n");
      return ER_FAILED;
    }

  hdr_page = (LOG_PAGE *) log_io_page;
  hdr = (LOG_HEADER *) hdr_page->area;

  FOPEN_AND_CHECK (fp, vol_path, "rb+");
  FREAD_AND_CHECK (log_io_page, sizeof (char), LOG_PAGESIZE, fp, vol_path);

  hdr->db_charset = codeset;

  rewind (fp);
  FWRITE_AND_CHECK (log_io_page, sizeof (char), LOG_PAGESIZE, fp, vol_path);
  FFLUSH_AND_CHECK (fp, vol_path);
  FCLOSE_AND_CHECK (fp, vol_path);

  return NO_ERROR;
}

static int
check_and_fix_compat_level (const char *db_name, const char *db_path)
{
  FILE *fp = NULL;
  char vol_path[PATH_MAX];
  LOG_HEADER *hdr;
  LOG_PAGE *hdr_page;
  char log_io_page[IO_MAX_PAGE_SIZE];

  if (get_active_log_vol_path (db_path, vol_path) != NO_ERROR)
    {
      printf ("Can't found log active volume path.\n");
      return ER_FAILED;
    }

  hdr_page = (LOG_PAGE *) log_io_page;
  hdr = (LOG_HEADER *) hdr_page->area;

  FOPEN_AND_CHECK (fp, vol_path, "rb+");
  FREAD_AND_CHECK (log_io_page, sizeof (char), LOG_PAGESIZE, fp, vol_path);

  if (hdr->db_compatibility == V9_2_LEVEL)
    {
      printf ("This database (%s) is already updated.\n", db_name);
      return ER_FAILED;
    }

  if (hdr->db_compatibility != V9_1_LEVEL)
    {
      printf ("Cannot migrate this database: " "%s is not CUBRID 9.1 database.\n", db_name);
      return ER_FAILED;
    }

  if (hdr->is_shutdown == false)
    {
      printf ("This database (%s) was not normally terminated.\n" "Please start and shutdown with CUBRID 9.1 ,"
          "and retry migration.\n", db_name);
      return ER_FAILED;
    }

  hdr->db_compatibility = rel_disk_compatible ();
  hdr->db_charset = INTL_CODESET_NONE;

  rewind (fp);
  FWRITE_AND_CHECK (log_io_page, sizeof (char), hdr->db_logpagesize, fp, vol_path);
  FFLUSH_AND_CHECK (fp, vol_path);
  FCLOSE_AND_CHECK (fp, vol_path);

  return NO_ERROR;
}

static int
undo_fix_compat_level (const char *db_path)
{
  FILE *fp = NULL;
  char vol_path[PATH_MAX];
  LOG_HEADER *hdr;
  LOG_PAGE *hdr_page;
  char log_io_page[IO_MAX_PAGE_SIZE];

  if (get_active_log_vol_path (db_path, vol_path) != NO_ERROR)
    {
      printf ("Can't found log active volume path.\n");
      return ER_FAILED;
    }

  hdr_page = (LOG_PAGE *) log_io_page;
  hdr = (LOG_HEADER *) hdr_page->area;

  FOPEN_AND_CHECK (fp, vol_path, "rb+");
  FREAD_AND_CHECK (log_io_page, sizeof (char), LOG_PAGESIZE, fp, vol_path);

  hdr->db_compatibility = V9_1_LEVEL;

  rewind (fp);
  FWRITE_AND_CHECK (log_io_page, sizeof (char), LOG_PAGESIZE, fp, vol_path);
  FFLUSH_AND_CHECK (fp, vol_path);
  FCLOSE_AND_CHECK (fp, vol_path);

  return NO_ERROR;
}

static int
get_db_path (const char *db_name, char *db_full_path)
{
  DB_INFO *dir = NULL;
  DB_INFO *db = NULL;

  if (cfg_read_directory (&dir, false) != NO_ERROR)
    {
      printf ("Can't found databases.txt.\n");
      return ER_FAILED;
    }

  db = cfg_find_db_list (dir, db_name);

  if (db == NULL)
    {
      if (dir)
    {
      cfg_free_directory (dir);
    }

      printf ("Unknown database: %s\n", db_name);
      return ER_FAILED;
    }

  COMPOSE_FULL_NAME (db_full_path, PATH_MAX, db->pathname, db_name);

  return NO_ERROR;
}

#if defined(WINDOWS)
static BOOL WINAPI
#else
static void
#endif
intr_handler (int sig_no)
{
  /* do nothing */
#if defined(WINDOWS)
  if (sig_no == CTRL_C_EVENT)
    {
      /* ignore */
      return TRUE;
    }

  return FALSE;
#endif /* WINDOWS */
}

static int
get_codeset_from_db_root (void)
{
#define QUERY_SIZE 1024

  DB_QUERY_RESULT *query_result;
  DB_QUERY_ERROR query_error;
  int db_status;
  char query[QUERY_SIZE];
  DB_VALUE value;

  snprintf (query, QUERY_SIZE, "SELECT [charset] FROM [db_root]");
  db_status = db_execute (query, &query_result, &query_error);
  if (db_status < 0)
    {
      return db_status;
    }

  db_status = db_query_next_tuple (query_result);
  if (db_status == DB_CURSOR_SUCCESS)
    {
      db_status = db_query_get_tuple_value (query_result, 0, &value);
      if (db_status != NO_ERROR)
    {
      db_query_end (query_result);
      return db_status;
    }
    }
  else if (db_status == DB_CURSOR_END)
    {
      return ER_FAILED;
    }
  else
    {
      return db_status;
    }

  db_query_end (query_result);
  return db_get_int (&value);
}


int
main (int argc, char *argv[])
{
  const char *db_name;
  char db_full_path[PATH_MAX];
  int coll_need_manual_migr = 0;
  INTL_CODESET codeset;
  int i, save;
  VOLUME_UNDO_INFO *p;
  LANG_COLL_COMPAT *db_collations = NULL;
  int db_coll_cnt;
  bool db_started = false;

  if (argc != 2)
    {
      printf ("Usage: %s <database name>\n", argv[0]);
      return EXIT_FAILURE;
    }

#if defined(WINDOWS)
  SetConsoleCtrlHandler ((PHANDLER_ROUTINE) intr_handler, TRUE);
#else
  os_set_signal_handler (SIGINT, intr_handler);
#endif

  db_name = argv[1];

  printf ("CUBRID Migration: 9.1 to 9.2\n\n");

  if (rel_disk_compatible () != V9_2_LEVEL)
    {
      /* invalid cubrid library */
      printf ("CUBRID library version is invalid.\n" "Please upgrade to CUBRID 9.2 and retry migrate.\n");
      return EXIT_FAILURE;
    }

  if (check_database_name (db_name))
    {
      return EXIT_FAILURE;
    }

  if (get_db_path (db_name, db_full_path) != NO_ERROR)
    {
      return EXIT_FAILURE;
    }

  if (check_and_fix_compat_level (db_name, db_full_path) != NO_ERROR)
    {
      return EXIT_FAILURE;
    }

  if (utility_initialize () != NO_ERROR)
    {
      goto error_undo_compat;
    }

  if (er_init ("./migrate_91_to_92.err", ER_NEVER_EXIT) != NO_ERROR)
    {
      printf ("Failed to initialize error manager.\n");
      goto error_undo_compat;
    }

  if (fix_all_volume_header (db_full_path) != NO_ERROR)
    {
      goto error_undo_vol_header;
    }

  sysprm_set_force (PRM_ID_PB_NBUFFERS, "1024");

  AU_DISABLE_PASSWORDS ();

  /* boot_set_skip_check_ct_classes (true); */
  db_set_client_type (DB_CLIENT_TYPE_ADMIN_UTILITY);
  db_login ("DBA", NULL);

  if (db_restart (argv[0], TRUE, db_name) != NO_ERROR)
    {
      printf ("\n%s\n", db_error_string (3));
      goto error_undo_vol_header;
    }
  db_started = true;

  if (catcls_get_db_collation (NULL, &db_collations, &db_coll_cnt) != NO_ERROR)
    {
      if (db_collations != NULL)
    {
      db_private_free (NULL, db_collations);
      db_collations = NULL;
    }
      goto error_undo_vol_header;
    }

  for (i = 0; i < db_coll_cnt; i++)
    {
      const LANG_COLL_COMPAT *ref_c;
      LANG_COLLATION *lc;

      ref_c = &(db_collations[i]);

      assert (ref_c->coll_id >= 0 && ref_c->coll_id < LANG_MAX_COLLATIONS);
      /* collation id is valid, check if same collation */
      lc = lang_get_collation (ref_c->coll_id);

      if (lc->coll.coll_id != ref_c->coll_id)
    {
      printf ("Collation '%s' with id %d from database %s " "was not loaded by migration process\n",
          ref_c->coll_name, ref_c->coll_id, db_name);
      goto error_undo_vol_header;
    }

      if (strcmp (lc->coll.coll_name, ref_c->coll_name))
    {
      printf ("Names of collation with id %d do not match : " "the collation loaded by migration process is '%s'; "
          "the collation in database %s, is '%s'\n", ref_c->coll_id, lc->coll.coll_name, db_name,
          ref_c->coll_name);
      goto error_undo_vol_header;
    }

      if (lc->codeset != ref_c->codeset)
    {
      printf ("Codesets of collation '%s' with id %d do not match : "
          "the collation loaded by migration process has codeset %d; "
          "the collation in database %s has codeset %d\n", ref_c->coll_name, ref_c->coll_id, lc->codeset,
          db_name, ref_c->codeset);
      goto error_undo_vol_header;
    }

      /* The 'COLL_CONTRACTION' struct exported in locales lib was reorganized to optimize memory; as a side-effect,
       * collations having contractions have altered checksum, but their properties are the same */

      /* collations with contractions are ignored, there is no acceptable solution to check them; we just assume that
       * user does not change their properties */
      if (lc->coll.count_contr == 0 && strcasecmp (lc->coll.checksum, ref_c->checksum))
    {
      printf ("Collation '%s' with id %d has changed : " "the collation loaded by migration process has checksum "
          "'%s'; the collation from database %s has checksum '%s'\n", ref_c->coll_name, ref_c->coll_id,
          lc->coll.checksum, db_name, ref_c->checksum);
      goto error_undo_vol_header;
    }
    }

  printf ("Updating database system collations \n\n");
  if (synccoll_force () != EXIT_SUCCESS)
    {
      db_abort_transaction ();
      goto error_undo_vol_header;
    }
  db_commit_transaction ();

  AU_DISABLE (save);

  if (file_update_used_pages_of_vol_header (NULL) == DISK_ERROR)
    {
      printf ("Could not update the statistics of the used pages of a volume: %s.\n", db_error_string (3));
      goto error_undo_vol_header;
    }

  /* read codeset from db_root */
  codeset = get_codeset_from_db_root ();
  db_shutdown ();
  db_started = false;

  if (codeset >= INTL_CODESET_ISO88591 && codeset <= INTL_CODESET_LAST)
    {
      /* write codeset to the header of an active log */
      if (fix_codeset_in_active_log (db_full_path, codeset) != NO_ERROR)
    {
      goto error_undo_vol_header;
    }
    }
  else
    {
      printf ("\nCould not get the codeset from db_root: %s", db_error_string (3));
      goto error_undo_vol_header;
    }

  printf ("\nMigration to CUBRID 9.2 has been successfully completed.\n");

  free_volume_header_undo_list ();

  return NO_ERROR;

error_undo_vol_header:
  if (db_collations != NULL)
    {
      db_private_free (NULL, db_collations);
      db_collations = NULL;
    }
  if (db_started)
    {
      db_shutdown ();
      db_started = false;
    }
  for (p = vol_undo_info, i = 0; i < vol_undo_count; i++, p++)
    {
      printf ("rollback volume header: %s\n", p->filename);
      fflush (stdout);

      if (undo_fix_volume_header (p->filename, p->page, p->page_size) != NO_ERROR)
    {
      printf ("recovering volume header fails.\n");
      break;
    }
    }

  free_volume_header_undo_list ();

error_undo_compat:
  if (undo_fix_compat_level (db_full_path) != NO_ERROR)
    {
      printf ("\nRecovering db_compatibility level fails.\n");
    }

  printf ("\nMigration failed.\n");
  return EXIT_FAILURE;
}


static int
fix_all_volume_header (const char *db_path)
{
  char vol_info_path[PATH_MAX], vol_path[PATH_MAX];
  FILE *vol_info_fp = NULL;
  int volid = NULL_VOLID;
  char scan_format[32];

  fileio_make_volume_info_name (vol_info_path, db_path);

  FOPEN_AND_CHECK (vol_info_fp, vol_info_path, "r");

  vol_undo_count = 0;
  vol_undo_list_length = UNDO_LIST_SIZE;
  vol_undo_info = (VOLUME_UNDO_INFO *) calloc (vol_undo_list_length, sizeof (VOLUME_UNDO_INFO));

  if (vol_undo_info == NULL)
    {
      fclose (vol_info_fp);
      return ER_FAILED;
    }

  sprintf (scan_format, "%%d %%%ds", (int) (sizeof (vol_path) - 1));
  while (true)
    {
      if (fscanf (vol_info_fp, scan_format, &volid, vol_path) != 2)
    {
      break;
    }

      if (volid == NULL_VOLID || volid < LOG_DBLOG_ACTIVE_VOLID)
    {
      continue;
    }

      if (volid != LOG_DBLOG_ACTIVE_VOLID)
    {
      if (fix_volume_header (vol_path) != NO_ERROR)
        {
          fclose (vol_info_fp);
          return ER_FAILED;
        }
    }
    }


  return NO_ERROR;
}

static int
fix_volume_header (const char *vol_path)
{
  FILE *fp = NULL;
  DISK_VAR_HEADER *r92_header;
  R91_DISK_VAR_HEADER *r91_header;
  char *undo_page;

  FILEIO_PAGE *r91_iopage, *r92_iopage;
  char r91_page[IO_MAX_PAGE_SIZE + MAX_ALIGNMENT];
  char r92_page[IO_MAX_PAGE_SIZE + MAX_ALIGNMENT];
  char *r91_aligned_buf, *r92_aligned_buf;
  int var_field_length;

  r91_aligned_buf = PTR_ALIGN (r91_page, MAX_ALIGNMENT);
  r92_aligned_buf = PTR_ALIGN (r92_page, MAX_ALIGNMENT);

  FOPEN_AND_CHECK (fp, vol_path, "rb+");
  FREAD_AND_CHECK (r91_aligned_buf, sizeof (char), IO_PAGESIZE, fp, vol_path);

  undo_page = make_volume_header_undo_page (vol_path, IO_PAGESIZE);
  if (undo_page == NULL)
    {
      FCLOSE_AND_CHECK (fp, vol_path);
      return ER_FAILED;
    }

  memcpy (undo_page, r91_aligned_buf, IO_PAGESIZE);

  r91_iopage = (FILEIO_PAGE *) r91_aligned_buf;
  r91_header = (R91_DISK_VAR_HEADER *) r91_iopage->page;

  r92_iopage = (FILEIO_PAGE *) r92_aligned_buf;
  r92_header = (DISK_VAR_HEADER *) r92_iopage->page;

  LSA_COPY (&r92_iopage->prv.lsa, &r91_iopage->prv.lsa);

  memcpy (r92_header->magic, r91_header->magic, CUBRID_MAGIC_MAX_LENGTH);
  r92_header->iopagesize = r91_header->iopagesize;
  r92_header->volid = r91_header->volid;
  r92_header->purpose = r91_header->purpose;
  r92_header->sect_npgs = r91_header->sect_npgs;
  r92_header->total_sects = r91_header->total_sects;
  r92_header->free_sects = r91_header->free_sects;
  r92_header->hint_allocsect = r91_header->hint_allocsect;
  r92_header->total_pages = r91_header->total_pages;
  r92_header->free_pages = r91_header->free_pages;
  r92_header->sect_alloctb_npages = r91_header->sect_alloctb_npages;
  r92_header->page_alloctb_npages = r91_header->page_alloctb_npages;
  r92_header->sect_alloctb_page1 = r91_header->sect_alloctb_page1;
  r92_header->page_alloctb_page1 = r91_header->page_alloctb_page1;
  r92_header->sys_lastpage = r91_header->sys_lastpage;
  r92_header->db_creation = r91_header->db_creation;
  r92_header->max_npages = r91_header->total_pages;
  LSA_COPY (&r92_header->chkpt_lsa, &r91_header->chkpt_lsa);
  HFID_COPY (&r92_header->boot_hfid, &r91_header->boot_hfid);
  r92_header->offset_to_vol_fullname = r91_header->offset_to_vol_fullname;
  r92_header->offset_to_next_vol_fullname = r91_header->offset_to_next_vol_fullname;
  r92_header->offset_to_vol_remarks = r91_header->offset_to_vol_remarks;

  var_field_length =
    r91_header->offset_to_vol_remarks +
    (int) strlen ((char *) (r91_header->var_fields + r91_header->offset_to_vol_remarks));

  memcpy (r92_header->var_fields, r91_header->var_fields, var_field_length);

  /* These new fields will be fixed later */
  r92_header->used_data_npages = r92_header->used_index_npages = 0;

  rewind (fp);
  FWRITE_AND_CHECK (r92_aligned_buf, sizeof (char), r92_header->iopagesize, fp, vol_path);
  FFLUSH_AND_CHECK (fp, vol_path);
  FCLOSE_AND_CHECK (fp, vol_path);

  printf ("%s... done\n", vol_path);
  fflush (stdout);

  return NO_ERROR;
}

static char *
make_volume_header_undo_page (const char *vol_path, int size)
{
  VOLUME_UNDO_INFO *new_undo_info;
  char *undo_page, *undo_file_name;

  if (vol_undo_count == vol_undo_list_length)
    {
      new_undo_info = (VOLUME_UNDO_INFO *) calloc (2 * vol_undo_list_length, sizeof (VOLUME_UNDO_INFO));
      if (new_undo_info == NULL)
    {
      return NULL;
    }

      memcpy (new_undo_info, vol_undo_info, vol_undo_list_length * sizeof (VOLUME_UNDO_INFO));
      free (vol_undo_info);
      vol_undo_info = new_undo_info;

      vol_undo_list_length *= 2;
    }

  undo_page = (char *) malloc (size * sizeof (char));
  if (undo_page == NULL)
    {
      return NULL;
    }

  undo_file_name = (char *) malloc (PATH_MAX);
  if (undo_file_name == NULL)
    {
      free (undo_page);
      return NULL;
    }


  strcpy (undo_file_name, vol_path);
  vol_undo_info[vol_undo_count].filename = undo_file_name;
  vol_undo_info[vol_undo_count].page = undo_page;
  vol_undo_info[vol_undo_count].page_size = size;
  vol_undo_count++;

  return undo_page;
}

static int
undo_fix_volume_header (const char *vol_path, char *undo_page, int size)
{
  FILE *fp = NULL;

  FOPEN_AND_CHECK (fp, vol_path, "rb+");
  FWRITE_AND_CHECK (undo_page, sizeof (char), size, fp, vol_path);
  FFLUSH_AND_CHECK (fp, vol_path);
  FCLOSE_AND_CHECK (fp, vol_path);

  return NO_ERROR;
}

static void
free_volume_header_undo_list (void)
{
  int i;
  VOLUME_UNDO_INFO *p;

  for (p = vol_undo_info, i = 0; i < vol_undo_count; i++, p++)
    {
      if (p->filename)
    {
      free (p->filename);
    }

      if (p->page)
    {
      free (p->page);
    }
    }

  if (vol_undo_info)
    {
      free (vol_undo_info);
    }
}