Skip to content

File file_manager.c

File List > cubrid > src > storage > file_manager.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.
 *
 */

/*
 * file_manager.c - file manager
 */

#ident "$Id$"

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <time.h>
#include <unordered_map>
#include <functional>

#include "file_manager.h"

#include "btree.h"
#include "porting.h"
#include "porting_inline.hpp"
#include "memory_alloc.h"
#include "storage_common.h"
#include "error_manager.h"
#include "file_io.h"
#include "page_buffer.h"
#include "disk_manager.h"
#include "log_append.hpp"
#include "log_manager.h"
#include "log_impl.h"
#include "log_lsa.hpp"
#include "lock_manager.h"
#include "system_parameter.h"
#include "boot_sr.h"
#include "memory_hash.h"
#include "environment_variable.h"
#include "xserver_interface.h"
#include "oid.h"
#include "heap_file.h"
#include "bit.h"
#include "util_func.h"
#include "vacuum.h"
#include "btree_load.h"
#include "critical_section.h"
#if defined(SERVER_MODE)
#include "connection_error.h"
#endif /* SERVER_MODE */
#include "fault_injection.h"
#include "thread_manager.hpp"   // for thread_get_thread_entry_info
#include "partition_sr.h"
// XXX: SHOULD BE THE LAST INCLUDE HEADER
#include "memory_wrapper.hpp"


/************************************************************************/
/* Define structures, globals, and macro's                              */
/************************************************************************/

/************************************************************************/
/* File manager section                                                 */
/************************************************************************/

/************************************************************************/
/* File header section                                                  */
/************************************************************************/

/* FILE_HEADER -
 * This structure keeps meta-data on files in the file header page. The rest of the file header page, based on the type
 * of files, is used by other tables.
 */
typedef struct file_header FILE_HEADER;
struct file_header
{
  INT64 time_creation;      /* Time of file creation. */

  VFID self;            /* Self VFID */
  FILE_TABLESPACE tablespace;   /* The table space definition */
  FILE_DESCRIPTORS descriptor;  /* File descriptor. Depends on file type. */

  /* Page counts. */
  int n_page_total;     /* File total page count. */
  int n_page_user;      /* User page count. */
  int n_page_ftab;      /* Page count used for file tables. */
  int n_page_free;      /* Free page count. Pages that were reserved on disk and can be allocated by user (or
                 * table) in the future. */
  int n_page_mark_delete;   /* used by numerable files to track marked deleted pages */

  /* Sector counts. */
  int n_sector_total;       /* File total sector count. */
  int n_sector_partial;     /* Partially allocated sectors count. */
  int n_sector_full;        /* Fully allocated sectors count. */
  int n_sector_empty;       /* Completely empty sectors count. Empty sectors are also considered partially
                 * allocated (so this is less or equal to n_sector_partial. */

  FILE_TYPE type;       /* File type. */

  INT32 file_flags;     /* File flags. */

  VOLID volid_last_expand;  /* Last volume used for expansion. */

  INT16 offset_to_partial_ftab; /* Offset to partial sectors table. */
  INT16 offset_to_full_ftab;    /* Offset to full sectors table. */
  INT16 offset_to_user_page_ftab;   /* Offset to user pages table. */

  VPID vpid_sticky_first;   /* VPID of first page (if it is sticky). This page should never be deallocated. */

  /* Temporary files. */
  /* Temporary files are handled differently than permanent files for simplicity. Temporary file pages are never
   * deallocated, they are deallocated when the file is destroyed or reclaimed when file is reset (but kept in cache).
   * Therefore, we can just keep a cursor that tracks the location of last allocated page. This cursor has two
   * components: the last page of partially allocated sectors and the offset to last sector in this page.
   * When the sector becomes full, the cursor is incremented. When all page becomes full, the cursor is moved to next
   * page. */
  VPID vpid_last_temp_alloc;    /* VPID of partial table page last used to allocate a page. */
  int offset_to_last_temp_alloc;    /* Sector offset in partial table last used to allocate a page. */

  /* Numerable files */
  /* Numerable files have an additional property compared to regular files. The order of user page allocation is
   * tracked and the user can get nth page according to this order. To optimize allocations, we keep the VPID of last
   * page of user page table. Newly allocated page is appended here. */
  VPID vpid_last_user_page_ftab;

  /* cache last file_numerable_find_nth page and index of its entry. used as an optimization for external sort files.
   * the extensible hash case is not interesting for this optimization (because they are usually small files and because
   * the access pattern is less predictable.
   * the usual pattern external sort files is find nth, find nth+1, find nth+2 and so on. so we can cache the page and
   * index of its first entry from last search to predict where the next file_numerable_find_nth will land.
   *
   * how it works:
   * cache last search location for file_numerable_find_nth by saving user page table page VPID and index of first entry
   * in page. next search will probably land in the same page (and it will just need to get to the right offset).
   * if a page is deallocated, the cached location is reset (this is just a safe-guard since external sort does not
   * deallocate pages currently.
   * if a page is allocated, since it is appended at the end, will not affect the cached search location. current thread
   * is actually the only one accessing the file so it can change it safely without promoting to write latch. to avoid
   * safe-guards, we won't set the page dirty (it is hot anyway and likely to remain in memory).
   */
  VPID vpid_find_nth_last;
  int first_index_find_nth_last;

  /* reserved area for future extension */
  INT32 reserved0;
  INT32 reserved1;
  INT32 reserved2;
  INT32 reserved3;
};

/* Disk size of file header. */
#define FILE_HEADER_ALIGNED_SIZE ((INT16) (DB_ALIGN (sizeof (FILE_HEADER), MAX_ALIGNMENT)))

/* File flags. */
#define FILE_FLAG_NUMERABLE     0x1 /* Is file numerable */
#define FILE_FLAG_TEMPORARY     0x2 /* Is file temporary */
#define FILE_FLAG_ENCRYPTED_AES 0x4 /* Is file encrypted using AES */
#define FILE_FLAG_ENCRYPTED_ARIA 0x8    /* Is file encrypted using ARIA */

#define FILE_FLAG_ENCRYPTED_MASK 0x0000000c /* 0x4 + 0x8 */

#define FILE_IS_NUMERABLE(fh) (((fh)->file_flags & FILE_FLAG_NUMERABLE) != 0)
#define FILE_IS_TEMPORARY(fh) (((fh)->file_flags & FILE_FLAG_TEMPORARY) != 0)
#define FILE_IS_TDE_ENCRYPTED(fh) (((fh)->file_flags & FILE_FLAG_ENCRYPTED_MASK) != 0)

#define FILE_CACHE_LAST_FIND_NTH(fh, thread_p) \
  (FILE_IS_NUMERABLE (fh) && FILE_IS_TEMPORARY (fh) && (fh)->type == FILE_TEMP \
   && thread_p->m_px_orig_thread_entry == NULL /* not parallel thread */)

/* Numerable file types. Currently, we used this property for extensible hashes and sort files. */
#define FILE_TYPE_CAN_BE_NUMERABLE(ftype) ((ftype) == FILE_EXTENDIBLE_HASH \
                       || (ftype) == FILE_EXTENDIBLE_HASH_DIRECTORY \
                       || (ftype) == FILE_TEMP)
#define FILE_TYPE_IS_ALWAYS_TEMP(ftype) ((ftype) == FILE_TEMP \
                                         || (ftype) == FILE_QUERY_AREA)
#define FILE_TYPE_IS_SOMETIMES_TEMP(ftype) ((ftype) == FILE_EXTENDIBLE_HASH \
                                            || (ftype) == FILE_EXTENDIBLE_HASH_DIRECTORY)
#define FILE_TYPE_IS_NEVER_TEMP(ftype) (!FILE_TYPE_IS_ALWAYS_TEMP (ftype) && !FILE_TYPE_IS_SOMETIMES_TEMP (ftype))

/* Convert VFID to file header page VPID. */
#define FILE_GET_HEADER_VPID(vfid, vpid) (vpid)->volid = (vfid)->volid; (vpid)->pageid = (vfid)->fileid

/* Get pointer to partial table in file header page */
#define FILE_HEADER_GET_PART_FTAB(fh, parttab) \
  assert ((fh)->offset_to_partial_ftab >= FILE_HEADER_ALIGNED_SIZE && (fh)->offset_to_partial_ftab < DB_PAGESIZE); \
  (parttab) = (FILE_EXTENSIBLE_DATA *) (((char *) fh) + (fh)->offset_to_partial_ftab)
/* Get pointer to full table in file header page */
#define FILE_HEADER_GET_FULL_FTAB(fh, fulltab) \
  assert (!FILE_IS_TEMPORARY (fh)); \
  assert ((fh)->offset_to_full_ftab>= FILE_HEADER_ALIGNED_SIZE && (fh)->offset_to_full_ftab < DB_PAGESIZE); \
  (fulltab) = (FILE_EXTENSIBLE_DATA *) (((char *) fh) + (fh)->offset_to_full_ftab)
/* Get pointer to user page table in file header page */
#define FILE_HEADER_GET_USER_PAGE_FTAB(fh, pagetab) \
  assert (FILE_IS_NUMERABLE (fh)); \
  assert ((fh)->offset_to_user_page_ftab >= FILE_HEADER_ALIGNED_SIZE && (fh)->offset_to_user_page_ftab < DB_PAGESIZE); \
  (pagetab) = (FILE_EXTENSIBLE_DATA *) (((char *) fh) + (fh)->offset_to_user_page_ftab)

/************************************************************************/
/* File extensible data section                                         */
/************************************************************************/

/* File extensible data is a generic format to keep theoretically unlimited data in multiple disk pages. Each page is
 * a component of full data. Each component has a header which keeps a link to next page and useful information about
 * this component. The header is followed by data.
 *
 * The data is considered a set of items. Items have constant size.
 *
 * File extensible data is designed to easily access and manipulate data items. It can be used for multiple purposes
 * (e.g. file tables, file tracker).
 *
 * Items in extensible data can be ordered. The user must provide the right compare function. */

/* FILE_EXTENSIBLE_DATA -
 * This structure is actually the beginning of a extensible data component. It is usually accessed as a pointer in page.
 */
typedef struct file_extensible_data FILE_EXTENSIBLE_DATA;
struct file_extensible_data
{
  VPID vpid_next;
  INT16 max_size;
  INT16 size_of_item;
  INT16 n_items;
};
/* Disk size for extensible data header */
#define FILE_EXTDATA_HEADER_ALIGNED_SIZE (DB_ALIGN (sizeof (FILE_EXTENSIBLE_DATA), MAX_ALIGNMENT))

/* FILE_EXTENSIBLE_DATA_SEARCH_CONTEXT -
 * Helper used for searching an item in extensible data. */
typedef struct file_extensible_data_search_context FILE_EXTENSIBLE_DATA_SEARCH_CONTEXT;
struct file_extensible_data_search_context
{
  const void *item_to_find; /* Pointer to item to find. */
  int (*compare_func) (const void *, const void *); /* Compare function used to find the item. */
  bool found;           /* Set to true when item is found. */
  int position;         /* Saves the position of found item in its component */
};

/* Functions for file_extdata_apply_funcs */
typedef int (*FILE_EXTDATA_FUNC) (THREAD_ENTRY * thread_p,
                  const FILE_EXTENSIBLE_DATA * extdata, bool * stop, void *args);
typedef int (*FILE_EXTDATA_ITEM_FUNC) (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args);

/************************************************************************/
/* Partially allocated sectors section                                  */
/************************************************************************/

/* Partial sector table (full name would be partially allocated sector table, but we will refer to them just by partial
 * sectors) stores reserved sectors and bitmaps for allocated pages in these sectors. Usually, when all sector pages are
 * allocated, the sector is removed from partial sector table. This rule does not apply to temporary files, which for
 * simplicity, only use this partial sector table without moving its content to full table sector.
 *
 * Each bit in bitmap represents a page, and a page is considered allocated if its bit is set or free if the bit is not
 * set.
 */


/************************************************************************/
/* Utility structures                                                   */
/************************************************************************/

/* Table space default macro's */
#define FILE_TABLESPACE_DEFAULT_RATIO_EXPAND ((float) 0.01) /* 1% of current size */
#define FILE_TABLESPACE_DEFAULT_MIN_EXPAND (DISK_SECTOR_NPAGES * DB_PAGESIZE);  /* one sector */
#define FILE_TABLESPACE_DEFAULT_MAX_EXPAND (DISK_SECTOR_NPAGES * DB_PAGESIZE * 1024);   /* 1k sectors */

#define FILE_TABLESPACE_FOR_PERM_NPAGES(tabspace, npages) \
  ((FILE_TABLESPACE *) (tabspace))->initial_size = (INT64) MAX (1, npages) * DB_PAGESIZE; \
  ((FILE_TABLESPACE *) (tabspace))->expand_ratio = FILE_TABLESPACE_DEFAULT_RATIO_EXPAND; \
  ((FILE_TABLESPACE *) (tabspace))->expand_min_size = FILE_TABLESPACE_DEFAULT_MIN_EXPAND; \
  ((FILE_TABLESPACE *) (tabspace))->expand_max_size = FILE_TABLESPACE_DEFAULT_MAX_EXPAND

#define FILE_TABLESPACE_FOR_TEMP_NPAGES(tabspace, npages) \
  ((FILE_TABLESPACE *) (tabspace))->initial_size = (INT64) MAX (1, npages) * DB_PAGESIZE; \
  ((FILE_TABLESPACE *) (tabspace))->expand_ratio = 0; \
  ((FILE_TABLESPACE *) (tabspace))->expand_min_size = 0; \
  ((FILE_TABLESPACE *) (tabspace))->expand_max_size = 0

/* FILE_VSID_COLLECTOR -
 * Collect sector ID's. File destroy uses it. */
typedef struct file_vsid_collector FILE_VSID_COLLECTOR;
struct file_vsid_collector
{
  VSID *vsids;
  int n_vsids;
};

/* logging */
static bool file_Logging = false;

#define file_log(func, msg, ...) \
  if (file_Logging) \
    _er_log_debug (ARG_FILE_LINE, "FILE " func " " LOG_THREAD_TRAN_MSG ": " msg "\n", \
                   LOG_THREAD_TRAN_ARGS(thread_get_thread_entry_info ()), __VA_ARGS__)

#define FILE_PERM_TEMP_STRING(is_temp) ((is_temp) ? "temporary" : "permanent")
#define FILE_NUMERABLE_REGULAR_STRING(is_numerable) ((is_numerable) ? "numerable" : "regular")

#define FILE_TABLESPACE_MSG \
  "\ttablespace = { init_size = %lld, expand_ratio = %f, expand_min_size = %d, expand_max_size = %d } \n"
#define FILE_TABLESPACE_AS_ARGS(tabspace) \
  (long long int) (tabspace)->initial_size, (tabspace)->expand_ratio, (tabspace)->expand_min_size, \
  (tabspace)->expand_max_size

#define FILE_HEAD_ALLOC_MSG \
 "\tfile header: \n" \
 "\t\tvfid = %d|%d \n" \
 "\t\t%s \n" \
 "\t\t%s \n" \
 "\t\ttde_algorithm: %s \n" \
 "\t\tpage: total = %d, user = %d, table = %d, free = %d \n" \
 "\t\tsector: total = %d, partial = %d, full = %d, empty = %d \n"
#define FILE_HEAD_ALLOC_AS_ARGS(fhead) \
  VFID_AS_ARGS (&(fhead)->self), \
  FILE_PERM_TEMP_STRING (FILE_IS_TEMPORARY (fhead)), \
  FILE_NUMERABLE_REGULAR_STRING (FILE_IS_NUMERABLE (fhead)), \
  tde_get_algorithm_name (file_get_tde_algorithm_internal (fhead)), \
  (fhead)->n_page_total, (fhead)->n_page_user, (fhead)->n_page_ftab, (fhead)->n_page_free, \
  (fhead)->n_sector_total, (fhead)->n_sector_partial, (fhead)->n_sector_full, (fhead)->n_sector_empty

#define FILE_HEAD_FULL_MSG \
  FILE_HEAD_ALLOC_MSG \
  "\t\ttime_creation = %lld, type = %s \n" \
  "\t" FILE_TABLESPACE_MSG \
  "\t\ttable offsets: partial = %d, full = %d, user page = %d \n" \
  "\t\tvpid_sticky_first = %d|%d \n" \
  "\t\tvpid_last_temp_alloc = %d|%d, offset_to_last_temp_alloc=%d \n" \
  "\t\tvpid_last_user_page_ftab = %d|%d \n" \
  "\t\tvpid_find_nth_last = %d|%d, first_index_find_nth_last = %d \n"
#define FILE_HEAD_FULL_AS_ARGS(fhead) \
  FILE_HEAD_ALLOC_AS_ARGS (fhead), \
  (long long int) fhead->time_creation, file_type_to_string ((fhead)->type), \
  FILE_TABLESPACE_AS_ARGS (&(fhead)->tablespace), \
  (fhead)->offset_to_partial_ftab, (fhead)->offset_to_full_ftab, (fhead)->offset_to_user_page_ftab, \
  VPID_AS_ARGS (&(fhead)->vpid_sticky_first), \
  VPID_AS_ARGS (&(fhead)->vpid_last_temp_alloc), (fhead)->offset_to_last_temp_alloc, \
  VPID_AS_ARGS (&(fhead)->vpid_last_user_page_ftab), \
  VPID_AS_ARGS (&(fhead)->vpid_find_nth_last), (fhead)->first_index_find_nth_last

#define FILE_EXTDATA_MSG(name) \
  "\t" name ": { vpid_next = %d|%d, max_size = %d, item_size = %d, n_items = %d } \n"
#define FILE_EXTDATA_AS_ARGS(extdata) \
  VPID_AS_ARGS (&(extdata)->vpid_next), (extdata)->max_size, (extdata)->size_of_item, (extdata)->n_items

#define FILE_PARTSECT_MSG(name) \
  "\t" name ": { vsid = %d|%d, page bitmap = " BIT64_HEXA_PRINT_FORMAT " } \n"
#define FILE_PARTSECT_AS_ARGS(ps) VSID_AS_ARGS (&(ps)->vsid), (long long unsigned int) (ps)->page_bitmap

#define FILE_ALLOC_TYPE_STRING(alloc_type) \
  ((alloc_type) == FILE_ALLOC_USER_PAGE ? "alloc user page" : "alloc table page")

#define FILE_TEMPCACHE_MSG \
  "\ttempcache: \n" \
  "\t\tfile cache: max = %d, numerable = %d, regular = %d, total = %d \n" \
  "\t\tfree entries: max = %d, count = %d \n"
#define FILE_TEMPCACHE_AS_ARGS \
  file_Tempcache.ncached_max, file_Tempcache.ncached_numerable, file_Tempcache.ncached_not_numerable, \
  file_Tempcache.ncached_numerable + file_Tempcache.ncached_not_numerable, \
  file_Tempcache.nfree_entries_max, file_Tempcache.nfree_entries

#define FILE_TEMPCACHE_ENTRY_MSG "%p, VFID %d|%d, %s"
#define FILE_TEMPCACHE_ENTRY_AS_ARGS(ent) ent, VFID_AS_ARGS (&(ent)->vfid), file_type_to_string ((ent)->ftype)

#define FILE_TRACK_ITEM_MSG "VFID %d|%d, %s"
#define FILE_TRACK_ITEM_AS_ARGS(item) (item)->volid, (item)->fileid, file_type_to_string ((FILE_TYPE) (item)->type)

/************************************************************************/
/* File manipulation section                                            */
/************************************************************************/

/* FILE_ALLOC_TYPE -
 * The internal workings of file manager may need disk pages to hold the header and data tables. these are considered
 * file table pages and must also be tracked besides user pages (pages allocated using file_alloc) */
typedef enum
{
  FILE_ALLOC_USER_PAGE,
  FILE_ALLOC_TABLE_PAGE,
  FILE_ALLOC_TABLE_PAGE_FULL_SECTOR /* used to allocate a table page necessary for full sectors */
} FILE_ALLOC_TYPE;

#define FILE_RV_DEALLOC_COMPENSATE true
#define FILE_RV_DEALLOC_RUN_POSTPONE false

/* FILE_MAP_CONTEXT - context variables for file_map_pages function. */
typedef struct file_map_context FILE_MAP_CONTEXT;
struct file_map_context
{
  bool is_partial;
  PGBUF_LATCH_MODE latch_mode;
  PGBUF_LATCH_CONDITION latch_cond;
  FILE_FTAB_COLLECTOR ftab_collector;

  bool stop;

  FILE_MAP_PAGE_FUNC func;
  void *args;
};

/* FILE_SET_TDE_ALGORITHM_ARGS - args varaible for file_apply_tde_algorithm() */
typedef struct file_set_tde_algorithm_args FILE_SET_TDE_ALGORITHM_ARGS;
struct file_set_tde_algorithm_args
{
  bool skip_logging;
  TDE_ALGORITHM tde_algo;
};

/************************************************************************/
/* Numerable files section                                              */
/************************************************************************/

#define FILE_USER_PAGE_MARK_DELETE_FLAG ((PAGEID) 0x80000000)
#define FILE_USER_PAGE_IS_MARKED_DELETED(vpid) ((((VPID *) vpid)->pageid & FILE_USER_PAGE_MARK_DELETE_FLAG) != 0)
#define FILE_USER_PAGE_MARK_DELETED(vpid) ((VPID *) vpid)->pageid |= FILE_USER_PAGE_MARK_DELETE_FLAG
#define FILE_USER_PAGE_CLEAR_MARK_DELETED(vpid) ((VPID *) vpid)->pageid &= ~FILE_USER_PAGE_MARK_DELETE_FLAG

/* FILE_FIND_NTH_CONTEXT -
 * structure used for finding nth page. */
typedef struct file_find_nth_context FILE_FIND_NTH_CONTEXT;
struct file_find_nth_context
{
  VPID *vpid_nth;
  int nth;
  int first_index;
};

/************************************************************************/
/* Temporary files section                                              */
/************************************************************************/

/************************************************************************/
/* Temporary cache section                                              */
/************************************************************************/
typedef struct file_tempcache_entry FILE_TEMPCACHE_ENTRY;
struct file_tempcache_entry
{
  VFID vfid;
  FILE_TYPE ftype;

  FILE_TEMPCACHE_ENTRY *next;
};

typedef struct file_tempcache_tran_entry FILE_TEMPCACHE_TRAN_ENTRY;
struct file_tempcache_tran_entry
{
  pthread_mutex_t mutex;
  FILE_TEMPCACHE_ENTRY *head;
#if !defined (NDEBUG)
  int owner_mutex;
#endif
};

typedef struct file_tempcache FILE_TEMPCACHE;
struct file_tempcache
{
  FILE_TEMPCACHE_ENTRY *free_entries;   /* preallocated entries free to be use */
  int nfree_entries_max;
  int nfree_entries;

  FILE_TEMPCACHE_ENTRY *cached_not_numerable;   /* cached temporary files */
  FILE_TEMPCACHE_ENTRY *cached_numerable;   /* cached temporary numerable files */
  int ncached_max;
  int ncached_not_numerable;
  int ncached_numerable;

  pthread_mutex_t mutex;
#if !defined (NDEBUG)
  int owner_mutex;
#endif              /* !NDEBUG */

  FILE_TEMPCACHE_TRAN_ENTRY *tran_files;    /* transaction temporary files */

  /* space info */
  SPACEDB_FILES spacedb_temp;
};

static FILE_TEMPCACHE file_Tempcache;

/************************************************************************/
/* File tracker section                                                 */
/************************************************************************/

static VFID file_Tracker_vfid = VFID_INITIALIZER;
static VPID file_Tracker_vpid = VPID_INITIALIZER;

typedef struct file_track_heap_metadata FILE_TRACK_HEAP_METADATA;
struct file_track_heap_metadata
{
  bool is_marked_deleted;
  bool dummy[7];        /* dummy fields to 8 bytes */
};

typedef union file_track_metadata FILE_TRACK_METADATA;
union file_track_metadata
{
  FILE_TRACK_HEAP_METADATA heap;

  INT64 metadata_size_tracker;
};

typedef struct file_track_item FILE_TRACK_ITEM;
struct file_track_item
{
  INT32 fileid;         /* 4 bytes */
  INT16 volid;          /* 2 bytes */
  INT16 type;           /* 2 bytes */
  FILE_TRACK_METADATA metadata; /* 8 bytes */

  /* total 16 bytes */
};

typedef struct file_tracker_dump_heap_context FILE_TRACKER_DUMP_HEAP_CONTEXT;
struct file_tracker_dump_heap_context
{
  FILE *fp;
  bool dump_records;
};

typedef struct file_track_mark_heap_deleted_context FILE_TRACK_MARK_HEAP_DELETED_CONTEXT;
struct file_track_mark_heap_deleted_context
{
  LOG_LSA ref_lsa;
  bool is_undo;
};

typedef struct file_tracker_reuse_heap_context FILE_TRACKER_REUSE_HEAP_CONTEXT;
struct file_tracker_reuse_heap_context
{
  OID class_oid;
  HFID *hfid_out;
};

typedef int (*FILE_TRACK_ITEM_FUNC) (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
                     int index_item, bool * stop, void *args);

/* Since indexes are built with SIX_LOCK, which allows check access an immature index file.
 * To skip checking an immature index, check it with IX_LOCK which is incompatible with SIX_LOCK.
 */
#define FILE_GET_TRACKER_LOCK_MODE(file_type) (((file_type) == FILE_BTREE) ? IX_LOCK : SCH_S_LOCK)

/************************************************************************/
/* End of structures, globals and macro's                               */
/************************************************************************/

/************************************************************************/
/* Declare static functions.                                            */
/************************************************************************/

/************************************************************************/
/* File manager section                                                 */
/************************************************************************/

/************************************************************************/
/* File header section.                                                 */
/************************************************************************/

STATIC_INLINE void file_header_init (FILE_HEADER * fhead) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_header_sanity_check (THREAD_ENTRY * thread_p, FILE_HEADER * fhead)
  __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_header_alloc (FILE_HEADER * fhead, FILE_ALLOC_TYPE alloc_type, bool was_empty, bool is_full)
  __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_header_dealloc (FILE_HEADER * fhead, FILE_ALLOC_TYPE alloc_type, bool is_empty, bool was_full)
  __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_log_fhead_alloc (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, FILE_ALLOC_TYPE alloc_type,
                     bool was_empty, bool is_full) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_log_fhead_dealloc (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, FILE_ALLOC_TYPE alloc_type,
                       bool is_empty, bool was_full) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_header_update_mark_deleted (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, int delta)
  __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE int file_header_copy (THREAD_ENTRY * thread_p, const VFID * vfid, FILE_HEADER * fhead_copy)
  __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_header_dump (THREAD_ENTRY * thread_p, const FILE_HEADER * fhead, FILE * fp)
  __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_header_dump_descriptor (THREAD_ENTRY * thread_p, const FILE_HEADER * fhead, FILE * fp)
  __attribute__ ((ALWAYS_INLINE));

/************************************************************************/
/* File extensible data section                                         */
/************************************************************************/

STATIC_INLINE void file_extdata_init (INT16 item_size, INT16 max_size,
                      FILE_EXTENSIBLE_DATA * extdata) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE int file_extdata_max_size (const FILE_EXTENSIBLE_DATA * extdata) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE int file_extdata_size (const FILE_EXTENSIBLE_DATA * extdata) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void *file_extdata_start (const FILE_EXTENSIBLE_DATA * extdata) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void *file_extdata_end (const FILE_EXTENSIBLE_DATA * extdata) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE bool file_extdata_is_full (const FILE_EXTENSIBLE_DATA * extdata) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE bool file_extdata_is_empty (const FILE_EXTENSIBLE_DATA * extdata) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE INT16 file_extdata_item_count (const FILE_EXTENSIBLE_DATA * extdata) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE INT16 file_extdata_remaining_capacity (const
                             FILE_EXTENSIBLE_DATA * extdata) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_extdata_append (FILE_EXTENSIBLE_DATA * extdata,
                    const void *append_data) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_extdata_append_array (FILE_EXTENSIBLE_DATA * extdata,
                          const void *append_data, INT16 count) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void *file_extdata_at (const FILE_EXTENSIBLE_DATA * extdata, int index) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE bool file_extdata_can_merge (const FILE_EXTENSIBLE_DATA * extdata_src,
                       const FILE_EXTENSIBLE_DATA * extdata_dest) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE bool file_extdata_merge_pages (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata_src,
                         const PAGE_PTR page_src, FILE_EXTENSIBLE_DATA * extdata_dest,
                         PAGE_PTR page_dest, int (*compare_func) (const void *, const void *),
                         bool ordered) __attribute__ ((ALWAYS_INLINE));
static int file_extdata_find_and_remove_item (THREAD_ENTRY * thread_p, FILE_EXTENSIBLE_DATA * extdata_first,
                          PAGE_PTR page_first, const void *item,
                          int (*compare_func) (const void *, const void *), bool ordered,
                          void *item_pop, VPID * vpid_merged);
STATIC_INLINE void file_extdata_merge_unordered (const FILE_EXTENSIBLE_DATA * extdata_src,
                         FILE_EXTENSIBLE_DATA * extdata_dest) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_extdata_merge_ordered (const FILE_EXTENSIBLE_DATA * extdata_src,
                           FILE_EXTENSIBLE_DATA * extdata_dest,
                           int (*compare_func) (const void *, const void *))
  __attribute__ ((ALWAYS_INLINE));
static void file_extdata_find_ordered (const FILE_EXTENSIBLE_DATA * extdata, const void *item_to_find,
                       int (*compare_func) (const void *, const void *), bool * found, int *position);
STATIC_INLINE void file_extdata_insert_at (FILE_EXTENSIBLE_DATA * extdata,
                       int position, int count, const void *data) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_extdata_remove_at (FILE_EXTENSIBLE_DATA * extdata,
                       int position, int count) __attribute__ ((ALWAYS_INLINE));
static int file_extdata_apply_funcs (THREAD_ENTRY * thread_p, FILE_EXTENSIBLE_DATA * extdata_in,
                     FILE_EXTDATA_FUNC f_extdata, void *f_extdata_args, FILE_EXTDATA_ITEM_FUNC f_item,
                     void *f_item_args, bool for_write, FILE_EXTENSIBLE_DATA ** extdata_out,
                     PAGE_PTR * page_out);
static int file_extdata_item_func_for_search (THREAD_ENTRY * thread_p,
                          const void *item, int index, bool * stop, void *args);
static int file_extdata_func_for_search_ordered (THREAD_ENTRY * thread_p,
                         const FILE_EXTENSIBLE_DATA * extdata, bool * stop, void *args);
static int file_extdata_search_item (THREAD_ENTRY * thread_p, FILE_EXTENSIBLE_DATA ** extdata,
                     const void *item_to_find,
                     int (*compare_func) (const void *, const void *),
                     bool is_ordered, bool for_write, bool * found, int *position,
                     PAGE_PTR * page_extdata);
static int file_extdata_find_not_full (THREAD_ENTRY * thread_p,
                       FILE_EXTENSIBLE_DATA ** extdata, PAGE_PTR * page_out, bool * found);
STATIC_INLINE void file_log_extdata_add (THREAD_ENTRY * thread_p,
                     const FILE_EXTENSIBLE_DATA * extdata, PAGE_PTR page, int position,
                     int count, const void *data) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_log_extdata_remove (THREAD_ENTRY * thread_p,
                        const FILE_EXTENSIBLE_DATA * extdata, PAGE_PTR page,
                        int position, int count) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_log_extdata_set_next (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata,
                          PAGE_PTR page, const VPID * vpid_next) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_extdata_update_item (THREAD_ENTRY * thread_p, PAGE_PTR page_extdata, const void *item_newval,
                         int index_item, FILE_EXTENSIBLE_DATA * extdata)
  __attribute__ ((ALWAYS_INLINE));
static int file_extdata_all_item_count (THREAD_ENTRY * thread_p, FILE_EXTENSIBLE_DATA * extdata, int *count);
static int file_extdata_add_item_count (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata, bool * stop,
                    void *args);

/************************************************************************/
/* Partially allocated sectors section                                  */
/************************************************************************/

STATIC_INLINE bool file_partsect_is_full (FILE_PARTIAL_SECTOR * partsect) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE bool file_partsect_is_empty (FILE_PARTIAL_SECTOR * partsect) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE bool file_partsect_is_bit_set (FILE_PARTIAL_SECTOR * partsect,
                         int offset) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_partsect_set_bit (FILE_PARTIAL_SECTOR * partsect, int offset) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_partsect_clear_bit (FILE_PARTIAL_SECTOR * partsect, int offset) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE int file_partsect_pageid_to_offset (FILE_PARTIAL_SECTOR * partsect,
                          PAGEID pageid) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE bool file_partsect_alloc (FILE_PARTIAL_SECTOR * partsect,
                    VPID * vpid_out, int *offset_out) __attribute__ ((ALWAYS_INLINE));

static int file_rv_partsect_update (THREAD_ENTRY * thread_p, LOG_RCV * rcv, bool set);

/************************************************************************/
/* Utility functions.                                                   */
/************************************************************************/

static int file_compare_vpids (const void *first, const void *second);
static int file_compare_vfids (const void *first, const void *second);
static int file_compare_track_items (const void *first, const void *second);

static void file_print_name_of_class (THREAD_ENTRY * thread_p, FILE * fp, const OID * class_oid_p);

/************************************************************************/
/* File manipulation section                                            */
/************************************************************************/

static int file_table_collect_vsid (THREAD_ENTRY * thread_p, const void *item,
                    int index_unused, bool * stop, void *args);
STATIC_INLINE int file_table_collect_all_vsids (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead,
                        FILE_VSID_COLLECTOR * collector_out) __attribute__ ((ALWAYS_INLINE));
static int file_perm_expand (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead);
static int file_table_move_partial_sectors_to_header (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead,
                              FILE_ALLOC_TYPE alloc_type, VPID * vpid_alloc_out);
static int file_table_append_full_sector_page (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, const VPID * vpid_new);
static int file_table_add_full_sector (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, const VSID * vsid);
STATIC_INLINE int file_table_collect_ftab_pages (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, bool collect_numerable,
                         FILE_FTAB_COLLECTOR * collector_out) __attribute__ ((ALWAYS_INLINE));
static int file_extdata_collect_ftab_pages (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata, bool * stop,
                        void *args);
STATIC_INLINE bool file_table_collector_has_page (FILE_FTAB_COLLECTOR * collector, VPID * vpid)
  __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE int file_init_page_type_internal (THREAD_ENTRY * thread_p, PAGE_PTR page, PAGE_TYPE ptype, bool is_temp)
  __attribute__ ((ALWAYS_INLINE));
static int file_perm_alloc (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, FILE_ALLOC_TYPE alloc_type,
                VPID * vpid_alloc_out);
static int file_perm_dealloc (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, const VPID * vpid_dealloc,
                  FILE_ALLOC_TYPE alloc_type);
static int file_rv_dealloc_internal (THREAD_ENTRY * thread_p, LOG_RCV * rcv, bool compensate_or_run_postpone);

STATIC_INLINE int file_create_temp_internal (THREAD_ENTRY * thread_p, int npages, FILE_TYPE ftype, bool is_numerable,
                         VFID * vfid_out) __attribute__ ((ALWAYS_INLINE));
static int file_sector_map_pages (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args);
static DISK_ISVALID file_table_check (THREAD_ENTRY * thread_p, const VFID * vfid, DISK_VOLMAP_CLONE * disk_map_clone);

STATIC_INLINE int file_table_dump (THREAD_ENTRY * thread_p, const FILE_HEADER * fhead, FILE * fp)
  __attribute__ ((ALWAYS_INLINE));
static int file_partial_table_extdata_dump (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata, bool * stop,
                        void *args);
static int file_partial_table_item_dump (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args);
static int file_full_table_extdata_dump (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata, bool * stop,
                     void *args);
static int file_full_table_item_dump (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args);
static int file_user_page_table_extdata_dump (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata,
                          bool * stop, void *args);
static int file_user_page_table_item_dump (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop,
                       void *args);
static int file_sector_map_dealloc (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args);
static int file_sector_map_dealloc_temp (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args);
static int file_set_tde_algorithm (THREAD_ENTRY * thread_p, const VFID * vfid, TDE_ALGORITHM tde_algo);
static TDE_ALGORITHM file_get_tde_algorithm_internal (const FILE_HEADER * fhead);
static void file_set_tde_algorithm_internal (FILE_HEADER * fhead, TDE_ALGORITHM tde_algo);

/************************************************************************/
/* Numerable files section.                                             */
/************************************************************************/

static int file_numerable_add_page (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, const VPID * vpid);
static int file_extdata_find_nth_vpid (THREAD_ENTRY * thread_p,
                       const FILE_EXTENSIBLE_DATA * extdata, bool * stop, void *args);
static int file_extdata_find_nth_vpid_and_skip_marked (THREAD_ENTRY * thread_p, const void *data, int index,
                               bool * stop, void *args);
static int file_table_check_page_is_in_sectors (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop,
                        void *args);

/************************************************************************/
/* Temporary files section                                              */
/************************************************************************/

static int file_temp_alloc (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, FILE_ALLOC_TYPE alloc_type,
                VPID * vpid_alloc_out);
STATIC_INLINE int file_temp_set_type (THREAD_ENTRY * thread_p, VFID * vfid,
                      FILE_TYPE ftype) __attribute__ ((ALWAYS_INLINE));
static int file_temp_reset_user_pages (THREAD_ENTRY * thread_p, const VFID * vfid);
STATIC_INLINE int file_temp_retire_internal (THREAD_ENTRY * thread_p, const VFID * vfid, bool was_preserved)
  __attribute__ ((ALWAYS_INLINE));

/************************************************************************/
/* Temporary cache section                                              */
/************************************************************************/

static int file_tempcache_init (void);
static void file_tempcache_final (void);
STATIC_INLINE void file_tempcache_lock (void) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_tempcache_unlock (void) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_tempcache_check_lock (void) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_tempcache_free_entry_list (FILE_TEMPCACHE_ENTRY ** list) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE int file_tempcache_alloc_entry (FILE_TEMPCACHE_ENTRY ** entry) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_tempcache_retire_entry (FILE_TEMPCACHE_ENTRY * entry) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE int file_tempcache_get (THREAD_ENTRY * thread_p, FILE_TYPE ftype, bool numerable,
                      FILE_TEMPCACHE_ENTRY ** entry) __attribute__ ((ALWAYS_INLINE));
static bool file_tempcache_check_duplicate (THREAD_ENTRY * thread_p, FILE_TEMPCACHE_ENTRY * entry, bool is_numerable);
STATIC_INLINE bool file_tempcache_put (THREAD_ENTRY * thread_p,
                       FILE_TEMPCACHE_ENTRY * entry) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE int file_get_tempcache_entry_index (THREAD_ENTRY * thread_p) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_tempcache_cache_or_drop_entries (THREAD_ENTRY * thread_p,
                             FILE_TEMPCACHE_ENTRY ** entries)
  __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE FILE_TEMPCACHE_ENTRY *file_tempcache_pop_tran_file (THREAD_ENTRY * thread_p, const VFID * vfid)
  __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_tempcache_push_tran_file (THREAD_ENTRY * thread_p, FILE_TEMPCACHE_ENTRY * entry)
  __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_tempcache_dump (FILE * fp) __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_tempcache_lock_tran_entry (FILE_TEMPCACHE_TRAN_ENTRY * tran_entry)
  __attribute__ ((ALWAYS_INLINE));
STATIC_INLINE void file_tempcache_unlock_tran_entry (FILE_TEMPCACHE_TRAN_ENTRY * tran_entry)
  __attribute__ ((ALWAYS_INLINE));

/************************************************************************/
/* File tracker section                                                 */
/************************************************************************/

static int file_tracker_init_page (THREAD_ENTRY * thread_p, PAGE_PTR page, void *args);
static int file_tracker_register (THREAD_ENTRY * thread_p, const VFID * vfid, FILE_TYPE ftype,
                  FILE_TRACK_METADATA * metadata);
static int file_tracker_register_internal (THREAD_ENTRY * thread_p, PAGE_PTR page_track_head,
                       const FILE_TRACK_ITEM * item);
static int file_tracker_unregister (THREAD_ENTRY * thread_p, const VFID * vfid);
static int file_tracker_apply_to_file (THREAD_ENTRY * thread_p, const VFID * vfid, PGBUF_LATCH_MODE mode,
                       FILE_TRACK_ITEM_FUNC func, void *args);
static int file_tracker_map (THREAD_ENTRY * thread_p, PGBUF_LATCH_MODE latch_mode, FILE_TRACK_ITEM_FUNC func,
                 void *args);
static int file_tracker_item_reuse_heap (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
                     int index_item, bool * stop, void *args);
static int file_tracker_item_mark_heap_deleted (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item,
                        FILE_EXTENSIBLE_DATA * extdata, int index_item, bool * stop,
                        void *ignore_args);
STATIC_INLINE int file_tracker_get_and_protect (THREAD_ENTRY * thread_p, FILE_TYPE desired_type, FILE_TRACK_ITEM * item,
                        OID * class_oid, bool * stop) __attribute__ ((ALWAYS_INLINE));
static int file_tracker_item_dump (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
                   int index_item, bool * stop, void *args);
static int file_tracker_item_dump_capacity (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item,
                        FILE_EXTENSIBLE_DATA * extdata, int index_item, bool * stop, void *args);
static int file_tracker_item_collect_invalid_file (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item,
                           FILE_EXTENSIBLE_DATA * extdata, int index_item, bool * stop,
                           void *args);
#if !defined (NDEBUG)
static int file_tracker_item_delete_target_file (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item,
                         FILE_EXTENSIBLE_DATA * extdata, int index_item, bool * stop,
                         void *args);
#endif
static int file_tracker_item_dump_heap (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
                    int index_item, bool * stop, void *args);
static int file_tracker_item_dump_heap_capacity (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item,
                         FILE_EXTENSIBLE_DATA * extdata, int index_item, bool * stop,
                         void *args);
static int file_tracker_item_dump_btree_capacity (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item,
                          FILE_EXTENSIBLE_DATA * extdata, int index_item, bool * stop,
                          void *args);
#if defined (SA_MODE)
static int file_tracker_item_check (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
                    int index_item, bool * stop, void *args);
#endif /* SA_MODE */
static int file_tracker_item_spacedb (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
                      int index_item, bool * stop, void *args);
static int file_tracker_spacedb (THREAD_ENTRY * thread_p, SPACEDB_FILES * spacedb);

/************************************************************************/
/* End of static functions                                              */
/************************************************************************/

/************************************************************************/
/* Define functions.                                                    */
/************************************************************************/

/************************************************************************/
/* File manager section                                                 */
/************************************************************************/

/*
 * file_manager_init () - initialize file manager
 */
int
file_manager_init (void)
{
  file_Logging = prm_get_bool_value (PRM_ID_FILE_LOGGING);

  assert (FILE_DESCRIPTORS_SIZE == sizeof (FILE_DESCRIPTORS));

  return file_tempcache_init ();
}

/*
 * file_manager_final () - finalize file manager
 */
void
file_manager_final (void)
{
  file_tempcache_final ();
}

/************************************************************************/
/* File header section.                                                 */
/************************************************************************/

/*
 * file_header_init () - initialize file header
 *
 * return      : void
 * fhead (out) : output initialized file header
 */
STATIC_INLINE void
file_header_init (FILE_HEADER * fhead)
{
  VFID_SET_NULL (&fhead->self);

  fhead->tablespace.initial_size = 0;
  fhead->tablespace.expand_ratio = 0;
  fhead->tablespace.expand_min_size = 0;
  fhead->tablespace.expand_max_size = 0;

  fhead->time_creation = 0;
  fhead->type = FILE_UNKNOWN_TYPE;
  fhead->file_flags = 0;
  fhead->volid_last_expand = NULL_VOLDES;
  VPID_SET_NULL (&fhead->vpid_last_temp_alloc);
  fhead->offset_to_last_temp_alloc = NULL_OFFSET;
  VPID_SET_NULL (&fhead->vpid_last_user_page_ftab);
  VPID_SET_NULL (&fhead->vpid_find_nth_last);
  fhead->first_index_find_nth_last = 0;
  VPID_SET_NULL (&fhead->vpid_sticky_first);

  fhead->n_page_total = 0;
  fhead->n_page_user = 0;
  fhead->n_page_ftab = 0;
  fhead->n_page_mark_delete = 0;

  fhead->n_sector_total = 0;
  fhead->n_sector_partial = 0;
  fhead->n_sector_full = 0;
  fhead->n_sector_empty = 0;

  fhead->offset_to_partial_ftab = NULL_OFFSET;
  fhead->offset_to_full_ftab = NULL_OFFSET;
  fhead->offset_to_user_page_ftab = NULL_OFFSET;

  fhead->reserved0 = 0;
  fhead->reserved1 = 0;
  fhead->reserved2 = 0;
  fhead->reserved3 = 0;
}

/*
 * file_header_sanity_check () - Debug function used to check the sanity of file header before and after certaion file
 *               file operations.
 *
 * return     : Void.
 * fhead (in) : File header.
 */
STATIC_INLINE void
file_header_sanity_check (THREAD_ENTRY * thread_p, FILE_HEADER * fhead)
{
#if !defined (NDEBUG)
  FILE_EXTENSIBLE_DATA *part_table;
  FILE_EXTENSIBLE_DATA *full_table;
  FILE_VSID_COLLECTOR collector;
  int iter_vsid;

  if (prm_get_bool_value (PRM_ID_FORCE_RESTART_TO_SKIP_RECOVERY))
    {
      /* we cannot guarantee sanity of files headers */
      return;
    }

  assert (!VFID_ISNULL (&fhead->self));

  assert (fhead->n_page_total > 0);
  assert (fhead->n_page_user >= 0);
  assert (fhead->n_page_ftab > 0);
  assert (fhead->n_page_free >= 0);
  assert (fhead->n_page_free + fhead->n_page_user + fhead->n_page_ftab == fhead->n_page_total);
  assert (fhead->n_page_mark_delete >= 0);
  assert (fhead->n_page_mark_delete <= fhead->n_page_user);

  assert (fhead->n_sector_total > 0);
  assert (fhead->n_sector_partial >= 0);
  assert (fhead->n_sector_empty >= 0);
  assert (fhead->n_sector_full >= 0);
  assert (fhead->n_sector_empty <= fhead->n_sector_partial);
  assert (fhead->n_sector_partial + fhead->n_sector_full == fhead->n_sector_total);

  if (fhead->n_page_free == 0)
    {
      assert (fhead->n_sector_total == fhead->n_sector_full);
    }

  // in some scenario with many file manipulation operations, next checks may be too slow. They gradually stack and
  // waiting times for file headers grow continuously.
  // skip the check if there are waiters on file header.
  // don't skip if file logging is activated. it means a bug in file manipulation is investigated and header checks may
  // be valuable.
  if (!prm_get_bool_value (PRM_ID_FILE_LOGGING) && pgbuf_has_any_waiters ((PAGE_PTR) fhead))
    {
      return;
    }

  er_stack_push ();

  FILE_HEADER_GET_PART_FTAB (fhead, part_table);
  if (fhead->n_sector_partial == 0)
    {
      assert (FILE_IS_TEMPORARY (fhead)
          || (file_extdata_is_empty (part_table) && VPID_ISNULL (&part_table->vpid_next)));
    }
  else
    {
      int part_cnt = 0;

      assert (!file_extdata_is_empty (part_table) || !VPID_ISNULL (&part_table->vpid_next));

      if (file_extdata_all_item_count (thread_p, part_table, &part_cnt) != NO_ERROR)
    {
      /* thread might be interrupted; give up checking */
      ASSERT_ERROR ();
      goto exit;
    }
      assert (FILE_IS_TEMPORARY (fhead) || fhead->n_sector_partial == part_cnt);
    }

  if (!FILE_IS_TEMPORARY (fhead))
    {
      FILE_HEADER_GET_FULL_FTAB (fhead, full_table);
      if (fhead->n_sector_full == 0)
    {
      assert (file_extdata_is_empty (full_table) && VPID_ISNULL (&full_table->vpid_next));
    }
      else
    {
      int full_cnt = 0;

      assert (!file_extdata_is_empty (full_table) || !VPID_ISNULL (&full_table->vpid_next));

      if (file_extdata_all_item_count (thread_p, full_table, &full_cnt) != NO_ERROR)
        {
          /* thread might be interrupted; give up checking */
          ASSERT_ERROR ();
          goto exit;
        }
      assert (FILE_IS_TEMPORARY (fhead) || fhead->n_sector_full == full_cnt);
    }
    }

  if (file_table_collect_all_vsids (thread_p, (PAGE_PTR) fhead, &collector) != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

  for (iter_vsid = 0; iter_vsid < collector.n_vsids - 1; iter_vsid++)
    {
      /* A VSID can't appears twice in tables. */
      assert (disk_compare_vsids (&collector.vsids[iter_vsid], &collector.vsids[iter_vsid + 1]) != 0);
    }

  if (collector.vsids != NULL)
    {
      db_private_free (thread_p, collector.vsids);
    }

exit:
  /* forget any error of the function */
  er_stack_pop ();
#endif /* !NDEBUG */
}

/*
 * file_rv_fhead_set_last_user_page_ftab () - Recovery of file header: set the VPID of last page in user page table.
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_fhead_set_last_user_page_ftab (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_fhead = rcv->pgptr;
  VPID *vpid = (VPID *) rcv->data;
  FILE_HEADER *fhead;

  assert (rcv->pgptr != NULL);
  assert (rcv->length == sizeof (VPID));

  fhead = (FILE_HEADER *) page_fhead;

  /* the correct VPID is logged. */

  VPID_COPY (&fhead->vpid_last_user_page_ftab, vpid);

  file_log ("file_rv_fhead_set_last_user_page_ftab",
        "update vpid_last_user_page_ftab to %d|%d in file %d|%d, "
        "header page %d|%d, lsa %lld|%d ", VPID_AS_ARGS (vpid), VFID_AS_ARGS (&fhead->self),
        PGBUF_PAGE_STATE_ARGS (page_fhead));

  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);
  return NO_ERROR;
}

/*
 * file_header_alloc () - Update stats in file header for page allocation.
 *
 * return      : Void
 * fhead (in)      : File header
 * alloc_type (in) : User/table page
 * was_empty (in)  : True if sector was empty before allocation.
 * is_full (in)    : True if sector is full after allocation.
 */
STATIC_INLINE void
file_header_alloc (FILE_HEADER * fhead, FILE_ALLOC_TYPE alloc_type, bool was_empty, bool is_full)
{
  assert (fhead != NULL);
  assert (alloc_type == FILE_ALLOC_USER_PAGE || alloc_type == FILE_ALLOC_TABLE_PAGE
      || alloc_type == FILE_ALLOC_TABLE_PAGE_FULL_SECTOR);
  assert (!was_empty || !is_full);

  fhead->n_page_free--;
  if (alloc_type == FILE_ALLOC_USER_PAGE)
    {
      fhead->n_page_user++;
    }
  else
    {
      fhead->n_page_ftab++;
    }

  if (was_empty)
    {
      fhead->n_sector_empty--;
    }

  if (is_full)
    {
      fhead->n_sector_partial--;
      fhead->n_sector_full++;
    }
}

/*
 * file_header_dealloc () - Update stats in file header for page deallocation.
 *
 * return      : Void
 * fhead (in)      : File header
 * alloc_type (in) : User/table page
 * is_empty (in)   : True if sector is empty after deallocation.
 * was_full (in)   : True if sector was full before deallocation.
 */
STATIC_INLINE void
file_header_dealloc (FILE_HEADER * fhead, FILE_ALLOC_TYPE alloc_type, bool is_empty, bool was_full)
{
  assert (fhead != NULL);
  assert (alloc_type == FILE_ALLOC_USER_PAGE || alloc_type == FILE_ALLOC_TABLE_PAGE);
  assert (!is_empty || !was_full);

  fhead->n_page_free++;
  if (alloc_type == FILE_ALLOC_USER_PAGE)
    {
      fhead->n_page_user--;
    }
  else
    {
      fhead->n_page_ftab--;
    }

  if (is_empty)
    {
      fhead->n_sector_empty++;
    }

  if (was_full)
    {
      fhead->n_sector_partial++;
      fhead->n_sector_full--;
    }
}

/*
 * file_rv_fhead_alloc () - Recovery for file header when a page is allocated.
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_fhead_alloc (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_fhead = rcv->pgptr;
  FILE_HEADER *fhead;
  bool is_ftab_page;
  bool is_full;
  bool was_empty;

  assert (rcv->length == sizeof (bool) * 3);

  is_ftab_page = ((bool *) rcv->data)[0];
  was_empty = ((bool *) rcv->data)[1];
  is_full = ((bool *) rcv->data)[2];

  fhead = (FILE_HEADER *) page_fhead;

  file_header_alloc (fhead, is_ftab_page ? FILE_ALLOC_TABLE_PAGE : FILE_ALLOC_USER_PAGE, was_empty, is_full);

  file_log ("file_rv_fhead_alloc",
        "update header in file %d|%d, header page %d|%d, lsa %lld|%d, "
        "after %s, was_empty %s, is_full %s \n" FILE_HEAD_ALLOC_MSG,
        VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_STATE_ARGS (page_fhead),
        FILE_ALLOC_TYPE_STRING (is_ftab_page ? FILE_ALLOC_TABLE_PAGE : FILE_ALLOC_USER_PAGE),
        was_empty ? "true" : "false", is_full ? "true" : "false", FILE_HEAD_ALLOC_AS_ARGS (fhead));

  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

  return NO_ERROR;
}

/*
 * file_rv_fhead_dealloc () - Recovery for file header when a page is deallocated.
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_fhead_dealloc (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_fhead = rcv->pgptr;
  FILE_HEADER *fhead;
  bool is_ftab_page;
  bool was_full;
  bool is_empty;

  assert (rcv->length == sizeof (bool) * 3);

  is_ftab_page = ((bool *) rcv->data)[0];
  is_empty = ((bool *) rcv->data)[1];
  was_full = ((bool *) rcv->data)[2];

  fhead = (FILE_HEADER *) page_fhead;

  file_header_dealloc (fhead, is_ftab_page ? FILE_ALLOC_TABLE_PAGE : FILE_ALLOC_USER_PAGE, is_empty, was_full);

  file_log ("file_rv_fhead_dealloc",
        "update header in file %d|%d, header page %d|%d, lsa %lld|%d, "
        "after de%s, is_empty %s, was_full %s \n" FILE_HEAD_ALLOC_MSG,
        VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_VPID_AS_ARGS (page_fhead),
        PGBUF_PAGE_LSA_AS_ARGS (page_fhead),
        FILE_ALLOC_TYPE_STRING (is_ftab_page ? FILE_ALLOC_TABLE_PAGE : FILE_ALLOC_USER_PAGE),
        is_empty ? "true" : "false", was_full ? "true" : "false", FILE_HEAD_ALLOC_AS_ARGS (fhead));

  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

  return NO_ERROR;
}

/*
 * file_log_fhead_alloc () - Log file header statistics update when a page is allocated.
 *
 * return      : Void.
 * thread_p (in)   : Thread entry.
 * page_fhead (in) : File header page.
 * alloc_type (in) : User/table page
 * was_empty (in)  : True if sector was empty before allocation.
 * is_full (in)    : True if sector is full after allocation.
 */
STATIC_INLINE void
file_log_fhead_alloc (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, FILE_ALLOC_TYPE alloc_type, bool was_empty,
              bool is_full)
{
#define LOG_BOOL_COUNT 3
  LOG_DATA_ADDR addr = LOG_DATA_ADDR_INITIALIZER;
  bool is_ftab_page = alloc_type != FILE_ALLOC_USER_PAGE;
  bool log_bools[3];

  assert (page_fhead != NULL);
  assert (alloc_type == FILE_ALLOC_TABLE_PAGE || alloc_type == FILE_ALLOC_USER_PAGE
      || alloc_type == FILE_ALLOC_TABLE_PAGE_FULL_SECTOR);
  assert (!was_empty || !is_full);

  log_bools[0] = is_ftab_page;
  log_bools[1] = was_empty;
  log_bools[2] = is_full;

  addr.pgptr = page_fhead;
  log_append_undoredo_data (thread_p, RVFL_FHEAD_ALLOC, &addr, sizeof (log_bools), sizeof (log_bools),
                log_bools, log_bools);
  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

#undef LOG_BOOL_COUNT
}

/*
 * file_log_fhead_dealloc () - Log file header statistics update when a page is deallocated.
 *
 * return      : Void.
 * thread_p (in)   : Thread entry.
 * page_fhead (in) : File header page.
 * alloc_type (in) : User/table page
 * is_empty (in)   : True if sector is empty after deallocation.
 * was_full (in)   : True if sector was full before deallocation.
 */
STATIC_INLINE void
file_log_fhead_dealloc (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, FILE_ALLOC_TYPE alloc_type, bool is_empty,
            bool was_full)
{
#define LOG_BOOL_COUNT 3
  LOG_DATA_ADDR addr = LOG_DATA_ADDR_INITIALIZER;
  bool is_ftab_page = (alloc_type == FILE_ALLOC_TABLE_PAGE);
  bool log_bools[3];

  assert (page_fhead != NULL);
  assert (alloc_type == FILE_ALLOC_TABLE_PAGE || alloc_type == FILE_ALLOC_USER_PAGE);
  assert (!is_empty || !was_full);

  log_bools[0] = is_ftab_page;
  log_bools[1] = is_empty;
  log_bools[2] = was_full;

  addr.pgptr = page_fhead;
  log_append_undoredo_data (thread_p, RVFL_FHEAD_DEALLOC, &addr, sizeof (log_bools), sizeof (log_bools),
                log_bools, log_bools);
  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

#undef LOG_BOOL_COUNT
}

/*
 * file_header_update_mark_deleted () - Update mark deleted count in file header and log change.
 *
 * return      : Void
 * thread_p (in)   : Thread entry
 * page_fhead (in) : File header page
 * delta (in)      : Mark deleted delta
 */
STATIC_INLINE void
file_header_update_mark_deleted (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, int delta)
{
  FILE_HEADER *fhead = (FILE_HEADER *) page_fhead;
  int undo_delta = -delta;

  LOG_LSA save_lsa = *pgbuf_get_lsa (page_fhead);

  fhead->n_page_mark_delete += delta;

  if (!FILE_IS_TEMPORARY (fhead))
    {
      log_append_undoredo_data2 (thread_p, RVFL_FHEAD_MARK_DELETE, NULL,
                 page_fhead, 0, sizeof (undo_delta), sizeof (delta), &undo_delta, &delta);
    }
  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

  file_log ("file_header_update_mark_deleted",
        "updated n_page_mark_delete by %d to %d in file %d|%d, "
        "header page %d|%d, prev_lsa %lld|%d, crt_lsa %lld|%d ", delta,
        fhead->n_page_mark_delete, VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_MODIFY_ARGS (page_fhead, &save_lsa));
}

/*
 * file_rv_header_update_mark_deleted () - Recovery for mark deleted count in file header
 *
 * return    : NO_ERROR
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_header_update_mark_deleted (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  int delta = *(int *) rcv->data;
  PAGE_PTR page_fhead = rcv->pgptr;
  FILE_HEADER *fhead;

  assert (page_fhead != NULL);
  assert (rcv->length == sizeof (int));

  fhead = (FILE_HEADER *) page_fhead;
  fhead->n_page_mark_delete += delta;
  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

  file_log ("file_rv_header_update_mark_deleted",
        "modified n_page_mark_delete by %d to %d in file %d|%d, "
        "header page %d|%d, lsa %lld|%d", delta,
        fhead->n_page_mark_delete, VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_STATE_ARGS (page_fhead));

  return NO_ERROR;
}

/*
 * file_header_copy () - get a file header copy
 *
 * return           : ERROR_CODE
 * thread_p (in)    : thread entry
 * vfid (in)        : file identifier
 * fhead_copy (out) : file header copy
 */
STATIC_INLINE int
file_header_copy (THREAD_ENTRY * thread_p, const VFID * vfid, FILE_HEADER * fhead_copy)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  int error_code = NO_ERROR;

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  *fhead_copy = *fhead;

  pgbuf_unfix (thread_p, page_fhead);
  return NO_ERROR;
}

/*
 * file_header_dump () - dump file header to file
 *
 * return        : void
 * thread_p (in) : thread entry
 * fhead (in)    : file header
 * fp (in)       : output file
 */
STATIC_INLINE void
file_header_dump (THREAD_ENTRY * thread_p, const FILE_HEADER * fhead, FILE * fp)
{
  fprintf (fp, FILE_HEAD_FULL_MSG, FILE_HEAD_FULL_AS_ARGS (fhead));
  file_header_dump_descriptor (thread_p, fhead, fp);
}

/*
 * file_header_dump_descriptor () - dump descriptor in file header
 *
 * return        : void
 * thread_p (in) : thread entry
 * fhead (in)    : file header
 * fp (in)       : output file
 */
STATIC_INLINE void
file_header_dump_descriptor (THREAD_ENTRY * thread_p, const FILE_HEADER * fhead, FILE * fp)
{
  BTID btid;
  char *index_name = NULL;

  switch (fhead->type)
    {
    case FILE_HEAP:
    case FILE_HEAP_REUSE_SLOTS:
      file_print_name_of_class (thread_p, fp, &fhead->descriptor.heap.class_oid);
      fprintf (fp, "\n");
      break;

    case FILE_MULTIPAGE_OBJECT_HEAP:
      fprintf (fp, "Overflow for HFID: %10d|%5d|%10d\n", HFID_AS_ARGS (&fhead->descriptor.heap_overflow.hfid));
      break;

    case FILE_BTREE:
      btid.vfid = fhead->self;
      btid.root_pageid = fhead->vpid_sticky_first.pageid;

      if (heap_get_indexinfo_of_btid (thread_p, &fhead->descriptor.btree.class_oid, &btid, NULL, NULL, NULL, NULL,
                      &index_name, NULL) == NO_ERROR)
    {
      file_print_name_of_class (thread_p, fp, &fhead->descriptor.btree.class_oid);
      fprintf (fp, ", %s, ATTRID: %5d ", index_name != NULL ? index_name : "*UNKNOWN-INDEX*",
           fhead->descriptor.btree.attr_id);
    }
      fprintf (fp, "\n");
      break;

    case FILE_BTREE_OVERFLOW_KEY:
      fprintf (fp, "Overflow keys for BTID: %10d|%5d|%10d\n",
           BTID_AS_ARGS (&fhead->descriptor.btree_key_overflow.btid));
      break;

    case FILE_EXTENDIBLE_HASH:
    case FILE_EXTENDIBLE_HASH_DIRECTORY:
      file_print_name_of_class (thread_p, fp, &fhead->descriptor.ehash.class_oid);
      fprintf (fp, ", ATTRID: %5d \n", fhead->descriptor.ehash.attr_id);
      break;

    case FILE_TRACKER:
    case FILE_CATALOG:
    case FILE_QUERY_AREA:
    case FILE_TEMP:
    case FILE_UNKNOWN_TYPE:
    case FILE_DROPPED_FILES:
    case FILE_VACUUM_DATA:
    default:
      fprintf (fp, "\n");
      break;
    }
}

/************************************************************************/
/* File extensible data section.                                        */
/************************************************************************/

/*
 * file_extdata_init () - Initialize extensible data component for the first time.
 *
 * return     : Void.
 * item_size (in) : Size of an item.
 * max_size (in)  : Desired maximum size for component.
 * extdata (out)  : Output initialized extensible data.
 */
STATIC_INLINE void
file_extdata_init (INT16 item_size, INT16 max_size, FILE_EXTENSIBLE_DATA * extdata)
{
  assert (extdata != NULL);
  assert (item_size > 0);
  assert (max_size > 0);

  VPID_SET_NULL (&extdata->vpid_next);
  extdata->size_of_item = item_size;
  extdata->n_items = 0;

  /* Align to size of item */
  extdata->max_size = DB_ALIGN_BELOW (max_size - FILE_EXTDATA_HEADER_ALIGNED_SIZE, extdata->size_of_item);
  if ((INT16) DB_ALIGN (extdata->max_size, MAX_ALIGNMENT) != extdata->max_size)
    {
      /* We need max alignment */
      extdata->max_size = DB_ALIGN (extdata->max_size - extdata->size_of_item, MAX_ALIGNMENT);
    }
  /* Safe guard: we should fit at least one item. */
  assert (extdata->max_size >= extdata->size_of_item);
}

/*
 * file_extdata_max_size () - Get maximum size of this extensible data including header.
 *
 * return   : Maximum size of extensible data.
 * extdata (in) : Extensible data.
 */
STATIC_INLINE int
file_extdata_max_size (const FILE_EXTENSIBLE_DATA * extdata)
{
  return FILE_EXTDATA_HEADER_ALIGNED_SIZE + extdata->max_size;
}

/*
 * file_extdata_size () - Get current size of this extensible data including header.
 *
 * return   : Current size of extensible data.
 * extdata (in) : Extensible data.
 */
STATIC_INLINE int
file_extdata_size (const FILE_EXTENSIBLE_DATA * extdata)
{
  return FILE_EXTDATA_HEADER_ALIGNED_SIZE + extdata->n_items * extdata->size_of_item;
}

/*
 * file_extdata_start () - Get pointer to first item in extensible data.
 *
 * return   : Pointer to first item.
 * extdata (in) : Extensible data.
 */
STATIC_INLINE void *
file_extdata_start (const FILE_EXTENSIBLE_DATA * extdata)
{
  return ((char *) extdata) + FILE_EXTDATA_HEADER_ALIGNED_SIZE;
}

/*
 * file_extdata_end () - Get pointer to the end of extensible data (after last item).
 *
 * return   : Pointer to end of extensible data.
 * extdata (in) : Extensible data.
 */
STATIC_INLINE void *
file_extdata_end (const FILE_EXTENSIBLE_DATA * extdata)
{
  return ((char *) extdata) + file_extdata_size (extdata);
}

/*
 * file_extdata_is_full () - Is this extensible data full (not enough room foradditional item).
 *
 * return   : True if full, false otherwise.
 * extdata (in) : Extensible data.
 */
STATIC_INLINE bool
file_extdata_is_full (const FILE_EXTENSIBLE_DATA * extdata)
{
  assert (extdata->n_items * extdata->size_of_item <= extdata->max_size);
  return (extdata->n_items + 1) * extdata->size_of_item > extdata->max_size;
}

/*
 * file_extdata_is_empty () - Is this extensible data empty?
 *
 * return   : True if empty, false otherwise.
 * extdata (in) : Extensible data.
 */
STATIC_INLINE bool
file_extdata_is_empty (const FILE_EXTENSIBLE_DATA * extdata)
{
  assert (extdata->n_items >= 0);
  return (extdata->n_items <= 0);
}

/*
 * file_extdata_item_count () - Get the item count in this extensible data.
 *
 * return   : Item count.
 * extdata (in) : Extensible data.
 */
STATIC_INLINE INT16
file_extdata_item_count (const FILE_EXTENSIBLE_DATA * extdata)
{
  return extdata->n_items;
}

/*
 * file_extdata_remaining_capacity () - Get the remaining capacity (in number of items) of this extensible data.
 *
 * return   : Remaining capacity.
 * extdata (in) : Extensible data.
 */
STATIC_INLINE INT16
file_extdata_remaining_capacity (const FILE_EXTENSIBLE_DATA * extdata)
{
  return extdata->max_size / extdata->size_of_item - extdata->n_items;
}

/*
 * file_extdata_append () - Append an item to extensible data. Caller should have checked there is enough room.
 *
 * return       : Void.
 * extdata (in)     : Extensible data.
 * append_item (in) : Item to append.
 */
STATIC_INLINE void
file_extdata_append (FILE_EXTENSIBLE_DATA * extdata, const void *append_item)
{
  assert (!file_extdata_is_full (extdata));

  memcpy (file_extdata_end (extdata), append_item, extdata->size_of_item);
  extdata->n_items++;
}

/*
 * file_extdata_append_array () - Append an array of items to extensible data. Caller should have checked there is
 *                enough room.
 *
 * return        : Void.
 * extdata (in)      : Extensible data.
 * append_items (in) : Item array.
 * count (in)        : Item count.
 */
STATIC_INLINE void
file_extdata_append_array (FILE_EXTENSIBLE_DATA * extdata, const void *append_items, INT16 count)
{
  assert (file_extdata_remaining_capacity (extdata) >= count);

  memcpy (file_extdata_end (extdata), append_items, extdata->size_of_item * count);
  extdata->n_items += count;
}

/*
 * file_extdata_at () - Pointer to item at given index.
 *
 * return   : Pointer to item.
 * extdata (in) : Extensible data.
 * index (in)   : Item index.
 */
STATIC_INLINE void *
file_extdata_at (const FILE_EXTENSIBLE_DATA * extdata, int index)
{
  assert (index >= 0 && index <= extdata->n_items);
  return (char *) file_extdata_start (extdata) + extdata->size_of_item * index;
}

/*
 * file_extdata_can_merge () - Check if source extensible data component can be merged into destination extensible data.
 *
 * return        : True if destination extensible data remaining capacity can cover all items in source extensible
 *             data. False otherwise.
 * extdata_src (in)  : Source extensible data.
 * extdata_dest (in) : Destination extensible data.
 */
STATIC_INLINE bool
file_extdata_can_merge (const FILE_EXTENSIBLE_DATA * extdata_src, const FILE_EXTENSIBLE_DATA * extdata_dest)
{
  return file_extdata_remaining_capacity (extdata_dest) >= extdata_src->n_items;
}

/*
 * file_extdata_merge_unordered () - Append source extensible data to destination extensible data.
 *
 * return        : Void.
 * extdata_src (in)  : Source extensible data.
 * extdata_dest (in/out) : Destination extensible data.
 */
STATIC_INLINE void
file_extdata_merge_unordered (const FILE_EXTENSIBLE_DATA * extdata_src, FILE_EXTENSIBLE_DATA * extdata_dest)
{
  file_extdata_append_array (extdata_dest, file_extdata_start (extdata_src), file_extdata_item_count (extdata_src));
}

/*
 * file_extdata_merge_ordered () - Merge source extensible data into destination extensible data and keep items ordered.
 *
 * return        : Void.
 * extdata_src (in)  : Source extensible data.
 * extdata_dest (in/out) : Destination extensible data.
 * compare_func (in)     : Compare function (to order items).
 */
STATIC_INLINE void
file_extdata_merge_ordered (const FILE_EXTENSIBLE_DATA * extdata_src, FILE_EXTENSIBLE_DATA * extdata_dest,
                int (*compare_func) (const void *, const void *))
{
  char *dest_ptr;
  char *dest_end_ptr;
  const char *src_ptr;
  const char *src_end_ptr;
  const char *src_new_ptr;
  int memsize = 0;

#if !defined (NDEBUG)
  char *debug_dest_end_ptr =
    (char *) file_extdata_end (extdata_dest) + extdata_src->n_items * extdata_src->size_of_item;
#endif /* !NDEBUG */

  /* safe guard: destination has enough capacity to include all source items. */
  assert (file_extdata_remaining_capacity (extdata_dest) >= file_extdata_item_count (extdata_src));

  src_ptr = (const char *) file_extdata_start (extdata_src);
  src_end_ptr = (const char *) file_extdata_end (extdata_src);

  dest_ptr = (char *) file_extdata_start (extdata_dest);
  dest_end_ptr = (char *) file_extdata_end (extdata_dest);

  /* advance source and destination pointers based on item order. Stop when end of destination is reached. */
  while (dest_ptr < dest_end_ptr)
    {
      /* collect all items from source that are smaller than current destination item. */
      for (src_new_ptr = src_ptr; src_new_ptr < src_end_ptr; src_new_ptr += extdata_src->size_of_item)
    {
      assert (compare_func (src_new_ptr, dest_ptr) != 0);
      if (compare_func (src_new_ptr, dest_ptr) > 0)
        {
          break;
        }
    }
      if (src_new_ptr > src_ptr)
    {
      /* move to dest_ptr */
      memsize = (int) (src_new_ptr - src_ptr);
      /* make room for new data */
      memmove (dest_ptr + memsize, dest_ptr, dest_end_ptr - dest_ptr);
      memcpy (dest_ptr, src_ptr, memsize);

      dest_ptr += memsize;
      dest_end_ptr += memsize;

      src_ptr = src_new_ptr;

      assert (dest_end_ptr <= debug_dest_end_ptr);
    }
      if (src_ptr >= src_end_ptr)
    {
      /* source extensible data was consumed. */
      assert (src_ptr == src_end_ptr);
      break;
    }
      /* skip all items from destination smaller than current source item */
      for (; dest_ptr < dest_end_ptr; dest_ptr += extdata_dest->size_of_item)
    {
      assert (compare_func (src_ptr, dest_ptr) != 0);
      if (compare_func (src_ptr, dest_ptr) <= 0)
        {
          break;
        }
    }
    }

  if (src_ptr < src_end_ptr)
    {
      /* end of destination reached. append remaining source items into destination. */
      assert (dest_ptr == dest_end_ptr);
      memcpy (dest_end_ptr, src_ptr, src_end_ptr - src_ptr);
      assert (dest_end_ptr + (src_end_ptr - src_ptr) == debug_dest_end_ptr);
    }
  else
    {
      /* all source items were moved to destination */
      assert (debug_dest_end_ptr == dest_end_ptr);
    }

  extdata_dest->n_items += extdata_src->n_items;
  assert (debug_dest_end_ptr == file_extdata_end (extdata_dest));
}

/*
 * file_extdata_find_ordered () - Find position for given item. If item does not exist, return the position of first
 *                bigger item.
 *
 * return        : Void.
 * extdata (in)      : Extensible data.
 * item_to_find (in) : Pointer to item to find.
 * compare_func (in) : Compare function used for binary search.
 * found (out)       : Output true if item was found, false otherwise.
 * position (out)    : Output the right position of item (found or not found).
 */
static void
file_extdata_find_ordered (const FILE_EXTENSIBLE_DATA * extdata, const void *item_to_find,
               int (*compare_func) (const void *, const void *), bool * found, int *position)
{
  assert (found != NULL);
  assert (position != NULL);

  *position = util_bsearch (item_to_find, file_extdata_start (extdata), file_extdata_item_count (extdata),
                extdata->size_of_item, compare_func, found);
}

/*
 * file_extdata_insert_at () - Insert items at given position in extensible data.
 *
 * return    : Void.
 * extdata (in)  : Extensible data.
 * position (in) : Position to insert items.
 * count (in)    : Item count.
 * data (in)     : Items to insert.
 */
STATIC_INLINE void
file_extdata_insert_at (FILE_EXTENSIBLE_DATA * extdata, int position, int count, const void *data)
{
  char *copy_at;
  int memmove_size;

  assert (extdata != NULL);
  assert (!file_extdata_is_full (extdata));
  assert (data != NULL);
  assert (position >= 0 && position <= file_extdata_item_count (extdata));

  /* move current items at desired position to the right. */
  memmove_size = (extdata->n_items - position) * extdata->size_of_item;
  copy_at = (char *) file_extdata_at (extdata, position);
  if (memmove_size > 0)
    {
      memmove (copy_at + extdata->size_of_item * count, copy_at, memmove_size);
    }

  /* copy new items at position */
  memcpy (copy_at, data, extdata->size_of_item * count);

  /* update item count */
  extdata->n_items += count;
}

/*
 * file_extdata_remove_at () - Remove items from give position in extensible data.
 *
 * return        : Void.
 * extdata (in)  : Extensible data.
 * position (in) : Position where items must be removed.
 * count (in)    : Item count.
 */
STATIC_INLINE void
file_extdata_remove_at (FILE_EXTENSIBLE_DATA * extdata, int position, int count)
{
  char *remove_at;
  int memmove_size;

  if (position < 0 || position >= extdata->n_items)
    {
      /* bad index. give up */
      assert_release (false);
      return;
    }

  /* remove items */
  remove_at = (char *) file_extdata_at (extdata, position);
  memmove_size = (extdata->n_items - count - position) * extdata->size_of_item;
  if (memmove_size > 0)
    {
      memmove (remove_at, remove_at + extdata->size_of_item * count, memmove_size);
    }

  /* update item count */
  extdata->n_items -= count;
}

/*
 * file_extdata_apply_funcs () - Process extensible data components and apply functions to each component and/or to each
 *               item.
 *
 * return          : Error code.
 * thread_p (in)       : Thread entry.
 * extdata_in (in)     : First extensible data component.
 * f_extdata (in)      : Function to apply for each extensible data component (can be NULL).
 * f_extdata_args (in/out) : Argument for component function.
 * f_item (in)         : Function to apply for each item (can be NULL).
 * f_item_args (in/out)    : Argument for item function.
 * for_write (in)          : Should page be fixed for write?
 * extdata_out (out)       : Output current extensible data component if processing is stopped.
 * page_out (out)      : Output page of current extensible data component if processing is stopped.
 */
static int
file_extdata_apply_funcs (THREAD_ENTRY * thread_p, FILE_EXTENSIBLE_DATA * extdata_in, FILE_EXTDATA_FUNC f_extdata,
              void *f_extdata_args, FILE_EXTDATA_ITEM_FUNC f_item, void *f_item_args,
              bool for_write, FILE_EXTENSIBLE_DATA ** extdata_out, PAGE_PTR * page_out)
{
  int i;
  bool stop = false;        /* forces to stop processing extensible data */
  PAGE_PTR page_extdata = NULL; /* extensible data page */
  PGBUF_LATCH_MODE latch_mode = for_write ? PGBUF_LATCH_WRITE : PGBUF_LATCH_READ;
  int error_code = NO_ERROR;
  VPID vpid_next = VPID_INITIALIZER;

  if (page_out != NULL)
    {
      *page_out = NULL;     /* make it sure for an error */
    }

  while (true)
    {
      /* catch infinite loop, if any */
      assert (page_extdata == NULL || !VPID_EQ (pgbuf_get_vpid_ptr (page_extdata), &extdata_in->vpid_next));
      if (f_extdata != NULL)
    {
      /* apply f_extdata */
      error_code = f_extdata (thread_p, extdata_in, &stop, f_extdata_args);
      if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          goto exit;
        }
      if (stop)
        {
          /* stop processing */
          goto exit;
        }
    }

      if (f_item != NULL)
    {
      /* iterate through all items in current page. */
      for (i = 0; i < file_extdata_item_count (extdata_in); i++)
        {
          /* apply f_item */
          error_code = f_item (thread_p, file_extdata_at (extdata_in, i), i, &stop, f_item_args);
          if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          goto exit;
        }
          if (stop)
        {
          goto exit;
        }
        }
    }

      if (VPID_ISNULL (&extdata_in->vpid_next))
    {
      /* end of extensible data. */
      break;
    }
      vpid_next = extdata_in->vpid_next;

      /* advance to next page */
      if (page_extdata != NULL)
    {
      pgbuf_unfix_and_init (thread_p, page_extdata);
    }
      page_extdata = pgbuf_fix (thread_p, &vpid_next, OLD_PAGE, latch_mode, PGBUF_UNCONDITIONAL_LATCH);
      if (page_extdata == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
      /* get component in next page */
      extdata_in = (FILE_EXTENSIBLE_DATA *) page_extdata;
    }

exit:
  if (stop && page_out != NULL)
    {
      /* output current page */
      *page_out = page_extdata;
    }
  else if (page_extdata != NULL)
    {
      /* unfix current page */
      pgbuf_unfix (thread_p, page_extdata);
    }

  if (stop && extdata_out != NULL)
    {
      /* output current extensible data component */
      *extdata_out = extdata_in;
    }

  return error_code;
}

/*
 * file_extdata_func_for_search_ordered () - Function callable by file_extdata_apply_funcs; binary search for an item
 *                       in ordered extensible data.
 *
 * return    : NO_ERROR.
 * thread_p (in) : Thread entry.
 * extdata (in)  : Extensible data.
 * stop (out)    : Output true when item is found and search can be stopped.
 * args (in/out) : Search context.
 */
static int
file_extdata_func_for_search_ordered (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata, bool * stop,
                      void *args)
{
  FILE_EXTENSIBLE_DATA_SEARCH_CONTEXT *search_context = (FILE_EXTENSIBLE_DATA_SEARCH_CONTEXT *) args;

  assert (search_context != NULL);

  file_extdata_find_ordered (extdata, search_context->item_to_find, search_context->compare_func,
                 &search_context->found, &search_context->position);
  if (search_context->found)
    {
      *stop = true;
    }

  return NO_ERROR;
}

/*
 * file_extdata_item_func_for_search () - Function callable by file_extdata_apply_funcs; searches item by comparing
 *                    extensible data item to the item in search context.
 *
 * return    : NO_ERROR.
 * thread_p (in) : Thread entry.
 * item (in)     : Item in extensible data.
 * index (in)    : Index of item in extensible data.
 * stop (out)    : Output true when item is found and search can be stopped.
 * args (in/out) : Search context.
 */
static int
file_extdata_item_func_for_search (THREAD_ENTRY * thread_p, const void *item, int index, bool * stop, void *args)
{
  FILE_EXTENSIBLE_DATA_SEARCH_CONTEXT *search_context = (FILE_EXTENSIBLE_DATA_SEARCH_CONTEXT *) args;

  if (search_context->compare_func (search_context->item_to_find, item) == 0)
    {
      /* Found */
      search_context->found = true;
      search_context->position = index;
      *stop = true;
    }
  return NO_ERROR;
}

/*
 * file_extdata_search_item () - Search for item in extensible data.
 *
 * return         : Error code.
 * thread_p (in)      : Thread entry.
 * extdata (in/out)   : Extensible data. First component as input, component where item was found as output.
 * item_to_find (in)  : Searched item.
 * compare_func (in)  : Compare function used to find item.
 * is_ordered (in)    : True if items in extensible are ordered and can be searched using binary search. False otherwise
 * for_write (in)     : Should page be fixed for write?
 * found (out)        : Output true if item is found in extensible data, false otherwise.
 * position (out)     : Output the position of found item (if found) in its extensible data component.
 * page_extdata (out) : Output page of extensible data component where item is found (if found).
 */
static int
file_extdata_search_item (THREAD_ENTRY * thread_p, FILE_EXTENSIBLE_DATA ** extdata, const void *item_to_find,
              int (*compare_func) (const void *, const void *),
              bool is_ordered, bool for_write, bool * found, int *position, PAGE_PTR * page_extdata)
{
  FILE_EXTENSIBLE_DATA_SEARCH_CONTEXT search_context;
  FILE_EXTENSIBLE_DATA *extdata_in = *extdata;
  int error_code = NO_ERROR;

  search_context.item_to_find = item_to_find;
  search_context.compare_func = compare_func;
  search_context.found = false;
  search_context.position = -1;

  if (is_ordered)
    {
      error_code = file_extdata_apply_funcs (thread_p, extdata_in, file_extdata_func_for_search_ordered,
                         &search_context, NULL, NULL, for_write, extdata, page_extdata);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      assert (*page_extdata == NULL);
      return error_code;
    }
    }
  else
    {
      error_code = file_extdata_apply_funcs (thread_p, extdata_in, NULL, NULL, file_extdata_item_func_for_search,
                         &search_context, for_write, extdata, page_extdata);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      assert (*page_extdata == NULL);
      return error_code;
    }
    }

  if (found != NULL)
    {
      *found = search_context.found;
    }

  if (position != NULL)
    {
      *position = search_context.position;
    }

  return NO_ERROR;
}

/*
 * file_extdata_find_not_full () - Find an extensible data component that is not full.
 *
 * return       : Error code.
 * thread_p (in)    : Thread entry.
 * extdata (in/out) : Extensible data. First component as input, component where free space is found as output.
 * page_out (out)   : Output page of component with free space.
 * found (out)      : Output true if a component with free space is found.
 */
static int
file_extdata_find_not_full (THREAD_ENTRY * thread_p, FILE_EXTENSIBLE_DATA ** extdata, PAGE_PTR * page_out, bool * found)
{
  VPID vpid_next;
  int error_code = NO_ERROR;

  assert (extdata != NULL && *extdata != NULL);
  assert (page_out != NULL);
  assert (found != NULL);

  /* how it works:
   * we are looking for the first extensible data component that still has space for one additional item. the extensible
   * data and page where the space is found is then output.
   *
   * note: the input page is usually NULL. the input extensible data usually belongs to file header. this would unfix
   *       page_out when it advances to next page and we don't want to unfix header page.
   * note: if all extensible data components are full, the last extensible data and page are output. the caller can
   *       then use them to append a new page and a new extensible data component.
   */

  *found = false;

  while (file_extdata_is_full (*extdata))
    {
      VPID_COPY (&vpid_next, &(*extdata)->vpid_next);
      if (VPID_ISNULL (&vpid_next))
    {
      /* Not found. */
      return NO_ERROR;
    }

      /* Move to next page */
      if (*page_out != NULL)
    {
      pgbuf_unfix_and_init (thread_p, *page_out);
    }

      *page_out = pgbuf_fix (thread_p, &vpid_next, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (*page_out == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

      *extdata = (FILE_EXTENSIBLE_DATA *) (*page_out);
    }

  /* Found not full */
  *found = true;
  return NO_ERROR;
}

/*
 * file_rv_extdata_set_next () - Recovery function to set extensible data next page.
 *
 * return    : NO_ERROR.
 * thread_p (in) : Thread entry.
 * rcv (in)  : Recovery data.
 */
int
file_rv_extdata_set_next (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_ftab = rcv->pgptr;
  VPID *vpid_next = (VPID *) rcv->data;
  FILE_EXTENSIBLE_DATA *extdata = NULL;

  assert (page_ftab != NULL);
  assert (rcv->length == sizeof (VPID));
  assert (rcv->offset >= 0 && rcv->offset < DB_PAGESIZE);

  extdata = (FILE_EXTENSIBLE_DATA *) (page_ftab + rcv->offset);
  VPID_COPY (&extdata->vpid_next, vpid_next);

  file_log ("file_rv_extdata_set_next",
        "page %d|%d, lsa %lld|%d, changed extdata link \n"
        FILE_EXTDATA_MSG ("extdata after"), PGBUF_PAGE_STATE_ARGS (page_ftab), FILE_EXTDATA_AS_ARGS (extdata));

  pgbuf_set_dirty (thread_p, page_ftab, DONT_FREE);
  return NO_ERROR;
}

/*
 * file_rv_dump_extdata_set_next () - Dump VPID for recovery of extensible data next page.
 *
 * return         : Void.
 * fp (in/out)        : Dump output.
 * ignore_length (in) : Length of recovery data.
 * data (in)          : Recovery data.
 */
void
file_rv_dump_extdata_set_next (FILE * fp, int ignore_length, void *data)
{
  VPID *vpid_next = (VPID *) data;

  fprintf (fp, "Set extensible data next page to %d|%d.\n", VPID_AS_ARGS (vpid_next));
}

/*
 * file_rv_extdata_add () - Add items to extensible data for recovery.
 *
 * return    : NO_ERROR
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_extdata_add (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_ftab = rcv->pgptr;
  FILE_EXTENSIBLE_DATA *extdata = NULL;
  int pos, count, offset = 0;

  assert (page_ftab != NULL);
  assert (rcv->offset >= 0 && rcv->offset < DB_PAGESIZE);

  pos = *(int *) (rcv->data + offset);
  offset += sizeof (pos);

  count = *(int *) (rcv->data + offset);
  offset += sizeof (count);

  extdata = (FILE_EXTENSIBLE_DATA *) (page_ftab + rcv->offset);
  assert (rcv->length == offset + extdata->size_of_item * count);

  file_extdata_insert_at (extdata, pos, count, rcv->data + offset);

  file_log ("file_rv_extdata_add",
        "add %d entries at position %d in page %d|%d, lsa %lld|%d \n"
        FILE_EXTDATA_MSG ("extdata after"), count, pos,
        PGBUF_PAGE_STATE_ARGS (page_ftab), FILE_EXTDATA_AS_ARGS (extdata));

  pgbuf_set_dirty (thread_p, page_ftab, DONT_FREE);
  return NO_ERROR;
}

/*
 * file_rv_extdata_remove () - Remove items from extensible data for recovery.
 *
 * return    : NO_ERROR
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_extdata_remove (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_ftab = rcv->pgptr;
  FILE_EXTENSIBLE_DATA *extdata = NULL;
  int pos;
  int count;
  int offset = 0;

  assert (page_ftab != NULL);
  assert (rcv->offset >= 0 && rcv->offset < DB_PAGESIZE);

  pos = *(int *) (rcv->data + offset);
  offset += sizeof (pos);

  count = *(int *) (rcv->data + offset);
  offset += sizeof (count);

  assert (offset == rcv->length);

  extdata = (FILE_EXTENSIBLE_DATA *) (page_ftab + rcv->offset);
  file_extdata_remove_at (extdata, pos, count);

  file_log ("file_rv_extdata_remove",
        "remove %d entries at position %d in page %d|%d, lsa %lld|%d"
        FILE_EXTDATA_MSG ("extdata after"), count, pos,
        PGBUF_PAGE_STATE_ARGS (page_ftab), FILE_EXTDATA_AS_ARGS (extdata));

  pgbuf_set_dirty (thread_p, page_ftab, DONT_FREE);
  return NO_ERROR;
}

/*
 * file_rv_dump_extdata_add () - Dump add items to extensible data for recovery
 *
 * return      : Void
 * fp (in)     : Dump output
 * length (in) : Recovery data length
 * data (in)   : Recovery data
 */
void
file_rv_dump_extdata_add (FILE * fp, int length, void *data)
{
  int pos, count, offset = 0;

  pos = *(int *) ((char *) data + offset);
  offset += sizeof (pos);

  count = *(int *) ((char *) data + offset);
  offset += sizeof (count);

  fprintf (fp, "Add to extensible data at position = %d, count = %d.\n", pos, count);
  log_rv_dump_hexa (fp, length - offset, (char *) data + offset);
}

/*
 * file_rv_dump_extdata_remove () - Dump remove items from extensible data for recovery
 *
 * return      : Void
 * fp (in)     : Dump output
 * length (in) : Recovery data length
 * data (in)   : Recovery data
 */
void
file_rv_dump_extdata_remove (FILE * fp, int length, void *data)
{
  int pos, count, offset = 0;

  pos = *(int *) ((char *) data + offset);
  offset += sizeof (pos);

  count = *(int *) ((char *) data + offset);
  offset += sizeof (count);
  assert (length == offset);

  fprintf (fp, "Remove from extensible data at position = %d, count = %d.\n", pos, count);
}

/*
 * file_log_extdata_add () - Log adding items to extensible data
 *
 * return    : Void
 * thread_p (in) : Thread entry
 * extdata (in)  : Extensible data
 * page (in)     : Page of extensible data
 * position (in) : Position in extensible data where items are added
 * count (in)    : Item count
 * data (in)     : Item(s)
 */
STATIC_INLINE void
file_log_extdata_add (THREAD_ENTRY * thread_p,
              const FILE_EXTENSIBLE_DATA * extdata, PAGE_PTR page, int position, int count, const void *data)
{
  LOG_DATA_ADDR addr = LOG_DATA_ADDR_INITIALIZER;
  LOG_CRUMB crumbs[3];

  addr.pgptr = page;
  addr.offset = (PGLENGTH) (((char *) extdata) - page);

  crumbs[0].data = &position;
  crumbs[0].length = sizeof (position);

  crumbs[1].data = &count;
  crumbs[1].length = sizeof (count);

  crumbs[2].data = data;
  crumbs[2].length = extdata->size_of_item * count;

  log_append_undoredo_crumbs (thread_p, RVFL_EXTDATA_ADD, &addr, 2, 3, crumbs, crumbs);
  pgbuf_set_dirty (thread_p, page, DONT_FREE);
}

/*
 * file_log_extdata_remove () - Log removing items from extensible data
 *
 * return    : Void
 * thread_p (in) : Thread entry
 * extdata (in)  : Extensible data
 * page (in)     : Page of extensible data
 * position (in) : Position in extensible data from where items are removed
 * count (in)    : Item count
 */
STATIC_INLINE void
file_log_extdata_remove (THREAD_ENTRY * thread_p,
             const FILE_EXTENSIBLE_DATA * extdata, PAGE_PTR page, int position, int count)
{
  LOG_DATA_ADDR addr = LOG_DATA_ADDR_INITIALIZER;
  LOG_CRUMB crumbs[3];

  addr.pgptr = page;
  addr.offset = (PGLENGTH) (((char *) extdata) - page);

  crumbs[0].data = &position;
  crumbs[0].length = sizeof (position);

  crumbs[1].data = &count;
  crumbs[1].length = sizeof (count);

  crumbs[2].data = file_extdata_at (extdata, position);
  crumbs[2].length = extdata->size_of_item * count;

  log_append_undoredo_crumbs (thread_p, RVFL_EXTDATA_REMOVE, &addr, 3, 2, crumbs, crumbs);
  pgbuf_set_dirty (thread_p, page, DONT_FREE);
}

/*
 * file_log_extdata_set_next () - Log setting next page VPID into extensible data
 *
 * return     : Void
 * thread_p (in)  : Thread entry
 * extdata (in)   : Extensible data
 * page (in)      : Page of extensible data
 * vpid_next (in) : New value for next page VPID.
 */
STATIC_INLINE void
file_log_extdata_set_next (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata, PAGE_PTR page,
               const VPID * vpid_next)
{
  LOG_DATA_ADDR addr = LOG_DATA_ADDR_INITIALIZER;
  LOG_LSA save_lsa;

  addr.pgptr = page;
  addr.offset = (PGLENGTH) (((char *) extdata) - page);
  /* extdata should belong to page */
  assert (addr.offset >= 0 && addr.offset < DB_PAGESIZE);

  save_lsa = *pgbuf_get_lsa (page);
  log_append_undoredo_data (thread_p, RVFL_EXTDATA_SET_NEXT, &addr, sizeof (VPID), sizeof (VPID), &extdata->vpid_next,
                vpid_next);

  file_log ("file_log_extdata_set_next",
        "page %d|%d, prev_lsa %lld|%d, crt_lsa %lld|%d, "
        "change extdata link to %d|%d, \n"
        FILE_EXTDATA_MSG ("extdata before"),
        PGBUF_PAGE_MODIFY_ARGS (page, &save_lsa), VPID_AS_ARGS (vpid_next), FILE_EXTDATA_AS_ARGS (extdata));

  pgbuf_set_dirty (thread_p, page, DONT_FREE);
}

/*
 * file_rv_extdata_merge () - recovery of merging extensible data components
 *
 * return    : NO_ERROR
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_extdata_merge (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  FILE_EXTENSIBLE_DATA *extdata_in_page;
  FILE_EXTENSIBLE_DATA *extdata_in_rcv;

  assert (rcv->pgptr != NULL);
  assert (rcv->offset >= 0 && rcv->offset < DB_PAGESIZE);

  /* how it works:
   * the entire extensible data is logged before and after merge. so this is used for both undo and redo.
   */

  extdata_in_page = (FILE_EXTENSIBLE_DATA *) (rcv->pgptr + rcv->offset);
  extdata_in_rcv = (FILE_EXTENSIBLE_DATA *) rcv->data;

  assert (file_extdata_size (extdata_in_rcv) == rcv->length);

  /* overwrite extdata with recovery. */
  memcpy (extdata_in_page, extdata_in_rcv, rcv->length);
  pgbuf_set_dirty (thread_p, rcv->pgptr, DONT_FREE);

  file_log ("file_rv_extdata_merge", PGBUF_PAGE_STATE_MSG ("extensible data page") FILE_EXTDATA_MSG ("extdata after"),
        PGBUF_PAGE_STATE_ARGS (rcv->pgptr), FILE_EXTDATA_AS_ARGS (extdata_in_page));

  return NO_ERROR;
}

/*
 * file_extdata_update_item () - Update extensible data item and log the change.
 *
 * return            : void
 * thread_p (in)     : thread entry
 * page_extdata (in) : page of extensible data
 * item_newval (in)  : new item value
 * index_item (in)   : index to item being updated
 * extdata (in/out)  : extensible data (one item is modified in this function)
 */
STATIC_INLINE void
file_extdata_update_item (THREAD_ENTRY * thread_p, PAGE_PTR page_extdata, const void *item_newval, int index_item,
              FILE_EXTENSIBLE_DATA * extdata)
{
  char *item_in_page = (char *) file_extdata_at (extdata, index_item);
  PGLENGTH offset_in_page = (PGLENGTH) (item_in_page - page_extdata);

  log_append_undoredo_data2 (thread_p, RVFL_EXTDATA_UPDATE_ITEM, NULL, page_extdata, offset_in_page,
                 extdata->size_of_item, extdata->size_of_item, item_in_page, item_newval);

  memcpy (item_in_page, item_newval, extdata->size_of_item);
  pgbuf_set_dirty (thread_p, page_extdata, DONT_FREE);
}

/*
 * file_extdata_merge_pages () - try to merge source extensible data page into destination extensible data page
 *
 * return            : return true if pages have been merged, false otherwise
 * thread_p (in)     : thread entry
 * extdata_src (in)  : source extensible data
 * page_src (in)     : source extensible data page
 * extdata_dest (in) : destination extensible data
 * page_dest (in)    : destination extensible data page
 * compare_func (in) : compare function (required if items are ordered)
 * ordered (in)      : true if items are ordered in pages (order must be preserved)
 */
STATIC_INLINE bool
file_extdata_merge_pages (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata_src, const PAGE_PTR page_src,
              FILE_EXTENSIBLE_DATA * extdata_dest, PAGE_PTR page_dest,
              int (*compare_func) (const void *, const void *), bool ordered)
{
  LOG_DATA_ADDR addr = LOG_DATA_ADDR_INITIALIZER;
  LOG_LSA save_lsa = LSA_INITIALIZER;

  assert (extdata_dest != NULL && page_dest != NULL);
  assert ((char *) extdata_dest >= page_dest && (char *) extdata_dest < page_dest + DB_PAGESIZE);
  assert (extdata_src != NULL && page_src != NULL);
  assert ((char *) extdata_src >= page_src && (char *) extdata_src < page_src + DB_PAGESIZE);
  assert (!ordered || compare_func != NULL);
  assert (log_check_system_op_is_started (thread_p));

  if (!file_extdata_can_merge (extdata_src, extdata_dest))
    {
      /* not enough space */
      return false;
    }

  save_lsa = *pgbuf_get_lsa (page_dest);
  /* log previous extensible data */
  addr.pgptr = page_dest;
  addr.offset = (PGLENGTH) (((char *) extdata_dest) - page_dest);
  log_append_undo_data (thread_p, RVFL_EXTDATA_MERGE, &addr, file_extdata_size (extdata_dest), extdata_dest);

  if (ordered)
    {
      /* ordered merge */
      file_extdata_merge_ordered (extdata_src, extdata_dest, compare_func);
    }
  else
    {
      /* unordered merge */
      file_extdata_merge_unordered (extdata_src, extdata_dest);
    }
  /* update vpid_next */
  extdata_dest->vpid_next = extdata_src->vpid_next;

  /* log new extensible data */
  log_append_redo_data (thread_p, RVFL_EXTDATA_MERGE, &addr, file_extdata_size (extdata_dest), extdata_dest);
  pgbuf_set_dirty (thread_p, page_dest, DONT_FREE);

  file_log ("file_extdata_merge_pages", "merged extensible data: \n" FILE_EXTDATA_MSG ("extdata dest")
        "\t" PGBUF_PAGE_MODIFY_MSG ("page dest") "\n" FILE_EXTDATA_MSG ("extdata src")
        "\t" PGBUF_PAGE_STATE_MSG ("page src") "\n", FILE_EXTDATA_AS_ARGS (extdata_dest),
        PGBUF_PAGE_MODIFY_ARGS (page_dest, &save_lsa), FILE_EXTDATA_AS_ARGS (extdata_src),
        PGBUF_PAGE_STATE_ARGS (page_src));

  /* successful merge */
  return true;
}

/*
 * file_extdata_find_and_remove_item () - find an item in extensible data and remove it. if possible, also merge
 *                                        extensible data pages
 *
 * return             : error code
 * thread_p (in)      : thread entry
 * extdata_first (in) : first extensible data
 * page_first (in)    : first extensible data page
 * item (in)          : item to find
 * compare_func (in)  : compare function
 * ordered (in)       : true if each extensible data page is ordered
 * item_pop (out)     : if not NULL it will output the item removed from extensible data.
 * vpid_merged (out)  : output the vpid if merged extensible data page or NULL vpid.
 */
static int
file_extdata_find_and_remove_item (THREAD_ENTRY * thread_p, FILE_EXTENSIBLE_DATA * extdata_first, PAGE_PTR page_first,
                   const void *item, int (*compare_func) (const void *, const void *), bool ordered,
                   void *item_pop, VPID * vpid_merged)
{
  PAGE_PTR page_prev = NULL;
  PAGE_PTR page_crt = NULL;
  FILE_EXTENSIBLE_DATA *extdata_prev = NULL;
  FILE_EXTENSIBLE_DATA *extdata_crt = NULL;
  bool found = false;
  int pos = 0;
  LOG_LSA save_lsa;
  int error_code = NO_ERROR;

  assert (extdata_first != NULL);
  assert (page_first != NULL);
  assert (item != NULL);
  assert (compare_func != NULL);
  assert (vpid_merged != NULL);
  assert (log_check_system_op_is_started (thread_p));

  /* how it works:
   *
   * first we must find the item in one of the extensible pages. we iterate through each page and try to find the item.
   * during page iteration we save the previous page (it is NULL when current page is first).
   *
   * if the items are ordered in each page, a binary search is executed. if not, items are compared one by one.
   *
   * when the item is found, it is removed from page. we also try to merge it with previous page or to merge next page
   * into current page. if merge is executed, the merged page vpid is output.
   *
   * if item_pop is not NULL, the removed item will be copied (note that the item is not always identical to search
   * item).
   */

  VPID_SET_NULL (vpid_merged);

  /* iterate through extensible data pages */
  extdata_crt = extdata_first;
  page_crt = page_first;
  while (true)
    {
      /* search item: do binary search if page is ordered, otherwise iterate through all until matched */
      if (ordered)
    {
      file_extdata_find_ordered (extdata_crt, item, compare_func, &found, &pos);
    }
      else
    {
      for (pos = 0; pos < file_extdata_item_count (extdata_crt); pos++)
        {
          if (compare_func (item, file_extdata_at (extdata_crt, pos)) == 0)
        {
          found = true;
          break;
        }
        }
    }

      if (found)
    {
      /* found item. remove it */
      assert (pos >= 0 && pos < file_extdata_item_count (extdata_crt));

      if (item_pop != NULL)
        {
          /* output item before removing */
          memcpy (item_pop, file_extdata_at (extdata_crt, pos), extdata_crt->size_of_item);
        }

      save_lsa = *pgbuf_get_lsa (page_crt);
      file_log_extdata_remove (thread_p, extdata_crt, page_crt, pos, 1);
      file_extdata_remove_at (extdata_crt, pos, 1);

      file_log ("file_extdata_find_and_remove_item", "removed extensible data item: \n"
            FILE_EXTDATA_MSG ("extensible data") "\t" PGBUF_PAGE_MODIFY_MSG ("extensible data page") "\n"
            "\tposition = %d \n", FILE_EXTDATA_AS_ARGS (extdata_crt),
            PGBUF_PAGE_MODIFY_ARGS (page_crt, &save_lsa), pos);

      if (page_prev != NULL)
        {
          /* try to merge to previous page */
          assert (extdata_prev != NULL);

          if (file_extdata_merge_pages (thread_p, extdata_crt, page_crt, extdata_prev, page_prev, compare_func,
                        ordered))
        {
          pgbuf_get_vpid (page_crt, vpid_merged);
          /* we are done here */
          goto exit;
        }
        }
    }
      if (VPID_ISNULL (&extdata_crt->vpid_next))
    {
      break;
    }
      if (page_prev != NULL && page_prev != page_first)
    {
      pgbuf_unfix_and_init (thread_p, page_prev);
    }
      page_prev = page_crt;
      extdata_prev = extdata_crt;
      page_crt = pgbuf_fix (thread_p, &extdata_prev->vpid_next, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_crt == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
      extdata_crt = (FILE_EXTENSIBLE_DATA *) page_crt;
      if (found)
    {
      /* try to merge to previous page */
      if (file_extdata_merge_pages (thread_p, extdata_crt, page_crt, extdata_prev, page_prev, compare_func,
                    ordered))
        {
          pgbuf_get_vpid (page_crt, vpid_merged);
        }
      break;
    }
    }
  if (!found)
    {
      /* item is missing which is unexpected */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  assert (error_code == NO_ERROR);

exit:
  assert (page_prev != page_crt);

  if (page_prev != NULL && page_prev != page_first)
    {
      pgbuf_unfix (thread_p, page_prev);
    }
  if (page_crt != NULL && page_crt != page_first)
    {
      pgbuf_unfix (thread_p, page_crt);
    }
  return error_code;
}

/*
 * file_extdata_all_item_count () - count items in all extensible data pages.
 *
 * return        : error code
 * thread_p (in) : thread entry
 * extdata (in)  : extensible data
 * count (out)   : output total count of items
 */
static int
file_extdata_all_item_count (THREAD_ENTRY * thread_p, FILE_EXTENSIBLE_DATA * extdata, int *count)
{
  return file_extdata_apply_funcs (thread_p, extdata, file_extdata_add_item_count, count, NULL, NULL, false, NULL,
                   NULL);
}

/*
 * file_extdata_add_item_count () - FILE_EXTDATA_FUNC to count extensible data items.
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * extdata (in)  : extensible data
 * stop (in)     : ignored
 * args (in)     : pointer to total count of items
 */
static int
file_extdata_add_item_count (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata, bool * stop, void *args)
{
  (*((int *) args)) += file_extdata_item_count (extdata);

  return NO_ERROR;
}

/************************************************************************/
/* Partially allocated sectors section                                  */
/************************************************************************/

/*
 * file_partsect_is_full () - Is partial sector full?
 *
 * return    : True if partial sector is full, false otherwise.
 * partsect (in) : Partial sector.
 */
STATIC_INLINE bool
file_partsect_is_full (FILE_PARTIAL_SECTOR * partsect)
{
  return partsect->page_bitmap == FILE_FULL_PAGE_BITMAP;
}

/*
 * file_partsect_is_empty () - Is partial sector empty?
 *
 * return    : True if partial sector is empty, false otherwise.
 * partsect (in) : Partial sector.
 */
STATIC_INLINE bool
file_partsect_is_empty (FILE_PARTIAL_SECTOR * partsect)
{
  return partsect->page_bitmap == FILE_EMPTY_PAGE_BITMAP;
}

/*
 * file_partsect_is_bit_set () - Is bit in partial sector bitmap set at offset?
 *
 * return    : True if bit is set, false otherwise.
 * partsect (in) : Partial sector.
 * offset (in)   : Offset to bit.
 */
STATIC_INLINE bool
file_partsect_is_bit_set (FILE_PARTIAL_SECTOR * partsect, int offset)
{
  return bit64_is_set (partsect->page_bitmap, offset);
}

/*
 * file_partsect_set_bit () - Set bit in partial sector bitmap at offset. The bit is expected to be unset.
 *
 * return    : Void.
 * partsect (in) : Partial sector.
 * offset (in)   : Offset to bit.
 */
STATIC_INLINE void
file_partsect_set_bit (FILE_PARTIAL_SECTOR * partsect, int offset)
{
  assert (!file_partsect_is_bit_set (partsect, offset));

  partsect->page_bitmap = bit64_set (partsect->page_bitmap, offset);
}

/*
 * file_partsect_clear_bit () - Clear bit in partial sector bitmap at offset. The bit is expected to be set.
 *
 * return    : Void.
 * partsect (in) : Partial sector.
 * offset (in)   : Offset to bit.
 */
STATIC_INLINE void
file_partsect_clear_bit (FILE_PARTIAL_SECTOR * partsect, int offset)
{
  assert (file_partsect_is_bit_set (partsect, offset));

  partsect->page_bitmap = bit64_clear (partsect->page_bitmap, offset);
}

/*
 * file_partsect_pageid_to_offset () - Convert pageid to offset in sector bitmap.
 *
 * return    : Offset to bit in bitmap.
 * partsect (in) : Partial sector.
 * pageid (in)   : Page ID.
 */
STATIC_INLINE int
file_partsect_pageid_to_offset (FILE_PARTIAL_SECTOR * partsect, PAGEID pageid)
{
  assert (SECTOR_FROM_PAGEID (pageid) == partsect->vsid.sectid);

  if (SECTOR_FROM_PAGEID (pageid) != partsect->vsid.sectid)
    {
      return -1;
    }

  return (int) (pageid - SECTOR_FIRST_PAGEID (partsect->vsid.sectid));
}

/*
 * file_partsect_alloc () - Try to allocate a page in partial sector.
 *
 * return        : True if allocation was successful. False if the partial sector was actually full.
 * partsect (in/out) : Partial sector to allocate a page from. The allocated page bit is set afterwards.
 * vpid_out (out)    : If not NULL, it outputs the VPID of allocated page.
 * offset_out (out)  : If not NULL, it outputs the allocated page bit offset in bitmap.
 */
STATIC_INLINE bool
file_partsect_alloc (FILE_PARTIAL_SECTOR * partsect, VPID * vpid_out, int *offset_out)
{
  int offset_to_zero = bit64_count_trailing_ones (partsect->page_bitmap);

  if (offset_to_zero >= FILE_ALLOC_BITMAP_NBITS)
    {
      assert (file_partsect_is_full (partsect));
      return false;
    }

  assert (offset_to_zero >= 0);

  file_partsect_set_bit (partsect, offset_to_zero);
  if (offset_out)
    {
      *offset_out = offset_to_zero;
    }

  if (vpid_out)
    {
      vpid_out->volid = partsect->vsid.volid;
      vpid_out->pageid = SECTOR_FIRST_PAGEID (partsect->vsid.sectid) + offset_to_zero;
    }

  return true;
}

/*
 * file_rv_partsect_update () - Set/clear bit in partial sector for recovery.
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 * set (in)  : True if bit should be set, false if bit should be cleared
 */
static int
file_rv_partsect_update (THREAD_ENTRY * thread_p, LOG_RCV * rcv, bool set)
{
  PAGE_PTR page_ftab = rcv->pgptr;
  FILE_PARTIAL_SECTOR *partsect;
  int offset;

  assert (page_ftab != NULL);
  assert (rcv->offset >= 0 && rcv->offset < DB_PAGESIZE);

  offset = *(int *) rcv->data;
  assert (rcv->length == sizeof (offset));

  partsect = (FILE_PARTIAL_SECTOR *) (page_ftab + rcv->offset);
  if (set)
    {
      file_partsect_set_bit (partsect, offset);
    }
  else
    {
      file_partsect_clear_bit (partsect, offset);
    }

  file_log ("file_rv_partsect_update",
        "recovery partial sector update in page %d|%d prev_lsa %lld|%d: "
        "%s bit at offset %d, partial sector offset %d \n"
        FILE_PARTSECT_MSG ("partsect after rcv"),
        PGBUF_PAGE_STATE_ARGS (rcv->pgptr), set ? "set" : "clear", offset, rcv->offset,
        FILE_PARTSECT_AS_ARGS (partsect));

  pgbuf_set_dirty (thread_p, page_ftab, DONT_FREE);
  return NO_ERROR;
}

/*
 * file_rv_partsect_set () - Set bit in partial sector for recovery.
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_partsect_set (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  return file_rv_partsect_update (thread_p, rcv, true);
}

/*
 * file_rv_partsect_clear () - Clear bit in partial sector for recovery.
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_partsect_clear (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  return file_rv_partsect_update (thread_p, rcv, false);
}

/************************************************************************/
/* Utility functions.                                                   */
/************************************************************************/

/*
 * file_compare_vpids () - Compare two page identifiers.
 *
 * return      : 1 if first page is bigger, -1 if first page is smaller and 0 if page ids are equal
 * first (in)  : first page id
 * second (in) : second page id
 *
 * note: we need to ignore FILE_USER_PAGE_MARK_DELETED flag.
 */
static int
file_compare_vpids (const void *first, const void *second)
{
  VPID first_vpid = *(VPID *) first;
  VPID second_vpid = *(VPID *) second;

  if (first_vpid.volid == second_vpid.volid)
    {
      FILE_USER_PAGE_CLEAR_MARK_DELETED (&first_vpid);
      FILE_USER_PAGE_CLEAR_MARK_DELETED (&second_vpid);
      return first_vpid.pageid - second_vpid.pageid;
    }
  else
    {
      return first_vpid.volid - second_vpid.volid;
    }
}

/*
 * file_compare_vfids () - Compare two file identifiers.
 *
 * return      : positive if first file is bigger, negative if first file is smaller and 0 if page ids are equal
 * first (in)  : first file id
 * second (in) : second file id
 *
 * note: we need to ignore FILE_USER_PAGE_MARK_DELETED flag.
 */
static int
file_compare_vfids (const void *first, const void *second)
{
  VFID *first_vfid = (VFID *) first;
  VFID *second_vfid = (VFID *) second;

  if (first_vfid->volid > second_vfid->volid)
    {
      return 1;
    }
  if (first_vfid->volid < second_vfid->volid)
    {
      return -1;
    }
  return first_vfid->fileid - second_vfid->fileid;
}

/*
 * file_compare_track_items () - compare two file tracker items
 *
 * return      : positive if first item is bigger, negative if first item is smaller, 0 if items are equal
 * first (in)  : first item
 * second (in) : second item
 */
static int
file_compare_track_items (const void *first, const void *second)
{
  FILE_TRACK_ITEM *first_item = (FILE_TRACK_ITEM *) first;
  FILE_TRACK_ITEM *second_item = (FILE_TRACK_ITEM *) second;

  if (first_item->volid > second_item->volid)
    {
      return 1;
    }
  if (first_item->volid < second_item->volid)
    {
      return -1;
    }
  return first_item->fileid - second_item->fileid;
}

/*
 * file_type_to_string () - Get a string of the given file type
 *   return: string of the file type
 *   fstruct_type(in): The type of the structure
 */
const char *
file_type_to_string (FILE_TYPE fstruct_type)
{
  switch (fstruct_type)
    {
    case FILE_TRACKER:
      return "TRACKER";
    case FILE_HEAP:
      return "HEAP";
    case FILE_MULTIPAGE_OBJECT_HEAP:
      return "MULTIPAGE_OBJECT_HEAP";
    case FILE_BTREE:
      return "BTREE";
    case FILE_BTREE_OVERFLOW_KEY:
      return "BTREE_OVERFLOW_KEY";
    case FILE_EXTENDIBLE_HASH:
      return "HASH";
    case FILE_EXTENDIBLE_HASH_DIRECTORY:
      return "HASH_DIRECTORY";
    case FILE_CATALOG:
      return "CATALOG";
    case FILE_DROPPED_FILES:
      return "DROPPED FILES";
    case FILE_VACUUM_DATA:
      return "VACUUM DATA";
    case FILE_QUERY_AREA:
      return "QUERY_AREA";
    case FILE_TEMP:
      return "TEMPORARILY";
    case FILE_UNKNOWN_TYPE:
      return "UNKNOWN";
    case FILE_HEAP_REUSE_SLOTS:
      return "HEAP_REUSE_SLOTS";
    }
  return "UNKNOWN";
}

static void
file_print_name_of_class (THREAD_ENTRY * thread_p, FILE * fp, const OID * class_oid_p)
{
  char *class_name_p = NULL;

  if (!OID_ISNULL (class_oid_p))
    {
      if (heap_get_class_name (thread_p, class_oid_p, &class_name_p) != NO_ERROR)
    {
      /* ignore */
      er_clear ();
    }
      fprintf (fp, "CLASS_OID: %5d|%10d|%5d (%s)", OID_AS_ARGS (class_oid_p),
           class_name_p != NULL ? class_name_p : "*UNKNOWN-CLASS*");
      if (class_name_p != NULL)
    {
      free_and_init (class_name_p);
    }
    }
}

/************************************************************************/
/* File manipulation section.                                           */
/************************************************************************/

/*
 * file_create_with_npages () - Create a permanent file big enough to store a number of pages.
 *
 * return     : Error code
 * thread_p (in)  : Thread entry
 * file_type (in) : File type
 * npages (in)    : Number of pages.
 * des (in)   : File descriptor.
 * vfid (out)     : File identifier.
 */
int
file_create_with_npages (THREAD_ENTRY * thread_p, FILE_TYPE file_type, int npages, FILE_DESCRIPTORS * des, VFID * vfid)
{
  FILE_TABLESPACE tablespace;

  assert (file_type != FILE_TEMP);

  assert (npages > 0);

  FILE_TABLESPACE_FOR_PERM_NPAGES (&tablespace, npages);

  return file_create (thread_p, file_type, &tablespace, des, false, false, vfid);
}

/*
 * file_create_heap () - Create heap file (permanent, not numerable)
 *
 * return     : Error code
 * thread_p (in)  : Thread entry
 * reuse_oid (in) : Reuse slots true or false
 * class_oid (in) : Class identifier
 * vfid (out)     : File identifier
 *
 * todo: add tablespace.
 */
int
file_create_heap (THREAD_ENTRY * thread_p, bool reuse_oid, const OID * class_oid, VFID * vfid)
{
  FILE_DESCRIPTORS des;
  FILE_TYPE file_type = reuse_oid ? FILE_HEAP_REUSE_SLOTS : FILE_HEAP;

  assert (class_oid != NULL);

  /* it's done this way because of annoying Valgrind complaints: */
  memset (&des, 0, sizeof (des));
  /* set class_oid here */
  des.heap.class_oid = *class_oid;
  /* hfid will be updated after create */

  return file_create_with_npages (thread_p, file_type, 1, &des, vfid);
}

/*
 * file_create_temp_internal () - common function to create files for temporary purpose. always try to use a cached
 *                                temporary file first. if there is no cached entry, create a new file.
 *                                in the end save the temp cache entry in transaction list of temporary files.
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * npages (in)       : desired number of pages (ignored when taking file from cache)
 * ftype (in)        : file type
 * is_numerable (in) : true if file must be numerable, false if should be regular file
 * vfid_out (out)    : VFID of file (obtained from cache or created).
 */
STATIC_INLINE int
file_create_temp_internal (THREAD_ENTRY * thread_p, int npages, FILE_TYPE ftype, bool is_numerable, VFID * vfid_out)
{
  FILE_TABLESPACE tablespace;
  FILE_TEMPCACHE_ENTRY *tempcache_entry = NULL;
  int error_code = NO_ERROR;

  assert (npages > 0);

  error_code = file_tempcache_get (thread_p, ftype, is_numerable, &tempcache_entry);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }
  assert (tempcache_entry != NULL);
  assert (tempcache_entry->ftype == ftype);

  if (VFID_ISNULL (&tempcache_entry->vfid))
    {
      int tran_index = file_get_tempcache_entry_index (thread_p);
      FILE_TEMPCACHE_TRAN_ENTRY *tran_entry = &file_Tempcache.tran_files[tran_index];

      FILE_TABLESPACE_FOR_TEMP_NPAGES (&tablespace, npages);

      /* file_create() internally calls log_sysop_start().
       * log_sysop_start() uses a transaction-level mutex (rmutex_topop) that does not
       * support concurrent access from multiple worker threads sharing the same transaction. */
      file_tempcache_lock_tran_entry (tran_entry);
      error_code = file_create (thread_p, ftype, &tablespace, NULL, true, is_numerable, vfid_out);
      file_tempcache_unlock_tran_entry (tran_entry);

      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();

      file_tempcache_retire_entry (tempcache_entry);
      return error_code;
    }

      tempcache_entry->vfid = *vfid_out;
    }
  else
    {
      /* we use the cached file */
      /* what about the number of pages? */
      *vfid_out = tempcache_entry->vfid;
    }

  /* save to transaction temporary file list */
  file_tempcache_push_tran_file (thread_p, tempcache_entry);
  return NO_ERROR;
}

/*
 * file_create_temp () - Create a temporary file.
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * npages (in)   : Number of pages
 * vfid (out)    : File identifier
 */
int
file_create_temp (THREAD_ENTRY * thread_p, int npages, VFID * vfid)
{
  return file_create_temp_internal (thread_p, npages, FILE_TEMP, false, vfid);
}

/*
 * file_create_temp_numerable () - Create a temporary file with numerable property.
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * npages (in)   : Number of pages
 * vfid (out)    : File identifier
 */
int
file_create_temp_numerable (THREAD_ENTRY * thread_p, int npages, VFID * vfid)
{
  return file_create_temp_internal (thread_p, npages, FILE_TEMP, true, vfid);
}

/*
 * file_create_query_area () - Create a query area file (temporary, not numerable).
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * vfid (out)    : File identifier
 */
int
file_create_query_area (THREAD_ENTRY * thread_p, VFID * vfid)
{
  return file_create_temp_internal (thread_p, 1, FILE_QUERY_AREA, false, vfid);
}

/*
 * file_create_ehash () - Create a permanent or temporary file for extensible hash table. This file will have the
 *            numerable property.
 *
 * return     : Error code
 * thread_p (in)  : Thread entry
 * npages (in)    : Number of pages
 * is_tmp (in)    : True if is temporary, false if is permanent
 * des_ehash (in) : Extensible hash descriptor.
 * vfid (out)     : File identifier.
 */
int
file_create_ehash (THREAD_ENTRY * thread_p, int npages, bool is_tmp, FILE_EHASH_DES * des_ehash, VFID * vfid)
{
  FILE_TABLESPACE tablespace;

  assert (npages > 0);

  /* todo: use temporary file cache? */

  FILE_TABLESPACE_FOR_TEMP_NPAGES (&tablespace, npages);
  return file_create (thread_p, FILE_EXTENDIBLE_HASH, &tablespace, (FILE_DESCRIPTORS *) des_ehash, is_tmp, true, vfid);
}

/*
 * file_create_ehash_dir () - Create a permanent or temporary file for extensible hash directory. This file will have
 *                the numerable property.
 *
 * return     : Error code
 * thread_p (in)  : Thread entry
 * npages (in)    : Number of pages
 * is_tmp (in)    : True if is temporary, false if is permanent
 * des_ehash (in) : Extensible hash descriptor.
 * vfid (out)     : File identifier.
 */
int
file_create_ehash_dir (THREAD_ENTRY * thread_p, int npages, bool is_tmp, FILE_EHASH_DES * des_ehash, VFID * vfid)
{
  FILE_TABLESPACE tablespace;

  assert (npages > 0);

  /* todo: use temporary file cache? */

  FILE_TABLESPACE_FOR_TEMP_NPAGES (&tablespace, npages);
  return file_create (thread_p, FILE_EXTENDIBLE_HASH_DIRECTORY, &tablespace, (FILE_DESCRIPTORS *) des_ehash, is_tmp,
              true, vfid);
}

/*
 * file_create () - Create a new file.
 *
 * return        : Error code.
 * thread_p (in)     : Thread entry.
 * file_type (in)    : File type.
 * tablespace (in)   : File table space.
 * des (in)      : File descriptor (based on file type).
 * is_temp (in)      : True if file should be temporary.
 * is_numerable (in) : True if file should be numerable.
 * vfid (out)        : Output new file identifier.
 */
int
file_create (THREAD_ENTRY * thread_p, FILE_TYPE file_type,
         FILE_TABLESPACE * tablespace, FILE_DESCRIPTORS * des, bool is_temp, bool is_numerable, VFID * vfid)
{
  INT64 total_size;
  int n_sectors;
  VSID *vsids_reserved = NULL;
  bool was_temp_reserved = false;
  DB_VOLPURPOSE volpurpose = DISK_UNKNOWN_PURPOSE;
  VSID *vsid_iter = NULL;
  INT16 size = 0;
  VOLID volid_last_expand;

  /* File header vars */
  VPID vpid_fhead = VPID_INITIALIZER;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;

  /* File table vars */
  INT64 max_size_ftab;
  FILE_PARTIAL_SECTOR *partsect_ftab = NULL;
  VPID vpid_ftab = VPID_INITIALIZER;
  PAGE_PTR page_ftab = NULL;
  bool found_vfid_page = false;
  INT16 offset_ftab = 0;

  /* Partial table. */
  FILE_PARTIAL_SECTOR partsect;

  /* File extensible data */
  FILE_EXTENSIBLE_DATA *extdata_part_ftab = NULL;
  FILE_EXTENSIBLE_DATA *extdata_part_ftab_in_fhead = NULL;
  FILE_EXTENSIBLE_DATA *extdata_full_ftab = NULL;
  FILE_EXTENSIBLE_DATA *extdata_user_page_ftab = NULL;

  /* Recovery */
  bool is_sysop_started = false;
  bool do_logging = !is_temp;

  int error_code = NO_ERROR;

  assert (tablespace->initial_size > 0);
  assert (file_type != FILE_TEMP || is_temp);
  assert (vfid != NULL);

  /* Estimate the required size including file header & tables. */
  total_size = tablespace->initial_size;

  if (!is_numerable)
    {
      /* Partial & full sectors tables.
       * The worst case is when all sectors are partially allocated. The required size will be sizeof (SECTOR_ID) and
       * the size of bitmap (in bytes), which counts to 16 bytes, for each sector. The sector size will be at least
       * 4k * 64 => 256KB. So we need to increase the total size of file with 16 bytes per every 256KB, or 1 byte for
       * each 16KB. Then we need again to consider the additional space used by table (another 1 byte for 16KB x 16 KB).
       * This of course translates to an infinite series and we want to simplify that. So, adding 1 byte for each 8KB
       * from the start should cover any space required by file tables.
       */
      max_size_ftab = total_size / 8 / 1024;
      total_size += max_size_ftab;
    }
  else
    {
      /* Partial & full sectors tables + page table.
       * By applying the same logic above, we consider the worst case (which is impossible actually) all sectors are
       * partially allocated (16 bytes per sector) and all pages are allocated by user (64 * 8 byte per sector). This
       * totals to 528 bytes for each 256KB bytes of data. By doubling the estimated size of tables, we need an extra
       * 1 byte for each 256KB / 528 / 2, which is equivalent to 1 byte for each 8/33 KB.
       */
      max_size_ftab = total_size * 33 / 8 / 1024;
      total_size += max_size_ftab;
    }

  /* convert the disk size to number of sectors. */
  n_sectors = (int) CEIL_PTVDIV (total_size, DB_SECTORSIZE);
  assert (n_sectors > 0);
  /* allocate a buffer to store all reserved sectors */
  vsids_reserved = (VSID *) db_private_alloc (thread_p, n_sectors * sizeof (VSID));
  if (vsids_reserved == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, n_sectors * sizeof (VSID));
      error_code = ER_OUT_OF_VIRTUAL_MEMORY;
      goto exit;
    }

  if (do_logging)
    {
      /* start a system operation */
      log_sysop_start (thread_p);
      is_sysop_started = true;
    }

  /* reserve sectors on disk */
  volpurpose = is_temp ? DB_TEMPORARY_DATA_PURPOSE : DB_PERMANENT_DATA_PURPOSE;

  file_log ("file_create",
        "create %s file \n\t%s \n\t%s \n" FILE_TABLESPACE_MSG
        " \tnsectors = %d", file_type_to_string (file_type),
        FILE_PERM_TEMP_STRING (is_temp),
        FILE_NUMERABLE_REGULAR_STRING (is_numerable), FILE_TABLESPACE_AS_ARGS (tablespace), n_sectors);

  error_code = disk_reserve_sectors (thread_p, volpurpose, NULL_VOLID, n_sectors, vsids_reserved);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
  /* found enough sectors to reserve */
  was_temp_reserved = is_temp;

  /* sort sectors by VSID. but before sorting, remember last volume ID used for reservations. */
  volid_last_expand = vsids_reserved[n_sectors - 1].volid;
  qsort (vsids_reserved, n_sectors, sizeof (VSID), disk_compare_vsids);

  /* decide on what page to use as file header page (which is going to decide the VFID also). */
#if defined (SERVER_MODE)
  if (file_type == FILE_BTREE || file_type == FILE_HEAP || file_type == FILE_HEAP_REUSE_SLOTS)
    {
      /* we need to consider dropped files in vacuum's list. If we create a file with a duplicate VFID, we can run
       * into problems. */
      VSID *vsid_iter = vsids_reserved;
      VFID vfid_iter;
      VFID found_vfid = VFID_INITIALIZER;
      MVCCID tran_mvccid = logtb_get_current_mvccid (thread_p);
      /* we really have to have the VFID in the same volume as the first allocated page. this means we cannot change
       * volume when we look for a valid VFID. */
      VOLID first_volid = vsids_reserved[0].volid;
      bool is_file_dropped;

      for (vsid_iter = vsids_reserved;
       vsid_iter < vsids_reserved + n_sectors && VFID_ISNULL (&found_vfid)
       && vsid_iter->volid == first_volid; vsid_iter++)
    {
      vfid_iter.volid = vsid_iter->volid;
      for (vfid_iter.fileid = SECTOR_FIRST_PAGEID (vsid_iter->sectid);
           vfid_iter.fileid <= SECTOR_LAST_PAGEID (vsid_iter->sectid); vfid_iter.fileid++)
        {
          error_code = vacuum_is_file_dropped (thread_p, &is_file_dropped, &vfid_iter, tran_mvccid);
          if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          goto exit;
        }

          if (is_file_dropped == false)
        {
          /* Good we found a file ID that is not considered dropped. */
          found_vfid = vfid_iter;
          break;
        }
        }
    }
      if (VFID_ISNULL (&found_vfid))
    {
      /* this is ridiculous. */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }
      *vfid = found_vfid;
    }
  else
#endif /* SERVER_MODE */
    {
      vfid->volid = vsids_reserved->volid;
      vfid->fileid = SECTOR_FIRST_PAGEID (vsids_reserved->sectid);
    }

  assert (!VFID_ISNULL (vfid));
  vpid_fhead.volid = vfid->volid;
  vpid_fhead.pageid = vfid->fileid;

  file_log ("file_create", "chose VFID = %d|%d.", VFID_AS_ARGS (vfid));

  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, NEW_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }

  memset (page_fhead, 0, DB_PAGESIZE);
  pgbuf_set_page_ptype (thread_p, page_fhead, PAGE_FTAB);
  fhead = (FILE_HEADER *) page_fhead;

  /* initialize header */
  fhead->self = *vfid;
  fhead->tablespace = *tablespace;
  if (des != NULL)
    {
      fhead->descriptor = *des;
    }
  fhead->time_creation = time (NULL);
  fhead->type = file_type;
  fhead->file_flags = 0;
  if (is_numerable)
    {
      fhead->file_flags |= FILE_FLAG_NUMERABLE;
    }
  if (is_temp)
    {
      fhead->file_flags |= FILE_FLAG_TEMPORARY;
    }

  fhead->volid_last_expand = volid_last_expand;
  VPID_SET_NULL (&fhead->vpid_last_temp_alloc);
  fhead->offset_to_last_temp_alloc = NULL_OFFSET;
  VPID_SET_NULL (&fhead->vpid_last_user_page_ftab);
  VPID_SET_NULL (&fhead->vpid_find_nth_last);
  fhead->first_index_find_nth_last = 0;
  VPID_SET_NULL (&fhead->vpid_sticky_first);

  fhead->n_page_total = 0;
  fhead->n_page_user = 0;
  fhead->n_page_ftab = 1;   /* file header */
  fhead->n_page_free = 0;
  fhead->n_page_mark_delete = 0;

  fhead->n_sector_total = 0;
  fhead->n_sector_partial = 0;
  fhead->n_sector_full = 0;
  fhead->n_sector_empty = 0;

  fhead->offset_to_partial_ftab = NULL_OFFSET;
  fhead->offset_to_full_ftab = NULL_OFFSET;
  fhead->offset_to_user_page_ftab = NULL_OFFSET;

  fhead->reserved0 = 0;
  fhead->reserved1 = 0;
  fhead->reserved2 = 0;
  fhead->reserved3 = 0;

  /* start with a negative empty sector (because we have allocated header). */
  fhead->n_sector_empty--;

  /* start creating required file tables.
   * file tables depend of the properties of permanent/temporary and numerable.
   * temporary files do not use full table, only partial table. permanent files use both partial and full tables.
   * numerable files (which can be both permanent or temporary) also require user page table.
   */
  offset_ftab = FILE_HEADER_ALIGNED_SIZE;
  if (is_numerable)
    {
      if (is_temp)
    {
      /* split the header page space into: 1/16 for partial table and 15/16 for user page table */
      fhead->offset_to_partial_ftab = offset_ftab;
      assert ((INT16) DB_ALIGN (fhead->offset_to_partial_ftab, MAX_ALIGNMENT) == fhead->offset_to_partial_ftab);
      size = (DB_PAGESIZE - offset_ftab) / 16;
      FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
      file_extdata_init (sizeof (FILE_PARTIAL_SECTOR), size, extdata_part_ftab);

      offset_ftab += file_extdata_max_size (extdata_part_ftab);
    }
      else
    {
      /* split the header space into three: 1/32 for each partial and full sector tables and the rest (15/16) for
       * user page table. */

      /* partial table. */
      fhead->offset_to_partial_ftab = offset_ftab;
      assert ((INT16) DB_ALIGN (fhead->offset_to_partial_ftab, MAX_ALIGNMENT) == fhead->offset_to_partial_ftab);
      size = (DB_PAGESIZE - offset_ftab) / 32;
      FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
      file_extdata_init (sizeof (FILE_PARTIAL_SECTOR), size, extdata_part_ftab);

      /* full table. */
      offset_ftab += file_extdata_max_size (extdata_part_ftab);
      fhead->offset_to_full_ftab = offset_ftab;
      assert ((INT16) DB_ALIGN (fhead->offset_to_full_ftab, MAX_ALIGNMENT) == fhead->offset_to_full_ftab);
      FILE_HEADER_GET_FULL_FTAB (fhead, extdata_full_ftab);
      file_extdata_init (sizeof (VSID), size, extdata_full_ftab);

      offset_ftab += file_extdata_max_size (extdata_full_ftab);
    }

      /* user page table - consume remaining space. */
      fhead->offset_to_user_page_ftab = offset_ftab;
      assert ((INT16) DB_ALIGN (fhead->offset_to_user_page_ftab, MAX_ALIGNMENT) == fhead->offset_to_user_page_ftab);
      size = DB_PAGESIZE - offset_ftab;
      FILE_HEADER_GET_USER_PAGE_FTAB (fhead, extdata_user_page_ftab);
      file_extdata_init (sizeof (VPID), size, extdata_user_page_ftab);
    }
  else
    {
      if (is_temp)
    {
      /* keep only partial table. */
      fhead->offset_to_partial_ftab = offset_ftab;
      assert ((INT16) DB_ALIGN (fhead->offset_to_partial_ftab, MAX_ALIGNMENT) == fhead->offset_to_partial_ftab);
      size = DB_PAGESIZE - offset_ftab;
      FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
      file_extdata_init (sizeof (FILE_PARTIAL_SECTOR), size, extdata_part_ftab);
    }
      else
    {
      /* split the header space into two: half for partial table and half for full table. */

      /* partial table. */
      fhead->offset_to_partial_ftab = offset_ftab;
      assert ((INT16) DB_ALIGN (fhead->offset_to_partial_ftab, MAX_ALIGNMENT) == fhead->offset_to_partial_ftab);
      size = (DB_PAGESIZE - offset_ftab) / 2;
      FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
      file_extdata_init (sizeof (FILE_PARTIAL_SECTOR), size, extdata_part_ftab);

      /* full table. */
      offset_ftab += file_extdata_max_size (extdata_part_ftab);
      fhead->offset_to_full_ftab = offset_ftab;
      assert ((INT16) DB_ALIGN (fhead->offset_to_full_ftab, MAX_ALIGNMENT) == fhead->offset_to_full_ftab);
      size = DB_PAGESIZE - offset_ftab;
      FILE_HEADER_GET_FULL_FTAB (fhead, extdata_full_ftab);
      file_extdata_init (sizeof (VSID), size, extdata_full_ftab);
    }
    }
  /* all required tables are created */
  /* all files must have partial table */
  assert (fhead->offset_to_partial_ftab != NULL_OFFSET);

  /* start populating partial table */
  /* partial sectors are initially added to the table in header. if the file is really big, and the table needs to
   * extend on other pages, we will keep track of pages/sectors used for file table using extdata_part_ftab.
   */
  FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
  extdata_part_ftab_in_fhead = extdata_part_ftab;
  for (vsid_iter = vsids_reserved; vsid_iter < vsids_reserved + n_sectors; vsid_iter++)
    {
      if (file_extdata_is_full (extdata_part_ftab))
    {
      /* a new page for file table is required */

      /* allocate a page from partial table. */
      if (partsect_ftab == NULL)
        {
          /* This is first page. */
          assert (!file_extdata_is_empty (extdata_part_ftab));
          partsect_ftab = (FILE_PARTIAL_SECTOR *) file_extdata_start (extdata_part_ftab);
          vpid_ftab.pageid = SECTOR_FIRST_PAGEID (partsect_ftab->vsid.sectid);
          vpid_ftab.volid = partsect_ftab->vsid.volid;

          if (!VSID_IS_SECTOR_OF_VPID (&partsect_ftab->vsid, &vpid_fhead))
        {
          /* another non-empty sector. */
          fhead->n_sector_empty--;
        }
          else
        {
          /* this non-empty sector was already counted */
        }
        }
      else if (file_partsect_is_full (partsect_ftab))
        {
          /* move to next partial sector. */
          partsect_ftab++;
          if ((void *) partsect_ftab >= file_extdata_end (extdata_part_ftab_in_fhead))
        {
          /* This is not possible! */
          assert_release (false);
          error_code = ER_FAILED;
          goto exit;
        }
          vpid_ftab.pageid = SECTOR_FIRST_PAGEID (partsect_ftab->vsid.sectid);
          vpid_ftab.volid = partsect_ftab->vsid.volid;

          if (!VSID_IS_SECTOR_OF_VPID (&partsect_ftab->vsid, &vpid_fhead))
        {
          /* another non-empty sector. */
          fhead->n_sector_empty--;
        }
          else
        {
          /* this non-empty sector was already counted */
        }

          fhead->n_sector_full++;
        }
      else
        {
          vpid_ftab.pageid++;
        }

      if (VPID_EQ (&vpid_fhead, &vpid_ftab))
        {
          /* Go to next page. This can't be last page in sector, because the sector bitmap would have been full */
          assert (file_partsect_is_bit_set (partsect_ftab,
                        file_partsect_pageid_to_offset (partsect_ftab, vpid_fhead.pageid)));
          vpid_ftab.pageid++;
          found_vfid_page = true;
        }
      /* Set bit in sector bitmap */
      file_partsect_set_bit (partsect_ftab, file_partsect_pageid_to_offset (partsect_ftab, vpid_ftab.pageid));

      /* Save link in previous page. */
      extdata_part_ftab->vpid_next = vpid_ftab;
      if (page_ftab != NULL)
        {
          if (do_logging)
        {
          pgbuf_log_new_page (thread_p, page_ftab, file_extdata_size (extdata_part_ftab), PAGE_FTAB);
        }
          pgbuf_set_dirty (thread_p, page_ftab, FREE);
        }
      page_ftab = pgbuf_fix (thread_p, &vpid_ftab, NEW_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_ftab == NULL)
        {
          ASSERT_ERROR_AND_SET (error_code);
          goto exit;
        }
      pgbuf_set_page_ptype (thread_p, page_ftab, PAGE_FTAB);
      memset (page_ftab, 0, DB_PAGESIZE);
      extdata_part_ftab = (FILE_EXTENSIBLE_DATA *) page_ftab;
      file_extdata_init (sizeof (FILE_PARTIAL_SECTOR), DB_PAGESIZE, extdata_part_ftab);

      fhead->n_page_ftab++;
    }
      assert (!file_extdata_is_full (extdata_part_ftab));

      partsect.vsid = *vsid_iter;
      partsect.page_bitmap = FILE_EMPTY_PAGE_BITMAP;
      if (partsect.vsid.sectid == SECTOR_FROM_PAGEID (vpid_fhead.pageid) && partsect.vsid.volid == vpid_fhead.volid)
    {
      /* Set bit for file header page. */
      file_partsect_set_bit (&partsect, file_partsect_pageid_to_offset (&partsect, vpid_fhead.pageid));
    }
      file_extdata_append (extdata_part_ftab, &partsect);
    }

  if (page_ftab != NULL)
    {
      if (do_logging)
    {
      pgbuf_log_new_page (thread_p, page_ftab, file_extdata_size (extdata_part_ftab), PAGE_FTAB);
      pgbuf_unfix_and_init (thread_p, page_ftab);
    }
      else
    {
      pgbuf_set_dirty_and_free (thread_p, page_ftab);
    }
    }

  if (partsect_ftab == NULL)
    {
      /* all partial sectors were fitted in header page */
      assert (fhead->n_sector_full == 0);
    }
  else
    {
      if (file_partsect_is_full (partsect_ftab))
    {
      partsect_ftab++;
      fhead->n_sector_full++;
    }
      if (!is_temp && fhead->n_sector_full > 0)
    {
      /* move sectors fully used by file table to full table */
      int i;
      FILE_PARTIAL_SECTOR *partsect_iter;
      FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
      FILE_HEADER_GET_FULL_FTAB (fhead, extdata_full_ftab);

      for (i = 0; i < fhead->n_sector_full; i++)
        {
          partsect_iter = (FILE_PARTIAL_SECTOR *) file_extdata_at (extdata_part_ftab, i);
          if (file_extdata_is_full (extdata_full_ftab))
        {
          /* Not possible. */
          assert_release (false);
          error_code = ER_FAILED;
          goto exit;
        }
          file_extdata_append (extdata_full_ftab, &partsect_iter->vsid);
        }
      /* Remove full sectors from partial table. */
      file_extdata_remove_at (extdata_part_ftab, 0, fhead->n_sector_full);
      assert (fhead->n_sector_full == file_extdata_item_count (extdata_full_ftab));
    }
    }

  if (is_temp)
    {
      /* temporary files do not keep separate tables for partial and full sectors. just set last allocation location. */
      VPID_COPY (&fhead->vpid_last_temp_alloc, &vpid_fhead);
      fhead->offset_to_last_temp_alloc = fhead->n_sector_full;
    }

  if (is_numerable)
    {
      /* set last user page table VPID to header */
      fhead->vpid_last_user_page_ftab = vpid_fhead;
      fhead->vpid_find_nth_last = vpid_fhead;
    }

  /* set all stats */
  /* sector stats; full stats already counted, empty stats are negative (we need to add partial sectors) */
  fhead->n_sector_total = n_sectors;
  fhead->n_sector_partial = fhead->n_sector_total - fhead->n_sector_full;
  fhead->n_sector_empty += fhead->n_sector_partial;
  /* page stats; file table pages already counted; user pages remain 0 */
  fhead->n_page_total = fhead->n_sector_total * DISK_SECTOR_NPAGES;
  fhead->n_page_free = fhead->n_page_total - fhead->n_page_ftab;

  /* File header ready. */
  file_header_sanity_check (thread_p, fhead);

  file_log ("file_create", "finished creating file. \n" FILE_HEAD_FULL_MSG, FILE_HEAD_FULL_AS_ARGS (fhead));

  if (do_logging)
    {
      pgbuf_log_new_page (thread_p, page_fhead, DB_PAGESIZE, PAGE_FTAB);
      pgbuf_unfix_and_init (thread_p, page_fhead);
    }
  else
    {
      pgbuf_set_dirty_and_free (thread_p, page_fhead);
    }

  if (!is_temp && file_type != FILE_TRACKER)
    {
      /* add to tracker */
      error_code = file_tracker_register (thread_p, vfid, file_type, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
    }

  if (is_temp)
    {
      /* update stats */
      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.nfile, 1);
      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_ftab, fhead->n_page_ftab);
      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_user, fhead->n_page_user);
      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_reserved, fhead->n_page_free);
    }

  /* Fall through to exit */
exit:

  if (page_ftab != NULL)
    {
      pgbuf_unfix (thread_p, page_ftab);
    }
  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }

  if (is_sysop_started)
    {
      assert (do_logging);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      log_sysop_abort (thread_p);
    }
      else
    {
      log_sysop_end_logical_undo (thread_p, RVFL_DESTROY, NULL, sizeof (*vfid), (char *) vfid);
    }
    }

  if (error_code != NO_ERROR)
    {
      /* make sure we don't output a bad VFID. */
      VFID_SET_NULL (vfid);

      if (was_temp_reserved)
    {
      /* recovery won't free reserved sectors. we have to manually handle the unreserve */
      bool save_check_interrupt = logtb_set_check_interrupt (thread_p, false);

      /* make sure sectors are sorted */
      qsort (vsids_reserved, n_sectors, sizeof (VSID), disk_compare_vsids);
      if (disk_unreserve_ordered_sectors (thread_p, DB_TEMPORARY_DATA_PURPOSE, n_sectors, vsids_reserved)
          != NO_ERROR)
        {
          /* sectors are leaked */
          assert_release (false);
          /* fall through */
        }
      (void) logtb_set_check_interrupt (thread_p, save_check_interrupt);
    }
    }

  if (vsids_reserved != NULL)
    {
      /* Deallocate reserved_sectors */
      db_private_free (thread_p, vsids_reserved);
    }

  return error_code;
}

/*
 * file_table_collect_vsid () - Function callable by file_extdata_apply_funcs. Used to collect sector ID's from file
 *              tables.
 *
 * return        : NO_ERROR
 * thread_p (in)     : Thread entry
 * item (in)         : Item in extensible data (VSID or FILE_PARTIAL_SECTOR which starts with a VSID)
 * index_unused (in) : Unused
 * stop (out)        : Unused
 * args (in/out)     : VSID collector
 */
static int
file_table_collect_vsid (THREAD_ENTRY * thread_p, const void *item, int index_unused, bool * stop, void *args)
{
  const VSID *vsid = (VSID *) item;
  FILE_VSID_COLLECTOR *collector = (FILE_VSID_COLLECTOR *) args;

  collector->vsids[collector->n_vsids++] = *vsid;

  return NO_ERROR;
}

/*
 * file_table_collect_all_vsids () - collect all sectors from file table
 *
 * return              : error code
 * thread_p (in)       : thread entry
 * page_fhead (in)     : file header page
 * collector_out (out) : output VSID collector
 */
STATIC_INLINE int
file_table_collect_all_vsids (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, FILE_VSID_COLLECTOR * collector_out)
{
  FILE_HEADER *fhead;
  FILE_EXTENSIBLE_DATA *extdata_ftab;
  int error_code = NO_ERROR;

  fhead = (FILE_HEADER *) page_fhead;

  collector_out->vsids = (VSID *) db_private_alloc (thread_p, fhead->n_sector_total * sizeof (VSID));
  if (collector_out->vsids == NULL)
    {
      error_code = ER_OUT_OF_VIRTUAL_MEMORY;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, error_code, 1, fhead->n_sector_total * sizeof (VSID));
      return error_code;
    }
  collector_out->n_vsids = 0;

  /* Collect from partial table */
  FILE_HEADER_GET_PART_FTAB (fhead, extdata_ftab);
  error_code = file_extdata_apply_funcs (thread_p, extdata_ftab, NULL, NULL, file_table_collect_vsid, collector_out,
                     false, NULL, NULL);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      db_private_free_and_init (thread_p, collector_out->vsids);
      return error_code;
    }

  if (!FILE_IS_TEMPORARY (fhead))
    {
      /* Collect from full table. */
      FILE_HEADER_GET_FULL_FTAB (fhead, extdata_ftab);
      error_code = file_extdata_apply_funcs (thread_p, extdata_ftab, NULL, NULL, file_table_collect_vsid,
                         collector_out, false, NULL, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      db_private_free_and_init (thread_p, collector_out->vsids);
      return error_code;
    }
    }

  if (collector_out->n_vsids != fhead->n_sector_total)
    {
      assert_release (false);
      db_private_free_and_init (thread_p, collector_out->vsids);
      return ER_FAILED;
    }

  qsort (collector_out->vsids, fhead->n_sector_total, sizeof (VSID), disk_compare_vsids);

  return NO_ERROR;
}

/*
 * file_sector_map_dealloc () - FILE_EXTDATA_ITEM_FUNC to deallocate user pages
 *
 * return        : error code
 * thread_p (in) : thread entry
 * data (in)     : FILE_PARTIAL_SECTOR * or VSID *
 * index (in)    : unused
 * stop (in)     : unused
 * args (in)     : is_partial
 */
static int
file_sector_map_dealloc (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args)
{
  bool is_partial = *(bool *) args;
  FILE_PARTIAL_SECTOR partsect = FILE_PARTIAL_SECTOR_INITIALIZER;
  int offset = 0;
  VPID vpid;
  PAGE_PTR page = NULL;
  int error_code = NO_ERROR;

  if (is_partial)
    {
      partsect = *(FILE_PARTIAL_SECTOR *) data;
    }
  else
    {
      partsect.vsid = *(VSID *) data;
    }

  vpid.volid = partsect.vsid.volid;
  for (offset = 0, vpid.pageid = SECTOR_FIRST_PAGEID (partsect.vsid.sectid); offset < DISK_SECTOR_NPAGES;
       offset++, vpid.pageid++)
    {
      if (is_partial && !file_partsect_is_bit_set (&partsect, offset))
    {
      /* not allocated */
      continue;
    }

      page = pgbuf_fix (thread_p, &vpid, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

      if (pgbuf_get_page_ptype (thread_p, page) == PAGE_FTAB)
    {
      /* table page, do not deallocate yet */
      pgbuf_unfix_and_init (thread_p, page);
      continue;
    }
      pgbuf_dealloc_page (thread_p, page);
      page = NULL;
    }

  return NO_ERROR;
}

/*
 * file_sector_map_dealloc_temp () - FILE_EXTDATA_ITEM_FUNC to dealloc user pages of temp table
 *
 * return        : error code
 * thread_p (in) : thread entry
 * data (in)     : FILE_PARTIAL_SECTOR * or VSID *
 * index (in)    : unused
 * stop (in)     : unused
 * args (in)     : is_partial
 */
static int
file_sector_map_dealloc_temp (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args)
{
  bool is_partial = *(bool *) args;
  FILE_PARTIAL_SECTOR partsect = FILE_PARTIAL_SECTOR_INITIALIZER;
  int offset = 0;
  VPID vpid;
  PAGE_PTR page = NULL;
  int error_code = NO_ERROR;

  if (is_partial)
    {
      partsect = *(FILE_PARTIAL_SECTOR *) data;
    }
  else
    {
      partsect.vsid = *(VSID *) data;
    }

  vpid.volid = partsect.vsid.volid;
  for (offset = 0, vpid.pageid = SECTOR_FIRST_PAGEID (partsect.vsid.sectid); offset < DISK_SECTOR_NPAGES;
       offset++, vpid.pageid++)
    {
      if (is_partial && !file_partsect_is_bit_set (&partsect, offset))
    {
      /* not allocated */
      continue;
    }

      page = pgbuf_simple_fix (thread_p, &vpid, false);
      if (page == NULL)
    {
      /* don't care about page which is not on page buffer. */
      continue;
    }

      if (pgbuf_get_page_ptype (thread_p, page) == PAGE_FTAB)
    {
      /* table page, do not invalidate yet */
      pgbuf_simple_unfix (thread_p, page);
      page = NULL;
      continue;
    }

      if (pgbuf_dealloc_temp_page (thread_p, page, true) != NO_ERROR)
    {
      return ER_FAILED;
    }
      page = NULL;
    }

  return NO_ERROR;
}

/*
 * file_destroy () - Destroy file - unreserve all sectors used by file on disk.
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * vfid (in)     : File identifier
 * is_temp (in)  : True for temporary, false otherwise. Caller must know. It relevant information before fixing the file
 *                 header page, whe, if not temporary, we need to remove from file tracker.
 */
int
file_destroy (THREAD_ENTRY * thread_p, const VFID * vfid, bool is_temp)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  FILE_VSID_COLLECTOR vsid_collector;
  FILE_FTAB_COLLECTOR ftab_collector;
  DB_VOLPURPOSE volpurpose;
  bool save_check_interrupt = false;
  int error_code = NO_ERROR;
  FILE_EXTENSIBLE_DATA *extdata_ftab = NULL;
  bool is_partial;
  int iter_sects;
  int offset;
  VPID vpid_ftab;
  PAGE_PTR page_ftab = NULL;

  assert (vfid != NULL && !VFID_ISNULL (vfid));

  vsid_collector.vsids = NULL;

  if (is_temp)
    {
      /* do not interrupt destroying temporary files. it will leak pages. */
      save_check_interrupt = logtb_set_check_interrupt (thread_p, false);
    }
  else
    {
      /* permanent files are first removed from tracker */
      error_code = file_tracker_unregister (thread_p, vfid);
      if (error_code != NO_ERROR)
    {
      assert_release (false);
      goto exit;
    }
    }

  ftab_collector.partsect_ftab = NULL;

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      assert_release (!is_temp);
      error_code = ER_FAILED;
      goto exit;
    }

  fhead = (FILE_HEADER *) page_fhead;

#if !defined(NDEBUG)
  if (file_get_tde_algorithm_internal (fhead) != TDE_ALGORITHM_NONE)
    {
      tde_er_log
    ("file_destroy(): clear tde bit in pflag in all user pages, VFID = %d|%d, # of encrypting (user) pages = %d, tde algorithm = %s\n",
     VFID_AS_ARGS (&fhead->self), fhead->n_page_user,
     tde_get_algorithm_name (file_get_tde_algorithm_internal (fhead)));
    }
#endif /* !NDEBUG */

  assert (is_temp == FILE_IS_TEMPORARY (fhead));
  assert (FILE_IS_TEMPORARY (fhead) || log_check_system_op_is_started (thread_p));

  error_code = file_table_collect_all_vsids (thread_p, page_fhead, &vsid_collector);
  if (error_code != NO_ERROR)
    {
      assert_release (false);
      goto exit;
    }
  volpurpose = FILE_IS_TEMPORARY (fhead) ? DB_TEMPORARY_DATA_PURPOSE : DB_PERMANENT_DATA_PURPOSE;

  file_log ("file_destroy",
        "file %d|%d unreserve %d sectors \n" FILE_HEAD_FULL_MSG,
        VFID_AS_ARGS (vfid), fhead->n_sector_total, FILE_HEAD_FULL_AS_ARGS (fhead));

  if (!FILE_IS_TEMPORARY (fhead))
    {
      /* we need to deallocate pages */
      ftab_collector.npages = 0;
      ftab_collector.nsects = 0;
      ftab_collector.partsect_ftab =
    (FILE_PARTIAL_SECTOR *) db_private_alloc (thread_p, fhead->n_page_ftab * sizeof (FILE_PARTIAL_SECTOR));
      if (ftab_collector.partsect_ftab == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1,
          fhead->n_page_ftab * sizeof (FILE_PARTIAL_SECTOR));
      error_code = ER_OUT_OF_VIRTUAL_MEMORY;
      goto exit;
    }

      FILE_HEADER_GET_PART_FTAB (fhead, extdata_ftab);
      is_partial = true;
      error_code =
    file_extdata_apply_funcs (thread_p, extdata_ftab, file_extdata_collect_ftab_pages, &ftab_collector,
                  file_sector_map_dealloc, &is_partial, true, NULL, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

      FILE_HEADER_GET_FULL_FTAB (fhead, extdata_ftab);
      is_partial = false;
      error_code =
    file_extdata_apply_funcs (thread_p, extdata_ftab, file_extdata_collect_ftab_pages, &ftab_collector,
                  file_sector_map_dealloc, &is_partial, true, NULL, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

      /* deallocate table pages - other than header page */
      for (iter_sects = 0; iter_sects < ftab_collector.nsects; iter_sects++)
    {
      vpid_ftab.volid = ftab_collector.partsect_ftab[iter_sects].vsid.volid;
      for (offset = 0,
           vpid_ftab.pageid = SECTOR_FIRST_PAGEID (ftab_collector.partsect_ftab[iter_sects].vsid.sectid);
           offset < DISK_SECTOR_NPAGES; offset++, vpid_ftab.pageid++)
        {
          if (file_partsect_is_bit_set (&ftab_collector.partsect_ftab[iter_sects], offset))
        {
          page_ftab = pgbuf_fix (thread_p, &vpid_ftab, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
          if (page_ftab == NULL)
            {
              ASSERT_ERROR_AND_SET (error_code);
              goto exit;
            }
          pgbuf_dealloc_page (thread_p, page_ftab);
          page_ftab = NULL;
        }
        }
    }
      /* deallocate header page */
      pgbuf_dealloc_page (thread_p, page_fhead);
      page_fhead = NULL;
    }
  else
    {
      /* invalidate pages in page buffer. actually remove dirty flag. */
      ftab_collector.npages = 0;
      ftab_collector.nsects = 0;
      ftab_collector.partsect_ftab =
    (FILE_PARTIAL_SECTOR *) db_private_alloc (thread_p, fhead->n_page_ftab * sizeof (FILE_PARTIAL_SECTOR));
      if (ftab_collector.partsect_ftab == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1,
          fhead->n_page_ftab * sizeof (FILE_PARTIAL_SECTOR));
      error_code = ER_OUT_OF_VIRTUAL_MEMORY;
      goto exit;
    }

      FILE_HEADER_GET_PART_FTAB (fhead, extdata_ftab);
      is_partial = true;
      error_code =
    file_extdata_apply_funcs (thread_p, extdata_ftab, file_extdata_collect_ftab_pages, &ftab_collector,
                  file_sector_map_dealloc_temp, &is_partial, true, NULL, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

      /* deallocate table pages - other than header page */
      for (iter_sects = 0; iter_sects < ftab_collector.nsects; iter_sects++)
    {
      vpid_ftab.volid = ftab_collector.partsect_ftab[iter_sects].vsid.volid;
      for (offset = 0,
           vpid_ftab.pageid = SECTOR_FIRST_PAGEID (ftab_collector.partsect_ftab[iter_sects].vsid.sectid);
           offset < DISK_SECTOR_NPAGES; offset++, vpid_ftab.pageid++)
        {
          if (file_partsect_is_bit_set (&ftab_collector.partsect_ftab[iter_sects], offset))
        {
          page_ftab = pgbuf_simple_fix (thread_p, &vpid_ftab, false);
          if (page_ftab == NULL)
            {
              /* don't care about page which is not on page buffer. */
              continue;
            }
          if (pgbuf_dealloc_temp_page (thread_p, page_ftab, true) != NO_ERROR)
            {
              ASSERT_ERROR ();
              goto exit;
            }
          page_ftab = NULL;
        }
        }
    }

      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.nfile, -1);
      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_ftab, -fhead->n_page_ftab);
      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_user, -fhead->n_page_user);
      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_reserved, -fhead->n_page_free);
      if (pgbuf_dealloc_temp_page (thread_p, page_fhead, false) != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
      pgbuf_unfix_and_init (thread_p, page_fhead);
    }

  /* release occupied sectors on disk */
  error_code = disk_unreserve_ordered_sectors (thread_p, volpurpose, vsid_collector.n_vsids, vsid_collector.vsids);
  if (error_code != NO_ERROR)
    {
      assert_release (false);
      goto exit;
    }

  /* Done */
  assert (error_code == NO_ERROR);

exit:
  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }
  if (vsid_collector.vsids != NULL)
    {
      db_private_free (thread_p, vsid_collector.vsids);
    }
  if (ftab_collector.partsect_ftab != NULL)
    {
      db_private_free (thread_p, ftab_collector.partsect_ftab);
    }
  if (is_temp)
    {
      (void) logtb_set_check_interrupt (thread_p, save_check_interrupt);
    }
  return error_code;
}

/*
 * file_rv_destroy () - Recovery function used to destroy files.
 *
 * return    : Error code.
 * thread_p (in) : Thread entry.
 * rcv (in)  : Recovery data.
 *
 * NOTE: This can be used in one of two contexts:
 *   1. Logical undo of create file. Should be under a system operation that ends with commit and compensate.
 *   2. Run postpone for postponed file destroy. Again, this should be under a system operation, but it should end
 *      with a commit and run postpone (of course).
 */
int
file_rv_destroy (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  VFID *vfid = (VFID *) rcv->data;
  int error_code = NO_ERROR;

  assert (sizeof (*vfid) == rcv->length);

  assert (log_check_system_op_is_started (thread_p));

  error_code = file_destroy (thread_p, vfid, false);
  if (error_code != NO_ERROR)
    {
      /* Not acceptable. */
      assert_release (false);
      return error_code;
    }

  return NO_ERROR;
}

/*
 * file_postpone_destroy () - Declare intention of destroying file. File will be destroyed on postpone phase, when the
 *                transaction commit is confirmed. This is the usual (if not only) way of destroying
 *                permanent files.
 *
 * return    : Void
 * thread_p (in) : Thread entry
 * vfid (in)     : File identifier
 */
void
file_postpone_destroy (THREAD_ENTRY * thread_p, const VFID * vfid)
{
  LOG_DATA_ADDR addr = LOG_DATA_ADDR_INITIALIZER;

#if !defined (NDEBUG)
  /* This should be used for permanent files! */
  {
    VPID vpid_fhead;
    PAGE_PTR page_fhead;
    FILE_HEADER *fhead;

    vpid_fhead.volid = vfid->volid;
    vpid_fhead.pageid = vfid->fileid;
    page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
    if (page_fhead == NULL)
      {
    ASSERT_ERROR ();
    return;
      }
    fhead = (FILE_HEADER *) page_fhead;
    assert (!FILE_IS_TEMPORARY (fhead));
    pgbuf_unfix_and_init (thread_p, page_fhead);
  }
#endif /* !NDEBUG */

  log_append_postpone (thread_p, RVFL_DESTROY, &addr, sizeof (*vfid), vfid);
}

/*
 * file_temp_retire () - retire temporary file (must not be preserved)
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 */
int
file_temp_retire (THREAD_ENTRY * thread_p, const VFID * vfid)
{
  return file_temp_retire_internal (thread_p, vfid, false);
}

/*
 * file_temp_retire_preserved () - retire temporary file that was preserved
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 */
int
file_temp_retire_preserved (THREAD_ENTRY * thread_p, const VFID * vfid)
{
  return file_temp_retire_internal (thread_p, vfid, true);
}

int
file_temp_truncate (THREAD_ENTRY * thread_p, const VFID * vfid)
{
  int error_code = NO_ERROR;
  if (vfid == NULL || VFID_ISNULL (vfid))
    {
      return NO_ERROR;
    }
  error_code = file_temp_reset_user_pages (thread_p, vfid);
  if (error_code != NO_ERROR)
    {
      assert (false);
    }
  return error_code;
}

/*
 * file_temp_retire_internal () - retire temporary file. put it in cache is possible or destroy the file.
 *
 * return             : error code
 * thread_p (in)      : thread entry
 * vfid (in)          : file identifier
 * was_preserved (in) : true if entry was preserved in session. it is important to know because we cannot find it in
 *                      transaction list.
 */
STATIC_INLINE int
file_temp_retire_internal (THREAD_ENTRY * thread_p, const VFID * vfid, bool was_preserved)
{
  FILE_TEMPCACHE_ENTRY *entry = NULL;
  int error_code = NO_ERROR;

  if (was_preserved)
    {
      file_tempcache_lock ();
      error_code = file_tempcache_alloc_entry (&entry);
      file_tempcache_unlock ();
      if (error_code != NO_ERROR)
    {
      assert (false);
    }
      if (entry != NULL)
    {
      entry->vfid = *vfid;
      /* type will be set later */
    }
      else
    {
      assert (false);
    }
    }
  else
    {
      entry = file_tempcache_pop_tran_file (thread_p, vfid);
      assert (entry != NULL);
    }

  if (entry != NULL && file_tempcache_put (thread_p, entry))
    {
      /* cached */
      return NO_ERROR;
    }

  /* was not cached. destroy */
  /* don't allow interrupt to avoid file leak */
  error_code = file_destroy (thread_p, vfid, true);
  if (error_code != NO_ERROR)
    {
      /* we should not have errors */
      assert_release (false);
    }

  if (entry != NULL)
    {
      file_tempcache_retire_entry (entry);
    }
  return error_code;
}

/*
 * file_rv_perm_expand_undo () - Undo permanent file expansion.
 *
 * return    : NO_ERROR
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_perm_expand_undo (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_fhead = rcv->pgptr;
  FILE_HEADER *fhead;
  FILE_EXTENSIBLE_DATA *part_table;
  DKNSECTS save_nsects;

  fhead = (FILE_HEADER *) page_fhead;
  assert (!FILE_IS_TEMPORARY (fhead));

  /* how is this done:
   * when the file was expanded, all newly reserved sectors have been added to partial table in file header, which was
   * empty before.
   * we will empty it back, and update statistics (sector count and free page count). */

  /* empty partial table */
  FILE_HEADER_GET_PART_FTAB (fhead, part_table);
  assert (file_extdata_item_count (part_table) == fhead->n_sector_partial);
  assert (VPID_ISNULL (&part_table->vpid_next));
  part_table->n_items = 0;

  /* update fhead */
  assert (fhead->n_page_free == fhead->n_sector_empty * DISK_SECTOR_NPAGES);
  fhead->n_page_total -= fhead->n_page_free;
  fhead->n_page_free = 0;
  fhead->n_sector_total -= fhead->n_sector_empty;
  save_nsects = fhead->n_sector_empty;
  fhead->n_sector_partial = 0;
  fhead->n_sector_empty = 0;

  file_log ("file_rv_perm_expand_undo",
        "removed expanded sectors from partial table and file header in file %d|%d, "
        "page header %d|%d, lsa %lld|%d, number of sectors %d \n"
        FILE_HEAD_ALLOC_MSG, VFID_AS_ARGS (&fhead->self),
        PGBUF_PAGE_STATE_ARGS (page_fhead), save_nsects, FILE_HEAD_ALLOC_AS_ARGS (fhead));

  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

  return NO_ERROR;
}

/*
 * file_rv_perm_expand_redo () - Redo permanent file expansion.
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_perm_expand_redo (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_fhead = rcv->pgptr;
  FILE_HEADER *fhead;
  FILE_EXTENSIBLE_DATA *extdata_part_table;
  int count_vsids;
  VSID *vsids, *vsid_iter;
  FILE_PARTIAL_SECTOR partsect;

  /* how is this done:
   * file expansion means a number of sectors have been added to partial table in file header, which was empty before.
   * recovery data includes the sector ID's of all new sectors.
   * append empty sectors to partial table in file header. */

  vsids = (VSID *) rcv->data;
  count_vsids = rcv->length / sizeof (VSID);
  assert (count_vsids * (int) sizeof (VSID) == rcv->length);

  fhead = (FILE_HEADER *) page_fhead;
  assert (!FILE_IS_TEMPORARY (fhead));

  FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_table);
  assert (file_extdata_is_empty (extdata_part_table));
  /* bitmaps will be empty */
  partsect.page_bitmap = FILE_EMPTY_PAGE_BITMAP;
  for (vsid_iter = vsids; vsid_iter < vsids + count_vsids; vsid_iter++)
    {
      partsect.vsid = *vsid_iter;
      file_extdata_append (extdata_part_table, &partsect);
    }

  fhead->n_sector_total += count_vsids;
  assert (fhead->n_sector_empty == 0);
  fhead->n_sector_empty = count_vsids;
  assert (fhead->n_sector_partial == 0);
  fhead->n_sector_partial = count_vsids;

  assert (fhead->n_page_free == 0);
  fhead->n_page_free = count_vsids * DISK_SECTOR_NPAGES;
  fhead->n_page_total += fhead->n_page_free;

  file_log ("file_rv_perm_expand_redo",
        "recovery expand in file %d|%d, file header %d|%d, lsa %lld|%d \n"
        FILE_HEAD_ALLOC_MSG FILE_EXTDATA_MSG ("partial table after"),
        VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_STATE_ARGS (page_fhead),
        FILE_HEAD_ALLOC_AS_ARGS (fhead), FILE_EXTDATA_AS_ARGS (extdata_part_table));

  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);
  return NO_ERROR;
}

/*
 * file_perm_expand () - Expand permanent file by reserving new sectors.
 *
 * return      : Error code
 * thread_p (in)   : Thread entry
 * page_fhead (in) : File header page
 */
static int
file_perm_expand (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead)
{
  FILE_HEADER *fhead = NULL;
  int expand_min_size_in_sectors;
  int expand_max_size_in_sectors;
  int expand_size_in_sectors;
  VSID *vsids_reserved = NULL;
  VSID *vsid_iter = NULL;
  FILE_EXTENSIBLE_DATA *extdata_part_ftab;
  FILE_PARTIAL_SECTOR partsect;
  bool is_sysop_started = false;
  LOG_LSA save_lsa;
  int error_code = NO_ERROR;

  /* caller should have started a system operation */
  assert (log_check_system_op_is_started (thread_p));

  fhead = (FILE_HEADER *) page_fhead;
  assert (!FILE_IS_TEMPORARY (fhead));

  /* compute desired expansion size. we should consider ratio, minimum size and maximum size. also, maximum size cannot
   * exceed the capacity of partial table in file header. we want to avoid the headache of allocating file table pages
   */
  FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
  expand_min_size_in_sectors = MAX (fhead->tablespace.expand_min_size / DB_SECTORSIZE, 1);
  expand_max_size_in_sectors =
    MIN (fhead->tablespace.expand_max_size / DB_SECTORSIZE, file_extdata_remaining_capacity (extdata_part_ftab));
  assert (expand_min_size_in_sectors <= expand_max_size_in_sectors);

  expand_size_in_sectors = (int) ((float) fhead->n_sector_total * fhead->tablespace.expand_ratio);
  expand_size_in_sectors = MAX (expand_size_in_sectors, expand_min_size_in_sectors);
  expand_size_in_sectors = MIN (expand_size_in_sectors, expand_max_size_in_sectors);

  file_log ("file_perm_expand",
        "expand file %d|%d by %d sectors. \n" FILE_HEAD_ALLOC_MSG FILE_TABLESPACE_MSG,
        VFID_AS_ARGS (&fhead->self), expand_size_in_sectors, FILE_HEAD_ALLOC_AS_ARGS (fhead),
        FILE_TABLESPACE_AS_ARGS (&fhead->tablespace));

  /* allocate a buffer to hold the new sectors */
  vsids_reserved = (VSID *) db_private_alloc (thread_p, expand_size_in_sectors * sizeof (VSID));
  if (vsids_reserved == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, expand_size_in_sectors * sizeof (VSID));
      return ER_OUT_OF_VIRTUAL_MEMORY;
    }

  /* before we start, we need to open a new system operation that gets committed. it is hard to undo file expansion,
   * because we would have to search for each VSID we added individually. moreover, it is very likely to need to expand
   * the file again. therefore, we should make the expansion permanent. */
  log_sysop_start (thread_p);
  is_sysop_started = true;

  /* reserve disk sectors */
  error_code =
    disk_reserve_sectors (thread_p, DB_PERMANENT_DATA_PURPOSE, fhead->volid_last_expand, expand_size_in_sectors,
              vsids_reserved);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

  /* sort VSID's. */
  qsort (vsids_reserved, expand_size_in_sectors, sizeof (VSID), disk_compare_vsids);

  /* save in file header partial table. all sectors will be empty */
  partsect.page_bitmap = FILE_EMPTY_PAGE_BITMAP;
  for (vsid_iter = vsids_reserved; vsid_iter < vsids_reserved + expand_size_in_sectors; vsid_iter++)
    {
      partsect.vsid = *vsid_iter;
      file_extdata_append (extdata_part_ftab, &partsect);
    }

  /* update header stats */
  fhead->n_sector_total += expand_size_in_sectors;
  assert (fhead->n_sector_empty == 0);
  fhead->n_sector_empty = expand_size_in_sectors;
  assert (fhead->n_sector_partial == 0);
  fhead->n_sector_partial = expand_size_in_sectors;

  assert (fhead->n_page_free == 0);
  fhead->n_page_free = expand_size_in_sectors * DISK_SECTOR_NPAGES;
  fhead->n_page_total += fhead->n_page_free;

  save_lsa = *pgbuf_get_lsa (page_fhead);

  /* file extended successfully. log the change. */
  log_append_undoredo_data2 (thread_p, RVFL_EXPAND, NULL, page_fhead, 0, 0,
                 expand_size_in_sectors * sizeof (VSID), NULL, vsids_reserved);

  file_log ("file_perm_expand",
        "expand file %d|%d, page header %d|%d, prev_lsa %lld|%d, crt_lsa %lld|%d; "
        "first sector %d|%d \n" FILE_HEAD_ALLOC_MSG FILE_EXTDATA_MSG ("partial table"),
        VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_MODIFY_ARGS (page_fhead, &save_lsa),
        VSID_AS_ARGS (vsids_reserved), FILE_HEAD_ALLOC_AS_ARGS (fhead), FILE_EXTDATA_AS_ARGS (extdata_part_ftab));

  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

exit:
  if (is_sysop_started)
    {
      if (error_code != NO_ERROR)
    {
      log_sysop_abort (thread_p);
    }
      else
    {
      log_sysop_commit (thread_p);
    }
    }
  if (vsids_reserved != NULL)
    {
      db_private_free (thread_p, vsids_reserved);
    }
  return error_code;
}

/*
 * file_table_move_partial_sectors_to_header () - Move partial sectors from first page of partial table to header
 *                        section of partial table
 *
 * return               : error code
 * thread_p (in)        : thread entry
 * page_fhead (in)      : file header page
 * alloc_type (in)      : page allocation type
 * vpid_alloc_out (out) : output VPID of page removed from partial table and reused for new allocation
 */
static int
file_table_move_partial_sectors_to_header (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, FILE_ALLOC_TYPE alloc_type,
                       VPID * vpid_alloc_out)
{
  FILE_HEADER *fhead = NULL;
  FILE_EXTENSIBLE_DATA *extdata_part_ftab_head = NULL;
  FILE_EXTENSIBLE_DATA *extdata_part_ftab_first = NULL;
  PAGE_PTR page_part_ftab_first = NULL;
  int n_items_to_move;
  LOG_LSA save_lsa;
  int error_code = NO_ERROR;

  /* how it works:
   * when we have an empty section in header partial table, but we still have partial sectors in other pages, we move
   * some or all partial sectors in first page (all are moved if they fit header section).
   *
   * if all partial sectors in first page have been moved, then first page is removed from partial table. we used to
   * deallocate the page, but to solve some strange corner cases (see CBRD-21242), it proved more convenient to reuse
   * the page.
   */

  fhead = (FILE_HEADER *) page_fhead;
  assert (!FILE_IS_TEMPORARY (fhead));

  /* Caller should have checked */
  assert (fhead->n_sector_partial > 0);

  FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab_head);
  if (VPID_ISNULL (&extdata_part_ftab_head->vpid_next))
    {
      /* caller should have checked and we should not be here */
      assert (false);
      error_code = ER_FAILED;
      goto exit;
    }

  /* get first page of partial table */
  page_part_ftab_first =
    pgbuf_fix (thread_p, &extdata_part_ftab_head->vpid_next, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_part_ftab_first == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }

  /* get partial table extensible data in first page and let's see how many items we can move */
  extdata_part_ftab_first = (FILE_EXTENSIBLE_DATA *) page_part_ftab_first;

  file_log ("file_table_move_partial_sectors_to_header",
        "file %d|%d \n" FILE_EXTDATA_MSG ("header (destination)")
        FILE_EXTDATA_MSG ("first page (source)"),
        VFID_AS_ARGS (&fhead->self),
        FILE_EXTDATA_AS_ARGS (extdata_part_ftab_head), FILE_EXTDATA_AS_ARGS (extdata_part_ftab_first));

  n_items_to_move = file_extdata_item_count (extdata_part_ftab_first);
  if (n_items_to_move == 0)
    {
      /* not expected. */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  /* get partial table extensible data in header page */
  FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab_head);
  if (!file_extdata_is_empty (extdata_part_ftab_head))
    {
      /* we shouldn't be here. release version will still work. */
      assert (false);
      goto exit;
    }
  /* we cannot move more than the capacity of extensible data in header page */
  n_items_to_move = MIN (n_items_to_move, file_extdata_remaining_capacity (extdata_part_ftab_head));

  /* copy items to header section */
  file_extdata_append_array (extdata_part_ftab_head,
                 file_extdata_start (extdata_part_ftab_first), (INT16) n_items_to_move);
  save_lsa = *pgbuf_get_lsa (page_fhead);
  /* log changes to extensible data in header page */
  file_log_extdata_add (thread_p, extdata_part_ftab_head, page_fhead, 0,
            n_items_to_move, file_extdata_start (extdata_part_ftab_first));

  file_log ("file_table_move_partial_sectors_to_header",
        "moved %d items from first page to header page file table. \n"
        "file %d|%d, header page %d|%d, prev_lsa %lld|%d, crt_lsa %lld|%d \n"
        FILE_EXTDATA_MSG ("header partial table"), n_items_to_move,
        VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_MODIFY_ARGS (page_fhead, &save_lsa),
        FILE_EXTDATA_AS_ARGS (extdata_part_ftab_head));

  /* now remove from first page. if all items have been moved, we can deallocate first page. */
  if (n_items_to_move < file_extdata_item_count (extdata_part_ftab_first))
    {
      /* Remove copied entries. */
      save_lsa = *pgbuf_get_lsa (page_part_ftab_first);
      file_log_extdata_remove (thread_p, extdata_part_ftab_first, page_part_ftab_first, 0, n_items_to_move);
      file_extdata_remove_at (extdata_part_ftab_first, 0, n_items_to_move);

      file_log ("file_table_move_partial_sectors_to_header",
        "removed %d items from first page partial table \n"
        "file %d|%d, page %d|%d, prev_lsa %lld|%d, crt_lsa %lld|%d \n"
        FILE_EXTDATA_MSG ("first page partial table"),
        n_items_to_move, VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_MODIFY_ARGS (page_part_ftab_first, &save_lsa),
        FILE_EXTDATA_AS_ARGS (extdata_part_ftab_first));
    }
  else
    {
      /* we can remove first page. but do not deallocate it completely. this is called in the context of
       * file_perm_alloc, so we can just use this page. output its VPID and file_perm_alloc will know what to do. */
      VPID save_next = extdata_part_ftab_head->vpid_next;
      file_log_extdata_set_next (thread_p, extdata_part_ftab_head, page_fhead, &extdata_part_ftab_first->vpid_next);
      VPID_COPY (&extdata_part_ftab_head->vpid_next, &extdata_part_ftab_first->vpid_next);

      file_log ("file_table_move_partial_sectors_to_header",
        "remove first partial table page %d|%d\n", VPID_AS_ARGS (&save_next));

      *vpid_alloc_out = save_next;
      /* callers usually expect a "deallocated" page. we need to simulate that.
       * note that maybe we can do without really deallocating. a page type update would be enough. this is however the
       * safest way to do it, even though not optimal. the case will not affect performance in any benchmark or
       * production scenario anyway. */
      pgbuf_dealloc_page (thread_p, page_part_ftab_first);
      page_part_ftab_first = NULL;

      if (alloc_type == FILE_ALLOC_TABLE_PAGE_FULL_SECTOR)
    {
      /* special case: file_perm_alloc also initializes & appends the new full table page. */
      error_code = file_table_append_full_sector_page (thread_p, page_fhead, vpid_alloc_out);
      if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          VPID_SET_NULL (vpid_alloc_out);
          goto exit;
        }
    }
      else if (alloc_type == FILE_ALLOC_USER_PAGE)
    {
      /* we need to update header statistics regarding numbers of table and user pages */
      fhead->n_page_ftab--;
      fhead->n_page_user++;

      log_append_undoredo_data2 (thread_p, RVFL_FHEAD_CONVERT_FTAB_TO_USER, NULL, page_fhead, NULL_OFFSET, 0, 0,
                     NULL, NULL);
    }
    }

  /* done */

exit:
  if (page_part_ftab_first != NULL)
    {
      pgbuf_unfix_and_init (thread_p, page_part_ftab_first);
    }

  return error_code;
}

/*
 * file_rv_fhead_convert_ftab_to_user_page () - recovery update header when converting a table page to user page
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * rcv (in)      : recovery data
 */
int
file_rv_fhead_convert_ftab_to_user_page (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  FILE_HEADER *fhead = (FILE_HEADER *) rcv->pgptr;

  fhead->n_page_ftab--;
  fhead->n_page_user++;

  pgbuf_set_dirty (thread_p, rcv->pgptr, DONT_FREE);

  return NO_ERROR;
}

/*
 * file_rv_fhead_convert_user_to_ftab_page () - recovery update header when converting a user page to table page
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * rcv (in)      : recovery data
 */
int
file_rv_fhead_convert_user_to_ftab_page (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  FILE_HEADER *fhead = (FILE_HEADER *) rcv->pgptr;

  fhead->n_page_ftab++;
  fhead->n_page_user--;

  pgbuf_set_dirty (thread_p, rcv->pgptr, DONT_FREE);

  return NO_ERROR;
}

/*
 * file_table_append_full_sector_page () - append a new page to full sectors table
 *
 * return          : error code
 * thread_p (in)   : thread entry
 * page_fhead (in) : page file header
 * vpid_new (in)   : new page VPID
 */
static int
file_table_append_full_sector_page (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, const VPID * vpid_new)
{
  /* add newly allocated page to full table extdata; this page was requested while adding a new full sector */
  PAGE_PTR page_ftab = NULL;
  FILE_EXTENSIBLE_DATA *extdata_new_ftab = NULL;
  FILE_EXTENSIBLE_DATA *extdata_full_ftab = NULL;
  FILE_HEADER *fhead = (FILE_HEADER *) page_fhead;

  int error_code = NO_ERROR;

  /* small optimization: insert the page at the beginning of the list; it will be easier to access
   * when adding the following new full sectors;
   * extdata_new_ftab->vpid_next = extdata_full_ftab->vpid_next; extdata_full_ftab->vpid_next = new_vpid;
   */
  FILE_HEADER_GET_FULL_FTAB (fhead, extdata_full_ftab);

  /* fix new table page */
  page_ftab = pgbuf_fix (thread_p, vpid_new, NEW_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_ftab == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  pgbuf_set_page_ptype (thread_p, page_ftab, PAGE_FTAB);

  /* init new table extensible data */
  extdata_new_ftab = (FILE_EXTENSIBLE_DATA *) page_ftab;
  file_extdata_init (sizeof (VSID), DB_PAGESIZE, extdata_new_ftab);
  VPID_COPY (&extdata_new_ftab->vpid_next, &extdata_full_ftab->vpid_next);

  pgbuf_log_new_page (thread_p, page_ftab, file_extdata_size (extdata_new_ftab), PAGE_FTAB);
  pgbuf_unfix_and_init (thread_p, page_ftab);

  /* Log and link the page in previous table. */
  file_log_extdata_set_next (thread_p, extdata_full_ftab, page_fhead, vpid_new);
  VPID_COPY (&extdata_full_ftab->vpid_next, vpid_new);

  file_log ("file_table_append_full_sector_page", "%s", "page has been added to full sectors table \n");
  return NO_ERROR;
}

/*
 * file_table_add_full_sector () - Add a new sector to full table
 *
 * return      : Error code
 * thread_p (in)   : Thread entry
 * page_fhead (in) : File header page
 * vsid (in)       : Sector ID
 */
static int
file_table_add_full_sector (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, const VSID * vsid)
{
  bool found = false;
  PAGE_PTR page_ftab = NULL;
  FILE_HEADER *fhead = NULL;
  FILE_EXTENSIBLE_DATA *extdata_full_ftab;
  PAGE_PTR page_extdata = NULL;
  LOG_LSA save_lsa;
  int error_code = NO_ERROR;
  int pos = -1;

  /* how it works:
   * add VSID to full table. first we try to find free space in the existing extensible data components. if there is no
   * free space, we allocate a new table page and append it to full table.
   *
   * note: temporary files does not keep a full table. temporary files only keep partial table (and full sectors are
   *       never moved)
   * note: this must be called in the context of a page allocation and should be under a system operation
   */

  assert (page_fhead != NULL);
  assert (vsid != NULL);
  assert (log_check_system_op_is_started (thread_p));

  fhead = (FILE_HEADER *) page_fhead;
  assert (!FILE_IS_TEMPORARY (fhead));

  /* get full table in file header */
  FILE_HEADER_GET_FULL_FTAB (fhead, extdata_full_ftab);
  /* search for full table component with free space */
  error_code = file_extdata_find_not_full (thread_p, &extdata_full_ftab, &page_ftab, &found);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

  if (!found)
    {
      /* no free space. add a new page to full table. */
      VPID vpid_ftab_new = VPID_INITIALIZER;

      /* unfix the last page that remained fixed from file_extdata_find_not_null */
      if (page_ftab != NULL)
    {
      pgbuf_unfix_and_init (thread_p, page_ftab);
    }

      error_code = file_perm_alloc (thread_p, page_fhead, FILE_ALLOC_TABLE_PAGE_FULL_SECTOR, &vpid_ftab_new);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
      assert (!VPID_ISNULL (&vpid_ftab_new));

      /* fix newly allocated table page. note that this is an old page, file_perm_alloc already initialized it. */
      page_ftab = pgbuf_fix (thread_p, &vpid_ftab_new, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_ftab == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }

      /* the new full table component is used */
      extdata_full_ftab = (FILE_EXTENSIBLE_DATA *) page_ftab;
    }

  page_extdata = page_ftab != NULL ? page_ftab : page_fhead;

  /* add the new VSID to full table. note that we keep sectors ordered. */
  file_extdata_find_ordered (extdata_full_ftab, vsid, disk_compare_vsids, &found, &pos);
  if (found)
    {
      /* ups, duplicate! */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }
  file_extdata_insert_at (extdata_full_ftab, pos, 1, vsid);

  /* log the change. */
  save_lsa = *pgbuf_get_lsa (page_extdata);
  file_log_extdata_add (thread_p, extdata_full_ftab, page_extdata, pos, 1, vsid);

  file_log ("file_table_add_full_sector",
        "add sector %d|%d at position %d in file %d|%d, full table page %d|%d, "
        "prev_lsa %lld|%d, crt_lsa %lld|%d, \n"
        FILE_EXTDATA_MSG ("full table component"),
        VSID_AS_ARGS (vsid), pos, VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_MODIFY_ARGS (page_extdata, &save_lsa),
        FILE_EXTDATA_AS_ARGS (extdata_full_ftab));

  /* done */
  assert (error_code == NO_ERROR);

exit:
  if (page_ftab != NULL)
    {
      pgbuf_unfix (thread_p, page_ftab);
    }
  return error_code;
}

/*
 * file_rv_dump_vfid_and_vpid () - Dump recovery data when VFID and VPID are logged (used for several cases).
 *
 * return      : Void
 * fp (in)     : Dump output
 * length (in) : Recovery data length
 * data (in)   : Recovery data
 */
void
file_rv_dump_vfid_and_vpid (FILE * fp, int length, void *data)
{
  VFID *vfid = NULL;
  VPID *vpid = NULL;
  char *rcv_data = (char *) data;
  int offset = 0;

  vfid = (VFID *) (rcv_data + offset);
  offset += sizeof (*vfid);

  vpid = (VPID *) (rcv_data + offset);
  offset += sizeof (*vpid);

  assert (offset == length);

  fprintf (fp, "VFID = %d|%d \nVPID = %d|%d.\n", VFID_AS_ARGS (vfid), VPID_AS_ARGS (vpid));
}

/*
 * file_perm_alloc () - Allocate a new page in permament file.
 *
 * return       : Error code
 * thread_p (in)    : Thread entry
 * page_fhead (in)  : File header page
 * alloc_type (in)  : User/table page
 * vpid_alloc_out (out) : VPID of allocated page
 */
static int
file_perm_alloc (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, FILE_ALLOC_TYPE alloc_type, VPID * vpid_alloc_out)
{
  FILE_HEADER *fhead = (FILE_HEADER *) page_fhead;
  FILE_EXTENSIBLE_DATA *extdata_part_ftab = NULL;
  FILE_PARTIAL_SECTOR *partsect;
  int offset_to_alloc_bit;
  bool was_empty = false;
  bool is_full = false;
  LOG_LSA save_lsa;
  int error_code = NO_ERROR;

  /* how it works:
   * the file header (without considering numerable files) is split into two tables: partial table and full table.
   * to allocate new pages, the full is not interesting. to do a very fast allocation, we just pick a page in the first
   * partial sector.
   *
   * if there is no partial sector (all sectors are full), we need to expand the file (by calling file_perm_expand).
   * new sectors are reserved and we can pick a page from these sectors.
   *
   * if the partial sector becomes full, we must move it to full table.
   *
   * note: this is not for temporary files. temporary file allocation is different.
   * note: this does not handle user page table for numerable files.
   * note: this should always be called under a system operation. the system operation should always be committed before
   *       file header page is unfixed.
   *
   * todo: we might consider to do lazy moves to full table. rather than moving the full sector immediately, we let it
   *       in partial table until something happens (e.g. the section of partial table in file header becomes entirely
   *       full).
   */

  assert (log_check_system_op_is_started (thread_p));
  assert (page_fhead != NULL);

  file_log ("file_perm_alloc", "%s", FILE_ALLOC_TYPE_STRING (alloc_type));

  if (fhead->n_page_free == 0)
    {
      /* no free pages. we need to expand file. */
      error_code = file_perm_expand (thread_p, page_fhead);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
    }
  assert (fhead->n_page_free > 0);
  assert (fhead->n_sector_partial > 0);

  /* get partial table in file header */
  FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
  if (file_extdata_is_empty (extdata_part_ftab))
    {
      /* we know we have free pages, so we should have partial sectors in other table pages */
      error_code = file_table_move_partial_sectors_to_header (thread_p, page_fhead, alloc_type, vpid_alloc_out);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
      if (!VPID_ISNULL (vpid_alloc_out))
    {
      /* table page has been reused. */
      goto exit;
    }
    }

  /* we must have partial sectors in header! */
  assert (!file_extdata_is_empty (extdata_part_ftab));

  /* allocate a new page in first partial sector */
  partsect = (FILE_PARTIAL_SECTOR *) file_extdata_start (extdata_part_ftab);

  /* should not be full */
  assert (!file_partsect_is_full (partsect));

  /* was partial sector actually empty? we keep this as a statistic for now */
  was_empty = file_partsect_is_empty (partsect);

  /* allocate a page in this partial sector */
  if (!file_partsect_alloc (partsect, vpid_alloc_out, &offset_to_alloc_bit))
    {
      /* should not happen, this is a logic error. */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  assert (file_partsect_is_bit_set (partsect, offset_to_alloc_bit));
  save_lsa = *pgbuf_get_lsa (page_fhead);

  /* log allocation */
  log_append_undoredo_data2 (thread_p, RVFL_PARTSECT_ALLOC, NULL, page_fhead,
                 (PGLENGTH) ((char *) partsect - page_fhead),
                 sizeof (offset_to_alloc_bit), sizeof (offset_to_alloc_bit),
                 &offset_to_alloc_bit, &offset_to_alloc_bit);

  file_log ("file_perm_alloc",
        "allocated page %d|%d in file %d|%d page %d|%d, prev_lsa %lld|%d, crt_lsa %lld|%d, "
        "set bit at offset %d in partial sector at offset %d \n"
        FILE_PARTSECT_MSG ("partsect after"),
        VPID_AS_ARGS (vpid_alloc_out), VFID_AS_ARGS (&fhead->self),
        PGBUF_PAGE_MODIFY_ARGS (page_fhead, &save_lsa), offset_to_alloc_bit,
        (PGLENGTH) ((char *) partsect - page_fhead), FILE_PARTSECT_AS_ARGS (partsect));

  if (alloc_type == FILE_ALLOC_TABLE_PAGE_FULL_SECTOR)
    {
      /* we need to add append page before we have to move yet another sector to full. otherwise we'll loop here. */
      error_code = file_table_append_full_sector_page (thread_p, page_fhead, vpid_alloc_out);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
    }

  is_full = file_partsect_is_full (partsect);

  /* update header statistics */
  file_header_alloc (fhead, alloc_type, was_empty, is_full);
  save_lsa = *pgbuf_get_lsa (page_fhead);
  file_log_fhead_alloc (thread_p, page_fhead, alloc_type, was_empty, is_full);

  file_log ("file_perm_alloc",
        "update header in file %d|%d, header page %d|%d, prev_lsa %lld|%d, crt_lsa %lld|%d, "
        "after %s, was_empty = %s, is_full = %s, \n" FILE_HEAD_ALLOC_MSG,
        VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_MODIFY_ARGS (page_fhead, &save_lsa),
        FILE_ALLOC_TYPE_STRING (alloc_type), was_empty ? "true" : "false",
        is_full ? "true" : "false", FILE_HEAD_ALLOC_AS_ARGS (fhead));

  /* we need to first update header and then move full partial sector to full table. we might need a new page and we
   * must know if no free pages are available to expand the file */
  if (is_full)
    {
      /* move to full table. */
      VSID vsid_full;

      assert (file_partsect_is_full (partsect));

      /* save VSID before removing from partial table */
      vsid_full = partsect->vsid;

      /* remove from partial table first. adding to full table may need a new page and it expects to find one in first
       * partial sector. */
      save_lsa = *pgbuf_get_lsa (page_fhead);
      file_log_extdata_remove (thread_p, extdata_part_ftab, page_fhead, 0, 1);
      file_extdata_remove_at (extdata_part_ftab, 0, 1);

      file_log ("file_perm_alloc",
        "removed full partial sector from position 0 in file %d|%d, header page %d|%d, "
        "prev_lsa %lld|%d, crt_lsa %lld|%d, \n"
        FILE_EXTDATA_MSG ("partial table after alloc"),
        VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_MODIFY_ARGS (page_fhead, &save_lsa),
        FILE_EXTDATA_AS_ARGS (extdata_part_ftab));

      /* add to full table */
      error_code = file_table_add_full_sector (thread_p, page_fhead, &vsid_full);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
    }

  /* done */

  assert (error_code == NO_ERROR);

exit:
  perfmon_inc_stat (thread_p, PSTAT_FILE_NUM_PAGE_ALLOCS);

  return error_code;
}

/*
 * file_init_page_type () - initialize new permanent page by setting its type
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * page (in)     : new page
 * args (in)     : PAGE_TYPE *
 */
int
file_init_page_type (THREAD_ENTRY * thread_p, PAGE_PTR page, void *args)
{
  PAGE_TYPE ptype = *(PAGE_TYPE *) args;

  return file_init_page_type_internal (thread_p, page, ptype, false);
}

/*
 * file_init_temp_page_type () - initialize new temporary page by setting its type
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * page (in)     : new page
 * args (in)     : PAGE_TYPE *
 */
int
file_init_temp_page_type (THREAD_ENTRY * thread_p, PAGE_PTR page, void *args)
{
  PAGE_TYPE ptype = *(PAGE_TYPE *) args;

  return file_init_page_type_internal (thread_p, page, ptype, true);
}

/*
 * file_init_page_type_internal () - initialize new page by setting its type
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * page (in)     : new page
 * ptype (in)    : page type
 * is_temp (in)  : true if temporary page, false if permanent
 */
STATIC_INLINE int
file_init_page_type_internal (THREAD_ENTRY * thread_p, PAGE_PTR page, PAGE_TYPE ptype, bool is_temp)
{
  pgbuf_set_page_ptype (thread_p, page, ptype);
  if (!is_temp)
    {
      log_append_undoredo_data2 (thread_p, RVPGBUF_NEW_PAGE, NULL, page, (PGLENGTH) ptype, 0, 0, NULL, NULL);
    }
  pgbuf_set_dirty (thread_p, page, DONT_FREE);
  return NO_ERROR;
}

/*
 * file_alloc () - Allocate an user page for file.
 *
 * return       : Error code
 * thread_p (in)    : Thread entry
 * vfid (in)        : File identifier
 * f_init (in)      : init page function (must not be NULL for permanent page allocation)
 * f_init_args (in) : arguments for init page function
 * vpid_out (out)   : VPID of page.
 * page_out (out)   : if not null, it will output newly allocated page.
 */
int
file_alloc (THREAD_ENTRY * thread_p, const VFID * vfid, FILE_INIT_PAGE_FUNC f_init, void *f_init_args, VPID * vpid_out,
        PAGE_PTR * page_out)
{
#define UNDO_DATA_SIZE (sizeof (VFID) + sizeof (VPID))
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  TDE_ALGORITHM tde_algo = TDE_ALGORITHM_NONE;
  bool is_sysop_started = false;
  char undo_log_data_buf[UNDO_DATA_SIZE + MAX_ALIGNMENT];
  char *undo_log_data = PTR_ALIGN (undo_log_data_buf, MAX_ALIGNMENT);
  int error_code = NO_ERROR;

  assert (vfid != NULL && !VFID_ISNULL (vfid));
  assert (vpid_out != NULL);

  VPID_SET_NULL (vpid_out);
  if (page_out != NULL)
    {
      *page_out = NULL;
    }

  /* fix header */
  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  file_log ("file_alloc", "allocate new %s page. \n" FILE_HEAD_ALLOC_MSG,
        FILE_PERM_TEMP_STRING (FILE_IS_TEMPORARY (fhead)), FILE_HEAD_ALLOC_AS_ARGS (fhead));

  if (FILE_IS_TEMPORARY (fhead))
    {
      /* allocate page */
      error_code = file_temp_alloc (thread_p, page_fhead, FILE_ALLOC_USER_PAGE, vpid_out);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
    }
  else
    {
      /* start a nested system operation. we will end it with commit & undo. this must be atomic. */
      log_sysop_start_atomic (thread_p);
      is_sysop_started = true;

      /* allocate page */
      error_code = file_perm_alloc (thread_p, page_fhead, FILE_ALLOC_USER_PAGE, vpid_out);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

      /* make undo data for system op commit */
      VFID_COPY ((VFID *) undo_log_data, vfid);
      VPID_COPY ((VPID *) (undo_log_data + sizeof (VFID)), vpid_out);
    }

  assert (!VPID_ISNULL (vpid_out));

  if (FILE_IS_NUMERABLE (fhead))
    {
      /* we also have to add page to user page table */
      error_code = file_numerable_add_page (thread_p, page_fhead, vpid_out);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
    }

  if (f_init)
    {
      /* initialize page */
      PAGE_PTR page_alloc = pgbuf_fix (thread_p, vpid_out, NEW_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_alloc == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
      error_code = f_init (thread_p, page_alloc, f_init_args);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      pgbuf_unfix (thread_p, page_alloc);
      goto exit;
    }

      assert (pgbuf_get_page_ptype (thread_p, page_alloc) != PAGE_UNKNOWN);

      tde_algo = file_get_tde_algorithm_internal (fhead);

#if !defined (NDEBUG)
      {
    TDE_ALGORITHM prev_tde_algo = pgbuf_get_tde_algorithm (page_alloc);
    if (tde_algo != prev_tde_algo)
      {
        tde_er_log
          ("file_alloc(): set tde bit in pflag, VFID = %d|%d, VPID = %d|%d, tde_algorithm of the file = %s, previous tde algorithm of the page = %s\n",
           VFID_AS_ARGS (&fhead->self), VPID_AS_ARGS (vpid_out), tde_get_algorithm_name (tde_algo),
           tde_get_algorithm_name (prev_tde_algo));
      }
      }
#endif /* NDEBUG */

      pgbuf_set_tde_algorithm (thread_p, page_alloc, tde_algo, FILE_IS_TEMPORARY (fhead));

      if (page_out != NULL)
    {
      *page_out = page_alloc;
    }
      else
    {
      pgbuf_unfix (thread_p, page_alloc);
    }
    }
  else
    {
      assert (FILE_IS_TEMPORARY (fhead));
      if (page_out != NULL)
    {
      *page_out = pgbuf_fix (thread_p, vpid_out, NEW_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (*page_out == NULL)
        {
          ASSERT_ERROR_AND_SET (error_code);
          goto exit;
        }
    }
    }

  /* done */
  assert (error_code == NO_ERROR);

exit:
  if (is_sysop_started)
    {
      if (error_code != NO_ERROR)
    {
      /* abort system operation. */
      log_sysop_abort (thread_p);
    }
      else
    {
      /* commit and undo (to deallocate) */
      log_sysop_end_logical_undo (thread_p, RVFL_ALLOC, NULL, UNDO_DATA_SIZE, undo_log_data);
    }
    }

  /* allocation should be completed; check header */
  file_header_sanity_check (thread_p, fhead);

  if (error_code != NO_ERROR && page_out != NULL && *page_out != NULL)
    {
      /* don't leak page. */
      pgbuf_unfix_and_init (thread_p, *page_out);
    }

  if (page_fhead != NULL)
    {
      pgbuf_unfix_and_init (thread_p, page_fhead);
    }

  return error_code;
#undef UNDO_DATA_SIZE
}

/*
 * file_alloc_multiple () - Allocate multiple pages at once.
 *
 * return           : Error code.
 * thread_p (in)    : Thread entry.
 * vfid (in)        : File identifier.
 * f_init (in)      : New page init function.
 * f_init_args (in) : Arguments for init function.
 * npages (in)      : Number of pages to allocate.
 * vpids_out (out)  : VPIDS for allocated pages.
 */
int
file_alloc_multiple (THREAD_ENTRY * thread_p, const VFID * vfid,
             FILE_INIT_PAGE_FUNC f_init, void *f_init_args, int npages, VPID * vpids_out)
{
  VPID *vpid_iter;
  VPID local_vpid = VPID_INITIALIZER;
  int iter;
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  bool is_temp;
  int error_code = NO_ERROR;

  assert (vfid != NULL && !VFID_ISNULL (vfid));
  assert (npages >= 1);

  /* fix header */
  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);
  /* keep header while allocating all pages. we have a great chance to allocate all pages in the same sectors */

  is_temp = FILE_IS_TEMPORARY (fhead);
  if (!is_temp)
    {
      assert (log_check_system_op_is_started (thread_p));

      /* start a system op. we may abort page allocations if an error occurs. */
      log_sysop_start (thread_p);
    }

  /* do not leak pages! if not numerable, it should use all allocated VPIDS */
  assert (FILE_IS_NUMERABLE (fhead) || vpids_out != NULL);

  for (iter = 0; iter < npages; iter++)
    {
      vpid_iter = vpids_out ? vpids_out + iter : &local_vpid;
      error_code = file_alloc (thread_p, vfid, f_init, f_init_args, vpid_iter, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
    }
  /* done */
  assert (error_code == NO_ERROR);

exit:
  if (!is_temp)
    {
      assert (log_check_system_op_is_started (thread_p));
      if (error_code == NO_ERROR)
    {
      /* caller will decide what happens with allocated pages */
      log_sysop_attach_to_outer (thread_p);
    }
      else
    {
      /* undo allocations */
      log_sysop_abort (thread_p);
    }
    }

  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }

  return error_code;
}

/*
 * file_alloc_sticky_first_page () - Allocate first file page and make it sticky. It is usually used as special headers
 *                                   and should never be deallocated.
 *
 * return           : Error code
 * thread_p (in)    : Thread entry
 * vfid (in)        : File identifier
 * f_init (in)      : init page function (must not be NULL for permanent page allocation)
 * f_init_args (in) : arguments for init page function
 * vpid_out (out)   : Allocated page VPID
 * page_out (out)   : if not null, it will output newly allocated page.
 */
int
file_alloc_sticky_first_page (THREAD_ENTRY * thread_p, const VFID * vfid, FILE_INIT_PAGE_FUNC f_init, void *f_init_args,
                  VPID * vpid_out, PAGE_PTR * page_out)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  LOG_LSA save_lsa;
  int error_code = NO_ERROR;

  assert (vfid != NULL && !VFID_ISNULL (vfid));
  assert (vpid_out != NULL);

  VPID_SET_NULL (vpid_out);
  if (page_out != NULL)
    {
      *page_out = NULL;
    }

  /* fix header */
  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  assert (fhead->n_page_user == 0);
  assert (VPID_ISNULL (&fhead->vpid_sticky_first));

  error_code = file_alloc (thread_p, vfid, f_init, f_init_args, vpid_out, page_out);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

  /* save VPID */
  save_lsa = *pgbuf_get_lsa (page_fhead);
  log_append_undoredo_data2 (thread_p, RVFL_FHEAD_STICKY_PAGE, NULL,
                 page_fhead, 0, sizeof (VPID), sizeof (VPID), &fhead->vpid_sticky_first, vpid_out);
  fhead->vpid_sticky_first = *vpid_out;
  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

  /* done */
  file_header_sanity_check (thread_p, fhead);
  assert (error_code == NO_ERROR);

  file_log ("file_alloc_sticky_first_page",
        "set vpid_sticky_first to %d|%d in file %d|%d, header page %d|%d, "
        "prev_lsa %lld|%d, crt_lsa %lld|%d", VPID_AS_ARGS (vpid_out),
        VFID_AS_ARGS (vfid), PGBUF_PAGE_MODIFY_ARGS (page_fhead, &save_lsa));

exit:
  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }
  return error_code;
}

/*
 * file_rv_fhead_sticky_page () - Recovery sticky page VPID in file header.
 *
 * return        : NO_ERROR
 * thread_p (in) : Thread entry
 * rcv (in)      : Recovery data
 */
int
file_rv_fhead_sticky_page (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_fhead = rcv->pgptr;
  FILE_HEADER *fhead = (FILE_HEADER *) page_fhead;
  VPID *vpid = (VPID *) rcv->data;

  assert (rcv->length == sizeof (*vpid));

  fhead->vpid_sticky_first = *vpid;
  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

  file_log ("file_rv_fhead_sticky_page",
        "set vpid_sticky_first to %d|%d in file %d|%d, header page %d|%d, lsa %lld|%d",
        VPID_AS_ARGS (vpid), VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_STATE_ARGS (page_fhead));
  return NO_ERROR;
}

/*
 * file_get_sticky_first_page () - Get VPID of first page. It should be a sticky page.
 *
 * return         : Error code
 * thread_p (in)  : Thread entry
 * vfid (in)      : File identifier
 * vpid_out (out) : VPID of sticky first page
 */
int
file_get_sticky_first_page (THREAD_ENTRY * thread_p, const VFID * vfid, VPID * vpid_out)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  int error_code = NO_ERROR;

  /* fix header */
  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;
  if (LOG_ISRESTARTED ())
    {
      /* sometimes called before recovery... we cannot guarantee the header is sane at this point. */
      file_header_sanity_check (thread_p, fhead);
    }

  *vpid_out = fhead->vpid_sticky_first;
  if (VPID_ISNULL (vpid_out))
    {
      assert_release (false);
      pgbuf_unfix (thread_p, page_fhead);
      return ER_FAILED;
    }

  pgbuf_unfix (thread_p, page_fhead);
  return NO_ERROR;
}

/*
 * file_set_tde_algorithm () - set encryption algorithm in file header for TDE.
 *
 * return        : NO_ERROR, or ER_code
 * thread_p (in)  : Thread entry
 * vfid (in)      : File identifier
 * tde_algo (in) : encryption algorithm - NONE, AES, ARIA
 *
 */
int
file_set_tde_algorithm (THREAD_ENTRY * thread_p, const VFID * vfid, TDE_ALGORITHM tde_algo)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  int error_code = NO_ERROR;

  if (!tde_is_loaded () && tde_algo != TDE_ALGORITHM_NONE)
    {
      error_code = ER_TDE_CIPHER_IS_NOT_LOADED;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TDE_CIPHER_IS_NOT_LOADED, 0);
      return error_code;
    }

  /* fix header */
  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  if (!FILE_IS_TEMPORARY (fhead))
    {
      TDE_ALGORITHM prev_tde_algo = file_get_tde_algorithm_internal (fhead);
      log_append_undoredo_data2 (thread_p, RVFL_FHEAD_SET_TDE_ALGORITHM, NULL, page_fhead, 0, sizeof (TDE_ALGORITHM),
                 sizeof (TDE_ALGORITHM), &prev_tde_algo, &tde_algo);
    }

  file_set_tde_algorithm_internal (fhead, tde_algo);

  pgbuf_set_dirty_and_free (thread_p, page_fhead);

  return NO_ERROR;
}

/*
 * file_rv_set_tde_algorithm () - recovery setting encryption algorithm in file header for TDE.
 *
 * return        : NO_ERROR
 * thread_p (in)  : Thread entry
 * rcv (in)       : Recovery data
 *
 */
int
file_rv_set_tde_algorithm (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_fhead = rcv->pgptr;
  FILE_HEADER *fhead = (FILE_HEADER *) page_fhead;
  TDE_ALGORITHM tde_algo = *(TDE_ALGORITHM *) rcv->data;

  assert (rcv->length == sizeof (TDE_ALGORITHM));

  file_set_tde_algorithm_internal (fhead, tde_algo);

  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

  return NO_ERROR;
}

/*
 * file_set_tde_algorithm_internal () - set encryption algorithm in file header for TDE.
 *
 * fhead (in)      : File Header
 * tde_algo (in) : encryption algorithm - NONE, AES, ARIA
 *
 */
void
file_set_tde_algorithm_internal (FILE_HEADER * fhead, TDE_ALGORITHM tde_algo)
{
  /* clear encrypted flag */
  fhead->file_flags &= ~FILE_FLAG_ENCRYPTED_MASK;

  switch (tde_algo)
    {
    case TDE_ALGORITHM_AES:
      fhead->file_flags |= FILE_FLAG_ENCRYPTED_AES;
      break;
    case TDE_ALGORITHM_ARIA:
      fhead->file_flags |= FILE_FLAG_ENCRYPTED_ARIA;
      break;
    case TDE_ALGORITHM_NONE:
      /* already cleared */
      break;
    }

  tde_er_log ("file_set_tde_algorithm_internal(): VFID = %d|%d, tde_algorithm = %s\n",
          VFID_AS_ARGS (&fhead->self), tde_get_algorithm_name (tde_algo));
}

/*
 * file_get_tde_algorithm () - get encryption algorithm in file header for TDE.
 *
 * return        : NO_ERROR
 * thread_p (in)  : Thread entry
 * vfid (in)      : File identifier
 * fix_head_cond (in):  file header page latch condition
 * tde_algo (out) : encryption algorithm - NONE, AES, ARIA
 *
 */
int
file_get_tde_algorithm (THREAD_ENTRY * thread_p, const VFID * vfid, PGBUF_LATCH_CONDITION fix_head_cond,
            TDE_ALGORITHM * tde_algo)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  int error_code = NO_ERROR;

  /* fix header */
  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, fix_head_cond);
  if (page_fhead == NULL)
    {
      error_code = ER_FAILED;
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;

  *tde_algo = file_get_tde_algorithm_internal (fhead);

  pgbuf_unfix (thread_p, page_fhead);

  return NO_ERROR;
}

/*
 * file_get_tde_algorithm_internal () - get encryption algorithm in file header for TDE.
 *
 * fhead (in)      : File Header
 * tde_algo (out) : encryption algorithm - NONE, AES, ARIA
 *
 */
TDE_ALGORITHM
file_get_tde_algorithm_internal (const FILE_HEADER * fhead)
{
  // encryption algorithms are exclusive
  assert (!((fhead->file_flags & FILE_FLAG_ENCRYPTED_AES) && (fhead->file_flags & FILE_FLAG_ENCRYPTED_ARIA)));

  if (fhead->file_flags & FILE_FLAG_ENCRYPTED_AES)
    {
      return TDE_ALGORITHM_AES;
    }
  else if (fhead->file_flags & FILE_FLAG_ENCRYPTED_ARIA)
    {
      return TDE_ALGORITHM_ARIA;
    }
  else
    {
      return TDE_ALGORITHM_NONE;
    }
}

static int
file_file_map_set_tde_algorithm (THREAD_ENTRY * thread_p, PAGE_PTR * page, bool * stop, void *args)
{
  FILE_SET_TDE_ALGORITHM_ARGS *tde_args = (FILE_SET_TDE_ALGORITHM_ARGS *) args;
  pgbuf_set_tde_algorithm (thread_p, *page, tde_args->tde_algo, tde_args->skip_logging);
  return NO_ERROR;
}


/*
 * file_apply_tde_algorithm () - set encryption algorithm to file and user pages belonging to the file 
 *
 * return        : NO_ERROR, or ER_code
 * thread_p (in)  : Thread entry
 * vfid (in)      : File identifier
 * tde_algo (in) : encryption algorithm - NONE, AES, ARIA
 *
 * NOTE: The iterating pages part is the same as file_map_pages(). To set TDE to all the pages, unconditional latch has to be latched, but file_map_pages() doesn't support unconditional latch in SERVER_MODE. Becuase this function uses PGBUF_UNCONDITIONAL_LATCH, this has to be called before the file(vfid) is accessed by other thread, or it can cause dead lock (see: file_map_pages()).
 *
 */
int
file_apply_tde_algorithm (THREAD_ENTRY * thread_p, const VFID * vfid, const TDE_ALGORITHM tde_algo)
{
  int error_code = NO_ERROR;
  FILE_SET_TDE_ALGORITHM_ARGS args;
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  FILE_EXTENSIBLE_DATA *extdata_ftab;
  FILE_MAP_CONTEXT context;
  TDE_ALGORITHM prev_tde_algo = TDE_ALGORITHM_NONE;

  assert (vfid != NULL && !VFID_ISNULL (vfid));

  /* fix header */
  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  prev_tde_algo = file_get_tde_algorithm_internal (fhead);

  if (prev_tde_algo == tde_algo)
    {
      /* it is already applied */
      pgbuf_unfix (thread_p, page_fhead);
      return NO_ERROR;
    }

  tde_er_log ("file_apply_tde_algorithm(): VFID = %d|%d, # of encrypting (user) pages = %d, tde algorithm = %s\n",
          VFID_AS_ARGS (&fhead->self), fhead->n_page_user, tde_get_algorithm_name (tde_algo));

  error_code = file_set_tde_algorithm (thread_p, vfid, tde_algo);
  if (error_code != NO_ERROR)
    {
      pgbuf_unfix (thread_p, page_fhead);
      return error_code;
    }

  /* init map context */
  args.skip_logging = FILE_IS_TEMPORARY (fhead);
  args.tde_algo = tde_algo;
  context.func = file_file_map_set_tde_algorithm;
  context.args = &args;
  context.latch_cond = PGBUF_UNCONDITIONAL_LATCH;
  context.latch_mode = PGBUF_LATCH_WRITE;
  context.stop = false;
  context.ftab_collector.partsect_ftab = NULL;

  /* collect table pages */
  error_code = file_table_collect_ftab_pages (thread_p, page_fhead, true, &context.ftab_collector);
  if (error_code != NO_ERROR)
    {
      goto exit;
    }

  /* map over partial sectors table */
  FILE_HEADER_GET_PART_FTAB (fhead, extdata_ftab);
  context.is_partial = true;
  error_code = file_extdata_apply_funcs (thread_p, extdata_ftab, NULL, NULL, file_sector_map_pages, &context, false,
                     NULL, NULL);
  if (error_code != NO_ERROR)
    {
      goto exit;
    }

  if (!FILE_IS_TEMPORARY (fhead))
    {
      /* map over full table */
      context.is_partial = false;
      FILE_HEADER_GET_FULL_FTAB (fhead, extdata_ftab);
      error_code = file_extdata_apply_funcs (thread_p, extdata_ftab, NULL, NULL, file_sector_map_pages, &context,
                         false, NULL, NULL);
      if (error_code != NO_ERROR)
    {
      goto exit;
    }
    }

  assert (error_code == NO_ERROR);

exit:
  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }
  if (context.ftab_collector.partsect_ftab != NULL)
    {
      db_private_free (thread_p, context.ftab_collector.partsect_ftab);
    }

  return error_code;
}


/*
 * file_dealloc () - Deallocate a file page.
 *
 * return          : Error code
 * thread_p (in)       : Thread entry
 * vfid (in)           : File identifier
 * vpid (in)           : Page identifier
 * file_type_hint (in) : Hint for file type. Usually caller knows exactly what kind of file he deallocates the page
 *           from. Since deallocate is different based on page type (permanent/temporary, numerable),
 *           and we don't always have to fix header page, it is good to tell dealloc what to do from the
 *           start.
 */
int
file_dealloc (THREAD_ENTRY * thread_p, const VFID * vfid, const VPID * vpid, FILE_TYPE file_type_hint)
{
#define LOG_DATA_SIZE (sizeof (VFID) + sizeof (VPID))
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  char log_data_buffer[LOG_DATA_SIZE + MAX_ALIGNMENT];
  char *log_data = PTR_ALIGN (log_data_buffer, MAX_ALIGNMENT);
  LOG_DATA_ADDR log_addr = LOG_DATA_ADDR_INITIALIZER;
  FILE_EXTENSIBLE_DATA *extdata_user_page_ftab = NULL;
  PAGE_PTR page_ftab = NULL;
  PAGE_PTR page_extdata = NULL;
  bool found = false;
  int pos = -1;
  VPID *vpid_found;
  LOG_LSA save_lsa;
  int error_code = NO_ERROR;

  /* how it works:
   *
   * permanent files: we don't actually deallocate page here. we postpone the deallocation after commit, since we don't
   *                  want anyone to reuse it until we are really sure the page is no longer used.
   *
   * temporary files: we do not deallocate the page.
   *
   * numerable files: we mark the page for
   */

  /* todo: add known is_temp/is_numerable */

  /* read file type from header if caller doesn't know it. debug always reads the type from file header to check caller
   * is not wrong. */
#if defined (NDEBUG)
  if (file_type_hint == FILE_UNKNOWN_TYPE
      || file_type_hint == FILE_EXTENDIBLE_HASH || file_type_hint == FILE_EXTENDIBLE_HASH_DIRECTORY)
#endif /* NDEBUG */
    {
      /* this should be avoided in release */
      vpid_fhead.volid = vfid->volid;
      vpid_fhead.pageid = vfid->fileid;

      page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
      fhead = (FILE_HEADER *) page_fhead;
      /* check file type hint is not incorrect.
       * note: sometimes the caller may not know if file is FILE_HEAP or FILE_HEAP_REUSE_SLOTS. it doesn't make a
       * difference here, so it is accepted to give the generic FILE_HEAP hint. */
      assert (file_type_hint == FILE_UNKNOWN_TYPE || file_type_hint == fhead->type
          || (file_type_hint == FILE_HEAP && fhead->type == FILE_HEAP_REUSE_SLOTS));
      file_type_hint = fhead->type;

      assert (!VPID_EQ (&fhead->vpid_sticky_first, vpid));
    }

  if ((fhead != NULL && !FILE_IS_TEMPORARY (fhead)) || file_type_hint != FILE_TEMP)
    {
      /* for all files, we add a postpone record and do the actual deallocation at run postpone. */
      VFID_COPY ((VFID *) log_data, vfid);
      VPID_COPY ((VPID *) (log_data + sizeof (VFID)), vpid);
      log_append_postpone (thread_p, RVFL_DEALLOC, &log_addr, LOG_DATA_SIZE, log_data);

      file_log ("file_dealloc", "file %s %d|%d dealloc vpid %d|%d postponed",
        file_type_to_string (file_type_hint), VFID_AS_ARGS (vfid), VPID_AS_ARGS (vpid));
    }
  else
    {
      /* we do not deallocate pages from temporary files */
    }

  if (!FILE_TYPE_CAN_BE_NUMERABLE (file_type_hint))
    {
      /* we don't need to do anything now. the actual deallocation is postponed (or skipped altogether). */
      goto exit;
    }

  if (page_fhead == NULL)
    {
      vpid_fhead.volid = vfid->volid;
      vpid_fhead.pageid = vfid->fileid;

      page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
      fhead = (FILE_HEADER *) page_fhead;
    }

  assert (page_fhead != NULL);
  assert (fhead != NULL);
  assert (!VPID_EQ (&fhead->vpid_sticky_first, vpid));

  if (!FILE_IS_NUMERABLE (fhead))
    {
      /* we don't need to do anything now. the actual deallocation is postponed (or skipped altogether). */
      goto exit;
    }

  /* search for VPID in user page table */
  FILE_HEADER_GET_USER_PAGE_FTAB (fhead, extdata_user_page_ftab);
  error_code = file_extdata_search_item (thread_p, &extdata_user_page_ftab, vpid, file_compare_vpids, false, true,
                     &found, &pos, &page_ftab);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
  if (!found)
    {
      /* not found?? corrupted table! */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  /* check not marked as deleted */
  vpid_found = (VPID *) file_extdata_at (extdata_user_page_ftab, pos);
  if (FILE_USER_PAGE_IS_MARKED_DELETED (vpid_found))
    {
      /* already marked as deleted? I don't think so! */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  /* mark page as deleted */
  FILE_USER_PAGE_MARK_DELETED (vpid_found);

  page_extdata = page_ftab != NULL ? page_ftab : page_fhead;
  save_lsa = *pgbuf_get_lsa (page_extdata);

  pgbuf_set_dirty (thread_p, page_extdata, DONT_FREE);
  if (!FILE_IS_TEMPORARY (fhead))
    {
      /* log it */
      LOG_DATA_ADDR addr = LOG_DATA_ADDR_INITIALIZER;

      addr.pgptr = page_extdata;
      addr.offset = (PGLENGTH) (((char *) vpid_found) - addr.pgptr);
      log_append_undoredo_data (thread_p, RVFL_USER_PAGE_MARK_DELETE, &addr, LOG_DATA_SIZE, 0, log_data, NULL);
    }

  file_log ("file_dealloc",
        "marked page %d|%d as deleted in file %d|%d, page %d|%d, prev_lsa %lld|%d, "
        "crt_lsa %lld_%d, at offset %d ", VPID_AS_ARGS (vpid_found),
        VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_MODIFY_ARGS (page_extdata, &save_lsa),
        (PGLENGTH) (((char *) vpid_found) - page_extdata));

  /* update file header */
  file_header_update_mark_deleted (thread_p, page_fhead, 1);

  file_log ("file_dealloc", "file %d|%d marked vpid %|%d as deleted", VFID_AS_ARGS (vfid), VPID_AS_ARGS (vpid));

  if (FILE_CACHE_LAST_FIND_NTH (fhead, thread_p))
    {
      /* reset cached search location */
      VPID_SET_NULL (&fhead->vpid_find_nth_last);
      fhead->first_index_find_nth_last = 0;
    }

  /* done */
  assert (error_code != NO_ERROR);

exit:
  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }
  if (page_ftab != NULL)
    {
      pgbuf_unfix (thread_p, page_ftab);
    }

  return error_code;

#undef LOG_DATA_SIZE
}

/*
 * file_perm_dealloc () - Deallocate page from file tables.
 *
 * return        : Error code
 * thread_p (in)     : Thread entry
 * page_fhead (in)   : File header page
 * vpid_dealloc (in) : VPID of page being deallocated
 * alloc_type (in)   : User/table page allocation type
 */
static int
file_perm_dealloc (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, const VPID * vpid_dealloc, FILE_ALLOC_TYPE alloc_type)
{
  FILE_HEADER *fhead = NULL;
  FILE_EXTENSIBLE_DATA *extdata_part_ftab;
  FILE_EXTENSIBLE_DATA *extdata_full_ftab;
  bool found;
  int position;
  PAGE_PTR page_ftab = NULL;
  VSID vsid_dealloc;
  bool is_empty = false;
  bool was_full = false;
  LOG_DATA_ADDR addr = LOG_DATA_ADDR_INITIALIZER;
  int offset_to_dealloc_bit;
  PAGE_PTR page_dealloc = NULL;
  LOG_LSA save_page_lsa;
  int error_code = NO_ERROR;

  /* how it works:
   * the deallocation should find sector of page and clear the bit of page in sector page bitmap. if the sector was
   * full, it should be moved to partial table.
   *
   * caller should have started a system operation! all table changes should be committed before file header is unfixed.
   *
   * note: this is only for permanent files. temporary files do not deallocate pages.
   * note: this does not handle user page table.
   * note: this should always be called under a system operation.
   */

  assert (log_check_system_op_is_started (thread_p));

  fhead = (FILE_HEADER *) page_fhead;
  assert (!FILE_IS_TEMPORARY (fhead));
  assert (!VPID_EQ (&fhead->vpid_sticky_first, vpid_dealloc));

  /* find page sector in one of the partial or full tables */
  vsid_dealloc.volid = vpid_dealloc->volid;
  vsid_dealloc.sectid = SECTOR_FROM_PAGEID (vpid_dealloc->pageid);

  file_log ("file_perm_dealloc",
        "file %d|%d de%s %d|%d (search for VSID %d|%d) \n" FILE_HEAD_ALLOC_MSG,
        VFID_AS_ARGS (&fhead->self), FILE_ALLOC_TYPE_STRING (alloc_type), VPID_AS_ARGS (vpid_dealloc),
        VSID_AS_ARGS (&vsid_dealloc), FILE_HEAD_ALLOC_AS_ARGS (fhead));

  /* search partial table. */
  FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
  error_code = file_extdata_search_item (thread_p, &extdata_part_ftab, &vsid_dealloc, disk_compare_vsids, true, true,
                     &found, &position, &page_ftab);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

  if (found)
    {
      /* clear the bit for page. */
      FILE_PARTIAL_SECTOR *partsect = (FILE_PARTIAL_SECTOR *) file_extdata_at (extdata_part_ftab, position);

      offset_to_dealloc_bit = file_partsect_pageid_to_offset (partsect, vpid_dealloc->pageid);

      file_partsect_clear_bit (partsect, offset_to_dealloc_bit);
      is_empty = file_partsect_is_empty (partsect);

      addr.pgptr = page_ftab != NULL ? page_ftab : page_fhead;
      addr.offset = (PGLENGTH) (((char *) partsect) - addr.pgptr);
      save_page_lsa = *pgbuf_get_lsa (addr.pgptr);

      log_append_undoredo_data (thread_p, RVFL_PARTSECT_DEALLOC, &addr,
                sizeof (offset_to_dealloc_bit), sizeof (offset_to_dealloc_bit),
                &offset_to_dealloc_bit, &offset_to_dealloc_bit);

      pgbuf_set_dirty (thread_p, addr.pgptr, DONT_FREE);

      file_log ("file_perm_dealloc",
        "dealloc page %d|%d in file %d|%d page %d|%d, prev_lsa %lld|%d, crt_lsa %lld|%d, "
        "clear bit at offset %d in partsect at offset %d \n"
        FILE_PARTSECT_MSG ("partsect after"),
        VPID_AS_ARGS (vpid_dealloc), VFID_AS_ARGS (&fhead->self),
        PGBUF_PAGE_MODIFY_ARGS (addr.pgptr, &save_page_lsa), offset_to_dealloc_bit,
        addr.offset, FILE_PARTSECT_AS_ARGS (partsect));

      if (page_ftab != NULL)
    {
      pgbuf_unfix_and_init (thread_p, page_ftab);
    }
    }
  else
    {
      /* not in partial table. */
      FILE_PARTIAL_SECTOR partsect_new;
      VPID vpid_merged = VPID_INITIALIZER;
      bool is_merged_page_from_sector = false;

      assert (page_ftab == NULL);

      /* remove from full table */

      was_full = true;
      FILE_HEADER_GET_FULL_FTAB (fhead, extdata_full_ftab);
      error_code = file_extdata_find_and_remove_item (thread_p, extdata_full_ftab, page_fhead, &vsid_dealloc,
                              disk_compare_vsids, true, NULL, &vpid_merged);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
      if (!VPID_ISNULL (&vpid_merged))
    {
      /* full table page was merged. we need to deallocate the merged page too, unless it belong to the same sector
       * as deallocated page. in that case, we only need to remove its bit from the sector we are moving to partial
       * table. */
      if (VSID_IS_SECTOR_OF_VPID (&vsid_dealloc, &vpid_merged))
        {
          is_merged_page_from_sector = true;
          /* we'll deal with it below */
        }
      else
        {
          /* deallocate page. */
          error_code = file_perm_dealloc (thread_p, page_fhead, &vpid_merged, FILE_ALLOC_TABLE_PAGE);
          if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          return error_code;
        }
        }
    }

      /* add to partial table. */

      /* create the partial sector structure without the bit for page */
      FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
      partsect_new.vsid = vsid_dealloc;
      partsect_new.page_bitmap = FILE_FULL_PAGE_BITMAP;
      offset_to_dealloc_bit = file_partsect_pageid_to_offset (&partsect_new, vpid_dealloc->pageid);
      file_partsect_clear_bit (&partsect_new, offset_to_dealloc_bit);
      if (is_merged_page_from_sector)
    {
      /* we also need to remove merged page from this sector. */
      offset_to_dealloc_bit = file_partsect_pageid_to_offset (&partsect_new, vpid_merged.pageid);
      file_partsect_clear_bit (&partsect_new, offset_to_dealloc_bit);

      file_log ("file_perm_dealloc",
            "merged full table page %d|%d also belongs to sector being moved to partial table \n",
            VPID_AS_ARGS (&vpid_merged));

      /* need to update file header */
      file_header_dealloc (fhead, FILE_ALLOC_TABLE_PAGE, false, false);
      save_page_lsa = *pgbuf_get_lsa (page_fhead);
      file_log_fhead_dealloc (thread_p, page_fhead, FILE_ALLOC_TABLE_PAGE, false, false);

      file_log ("file_perm_dealloc",
            "update header in file %d|%d, header page %d|%d, prev_lsa %lld|%d, crt_lsa %lld|%d, "
            "after simulating the deallocation of full table page \n" FILE_HEAD_ALLOC_MSG,
            VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_MODIFY_ARGS (page_fhead, &save_page_lsa),
            FILE_HEAD_ALLOC_AS_ARGS (fhead));

      /* deallocate page */
      page_dealloc = pgbuf_fix (thread_p, &vpid_merged, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_dealloc == NULL)
        {
          ASSERT_ERROR_AND_SET (error_code);
          return error_code;
        }

      pgbuf_dealloc_page (thread_p, page_dealloc);
      perfmon_inc_stat (thread_p, PSTAT_FILE_NUM_PAGE_DEALLOCS);
    }

      /* find free space */
      error_code = file_extdata_find_not_full (thread_p, &extdata_part_ftab, &page_ftab, &found);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
      /* where is extdata_part_ftab? */
      addr.pgptr = page_ftab != NULL ? page_ftab : page_fhead;

      if (found)
    {
      /* found free space in table */

      /* find the correct position for new partial sector to keep the table ordered */
      file_extdata_find_ordered (extdata_part_ftab, &vsid_dealloc, disk_compare_vsids, &found, &position);
      if (found)
        {
          /* ups, duplicate! */
          assert_release (false);
          error_code = ER_FAILED;
          goto exit;
        }
      save_page_lsa = *pgbuf_get_lsa (addr.pgptr);
      file_log_extdata_add (thread_p, extdata_part_ftab, addr.pgptr, position, 1, &partsect_new);
      file_extdata_insert_at (extdata_part_ftab, position, 1, &partsect_new);

      file_log ("file_perm_dealloc",
            "add new partsect at position %d in file %d|%d, page %d|%d, prev_lsa %lld|%d, "
            "crt_lsa %lld|%d \n"
            FILE_PARTSECT_MSG ("new partial sector")
            FILE_EXTDATA_MSG ("partial table component"), position,
            VFID_AS_ARGS (&fhead->self),
            PGBUF_PAGE_MODIFY_ARGS (addr.pgptr, &save_page_lsa),
            FILE_PARTSECT_AS_ARGS (&partsect_new), FILE_EXTDATA_AS_ARGS (extdata_part_ftab));

      if (page_ftab != NULL)
        {
          pgbuf_unfix_and_init (thread_p, page_ftab);
        }
    }
      else
    {
      /* no free space in partial table. we need to add a new page. */
      VPID vpid_ftab_new = VPID_INITIALIZER;

      error_code = file_perm_alloc (thread_p, page_fhead, FILE_ALLOC_TABLE_PAGE, &vpid_ftab_new);
      if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          goto exit;
        }
      assert (!VPID_ISNULL (&vpid_ftab_new));

      /* set next VPID in last extensible component */
      assert (VPID_ISNULL (&extdata_part_ftab->vpid_next));
      file_log_extdata_set_next (thread_p, extdata_part_ftab, addr.pgptr, &vpid_ftab_new);
      VPID_COPY (&extdata_part_ftab->vpid_next, &vpid_ftab_new);
      if (page_ftab != NULL)
        {
          /* no longer needed */
          pgbuf_unfix_and_init (thread_p, page_ftab);
        }

      /* fix new table page */
      page_ftab = pgbuf_fix (thread_p, &vpid_ftab_new, NEW_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_ftab == NULL)
        {
          ASSERT_ERROR_AND_SET (error_code);
          goto exit;
        }
      pgbuf_set_page_ptype (thread_p, page_ftab, PAGE_FTAB);

      /* init new extensible data and add new partial sector */
      extdata_part_ftab = (FILE_EXTENSIBLE_DATA *) page_ftab;
      file_extdata_init (sizeof (FILE_PARTIAL_SECTOR), DB_PAGESIZE, extdata_part_ftab);
      file_extdata_append (extdata_part_ftab, &partsect_new);
      pgbuf_log_new_page (thread_p, page_ftab, file_extdata_size (extdata_part_ftab), PAGE_FTAB);

      file_log ("file_perm_dealloc",
            "file %d|%d moved to new partial table page %d|%d \n"
            FILE_PARTSECT_MSG ("new partial sector")
            FILE_EXTDATA_MSG ("partial table component"),
            VFID_AS_ARGS (&fhead->self),
            VPID_AS_ARGS (&vpid_ftab_new),
            FILE_PARTSECT_AS_ARGS (&partsect_new), FILE_EXTDATA_AS_ARGS (extdata_part_ftab));

      pgbuf_unfix_and_init (thread_p, page_ftab);
    }
    }

  /* we should have freed any other file table pages */
  assert (page_ftab == NULL);

  /* almost done. We need to update header statistics */
  file_header_dealloc (fhead, alloc_type, is_empty, was_full);
  save_page_lsa = *pgbuf_get_lsa (page_fhead);
  file_log_fhead_dealloc (thread_p, page_fhead, alloc_type, is_empty, was_full);

  file_log ("file_perm_dealloc",
        "update header in file %d|%d, header page %d|%d, prev_lsa %lld|%d, crt_lsa %lld|%d, "
        "after de%s, is_empty = %s, was_full = %s, \n" FILE_HEAD_ALLOC_MSG,
        VFID_AS_ARGS (&fhead->self), PGBUF_PAGE_MODIFY_ARGS (page_fhead, &save_page_lsa),
        FILE_ALLOC_TYPE_STRING (alloc_type), is_empty ? "true" : "false", was_full ? "true" : "false",
        FILE_HEAD_ALLOC_AS_ARGS (fhead));

  /* deallocate page */
  page_dealloc = pgbuf_fix (thread_p, vpid_dealloc, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_dealloc == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }

  pgbuf_dealloc_page (thread_p, page_dealloc);
  perfmon_inc_stat (thread_p, PSTAT_FILE_NUM_PAGE_DEALLOCS);

  /* done */
  assert (error_code == NO_ERROR);

exit:
  if (page_ftab != NULL)
    {
      pgbuf_unfix (thread_p, page_ftab);
    }

  return error_code;
}

/*
 * file_rv_dealloc_internal () - Actual deallocation at recovery.
 *
 * return              : Error code
 * thread_p (in)           : Thread entry
 * rcv (in)            : Recovery data
 * compensate_or_run_postpone (in) : Compensate if this is called for undo, run postpone if this is called for postpone.
 */
static int
file_rv_dealloc_internal (THREAD_ENTRY * thread_p, LOG_RCV * rcv, bool compensate_or_run_postpone)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  VFID *vfid;
  VPID *vpid_dealloc;
  int offset = 0;
  FILE_HEADER *fhead = NULL;
  bool is_sysop_started = false;
  int error_code = NO_ERROR;

  /* how it works:
   * deallocates a page in either of two contexts:
   * 1. undoing a page allocation.
   * 2. doing (actual) page deallocation on do postpone phase.
   *
   * this should clear the page bit in file tables (partial or full) by calling file_perm_dealloc.
   * also we remove the VPID from user page table.
   */

  /* recovery data: file identifier + page identifier */
  vfid = (VFID *) (rcv->data + offset);
  offset += sizeof (*vfid);

  vpid_dealloc = (VPID *) (rcv->data + offset);
  offset += sizeof (*vpid_dealloc);

  assert (offset == rcv->length);

  /* get file header */
  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      /* should not be interrupted */
      assert_release (false);
      return ER_FAILED;
    }

  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  if (FILE_IS_TEMPORARY (fhead))
    {
      /* no need to actually deallocate page. should not even be here. */
      assert (false);
      goto exit;
    }

  /* so, we do need a system operation here. the system operation will end, based on context, with commit & compensate
   * or with commit & run postpone. */
  log_sysop_start_atomic (thread_p);
  is_sysop_started = true;

  /* clear page bit by calling file_perm_dealloc */
  error_code = file_perm_dealloc (thread_p, page_fhead, vpid_dealloc, FILE_ALLOC_USER_PAGE);
  if (error_code != NO_ERROR)
    {
      /* We cannot allow errors during recovery! */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  if (FILE_IS_NUMERABLE (fhead))
    {
      /* remove VPID from user page table. */
      FILE_EXTENSIBLE_DATA *extdata_user_page_ftab;
      VPID vpid_removed = VPID_INITIALIZER;
      VPID vpid_merged = VPID_INITIALIZER;

      /* remove from user table */
      FILE_HEADER_GET_USER_PAGE_FTAB (fhead, extdata_user_page_ftab);
      error_code = file_extdata_find_and_remove_item (thread_p, extdata_user_page_ftab, page_fhead, vpid_dealloc,
                              file_compare_vpids, false, &vpid_removed, &vpid_merged);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

      if (!VPID_ISNULL (&vpid_merged))
    {
      /* user page table shrank */
      error_code = file_perm_dealloc (thread_p, page_fhead, &vpid_merged, FILE_ALLOC_TABLE_PAGE);
      if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          goto exit;
        }
    }

      /* is mark deleted? */
      if (FILE_USER_PAGE_IS_MARKED_DELETED (&vpid_removed))
    {
      /* update file header */
      file_header_update_mark_deleted (thread_p, page_fhead, -1);
    }
    }

  /* done */
  assert (error_code == NO_ERROR);

exit:
  /* system operation must be committed before releasing header page. */
  if (is_sysop_started)
    {
      if (error_code != NO_ERROR)
    {
      log_sysop_abort (thread_p);
    }
      else
    {
      if (compensate_or_run_postpone == FILE_RV_DEALLOC_COMPENSATE)
        {
          log_sysop_end_logical_compensate (thread_p, &rcv->reference_lsa);
        }
      else
        {
          log_sysop_end_logical_run_postpone (thread_p, &rcv->reference_lsa);
        }
    }
    }

  /* deallocation should be completed; check header */
  file_header_sanity_check (thread_p, fhead);

  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }
  return error_code;
}

/*
 * file_rv_dealloc_on_undo () - Deallocate the page on undo (we need to use compensate on system op commit)
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_dealloc_on_undo (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  file_log ("file_rv_dealloc_on_undo", "lsa = %lld|%d", LSA_AS_ARGS (&rcv->reference_lsa));

  return file_rv_dealloc_internal (thread_p, rcv, FILE_RV_DEALLOC_COMPENSATE);
}

/*
 * file_rv_dealloc_on_postpone () - Deallocate the page on do postpone (we need to use run postpone on system op commit)
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_dealloc_on_postpone (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  file_log ("file_rv_dealloc_on_postpone", "lsa = %lld|%d", LSA_AS_ARGS (&rcv->reference_lsa));

  return file_rv_dealloc_internal (thread_p, rcv, FILE_RV_DEALLOC_RUN_POSTPONE);
}

/*
 * file_get_num_user_pages () - Output number of user pages in file
 *
 * return                 : Error code
 * thread_p (in)          : Thread entry
 * vfid (in)              : File identifier
 * n_user_pages_out (out) : Output number of user pages
 */
int
file_get_num_user_pages (THREAD_ENTRY * thread_p, const VFID * vfid, int *n_user_pages_out)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead;
  FILE_HEADER *fhead;
  int error_code = NO_ERROR;

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  *n_user_pages_out = fhead->n_page_user;
  pgbuf_unfix (thread_p, page_fhead);

  return NO_ERROR;
}

int
file_get_num_data_sectors (THREAD_ENTRY * thread_p, const VFID * vfid, int *n_sectors_out)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead;
  FILE_HEADER *fhead;
  int error_code = NO_ERROR;

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  *n_sectors_out = fhead->n_sector_full + fhead->n_sector_partial;
  pgbuf_unfix (thread_p, page_fhead);

  return NO_ERROR;
}

/*
 * file_get_num_total_user_pages () - Output number of user pages in class
 *
 * return                 : Error code
 * thread_p (in)          : Thread entry
 * OID (in)               : Class identifier
 * total_pages (out) : Output number of user pages of class
 */
int
file_get_num_total_user_pages (THREAD_ENTRY * thread_p, OID * cls_oid, int *total_pages)
{
  int count = 0, part_pages = 0;
  OID *partitions = NULL;
  CLS_INFO *cls_info_p = NULL;
  int error = NO_ERROR;

  if (partition_get_partition_oids (thread_p, cls_oid, &partitions, &count) != NO_ERROR)
    {
      error = ER_FAILED;
      goto end;
    }

  if (count > 0)
    {
      /* partition table */
      for (int i = 0; i < count; i++)
    {
      cls_info_p = catalog_get_class_info (thread_p, &partitions[i], NULL);
      if (cls_info_p == NULL)
        {
          error = ER_FAILED;
          goto end;
        }
      if (file_get_num_user_pages (thread_p, &(cls_info_p->ci_hfid.vfid), &part_pages) != NO_ERROR)
        {
          error = ER_FAILED;
          goto end;
        }
      *total_pages += part_pages;
      catalog_free_class_info_and_init (cls_info_p);
    }
    }
  else
    {
      cls_info_p = catalog_get_class_info (thread_p, cls_oid, NULL);
      if (cls_info_p == NULL)
    {
      error = ER_FAILED;
      goto end;
    }
      if (file_get_num_user_pages (thread_p, &(cls_info_p->ci_hfid.vfid), total_pages) != NO_ERROR)
    {
      error = ER_FAILED;
      goto end;
    }
      catalog_free_class_info_and_init (cls_info_p);
    }

end:
  if (partitions != NULL)
    {
      db_private_free (thread_p, partitions);
    }
  catalog_free_class_info_and_init (cls_info_p);

  return NO_ERROR;
}

/*
 * file_check_vpid () - check vpid is one of the file's user pages
 *
 * return           : DISK_INVALID if page does not belong to file, DISK_ERROR for errors and DISK_VALID for successful
 *                    check
 * thread_p (in)    : thread entry
 * vfid (in)        : file identifier
 * vpid_lookup (in) : checked VPID
 */
DISK_ISVALID
file_check_vpid (THREAD_ENTRY * thread_p, const VFID * vfid, const VPID * vpid_lookup)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead;
  FILE_HEADER *fhead;
  PAGE_PTR page_ftab = NULL;
  VSID vsid_lookup;
  bool found;
  int pos;
  FILE_EXTENSIBLE_DATA *extdata_part_ftab;
  FILE_EXTENSIBLE_DATA *extdata_full_ftab;
  FILE_EXTENSIBLE_DATA *extdata_user_page_ftab;
  DISK_ISVALID isvalid = DISK_VALID;

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR ();
      return DISK_ERROR;
    }

  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  /* first search the VPID in sector tables: partial, then full. */
  VSID_FROM_VPID (&vsid_lookup, vpid_lookup);

  FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
  if (file_extdata_search_item (thread_p, &extdata_part_ftab, &vsid_lookup, disk_compare_vsids, true, false, &found,
                &pos, &page_ftab) != NO_ERROR)
    {
      ASSERT_ERROR ();
      isvalid = DISK_ERROR;
      goto exit;
    }
  if (found)
    {
      FILE_PARTIAL_SECTOR *partsect;

      partsect = (FILE_PARTIAL_SECTOR *) file_extdata_at (extdata_part_ftab, pos);
      if (file_partsect_is_bit_set (partsect, file_partsect_pageid_to_offset (partsect, vpid_lookup->pageid)))
    {
      /* ok */
      /* fall through */
    }
      else
    {
      /* not ok */
      assert_release (false);
      isvalid = DISK_INVALID;
      goto exit;
    }
    }
  else
    {
      /* Search in full table */
      if (page_ftab != NULL)
    {
      pgbuf_unfix_and_init (thread_p, page_ftab);
    }
      FILE_HEADER_GET_FULL_FTAB (fhead, extdata_full_ftab);
      if (file_extdata_search_item (thread_p, &extdata_full_ftab, &vsid_lookup, disk_compare_vsids, true, false,
                    &found, &pos, &page_ftab) != NO_ERROR)
    {
      ASSERT_ERROR ();
      isvalid = DISK_ERROR;
      goto exit;
    }
      if (!found)
    {
      /* not ok */
      assert_release (false);
      isvalid = DISK_INVALID;
      goto exit;
    }
    }
  if (page_ftab != NULL)
    {
      pgbuf_unfix_and_init (thread_p, page_ftab);
    }

  if (FILE_IS_NUMERABLE (fhead))
    {
      /* Search in user page table */
      VPID *vpid_in_table;

      FILE_HEADER_GET_USER_PAGE_FTAB (fhead, extdata_user_page_ftab);
      if (file_extdata_search_item (thread_p, &extdata_user_page_ftab, vpid_lookup, file_compare_vpids, false, false,
                    &found, &pos, &page_ftab) != NO_ERROR)
    {
      ASSERT_ERROR ();
      isvalid = DISK_ERROR;
      goto exit;
    }
      if (!found)
    {
      /* not ok */
      assert_release (false);
      isvalid = DISK_INVALID;
      goto exit;
    }
      /* check not marked as deleted */
      vpid_in_table = (VPID *) file_extdata_at (extdata_user_page_ftab, pos);
      if (FILE_USER_PAGE_IS_MARKED_DELETED (vpid_in_table))
    {
      /* not ok */
      assert_release (false);
      isvalid = DISK_INVALID;
      goto exit;
    }
      /* ok */
    }

  /* page is part of file */
  assert (isvalid == DISK_VALID);

exit:
  if (page_ftab != NULL)
    {
      pgbuf_unfix (thread_p, page_ftab);
    }
  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }
  return isvalid;
}

/*
 * file_get_type () - Get file type for VFID.
 *
 * return          : Error code
 * thread_p (in)   : Thread entry
 * vfid (in)       : File identifier
 * ftype_out (out) : Output file type
 */
int
file_get_type (THREAD_ENTRY * thread_p, const VFID * vfid, FILE_TYPE * ftype_out)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;

  assert (vfid != NULL && !VFID_ISNULL (vfid));
  assert (ftype_out != NULL);

  /* read from file header */
  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      int error_code = NO_ERROR;

      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  *ftype_out = fhead->type;
  assert (*ftype_out != FILE_UNKNOWN_TYPE);

  pgbuf_unfix (thread_p, page_fhead);

  return NO_ERROR;
}

/*
 * file_is_temp () - is file temporary?
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 * is_temp (out) : true for temporary, false otherwise
 */
int
file_is_temp (THREAD_ENTRY * thread_p, const VFID * vfid, bool * is_temp)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;

  assert (vfid != NULL && !VFID_ISNULL (vfid));
  assert (is_temp != NULL);

  /* read from file header */
  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      int error_code = NO_ERROR;
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  *is_temp = FILE_IS_TEMPORARY (fhead);

  pgbuf_unfix (thread_p, page_fhead);

  return NO_ERROR;
}

/*
 * file_table_collect_ftab_pages () - collect file table pages
 *
 * return                 : error code
 * thread_p (in)          : thread entry
 * page_fhead (in)        : file header page
 * collect_numerable (in) : true to collect also user page table, false otherwise
 * collector_out (out)    : output collected table pages
 */
STATIC_INLINE int
file_table_collect_ftab_pages (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, bool collect_numerable,
                   FILE_FTAB_COLLECTOR * collector_out)
{
  VPID vpid_fhead;
  FILE_HEADER *fhead = NULL;
  FILE_EXTENSIBLE_DATA *extdata_ftab;
  int error_code;

  fhead = (FILE_HEADER *) page_fhead;

  /* init collector */
  collector_out->partsect_ftab =
    (FILE_PARTIAL_SECTOR *) db_private_alloc (thread_p, sizeof (FILE_PARTIAL_SECTOR) * fhead->n_page_ftab);
  if (collector_out->partsect_ftab == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1,
          sizeof (FILE_PARTIAL_SECTOR) * fhead->n_page_ftab);
      return ER_OUT_OF_VIRTUAL_MEMORY;
    }

  /* add page header */
  pgbuf_get_vpid (page_fhead, &vpid_fhead);
  VSID_FROM_VPID (&collector_out->partsect_ftab[0].vsid, &vpid_fhead);
  collector_out->partsect_ftab[0].page_bitmap = FILE_EMPTY_PAGE_BITMAP;
  file_partsect_set_bit (&collector_out->partsect_ftab[0],
             file_partsect_pageid_to_offset (&collector_out->partsect_ftab[0], vpid_fhead.pageid));
  collector_out->nsects = 1;
  collector_out->npages = 1;

  file_log ("file_temp_reset_user_pages",
        "init collector with page %d|%d \n "
        FILE_PARTSECT_MSG ("partsect"), VPID_AS_ARGS (&vpid_fhead),
        FILE_PARTSECT_AS_ARGS (collector_out->partsect_ftab));

  /* add other pages in partial sector table */
  FILE_HEADER_GET_PART_FTAB (fhead, extdata_ftab);
  error_code = file_extdata_apply_funcs (thread_p, extdata_ftab, file_extdata_collect_ftab_pages, collector_out, NULL,
                     NULL, false, NULL, NULL);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      db_private_free_and_init (thread_p, collector_out->partsect_ftab);
      return error_code;
    }

  if (!FILE_IS_TEMPORARY (fhead))
    {
      /* add other pages from full sector table */
      FILE_HEADER_GET_FULL_FTAB (fhead, extdata_ftab);
      error_code = file_extdata_apply_funcs (thread_p, extdata_ftab, file_extdata_collect_ftab_pages, collector_out,
                         NULL, NULL, false, NULL, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      db_private_free_and_init (thread_p, collector_out->partsect_ftab);
      return error_code;
    }
    }

  if (collect_numerable && FILE_IS_NUMERABLE (fhead))
    {
      /* add other pages from user-page table */
      FILE_HEADER_GET_USER_PAGE_FTAB (fhead, extdata_ftab);
      error_code = file_extdata_apply_funcs (thread_p, extdata_ftab, file_extdata_collect_ftab_pages, collector_out,
                         NULL, NULL, false, NULL, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      db_private_free_and_init (thread_p, collector_out->partsect_ftab);
      return error_code;
    }
    }

  return NO_ERROR;
}

/*
 * file_extdata_collect_ftab_pages () - FILE_EXTDATA_FUNC to collect pages used for file table
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * extdata (in)  : extensible data
 * stop (in)     : ignored
 * args (in/out) : FILE_FTAB_COLLECTOR * - file table page collector
 */
static int
file_extdata_collect_ftab_pages (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata, bool * stop, void *args)
{
  FILE_FTAB_COLLECTOR *collect = (FILE_FTAB_COLLECTOR *) args;
  VSID vsid_this;
  int idx_sect = 0;

  if (!VPID_ISNULL (&extdata->vpid_next))
    {
      VSID_FROM_VPID (&vsid_this, &extdata->vpid_next);

      /* find in collected sectors */
      for (idx_sect = 0; idx_sect < collect->nsects; idx_sect++)
    {
      if (disk_compare_vsids (&vsid_this, &collect->partsect_ftab[idx_sect].vsid) == 0)
        {
          break;
        }
    }

      if (idx_sect == collect->nsects)
    {
      /* not found, append new sector */
      collect->partsect_ftab[collect->nsects].vsid = vsid_this;
      collect->partsect_ftab[collect->nsects].page_bitmap = FILE_EMPTY_PAGE_BITMAP;
      collect->nsects++;

      file_log ("file_extdata_collect_ftab_pages",
            "add new vsid %d|%d, nsects = %d", VSID_AS_ARGS (&vsid_this), collect->nsects);
    }
      file_partsect_set_bit (&collect->partsect_ftab[idx_sect],
                 file_partsect_pageid_to_offset (&collect->partsect_ftab[idx_sect],
                                 extdata->vpid_next.pageid));

      file_log ("file_extdata_collect_ftab_pages",
        "collect ftab page %d|%d, \n" FILE_PARTSECT_MSG ("partsect"),
        VPID_AS_ARGS (&extdata->vpid_next), FILE_PARTSECT_AS_ARGS (&collect->partsect_ftab[idx_sect]));
      collect->npages++;
    }

  return NO_ERROR;
}

/*
 * file_table_collector_has_page () - check if page is in collected file table pages
 *
 * return         : true if page is file table page, false otherwise
 * collector (in) : file table pages collector
 * vpid (in)      : page id
 */
STATIC_INLINE bool
file_table_collector_has_page (FILE_FTAB_COLLECTOR * collector, VPID * vpid)
{
  int iter;
  VSID vsid;

  VSID_FROM_VPID (&vsid, vpid);

  for (iter = 0; iter < collector->nsects; iter++)
    {
      if (VSID_EQ (&vsid, &collector->partsect_ftab[iter].vsid))
    {
      return file_partsect_is_bit_set (&collector->partsect_ftab[iter],
                       file_partsect_pageid_to_offset (&collector->partsect_ftab[iter],
                                       vpid->pageid));
    }
    }

  return false;
}

/*
 * file_sector_map_pages () - FILE_EXTDATA_ITEM_FUNC used for mapping a function on all user pages
 *
 * return        : error code
 * thread_p (in) : thread entry
 * data (in)     : FILE_PARTIAL_SECTOR or VSID
 * index (in)    : ignored
 * stop (out)    : output true when to stop the mapping
 * args (in)     : map context
 */
static int
file_sector_map_pages (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args)
{
  FILE_MAP_CONTEXT *context = (FILE_MAP_CONTEXT *) args;
  FILE_PARTIAL_SECTOR partsect = FILE_PARTIAL_SECTOR_INITIALIZER;
  int iter;
  VPID vpid;
  PAGE_PTR page = NULL;
  int error_code = NO_ERROR;

  assert (context != NULL);
  assert (context->stop == false);
  assert (context->func != NULL);
  assert (context->latch_mode == PGBUF_LATCH_WRITE || context->latch_mode == PGBUF_LATCH_READ);
  assert (context->latch_cond == PGBUF_CONDITIONAL_LATCH || context->latch_cond == PGBUF_UNCONDITIONAL_LATCH);

  /* how this works:
   * get all allocated pages from sector in file table. data can be either a partial sector (vsid + allocation bitmap)
   * or a vsid (full sector - all its pages are allocated).
   * for each page, we have to check first it is not a table page. we only want to map the function for user pages.
   *
   * if the page cannot be fixed conditionally, it is simply skipped. careful when using unconditional latch in server
   * mode. there is a risk of dead-latches.
   *
   * map function can keep the page, but it must set the page pointer to NULL.
   */

  /* hack to know this is partial table or full table */
  if (context->is_partial)
    {
      partsect = *(FILE_PARTIAL_SECTOR *) data;
    }
  else
    {
      partsect.vsid = *(VSID *) data;
    }

  vpid.volid = partsect.vsid.volid;
  for (iter = 0, vpid.pageid = SECTOR_FIRST_PAGEID (partsect.vsid.sectid); iter < FILE_ALLOC_BITMAP_NBITS;
       iter++, vpid.pageid++)
    {
      if (context->is_partial && !file_partsect_is_bit_set (&partsect, iter))
    {
      /* not allocated */
      continue;
    }

      if (file_table_collector_has_page (&context->ftab_collector, &vpid))
    {
      /* skip table pages */
      continue;
    }

      page = pgbuf_fix (thread_p, &vpid, OLD_PAGE, context->latch_mode, context->latch_cond);
      if (page == NULL)
    {
      if (context->latch_cond == PGBUF_CONDITIONAL_LATCH)
        {
          /* exit gracefully */
          return NO_ERROR;
        }
      /* error */
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

      assert (pgbuf_get_page_ptype (thread_p, page) != PAGE_FTAB);

      /* call map function */
      error_code = context->func (thread_p, &page, stop, context->args);
      if (page != NULL)
    {
      pgbuf_unfix_and_init (thread_p, page);
    }
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

      if (*stop)
    {
      /* early out */
      context->stop = true;
      return NO_ERROR;
    }
    }

  return NO_ERROR;
}

/*
 * file_map_pages () - map given function on all user pages
 *
 * return          : error code
 * thread_p (in)   : thread entry
 * vfid (in)       : file identifier
 * latch_mode (in) : latch mode
 * latch_cond (in) : latch condition
 * func (in)       : map function
 * args (in)       : map function arguments
 */
int
file_map_pages (THREAD_ENTRY * thread_p, const VFID * vfid, PGBUF_LATCH_MODE latch_mode,
        PGBUF_LATCH_CONDITION latch_cond, FILE_MAP_PAGE_FUNC func, void *args)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  FILE_EXTENSIBLE_DATA *extdata_ftab;
  FILE_MAP_CONTEXT context;
  int error_code = NO_ERROR;

  assert (vfid != NULL && !VFID_ISNULL (vfid));
  assert (latch_mode == PGBUF_LATCH_READ || latch_mode == PGBUF_LATCH_WRITE);
  assert (func != NULL);

  /* how it works:
   * get all user pages in partial sector table and full sector table and apply function. the file_sector_map_pages
   * is first mapped on entries of partial table and then on full table.
   *
   * note that file header is read-latched during the whole time. this means page allocations/deallocations are blocked
   * during the process. careful when using the function in server-mode. it is not advisable to be used for hot and
   * large files.
   *
   * function must be mapped on user pages only. as a first step we have to collect file table pages. while mapping
   * function on pages, these are skipped.
   */

#if defined (SERVER_MODE)
  /* we cannot use unconditional latch. allocations can sometimes keep latch on a file page and then try to lock
   * header/table. we may cause dead latch.
   */
  assert (latch_cond == PGBUF_CONDITIONAL_LATCH);
  latch_cond = PGBUF_CONDITIONAL_LATCH;
#endif /* SERVER_MODE */

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  /* init map context */
  context.func = func;
  context.args = args;
  context.latch_cond = latch_cond;
  context.latch_mode = latch_mode;
  context.stop = false;
  context.ftab_collector.partsect_ftab = NULL;

  /* collect table pages */
  error_code = file_table_collect_ftab_pages (thread_p, page_fhead, true, &context.ftab_collector);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

  /* map over partial sectors table */
  FILE_HEADER_GET_PART_FTAB (fhead, extdata_ftab);
  context.is_partial = true;
  error_code = file_extdata_apply_funcs (thread_p, extdata_ftab, NULL, NULL, file_sector_map_pages, &context, false,
                     NULL, NULL);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
  if (context.stop)
    {
      goto exit;
    }

  if (!FILE_IS_TEMPORARY (fhead))
    {
      /* map over full table */
      context.is_partial = false;
      FILE_HEADER_GET_FULL_FTAB (fhead, extdata_ftab);
      error_code = file_extdata_apply_funcs (thread_p, extdata_ftab, NULL, NULL, file_sector_map_pages, &context,
                         false, NULL, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
    }

  assert (error_code == NO_ERROR);

exit:
  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }
  if (context.ftab_collector.partsect_ftab != NULL)
    {
      db_private_free (thread_p, context.ftab_collector.partsect_ftab);
    }

  return error_code;
}

/*
 * file_table_check () - check file table is valid
 *
 * return                  : DISK_INVALID for unexpected errors, DISK_ERROR for expected errors,
 *                           DISK_VALID for successful check
 * thread_p (in)           : thread entry
 * vfid (in)               : file identifier
 * disk_map_clone (in/out) : clone of disk sector table maps used in stand-alone mode only to cross-check reserved
 *                           sectors
 */
static DISK_ISVALID
file_table_check (THREAD_ENTRY * thread_p, const VFID * vfid, DISK_VOLMAP_CLONE * disk_map_clone)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  FILE_VSID_COLLECTOR collector;
#if defined (SA_MODE)
  int iter_vsid;
#endif /* SA_MODE */

  DISK_ISVALID valid = DISK_VALID;
  DISK_ISVALID allvalid = DISK_VALID;
  int error_code = NO_ERROR;

#if defined (SA_MODE)
  assert (disk_map_clone != NULL);
#endif /* SA_MODE */

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR ();
      return DISK_ERROR;
    }
  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  error_code = file_table_collect_all_vsids (thread_p, page_fhead, &collector);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

  if (FILE_IS_NUMERABLE (fhead))
    {
      /* check all pages in user table belong to collected page */
      FILE_EXTENSIBLE_DATA *extdata_user_page_ftab;

      FILE_HEADER_GET_USER_PAGE_FTAB (fhead, extdata_user_page_ftab);
      error_code = file_extdata_apply_funcs (thread_p, extdata_user_page_ftab, NULL, NULL,
                         file_table_check_page_is_in_sectors, &collector, false, NULL, NULL);
      if (error_code == ER_FAILED)
    {
      /* set for unexpected cases */
      allvalid = DISK_INVALID;
      /* fall through: also check sectors are reserved */
    }
      else if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
    }

  /* check all sectors are reserved. if the file is very big, this can take a while. don't keep header page fixed */
  pgbuf_unfix_and_init (thread_p, page_fhead);

#if defined (SERVER_MODE)
  valid = disk_check_sectors_are_reserved (thread_p, collector.vsids, collector.n_vsids);
  if (valid != DISK_VALID && allvalid == DISK_VALID)
    {
      allvalid = valid;
    }
#else   /* !SERVER_MODE */         /* SA_MODE */
  for (iter_vsid = 0; iter_vsid < collector.n_vsids; iter_vsid++)
    {
      valid = disk_map_clone_clear (&collector.vsids[iter_vsid], disk_map_clone);
      if (valid == DISK_INVALID)
    {
      allvalid = DISK_INVALID;
    }
      else
    {
      assert (valid == DISK_VALID);
    }
    }
#endif /* SA_MODE */

exit:
  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }

  if (error_code != NO_ERROR && allvalid == DISK_VALID)
    {
      allvalid = DISK_ERROR;
    }

  if (collector.vsids != NULL)
    {
      db_private_free (thread_p, collector.vsids);
    }
  return allvalid;
}

/*
 * file_dump () - file dump
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 * fp (in)       : output file
 */
int
file_dump (THREAD_ENTRY * thread_p, const VFID * vfid, FILE * fp)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  int error_code = NO_ERROR;

  fprintf (fp, "\n\n Dumping file %d|%d \n", VFID_AS_ARGS (vfid));

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  fhead = (FILE_HEADER *) page_fhead;

  file_header_dump (thread_p, fhead, fp);

  error_code = file_table_dump (thread_p, fhead, fp);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      pgbuf_unfix (thread_p, page_fhead);
      return error_code;
    }

  pgbuf_unfix (thread_p, page_fhead);
  return NO_ERROR;
}

/*
 * file_table_dump () - dump file table
 *
 * return        : error code
 * thread_p (in) : thread entry
 * fhead (in)    : file header
 * fp (in)       : output file
 */
STATIC_INLINE int
file_table_dump (THREAD_ENTRY * thread_p, const FILE_HEADER * fhead, FILE * fp)
{
  int error_code = NO_ERROR;
  FILE_EXTENSIBLE_DATA *extdata_ftab = NULL;

  fprintf (fp, "FILE TABLE: \n");
  FILE_HEADER_GET_PART_FTAB (fhead, extdata_ftab);
  error_code = file_extdata_apply_funcs (thread_p, extdata_ftab, file_partial_table_extdata_dump, fp,
                     file_partial_table_item_dump, fp, false, NULL, NULL);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  if (!FILE_IS_TEMPORARY (fhead))
    {
      FILE_HEADER_GET_FULL_FTAB (fhead, extdata_ftab);
      error_code = file_extdata_apply_funcs (thread_p, extdata_ftab, file_full_table_extdata_dump, fp,
                         file_full_table_item_dump, fp, false, NULL, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }
    }

  if (FILE_IS_NUMERABLE (fhead))
    {
      FILE_HEADER_GET_USER_PAGE_FTAB (fhead, extdata_ftab);
      error_code = file_extdata_apply_funcs (thread_p, extdata_ftab, file_full_table_extdata_dump, fp,
                         file_full_table_item_dump, fp, false, NULL, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }
    }

  return NO_ERROR;
}

/*
 * file_partial_table_extdata_dump () - dump an extensible data from partial table
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * extdata (in)  : extensible data
 * stop (in)     : not used
 * args (in)     : FILE *
 */
static int
file_partial_table_extdata_dump (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata, bool * stop, void *args)
{
  FILE *fp = (FILE *) args;

  fprintf (fp, FILE_EXTDATA_MSG ("partial table component"), FILE_EXTDATA_AS_ARGS (extdata));
  return NO_ERROR;
}

/*
 * file_partial_table_item_dump () - dump an item from partial table
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * data (in)     : FILE_PARTIAL_SECTOR *
 * index (in)    : item index
 * stop (in)     : not used
 * args (in)     : FILE *
 */
static int
file_partial_table_item_dump (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args)
{
  FILE *fp = (FILE *) args;
  FILE_PARTIAL_SECTOR *partsect;
  int iter;
  int line_count;
  VPID vpid;

  partsect = (FILE_PARTIAL_SECTOR *) data;
  fprintf (fp, FILE_PARTSECT_MSG ("partially allocated sector"), FILE_PARTSECT_AS_ARGS (partsect));

  vpid.volid = partsect->vsid.volid;
  fprintf (fp, "\t\t allocated pages:");
  line_count = 0;
  for (iter = 0, vpid.pageid = SECTOR_FIRST_PAGEID (partsect->vsid.sectid); iter < DISK_SECTOR_NPAGES;
       iter++, vpid.pageid++)
    {
      if (file_partsect_is_bit_set (partsect, iter))
    {
      if (line_count++ % 8 == 0)
        {
          fprintf (fp, "\n\t\t\t");
        }
      fprintf (fp, "%5d|%10d ", VPID_AS_ARGS (&vpid));
    }
    }

  fprintf (fp, "\n\t\t reserved pages:");
  line_count = 0;
  for (iter = 0, vpid.pageid = SECTOR_FIRST_PAGEID (partsect->vsid.sectid); iter < DISK_SECTOR_NPAGES;
       iter++, vpid.pageid++)
    {
      if (!file_partsect_is_bit_set (partsect, iter))
    {
      if (line_count++ % 8 == 0)
        {
          fprintf (fp, "\n\t\t\t");
        }
      fprintf (fp, "%5d|%10d ", VPID_AS_ARGS (&vpid));
    }
    }
  fprintf (fp, "\n");

  return NO_ERROR;
}

/*
 * file_full_table_extdata_dump () - dump an extensible data from full table
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * extdata (in)  : extensible data
 * stop (in)     : not used
 * args (in)     : FILE *
 */
static int
file_full_table_extdata_dump (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata, bool * stop, void *args)
{
  FILE *fp = (FILE *) args;

  fprintf (fp, FILE_EXTDATA_MSG ("full table component"), FILE_EXTDATA_AS_ARGS (extdata));
  return NO_ERROR;
}

/*
 * file_full_table_item_dump () - dump an item from full table
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * data (in)     : VSID *
 * index (in)    : item index
 * stop (in)     : not used
 * args (in)     : FILE *
 */
static int
file_full_table_item_dump (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args)
{
  FILE *fp = (FILE *) args;
  VSID *vsid;
  int iter;
  int line_count;
  VPID vpid;

  vsid = (VSID *) data;
  fprintf (fp, "fully allocated sector: vsid = %d|%d \n", VSID_AS_ARGS (vsid));

  line_count = 0;
  vpid.volid = vsid->volid;
  for (iter = 0, vpid.pageid = SECTOR_FIRST_PAGEID (vsid->sectid); iter < DISK_SECTOR_NPAGES; iter++, vpid.pageid++)
    {
      if (line_count++ % 8 == 0)
    {
      fprintf (fp, "\n\t\t\t");
    }
      fprintf (fp, "%5d|%10d ", VPID_AS_ARGS (&vpid));
    }
  return NO_ERROR;
}

/*
 * file_user_page_table_extdata_dump () - dump an extensible data from user page table
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * extdata (in)  : extensible data
 * stop (in)     : not used
 * args (in)     : FILE *
 */
static int
file_user_page_table_extdata_dump (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata, bool * stop,
                   void *args)
{
  FILE *fp = (FILE *) args;

  fprintf (fp, FILE_EXTDATA_MSG ("user page table component"), FILE_EXTDATA_AS_ARGS (extdata));
  return NO_ERROR;
}

/*
 * file_user_page_table_item_dump () - dump an item from user page table
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * data (in)     : VPID *
 * index (in)    : item index
 * stop (in)     : not used
 * args (in)     : FILE *
 */
static int
file_user_page_table_item_dump (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args)
{
  FILE *fp = (FILE *) args;
  VPID *vpid = (VPID *) data;

  if (index % 8 == 0)
    {
      fprintf (fp, "\n\t\t\t");
    }
  if (FILE_USER_PAGE_IS_MARKED_DELETED (vpid))
    {
      fprintf (fp, "\n WARNING: page %d|%d is marked as deleted!! \n\t\t\t", VPID_AS_ARGS (vpid));
    }
  fprintf (fp, "%5d|%10d ", VPID_AS_ARGS (vpid));

  return NO_ERROR;
}

/*
 * file_spacedb () - get space usage information
 *
 * return        : error code
 * thread_p (in) : thread entry
 * spacedb (out) : output space usage information
 */
int
file_spacedb (THREAD_ENTRY * thread_p, SPACEDB_FILES * spacedb)
{
  int i;
  int error_code = NO_ERROR;

  /* init */
  memset (spacedb, 0, sizeof (SPACEDB_FILES) * SPACEDB_FILE_COUNT);

  /* temporary files stats are already cached. */
  spacedb[SPACEDB_TEMP_FILE] = file_Tempcache.spacedb_temp;

  /* use file tracker to get info on permanent purpose files */
  error_code = file_tracker_spacedb (thread_p, spacedb);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  /* compute total */
  memset (&spacedb[SPACEDB_TOTAL_FILE], 0, sizeof (spacedb[SPACEDB_TOTAL_FILE]));
  for (i = 0; i < SPACEDB_TOTAL_FILE; i++)
    {
      spacedb[SPACEDB_TOTAL_FILE].nfile += spacedb[i].nfile;
      spacedb[SPACEDB_TOTAL_FILE].npage_ftab += spacedb[i].npage_ftab;
      spacedb[SPACEDB_TOTAL_FILE].npage_user += spacedb[i].npage_user;
      spacedb[SPACEDB_TOTAL_FILE].npage_reserved += spacedb[i].npage_reserved;
    }
  return NO_ERROR;
}

/************************************************************************/
/* Numerable files section.                                             */
/************************************************************************/

/*
 * file_numerable_add_page () - Add a page at the end of user page table. This is part of the implementation for
 *              numerable files.
 *
 * return      : Error code
 * thread_p (in)   : Thread entry
 * page_fhead (in) : File header page
 * vpid (in)       : VPID of page to add to use page table.
 */
static int
file_numerable_add_page (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, const VPID * vpid)
{
  FILE_HEADER *fhead = (FILE_HEADER *) page_fhead;
  FILE_EXTENSIBLE_DATA *extdata_user_page_ftab = NULL;
  VPID vpid_fhead;
  PAGE_PTR page_ftab = NULL;
  PAGE_PTR page_extdata = NULL;
  LOG_LSA save_lsa;
  int error_code = NO_ERROR;

  assert (fhead != NULL);
  assert (vpid != NULL && !VPID_ISNULL (vpid));
  assert (FILE_IS_NUMERABLE (fhead));

  /* how it works:
   * we need to add the page at the end of user page table. the order of pages in numerable files is the same as their
   * allocation order, so we always add a page at the end.
   *
   * note: fhead->vpid_last_user_page_ftab is a hint to last page in user page table, where the VPID should be added.
   */

  FILE_GET_HEADER_VPID (&fhead->self, &vpid_fhead);

  /* we have a hint on where the page must be added */
  if (VPID_EQ (&fhead->vpid_last_user_page_ftab, &vpid_fhead))
    {
      /* hint points to file header */
      page_extdata = page_fhead;
      FILE_HEADER_GET_USER_PAGE_FTAB (fhead, extdata_user_page_ftab);
    }
  else
    {
      /* hint points to another table page. */
      page_ftab =
    pgbuf_fix (thread_p, &fhead->vpid_last_user_page_ftab, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_ftab == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
      page_extdata = page_ftab;
      extdata_user_page_ftab = (FILE_EXTENSIBLE_DATA *) page_ftab;
    }

  file_log ("file_numerable_add_page",
        "file %d|%d add page %d|%d to user page table \n"
        FILE_HEAD_FULL_MSG
        FILE_EXTDATA_MSG ("last user page table component"),
        VFID_AS_ARGS (&fhead->self), VPID_AS_ARGS (vpid),
        FILE_HEAD_FULL_AS_ARGS (fhead), FILE_EXTDATA_AS_ARGS (extdata_user_page_ftab));

  if (file_extdata_is_full (extdata_user_page_ftab))
    {
      /* no more room for new pages. allocate a new table page. */
      VPID vpid_ftab_new = VPID_INITIALIZER;

      if (FILE_IS_TEMPORARY (fhead))
    {
      /* we can have temporary numerable files */
      error_code = file_temp_alloc (thread_p, page_fhead, FILE_ALLOC_TABLE_PAGE, &vpid_ftab_new);
      if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          goto exit;
        }
    }
      else
    {
      error_code = file_perm_alloc (thread_p, page_fhead, FILE_ALLOC_TABLE_PAGE, &vpid_ftab_new);
      if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          goto exit;
        }
    }
      assert (!VPID_ISNULL (&vpid_ftab_new));

      /* create a link from previous page table. */
      if (!FILE_IS_TEMPORARY (fhead))
    {
      /* log setting next page */
      file_log_extdata_set_next (thread_p, extdata_user_page_ftab, page_extdata, &vpid_ftab_new);
    }
      else
    {
      /* just set dirty */
      pgbuf_set_dirty (thread_p, page_extdata, DONT_FREE);
    }
      VPID_COPY (&extdata_user_page_ftab->vpid_next, &vpid_ftab_new);
      if (page_ftab != NULL)
    {
      /* we don't need it anymore */
      pgbuf_set_dirty_and_free (thread_p, page_ftab);
    }

      /* fix new table page */
      page_ftab = pgbuf_fix (thread_p, &vpid_ftab_new, NEW_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_ftab == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
      page_extdata = page_ftab;
      pgbuf_set_page_ptype (thread_p, page_ftab, PAGE_FTAB);

      save_lsa = *pgbuf_get_lsa (page_fhead);
      if (!FILE_IS_TEMPORARY (fhead))
    {
      /* log that we are going to change fhead->vpid_last_page_ftab. */
      log_append_undoredo_data2 (thread_p, RVFL_FHEAD_SET_LAST_USER_PAGE_FTAB, NULL, page_fhead, 0, sizeof (VPID),
                     sizeof (VPID), &fhead->vpid_last_user_page_ftab, &vpid_ftab_new);
    }

      VPID_COPY (&fhead->vpid_last_user_page_ftab, &vpid_ftab_new);
      pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

      file_log ("file_numerable_add_page",
        "file %d|%d added new page %d|%d to user table; "
        "updated vpid_last_user_page_ftab in header page %d|%d, prev_lsa %lld|%d, crt_lsa %lld|%d ",
        VFID_AS_ARGS (&fhead->self), VPID_AS_ARGS (&vpid_ftab_new),
        PGBUF_PAGE_MODIFY_ARGS (page_fhead, &save_lsa));

      /* initialize new page table */
      extdata_user_page_ftab = (FILE_EXTENSIBLE_DATA *) page_ftab;
      file_extdata_init (sizeof (VPID), DB_PAGESIZE, extdata_user_page_ftab);

      if (!FILE_IS_TEMPORARY (fhead))
    {
      /* log changes. */
      pgbuf_log_new_page (thread_p, page_ftab, file_extdata_size (extdata_user_page_ftab), PAGE_FTAB);
    }
      else
    {
      /* just set dirty */
      pgbuf_set_dirty (thread_p, page_ftab, DONT_FREE);
    }
    }

  assert (!file_extdata_is_full (extdata_user_page_ftab));
  save_lsa = *pgbuf_get_lsa (page_extdata);
  if (!FILE_IS_TEMPORARY (fhead))
    {
      /* log changes */
      file_log_extdata_add (thread_p, extdata_user_page_ftab, page_extdata,
                file_extdata_item_count (extdata_user_page_ftab), 1, vpid);
    }
  else
    {
      /* just set dirty */
      pgbuf_set_dirty (thread_p, page_extdata, DONT_FREE);
    }
  file_extdata_append (extdata_user_page_ftab, vpid);

  file_log ("file_numerable_add_page",
        "add page %d|%d to position %d in file %d|%d, page %d|%d, prev_lsa = %lld|%d, crt_lsa = %lld|%d \n"
        FILE_EXTDATA_MSG ("last user page table component") FILE_HEAD_FULL_MSG, VPID_AS_ARGS (vpid),
        file_extdata_item_count (extdata_user_page_ftab) - 1, VFID_AS_ARGS (&fhead->self),
        PGBUF_PAGE_MODIFY_ARGS (page_extdata, &save_lsa), FILE_EXTDATA_AS_ARGS (extdata_user_page_ftab),
        FILE_HEAD_FULL_AS_ARGS (fhead));

  /* done */
  assert (error_code == NO_ERROR);

exit:
  if (page_ftab != NULL)
    {
      assert (page_ftab != page_fhead);
      pgbuf_unfix (thread_p, page_ftab);
    }

  return error_code;
}

/*
 * file_extdata_find_nth_vpid () - Function callable by file_extdata_apply_funcs. An optimal way of finding nth page
 *                 when there is no page marked as deleted.
 *
 * return    : NO_ERROR
 * thread_p (in) : Thread entry
 * extdata (in)  : Extensible data
 * stop (in)     : Output true when search must be stopped
 * args (in)     : FILE_FIND_NTH_CONTEXT *
 */
static int
file_extdata_find_nth_vpid (THREAD_ENTRY * thread_p, const FILE_EXTENSIBLE_DATA * extdata, bool * stop, void *args)
{
  FILE_FIND_NTH_CONTEXT *find_nth_context = (FILE_FIND_NTH_CONTEXT *) args;
  int count_vpid = file_extdata_item_count (extdata);

  if (count_vpid <= find_nth_context->nth)
    {
      /* not in this extensible data. continue searching. */
      find_nth_context->nth -= count_vpid;
      find_nth_context->first_index += count_vpid;
    }
  else
    {
      /* nth VPID is in this extensible data. get it and stop searching */
      VPID_COPY (find_nth_context->vpid_nth, (VPID *) file_extdata_at (extdata, find_nth_context->nth));
      assert (!FILE_USER_PAGE_IS_MARKED_DELETED (find_nth_context->vpid_nth));
      *stop = true;
    }

  return NO_ERROR;
}

/*
 * file_extdata_find_nth_vpid_and_skip_marked () - Function callable by file_extdata_apply_funcs. Used to find the nth
 *                         VPID not marked as deleted (numerable files).
 *
 * return        : NO_ERROR
 * thread_p (in) : Thread entry
 * data (in)     : Pointer in user page table (VPID *).
 * index (in)    : Not used.
 * stop (out)    : Output true when nth page is reached.
 * args (in/out) : FILE_FIND_NTH_CONTEXT *
 */
static int
file_extdata_find_nth_vpid_and_skip_marked (THREAD_ENTRY * thread_p,
                        const void *data, int index, bool * stop, void *args)
{
  FILE_FIND_NTH_CONTEXT *find_nth_context = (FILE_FIND_NTH_CONTEXT *) args;
  VPID *vpidp = (VPID *) data;

  if (FILE_USER_PAGE_IS_MARKED_DELETED (vpidp))
    {
      /* skip marked deleted */
      return NO_ERROR;
    }

  if (find_nth_context->nth == 0)
    {
      /* found nth */
      *find_nth_context->vpid_nth = *vpidp;
      *stop = true;
    }
  else
    {
      /* update nth */
      find_nth_context->nth--;
    }

  return NO_ERROR;
}

/*
 * file_numerable_find_nth () - Find nth page VPID in numerable file.
 *
 * return       : Error code
 * thread_p (in)    : Thread entry
 * vfid (in)        : File identifier
 * nth (in)     : Index of page
 * auto_alloc (in)  : True to allow file extension.
 * f_init (in)      : init page function (must not be NULL for permanent page allocation)
 * f_init_args (in) : arguments for init page function
 * vpid_nth (out)   : VPID at index
 */
int
file_numerable_find_nth (THREAD_ENTRY * thread_p, const VFID * vfid, int nth, bool auto_alloc,
             FILE_INIT_PAGE_FUNC f_init, void *f_init_args, VPID * vpid_nth)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  FILE_EXTENSIBLE_DATA *extdata_user_page_ftab = NULL;
  PAGE_PTR page_ftab_start = NULL;
  PAGE_PTR page_ftab_nth_location = NULL;
  FILE_FIND_NTH_CONTEXT find_nth_context;
  int error_code = NO_ERROR;

  assert (vfid != NULL && !VFID_ISNULL (vfid));
  assert (nth >= 0);
  assert (vpid_nth != NULL);

  VPID_SET_NULL (vpid_nth);

  /* how it works:
   * iterate through user page table, skipping pages marked as deleted, and decrement counter until it reaches 0.
   * save the VPID found on the nth position.
   * if there are no marked deleted pages, we can skip entire extensible data components and go directly to nth page.
   */

  /* get file header */
  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);
  assert (FILE_IS_NUMERABLE (fhead));
  assert (nth < fhead->n_page_user || (auto_alloc && nth == fhead->n_page_user));

  if (auto_alloc && nth == (fhead->n_page_user - fhead->n_page_mark_delete))
    {
      /* we need new page */
      /* todo: can this be simplified? */
      error_code = pgbuf_promote_read_latch (thread_p, &page_fhead, PGBUF_PROMOTE_SHARED_READER);
      if (error_code == ER_PAGE_LATCH_PROMOTE_FAIL)
    {
      /* re-fix page */
      pgbuf_unfix (thread_p, page_fhead);
      page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_fhead == NULL)
        {
          ASSERT_ERROR_AND_SET (error_code);
          goto exit;
        }
      fhead = (FILE_HEADER *) page_fhead;
      file_header_sanity_check (thread_p, fhead);
      if (auto_alloc && nth == (fhead->n_page_user - fhead->n_page_mark_delete))
        {
          error_code = file_alloc (thread_p, vfid, f_init, f_init_args, vpid_nth, NULL);
          if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
        }
          goto exit;
        }
    }
      else if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
      else if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }

      error_code = file_alloc (thread_p, vfid, f_init, f_init_args, vpid_nth, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
    }

      goto exit;
    }

  /* iterate in user page table */
  FILE_HEADER_GET_USER_PAGE_FTAB (fhead, extdata_user_page_ftab);
  find_nth_context.vpid_nth = vpid_nth;
  find_nth_context.nth = nth;

  if (fhead->n_page_mark_delete > 0)
    {
      /* we don't know where the marked deleted pages are... we need to iterate through all pages and skip the marked
       * deleted. */
      error_code = file_extdata_apply_funcs (thread_p, extdata_user_page_ftab, NULL, NULL,
                         file_extdata_find_nth_vpid_and_skip_marked, &find_nth_context, false,
                         NULL, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
    }
  else
    {
      if (FILE_CACHE_LAST_FIND_NTH (fhead, thread_p) && !VPID_ISNULL (&fhead->vpid_find_nth_last)
      && !VPID_EQ (&vpid_fhead, &fhead->vpid_find_nth_last) && nth >= fhead->first_index_find_nth_last)
    {
      /* start searching from last search location */
      page_ftab_start =
        pgbuf_fix (thread_p, &fhead->vpid_find_nth_last, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
      if (page_ftab_start == NULL)
        {
          ASSERT_ERROR_AND_SET (error_code);
          goto exit;
        }
      extdata_user_page_ftab = (FILE_EXTENSIBLE_DATA *) page_ftab_start;
      find_nth_context.first_index = fhead->first_index_find_nth_last;
      find_nth_context.nth -= fhead->first_index_find_nth_last;
    }
      else
    {
      /* no last search location or it could not be used. start searching from the beginning. */
      find_nth_context.first_index = 0;
    }
      /* we can go directly to the right VPID. */
      error_code = file_extdata_apply_funcs (thread_p, extdata_user_page_ftab, file_extdata_find_nth_vpid,
                         &find_nth_context, NULL, NULL, false, NULL, &page_ftab_nth_location);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

      if (FILE_CACHE_LAST_FIND_NTH (fhead, thread_p))
    {
      /* note that we consider this file cannot be accessed concurrently. therefore we do not promote to write latch
       * and we do not set page dirty to update the cached search location. */
      if (page_ftab_nth_location == NULL)
        {
          /* it was found in the starting page */
          page_ftab_nth_location = page_ftab_start != NULL ? page_ftab_start : page_fhead;
        }
      pgbuf_get_vpid (page_ftab_nth_location, &fhead->vpid_find_nth_last);
      fhead->first_index_find_nth_last = find_nth_context.first_index;

      file_log ("file_numerable_find_nth", "update fhead.fist_index_find_nth_last to %d "
            "and fhead->vpid_find_nth_last to %d|%d while searching nth=%d in file %d|%d",
            fhead->first_index_find_nth_last, VPID_AS_ARGS (&fhead->vpid_find_nth_last), nth,
            VFID_AS_ARGS (vfid));
    }
    }

  if (VPID_ISNULL (vpid_nth))
    {
      /* should not happen */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  assert (error_code == NO_ERROR);

exit:
  if (page_ftab_nth_location != NULL && page_ftab_nth_location != page_ftab_start
      && page_ftab_nth_location != page_fhead)
    {
      pgbuf_unfix (thread_p, page_ftab_nth_location);
    }
  if (page_ftab_start != NULL)
    {
      pgbuf_unfix (thread_p, page_ftab_start);
    }
  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }

  return error_code;
}

/*
 * file_rv_user_page_mark_delete () - Recover page mark delete.
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_user_page_mark_delete (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_ftab = rcv->pgptr;
  VPID *vpid_ptr = NULL;

  vpid_ptr = (VPID *) (page_ftab + rcv->offset);
  assert (!FILE_USER_PAGE_IS_MARKED_DELETED (vpid_ptr));
  FILE_USER_PAGE_MARK_DELETED (vpid_ptr);

  file_log ("file_rv_user_page_mark_delete",
        "marked deleted vpid %d|%d in page %d|%d lsa %lld|%d at offset %d",
        VPID_AS_ARGS (vpid_ptr), PGBUF_PAGE_STATE_ARGS (page_ftab), rcv->offset);

  pgbuf_set_dirty (thread_p, page_ftab, DONT_FREE);
  return NO_ERROR;
}

/*
 * file_rv_user_page_unmark_delete_logical () - Recover page unmark delete (logical)
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_user_page_unmark_delete_logical (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_fhead;
  PAGE_PTR page_ftab;
  FILE_HEADER *fhead;
  VFID *vfid;
  VPID *vpid;
  VPID *vpid_in_table;
  VPID vpid_fhead;
  FILE_EXTENSIBLE_DATA *extdata_user_page_ftab;
  LOG_DATA_ADDR addr = LOG_DATA_ADDR_INITIALIZER;
  bool found = false;
  int position = -1;
  int offset = 0;
  LOG_LSA save_lsa;
  int error_code = NO_ERROR;

  /* how it works
   * when we mark a page as deleted in user page table and unfix file header, the user page table can be modified by
   * concurrent transactions. if allocations are always appended at the end, deallocations will remove VPID's from
   * user page table and can sometime merge pages. which means that by the time we get to undo our change, the location
   * of this page becomes uncertain.
   *
   * therefore, removing mark for deletion should lookup VPID in table.
   */

  vfid = (VFID *) (rcv->data + offset);
  offset += sizeof (*vfid);

  vpid = (VPID *) (rcv->data + offset);
  offset += sizeof (*vpid);

  assert (offset == rcv->length);

  vpid_fhead.volid = vfid->volid;
  vpid_fhead.pageid = vfid->fileid;
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      /* Should not be interrupted. */
      assert_release (false);
      return ER_FAILED;
    }

  fhead = (FILE_HEADER *) page_fhead;

  assert (FILE_IS_NUMERABLE (fhead));
  assert (!FILE_IS_TEMPORARY (fhead));

  /* search for VPID in user page table */
  FILE_HEADER_GET_USER_PAGE_FTAB (fhead, extdata_user_page_ftab);
  error_code =
    file_extdata_search_item (thread_p, &extdata_user_page_ftab, vpid,
                  file_compare_vpids, false, true, &found, &position, &page_ftab);
  if (error_code != NO_ERROR)
    {
      /* errors not expected during recovery. */
      assert_release (false);
      goto exit;
    }
  if (!found)
    {
      /* should be found. */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  /* go to VPID in current extensible data */
  vpid_in_table = (VPID *) file_extdata_at (extdata_user_page_ftab, position);
  assert (FILE_USER_PAGE_IS_MARKED_DELETED (vpid_in_table));

  FILE_USER_PAGE_CLEAR_MARK_DELETED (vpid_in_table);
  assert (VPID_EQ (vpid, vpid_in_table));

  /* compensate logging. */
  addr.pgptr = page_ftab != NULL ? page_ftab : page_fhead;
  addr.offset = (PGLENGTH) (((char *) vpid_in_table) - addr.pgptr);
  save_lsa = *pgbuf_get_lsa (addr.pgptr);
  log_append_compensate (thread_p, RVFL_USER_PAGE_MARK_DELETE_COMPENSATE, pgbuf_get_vpid_ptr (addr.pgptr), addr.offset,
             addr.pgptr, 0, NULL, LOG_FIND_CURRENT_TDES (thread_p));

  file_log ("file_rv_user_page_unmark_delete_logical",
        "unmark delete vpid %d|%d in file %d|%d, page %d|%d, "
        "prev_lsa %lld|%d, crt_lsa %lld|%d, at offset %d",
        VPID_AS_ARGS (vpid_in_table), VFID_AS_ARGS (vfid), PGBUF_PAGE_MODIFY_ARGS (addr.pgptr, &save_lsa),
        addr.offset);

  pgbuf_set_dirty (thread_p, addr.pgptr, DONT_FREE);

  /* done */
  assert (error_code == NO_ERROR);

exit:
  if (page_ftab != NULL)
    {
      pgbuf_unfix (thread_p, page_ftab);
    }
  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }
  return error_code;
}

/*
 * file_rv_user_page_unmark_delete_physical () - Recover page unmark delete (physical)
 *
 * return    : Error code
 * thread_p (in) : Thread entry
 * rcv (in)  : Recovery data
 */
int
file_rv_user_page_unmark_delete_physical (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_ftab = rcv->pgptr;
  VPID *vpid_ptr = NULL;

  /* note: this is used to compensate undo of mark delete. this time the location of VPID is known */

  vpid_ptr = (VPID *) (page_ftab + rcv->offset);
  assert (FILE_USER_PAGE_IS_MARKED_DELETED (vpid_ptr));

  FILE_USER_PAGE_CLEAR_MARK_DELETED (vpid_ptr);

  file_log ("file_rv_user_page_unmark_delete_physical",
        "unmark delete vpid %d|%d in page %d|%d, lsa %lld|%d, "
        "at offset %d", VPID_AS_ARGS (vpid_ptr), PGBUF_PAGE_STATE_ARGS (page_ftab), rcv->offset);

  pgbuf_set_dirty (thread_p, page_ftab, DONT_FREE);
  return NO_ERROR;
}

/*
 * file_table_check_page_is_in_sectors () - FILE_EXTDATA_ITEM_FUNC to check user page table is in one of the sectors
 *
 * return        : error code
 * thread_p (in) : thread entry
 * data (in)     : user page table entry
 * index (in)    : index of user page table entry
 * stop (in)     : not used
 * args (in)     : FILE_VSID_COLLECTOR *
 */
static int
file_table_check_page_is_in_sectors (THREAD_ENTRY * thread_p, const void *data, int index, bool * stop, void *args)
{
  FILE_VSID_COLLECTOR *collector = (FILE_VSID_COLLECTOR *) args;
  VPID vpid = *(VPID *) data;
  VSID vsid_of_vpid;

  FILE_USER_PAGE_CLEAR_MARK_DELETED (&vpid);
  VSID_FROM_VPID (&vsid_of_vpid, &vpid);

  if (bsearch (&vsid_of_vpid, collector->vsids, collector->n_vsids, sizeof (VSID), disk_compare_vsids) == NULL)
    {
      /* not found! */
      assert_release (false);
      return ER_FAILED;
    }
  return NO_ERROR;
}

/*
 * file_numerable_truncate () - truncate numerable files to a smaller number of pages
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 * npages (in)   : desired number of pages
 */
int
file_numerable_truncate (THREAD_ENTRY * thread_p, const VFID * vfid, DKNPAGES npages)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead;
  VPID vpid;
  int error_code = NO_ERROR;

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return ER_FAILED;
    }
  fhead = (FILE_HEADER *) page_fhead;

  if (!FILE_IS_NUMERABLE (fhead))
    {
      /* cannot truncate */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  if (fhead->n_page_mark_delete != 0)
    {
      /* I am not sure what we should do in this case. We give up truncating and in debug it will crash. */
      assert (false);
      return NO_ERROR;
    }

  while (fhead->n_page_user > npages)
    {
      /* maybe this can be done in a more optimal way... but for now it will have to do */
      error_code = file_numerable_find_nth (thread_p, vfid, npages, false, NULL, NULL, &vpid);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
      error_code = file_dealloc (thread_p, vfid, &vpid, fhead->type);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
    }
  assert (fhead->n_page_user == npages);
  assert (error_code == NO_ERROR);

exit:
  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }
  return error_code;
}

/************************************************************************/
/* Temporary files section.                                             */
/************************************************************************/

/*
 * file_temp_alloc () - Allocate a new page in temporary file.
 *
 * return       : Error code
 * thread_p (in)    : Thread entry
 * page_fhead (in)  : File header page
 * alloc_type (in)  : User/table page
 * vpid_alloc_out (out) : Output allocated page VPID.
 */
static int
file_temp_alloc (THREAD_ENTRY * thread_p, PAGE_PTR page_fhead, FILE_ALLOC_TYPE alloc_type, VPID * vpid_alloc_out)
{
  FILE_HEADER *fhead = (FILE_HEADER *) page_fhead;
  VPID vpid_fhead;
  FILE_EXTENSIBLE_DATA *extdata_part_ftab = NULL;
  PAGE_PTR page_ftab = NULL;
  FILE_PARTIAL_SECTOR *partsect = NULL;
  bool was_empty = false;
  bool is_full = false;
  /* we don't have rollback, so don't interrupt */
  bool save_check_interrupt = logtb_set_check_interrupt (thread_p, false);
  int error_code = NO_ERROR;

  file_header_sanity_check (thread_p, fhead);
  assert (FILE_IS_TEMPORARY (fhead));
  assert (page_fhead != NULL);
  assert (vpid_alloc_out != NULL);

  /* how it works
   * temporary files, compared to permanent files, have a simplified design. they do not keep two different tables
   * (partial and full). they only keep the partial table, and when sectors become full, they remain in the partial
   * table.
   * the second difference is that temporary files never deallocate pages. since they are temporary, and soon to be
   * freed (or cached for reuse), there is no point in deallocating pages.
   * the simplified design was chosen because temporary files are never logged. and it is hard to undo changes without
   * logging when errors happen (e.g. interrupted transaction).
   *
   * all we have to do to allocate a page is to try to allocate from last used sector. if that sector is full, advance
   * to next sector. if no new sectors, reserve a new one. that's all.
   */

  file_log ("file_temp_alloc", "%s", FILE_ALLOC_TYPE_STRING (alloc_type));

  FILE_GET_HEADER_VPID (&fhead->self, &vpid_fhead);

  /* get page for last allocated partial table */
  if (VPID_EQ (&vpid_fhead, &fhead->vpid_last_temp_alloc))
    {
      /* partial table in header */
      FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
    }
  else
    {
      page_ftab =
    pgbuf_fix (thread_p, &fhead->vpid_last_temp_alloc, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_ftab == NULL)
    {
      error_code = er_errid ();
      if (error_code != ER_INTERRUPTED)
        {
          assert_release (false);
        }

      goto exit;
    }
      extdata_part_ftab = (FILE_EXTENSIBLE_DATA *) page_ftab;
    }

  if (fhead->n_page_free == 0)
    {
      /* expand file by one sector */
      FILE_PARTIAL_SECTOR partsect_new = FILE_PARTIAL_SECTOR_INITIALIZER;

      /* reserve a sector */
      error_code =
    disk_reserve_sectors (thread_p, DB_TEMPORARY_DATA_PURPOSE, fhead->volid_last_expand, 1, &partsect_new.vsid);
      if (error_code != NO_ERROR)
    {
      error_code = er_errid ();
      if (error_code != ER_INTERRUPTED)
        {
          assert_release (false);
        }

      goto exit;
    }

      file_log ("file_temp_alloc",
        "no free pages" FILE_HEAD_ALLOC_MSG "\texpand file with VSID = %d|%d ",
        FILE_HEAD_ALLOC_AS_ARGS (fhead), VSID_AS_ARGS (&partsect_new.vsid));

      assert (extdata_part_ftab != NULL);
      if (file_extdata_is_full (extdata_part_ftab))
    {
      /* we need a new page. */
      /* we will use first page of newly reserved sector. */
      VPID vpid_ftab_new;
      PAGE_PTR page_ftab_new = NULL;

      vpid_ftab_new.volid = partsect_new.vsid.volid;
      vpid_ftab_new.pageid = SECTOR_FIRST_PAGEID (partsect_new.vsid.sectid);
      file_partsect_set_bit (&partsect_new, 0);

      /* fix new file table page */
      page_ftab_new = pgbuf_fix (thread_p, &vpid_ftab_new, NEW_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_ftab_new == NULL)
        {
          error_code = ER_FAILED;

          /* todo: unreserve sector */
          goto exit;
        }
      pgbuf_set_page_ptype (thread_p, page_ftab_new, PAGE_FTAB);

      /* set link to previous page. */
      VPID_COPY (&extdata_part_ftab->vpid_next, &vpid_ftab_new);

      /* we don't need old file table page anymore */
      assert (page_ftab != page_fhead);
      if (page_ftab != NULL)
        {
          pgbuf_set_dirty_and_free (thread_p, page_ftab);
        }

      VPID_COPY (&fhead->vpid_last_temp_alloc, &vpid_ftab_new);
      fhead->offset_to_last_temp_alloc = 0;
      pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

      page_ftab = page_ftab_new;
      extdata_part_ftab = (FILE_EXTENSIBLE_DATA *) page_ftab;
      file_extdata_init (sizeof (FILE_PARTIAL_SECTOR), DB_PAGESIZE, extdata_part_ftab);

      file_log ("file_temp_alloc",
            "used newly reserved sector's first page %d|%d for partial table.", VPID_AS_ARGS (&vpid_ftab_new));

      /* update temporary file stats - DISK_SECTOR_NPAGES -1 reserved pages and 1 file table page. */
      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_reserved, DISK_SECTOR_NPAGES - 1);
      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_ftab, 1);
    }
      else
    {
      /* update temporary file stats - DISK_SECTOR_NPAGES reserved pages */
      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_reserved, DISK_SECTOR_NPAGES);
    }
      assert (!file_extdata_is_full (extdata_part_ftab));
      assert (file_extdata_item_count (extdata_part_ftab) == fhead->offset_to_last_temp_alloc);
      file_extdata_append (extdata_part_ftab, &partsect_new);

      fhead->n_sector_partial++;
      fhead->n_sector_total++;
      fhead->n_page_free += DISK_SECTOR_NPAGES;
      fhead->n_page_total += DISK_SECTOR_NPAGES;
      if (partsect_new.page_bitmap == FILE_EMPTY_PAGE_BITMAP)
    {
      fhead->n_sector_empty++;
    }
      else
    {
      fhead->n_page_free--;
      fhead->n_page_ftab++;
    }

      file_log ("file_temp_alloc",
        "new partial sector added to partial extensible data:\n"
        FILE_PARTSECT_MSG ("newly reserved sector")
        FILE_EXTDATA_MSG ("last partial table component"),
        FILE_PARTSECT_AS_ARGS (&partsect_new), FILE_EXTDATA_AS_ARGS (extdata_part_ftab));
    }
  assert (fhead->n_page_free > 0);

  if (fhead->offset_to_last_temp_alloc == file_extdata_item_count (extdata_part_ftab))
    {
      VPID vpid_next;

      /* must be full */
      assert (file_extdata_is_full (extdata_part_ftab));
      /* we must have another extensible data */
      assert (!VPID_ISNULL (&extdata_part_ftab->vpid_next));

      /* move allocation cursor to next partial table page */
      vpid_next = extdata_part_ftab->vpid_next;
      if (page_ftab != NULL)
    {
      pgbuf_unfix_and_init (thread_p, page_ftab);
    }
      page_ftab = pgbuf_fix (thread_p, &vpid_next, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_ftab == NULL)
    {
      error_code = er_errid ();
      if (error_code != ER_INTERRUPTED)
        {
          assert_release (false);
        }

      goto exit;
    }
      extdata_part_ftab = (FILE_EXTENSIBLE_DATA *) page_ftab;

      fhead->vpid_last_temp_alloc = vpid_next;
      fhead->offset_to_last_temp_alloc = 0;
    }

  assert (extdata_part_ftab != NULL);
  assert (fhead->offset_to_last_temp_alloc < file_extdata_item_count (extdata_part_ftab));
  partsect = (FILE_PARTIAL_SECTOR *) file_extdata_at (extdata_part_ftab, fhead->offset_to_last_temp_alloc);

  /* Allocate the page. */
  was_empty = file_partsect_is_empty (partsect);
  if (!file_partsect_alloc (partsect, vpid_alloc_out, NULL))
    {
      /* full sector? that is unexpected. we must have a logic error. */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }
  if (file_partsect_is_full (partsect))
    {
      is_full = true;
      fhead->offset_to_last_temp_alloc++;
    }

  file_header_alloc (fhead, alloc_type, was_empty, is_full);
  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

  file_log ("file_temp_alloc", "%s %d|%d successful. \n" FILE_HEAD_FULL_MSG
        FILE_PARTSECT_MSG ("partial sector after alloc"),
        FILE_ALLOC_TYPE_STRING (alloc_type),
        VPID_AS_ARGS (vpid_alloc_out), FILE_HEAD_FULL_AS_ARGS (fhead), FILE_PARTSECT_AS_ARGS (partsect));

  if (page_ftab != NULL)
    {
      assert (page_ftab != page_fhead);
      pgbuf_set_dirty_and_free (thread_p, page_ftab);
    }

  /* update temporary file stats */
  if (alloc_type == FILE_ALLOC_USER_PAGE)
    {
      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_user, 1);
    }
  else
    {
      ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_ftab, 1);
    }
  ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_reserved, -1);

  /* done */
  assert (error_code == NO_ERROR);

exit:
  file_header_sanity_check (thread_p, fhead);
  if (page_ftab != NULL)
    {
      pgbuf_unfix_and_init (thread_p, page_ftab);
    }
  (void) logtb_set_check_interrupt (thread_p, save_check_interrupt);
  return error_code;
}

/*
 * file_temp_set_type () - set new type in existing temporary file
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 * ftype (in)    : new file type
 */
STATIC_INLINE int
file_temp_set_type (THREAD_ENTRY * thread_p, VFID * vfid, FILE_TYPE ftype)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  int error_code = NO_ERROR;

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  if (!FILE_IS_TEMPORARY (fhead))
    {
      /* we cannot change type */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  fhead->type = ftype;
  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);

exit:
  pgbuf_unfix (thread_p, page_fhead);
  return error_code;
}

/*
 * file_temp_reset_user_pages () - reset all user pages in temporary file.
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 */
static int
file_temp_reset_user_pages (THREAD_ENTRY * thread_p, const VFID * vfid)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  FILE_FTAB_COLLECTOR collector = FILE_FTAB_COLLECTOR_INITIALIZER;
  FILE_EXTENSIBLE_DATA *extdata_part_ftab = NULL;
  PAGE_PTR page_ftab = NULL;
  FILE_PARTIAL_SECTOR *partsect = NULL;
  int idx_sect = 0;
  VPID vpid_next;
  int nsect_part_new;
  int nsect_full_new;
  int nsect_empty_new;
  FILE_EXTENSIBLE_DATA *extdata_user_page_ftab;
  bool save_interrupt, is_partial;
  bool found = false;
  int error_code = NO_ERROR;

  /* don't let this be interrupted, because we might ruin the file */
  save_interrupt = logtb_set_check_interrupt (thread_p, false);

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  if (!FILE_IS_TEMPORARY (fhead))
    {
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  if (FILE_IS_NUMERABLE (fhead))
    {
      /* reset user page table */
      FILE_HEADER_GET_USER_PAGE_FTAB (fhead, extdata_user_page_ftab);
      VPID_SET_NULL (&extdata_user_page_ftab->vpid_next);
      extdata_user_page_ftab->n_items = 0;
      fhead->vpid_last_user_page_ftab = vpid_fhead;
      fhead->vpid_find_nth_last = vpid_fhead;
      fhead->first_index_find_nth_last = 0;
    }

  /* collect table pages */
  error_code = file_table_collect_ftab_pages (thread_p, page_fhead, false, &collector);
  if (error_code != NO_ERROR)
    {
      assert_release (false);
      goto exit;
    }

  /* dealloc temp page */
  FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
  is_partial = true;
  error_code =
    file_extdata_apply_funcs (thread_p, extdata_part_ftab, NULL, NULL, file_sector_map_dealloc_temp, &is_partial, true,
                  NULL, NULL);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

  /* count partial, full, empty */
  nsect_full_new = 0;
  for (idx_sect = 0; idx_sect < collector.nsects; idx_sect++)
    {
      if (collector.partsect_ftab[idx_sect].page_bitmap == FILE_FULL_PAGE_BITMAP)
    {
      nsect_full_new++;
    }
    }
  nsect_part_new = collector.nsects - nsect_full_new;
  nsect_empty_new = fhead->n_sector_total - collector.nsects;
  nsect_part_new += nsect_empty_new;

  /* reset partial table sectors, but leave file table pages allocated */
  page_ftab = page_fhead;
  FILE_HEADER_GET_PART_FTAB (fhead, extdata_part_ftab);
  while (true)
    {
      assert (extdata_part_ftab != NULL);

      for (partsect = (FILE_PARTIAL_SECTOR *) file_extdata_start (extdata_part_ftab);
       partsect < (FILE_PARTIAL_SECTOR *) file_extdata_end (extdata_part_ftab); partsect++)
    {
      /* does it have file table pages? */
      found = false;
      for (idx_sect = 0; idx_sect < collector.nsects; idx_sect++)
        {
          if (disk_compare_vsids (&partsect->vsid, &collector.partsect_ftab[idx_sect].vsid) == 0)
        {
          /* get bitmap from collector */
          partsect->page_bitmap = collector.partsect_ftab[idx_sect].page_bitmap;

          /* sector cannot be found again. remove it from collector */
          if (idx_sect < collector.nsects - 1)
            {
              memmove (&collector.partsect_ftab[idx_sect],
                   &collector.partsect_ftab[idx_sect + 1],
                   (collector.nsects - 1 - idx_sect) * sizeof (FILE_PARTIAL_SECTOR));
            }
          collector.nsects--;
          found = true;
          break;
        }
        }
      if (!found)
        {
          /* not found in collector, must be empty sector */
          partsect->page_bitmap = FILE_EMPTY_PAGE_BITMAP;
        }
    }

      pgbuf_set_dirty (thread_p, page_ftab, DONT_FREE);

      vpid_next = extdata_part_ftab->vpid_next;
      if (page_ftab != page_fhead)
    {
      pgbuf_unfix (thread_p, page_ftab);
    }
      page_ftab = NULL;
      if (VPID_ISNULL (&vpid_next))
    {
      break;
    }
      page_ftab = pgbuf_fix (thread_p, &vpid_next, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_ftab == NULL)
    {
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }
      extdata_part_ftab = (FILE_EXTENSIBLE_DATA *) page_ftab;
    }
  /* partial table sectors have been reset */

  /* update header */
  fhead->n_sector_empty = nsect_empty_new;
  fhead->n_sector_partial = nsect_part_new;
  fhead->n_sector_full = nsect_full_new;

  /* also update temporary files global stats */
  ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_ftab, collector.npages - fhead->n_page_ftab);
  fhead->n_page_ftab = collector.npages;
  ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_user, -fhead->n_page_user);
  fhead->n_page_user = 0;
  ATOMIC_INC_32 (&file_Tempcache.spacedb_temp.npage_reserved,
         fhead->n_page_total - fhead->n_page_ftab - fhead->n_page_free);
  fhead->n_page_free = fhead->n_page_total - fhead->n_page_ftab;

  /* reset pointers used for allocations */
  fhead->vpid_last_temp_alloc = vpid_fhead;
  fhead->offset_to_last_temp_alloc = 0;

  file_log ("file_temp_reset_user_pages", "finished \n" FILE_HEAD_FULL_MSG, FILE_HEAD_FULL_AS_ARGS (fhead));

  pgbuf_set_dirty (thread_p, page_fhead, DONT_FREE);
  /* done */
  assert (error_code == NO_ERROR);

exit:
  assert (page_ftab == NULL);

  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }

  (void) logtb_set_check_interrupt (thread_p, save_interrupt);

  if (collector.partsect_ftab != NULL)
    {
      db_private_free (thread_p, collector.partsect_ftab);
    }

  return error_code;
}

/*
 * file_temp_preserve () - preserve temporary file
 *
 * return :
 * THREAD_ENTRY * thread_p (in) :
 * const VFID * vfid (in) :
 */
void
file_temp_preserve (THREAD_ENTRY * thread_p, const VFID * vfid)
{
  /* to preserve the file, we need to remove it from transaction list */
  FILE_TEMPCACHE_ENTRY *entry = NULL;

  assert (vfid != NULL && !VFID_ISNULL (vfid));

  entry = file_tempcache_pop_tran_file (thread_p, vfid);
  if (entry == NULL)
    {
      assert_release (false);
    }
  else
    {
      file_tempcache_retire_entry (entry);
    }
}

/************************************************************************/
/* Temporary cache section                                              */
/************************************************************************/

/*
 * file_tempcache_init () - init temporary file cache
 *
 * return : ER_OUT_OF_VIRTUAL_MEMORY or NO_ERROR
 */
static int
file_tempcache_init (void)
{
  int memsize = 0;
#if defined (SERVER_MODE)
  int ntrans = logtb_get_number_of_total_tran_indices () + 1;
#else
  int ntrans = 1;
#endif

  /* file_Tempcache.tran_files cannot be NULL if file_Tempcache is initialized */
  assert (file_Tempcache.tran_files == NULL);

  /* initialize free entry list... used to avoid entry allocation/deallocation */
  file_Tempcache.free_entries = NULL;
  file_Tempcache.nfree_entries_max = ntrans * 8;    /* I set 8 per transaction, maybe there is a better value */
  file_Tempcache.nfree_entries = 0;

  /* initialize temporary file cache. we keep two separate lists for numerable and regular files */
  file_Tempcache.cached_not_numerable = NULL;
  file_Tempcache.cached_numerable = NULL;
  file_Tempcache.ncached_max = prm_get_integer_value (PRM_ID_MAX_ENTRIES_IN_TEMP_FILE_CACHE);
  file_Tempcache.ncached_not_numerable = 0;
  file_Tempcache.ncached_numerable = 0;

  /* initialize mutex used to protect temporary file cache and entry allocation/deallocation */
  pthread_mutex_init (&file_Tempcache.mutex, NULL);
#if !defined (NDEBUG)
  file_Tempcache.owner_mutex = -1;
#endif

  /* allocate transaction temporary files lists */
  memsize = ntrans * sizeof (FILE_TEMPCACHE_TRAN_ENTRY);
  file_Tempcache.tran_files = (FILE_TEMPCACHE_TRAN_ENTRY *) malloc (memsize);
  if (file_Tempcache.tran_files == NULL)
    {
      pthread_mutex_destroy (&file_Tempcache.mutex);
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, memsize);
      return ER_OUT_OF_VIRTUAL_MEMORY;
    }
  memset (file_Tempcache.tran_files, 0, memsize);


  for (int i = 0; i < ntrans; i++)
    {
      pthread_mutex_init (&file_Tempcache.tran_files[i].mutex, NULL);
#if !defined (NDEBUG)
      file_Tempcache.tran_files[i].owner_mutex = -1;
#endif
    }

  /* stats */
  memset (&file_Tempcache.spacedb_temp, 0, sizeof (file_Tempcache.spacedb_temp));

  /* all ok */
  return NO_ERROR;
}

/*
 * file_tempcache_final () - free temporary files cache resources
 *
 * return : void
 */
static void
file_tempcache_final (void)
{
  int tran = 0;
  int ntrans;

  /* file_Tempcache.tran_files cannot be NULL if file_Tempcache is initialized */
  if (file_Tempcache.tran_files == NULL)
    {
      return;
    }

#if defined (SERVER_MODE)
  ntrans = logtb_get_number_of_total_tran_indices ();
#else
  ntrans = 1;
#endif

  file_tempcache_lock ();

  /* free all transaction lists... they should be empty anyway, but be conservative */
  for (tran = 0; tran < ntrans; tran++)
    {
      file_tempcache_lock_tran_entry (&file_Tempcache.tran_files[tran]);
      if (file_Tempcache.tran_files[tran].head != NULL)
    {
      /* should be empty */
      file_tempcache_free_entry_list (&file_Tempcache.tran_files[tran].head);
    }
      file_tempcache_unlock_tran_entry (&file_Tempcache.tran_files[tran]);
      pthread_mutex_destroy (&file_Tempcache.tran_files[tran].mutex);
    }
  free_and_init (file_Tempcache.tran_files);

  /* temporary volumes are removed, we don't have to destroy files */
  file_tempcache_free_entry_list (&file_Tempcache.cached_not_numerable);
  file_tempcache_free_entry_list (&file_Tempcache.cached_numerable);

  file_tempcache_free_entry_list (&file_Tempcache.free_entries);

  file_tempcache_unlock ();

  pthread_mutex_destroy (&file_Tempcache.mutex);
}

/*
 * file_tempcache_free_entry_list () - free entry list and set it to NULL
 *
 * return        : void
 * list (in/out) : list to free. it becomes NULL.
 */
STATIC_INLINE void
file_tempcache_free_entry_list (FILE_TEMPCACHE_ENTRY ** list)
{
  FILE_TEMPCACHE_ENTRY *entry;
  FILE_TEMPCACHE_ENTRY *next;

  file_tempcache_check_lock ();

  for (entry = *list; entry != NULL; entry = next)
    {
      next = entry->next;
      free (entry);
    }
  *list = NULL;
}

/*
 * file_tempcache_alloc_entry () - allocate a new file temporary cache entry
 *
 * return      : error code
 * entry (out) : entry from free entries or newly allocated
 */
STATIC_INLINE int
file_tempcache_alloc_entry (FILE_TEMPCACHE_ENTRY ** entry)
{
  file_tempcache_check_lock ();

  if (file_Tempcache.free_entries != NULL)
    {
      assert (file_Tempcache.nfree_entries > 0);

      *entry = file_Tempcache.free_entries;
      file_Tempcache.free_entries = file_Tempcache.free_entries->next;
      file_Tempcache.nfree_entries--;
    }
  else
    {
      *entry = (FILE_TEMPCACHE_ENTRY *) malloc (sizeof (FILE_TEMPCACHE_ENTRY));
      if (*entry == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, sizeof (FILE_TEMPCACHE_ENTRY));
      return ER_OUT_OF_VIRTUAL_MEMORY;
    }
    }

  (*entry)->next = NULL;
  VFID_SET_NULL (&(*entry)->vfid);
  (*entry)->ftype = FILE_UNKNOWN_TYPE;

  return NO_ERROR;
}

/*
 * file_tempcache_retire_entry () - retire entry to free entry list (if not maxed) or deallocate
 *
 * return     : void
 * entry (in) : retired entry
 */
STATIC_INLINE void
file_tempcache_retire_entry (FILE_TEMPCACHE_ENTRY * entry)
{
  /* we lock to change free entry list */
  file_tempcache_lock ();

  if (file_Tempcache.nfree_entries < file_Tempcache.nfree_entries_max)
    {
      entry->next = file_Tempcache.free_entries;
      file_Tempcache.free_entries = entry;
      file_Tempcache.nfree_entries++;
    }
  else
    {
      free (entry);
    }

  file_tempcache_unlock ();
}

/*
 * file_tempcache_lock () - lock temporary cache mutex
 *
 * return : void
 */
STATIC_INLINE void
file_tempcache_lock (void)
{
  assert (file_Tempcache.owner_mutex != thread_get_current_entry_index ());
  pthread_mutex_lock (&file_Tempcache.mutex);
  assert (file_Tempcache.owner_mutex == -1);
#if !defined (NDEBUG)
  file_Tempcache.owner_mutex = thread_get_current_entry_index ();
#endif /* !NDEBUG */
}

/*
 * file_tempcache_unlock () - unlock temporary cache mutex
 *
 * return : void
 */
STATIC_INLINE void
file_tempcache_unlock (void)
{
  assert (file_Tempcache.owner_mutex == thread_get_current_entry_index ());
#if !defined (NDEBUG)
  file_Tempcache.owner_mutex = -1;
#endif /* !NDEBUG */
  pthread_mutex_unlock (&file_Tempcache.mutex);
}

/*
 * file_tempcache_check_lock () - check temporary cache mutex is locked (for debug)
 *
 * return : void
 */
STATIC_INLINE void
file_tempcache_check_lock (void)
{
  assert (file_Tempcache.owner_mutex == thread_get_current_entry_index ());
}

/*
 * file_tempcache_get () - get a file from temporary file cache
 *
 * return         : error code
 * thread_p (in)  : thread entry
 * ftype (in)     : file type
 * numerable (in) : true for numerable file, false for regular file
 * entry (out)    : always output an temporary cache entry. caller must check entry VFID to find if cached file was used
 */
STATIC_INLINE int
file_tempcache_get (THREAD_ENTRY * thread_p, FILE_TYPE ftype, bool numerable, FILE_TEMPCACHE_ENTRY ** entry)
{
  int error_code = NO_ERROR;

  assert (entry != NULL && *entry == NULL);

  file_tempcache_lock ();

  *entry = numerable ? file_Tempcache.cached_numerable : file_Tempcache.cached_not_numerable;
  if (*entry != NULL && (*entry)->ftype != ftype)
    {
      /* change type */
      error_code = file_temp_set_type (thread_p, &(*entry)->vfid, ftype);
      if (error_code != NO_ERROR)
    {
      /* could not change it, give up */
      *entry = NULL;
    }
      else
    {
      (*entry)->ftype = ftype;
    }
    }

  if (*entry != NULL)
    {
      /* remove from cache */
      if (numerable)
    {
      assert (*entry == file_Tempcache.cached_numerable);
      assert (file_Tempcache.ncached_numerable > 0);

      file_Tempcache.cached_numerable = file_Tempcache.cached_numerable->next;
      file_Tempcache.ncached_numerable--;
    }
      else
    {
      assert (*entry == file_Tempcache.cached_not_numerable);
      assert (file_Tempcache.ncached_not_numerable > 0);

      file_Tempcache.cached_not_numerable = file_Tempcache.cached_not_numerable->next;
      file_Tempcache.ncached_not_numerable--;
    }

      (*entry)->next = NULL;

      file_log ("file_tempcache_get",
        "found in cache temporary file entry "
        FILE_TEMPCACHE_ENTRY_MSG ", %s\n" FILE_TEMPCACHE_MSG,
        FILE_TEMPCACHE_ENTRY_AS_ARGS (*entry), numerable ? "numerable" : "regular", FILE_TEMPCACHE_AS_ARGS);

      file_tempcache_unlock ();
      return NO_ERROR;
    }

  /* not from cache, get a new entry */
  error_code = file_tempcache_alloc_entry (entry);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      file_tempcache_unlock ();
      return error_code;
    }

  /* init new entry */
  assert (*entry != NULL);
  (*entry)->next = NULL;
  (*entry)->ftype = ftype;
  VFID_SET_NULL (&(*entry)->vfid);

  file_tempcache_unlock ();

  return NO_ERROR;
}

/*
 * file_tempcache_check_duplicate () - check file cache whether it already has the entry to be added
 *
 * return        : true if file is already in file cache, otherwise false
 * thread_p (in) : thread entry
 * entry (in)    : entry of temporary file to be added to file cache
 * is_numerable(in) : is numerable temporary file
 *
 * note it is a debugging function.
 */
static bool
file_tempcache_check_duplicate (THREAD_ENTRY * thread_p, FILE_TEMPCACHE_ENTRY * entry, bool is_numerable)
{
  FILE_TEMPCACHE_ENTRY *p;

  assert (entry != NULL);
  assert (!VFID_ISNULL (&entry->vfid));

  if (is_numerable)
    {
      for (p = file_Tempcache.cached_numerable; p != NULL; p = p->next)
    {
      if (VFID_EQ (&p->vfid, &entry->vfid))
        {
          assert (!VFID_EQ (&p->vfid, &entry->vfid));
          return true;
        }
    }
    }
  else
    {
      for (p = file_Tempcache.cached_not_numerable; p != NULL; p = p->next)
    {
      if (VFID_EQ (&p->vfid, &entry->vfid))
        {
          assert (!VFID_EQ (&p->vfid, &entry->vfid));
          return true;
        }
    }
    }

  return false;
}

/*
 * file_tempcache_put () - put entry to file cache (if cache is not full and if file passes vetting)
 *
 * return        : true if file was cached, false otherwise
 * thread_p (in) : thread entry
 * entry (in)    : entry of retired temporary file
 */
STATIC_INLINE bool
file_tempcache_put (THREAD_ENTRY * thread_p, FILE_TEMPCACHE_ENTRY * entry)
{
  FILE_HEADER fhead;

  assert (entry != NULL);
  assert (!VFID_ISNULL (&entry->vfid));
  assert (entry->next == NULL);

  fhead.n_page_user = -1;
  if (file_header_copy (thread_p, &entry->vfid, &fhead) != NO_ERROR
      || fhead.n_page_user > prm_get_integer_value (PRM_ID_MAX_PAGES_IN_TEMP_FILE_CACHE))
    {
      /* file not valid for cache */
      file_log ("file_tempcache_put",
        "could not cache temporary file " FILE_TEMPCACHE_ENTRY_MSG
        ", fhead->n_page_user = %d ", FILE_TEMPCACHE_ENTRY_AS_ARGS (entry), fhead.n_page_user);
      return false;
    }
  /* make sure entry has correct type. */
  entry->ftype = fhead.type;

  /* lock temporary cache */
  file_tempcache_lock ();

  if (file_Tempcache.ncached_not_numerable + file_Tempcache.ncached_numerable < file_Tempcache.ncached_max)
    {
      /* cache not full */
      assert ((file_Tempcache.cached_not_numerable == NULL) == (file_Tempcache.ncached_not_numerable == 0));
      assert ((file_Tempcache.cached_numerable == NULL) == (file_Tempcache.ncached_numerable == 0));

      /* reset file */
      if (file_temp_reset_user_pages (thread_p, &entry->vfid) != NO_ERROR)
    {
      /* failed to reset file, we cannot cache it */
      ASSERT_ERROR ();

      file_log ("file_tempcache_put",
            "could not cache temporary file " FILE_TEMPCACHE_ENTRY_MSG
            ", error during file reset", FILE_TEMPCACHE_ENTRY_AS_ARGS (entry));
      file_tempcache_unlock ();
      return false;
    }

      assert (file_tempcache_check_duplicate (thread_p, entry, FILE_IS_NUMERABLE (&fhead)) == false);

      /* add numerable temporary file to cached numerable file list, regular file to not numerable list */
      if (FILE_IS_NUMERABLE (&fhead))
    {
      entry->next = file_Tempcache.cached_numerable;
      file_Tempcache.cached_numerable = entry;
      file_Tempcache.ncached_numerable++;
    }
      else
    {
      entry->next = file_Tempcache.cached_not_numerable;
      file_Tempcache.cached_not_numerable = entry;
      file_Tempcache.ncached_not_numerable++;
    }

      file_log ("file_tempcache_put",
        "cached temporary file " FILE_TEMPCACHE_ENTRY_MSG ", %s\n"
        FILE_TEMPCACHE_MSG, FILE_TEMPCACHE_ENTRY_AS_ARGS (entry),
        FILE_IS_NUMERABLE (&fhead) ? "numerable" : "regular", FILE_TEMPCACHE_AS_ARGS);

      file_tempcache_unlock ();

      /* cached */
      return true;
    }
  else
    {
      /* cache full */
      file_log ("file_tempcache_put",
        "could not cache temporary file " FILE_TEMPCACHE_ENTRY_MSG
        ", temporary cache is full \n" FILE_TEMPCACHE_MSG,
        FILE_TEMPCACHE_ENTRY_AS_ARGS (entry), FILE_TEMPCACHE_AS_ARGS);
      file_tempcache_unlock ();
      return false;
    }
}

/*
 * file_get_tempcache_entry_index () - returns entry index of tempcache
 *
 * return        : int
 * thread_p (in) : thread entry
 */
STATIC_INLINE int
file_get_tempcache_entry_index (THREAD_ENTRY * thread_p)
{
#if defined (SERVER_MODE)
  return logtb_get_current_tran_index ();
#else
  return 0;
#endif
}

/*
 * file_tempcache_drop_tran_temp_files () - drop all temporary files created by current transaction
 *
 * return        : void
 * thread_p (in) : thread entry
 */
void
file_tempcache_drop_tran_temp_files (THREAD_ENTRY * thread_p)
{
  int tran_index = file_get_tempcache_entry_index (thread_p);
  file_tempcache_lock_tran_entry (&file_Tempcache.tran_files[tran_index]);
  if (file_Tempcache.tran_files[tran_index].head != NULL)
    {
      file_tempcache_cache_or_drop_entries (thread_p, &file_Tempcache.tran_files[tran_index].head);
    }
  file_tempcache_unlock_tran_entry (&file_Tempcache.tran_files[tran_index]);
}

/*
 * file_tempcache_cache_or_drop_entries () - drop all temporary files in the give entry list
 *
 * return        : void
 * thread_p (in) : thread entry
 * entries (in)  : temporary files entry list
 */
STATIC_INLINE void
file_tempcache_cache_or_drop_entries (THREAD_ENTRY * thread_p, FILE_TEMPCACHE_ENTRY ** entries)
{
  FILE_TEMPCACHE_ENTRY *temp_file;
  FILE_TEMPCACHE_ENTRY *next = NULL;
  int num = 0;

  for (temp_file = *entries; temp_file != NULL; temp_file = next)
    {
      next = temp_file->next;
      temp_file->next = NULL;

      if (!file_tempcache_put (thread_p, temp_file))
    {
      /* was not cached. destroy the file */
      file_log ("file_tempcache_cache_or_drop_entries",
            "drop entry " FILE_TEMPCACHE_ENTRY_MSG, FILE_TEMPCACHE_ENTRY_AS_ARGS (temp_file));
      if (file_destroy (thread_p, &temp_file->vfid, true) != NO_ERROR)
        {
          /* file is leaked */
          assert_release (false);
          /* ignore error and continue free as many files as possible */
        }
      file_tempcache_retire_entry (temp_file);
    }
      num++;
    }
  file_log ("file_tempcache_cache_or_drop_entries", "drop %d transaction temporary files", num);
  *entries = NULL;
}

/*
 * file_tempcache_pop_tran_file () - pop entry with the given VFID from transaction list
 *
 * return        : popped entry
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 */
STATIC_INLINE FILE_TEMPCACHE_ENTRY *
file_tempcache_pop_tran_file (THREAD_ENTRY * thread_p, const VFID * vfid)
{
  int tran_index = file_get_tempcache_entry_index (thread_p);
  FILE_TEMPCACHE_TRAN_ENTRY *tran_entry = &file_Tempcache.tran_files[tran_index];
  FILE_TEMPCACHE_ENTRY *entry = NULL, *prev_entry = NULL;

  file_tempcache_lock_tran_entry (tran_entry);
  for (entry = tran_entry->head; entry != NULL; entry = entry->next)
    {
      if (VFID_EQ (&entry->vfid, vfid))
    {
      /* remove entry from transaction list */
      if (prev_entry != NULL)
        {
          prev_entry->next = entry->next;
        }
      else
        {
          tran_entry->head = entry->next;
        }
      entry->next = NULL;

      file_log ("file_tempcache_pop_tran_file", "removed entry " FILE_TEMPCACHE_ENTRY_MSG,
            FILE_TEMPCACHE_ENTRY_AS_ARGS (entry));
      file_tempcache_unlock_tran_entry (tran_entry);
      return entry;
    }
      prev_entry = entry;
    }
  file_tempcache_unlock_tran_entry (tran_entry);
  /* should have found it */
  assert_release (false);
  return NULL;
}

/*
 * file_tempcache_push_tran_file () - push temporary file entry to transaction list
 *
 * return        : void
 * thread_p (in) : thread entry
 * entry (in)    : temporary cache entry
 */
STATIC_INLINE void
file_tempcache_push_tran_file (THREAD_ENTRY * thread_p, FILE_TEMPCACHE_ENTRY * entry)
{
  int tran_index = file_get_tempcache_entry_index (thread_p);
  FILE_TEMPCACHE_TRAN_ENTRY *tran_entry = &file_Tempcache.tran_files[tran_index];

  file_tempcache_lock_tran_entry (tran_entry);
  entry->next = tran_entry->head;
  tran_entry->head = entry;

  file_log ("file_tempcache_push_tran_file", "pushed entry " FILE_TEMPCACHE_ENTRY_MSG,
        FILE_TEMPCACHE_ENTRY_AS_ARGS (entry));
  file_tempcache_unlock_tran_entry (tran_entry);
}

/*
 * file_get_tran_num_temp_files () - returns the number of temp file entries of the given transaction
 *
 * return        : the number of temp files of the given transaction
 * thread_p (in) : thread entry
 */
int
file_get_tran_num_temp_files (THREAD_ENTRY * thread_p)
{
  FILE_TEMPCACHE_TRAN_ENTRY *tran_entry = &file_Tempcache.tran_files[file_get_tempcache_entry_index (thread_p)];
  FILE_TEMPCACHE_ENTRY *entry;
  int num = 0;
  file_tempcache_lock_tran_entry (tran_entry);
  for (entry = tran_entry->head; entry != NULL; entry = entry->next)
    {
      num++;
    }
  file_tempcache_unlock_tran_entry (tran_entry);
  return num;
}

/*
 * file_tempcache_dump () - dump temporary files cache
 *
 * return  : void
 * fp (in) : dump output
 */
STATIC_INLINE void
file_tempcache_dump (FILE * fp)
{
  FILE_TEMPCACHE_ENTRY *cached_files;

  file_tempcache_lock ();

  fprintf (fp, "DUMPING file manager's temporary files cache.\n");
  fprintf (fp,
       "  max files = %d, regular files count = %d, numerable files count = %d.\n\n",
       file_Tempcache.ncached_max, file_Tempcache.ncached_not_numerable, file_Tempcache.ncached_numerable);

  if (file_Tempcache.cached_not_numerable != NULL)
    {
      fprintf (fp, "  cached regular files: \n");
      for (cached_files = file_Tempcache.cached_not_numerable; cached_files != NULL; cached_files = cached_files->next)
    {
      fprintf (fp, "    VFID = %d|%d, file type = %s \n",
           VFID_AS_ARGS (&cached_files->vfid), file_type_to_string (cached_files->ftype));
    }
      fprintf (fp, "\n");
    }
  if (file_Tempcache.cached_numerable != NULL)
    {
      fprintf (fp, "  cached numerable files: \n");
      for (cached_files = file_Tempcache.cached_numerable; cached_files != NULL; cached_files = cached_files->next)
    {
      fprintf (fp, "    VFID = %d|%d, file type = %s \n",
           VFID_AS_ARGS (&cached_files->vfid), file_type_to_string (cached_files->ftype));
    }
      fprintf (fp, "\n");
    }

  file_tempcache_unlock ();

  /* todo: to print transaction temporary files we need some kind of synchronization... right now each transaction
   *       manages its own list freely. */
}

STATIC_INLINE void
file_tempcache_lock_tran_entry (FILE_TEMPCACHE_TRAN_ENTRY * tran_entry)
{
  assert (tran_entry->owner_mutex != thread_get_current_entry_index ());
  pthread_mutex_lock (&tran_entry->mutex);
  assert (tran_entry->owner_mutex == -1);
#if !defined (NDEBUG)
  tran_entry->owner_mutex = thread_get_current_entry_index ();
#endif /* !NDEBUG */
}

STATIC_INLINE void
file_tempcache_unlock_tran_entry (FILE_TEMPCACHE_TRAN_ENTRY * tran_entry)
{
  assert (tran_entry->owner_mutex == thread_get_current_entry_index ());
#if !defined (NDEBUG)
  tran_entry->owner_mutex = -1;
#endif /* !NDEBUG */
  pthread_mutex_unlock (&tran_entry->mutex);
}




/************************************************************************/
/* File tracker section                                                 */
/************************************************************************/

/*
 * file_tracker_create () - create file tracker.
 *
 * return                 : error code
 * thread_p (in)          : thread entry
 * vfid_tracker_out (out) : file tracker VFID
 */
int
file_tracker_create (THREAD_ENTRY * thread_p, VFID * vfid_tracker_out)
{
  int error_code = NO_ERROR;

  /* start sys op */
  log_sysop_start (thread_p);

  error_code = file_create_with_npages (thread_p, FILE_TRACKER, 1, NULL, vfid_tracker_out);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
  error_code = file_alloc_sticky_first_page (thread_p, vfid_tracker_out, file_tracker_init_page, NULL,
                         &file_Tracker_vpid, NULL);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

  /* success */
  assert (error_code == NO_ERROR);

exit:

  if (error_code != NO_ERROR)
    {
      log_sysop_abort (thread_p);
      VFID_SET_NULL (vfid_tracker_out);
      VPID_SET_NULL (&file_Tracker_vpid);
    }
  else
    {
      log_sysop_commit (thread_p);

      VFID_COPY (&file_Tracker_vfid, vfid_tracker_out);
    }
  return error_code;
}

/*
 * file_tracker_load () - load file tracker
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file tracker file identifier
 */
int
file_tracker_load (THREAD_ENTRY * thread_p, const VFID * vfid)
{
  int error_code = NO_ERROR;

  assert (vfid != NULL && !VFID_ISNULL (vfid));

  error_code = file_get_sticky_first_page (thread_p, vfid, &file_Tracker_vpid);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  file_Tracker_vfid = *vfid;

  return NO_ERROR;
}

/*
 * file_tracker_init_page () - initialize new file tracker page
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * page (in)     : new page
 * args (in)     : ignored
 */
static int
file_tracker_init_page (THREAD_ENTRY * thread_p, PAGE_PTR page, void *args)
{
  FILE_EXTENSIBLE_DATA *extdata = (FILE_EXTENSIBLE_DATA *) page;

  pgbuf_set_page_ptype (thread_p, page, PAGE_FTAB);
  file_extdata_init (sizeof (FILE_TRACK_ITEM), DB_PAGESIZE, extdata);
  log_append_undoredo_data2 (thread_p, RVPGBUF_NEW_PAGE, NULL, page, (PGLENGTH) PAGE_FTAB, 0, sizeof (*extdata), NULL,
                 page);
  pgbuf_set_dirty (thread_p, page, DONT_FREE);

  return NO_ERROR;
}

/*
 * file_tracker_register () - register new file in file tracker
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 * ftype (in)    : file type
 * metadata (in) : meta-data about file (if NULL will be initialized as 0).
 */
static int
file_tracker_register (THREAD_ENTRY * thread_p, const VFID * vfid, FILE_TYPE ftype, FILE_TRACK_METADATA * metadata)
{
  FILE_TRACK_ITEM item;
  PAGE_PTR page_track_head = NULL;
  int error_code = NO_ERROR;

  assert (vfid != NULL);
  assert (sizeof (item.metadata) == sizeof (item.metadata.metadata_size_tracker));
  assert (!VPID_ISNULL (&file_Tracker_vpid));
  assert (log_check_system_op_is_started (thread_p));

  item.volid = vfid->volid;
  item.fileid = vfid->fileid;
  item.type = (INT16) ftype;

  if (metadata == NULL)
    {
      /* set 0 */
      item.metadata.metadata_size_tracker = 0;
    }
  else
    {
      /* set given metadata */
      item.metadata = *metadata;
    }

  page_track_head = pgbuf_fix (thread_p, &file_Tracker_vpid, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_track_head == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  error_code = file_tracker_register_internal (thread_p, page_track_head, &item);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
    }

  /* unfix head */
  pgbuf_unfix (thread_p, page_track_head);

  /* return error code */
  return error_code;
}

/*
 * file_tracker_register_internal () - register new file in file tracker internal function. called by register and
 *                                     unregister undo.
 *
 * return               : error code
 * thread_p (in)        : thread entry
 * page_track_head (in) : tracker header page
 * item (in)            : item (with file data)
 */
static int
file_tracker_register_internal (THREAD_ENTRY * thread_p, PAGE_PTR page_track_head, const FILE_TRACK_ITEM * item)
{
  FILE_EXTENSIBLE_DATA *extdata = NULL;
  PAGE_PTR page_track_other = NULL;
  PAGE_PTR page_extdata = NULL;
  bool found;
  int pos;
  LOG_LSA save_lsa;
  int error_code = NO_ERROR;

  assert (log_check_system_op_is_started (thread_p));

  /* find a space to add new item */
  extdata = (FILE_EXTENSIBLE_DATA *) page_track_head;
  error_code = file_extdata_find_not_full (thread_p, &extdata, &page_track_other, &found);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
  page_extdata = page_track_other != NULL ? page_track_other : page_track_head;

  if (!found)
    {
      /* allocate a new page */
      VPID vpid_new_page;

      error_code = file_alloc (thread_p, &file_Tracker_vfid, file_tracker_init_page, NULL, &vpid_new_page, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

      /* add link to new page */
      file_log_extdata_set_next (thread_p, extdata, page_extdata, &vpid_new_page);
      extdata->vpid_next = vpid_new_page;
      pgbuf_set_dirty (thread_p, page_extdata, DONT_FREE);

      if (page_track_other != NULL)
    {
      /* no longer needed */
      pgbuf_unfix_and_init (thread_p, page_track_other);
    }

      /* initialize new page */
      page_track_other = pgbuf_fix (thread_p, &vpid_new_page, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_track_other == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
      assert (pgbuf_get_page_ptype (thread_p, page_track_other) == PAGE_FTAB);

      page_extdata = page_track_other;
      extdata = (FILE_EXTENSIBLE_DATA *) page_extdata;
    }

  assert (page_extdata != NULL);
  assert (extdata != NULL);
  assert (extdata == (FILE_EXTENSIBLE_DATA *) page_extdata);
  assert (!file_extdata_is_full (extdata));

  file_extdata_find_ordered (extdata, item, file_compare_track_items, &found, &pos);
  if (found)
    {
      /* impossible */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  save_lsa = *pgbuf_get_lsa (page_extdata);
  file_extdata_insert_at (extdata, pos, 1, item);
  file_log_extdata_add (thread_p, extdata, page_extdata, pos, 1, item);
  pgbuf_set_dirty (thread_p, page_extdata, DONT_FREE);

  file_log ("file_tracker_register_internal", "added " FILE_TRACK_ITEM_MSG ", to page %d|%d, prev_lsa = %lld|%d, "
        "crt_lsa = %lld|%d, at pos %d ", FILE_TRACK_ITEM_AS_ARGS (item),
        PGBUF_PAGE_MODIFY_ARGS (page_extdata, &save_lsa), pos);

exit:
  if (page_track_other != NULL)
    {
      pgbuf_unfix (thread_p, page_track_other);
    }
  return error_code;
}

/*
 * file_tracker_unregister () - unregister file from tracker
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 */
static int
file_tracker_unregister (THREAD_ENTRY * thread_p, const VFID * vfid)
{
  PAGE_PTR page_track_head = NULL;
  FILE_EXTENSIBLE_DATA *extdata = NULL;
  FILE_TRACK_ITEM item_inout;
  VPID vpid_merged = VPID_INITIALIZER;
  int error_code = NO_ERROR;

  assert (vfid != NULL && !VFID_ISNULL (vfid));
  assert (log_check_system_op_is_started (thread_p));

  page_track_head = pgbuf_fix (thread_p, &file_Tracker_vpid, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_track_head == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  extdata = (FILE_EXTENSIBLE_DATA *) page_track_head;

  /* we still need to start a system op. */
  log_sysop_start (thread_p);

  item_inout.volid = vfid->volid;
  item_inout.fileid = vfid->fileid;

  error_code = file_extdata_find_and_remove_item (thread_p, extdata, page_track_head, &item_inout,
                          file_compare_track_items, true, &item_inout, &vpid_merged);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

  if (!VPID_ISNULL (&vpid_merged))
    {
      /* merged page. deallocate it */
      file_log ("file_tracker_unregister", "deallocate page %d|%d ", VPID_AS_ARGS (&vpid_merged));

      error_code = file_dealloc (thread_p, &file_Tracker_vfid, &vpid_merged, FILE_TRACKER);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
    }

  /* success */
  assert (error_code == NO_ERROR);

exit:
  if (error_code != NO_ERROR)
    {
      log_sysop_abort (thread_p);
    }
  else
    {
      log_sysop_end_logical_undo (thread_p, RVFL_TRACKER_UNREGISTER, NULL, sizeof (item_inout), (char *) &item_inout);
    }
  if (page_track_head != NULL)
    {
      pgbuf_unfix (thread_p, page_track_head);
    }
  return error_code;
}

/*
 * file_rv_tracker_unregister_undo () - undo the unregister of file. may happen if file_destroy is only partially
 *                                      executed. since data inside tracker moved from page to page, the unregister
 *                                      operation is logged using system ops with logical undo/compensate.
 *
 * return        : error code
 * thread_p (in) : thread entry
 * rcv (in)      : recovery data
 */
int
file_rv_tracker_unregister_undo (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  PAGE_PTR page_track_head;
  int error_code = NO_ERROR;

  assert (rcv->length == sizeof (FILE_TRACK_ITEM));

  page_track_head = pgbuf_fix (thread_p, &file_Tracker_vpid, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_track_head == NULL)
    {
      assert (false);
      return ER_FAILED;
    }

  log_sysop_start (thread_p);

  /* undo unregister => register the logged item */
  error_code = file_tracker_register_internal (thread_p, page_track_head, (FILE_TRACK_ITEM *) rcv->data);
  if (error_code != NO_ERROR)
    {
      /* not expected */
      assert (false);

      log_sysop_abort (thread_p);
    }
  else
    {
      /* need to finish system op while holding latch on header */
      log_sysop_end_logical_compensate (thread_p, &rcv->reference_lsa);
    }

  pgbuf_unfix_and_init (thread_p, page_track_head);

  return error_code;
}

/*
 * file_tracker_apply_to_file () - search for file tracker item and apply function
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 * mode (in)     : latch mode for tracker pages
 * func (in)     : apply function for found item
 * args (in)     : arguments for applied function
 */
static int
file_tracker_apply_to_file (THREAD_ENTRY * thread_p, const VFID * vfid, PGBUF_LATCH_MODE mode,
                FILE_TRACK_ITEM_FUNC func, void *args)
{
  PAGE_PTR page_track_head = NULL;
  PAGE_PTR page_track_other = NULL;
  FILE_EXTENSIBLE_DATA *extdata;
  FILE_TRACK_ITEM item_search;
  bool for_write = (mode == PGBUF_LATCH_WRITE);
  bool found = false;
  int pos = -1;
  int error_code = NO_ERROR;

  assert (func != NULL);
  assert (mode == PGBUF_LATCH_READ || mode == PGBUF_LATCH_WRITE);

  page_track_head = pgbuf_fix (thread_p, &file_Tracker_vpid, OLD_PAGE, mode, PGBUF_UNCONDITIONAL_LATCH);
  if (page_track_head == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  extdata = (FILE_EXTENSIBLE_DATA *) page_track_head;

  item_search.volid = vfid->volid;
  item_search.fileid = vfid->fileid;
  error_code = file_extdata_search_item (thread_p, &extdata, &item_search, file_compare_track_items, true, for_write,
                     &found, &pos, &page_track_other);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
  if (!found)
    {
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }

  error_code = func (thread_p, page_track_other != NULL ? page_track_other : page_track_head, extdata, pos, NULL, args);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }

  /* success */
  assert (error_code == NO_ERROR);

exit:
  if (page_track_other != NULL)
    {
      pgbuf_unfix (thread_p, page_track_other);
    }
  if (page_track_head != NULL)
    {
      pgbuf_unfix (thread_p, page_track_head);
    }
  return error_code;
}

/*
 * file_tracker_map () - map item function to all tracked files
 *
 * return          : error code
 * thread_p (in)   : thread entry
 * latch_mode (in) : latch mode
 * func (in)       : function called for each item
 * args (in)       : arguments for function
 */
static int
file_tracker_map (THREAD_ENTRY * thread_p, PGBUF_LATCH_MODE latch_mode, FILE_TRACK_ITEM_FUNC func, void *args)
{
  PAGE_PTR page_track_head = NULL;
  PAGE_PTR page_track_other = NULL;
  PAGE_PTR page_extdata = NULL;
  FILE_EXTENSIBLE_DATA *extdata;
  VPID vpid_next = VPID_INITIALIZER;
  bool stop = false;
  int index_item;
  int error_code = NO_ERROR;

  page_track_head = pgbuf_fix (thread_p, &file_Tracker_vpid, OLD_PAGE, latch_mode, PGBUF_UNCONDITIONAL_LATCH);
  if (page_track_head == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  page_extdata = page_track_head;
  while (true)
    {
      extdata = (FILE_EXTENSIBLE_DATA *) page_extdata;
      for (index_item = 0; index_item < file_extdata_item_count (extdata); index_item++)
    {
      error_code = func (thread_p, page_extdata, extdata, index_item, &stop, args);
      if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          goto exit;
        }
      if (stop)
        {
          /* early out */
          goto exit;
        }
    }

      vpid_next = extdata->vpid_next;
      if (page_track_other != NULL)
    {
      pgbuf_unfix_and_init (thread_p, page_track_other);
    }
      if (VPID_ISNULL (&vpid_next))
    {
      break;
    }

      page_track_other = pgbuf_fix (thread_p, &vpid_next, OLD_PAGE, latch_mode, PGBUF_UNCONDITIONAL_LATCH);
      if (page_track_other == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }

      page_extdata = page_track_other;
    }

  /* success */
  assert (error_code == NO_ERROR);

exit:
  if (page_track_other != NULL)
    {
      pgbuf_unfix (thread_p, page_track_other);
    }
  if (page_track_head != NULL)
    {
      pgbuf_unfix (thread_p, page_track_head);
    }
  return error_code;
}

/*
 * file_rv_tracker_reuse_heap () - recover reuse heap file in tracker
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * rcv (in)      : recovery data
 */
int
file_rv_tracker_reuse_heap (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  FILE_EXTENSIBLE_DATA *extdata = NULL;
  FILE_TRACK_ITEM *item = NULL;

  assert (rcv->length == 0);
  assert (rcv->pgptr != NULL);
  assert (rcv->offset >= 0);

  extdata = (FILE_EXTENSIBLE_DATA *) rcv->pgptr;
  assert (rcv->offset < file_extdata_item_count (extdata));

  item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, rcv->offset);
  assert (item->type == FILE_HEAP);
  assert (item->metadata.heap.is_marked_deleted);

  item->metadata.heap.is_marked_deleted = false;

  file_log ("file_rv_tracker_reuse_heap", "recovery reuse heap " FILE_TRACK_ITEM_MSG ", in "
        PGBUF_PAGE_STATE_MSG ("tracker page"), FILE_TRACK_ITEM_AS_ARGS (item), PGBUF_PAGE_STATE_ARGS (rcv->pgptr));

  pgbuf_set_dirty (thread_p, rcv->pgptr, DONT_FREE);
  return NO_ERROR;
}

/*
 * file_tracker_item_reuse_heap () - reuse heap file if marked as deleted. at the same time, update file descriptor in
 *                                   file header (descriptor update must happen with tracker protection to provide
 *                                   consistent checks).s
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * page_of_item (in) : page of item
 * extdata (in)      : extensible data
 * index_item (in)   : index of item
 * args (in/out)     : FILE_TRACKER_REUSE_HEAP_CONTEXT *
 */
static int
file_tracker_item_reuse_heap (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
                  int index_item, bool * stop, void *args)
{
  FILE_TRACK_ITEM *item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, index_item);
  FILE_TRACKER_REUSE_HEAP_CONTEXT *context = (FILE_TRACKER_REUSE_HEAP_CONTEXT *) args;
  LOG_LSA save_lsa;
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  FILE_DESCRIPTORS des_new;
  int error_code = NO_ERROR;
#if defined (SERVER_MODE)
  bool is_dropped = false;
#endif

  assert (log_check_system_op_is_started (thread_p));

  if (item->type != (INT16) FILE_HEAP)
    {
      return NO_ERROR;
    }
  if (!item->metadata.heap.is_marked_deleted)
    {
      return NO_ERROR;
    }

  /* get vfid */
  context->hfid_out->vfid.volid = item->volid;
  context->hfid_out->vfid.fileid = item->fileid;

#if defined (SERVER_MODE)
  /* we need it to check vacuum won't consider this dropped. */
  error_code = vacuum_is_file_dropped (thread_p, &is_dropped, &context->hfid_out->vfid,
                       logtb_get_current_mvccid (thread_p));
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      VFID_SET_NULL (&context->hfid_out->vfid);
      return error_code;
    }
  if (is_dropped)
    {
      file_log ("file_tracker_item_reuse_heap", "can't reuse heap file %d|%d with mvccid %llu because vacuum thinks it "
        "is dropped.\n", VFID_AS_ARGS (&context->hfid_out->vfid),
        (unsigned long long) logtb_get_current_mvccid (thread_p));
      VFID_SET_NULL (&context->hfid_out->vfid);
      return NO_ERROR;
    }
#endif

  /* reuse this heap. but we need to update its descriptor first. */
  FILE_GET_HEADER_VPID (&context->hfid_out->vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
  fhead = (FILE_HEADER *) page_fhead;
  assert (fhead->type == FILE_HEAP || fhead->type == FILE_HEAP_REUSE_SLOTS);
  assert (VFID_EQ (&context->hfid_out->vfid, &fhead->self));
  assert (VFID_EQ (&context->hfid_out->vfid, &fhead->descriptor.heap.hfid.vfid));
  file_header_sanity_check (thread_p, fhead);

  /* get hfid */
  *context->hfid_out = fhead->descriptor.heap.hfid;
#if !defined (NDEBUG)
  {
    /* safeguard: check hfid & sticky first page match */
    VPID vpid_heap_header = VPID_INITIALIZER;
    error_code = file_get_sticky_first_page (thread_p, &context->hfid_out->vfid, &vpid_heap_header);
    if (error_code != NO_ERROR)
      {
    ASSERT_ERROR ();
    goto exit;
      }
    assert (context->hfid_out->vfid.volid == vpid_heap_header.volid);
    assert (context->hfid_out->hpgid == vpid_heap_header.pageid);
  }
#endif /* !NDEBUG */

  /* log & update class_oid */
  des_new = fhead->descriptor;
  des_new.heap.class_oid = context->class_oid;
  log_append_undoredo_data2 (thread_p, RVFL_FILEDESC_UPD, NULL, page_fhead,
                 (PGLENGTH) ((char *) &fhead->descriptor - page_fhead), sizeof (fhead->descriptor),
                 sizeof (des_new), &fhead->descriptor, &des_new);
  fhead->descriptor = des_new;
  pgbuf_set_dirty_and_free (thread_p, page_fhead);

  /* now update mark deleted flag in tracker item */
  save_lsa = *pgbuf_get_lsa (page_of_item);

  item->metadata.heap.is_marked_deleted = false;
  log_append_undoredo_data2 (thread_p, RVFL_TRACKER_HEAP_REUSE, NULL, page_of_item, index_item,
                 sizeof (context->hfid_out->vfid), 0, &context->hfid_out->vfid, NULL);
  pgbuf_set_dirty (thread_p, page_of_item, DONT_FREE);

  file_log ("file_tracker_item_reuse_heap", "reuse heap file %d|%d; tracker page %d|%d, prev_lsa = %lld|%d, "
        "crt_lsa = %lld|%d, item at pos %d ", VFID_AS_ARGS (&context->hfid_out->vfid),
        PGBUF_PAGE_MODIFY_ARGS (page_of_item, &save_lsa), index_item);

  /* stop looking */
  *stop = true;
  assert (error_code == NO_ERROR);

exit:
  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }
  return error_code;
}

/*
 * file_tracker_reuse_heap () - search for heap file marked as deleted and reuse.
 *
 * return         : error code
 * thread_p (in)  : thread entry
 * class_oid (in) : class identifier for new heap file
 * hfid_out (out) : HFID of reused file or NULL HFID if no file was found
 *
 * note: file descriptor will also be updated if heap file is reused
 */
int
file_tracker_reuse_heap (THREAD_ENTRY * thread_p, const OID * class_oid, HFID * hfid_out)
{
  FILE_TRACKER_REUSE_HEAP_CONTEXT context;

  assert (hfid_out != NULL);
  assert (class_oid != NULL);

  HFID_SET_NULL (hfid_out);
  context.hfid_out = hfid_out;
  context.class_oid = *class_oid;

  return file_tracker_map (thread_p, PGBUF_LATCH_WRITE, file_tracker_item_reuse_heap, &context);
}

/*
 * file_tracker_item_mark_heap_deleted () - FILE_TRACK_ITEM_FUNC to mark heap entry as deleted
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * page_of_item (in) : page of item
 * extdata (in)      : extensible data
 * index_item (in)   : index of item
 * stop (in)         : not used
 * args (in)         : FILE_TRACK_MARK_HEAP_DELETED_CONTEXT *
 */
static int
file_tracker_item_mark_heap_deleted (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
                     int index_item, bool * stop, void *args)
{
  FILE_TRACK_ITEM *item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, index_item);
  FILE_TRACK_MARK_HEAP_DELETED_CONTEXT *context = (FILE_TRACK_MARK_HEAP_DELETED_CONTEXT *) args;
  LOG_LSA save_lsa;

  assert ((FILE_TYPE) item->type == FILE_HEAP);
  assert (!item->metadata.heap.is_marked_deleted);

  item->metadata.heap.is_marked_deleted = true;

  save_lsa = *pgbuf_get_lsa (page_of_item);
  if (context->is_undo)
    {
      log_append_compensate_with_undo_nxlsa (thread_p, RVFL_TRACKER_HEAP_MARK_DELETED,
                         pgbuf_get_vpid_ptr (page_of_item), index_item, page_of_item, 0, NULL,
                         LOG_FIND_CURRENT_TDES (thread_p), &context->ref_lsa);
    }
  else
    {
      LOG_DATA_ADDR addr = LOG_DATA_ADDR_INITIALIZER;
      addr.pgptr = page_of_item;
      addr.offset = index_item;
      log_append_run_postpone (thread_p, RVFL_TRACKER_HEAP_MARK_DELETED, &addr, pgbuf_get_vpid_ptr (page_of_item),
                   0, NULL, &context->ref_lsa);
    }
  pgbuf_set_dirty (thread_p, page_of_item, DONT_FREE);

  file_log ("file_tracker_item_mark_heap_deleted", "mark delete heap file %d|%d; "
        PGBUF_PAGE_MODIFY_MSG ("tracker page") ", item at pos %d, on %s, ref_lsa = %lld|%d",
        item->volid, item->fileid, PGBUF_PAGE_MODIFY_ARGS (page_of_item, &save_lsa), index_item,
        context->is_undo ? "undo" : "postpone", LSA_AS_ARGS (&context->ref_lsa));

  return NO_ERROR;
}

/*
 * file_rv_tracker_mark_heap_deleted () - search for heap file and mark it for delete
 *
 * return        : error code
 * thread_p (in) : thread entry
 * rcv (in)      : recovery data
 * is_undo (in)  : true if called on undo/rollback, false if called on postpone
 */
int
file_rv_tracker_mark_heap_deleted (THREAD_ENTRY * thread_p, LOG_RCV * rcv, bool is_undo)
{
  VFID *vfid = (VFID *) rcv->data;
  FILE_TRACK_MARK_HEAP_DELETED_CONTEXT context;
  int error_code = NO_ERROR;

  assert (rcv->length == sizeof (*vfid));
  assert (!LSA_ISNULL (&rcv->reference_lsa));

  context.is_undo = is_undo;
  context.ref_lsa = rcv->reference_lsa;

  error_code = file_tracker_apply_to_file (thread_p, vfid, PGBUF_LATCH_WRITE, file_tracker_item_mark_heap_deleted,
                       &context);
  if (error_code != NO_ERROR)
    {
      assert_release (false);
    }

  return error_code;
}

/*
 * file_rv_tracker_mark_heap_deleted_compensate_or_run_postpone () - used for recovery as compensate or run postpone
 *                                                                   when heap file is marked as deleted.
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * rcv (in)      : recovery data
 */
int
file_rv_tracker_mark_heap_deleted_compensate_or_run_postpone (THREAD_ENTRY * thread_p, LOG_RCV * rcv)
{
  FILE_EXTENSIBLE_DATA *extdata = NULL;
  FILE_TRACK_ITEM *item = NULL;

  assert (rcv->length == 0);
  assert (rcv->pgptr != NULL);
  assert (rcv->offset >= 0);

  extdata = (FILE_EXTENSIBLE_DATA *) rcv->pgptr;
  assert (rcv->offset < file_extdata_item_count (extdata));

  item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, rcv->offset);
  assert (item->type == FILE_HEAP);
  assert (!item->metadata.heap.is_marked_deleted);

  item->metadata.heap.is_marked_deleted = true;

  file_log ("file_rv_tracker_mark_heap_deleted_compensate_or_run_postpone", "mark heap deleted" FILE_TRACK_ITEM_MSG
        ", in " PGBUF_PAGE_STATE_MSG ("tracker page"), FILE_TRACK_ITEM_AS_ARGS (item),
        PGBUF_PAGE_STATE_ARGS (rcv->pgptr));

  pgbuf_set_dirty (thread_p, rcv->pgptr, DONT_FREE);
  return NO_ERROR;
}

#if defined (SA_MODE)
/*
 * file_tracker_reclaim_marked_deleted () - reclaim all files marked as deleted. this can work only in stand-alone
 *                                          mode.
 *
 * return        : error code
 * thread_p (in) : thread entry
 */
int
file_tracker_reclaim_marked_deleted (THREAD_ENTRY * thread_p)
{
  PAGE_PTR page_track_head = NULL;

  PAGE_PTR page_extdata = NULL;
  FILE_EXTENSIBLE_DATA *extdata = NULL;
  PAGE_PTR page_extdata_next = NULL;
  FILE_EXTENSIBLE_DATA *extdata_next = NULL;
  FILE_TRACK_ITEM *item = NULL;
  VPID vpid_next;
  VFID vfid;
  int idx_item;
  int error_code = NO_ERROR;

  assert (!VPID_ISNULL (&file_Tracker_vpid));
  assert (!VFID_ISNULL (&file_Tracker_vfid));

  /* how this works:
   * do two steps:
   * 1. go through all tracker items and identify heap file entires marked as deleted. deallocate the files and remove
   *    the items from tracker.
   * 2. loop through tracker pages and try to merge two-by-two.
   */

  page_track_head = pgbuf_fix (thread_p, &file_Tracker_vpid, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_track_head == NULL)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  log_sysop_start (thread_p);

  /* first step: process tracker data, search for files marked deleted, destroy them and remove them from tracker */
  page_extdata = page_track_head;

  while (true)
    {
      extdata = (FILE_EXTENSIBLE_DATA *) page_extdata;
      for (idx_item = 0; idx_item < file_extdata_item_count (extdata);)
    {
      item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, idx_item);
      if ((FILE_TYPE) item->type == FILE_HEAP && item->metadata.heap.is_marked_deleted)
        {
          /* destroy file */
          vfid.volid = item->volid;
          vfid.fileid = item->fileid;
          error_code = file_destroy (thread_p, &vfid, false);
          if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          goto exit;
        }
          /* already removed by file_destroy. we don't have to increment idx_item */
        }
      else
        {
          /* go to next */
          idx_item++;
        }
    }

      /* go to next extensible data page */
      vpid_next = extdata->vpid_next;
      if (page_extdata != NULL && page_extdata != page_track_head)
    {
      pgbuf_unfix_and_init (thread_p, page_extdata);
    }
      page_extdata = NULL;
      if (VPID_ISNULL (&vpid_next))
    {
      /* no next page */
      break;
    }
      page_extdata = pgbuf_fix (thread_p, &vpid_next, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (page_extdata == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
      /* loop again */
    }

  /* try merges */
  assert (page_extdata == NULL);
  page_extdata = page_track_head;
  extdata = (FILE_EXTENSIBLE_DATA *) page_extdata;
  while (!VPID_ISNULL (&extdata->vpid_next))
    {
      page_extdata_next = pgbuf_fix (thread_p, &extdata->vpid_next, OLD_PAGE, PGBUF_LATCH_WRITE,
                     PGBUF_UNCONDITIONAL_LATCH);
      if (page_extdata_next == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
      extdata_next = (FILE_EXTENSIBLE_DATA *) page_extdata_next;
      if (file_extdata_merge_pages (thread_p, extdata_next, page_extdata_next, extdata, page_extdata,
                    file_compare_track_items, true))
    {
      /* merged next into current. deallocate next. */
      pgbuf_get_vpid (page_extdata_next, &vpid_next);
      pgbuf_unfix_and_init (thread_p, page_extdata_next);

      error_code = file_dealloc (thread_p, &file_Tracker_vfid, &vpid_next, FILE_TRACKER);
      if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          goto exit;
        }

      /* we can try to merge into this page again. fall through without advancing */
    }
      else
    {
      /* advance to next page */
      if (page_extdata != page_track_head)
        {
          pgbuf_unfix (thread_p, page_extdata);
        }
      page_extdata = page_extdata_next;
      extdata = extdata_next;
      page_extdata_next = NULL;
    }
    }

  /* finished successfully */
  assert (error_code == NO_ERROR);

exit:

  assert (log_check_system_op_is_started (thread_p));
  if (error_code != NO_ERROR)
    {
      log_sysop_abort (thread_p);
    }
  else
    {
      log_sysop_commit (thread_p);
    }
  assert (page_extdata_next == NULL || page_extdata_next != page_track_head);
  if (page_extdata_next != NULL)
    {
      pgbuf_unfix (thread_p, page_extdata_next);
    }
  if (page_extdata != NULL && page_extdata != page_track_head)
    {
      pgbuf_unfix (thread_p, page_extdata);
    }
  if (page_track_head != NULL)
    {
      pgbuf_unfix (thread_p, page_track_head);
    }
  return error_code;
}
#endif /* SA_MODE */

/*
 * file_tracker_get_and_protect () - get a file from tracker. if we want to get b-tree or heap files, we must first
 *                                   protect them by locking class.
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * desired_type (in) : desired file type. FILE_UNKNOWN_TYPE for any type.
 * item (in)         : tracker item
 * class_oid (out)   : output locked class OID (for b-tree and heap). NULL OID if no class is locked.
 * stop (out)        : output true when item is accepted
 */
STATIC_INLINE int
file_tracker_get_and_protect (THREAD_ENTRY * thread_p, FILE_TYPE desired_type, FILE_TRACK_ITEM * item, OID * class_oid,
                  bool * stop)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  int error_code = NO_ERROR;

  assert (class_oid != NULL && OID_ISNULL (class_oid));

  /* how it works:
   * this is part of the tracker iterate without holding latch on tracker during the entire iteration. however, it can
   * only work if the files processed outside latch are protected from being destroyed. otherwise, resuming is
   * impossible. most file types are not mutable, so they don't need protection. however, for b-tree and heap files we
   * need to read class OID from descriptor and try to lock it (conditionally!). this is a best-effort approach, if the
   * locking fails, we just skip the file. */

  /* check file type is right */
  switch (desired_type)
    {
    case FILE_UNKNOWN_TYPE:
      /* accept any type */
      break;
    case FILE_HEAP:
    case FILE_HEAP_REUSE_SLOTS:
      /* accept heap or heap reuse slots */
      if ((FILE_TYPE) item->type != FILE_HEAP && (FILE_TYPE) item->type != FILE_HEAP_REUSE_SLOTS)
    {
      /* reject */
      return NO_ERROR;
    }
      break;
    default:
      /* accept the exact file type */
      if ((FILE_TYPE) item->type != desired_type)
    {
      /* reject */
      return NO_ERROR;
    }
      break;
    }

  /* now we need to make sure the file is protected. most types are not mutable (cannot be created or destroyed during
   * run-time), but b-tree and heap files must be protected by lock. */
  switch ((FILE_TYPE) item->type)
    {
    case FILE_HEAP:
      /* these files may be marked for delete. check this is not a deleted file */
      if (item->metadata.heap.is_marked_deleted)
    {
      /* reject */
      return NO_ERROR;
    }
      /* we need to protect with lock. fall through */
      break;
    case FILE_HEAP_REUSE_SLOTS:
    case FILE_BTREE:
    case FILE_MULTIPAGE_OBJECT_HEAP:
    case FILE_BTREE_OVERFLOW_KEY:
      /* we need to protect with lock. fall through */
      break;
    default:
      /* immutable file types. no protection required */
      *stop = true;
      return NO_ERROR;
    }

  /* we need to fix file header and read the class oid from descriptor */
  vpid_fhead.volid = item->volid;
  vpid_fhead.pageid = item->fileid;
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  /* read class OID */
  switch ((FILE_TYPE) item->type)
    {
    case FILE_BTREE:
      *class_oid = fhead->descriptor.btree.class_oid;
      break;
    case FILE_HEAP:
    case FILE_HEAP_REUSE_SLOTS:
      *class_oid = fhead->descriptor.heap.class_oid;
      break;
    case FILE_MULTIPAGE_OBJECT_HEAP:
      *class_oid = fhead->descriptor.heap_overflow.class_oid;
      break;
    case FILE_BTREE_OVERFLOW_KEY:
      *class_oid = fhead->descriptor.btree_key_overflow.class_oid;
      break;
    default:
      assert (false);
      break;
    }
  pgbuf_unfix (thread_p, page_fhead);

  if (OID_ISNULL (class_oid))
    {
      /* this must be boot_Db_parm file; cannot be deleted so we don't need lock. */
      *stop = true;
      return NO_ERROR;
    }

  /* try conditional lock */
  if (lock_object (thread_p, class_oid, oid_Root_class_oid, FILE_GET_TRACKER_LOCK_MODE (desired_type),
           LK_COND_LOCK) != LK_GRANTED)
    {
      er_set (ER_NOTIFICATION_SEVERITY, ARG_FILE_LINE, ER_CANNOT_CHECK_FILE, 5, item->volid, item->fileid,
          OID_AS_ARGS (class_oid));
      OID_SET_NULL (class_oid);
    }
  else
    {
      /* stop at this file */
      *stop = true;
    }

  /* finished */
  return NO_ERROR;
}

/*
 * file_tracker_interruptable_iterate () - iterate in file tracker and get a new file of desired type
 *
 * return             : error code
 * thread_p (in)      : thread entry
 * desired_ftype (in) : desired type
 * vfid (in)          : file identifier and iterator cursor. iterate must start with a NULL identifier
 * class_oid (in)     : locked class OID (used to protect b-tree and heap files)
 */
int
file_tracker_interruptable_iterate (THREAD_ENTRY * thread_p, FILE_TYPE desired_ftype, VFID * vfid, OID * class_oid)
{
  PAGE_PTR page_track_head = NULL;
  PAGE_PTR page_track_other = NULL;
  PAGE_PTR page_extdata = NULL;
  FILE_EXTENSIBLE_DATA *extdata = NULL;
  FILE_TRACK_ITEM *item;
  bool found = false;
  bool stop = false;
  int idx_item;
  VPID vpid_next;
#if !defined (NDEBUG)
  VFID vfid_prev_cursor = *vfid;
#endif /* !NDEBUG */
  int error_code = NO_ERROR;

  assert (vfid != NULL);
  assert (class_oid != NULL);

  /* how it works:
   * start from given VFID and get a new file of desired type. for b-tree and heap files, we also need to lock their
   * class OID in order to protect them from being removed. otherwise we could not resume in next iteration. */

  page_track_head = pgbuf_fix (thread_p, &file_Tracker_vpid, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_track_head == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  if (!OID_ISNULL (class_oid))
    {
      /* now that we fixed tracker header page, we no longer need lock protection. */
      lock_unlock_object (thread_p, class_oid, oid_Root_class_oid, FILE_GET_TRACKER_LOCK_MODE (desired_ftype), true);
      OID_SET_NULL (class_oid);
    }

  extdata = (FILE_EXTENSIBLE_DATA *) page_track_head;
  if (VFID_ISNULL (vfid))
    {
      /* starting position is first */
      idx_item = 0;
      page_extdata = page_track_head;
    }
  else
    {
      FILE_TRACK_ITEM item_search;
      item_search.volid = vfid->volid;
      item_search.fileid = vfid->fileid;
      error_code =
    file_extdata_search_item (thread_p, &extdata, &item_search, file_compare_track_items, true, false, &found,
                  &idx_item, &page_track_other);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      goto exit;
    }
      if (!found)
    {
      /* we are in trouble */
      assert_release (false);
      error_code = ER_FAILED;
      goto exit;
    }
      page_extdata = page_track_other != NULL ? page_track_other : page_track_head;
      /* move to next */
      idx_item++;
    }

  assert (extdata == (FILE_EXTENSIBLE_DATA *) page_extdata);
  /* start iterating until stop is issued */
  while (true)
    {
      for (; idx_item < file_extdata_item_count (extdata); idx_item++)
    {
      item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, idx_item);
      error_code = file_tracker_get_and_protect (thread_p, desired_ftype, item, class_oid, &stop);
      if (error_code != NO_ERROR)
        {
          ASSERT_ERROR ();
          goto exit;
        }
      if (stop)
        {
          vfid->volid = item->volid;
          vfid->fileid = item->fileid;
          goto exit;
        }
    }
      vpid_next = extdata->vpid_next;
      if (page_track_other != NULL)
    {
      pgbuf_unfix_and_init (thread_p, page_track_other);
    }
      if (VPID_ISNULL (&vpid_next))
    {
      /* ended */
      break;
    }
      page_track_other = pgbuf_fix (thread_p, &vpid_next, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
      if (page_track_other == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
      page_extdata = page_track_other;
      extdata = (FILE_EXTENSIBLE_DATA *) page_extdata;
      idx_item = 0;
    }

  /* end of tracker */
  VFID_SET_NULL (vfid);
  assert (OID_ISNULL (class_oid));
  assert (error_code == NO_ERROR);

exit:
  if (page_track_other != NULL)
    {
      pgbuf_unfix (thread_p, page_track_other);
    }
  if (page_track_head != NULL)
    {
      pgbuf_unfix (thread_p, page_track_head);
    }

  /* check cursor is not repeated (unless there is an error) */
  assert (error_code != NO_ERROR || VFID_ISNULL (&vfid_prev_cursor) || !VFID_EQ (&vfid_prev_cursor, vfid));

  return error_code;
}

/*
 * file_tracker_item_dump () - FILE_TRACK_ITEM_FUNC to dump file
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * page_of_item (in) : tracker page
 * extdata (in)      : tracker extensible data
 * index_item (in)   : item index
 * stop (in)         : not used
 * args (in)         : FILE *
 */
static int
file_tracker_item_dump (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata, int index_item,
            bool * stop, void *args)
{
  VFID vfid;
  FILE_TRACK_ITEM *item;
  FILE *fp = (FILE *) args;
  int error_code = NO_ERROR;

  item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, index_item);
  vfid.volid = item->volid;
  vfid.fileid = item->fileid;

  error_code = file_dump (thread_p, &vfid, fp);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  return NO_ERROR;
}

/*
 * file_tracker_dump () - dump all files in file tracker
 *
 * return        : error code
 * thread_p (in) : thread entry
 * fp (in)       : output file
 */
int
file_tracker_dump (THREAD_ENTRY * thread_p, FILE * fp)
{
  fprintf (fp, "\n\n DUMPING TRACKED FILES \n");
  return file_tracker_map (thread_p, PGBUF_LATCH_READ, file_tracker_item_dump, fp);
}

/*
 * file_tracker_item_dump_capacity () - FILE_TRACK_ITEM_FUNC to dump file capacity
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * page_of_item (in) : tracker page
 * extdata (in)      : tracker extensible data
 * index_item (in)   : item index
 * stop (in)         : not used
 * args (in)         : FILE *
 */
static int
file_tracker_item_dump_capacity (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
                 int index_item, bool * stop, void *args)
{
  FILE_TRACK_ITEM *item;
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  FILE *fp = (FILE *) args;
  int error_code = NO_ERROR;

  item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, index_item);

  vpid_fhead.volid = item->volid;
  vpid_fhead.pageid = item->fileid;
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  fprintf (fp, "%4d|%4d %5d  %-22s ", item->volid, item->fileid, fhead->n_page_user, file_type_to_string (fhead->type));
  if ((FILE_TYPE) item->type == FILE_HEAP && item->metadata.heap.is_marked_deleted)
    {
      fprintf (fp, "Marked as deleted... ");
    }

  file_header_dump_descriptor (thread_p, fhead, fp);

  pgbuf_unfix (thread_p, page_fhead);
  return NO_ERROR;
}

/*
 * file_tracker_dump_all_capacities () - dump capacities for all files
 *
 * return        : error code
 * thread_p (in) : thread entry
 * fp (in)       : output file
 */
int
file_tracker_dump_all_capacities (THREAD_ENTRY * thread_p, FILE * fp)
{
  int error_code = NO_ERROR;

  fprintf (fp, "    VFID   npages    type             FDES\n");
  error_code = file_tracker_map (thread_p, PGBUF_LATCH_READ, file_tracker_item_dump_capacity, fp);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  return NO_ERROR;
}

bool
file_is_valid_heap_file (THREAD_ENTRY * thread_p, OID * class_oid_p)
{
  bool is_valid = true;

  if (!OID_ISNULL (class_oid_p))
    {
      RECDES recdes;
      HEAP_SCANCACHE scan_cache;

      (void) heap_scancache_quick_start_root_hfid (thread_p, &scan_cache);

      if (heap_get_class_record (thread_p, class_oid_p, &recdes, &scan_cache, PEEK) != S_SUCCESS)
    {
      is_valid = false;
    }

      heap_scancache_end (thread_p, &scan_cache);
    }

  return is_valid;
}

/*
 * file_tracker_item_dump_file () - FILE_TRACK_ITEM_FUNC to dump file
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * page_of_item (in) : tracker page
 * extdata (in)      : tracker extensible data
 * index_item (in)   : item index
 * stop (in)         : not used
 * args (in)         : FILE *
 */
static int
file_tracker_item_dump_file (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
                 int index_item, bool * stop, void *args)
{
  FILE_TRACK_ITEM *item;
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
// *INDENT-OFF*
  void **params = static_cast<void **> (args);
  FILE *fp = static_cast<FILE *> (params[0]);
  bool invalid_only = *static_cast<bool *> (params[1]);
  std::unordered_set<OID> *valid_oids = static_cast<std::unordered_set<OID> *> (params[2]);
  std::unordered_set<OID> *invalid_oids = static_cast<std::unordered_set<OID> *> (params[3]);
// *INDENT-ON*
  int error_code = NO_ERROR;

  item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, index_item);

  vpid_fhead.volid = item->volid;
  vpid_fhead.pageid = item->fileid;

  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;

  file_header_sanity_check (thread_p, fhead);

  bool need_dump = true;

  if (invalid_only)
    {
      OID *class_oid_p = NULL;

      switch (fhead->type)
    {
    case FILE_HEAP:
    case FILE_HEAP_REUSE_SLOTS:
      class_oid_p = &fhead->descriptor.heap.class_oid;
      break;
    case FILE_MULTIPAGE_OBJECT_HEAP:
      class_oid_p = &fhead->descriptor.heap_overflow.class_oid;
      break;
    case FILE_BTREE:
      class_oid_p = &fhead->descriptor.btree.class_oid;
      break;
    case FILE_BTREE_OVERFLOW_KEY:
      class_oid_p = &fhead->descriptor.btree_key_overflow.class_oid;
      break;
    default:
      break;
    }

      if (class_oid_p != NULL)
    {
      auto it = valid_oids->find (*class_oid_p);
      if (it != valid_oids->end ())
        {
          need_dump = false;
        }
      else
        {
          auto it = invalid_oids->find (*class_oid_p);
          if (it == invalid_oids->end ())
        {
          if (file_is_valid_heap_file (thread_p, class_oid_p))
            {
              need_dump = false;

              valid_oids->insert (*class_oid_p);
            }
          else
            {
              invalid_oids->insert (*class_oid_p);
            }
        }
        }
    }
      else
    {
      need_dump = false;
    }
    }

  if (need_dump)
    {
      fprintf (fp, "%4d|%4d %5d  %-22s ", item->volid, item->fileid, fhead->n_page_user,
           file_type_to_string (fhead->type));
      if ((FILE_TYPE) item->type == FILE_HEAP && item->metadata.heap.is_marked_deleted)
    {
      fprintf (fp, "Marked as deleted... ");
    }

      file_header_dump_descriptor (thread_p, fhead, fp);
    }

  pgbuf_unfix (thread_p, page_fhead);

  return NO_ERROR;
}

/*
 * xfile_tracker_dump_file_list () - dump all files
 *
 * return           : error code
 * thread_p (in)    : thread entry
 * invalid_only(in) : dump only invalid files if set
 * outfp (in)       : FILE stream where to dump the file list
 */
int
xfile_tracker_dump_file_list (THREAD_ENTRY * thread_p, FILE * outfp, bool invalid_only)
{
  int error_code = NO_ERROR;
  void *args[4];
// *INDENT-OFF*
  std::unordered_set<OID> valid_oids;
  std::unordered_set<OID> invalid_oids;
// *INDENT-ON*

  if (outfp == NULL)
    {
      outfp = stdout;
    }

// *INDENT-OFF*
  args[0] = static_cast<void *> (outfp);
  args[1] = static_cast<void *> (&invalid_only);
  args[2] = static_cast<void *> (&valid_oids);
  args[3] = static_cast<void *> (&invalid_oids);
// *INDENT-ON*

  fprintf (outfp, "    VFID   npages    type             FDES\n");
  error_code = file_tracker_map (thread_p, PGBUF_LATCH_READ, file_tracker_item_dump_file, (void *) args);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  return NO_ERROR;
}

// *INDENT-OFF*
inline bool operator==(const VFID &a, const VFID &b)
{
  return a.fileid == b.fileid && a.volid == b.volid;
}

namespace std
{
  template <>
  struct hash<VFID>
  {
    size_t operator()(const VFID &v) const
    {
      return std::hash<int>()(v.fileid) ^ (std::hash<int>()(v.volid) << 1);
    }
  };
}
// *INDENT-ON*

/*
 * file_tracker_item_collect_invalid_file () - collect invalid file
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * page_of_item (in) : tracker page
 * extdata (in)      : tracker extensible data
 * index_item (in)   : item index
 * stop (in)         : not used
 * args (in)         : not used
 */
static int
file_tracker_item_collect_invalid_file (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
                    int index_item, bool * stop, void *args)
{
  FILE_TRACK_ITEM *item;
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  int error_code = NO_ERROR;
// *INDENT-OFF*
  void **params = static_cast<void **> (args);
  std::unordered_set<OID> *valid_oids = static_cast<std::unordered_set<OID> *> (params[0]);
  std::unordered_set<OID> *invalid_oids = static_cast<std::unordered_set<OID> *> (params[1]);
  std::unordered_map<VFID, FILE_TYPE> *invalid_files = static_cast<std::unordered_map<VFID, FILE_TYPE> *> (params[2]);
// *INDENT-ON*

  item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, index_item);

  vpid_fhead.volid = item->volid;
  vpid_fhead.pageid = item->fileid;

  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;

  file_header_sanity_check (thread_p, fhead);

  OID *class_oid_p = NULL;
  bool is_found = false;

  switch (fhead->type)
    {
    case FILE_HEAP:
    case FILE_HEAP_REUSE_SLOTS:
      class_oid_p = &fhead->descriptor.heap.class_oid;
      break;
    case FILE_MULTIPAGE_OBJECT_HEAP:
      class_oid_p = &fhead->descriptor.heap_overflow.class_oid;
      break;
    case FILE_BTREE:
      class_oid_p = &fhead->descriptor.btree.class_oid;
      break;
    case FILE_BTREE_OVERFLOW_KEY:
      class_oid_p = &fhead->descriptor.btree_key_overflow.class_oid;
      break;
    default:
      break;
    }

  if (class_oid_p != NULL)
    {
      auto it = invalid_oids->find (*class_oid_p);
      if (it != invalid_oids->end ())
    {
      is_found = true;
    }
      else
    {
      auto it = valid_oids->find (*class_oid_p);
      if (it == valid_oids->end ())
        {
          if (!file_is_valid_heap_file (thread_p, class_oid_p))
        {
          is_found = true;

          invalid_oids->insert (*class_oid_p);
        }
          else
        {
          valid_oids->insert (*class_oid_p);
        }
        }
    }
    }

  if (is_found)
    {
// *INDENT-OFF*
      auto [it, inserted] = invalid_files->emplace (VFID{item->fileid, item->volid}, fhead->type);
// *INDENT-ON*
      assert (inserted && "Duplicate VFID insertion detected");
    }

  pgbuf_unfix (thread_p, page_fhead);

  return NO_ERROR;
}

/*
 * file_delete_invalid_file () - delete invalid file
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * invalid_files (in):
 * heap (out)        :
 * heap_ovf (out)    :
 * btree (out)       :
 * btree_ovf (out)   :
 */
// *INDENT-OFF*
int
file_delete_invalid_file (THREAD_ENTRY * thread_p,
              const std::unordered_map<VFID, FILE_TYPE> &invalid_files,
              int *heap, int *heap_ovf, int *btree, int *btree_ovf)
// *INDENT-ON*
{
  int error_code = NO_ERROR;
  assert (heap != nullptr);
  assert (heap_ovf != nullptr);
  assert (btree != nullptr);
  assert (btree_ovf != nullptr);

// *INDENT-OFF*
  for (const auto &[vfid, file_type]:invalid_files)
// *INDENT-ON*
  {
    log_sysop_start (thread_p);

    if (file_destroy (thread_p, &vfid, false) != NO_ERROR)
      {
    ASSERT_ERROR_AND_SET (error_code);
    log_sysop_abort (thread_p);
    return error_code;
      }

    log_sysop_commit (thread_p);

    switch (file_type)
      {
      case FILE_HEAP:
      case FILE_HEAP_REUSE_SLOTS:
    ++(*heap);
    break;
      case FILE_MULTIPAGE_OBJECT_HEAP:
    ++(*heap_ovf);
    break;
      case FILE_BTREE:
    ++(*btree);
    break;
      case FILE_BTREE_OVERFLOW_KEY:
    ++(*btree_ovf);
    break;
      default:
    assert (false && "Unknown FILE_TYPE");
    break;
      }
  }

  return NO_ERROR;
}

/*
 * xfile_tracker_clean_invalid_file () - clean invalid file
 *
 * return         : error code
 * thread_p (in)  : thread entry
 * heap (out)     :
 * heap_ovf (out) :
 * btree (out)    :
 * btree_ovf (out):
 */
int
xfile_tracker_clean_invalid_file (THREAD_ENTRY * thread_p, int *heap, int *heap_ovf, int *btree, int *btree_ovf)
{
  void *args[3];
  int error_code = NO_ERROR;
// *INDENT-OFF*
  std::unordered_set<OID> valid_oids;
  std::unordered_set<OID> invalid_oids;
  std::unordered_map<VFID, FILE_TYPE> invalid_files;

  args[0] = static_cast<void *> (&valid_oids);
  args[1] = static_cast<void *> (&invalid_oids);
  args[2] = static_cast<void *> (&invalid_files);
// *INDENT-ON*

  error_code = file_tracker_map (thread_p, PGBUF_LATCH_WRITE, file_tracker_item_collect_invalid_file, args);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  if (!invalid_files.empty ())
    {
      error_code = file_delete_invalid_file (thread_p, invalid_files, heap, heap_ovf, btree, btree_ovf);
      if (error_code != NO_ERROR)
    {
      return error_code;
    }
    }

  return NO_ERROR;
}

#if !defined(NDEBUG)
/*
 * parse_target_vfid () - Parse vfid string "fileid|volid" (e.g., "123|1")
 *
 * return          : true on success, false on failure
 * in_vfid_str (in): vfid string
 * out_vfid (out)  : parsed vfid
 */
bool
parse_target_vfid (const char *in_vfid_str, VFID * out_vfid)
{
  char sentinel;

  if (in_vfid_str == NULL || out_vfid == NULL)
    {
      return false;
    }

  for (int i = 0; in_vfid_str[i] != '\0'; i++)
    {
      if (!isdigit ((unsigned char) in_vfid_str[i]) && in_vfid_str[i] != '|')
    {
      return false;
    }
    }

  if (sscanf (in_vfid_str, "%d|%hd%c", &out_vfid->fileid, &out_vfid->volid, &sentinel) != 2)
    {
      return false;
    }

  return true;
}

/*
 * file_tracker_item_delete_target_file () - delete target file
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * page_of_item (in) : tracker page
 * extdata (in)      : tracker extensible data
 * index_item (in)   : item index
 * stop (in)         : not used
 * args (in)         : vfid
 */
static int
file_tracker_item_delete_target_file (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item,
                      FILE_EXTENSIBLE_DATA * extdata, int index_item, bool * stop, void *args)
{
  FILE_TRACK_ITEM *item;
  VFID *target_vfid;
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  int error_code = NO_ERROR;

  target_vfid = (VFID *) args;

  item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, index_item);

  if (target_vfid->fileid != item->fileid || target_vfid->volid != item->volid)
    {
      return NO_ERROR;
    }

  vpid_fhead.volid = item->volid;
  vpid_fhead.pageid = item->fileid;

  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;

  file_header_sanity_check (thread_p, fhead);

  FILE_TYPE file_type = fhead->type;

  if (file_type == FILE_HEAP || file_type == FILE_HEAP_REUSE_SLOTS || file_type == FILE_MULTIPAGE_OBJECT_HEAP
      || file_type == FILE_BTREE || file_type == FILE_BTREE_OVERFLOW_KEY || file_type == FILE_QUERY_AREA
      || file_type == FILE_TEMP || file_type == FILE_UNKNOWN_TYPE)
    {
      if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);

      page_fhead = NULL;
    }

      log_sysop_start (thread_p);

      if (file_type == FILE_QUERY_AREA || file_type == FILE_TEMP)
    {
      if (file_destroy (thread_p, target_vfid, true) != NO_ERROR)
        {
          ASSERT_ERROR_AND_SET (error_code);

          log_sysop_abort (thread_p);

          return error_code;
        }
    }
      else
    {
      if (file_destroy (thread_p, target_vfid, false) != NO_ERROR)
        {
          ASSERT_ERROR_AND_SET (error_code);

          log_sysop_abort (thread_p);

          return error_code;
        }
    }

      log_sysop_commit (thread_p);
    }

  if (page_fhead != NULL)
    {
      pgbuf_unfix (thread_p, page_fhead);
    }

  return NO_ERROR;
}

/*
 * xfile_tracker_delete_target_file () - delete target file
 *
 * return               : error code
 * thread_p (in)        : thread entry
 * target_vfid_str (in) : vfid string
 */
int
xfile_tracker_delete_target_file (THREAD_ENTRY * thread_p, const char *target_vfid_str)
{
  VFID target_vfid = VFID_INITIALIZER;
  int error_code = NO_ERROR;

  if (parse_target_vfid (target_vfid_str, &target_vfid) != true)
    {
      // TODO: Define error message for release build
      return ER_FAILED;
    }

  error_code = file_tracker_map (thread_p, PGBUF_LATCH_WRITE, file_tracker_item_delete_target_file, &target_vfid);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  return NO_ERROR;
}
#endif

/*
 * file_tracker_item_dump_heap () - FILE_TRACK_ITEM_FUNC to dump heap file
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * page_of_item (in) : tracker page
 * extdata (in)      : tracker extensible data
 * index_item (in)   : item index
 * stop (in)         : not used
 * args (in)         : context
 */
static int
file_tracker_item_dump_heap (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
                 int index_item, bool * stop, void *args)
{
  FILE_TRACK_ITEM *item;
  HFID hfid;
  FILE_TRACKER_DUMP_HEAP_CONTEXT *context = (FILE_TRACKER_DUMP_HEAP_CONTEXT *) args;
  int error_code = NO_ERROR;

  item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, index_item);
  if ((FILE_TYPE) item->type != FILE_HEAP && (FILE_TYPE) item->type != FILE_HEAP_REUSE_SLOTS)
    {
      return NO_ERROR;
    }

  hfid.vfid.volid = item->volid;
  hfid.vfid.fileid = item->fileid;

  error_code = heap_get_hfid_from_vfid (thread_p, &hfid.vfid, &hfid);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  heap_dump (thread_p, context->fp, &hfid, context->dump_records);

  return NO_ERROR;
}

/*
 * file_tracker_dump_all_heap () - dump all heap files
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * fp (in)           : output file
 * dump_records (in) : true to dump records
 */
int
file_tracker_dump_all_heap (THREAD_ENTRY * thread_p, FILE * fp, bool dump_records)
{
  FILE_TRACKER_DUMP_HEAP_CONTEXT context;

  context.fp = fp;
  context.dump_records = dump_records;

  return file_tracker_map (thread_p, PGBUF_LATCH_READ, file_tracker_item_dump_heap, &context);
}

/*
 * file_tracker_item_dump_heap_capacity () - FILE_TRACK_ITEM_FUNC to dump heap file capacity
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * page_of_item (in) : tracker page
 * extdata (in)      : tracker extensible data
 * index_item (in)   : item index
 * stop (in)         : not used
 * args (in)         : context
 */
static int
file_tracker_item_dump_heap_capacity (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item,
                      FILE_EXTENSIBLE_DATA * extdata, int index_item, bool * stop, void *args)
{
  FILE_TRACK_ITEM *item;
  HFID hfid;
  FILE *fp = (FILE *) args;
  int error_code = NO_ERROR;

  item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, index_item);
  if ((FILE_TYPE) item->type != FILE_HEAP && (FILE_TYPE) item->type != FILE_HEAP_REUSE_SLOTS)
    {
      return NO_ERROR;
    }

  hfid.vfid.volid = item->volid;
  hfid.vfid.fileid = item->fileid;

  error_code = heap_get_hfid_from_vfid (thread_p, &hfid.vfid, &hfid);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  error_code = heap_dump_capacity (thread_p, fp, &hfid);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  return NO_ERROR;
}

/*
 * file_tracker_dump_all_heap_capacities () - dump all heap capacities
 *
 * return        : error code
 * thread_p (in) : thread entry
 * fp (in)       : output file
 */
int
file_tracker_dump_all_heap_capacities (THREAD_ENTRY * thread_p, FILE * fp)
{
  fprintf (fp, "IO_PAGESIZE = %d, DB_PAGESIZE = %d, Recv_overhead = %d\n", IO_PAGESIZE, DB_PAGESIZE,
       IO_PAGESIZE - DB_PAGESIZE);
  return file_tracker_map (thread_p, PGBUF_LATCH_READ, file_tracker_item_dump_heap_capacity, fp);
}

/*
 * file_tracker_item_dump_btree_capacity () - FILE_TRACK_ITEM_FUNC to dump b-tree file capacity
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * page_of_item (in) : tracker page
 * extdata (in)      : tracker extensible data
 * index_item (in)   : item index
 * stop (in)         : not used
 * args (in)         : context
 */
static int
file_tracker_item_dump_btree_capacity (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item,
                       FILE_EXTENSIBLE_DATA * extdata, int index_item, bool * stop, void *args)
{
  FILE_TRACK_ITEM *item;
  BTID btid;
  FILE *fp = (FILE *) args;
  int error_code = NO_ERROR;

  item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, index_item);
  if ((FILE_TYPE) item->type != FILE_BTREE)
    {
      return NO_ERROR;
    }

  /* get btid */
  btid.vfid.volid = item->volid;
  btid.vfid.fileid = item->fileid;

  error_code = btree_get_btid_from_file (thread_p, &btid.vfid, &btid);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  /* dump */
  error_code = btree_dump_capacity (thread_p, fp, &btid);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  return NO_ERROR;
}

/*
 * file_tracker_dump_all_btree_capacities () - dump all b-tree capacities
 *
 * return        : error code
 * thread_p (in) : thread entry
 * fp (in)       : output file
 */
int
file_tracker_dump_all_btree_capacities (THREAD_ENTRY * thread_p, FILE * fp)
{
  return file_tracker_map (thread_p, PGBUF_LATCH_READ, file_tracker_item_dump_btree_capacity, fp);
}

/*
 * file_tracker_check () - check all files have valid tables. stand-alone mode will also cross check with disk sector
 *                         table maps.
 *
 * return        : error code
 * thread_p (in) : thread entry
 */
DISK_ISVALID
file_tracker_check (THREAD_ENTRY * thread_p)
{
  DISK_ISVALID allvalid = DISK_VALID;
  DISK_ISVALID valid = DISK_VALID;
  int error_code = NO_ERROR;

#if defined (SERVER_MODE)
  VFID vfid = VFID_INITIALIZER;
  OID class_oid = OID_INITIALIZER;

  valid = file_table_check (thread_p, &file_Tracker_vfid, NULL);
  if (valid == DISK_ERROR)
    {
      ASSERT_ERROR ();
      return DISK_ERROR;
    }
  else if (valid == DISK_INVALID)
    {
      assert_release (false);
      allvalid = DISK_INVALID;
    }

  while (true)
    {
      error_code = file_tracker_interruptable_iterate (thread_p, FILE_UNKNOWN_TYPE, &vfid, &class_oid);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      allvalid = (allvalid == DISK_VALID) ? DISK_ERROR : allvalid;
      break;
    }
      if (VFID_ISNULL (&vfid))
    {
      /* all files processed */
      break;
    }
      valid = file_table_check (thread_p, &vfid, NULL);
      if (valid == DISK_INVALID)
    {
      assert (false);
      allvalid = DISK_INVALID;
    }
      else if (valid == DISK_ERROR)
    {
      ASSERT_ERROR ();
      allvalid = (allvalid == DISK_VALID) ? DISK_ERROR : allvalid;
      break;
    }
    }

  if (!OID_ISNULL (&class_oid))
    {
      lock_unlock_object (thread_p, &class_oid, oid_Root_class_oid, SCH_S_LOCK, true);
    }
#else   /* !SERVER_MODE */         /* SA_MODE */
  DISK_VOLMAP_CLONE *disk_map_clone = NULL;

  error_code = disk_map_clone_create (thread_p, &disk_map_clone);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return DISK_ERROR;
    }

  valid = file_table_check (thread_p, &file_Tracker_vfid, disk_map_clone);
  if (valid == DISK_INVALID)
    {
      assert_release (false);
      allvalid = DISK_INVALID;
      /* continue checks */
    }
  else if (valid == DISK_ERROR)
    {
      ASSERT_ERROR ();
      return DISK_ERROR;
    }

  error_code = file_tracker_map (thread_p, PGBUF_LATCH_READ, file_tracker_item_check, disk_map_clone);
  if (error_code == ER_FAILED)
    {
      assert_release (false);
      disk_map_clone_free (&disk_map_clone);
      allvalid = DISK_INVALID;
    }
  else if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      disk_map_clone_free (&disk_map_clone);
      return allvalid == DISK_VALID ? DISK_ERROR : allvalid;
    }
  else
    {
      /* check all sectors have been cleared */
      valid = disk_map_clone_check_leaks (disk_map_clone);
      if (valid == DISK_INVALID)
    {
      assert_release (false);
      allvalid = DISK_INVALID;
    }
      else if (valid == DISK_ERROR)
    {
      ASSERT_ERROR ();
      allvalid = allvalid == DISK_VALID ? DISK_ERROR : allvalid;
    }
      disk_map_clone_free (&disk_map_clone);
    }

#endif /* SA_MODE */

  return allvalid;
}

#if defined (SA_MODE)
/*
 * file_tracker_item_check () - check file table and cross-check sector usage with disk sector table maps
 *
 * return            : error code (ER_FAILED for invalid state)
 * thread_p (in)     : thread entry
 * page_of_item (in) : tracker page
 * extdata (in)      : tracker extensible data
 * index_item (in)   : item index
 * stop (in)         : not used
 * args (in/out)     : DISK_VOLMAP_CLONE *
 */
static int
file_tracker_item_check (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
             int index_item, bool * stop, void *args)
{
  DISK_VOLMAP_CLONE *disk_map_clone = (DISK_VOLMAP_CLONE *) args;
  FILE_TRACK_ITEM *item;
  VFID vfid;
  DISK_ISVALID valid = DISK_VALID;
  int error_code = NO_ERROR;

  item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, index_item);
  vfid.volid = item->volid;
  vfid.fileid = item->fileid;

  valid = file_table_check (thread_p, &vfid, disk_map_clone);
  if (valid == DISK_INVALID)
    {
      assert_release (false);
      return ER_FAILED;
    }
  else if (valid == DISK_ERROR)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  return NO_ERROR;
}
#endif /* SA_MODE */

/*
 * file_tracker_item_spacedb () - FILE_TRACKER_ITEM_FUNC to collect space information
 *
 * return            : error code
 * thread_p (in)     : thread entry
 * page_of_item (in) : page of item
 * extdata (in)      : extensible data
 * index_item (in)   : index of item
 * stop (in)         : ignored
 * args (in/out)     : SPACEDB_FILES *
 */
static int
file_tracker_item_spacedb (THREAD_ENTRY * thread_p, PAGE_PTR page_of_item, FILE_EXTENSIBLE_DATA * extdata,
               int index_item, bool * stop, void *args)
{
  FILE_TRACK_ITEM *item;
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  SPACEDB_FILES *spacedb = (SPACEDB_FILES *) args;
  SPACEDB_FILE_TYPE spacedb_ftype;
  int error_code = NO_ERROR;

  item = (FILE_TRACK_ITEM *) file_extdata_at (extdata, index_item);
  vpid_fhead.volid = item->volid;
  vpid_fhead.pageid = item->fileid;

  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  fhead = (FILE_HEADER *) page_fhead;

  switch (fhead->type)
    {
    case FILE_BTREE:
    case FILE_BTREE_OVERFLOW_KEY:
      /* index file */
      spacedb_ftype = SPACEDB_INDEX_FILE;
      break;
    case FILE_HEAP:
    case FILE_HEAP_REUSE_SLOTS:
    case FILE_MULTIPAGE_OBJECT_HEAP:
      /* heap file */
      spacedb_ftype = SPACEDB_HEAP_FILE;
      break;
    default:
      /* system file */
      spacedb_ftype = SPACEDB_SYSTEM_FILE;
      break;
    }
  spacedb[spacedb_ftype].nfile++;
  spacedb[spacedb_ftype].npage_ftab += fhead->n_page_ftab;
  spacedb[spacedb_ftype].npage_user += fhead->n_page_user;
  spacedb[spacedb_ftype].npage_reserved += fhead->n_page_free;

  pgbuf_unfix_and_init (thread_p, page_fhead);
  return NO_ERROR;
}

/*
 * file_tracker_spacedb () - collect space usage information from all files
 *
 * return        : error code
 * thread_p (in) : thread entry
 * spacedb (out) : output space usage information
 */
static int
file_tracker_spacedb (THREAD_ENTRY * thread_p, SPACEDB_FILES * spacedb)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead;
  FILE_HEADER *fhead;

  int error_code = NO_ERROR;

  error_code = file_tracker_map (thread_p, PGBUF_LATCH_READ, file_tracker_item_spacedb, spacedb);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      return error_code;
    }

  /* file tracker is also a system file */
  FILE_GET_HEADER_VPID (&file_Tracker_vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  fhead = (FILE_HEADER *) page_fhead;

  spacedb[SPACEDB_SYSTEM_FILE].nfile++;
  spacedb[SPACEDB_SYSTEM_FILE].npage_ftab += fhead->n_page_ftab;
  spacedb[SPACEDB_SYSTEM_FILE].npage_user += fhead->n_page_user;
  spacedb[SPACEDB_SYSTEM_FILE].npage_reserved += fhead->n_page_free;

  pgbuf_unfix_and_init (thread_p, page_fhead);
  return NO_ERROR;
}

/************************************************************************/
/* File descriptor section                                              */
/************************************************************************/

/*
 * file_descriptor_get () - get file descriptor from header
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 * desc_out (in) : output file descriptor
 */
int
file_descriptor_get (THREAD_ENTRY * thread_p, const VFID * vfid, FILE_DESCRIPTORS * desc_out)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead;
  int error_code = NO_ERROR;

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  *desc_out = fhead->descriptor;

  pgbuf_unfix (thread_p, page_fhead);
  return NO_ERROR;
}

/*
 * file_descriptor_update () - Update file descriptor
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 * des_new (in)  : new file descriptor
 */
int
file_descriptor_update (THREAD_ENTRY * thread_p, const VFID * vfid, void *des_new)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  int error_code = NO_ERROR;

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  log_append_undoredo_data2 (thread_p, RVFL_FILEDESC_UPD, NULL, page_fhead,
                 (PGLENGTH) ((char *) &fhead->descriptor - page_fhead), sizeof (fhead->descriptor),
                 sizeof (fhead->descriptor), &fhead->descriptor, des_new);

  memcpy (&fhead->descriptor, des_new, sizeof (fhead->descriptor));

  pgbuf_set_dirty (thread_p, page_fhead, FREE);
  return NO_ERROR;
}

/*
 * file_descriptor_dump () - dump file descriptor
 *
 * return        : error code
 * thread_p (in) : thread entry
 * vfid (in)     : file identifier
 * fp (in)       : output file
 */
int
file_descriptor_dump (THREAD_ENTRY * thread_p, const VFID * vfid, FILE * fp)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead = NULL;
  FILE_HEADER *fhead = NULL;
  int error_code = NO_ERROR;

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }
  fhead = (FILE_HEADER *) page_fhead;
  file_header_sanity_check (thread_p, fhead);

  file_header_dump_descriptor (thread_p, fhead, fp);

  pgbuf_unfix (thread_p, page_fhead);
  return NO_ERROR;
}


/*
 * xfile_apply_tde_to_class_files () - set TDE information to all permanent files related with class_oid
 *
 * return        : error code
 * thread_p (in) : thread entry
 * oid (in)     : class oid
 */
int
xfile_apply_tde_to_class_files (THREAD_ENTRY * thread_p, const OID * class_oid)
{
  OR_CLASSREP *or_repr = NULL;
  TDE_ALGORITHM tde_algo = TDE_ALGORITHM_NONE;
  TDE_ALGORITHM prev_tde_algo = TDE_ALGORITHM_NONE;
  HFID hfid = HFID_INITIALIZER;
  VFID hovf_vfid;
  int idx_in_cache = -1;
  int error_code = NO_ERROR;
  int i = 0;

  assert (class_oid != NULL);
  assert (!OID_ISNULL (class_oid));

  error_code = heap_get_class_tde_algorithm (thread_p, class_oid, &tde_algo);
  if (error_code != NO_ERROR)
    {
      goto exit;
    }

  /* It is expected for flags in the class record to be set in advance */
  assert (tde_algo != TDE_ALGORITHM_NONE);

  /* apply to heap file and heap overflow file */
  error_code = heap_get_class_info (thread_p, class_oid, &hfid, NULL, NULL);
  if (error_code != NO_ERROR)
    {
      goto exit;
    }

  error_code = file_apply_tde_algorithm (thread_p, &hfid.vfid, tde_algo);
  if (error_code != NO_ERROR)
    {
      goto exit;
    }

  if (heap_ovf_find_vfid (thread_p, &hfid, &hovf_vfid, false, PGBUF_UNCONDITIONAL_LATCH) != NULL)
    {
      /* not exist */
      error_code = file_apply_tde_algorithm (thread_p, &hovf_vfid, tde_algo);
      if (error_code != NO_ERROR)
    {
      goto exit;
    }
    }

  or_repr = heap_classrepr_get (thread_p, class_oid, NULL, NULL_REPRID, &idx_in_cache);
  if (or_repr == NULL)
    {
      error_code = ER_FAILED;
      goto exit;
    }

  /* apply to btree files and btree overflow files */
  for (i = 0; i < or_repr->n_indexes; i++)
    {
      VFID bovf_vfid;
      BTID btid;
      PAGE_PTR root_page = NULL;
      VPID root_vpid;
      BTREE_ROOT_HEADER *root_header = NULL;

      btid = or_repr->indexes[i].btid;

      root_vpid.volid = btid.vfid.volid;    /* read the root page */
      root_vpid.pageid = btid.root_pageid;
      root_page = pgbuf_fix (thread_p, &root_vpid, OLD_PAGE, PGBUF_LATCH_WRITE, PGBUF_UNCONDITIONAL_LATCH);
      if (root_page == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }

#if !defined (NDEBUG)
      (void) pgbuf_check_page_ptype (thread_p, root_page, PAGE_BTREE);
#endif /* !NDEBUG */

      root_header = btree_get_root_header (thread_p, root_page);
      if (root_header == NULL)
    {
      pgbuf_unfix_and_init (thread_p, root_page);
      ASSERT_ERROR_AND_SET (error_code);
      goto exit;
    }
      bovf_vfid = root_header->ovfid;

      pgbuf_unfix_and_init (thread_p, root_page);

      error_code = file_apply_tde_algorithm (thread_p, &btid.vfid, tde_algo);
      if (error_code != NO_ERROR)
    {
      goto exit;
    }

      if (!VFID_ISNULL (&bovf_vfid))
    {
      error_code = file_apply_tde_algorithm (thread_p, &bovf_vfid, tde_algo);
      if (error_code != NO_ERROR)
        {
          goto exit;
        }
    }
    }

exit:
  if (or_repr != NULL)
    {
      heap_classrepr_free_and_init (or_repr, &idx_in_cache);
    }

  return error_code;
}

/*
 * file_extdata_collect_data_sectors_part () - collect FILE_PARTIAL_SECTOR from partial table
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * item (in)     : item (FILE_PARTIAL_SECTOR)
 * index (in)    : index
 * stop (out)    : ignored
 * args (in/out) : FILE_FTAB_COLLECTOR *
 */
static int
file_extdata_collect_data_sectors_part (THREAD_ENTRY * thread_p, const void *item, int index_unused, bool * stop,
                    void *args)
{
  FILE_FTAB_COLLECTOR *collect = (FILE_FTAB_COLLECTOR *) args;
  FILE_PARTIAL_SECTOR *partsect = (FILE_PARTIAL_SECTOR *) item;

  collect->partsect_ftab[collect->nsects++] = *partsect;
  collect->npages += bit64_count_ones (partsect->page_bitmap);

  return NO_ERROR;
}

/*
 * file_extdata_collect_data_sectors_full () - collect VSID from full table and convert to FILE_PARTIAL_SECTOR
 *
 * return        : NO_ERROR
 * thread_p (in) : thread entry
 * item (in)     : item (VSID)
 * index (in)    : index
 * stop (out)    : ignored
 * args (in/out) : FILE_FTAB_COLLECTOR *
 */
static int
file_extdata_collect_data_sectors_full (THREAD_ENTRY * thread_p, const void *item, int index_unused, bool * stop,
                    void *args)
{
  FILE_FTAB_COLLECTOR *collect = (FILE_FTAB_COLLECTOR *) args;
  VSID *vsid = (VSID *) item;

  collect->partsect_ftab[collect->nsects].vsid = *vsid;
  collect->partsect_ftab[collect->nsects].page_bitmap = FILE_FULL_PAGE_BITMAP;
  collect->nsects++;
  collect->npages += DISK_SECTOR_NPAGES;

  return NO_ERROR;
}

/*
 * file_get_all_data_sectors () - collect all data sectors
 *
 * return        : NO_ERROR or error code
 * thread_p (in) : thread entry
 * hfid (in)     : heap file ID
 * collector_out(out): output collected table pages
 */
int
file_get_all_data_sectors (THREAD_ENTRY * thread_p, const VFID * vfid, FILE_FTAB_COLLECTOR * collector_out)
{
  VPID vpid_fhead;
  PAGE_PTR page_fhead;
  FILE_HEADER *fhead = NULL;
  FILE_EXTENSIBLE_DATA *extdata_ftab = NULL;
  FILE_FTAB_COLLECTOR ftab_collector;
  int i, j;
  int error_code = NO_ERROR;

  FILE_GET_HEADER_VPID (vfid, &vpid_fhead);
  page_fhead = pgbuf_fix (thread_p, &vpid_fhead, OLD_PAGE, PGBUF_LATCH_READ, PGBUF_UNCONDITIONAL_LATCH);
  if (page_fhead == NULL)
    {
      ASSERT_ERROR_AND_SET (error_code);
      return error_code;
    }

  fhead = (FILE_HEADER *) page_fhead;
  collector_out->npages = 0;
  collector_out->nsects = 0;
  collector_out->partsect_ftab =
    (FILE_PARTIAL_SECTOR *) db_private_alloc (thread_p, fhead->n_sector_total * sizeof (FILE_PARTIAL_SECTOR));

  if (collector_out->partsect_ftab == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1,
          fhead->n_sector_total * sizeof (FILE_PARTIAL_SECTOR));
      pgbuf_unfix (thread_p, page_fhead);
      return ER_OUT_OF_VIRTUAL_MEMORY;
    }

  ftab_collector.npages = 0;
  ftab_collector.nsects = 0;
  ftab_collector.partsect_ftab =
    (FILE_PARTIAL_SECTOR *) db_private_alloc (thread_p, fhead->n_page_ftab * sizeof (FILE_PARTIAL_SECTOR));

  if (ftab_collector.partsect_ftab == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1,
          fhead->n_page_ftab * sizeof (FILE_PARTIAL_SECTOR));
      pgbuf_unfix (thread_p, page_fhead);
      db_private_free_and_init (thread_p, collector_out->partsect_ftab);
      return ER_OUT_OF_VIRTUAL_MEMORY;
    }

  VSID_FROM_VPID (&ftab_collector.partsect_ftab[0].vsid, &vpid_fhead);
  ftab_collector.partsect_ftab[0].page_bitmap = FILE_EMPTY_PAGE_BITMAP;
  file_partsect_set_bit (&ftab_collector.partsect_ftab[0],
             file_partsect_pageid_to_offset (&ftab_collector.partsect_ftab[0], vpid_fhead.pageid));
  ftab_collector.nsects = 1;
  ftab_collector.npages = 1;

  FILE_HEADER_GET_PART_FTAB (fhead, extdata_ftab);
  error_code =
    file_extdata_apply_funcs (thread_p, extdata_ftab, file_extdata_collect_ftab_pages, &ftab_collector,
                  file_extdata_collect_data_sectors_part, collector_out, false, NULL, NULL);

  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      db_private_free_and_init (thread_p, ftab_collector.partsect_ftab);
      db_private_free_and_init (thread_p, collector_out->partsect_ftab);
      pgbuf_unfix (thread_p, page_fhead);
      return ER_FAILED;
    }

  if (!FILE_IS_TEMPORARY (fhead))
    {
      FILE_HEADER_GET_FULL_FTAB (fhead, extdata_ftab);
      error_code =
    file_extdata_apply_funcs (thread_p, extdata_ftab, file_extdata_collect_ftab_pages, &ftab_collector,
                  file_extdata_collect_data_sectors_full, collector_out, false, NULL, NULL);
      if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      db_private_free_and_init (thread_p, ftab_collector.partsect_ftab);
      db_private_free_and_init (thread_p, collector_out->partsect_ftab);
      pgbuf_unfix (thread_p, page_fhead);
      return ER_FAILED;
    }
    }

  for (i = 0; i < ftab_collector.nsects; i++)
    {
      FILE_PARTIAL_SECTOR *ftab_sec = &ftab_collector.partsect_ftab[i];
      FILE_PARTIAL_SECTOR *data_sec;
      for (j = 0; j < collector_out->nsects; j++)
    {
      data_sec = &collector_out->partsect_ftab[j];
      if (VSID_EQ (&ftab_sec->vsid, &data_sec->vsid))
        {
          data_sec->page_bitmap &= ~ftab_sec->page_bitmap;
          break;
        }
    }
    }

  pgbuf_unfix (thread_p, page_fhead);
  db_private_free_and_init (thread_p, ftab_collector.partsect_ftab);
  if (error_code != NO_ERROR)
    {
      ASSERT_ERROR ();
      db_private_free_and_init (thread_p, collector_out->partsect_ftab);
      return ER_FAILED;
    }

  return NO_ERROR;
}

/************************************************************************/
/* End of file                                                          */
/************************************************************************/