/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by The HDF Group.                                               *
 * Copyright by the Board of Trustees of the University of Illinois.         *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of HDF5.  The full HDF5 copyright notice, including     *
 * terms governing use, modification, and redistribution, is contained in    *
 * the COPYING file, which can be found at the root of the source code       *
 * distribution tree, or in https://support.hdfgroup.org/ftp/HDF5/releases.  *
 * If you do not have access to either file, you may request a copy from     *
 * help@hdfgroup.org.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*-------------------------------------------------------------------------
 *
 * Created:     H5Cimage.c
 *              July 20, 2015
 *              John Mainzer
 *
 * Purpose:     Functions in this file are specific to the implementation
 *		of the metadata cache image feature.
 *
 *-------------------------------------------------------------------------
 */

/****************/
/* Module Setup */
/****************/

#include "H5Cmodule.h"          /* This source code file is part of the H5C module */
#define H5F_FRIEND		/*suppress error about including H5Fpkg	  */


/***********/
/* Headers */
/***********/
#include "H5private.h"		/* Generic Functions			*/
#ifdef H5_HAVE_PARALLEL
#define H5AC_FRIEND		/*suppress error about including H5ACpkg  */
#include "H5ACpkg.h"            /* Metadata cache                       */
#endif /* H5_HAVE_PARALLEL */
#include "H5Cpkg.h"		/* Cache				*/
#include "H5Eprivate.h"		/* Error handling		  	*/
#include "H5Fpkg.h"		/* Files				*/
#include "H5FDprivate.h"	/* File drivers				*/
#include "H5FLprivate.h"	/* Free Lists                           */
#include "H5MFprivate.h"	/* File memory management		*/
#include "H5MMprivate.h"	/* Memory management			*/


/****************/
/* Local Macros */
/****************/
#if H5C_DO_MEMORY_SANITY_CHECKS
#define H5C_IMAGE_EXTRA_SPACE 8
#define H5C_IMAGE_SANITY_VALUE "DeadBeef"
#else /* H5C_DO_MEMORY_SANITY_CHECKS */
#define H5C_IMAGE_EXTRA_SPACE 0
#endif /* H5C_DO_MEMORY_SANITY_CHECKS */

/* Cache image buffer components, on disk */
#define H5C__MDCI_BLOCK_SIGNATURE	"MDCI"
#define H5C__MDCI_BLOCK_SIGNATURE_LEN	4
#define H5C__MDCI_BLOCK_VERSION_0	0

/* Metadata cache image header flags -- max 8 bits */
#define H5C__MDCI_HEADER_HAVE_RESIZE_STATUS	0x01

/* Metadata cache image entry flags -- max 8 bits */
#define H5C__MDCI_ENTRY_DIRTY_FLAG		0x01
#define H5C__MDCI_ENTRY_IN_LRU_FLAG		0x02
#define H5C__MDCI_ENTRY_IS_FD_PARENT_FLAG	0x04
#define H5C__MDCI_ENTRY_IS_FD_CHILD_FLAG	0x08

/* Limits on flush dependency values, stored in 16-bit values on disk */
#define H5C__MDCI_MAX_FD_CHILDREN		USHRT_MAX
#define H5C__MDCI_MAX_FD_PARENTS		USHRT_MAX

/* Values for image entry magic field */
#define H5C_IMAGE_ENTRY_T_MAGIC		0x005CAC08
#define H5C_IMAGE_ENTRY_T_BAD_MAGIC	0xBeefDead

/* Maximum ring allowed in image */
#define H5C_MAX_RING_IN_IMAGE   H5C_RING_MDFSM


/******************/
/* Local Typedefs */
/******************/


/********************/
/* Local Prototypes */
/********************/

/* Helper routines */
static size_t H5C__cache_image_block_entry_header_size(const H5F_t *f);
static size_t H5C__cache_image_block_header_size(const H5F_t *f);
static herr_t H5C__decode_cache_image_header(const H5F_t *f,
    H5C_t *cache_ptr, const uint8_t **buf);
#ifndef NDEBUG	/* only used in assertions */
static herr_t H5C__decode_cache_image_entry(const H5F_t *f,
    const H5C_t *cache_ptr, const uint8_t **buf, unsigned entry_num);
#endif /* NDEBUG */ /* only used in assertions */
static herr_t H5C__destroy_pf_entry_child_flush_deps(H5C_t *cache_ptr, 
    H5C_cache_entry_t *pf_entry_ptr, H5C_cache_entry_t **fd_children);
static herr_t H5C__encode_cache_image_header(const H5F_t *f,
    const H5C_t *cache_ptr, uint8_t **buf);
static herr_t H5C__encode_cache_image_entry(H5F_t *f, H5C_t *cache_ptr, 
    uint8_t **buf, unsigned entry_num);
static herr_t H5C__prep_for_file_close__compute_fd_heights(const H5C_t *cache_ptr);
static void H5C__prep_for_file_close__compute_fd_heights_real(
    H5C_cache_entry_t  *entry_ptr, uint32_t fd_height);
static herr_t H5C__prep_for_file_close__setup_image_entries_array(H5C_t *cache_ptr);
static herr_t H5C__prep_for_file_close__scan_entries(const H5F_t *f,
    H5C_t *cache_ptr);
static herr_t H5C__reconstruct_cache_contents(H5F_t *f, hid_t dxpl_id, 
    H5C_t *cache_ptr);
static H5C_cache_entry_t *H5C__reconstruct_cache_entry(const H5F_t *f,
    H5C_t *cache_ptr, const uint8_t **buf);
static herr_t H5C__write_cache_image_superblock_msg(H5F_t *f, hid_t dxpl_id, 
    hbool_t create);
static herr_t H5C__read_cache_image(H5F_t * f, hid_t dxpl_id, H5C_t *cache_ptr);
static herr_t H5C__write_cache_image(H5F_t *f, hid_t dxpl_id, const H5C_t *cache_ptr);
static herr_t H5C__construct_cache_image_buffer(H5F_t *f, H5C_t *cache_ptr);
static herr_t H5C__free_image_entries_array(H5C_t *cache_ptr);


/*********************/
/* Package Variables */
/*********************/

/* Declare a free list to manage H5C_cache_entry_t objects */
H5FL_DEFINE(H5C_cache_entry_t);


/*****************************/
/* Library Private Variables */
/*****************************/


/*******************/
/* Local Variables */
/*******************/


/*-------------------------------------------------------------------------
 *
 * Function:    H5C_cache_image_pending()
 *
 * Purpose:     Tests to see if the load of a metadata cache image 
 *              load is pending (i.e. will be executed on the next 
 *              protect or insert)
 *
 *              Returns TRUE if a cache image load is pending, and FALSE
 *              if not.  Throws an assertion failure on error.
 *
 * Return:      TRUE if a cache image load is pending, and FALSE otherwise.
 *
 * Programmer:  John Mainzer, 6/18/16
 *
 *-------------------------------------------------------------------------
 */
hbool_t
H5C_cache_image_pending(const H5C_t *cache_ptr)
{
    hbool_t             ret_value = TRUE;      /* Return value */

    FUNC_ENTER_NOAPI_NOINIT_NOERR

    /* Sanity checks */
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);

    ret_value = (cache_ptr->load_image && !cache_ptr->image_loaded);

    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C_cache_image_pending() */


/*-------------------------------------------------------------------------
 * Function:    H5C_cache_image_status()
 *
 * Purpose:     Examine the metadata cache associated with the supplied 
 *              instance of H5F_t to determine whether the load of a 
 *              cache image has either been queued or executed, and if 
 *              construction of a cache image has been requested.
 *
 *              This done, it set *load_ci_ptr to TRUE if a cache image
 *              has either been loaded or a load has been requested, and
 *              to FALSE otherwise.
 *
 *              Similarly, set *write_ci_ptr to TRUE if construction of 
 *              a cache image has been requested, and to FALSE otherwise.
 *
 * Return:      SUCCEED on success, and FAIL on failure.
 *
 * Programmer:  John Mainzer
 *              12/29/16
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5C_cache_image_status(H5F_t * f, hbool_t *load_ci_ptr, hbool_t *write_ci_ptr)
{
    H5C_t *     cache_ptr;

    FUNC_ENTER_NOAPI_NOINIT_NOERR

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    cache_ptr = f->shared->cache;
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(load_ci_ptr);
    HDassert(write_ci_ptr);
 
    *load_ci_ptr = cache_ptr->load_image || cache_ptr->image_loaded;
    *write_ci_ptr = cache_ptr->image_ctl.generate_image;

    FUNC_LEAVE_NOAPI(SUCCEED)
} /* H5C_cache_image_status() */


/*-------------------------------------------------------------------------
 * Function:    H5C__construct_cache_image_buffer()
 *
 * Purpose:     Allocate a buffer of size cache_ptr->image_len, and 
 *		load it with an image of the metadata cache image block.
 *
 *		Note that by the time this function is called, the cache
 *		should have removed all entries from its data structures.
 *
 * Return:      SUCCEED on success, and FAIL on failure.
 *
 * Programmer:  John Mainzer
 *              8/5/15
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__construct_cache_image_buffer(H5F_t * f, H5C_t *cache_ptr)
{
    uint8_t *	p;                      /* Pointer into image buffer */
    uint32_t    chksum;
    unsigned	u;                      /* Local index variable */
    herr_t 	ret_value = SUCCEED;    /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    HDassert(cache_ptr == f->shared->cache);
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(cache_ptr->close_warning_received);
    HDassert(cache_ptr->image_ctl.generate_image);
    HDassert(cache_ptr->num_entries_in_image > 0);
    HDassert(cache_ptr->index_len == 0);
    HDassert(cache_ptr->image_data_len > 0);
    HDassert(cache_ptr->image_data_len <= cache_ptr->image_len);

    /* Allocate the buffer in which to construct the cache image block */
    if(NULL == (cache_ptr->image_buffer = H5MM_malloc(cache_ptr->image_len + 1)))
	HGOTO_ERROR(H5E_CACHE, H5E_CANTALLOC, FAIL, "memory allocation failed for cache image buffer")

    /* Construct the cache image block header image */
    p = (uint8_t *)cache_ptr->image_buffer;
    if(H5C__encode_cache_image_header(f, cache_ptr, &p) < 0)
        HGOTO_ERROR(H5E_CACHE, H5E_CANTENCODE, FAIL, "header image construction failed")
    HDassert((size_t)(p - (uint8_t *)cache_ptr->image_buffer) < cache_ptr->image_data_len);

    /* Construct the cache entry images */
    for(u = 0; u < cache_ptr->num_entries_in_image; u++)
	if(H5C__encode_cache_image_entry(f, cache_ptr, &p, u) < 0)
            HGOTO_ERROR(H5E_CACHE, H5E_CANTENCODE, FAIL, "entry image construction failed")
    HDassert((size_t)(p - (uint8_t *)cache_ptr->image_buffer) < cache_ptr->image_data_len);

    /* Construct the adaptive resize status image -- not yet */

    /* Compute the checksum and encode */
    chksum = H5_checksum_metadata(cache_ptr->image_buffer, (size_t)(cache_ptr->image_data_len - H5F_SIZEOF_CHKSUM), 0);
    UINT32ENCODE(p, chksum);
    HDassert((size_t)(p - (uint8_t *)cache_ptr->image_buffer) == cache_ptr->image_data_len);
    HDassert((size_t)(p - (uint8_t *)cache_ptr->image_buffer) <= cache_ptr->image_len);

#ifndef NDEBUG
    /* validate the metadata cache image we just constructed by decoding it
     * and comparing the result with the original data.
     */
    {
        uint32_t        old_chksum;
        const uint8_t *	q;
        H5C_t *	        fake_cache_ptr = NULL;
        unsigned        v;
        herr_t          status;      /* Status from decoding */

	fake_cache_ptr = (H5C_t *)H5MM_malloc(sizeof(H5C_t));
        HDassert(fake_cache_ptr);
        fake_cache_ptr->magic = H5C__H5C_T_MAGIC;

	/* needed for sanity checks */
	fake_cache_ptr->image_len = cache_ptr->image_len;
        q = (const uint8_t *)cache_ptr->image_buffer;
        status = H5C__decode_cache_image_header(f, fake_cache_ptr, &q);
        HDassert(status >= 0);

        HDassert(NULL != p);
        HDassert(fake_cache_ptr->num_entries_in_image == cache_ptr->num_entries_in_image);

	fake_cache_ptr->image_entries = (H5C_image_entry_t *)H5MM_malloc(sizeof(H5C_image_entry_t) *
                (size_t)(fake_cache_ptr->num_entries_in_image + 1));
	HDassert(fake_cache_ptr->image_entries);

        for(u = 0; u < fake_cache_ptr->num_entries_in_image; u++) {
	    (fake_cache_ptr->image_entries)[u].magic = H5C_IMAGE_ENTRY_T_MAGIC;
            (fake_cache_ptr->image_entries)[u].image_ptr = NULL;

	    /* touch up f->shared->cache to satisfy sanity checks... */
            f->shared->cache = fake_cache_ptr;
	    status = H5C__decode_cache_image_entry(f, fake_cache_ptr, &q, u);
	    HDassert(status >= 0);

	    /* ...and then return f->shared->cache to its correct value */
            f->shared->cache = cache_ptr;

	    /* verify expected contents */
	    HDassert((cache_ptr->image_entries)[u].addr == (fake_cache_ptr->image_entries)[u].addr);
	    HDassert((cache_ptr->image_entries)[u].size == (fake_cache_ptr->image_entries)[u].size);
	    HDassert((cache_ptr->image_entries)[u].type_id == (fake_cache_ptr->image_entries)[u].type_id);
	    HDassert((cache_ptr->image_entries)[u].lru_rank == (fake_cache_ptr->image_entries)[u].lru_rank);
	    HDassert((cache_ptr->image_entries)[u].is_dirty == (fake_cache_ptr->image_entries)[u].is_dirty);
	    /* don't check image_fd_height as it is not stored in 
             * the metadata cache image block.
             */
	    HDassert((cache_ptr->image_entries)[u].fd_child_count == (fake_cache_ptr->image_entries)[u].fd_child_count);
	    HDassert((cache_ptr->image_entries)[u].fd_dirty_child_count == (fake_cache_ptr->image_entries)[u].fd_dirty_child_count);
	    HDassert((cache_ptr->image_entries)[u].fd_parent_count == (fake_cache_ptr->image_entries)[u].fd_parent_count);

	    for(v = 0; v < (cache_ptr->image_entries)[u].fd_parent_count; v++)
		HDassert((cache_ptr->image_entries)[u].fd_parent_addrs[v] == (fake_cache_ptr->image_entries)[u].fd_parent_addrs[v]);

	    /* free the fd_parent_addrs array if it exists */
	    if((fake_cache_ptr->image_entries)[u].fd_parent_addrs) {
		HDassert((fake_cache_ptr->image_entries)[u].fd_parent_count > 0);
		(fake_cache_ptr->image_entries)[u].fd_parent_addrs = (haddr_t *)H5MM_xfree((fake_cache_ptr->image_entries)[u].fd_parent_addrs);
		(fake_cache_ptr->image_entries)[u].fd_parent_count = 0;
	    } /* end if */
            else 
		HDassert((fake_cache_ptr->image_entries)[u].fd_parent_count == 0);

	    HDassert((cache_ptr->image_entries)[u].image_ptr);
            HDassert((fake_cache_ptr->image_entries)[u].image_ptr);
            HDassert(!HDmemcmp((cache_ptr->image_entries)[u].image_ptr,
                               (fake_cache_ptr->image_entries)[u].image_ptr,
                               (cache_ptr->image_entries)[u].size));

	    (fake_cache_ptr->image_entries)[u].image_ptr = H5MM_xfree((fake_cache_ptr->image_entries)[u].image_ptr);
	} /* end for */

        HDassert((size_t)(q - (const uint8_t *)cache_ptr->image_buffer) == cache_ptr->image_data_len - H5F_SIZEOF_CHKSUM);

        /* compute the checksum  */
        old_chksum = chksum;
        chksum = H5_checksum_metadata(cache_ptr->image_buffer, (size_t)(cache_ptr->image_data_len - H5F_SIZEOF_CHKSUM), 0);
	HDassert(chksum == old_chksum);

	fake_cache_ptr->image_entries = (H5C_image_entry_t *)H5MM_xfree(fake_cache_ptr->image_entries);
	fake_cache_ptr = (H5C_t *)H5MM_xfree(fake_cache_ptr);
    } /* end block */
#endif /* NDEBUG */

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__construct_cache_image_buffer() */


/*-------------------------------------------------------------------------
 * Function:    H5C__generate_cache_image()
 *
 * Purpose:	Generate the cache image and write it to the file, if
 *		directed.
 *
 * Return:      SUCCEED on success, and FAIL on failure.
 *
 * Programmer:  Quincey Koziol
 *              1/26/17
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5C__generate_cache_image(H5F_t *f, hid_t dxpl_id, H5C_t *cache_ptr)
{
    herr_t 	ret_value = SUCCEED;    /* Return value */

    FUNC_ENTER_PACKAGE

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    HDassert(cache_ptr == f->shared->cache);
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);

    /* Construct cache image */
    if(H5C__construct_cache_image_buffer(f, cache_ptr) < 0)
        HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "Can't create metadata cache image")

    /* Free image entries array */
    if(H5C__free_image_entries_array(cache_ptr) < 0)
        HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "Can't free image entries array")

    /* Write cache image block if so configured */
    if(cache_ptr->image_ctl.flags & H5C_CI__GEN_MDC_IMAGE_BLK) {
        if(H5C__write_cache_image(f, dxpl_id, cache_ptr) < 0)
            HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "Can't write metadata cache image block to file")

        H5C__UPDATE_STATS_FOR_CACHE_IMAGE_CREATE(cache_ptr);
    } /* end if */

    /* Free cache image buffer */
    HDassert(cache_ptr->image_buffer);
    cache_ptr->image_buffer = H5MM_xfree(cache_ptr->image_buffer);

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__generate_cache_image() */


/*-------------------------------------------------------------------------
 * Function:    H5C__deserialize_prefetched_entry()
 *
 * Purpose:     Deserialize the supplied prefetched entry entry, and return
 *		a pointer to the deserialized entry in *entry_ptr_ptr. 
 *		If successful, remove the prefetched entry from the cache,
 *		and free it.  Insert the deserialized entry into the cache.
 *
 *		Note that the on disk image of the entry is not freed -- 
 *		a pointer to it is stored in the deserialized entries'
 *		image_ptr field, and its image_up_to_date field is set to
 *		TRUE unless the entry is dirtied by the deserialize call.
 *
 *		If the prefetched entry is a flush dependency child,
 *		destroy that flush dependency prior to calling the 
 *		deserialize callback.  If appropriate, the flush dependency
 *		relationship will be recreated by the cache client.
 *
 *		If the prefetched entry is a flush dependency parent,
 *		destroy the flush dependency relationship with all its 
 *		children.  As all these children must be prefetched entries,
 *		recreate these flush dependency relationships with 
 *		deserialized entry after it is inserted in the cache.
 *
 *		Since deserializing a prefetched entry is semantically 
 *		equivalent to a load, issue an entry loaded nofification 
 *		if the notify callback is defined.
 *
 * Return:      SUCCEED on success, and FAIL on failure.
 *
 *		Note that *entry_ptr_ptr is undefined on failure.
 *
 * Programmer:  John Mainzer, 8/10/15
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5C__deserialize_prefetched_entry(H5F_t *f, hid_t dxpl_id, H5C_t *cache_ptr,
    H5C_cache_entry_t **entry_ptr_ptr, const H5C_class_t *type,
    haddr_t addr, void *udata)
{
    hbool_t		dirty = FALSE;  /* Flag indicating whether thing was 
                                         * dirtied during deserialize 
                                         */
    size_t              len;            /* Size of image in file */
    void *		thing = NULL;   /* Pointer to thing loaded */
    H5C_cache_entry_t * pf_entry_ptr;   /* pointer to the prefetched entry   */
                                        /* supplied in *entry_ptr_ptr.       */
    H5C_cache_entry_t *	ds_entry_ptr;   /* Alias for thing loaded, as cache 
                                         * entry 
                                         */
    H5C_cache_entry_t** fd_children = NULL; /* Pointer to a dynamically      */
                                        /* allocated array of pointers to    */
                                        /* the flush dependency children of  */
                                        /* the prefetched entry, or NULL if  */
                                        /* that array does not exist.        */
    unsigned            flush_flags = (H5C__FLUSH_INVALIDATE_FLAG | 
				       H5C__FLUSH_CLEAR_ONLY_FLAG);
    int			i;
    herr_t      	ret_value = SUCCEED;      /* Return value */

    FUNC_ENTER_PACKAGE

    /* sanity checks */
    HDassert(f);
    HDassert(f->shared);
    HDassert(f->shared->cache);
    HDassert(f->shared->cache == cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(entry_ptr_ptr);
    HDassert(*entry_ptr_ptr);
    pf_entry_ptr = *entry_ptr_ptr;
    HDassert(pf_entry_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
    HDassert(pf_entry_ptr->type);
    HDassert(pf_entry_ptr->type->id == H5AC_PREFETCHED_ENTRY_ID);
    HDassert(pf_entry_ptr->prefetched);
    HDassert(pf_entry_ptr->image_up_to_date);
    HDassert(pf_entry_ptr->image_ptr);
    HDassert(pf_entry_ptr->size > 0);
    HDassert(pf_entry_ptr->addr == addr);
    HDassert(type);
    HDassert(type->id == pf_entry_ptr->prefetch_type_id);
    HDassert(type->mem_type == cache_ptr->class_table_ptr[type->id]->mem_type);

    /* verify absence of prohibited or unsupported type flag combinations */
    HDassert(!(type->flags & H5C__CLASS_SKIP_READS));
 
    /* Can't see how skip reads could be usefully combined with 
     * either the speculative read flag.  Hence disallow.
     */
    HDassert(!((type->flags & H5C__CLASS_SKIP_READS) &&
               (type->flags & H5C__CLASS_SPECULATIVE_LOAD_FLAG)));
    HDassert(H5F_addr_defined(addr));
    HDassert(type->get_initial_load_size);
    HDassert(type->deserialize);

    /* if *pf_entry_ptr is a flush dependency child, destroy all such
     * relationships now.  The client will restore the relationship(s) with
     * the deserialized entry if appropriate.
     */
    HDassert(pf_entry_ptr->fd_parent_count == pf_entry_ptr->flush_dep_nparents);
    for(i = (int)(pf_entry_ptr->fd_parent_count) - 1; i >= 0; i--) {
        HDassert(pf_entry_ptr->flush_dep_parent);
        HDassert(pf_entry_ptr->flush_dep_parent[i]);
        HDassert(pf_entry_ptr->flush_dep_parent[i]->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
        HDassert(pf_entry_ptr->flush_dep_parent[i]->flush_dep_nchildren > 0);
        HDassert(pf_entry_ptr->fd_parent_addrs);
        HDassert(pf_entry_ptr->flush_dep_parent[i]->addr == pf_entry_ptr->fd_parent_addrs[i]);
        
        if(H5C_destroy_flush_dependency(pf_entry_ptr->flush_dep_parent[i], pf_entry_ptr) < 0)
            HGOTO_ERROR(H5E_CACHE, H5E_CANTUNDEPEND, FAIL, "can't destroy pf entry parent flush dependency")

        pf_entry_ptr->fd_parent_addrs[i] = HADDR_UNDEF;
    } /* end for */
    HDassert(pf_entry_ptr->flush_dep_nparents == 0);

    /* If *pf_entry_ptr is a flush dependency parent, destroy its flush 
     * dependency relationships with all its children (which must be 
     * prefetched entries as well).
     *
     * These flush dependency relationships will have to be restored 
     * after the deserialized entry is inserted into the cache in order
     * to transfer these relationships to the new entry.  Hence save the
     * pointers to the flush dependency children of *pf_enty_ptr for later
     * use.
     */
    if(pf_entry_ptr->fd_child_count > 0) {
        if(NULL == (fd_children = (H5C_cache_entry_t **)H5MM_calloc(sizeof(H5C_cache_entry_t **) * (size_t)(pf_entry_ptr->fd_child_count + 1))))
            HGOTO_ERROR(H5E_CACHE, H5E_CANTALLOC, FAIL, "memory allocation failed for fd child ptr array")

        if(H5C__destroy_pf_entry_child_flush_deps(cache_ptr, pf_entry_ptr, fd_children) < 0)
            HGOTO_ERROR(H5E_CACHE, H5E_CANTUNDEPEND, FAIL, "can't destroy pf entry child flush dependency(s).")
    } /* end if */

    /* Since the size of the on disk image is known exactly, there is 
     * no need for either a call to the get_initial_load_size() callback, 
     * or retries if the H5C__CLASS_SPECULATIVE_LOAD_FLAG flag is set.
     * Similarly, there is no need to clamp possible reads beyond
     * EOF.
     */
    len = pf_entry_ptr->size;

    /* Deserialize the prefetched on-disk image of the entry into the 
     * native memory form 
     */
    if(NULL == (thing = type->deserialize(pf_entry_ptr->image_ptr, len, udata, &dirty)))
        HGOTO_ERROR(H5E_CACHE, H5E_CANTLOAD, FAIL, "Can't deserialize image")
    ds_entry_ptr = (H5C_cache_entry_t *)thing;

    /* In general, an entry should be clean just after it is loaded.
     *
     * However, when this code is used in the metadata cache, it is
     * possible that object headers will be dirty at this point, as
     * the deserialize function will alter object headers if necessary to
     * fix an old bug.
     *
     * In the following assert:
     *
     * 	HDassert( ( dirty == FALSE ) || ( type->id == 5 || type->id == 6 ) );
     *
     * note that type ids 5 & 6 are associated with object headers in the 
     * metadata cache.
     *
     * When we get to using H5C for other purposes, we may wish to
     * tighten up the assert so that the loophole only applies to the
     * metadata cache.
     *
     * Note that at present, dirty can't be set to true with prefetched 
     * entries.  However this may change, so include this functionality
     * against that posibility.
     *
     * Also, note that it is possible for a prefetched entry to be dirty --
     * hence the value assigned to ds_entry_ptr->is_dirty below.
     */

    HDassert( ( dirty == FALSE ) || ( type->id == 5 || type->id == 6) );

    ds_entry_ptr->magic                     	= H5C__H5C_CACHE_ENTRY_T_MAGIC;
    ds_entry_ptr->cache_ptr                 	= f->shared->cache;
    ds_entry_ptr->addr                      	= addr;
    ds_entry_ptr->size                      	= len;
    HDassert(ds_entry_ptr->size < H5C_MAX_ENTRY_SIZE);
    ds_entry_ptr->image_ptr                 	= pf_entry_ptr->image_ptr;
    ds_entry_ptr->image_up_to_date          	= !dirty;
    ds_entry_ptr->type                      	= type;
    ds_entry_ptr->is_dirty	            	= dirty | pf_entry_ptr->is_dirty;
    ds_entry_ptr->dirtied                   	= FALSE;
    ds_entry_ptr->is_protected              	= FALSE;
    ds_entry_ptr->is_read_only              	= FALSE;
    ds_entry_ptr->ro_ref_count              	= 0;
    ds_entry_ptr->is_pinned                 	= FALSE;
    ds_entry_ptr->in_slist                  	= FALSE;
    ds_entry_ptr->flush_marker              	= FALSE;
#ifdef H5_HAVE_PARALLEL
    ds_entry_ptr->clear_on_unprotect        	= FALSE;
    ds_entry_ptr->flush_immediately         	= FALSE;
    ds_entry_ptr->coll_access               	= FALSE;
#endif /* H5_HAVE_PARALLEL */
    ds_entry_ptr->flush_in_progress         	= FALSE;
    ds_entry_ptr->destroy_in_progress       	= FALSE;

    ds_entry_ptr->ring		            	= pf_entry_ptr->ring;

    /* Initialize flush dependency height fields */
    ds_entry_ptr->flush_dep_parent          	= NULL;
    ds_entry_ptr->flush_dep_nparents        	= 0;
    ds_entry_ptr->flush_dep_parent_nalloc   	= 0;
    ds_entry_ptr->flush_dep_nchildren       	= 0;
    ds_entry_ptr->flush_dep_ndirty_children 	= 0;
    ds_entry_ptr->flush_dep_nunser_children 	= 0;

    /* Initialize fields supporting the hash table: */
    ds_entry_ptr->ht_next                   	= NULL;
    ds_entry_ptr->ht_prev                   	= NULL;
    ds_entry_ptr->il_next                   	= NULL;
    ds_entry_ptr->il_prev                   	= NULL;

    /* Initialize fields supporting replacement policies: */
    ds_entry_ptr->next                      	= NULL;
    ds_entry_ptr->prev                      	= NULL;
    ds_entry_ptr->aux_next                  	= NULL;
    ds_entry_ptr->aux_prev                  	= NULL;
#ifdef H5_HAVE_PARALLEL
    pf_entry_ptr->coll_next                 	= NULL;
    pf_entry_ptr->coll_prev                 	= NULL;
#endif /* H5_HAVE_PARALLEL */

    /* Initialize cache image related fields */
    ds_entry_ptr->include_in_image          	= FALSE;
    ds_entry_ptr->lru_rank	            	= 0;
    ds_entry_ptr->image_dirty	            	= FALSE;
    ds_entry_ptr->fd_parent_count           	= 0;
    ds_entry_ptr->fd_parent_addrs           	= NULL;
    ds_entry_ptr->fd_child_count            	= pf_entry_ptr->fd_child_count;
    ds_entry_ptr->fd_dirty_child_count      	= 0;
    ds_entry_ptr->image_fd_height           	= 0;
    ds_entry_ptr->prefetched	            	= FALSE;
    ds_entry_ptr->prefetch_type_id          	= 0;
    ds_entry_ptr->age		          	= 0;
    ds_entry_ptr->prefetched_dirty              = pf_entry_ptr->prefetched_dirty;
#ifndef NDEBUG  /* debugging field */
    ds_entry_ptr->serialization_count           = 0;
#endif /* NDEBUG */

    H5C__RESET_CACHE_ENTRY_STATS(ds_entry_ptr);

    /* Apply to to the newly deserialized entry */
    if(H5C__tag_entry(cache_ptr, ds_entry_ptr, dxpl_id) < 0)
        HGOTO_ERROR(H5E_CACHE, H5E_CANTTAG, FAIL, "Cannot tag metadata entry")

    /* We have successfully deserialized the prefetched entry.
     *
     * Before we return a pointer to the deserialized entry, we must remove
     * the prefetched entry from the cache, discard it, and replace it with 
     * the deserialized entry.  Note that we do not free the prefetched 
     * entries image, as that has been transferred to the deserialized
     * entry.
     *
     * Also note that we have not yet restored any flush dependencies.  This
     * must wait until the deserialized entry is inserted in the cache.
     *
     * To delete the prefetched entry from the cache:
     *
     *  1) Set pf_entry_ptr->image_ptr to NULL.  Since we have already
     *     transferred the buffer containing the image to *ds_entry_ptr,
     *	   this is not a memory leak.
     * 
     *  2) Call H5C__flush_single_entry() with the H5C__FLUSH_INVALIDATE_FLAG
     *     and H5C__FLUSH_CLEAR_ONLY_FLAG flags set.
     */
    pf_entry_ptr->image_ptr = NULL; 
    if(pf_entry_ptr->is_dirty) {
        HDassert(pf_entry_ptr->in_slist);
        flush_flags |= H5C__DEL_FROM_SLIST_ON_DESTROY_FLAG;
    } /* end if */

    if(H5C__flush_single_entry(f, dxpl_id, pf_entry_ptr, flush_flags) < 0)
        HGOTO_ERROR(H5E_CACHE, H5E_CANTEXPUNGE, FAIL, "can't expunge prefetched entry")

#ifndef NDEGUG /* verify deletion */
    H5C__SEARCH_INDEX(cache_ptr, addr, pf_entry_ptr, FAIL);

    HDassert(NULL == pf_entry_ptr);
#endif /* NDEBUG */

    /* Insert the deserialized entry into the cache.  */
    H5C__INSERT_IN_INDEX(cache_ptr, ds_entry_ptr, FAIL)

    HDassert(!ds_entry_ptr->in_slist);
    if(ds_entry_ptr->is_dirty)
        H5C__INSERT_ENTRY_IN_SLIST(cache_ptr, ds_entry_ptr, FAIL)

    H5C__UPDATE_RP_FOR_INSERTION(cache_ptr, ds_entry_ptr, FAIL)

    /* Deserializing a prefetched entry is the conceptual equivalent of 
     * loading it from file.  If the deserialized entry has a notify callback,
     * send an "after load" notice now that the deserialized entry is fully
     * integrated into the cache.
     */
    if(ds_entry_ptr->type->notify &&
            (ds_entry_ptr->type->notify)(H5C_NOTIFY_ACTION_AFTER_LOAD, ds_entry_ptr) < 0)
        HGOTO_ERROR(H5E_CACHE, H5E_CANTNOTIFY, FAIL, "can't notify client about entry loaded into cache")

    /* Restore flush dependencies with the flush dependency children of 
     * of the prefetched entry.  Note that we must protect *ds_entry_ptr 
     * before the call to avoid triggering sanity check failures, and 
     * then unprotect it afterwards.
     */
    i = 0;
    if(fd_children != NULL) {
        H5C__UPDATE_RP_FOR_PROTECT(cache_ptr, ds_entry_ptr, FAIL)
        ds_entry_ptr->is_protected = TRUE;
        while(fd_children[i] != NULL) {
            /* Sanity checks */
            HDassert((fd_children[i])->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
            HDassert((fd_children[i])->prefetched);
            HDassert((fd_children[i])->fd_parent_count > 0);
            HDassert((fd_children[i])->fd_parent_addrs);

#ifndef NDEBUG
            {
                int j;
                hbool_t found;

                j = 0;
                found = FALSE;
                while((j < (int)((fd_children[i])->fd_parent_count)) && (!found)) {
                    if((fd_children[i])->fd_parent_addrs[j] == ds_entry_ptr->addr)
                        found = TRUE;

                    j++;
                } /* end while */
                HDassert(found);
            }
#endif /* NDEBUG */

            if(H5C_create_flush_dependency(ds_entry_ptr, fd_children[i]) < 0)
                HGOTO_ERROR(H5E_CACHE, H5E_CANTDEPEND, FAIL, "Can't restore child flush dependency")

            i++;
        } /* end while */

        H5C__UPDATE_RP_FOR_UNPROTECT(cache_ptr, ds_entry_ptr, FAIL);
        ds_entry_ptr->is_protected = FALSE;
    } /* end if ( fd_children != NULL ) */
    HDassert((unsigned)i == ds_entry_ptr->fd_child_count);

    ds_entry_ptr->fd_child_count = 0;
    H5C__UPDATE_STATS_FOR_PREFETCH_HIT(cache_ptr)

    /* finally, pass ds_entry_ptr back to the caller */
    *entry_ptr_ptr = ds_entry_ptr;

done:
    if(fd_children)
        fd_children = (H5C_cache_entry_t **)H5MM_xfree((void *)fd_children);

    /* Release resources on error */
    if(FAIL == ret_value)
        if(thing && type->free_icr(thing) < 0)
            HDONE_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "free_icr callback failed")

    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__deserialize_prefetched_entry() */


/*-------------------------------------------------------------------------
 * Function:    H5C__free_image_entries_array
 *
 * Purpose:     If the image entries array exists, free the image 
 *		associated with each entry, and then free the image 
 *		entries array proper.
 *
 *		Note that by the time this function is called, the cache
 *		should have removed all entries from its data structures.
 *
 * Return:      SUCCEED on success, and FAIL on failure.
 *
 * Programmer:  John Mainzer
 *              8/4/15
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__free_image_entries_array(H5C_t * cache_ptr)
{
    FUNC_ENTER_STATIC_NOERR

    /* Sanity checks */
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(cache_ptr->close_warning_received);
    HDassert(cache_ptr->image_ctl.generate_image);
    HDassert(cache_ptr->index_len == 0);

    /* Check for entries to free */
    if(cache_ptr->image_entries != NULL) {
        unsigned u;     /* Local index variable */

        for(u = 0; u < cache_ptr->num_entries_in_image; u++) {
            H5C_image_entry_t *ie_ptr;          /* Image entry to release */

            /* Get pointer to image entry */
 	    ie_ptr = &((cache_ptr->image_entries)[u]);

            /* Sanity checks */ 
	    HDassert(ie_ptr);
            HDassert(ie_ptr->magic == H5C_IMAGE_ENTRY_T_MAGIC);
	    HDassert(ie_ptr->image_ptr);

	    /* Free the parent addrs array if appropriate */
	    if(ie_ptr->fd_parent_addrs) {
		HDassert(ie_ptr->fd_parent_count > 0);

		ie_ptr->fd_parent_addrs = (haddr_t *)H5MM_xfree(ie_ptr->fd_parent_addrs);
            } /* end if */
            else
		HDassert(ie_ptr->fd_parent_count == 0);

            /* Free the image */
            ie_ptr->image_ptr = H5MM_xfree(ie_ptr->image_ptr);

	    /* Set magic field to bad magic so we can detect freed entries */
	    ie_ptr->magic = H5C_IMAGE_ENTRY_T_BAD_MAGIC;
	} /* end for */

	/* Free the image entries array */
	cache_ptr->image_entries = (H5C_image_entry_t *)H5MM_xfree(cache_ptr->image_entries);
    } /* end if */

    FUNC_LEAVE_NOAPI(SUCCEED)
} /* H5C__free_image_entries_array() */


/*-------------------------------------------------------------------------
 * Function:    H5C_force_cache_image_load()
 *
 * Purpose:     On rare occasions, it is necessary to run 
 *		H5MF_tidy_self_referential_fsm_hack() prior to the first
 *              metadata cache access.  This is a problem as if there is a 
 *              cache image at the end of the file, that routine will 
 *              discard it.
 *
 *              We solve this issue by calling this function, which will
 *		load the cache image and then call 
 *              H5MF_tidy_self_referential_fsm_hack() to discard it.
 *
 * Return:      SUCCEED on success, and FAIL on failure.
 *
 * Programmer:  John Mainzer
 *              1/11/17
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5C_force_cache_image_load(H5F_t *f, hid_t dxpl_id)
{
    H5C_t *cache_ptr;
    herr_t ret_value = SUCCEED;      /* Return value */

    FUNC_ENTER_NOAPI(FAIL)

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    cache_ptr = f->shared->cache;
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(cache_ptr->load_image);

    /* Load the cache image, if requested */
    if(cache_ptr->load_image) {
        cache_ptr->load_image = FALSE;
        if(H5C__load_cache_image(f, dxpl_id) < 0)
            HGOTO_ERROR(H5E_CACHE, H5E_CANTLOAD, FAIL, "can't load cache image")
    } /* end if */

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C_force_cache_image_load() */


/*-------------------------------------------------------------------------
 * Function:    H5C_get_cache_image_config
 *
 * Purpose:     Copy the current configuration for cache image generation
 *              on file close into the instance of H5C_cache_image_ctl_t
 *              pointed to by config_ptr.
 *
 * Return:      SUCCEED on success, and FAIL on failure.
 *
 * Programmer:  John Mainzer
 *              7/3/15
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5C_get_cache_image_config(const H5C_t * cache_ptr,
    H5C_cache_image_ctl_t *config_ptr)
{
    herr_t ret_value = SUCCEED;      /* Return value */

    FUNC_ENTER_NOAPI(FAIL)

    if((cache_ptr == NULL) || (cache_ptr->magic != H5C__H5C_T_MAGIC))
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "Bad cache_ptr on entry")
    if(config_ptr == NULL)
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "Bad config_ptr on entry")

    *config_ptr = cache_ptr->image_ctl;

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C_get_cache_image_config() */


/*-------------------------------------------------------------------------
 * Function:    H5C_image_stats
 *
 * Purpose:     Prints statistics specific to the cache image.
 *
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              10/26/15
 *
 *-------------------------------------------------------------------------
 */
herr_t
#if H5C_COLLECT_CACHE_STATS
H5C_image_stats(H5C_t * cache_ptr, hbool_t print_header)
#else /* H5C_COLLECT_CACHE_STATS */
H5C_image_stats(H5C_t * cache_ptr, hbool_t H5_ATTR_UNUSED print_header)
#endif /* H5C_COLLECT_CACHE_STATS */
{
#if H5C_COLLECT_CACHE_STATS
    int         i;
    int64_t     total_hits = 0;
    int64_t     total_misses = 0;
    double      hit_rate;
    double      prefetch_use_rate;
#endif /* H5C_COLLECT_CACHE_STATS */
    herr_t      ret_value = SUCCEED;   /* Return value */

    FUNC_ENTER_NOAPI(FAIL)

    if(!cache_ptr || cache_ptr->magic != H5C__H5C_T_MAGIC)
        HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "Bad cache_ptr")

#if H5C_COLLECT_CACHE_STATS
    for(i = 0; i <= cache_ptr->max_type_id; i++) {
        total_hits              += cache_ptr->hits[i];
        total_misses            += cache_ptr->misses[i];
    } /* end for */

    if((total_hits > 0) || (total_misses > 0))
        hit_rate = (double)100.0f * ((double)(total_hits)) / ((double)(total_hits + total_misses));
    else
        hit_rate = 0.0f;

    if(cache_ptr->prefetches > 0)
        prefetch_use_rate = (double)100.0f * ((double)(cache_ptr->prefetch_hits)) /
                   ((double)(cache_ptr->prefetches));
    else
        prefetch_use_rate = 0.0f;

    if(print_header) {
        HDfprintf(stdout,
           "\nhit     prefetches      prefetch              image  pf hit\n");
        HDfprintf(stdout,
             "rate:   total:  dirty:  hits:  flshs:  evct:  size:  rate:\n");
    } /* end if */

    HDfprintf(stdout,
           "%3.1lf    %5lld   %5lld   %5lld  %5lld   %5lld   %5lld   %3.1lf\n",
            hit_rate,
            (long long)(cache_ptr->prefetches),
            (long long)(cache_ptr->dirty_prefetches),
            (long long)(cache_ptr->prefetch_hits),
            (long long)(cache_ptr->flushes[H5AC_PREFETCHED_ENTRY_ID]),
            (long long)(cache_ptr->evictions[H5AC_PREFETCHED_ENTRY_ID]),
            (long long)(cache_ptr->last_image_size),
            prefetch_use_rate);
#endif /* H5C_COLLECT_CACHE_STATS */

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C_image_stats() */


/*-------------------------------------------------------------------------
 * Function:    H5C__read_cache_image
 *
 * Purpose:	Load the metadata cache image from the specified location
 *		in the file, and return it in the supplied buffer.
 *
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              8/16/15
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5C__read_cache_image(H5F_t *f, hid_t dxpl_id, H5C_t *cache_ptr)
{
    herr_t              ret_value = SUCCEED;    /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(f);
    HDassert(cache_ptr);
    HDassert(H5F_addr_defined(cache_ptr->image_addr));
    HDassert(cache_ptr->image_len > 0);
    HDassert(cache_ptr->image_buffer);

#ifdef H5_HAVE_PARALLEL
{
    H5AC_aux_t *aux_ptr = (H5AC_aux_t *)cache_ptr->aux_ptr;
    int mpi_result;

    if ( ( NULL == aux_ptr ) || ( aux_ptr->mpi_rank == 0 ) ) {

	HDassert((NULL == aux_ptr) || 
                 (aux_ptr->magic == H5AC__H5AC_AUX_T_MAGIC));
#endif /* H5_HAVE_PARALLEL */

	/* Read the buffer (if serial access, or rank 0 of parallel access) */
        if ( H5F_block_read(f, H5FD_MEM_SUPER, cache_ptr->image_addr, 
                            cache_ptr->image_len, dxpl_id, 
                            cache_ptr->image_buffer) < 0)

            HGOTO_ERROR(H5E_CACHE, H5E_READERROR, FAIL, \
                        "Can't read metadata cache image block")

        H5C__UPDATE_STATS_FOR_CACHE_IMAGE_READ(cache_ptr)

#ifdef H5_HAVE_PARALLEL
	if ( aux_ptr ) {

	    /* Broadcast cache image */
            if ( MPI_SUCCESS != 
                 (mpi_result = MPI_Bcast(cache_ptr->image_buffer, 
                                         (int)cache_ptr->image_len, MPI_BYTE, 
                                         0, aux_ptr->mpi_comm)) )

                HMPI_GOTO_ERROR(FAIL, "MPI_Bcast failed", mpi_result)

        } /* end if */
    } /* end if */
    else if ( aux_ptr ) {

        /* Retrieve the contents of the metadata cache image from process 0 */
        if ( MPI_SUCCESS != 
             (mpi_result = MPI_Bcast(cache_ptr->image_buffer, 
                                     (int)cache_ptr->image_len, MPI_BYTE, 
                                     0, aux_ptr->mpi_comm)) )

            HMPI_GOTO_ERROR(FAIL, "can't receive cache image MPI_Bcast", \
                            mpi_result)
    } /* end else-if */
} /* end block */
#endif /* H5_HAVE_PARALLEL */

done:

    FUNC_LEAVE_NOAPI(ret_value)

} /* H5C__read_cache_image() */


/*-------------------------------------------------------------------------
 * Function:    H5C__load_cache_image
 *
 * Purpose:     Read the cache image superblock extension message and
 *		delete it if so directed.
 *
 *		Then load the cache image block at the specified location,
 *		decode it, and insert its contents into the metadata
 *		cache.
 *
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *		7/6/15
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5C__load_cache_image(H5F_t *f, hid_t dxpl_id)
{
    H5C_t *             cache_ptr;
    herr_t		ret_value = SUCCEED;    /* Return value */

    FUNC_ENTER_PACKAGE

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    cache_ptr = f->shared->cache;
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);

    /* If the image address is defined, load the image, decode it, 
     * and insert its contents into the metadata cache. 
     *
     * Note that under normal operating conditions, it is an error if the 
     * image address is HADDR_UNDEF.  However, to facilitate testing,
     * we allow this special value of the image address which means that
     * no image exists, and that the load operation should be skipped 
     * silently.  
     */
    if(H5F_addr_defined(cache_ptr->image_addr)) {
        /* Sanity checks */
	HDassert(cache_ptr->image_len > 0);
        HDassert(cache_ptr->image_buffer == NULL);

	/* Allocate space for the image */
        if(NULL == (cache_ptr->image_buffer = H5MM_malloc(cache_ptr->image_len + 1)))
            HGOTO_ERROR(H5E_CACHE, H5E_CANTALLOC, FAIL, "memory allocation failed for cache image buffer")

	/* Load the image from file */
	if(H5C__read_cache_image(f, dxpl_id, cache_ptr) < 0)
            HGOTO_ERROR(H5E_CACHE, H5E_READERROR, FAIL, "Can't read metadata cache image block")

	/* Reconstruct cache contents, from image */
	if(H5C__reconstruct_cache_contents(f, dxpl_id, cache_ptr) < 0)
            HGOTO_ERROR(H5E_CACHE, H5E_CANTDECODE, FAIL, "Can't reconstruct cache contents from image block")

	/* Free the image buffer */
        cache_ptr->image_buffer = H5MM_xfree(cache_ptr->image_buffer);

        /* Update stats -- must do this now, as we are about
         * to discard the size of the cache image.
         */
        H5C__UPDATE_STATS_FOR_CACHE_IMAGE_LOAD(cache_ptr)

        cache_ptr->image_loaded = TRUE;
    } /* end if */

    /* If directed, free the on disk metadata cache image */
    if(cache_ptr->delete_image) {
        if(H5F_super_ext_remove_msg(f, dxpl_id, H5O_MDCI_MSG_ID) < 0)
            HGOTO_ERROR(H5E_CACHE, H5E_CANTREMOVE, FAIL, "can't remove metadata cache image message from superblock extension")

        /* Reset image block values */
        cache_ptr->image_len = 0;
        cache_ptr->image_data_len = 0;
        cache_ptr->image_addr = HADDR_UNDEF;
    } /* end if */

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__load_cache_image() */


/*-------------------------------------------------------------------------
 * Function:    H5C_load_cache_image_on_next_protect()
 *
 * Purpose:     Note the fact that a metadata cache image superblock 
 *		extension message exists, along with the base address
 *		and length of the metadata cache image block.
 *
 *		Once this notification is received the metadata cache 
 *		image block must be read, decoded, and loaded into the 
 *		cache on the next call to H5C_protect().
 *
 *		Further, if the file is opened R/W, the metadata cache 
 *		image superblock extension message must be deleted from 
 *		the superblock extension and the image block freed
 *
 *		Contrawise, if the file is openened R/O, the metadata
 *		cache image superblock extension message and image block
 *		must be left as is.  Further, any dirty entries in the
 *		cache image block must be marked as clean to avoid 
 *		attempts to write them on file close.
 *
 * Return:      SUCCEED 
 *
 * Programmer:  John Mainzer
 *		7/6/15
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5C_load_cache_image_on_next_protect(H5F_t *f, haddr_t addr, hsize_t len,
    hbool_t rw)
{
    H5C_t *cache_ptr;

    FUNC_ENTER_NOAPI_NOINIT_NOERR

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    cache_ptr = f->shared->cache;
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);

    /* Set information needed to load cache image */
    cache_ptr->image_addr   = addr,
    cache_ptr->image_len    = len;
    cache_ptr->load_image   = TRUE;
    cache_ptr->delete_image = rw;

    FUNC_LEAVE_NOAPI(SUCCEED)
} /* H5C_load_cache_image_on_next_protect() */


/*-------------------------------------------------------------------------
 * Function:    H5C__image_entry_cmp
 *
 * Purpose:     Comparison callback for qsort(3) on image entries.
 *		Entries are sorted first by flush dependency height,
 *		and then by LRU rank.
 *
 * Note:        Entries with a _greater_ flush dependency height should
 *		be sorted earlier than entries with lower heights, since
 *		leafs in the flush dependency graph are at height 0, and their
 *		parents need to be earlier in the image, so that they can
 *		construct their flush dependencies when decoded.
 *
 * Return:      An integer less than, equal to, or greater than zero if the
 *		first entry is considered to be respectively less than,
 *		equal to, or greater than the second.
 *
 * Programmer:  Quincey Koziol
 *		1/20/16
 *
 *-------------------------------------------------------------------------
 */
static int
H5C__image_entry_cmp(const void *_entry1, const void *_entry2)
{
    const H5C_image_entry_t *entry1 = (const H5C_image_entry_t *)_entry1;  /* Pointer to first image entry to compare */
    const H5C_image_entry_t *entry2 = (const H5C_image_entry_t *)_entry2;  /* Pointer to second image entry to compare */
    int ret_value = 0;          /* Return value */

    FUNC_ENTER_STATIC_NOERR

    /* Sanity checks */
    HDassert(entry1);
    HDassert(entry2);

    if(entry1->image_fd_height > entry2->image_fd_height)
        ret_value = -1;
    else if(entry1->image_fd_height < entry2->image_fd_height)
        ret_value = 1;
    else {
        /* Sanity check */
        HDassert(entry1->lru_rank >= -1);
        HDassert(entry2->lru_rank >= -1);

        if(entry1->lru_rank < entry2->lru_rank)
            ret_value = -1;
        else if(entry1->lru_rank > entry2->lru_rank)
            ret_value = 1;
    } /* end else */

    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__image_entry_cmp() */


/*-------------------------------------------------------------------------
 * Function:    H5C__prep_image_for_file_close
 *
 * Purpose:     The objective of the call is to allow the metadata cache 
 *		to do any preparatory work prior to generation of a 
 *		cache image.
 *
 *		In particular, the cache must 
 *
 *		1) serialize all its entries,
 *
 *		2) compute the size of the metadata cache image, 
 *
 *		3) allocate space for the metadata cache image, and
 *
 *		4) setup the metadata cache image superblock extension
 *		   message with the address and size of the metadata 
 *		   cache image.
 *
 *		The parallel case is complicated by the fact that 
 *		while all metadata caches must contain the same set of 
 *		dirty entries, there is no such requirement for clean 
 *		entries or the order that entries appear in the LRU.
 *
 *		Thus, there is no requirement that different processes
 *		will construct cache images of the same size.
 *
 *		This is not a major issue as long as all processes include 
 *		the same set of dirty entries in the cache -- as they 
 *		currently do (note that this will change when we implement 
 *		the ageout feature).  Since only the process zero cache 
 *		writes the cache image, all that is necessary is to 
 *		broadcast the process zero cache size for use in the 
 *		superblock extension messages and cache image block 
 *		allocations.
 *
 *		Note: At present, cache image is disabled in the 
 *		parallel case as the new collective metadata write 
 *		code must be modified to support cache image.
 *
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              7/3/15
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5C__prep_image_for_file_close(H5F_t *f, hid_t dxpl_id, hbool_t *image_generated)
{
    H5C_t *     cache_ptr = NULL;
    haddr_t     eoa_frag_addr = HADDR_UNDEF;
    hsize_t     eoa_frag_size = 0;
    herr_t	ret_value = SUCCEED;      /* Return value */

    FUNC_ENTER_PACKAGE

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    HDassert(f->shared->cache);
    cache_ptr = f->shared->cache;
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(image_generated);

    /* If the file is opened and closed without any access to 
     * any group or data set, it is possible that the cache image (if 
     * it exists) has not been read yet.  Do this now if required.
     */
    if(cache_ptr->load_image) {
        cache_ptr->load_image = FALSE;
        if(H5C__load_cache_image(f, dxpl_id) < 0)
	    HGOTO_ERROR(H5E_CACHE, H5E_CANTLOAD, FAIL, "can't load cache image")
    } /* end if */

    /* Before we start to generate the cache image (if requested), verify
     * that the superblock supports superblock extension messages, and 
     * silently cancel any request for a cache image if it does not.
     *
     * Ideally, we would do this when the cache image is requested,
     * but the necessary information is not necessary available at that 
     * time -- hence this last minute check.
     *
     * Note that under some error conditions, the superblock will be 
     * undefined in this case as well -- if so, assume that the 
     * superblock does not support superblock extension messages.
     */
    if((NULL == f->shared->sblock) ||
         (f->shared->sblock->super_vers < HDF5_SUPERBLOCK_VERSION_2)) {
        H5C_cache_image_ctl_t default_image_ctl = H5C__DEFAULT_CACHE_IMAGE_CTL;

        cache_ptr->image_ctl = default_image_ctl;
        HDassert(!(cache_ptr->image_ctl.generate_image));
    } /* end if */

    /* Generate the cache image, if requested */
    if(cache_ptr->image_ctl.generate_image) {
        /* Create the cache image super block extension message.
         * 
         * Note that the base address and length of the metadata cache
         * image are undefined at this point, and thus will have to be
         * updated later.
         *
         * Create the super block extension message now so that space 
         * is allocated for it (if necessary) before we allocate space
         * for the cache image block.
         *
         * To simplify testing, do this only if the 
         * H5C_CI__GEN_MDCI_SBE_MESG bit is set in 
         * cache_ptr->image_ctl.flags.
         */
        if(cache_ptr->image_ctl.flags & H5C_CI__GEN_MDCI_SBE_MESG)
            if(H5C__write_cache_image_superblock_msg(f, dxpl_id, TRUE) < 0)
                HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "creation of cache image SB mesg failed.")

        /* Serialize the cache */
        if(H5C__serialize_cache(f, dxpl_id) < 0)
            HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "serialization of the cache failed")

        /* Scan the cache and record data needed to construct the 
         * cache image.  In particular, for each entry we must record:
         *
         * 1) rank in LRU (if entry is in LRU)
         *
         * 2) Whether the entry is dirty prior to flush of 
         *    cache just prior to close.
         *
         * 3) Addresses of flush dependency parents (if any).
         *
         * 4) Number of flush dependency children (if any).  
         *
         * In passing, also compute the size of the metadata cache 
         * image.  With the recent modifications of the free space 
         * manager code, this size should be correct.
         */
        if(H5C__prep_for_file_close__scan_entries(f, cache_ptr) < 0)
            HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "H5C__prep_for_file_close__scan_entries failed")
        HDassert(HADDR_UNDEF == cache_ptr->image_addr);

#ifdef H5_HAVE_PARALLEL
        /* In the parallel case, overwrite the image_len with the 
         * value computed by process 0.
         */
        if(cache_ptr->aux_ptr) { /* we have multiple processes */
            int mpi_result;
            unsigned p0_image_len;
            H5AC_aux_t * aux_ptr;

            aux_ptr = (H5AC_aux_t *)cache_ptr->aux_ptr;
            if(aux_ptr->mpi_rank == 0) {
                aux_ptr->p0_image_len = (unsigned)cache_ptr->image_data_len;
                p0_image_len = aux_ptr->p0_image_len;

                if(MPI_SUCCESS != (mpi_result = MPI_Bcast(&p0_image_len, 1, MPI_UNSIGNED, 0, aux_ptr->mpi_comm)))
                    HMPI_GOTO_ERROR(FAIL, "MPI_Bcast failed", mpi_result)

                HDassert(p0_image_len == aux_ptr->p0_image_len);
            } /* end if */
            else {
                if(MPI_SUCCESS != (mpi_result = MPI_Bcast(&p0_image_len, 1, MPI_UNSIGNED, 0, aux_ptr->mpi_comm)))
                    HMPI_GOTO_ERROR(FAIL, "MPI_Bcast failed", mpi_result)
                
                aux_ptr->p0_image_len = p0_image_len;
            } /* end else */

            /* Allocate space for a cache image of size equal to that 
             * computed by the process 0.  This may be different from 
             * cache_ptr->image_data_len if mpi_rank != 0.  However, since
             * cache image write is suppressed on all processes other than 
             * process 0, this doesn't matter.
             *
             * Note that we allocate the cache image directly from the file 
             * driver so as to avoid unsettling the free space managers.
             */
            if(HADDR_UNDEF == (cache_ptr->image_addr = H5FD_alloc(f->shared->lf, dxpl_id, H5FD_MEM_SUPER, f,
                        (hsize_t)p0_image_len, &eoa_frag_addr, &eoa_frag_size)))
                HGOTO_ERROR(H5E_CACHE, H5E_NOSPACE, FAIL, "can't allocate file space for metadata cache image")
        } /* end if */
        else
#endif /* H5_HAVE_PARALLEL */
            /* Allocate the cache image block.  Note that we allocate this 
             * this space directly from the file driver so as to avoid 
             * unsettling the free space managers.
             */
            if(HADDR_UNDEF == (cache_ptr->image_addr = H5FD_alloc(f->shared->lf, dxpl_id, H5FD_MEM_SUPER, f,
                        (hsize_t)(cache_ptr->image_data_len), &eoa_frag_addr, &eoa_frag_size)))
                HGOTO_ERROR(H5E_CACHE, H5E_NOSPACE, FAIL, "can't allocate file space for metadata cache image")

        /* Make note of the eoa after allocation of the cache image
         * block.  This value is used for sanity checking when we
         * shutdown the self referential free space managers after
         * we destroy the metadata cache.
         */
        HDassert(HADDR_UNDEF == f->shared->eoa_post_mdci_fsalloc);
        if(HADDR_UNDEF == (f->shared->eoa_post_mdci_fsalloc = H5FD_get_eoa(f->shared->lf, H5FD_MEM_DEFAULT)) )
            HGOTO_ERROR(H5E_FILE, H5E_CANTGET, FAIL, "unable to get file size")

        /* For now, drop any fragment left over from the allocation of the
         * image block on the ground.  A fragment should only be returned
         * if the underlying file alignment is greater than 1.
         *
         * Clean this up eventually by extending the size of the cache
         * image block to the next alignement boundary, and then setting
         * the image_data_len to the actual size of the cache_image.
         *
         * On the off chance that there is some other way to get a 
         * a fragment on a cache image allocation, leave the following
         * assertion in the code so we will find out.
         */
        HDassert((eoa_frag_size == 0) || (f->shared->alignment != 1));

        /* Eventually it will be possible for the length of the cache image
         * block on file to be greater than the size of the data it 
         * contains.  However, for now they must be the same.  Set 
         * cache_ptr->image_len accordingly.
         */
        cache_ptr->image_len = cache_ptr->image_data_len;

        /* update the metadata cache image superblock extension 
         * message with the new cache image block base address and 
         * length.
         *
         * to simplify testing, do this only if the 
         * H5C_CI__GEN_MDC_IMAGE_BLK bit is set in 
         * cache_ptr->image_ctl.flags.
         */
        if(cache_ptr->image_ctl.flags & H5C_CI__GEN_MDC_IMAGE_BLK)
            if(H5C__write_cache_image_superblock_msg(f, dxpl_id, FALSE) < 0)
                HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "update of cache image SB mesg failed")

        /* At this point:
         *
         *   1) space in the file for the metadata cache image
         *      is allocated, 
         *
         *   2) the metadata cache image superblock extension 
         *      message exists and (if so configured) contains 
         *      the correct data,
         *
         *   3) All entries in the cache that will appear in the 
         *      cache image are serialized with up to date images.
         *
         *      Since we just updated the cache image message,
         *      the super block extension message is dirty.  However,
         *      since the superblock and the superblock extension 
         *      can't be included in the cache image, this is a non-
         *      issue.
         *
         *   4) All entries in the cache that will be include in
         *      the cache are marked as such, and we have a count
         *      of same.
         *
         *   5) Flush dependency heights are calculated for all 
         *      entries that will be included in the cache image.
         *
         * If there are any entries to be included in the metadata cache
         * image, allocate, populate, and sort the image_entries array.  
         *
         * If the metadata cache image will be empty, delete the 
         * metadata cache image superblock extension message, set 
         * cache_ptr->image_ctl.generate_image to FALSE.  This will
         * allow the file close to continue normally without the 
         * unecessary generation of the metadata cache image.
         */
        if(cache_ptr->num_entries_in_image > 0) {
            if(H5C__prep_for_file_close__setup_image_entries_array(cache_ptr) < 0)
                HGOTO_ERROR(H5E_CACHE, H5E_CANTINIT, FAIL, "can't setup image entries array.")

            /* Sort the entries */
            HDqsort(cache_ptr->image_entries, (size_t)cache_ptr->num_entries_in_image,
                    sizeof(H5C_image_entry_t), H5C__image_entry_cmp);
        } /* end if */
        else { /* cancel creation of metadata cache image */
            HDassert(cache_ptr->image_entries == NULL);

            /* To avoid breaking the control flow tests, only delete 
             * the mdci superblock extension message if the 
             * H5C_CI__GEN_MDC_IMAGE_BLK flag is set in 
             * cache_ptr->image_ctl.flags.
             */
            if(cache_ptr->image_ctl.flags & H5C_CI__GEN_MDC_IMAGE_BLK)
                if(H5F_super_ext_remove_msg(f, dxpl_id, H5O_MDCI_MSG_ID) < 0)
                    HGOTO_ERROR(H5E_CACHE, H5E_CANTREMOVE, FAIL, "can't remove MDC image msg from superblock ext")

            cache_ptr->image_ctl.generate_image = FALSE;
        } /* end else */

        /* Indicate that a cache image was generated */
        *image_generated = TRUE;
    } /* end if */

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__prep_image_for_file_close() */


/*-------------------------------------------------------------------------
 * Function:    H5C_set_cache_image_config
 *
 * Purpose:	If *config_ptr contains valid data, copy it into the 
 *		image_ctl field of *cache_ptr.  Make adjustments for 
 *		changes in configuration as required.
 *
 *              If the file is open read only, silently
 *              force the cache image configuration to its default
 *              (which disables construction of a cache image).
 *
 *              Note that in addition to being inapplicable in the
 *              read only case, cache image is also inapplicable if
 *              the superblock does not support superblock extension 
 *              messages.  Unfortunately, this information need not 
 *              be available at this point. Thus we check for this 
 *              later, in H5C_prep_for_file_close() and cancel the
 *              cache image request if appropriate.
 *
 *		Fail if the new configuration is invalid.
 *
 * Return:      SUCCEED on success, and FAIL on failure.
 *
 * Programmer:  John Mainzer
 *		7/3/15
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5C_set_cache_image_config(const H5F_t *f, H5C_t *cache_ptr,
    H5C_cache_image_ctl_t *config_ptr)
{
    herr_t	ret_value = SUCCEED;      /* Return value */

    FUNC_ENTER_NOAPI(FAIL)

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    HDassert(f->shared->cache == f->shared->cache);

    /* Check arguments */
    if((cache_ptr == NULL) || (cache_ptr->magic != H5C__H5C_T_MAGIC))
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "Bad cache_ptr on entry")

    /* Validate the config: */
    if(H5C_validate_cache_image_config(config_ptr) < 0)
        HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, FAIL, "invalid cache image configuration")

#ifdef H5_HAVE_PARALLEL
    /* The collective metadata write code is not currently compatible 
     * with cache image.  Until this is fixed, suppress cache image silently
     * if there is more than one process.
     *                                         JRM -- 11/8/16
     */
    if(cache_ptr->aux_ptr) {
	H5C_cache_image_ctl_t default_image_ctl = H5C__DEFAULT_CACHE_IMAGE_CTL;

        cache_ptr->image_ctl = default_image_ctl;
	HDassert(!(cache_ptr->image_ctl.generate_image));
    } /* end if */
    else {
#endif /* H5_HAVE_PARALLEL */
        /* A cache image can only be generated if the file is opened read / write
         * and the superblock supports superblock extension messages.  
         *
         * However, the superblock version is not available at this point -- 
         * hence we can only check the former requirement now.  Do the latter
         * check just before we construct the image..  
         *
         * If the file is opened read / write, apply the supplied configuration.
         *
         * If it is not, set the image configuration to the default, which has 
         * the effect of silently disabling the cache image if it was requested.
         */
        if(H5F_INTENT(f) & H5F_ACC_RDWR)
            cache_ptr->image_ctl = *config_ptr;
        else {
            H5C_cache_image_ctl_t default_image_ctl = H5C__DEFAULT_CACHE_IMAGE_CTL;

            cache_ptr->image_ctl = default_image_ctl;
            HDassert(!(cache_ptr->image_ctl.generate_image));
        } /* end else */
#ifdef H5_HAVE_PARALLEL
    } /* end else */
#endif /* H5_HAVE_PARALLEL */

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C_set_cache_image_config() */


/*-------------------------------------------------------------------------
 * Function:    H5C_validate_cache_image_config()
 *
 * Purpose:	Run a sanity check on the provided instance of struct 
 *		H5AC_cache_image_config_t.
 *
 *		Do nothing and return SUCCEED if no errors are detected,
 *		and flag an error and return FAIL otherwise.
 *
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              6/15/15
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5C_validate_cache_image_config(H5C_cache_image_ctl_t * ctl_ptr)
{
    herr_t              ret_value = SUCCEED;    /* Return value */

    FUNC_ENTER_NOAPI(FAIL)

    if(ctl_ptr == NULL)
        HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "NULL ctl_ptr on entry")
    if(ctl_ptr->version != H5C__CURR_CACHE_IMAGE_CTL_VER)
        HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "Unknown cache image control version")

    /* At present, we do not support inclusion of the adaptive resize
     * configuration in the cache image.  Thus the save_resize_status
     * field must be FALSE.
     */
    if(ctl_ptr->save_resize_status != FALSE)
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "unexpected value in save_resize_status field")

    /* At present, we do not support prefetched entry ageouts.  Thus 
     * the entry_ageout field must be set to 
     * H5AC__CACHE_IMAGE__ENTRY_AGEOUT__NONE.
     */
    if(ctl_ptr->entry_ageout != H5AC__CACHE_IMAGE__ENTRY_AGEOUT__NONE)
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "unexpected value in entry_ageout field")

    if((ctl_ptr->flags & ~H5C_CI__ALL_FLAGS) != 0)
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "unknown flag set")

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C_validate_cache_image_config() */


/*************************************************************************/
/**************************** Private Functions: *************************/
/*************************************************************************/

/*-------------------------------------------------------------------------
 * Function:    H5C__cache_image_block_entry_header_size
 *
 * Purpose:     Compute the size of the header of the metadata cache
 *		image block, and return the value.
 *
 * Return:      Size of the header section of the metadata cache image 
 *		block in bytes.
 *
 * Programmer:  John Mainzer
 *		7/27/15
 *
 *-------------------------------------------------------------------------
 */
static size_t
H5C__cache_image_block_entry_header_size(const H5F_t * f)
{
    size_t ret_value = 0;       /* Return value */

    FUNC_ENTER_STATIC_NOERR

    /* Set return value */
    ret_value = (size_t)( 1 +			/* type                     */
			  1 +			/* flags                    */
			  1 +			/* ring                     */
			  1 +			/* age                      */
			  2 +			/* dependency child count   */
			  2 +			/* dirty dep child count    */
			  2 +			/* dependency parent count  */
			  4 +			/* index in LRU             */
			  H5F_SIZEOF_ADDR(f) +  /* entry offset             */
			  H5F_SIZEOF_SIZE(f) ); /* entry length             */

    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__cache_image_block_entry_header_size() */


/*-------------------------------------------------------------------------
 * Function:    H5C__cache_image_block_header_size
 *
 * Purpose:     Compute the size of the header of the metadata cache
 *		image block, and return the value.
 *
 * Return:      Size of the header section of the metadata cache image 
 *		block in bytes.
 *
 * Programmer:  John Mainzer
 *		7/27/15
 *
 *-------------------------------------------------------------------------
 */
static size_t
H5C__cache_image_block_header_size(const H5F_t * f)
{
    size_t ret_value = 0;       /* Return value */

    FUNC_ENTER_STATIC_NOERR

    /* Set return value */
    ret_value = (size_t)( 4 +                   /* signature           */
			  1 +			/* version             */
			  1 +			/* flags               */
			  H5F_SIZEOF_SIZE(f) +	/* image data length   */
			  4 );			/* num_entries         */

    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__cache_image_block_header_size() */


/*-------------------------------------------------------------------------
 * Function:    H5C__decode_cache_image_header()
 *
 * Purpose:     Decode the metadata cache image buffer header from the 
 *		supplied buffer and load the data into the supplied instance
 *		of H5C_t.  Advances the buffer pointer to the first byte 
 *		after the header image, or unchanged on failure.
 *
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              8/6/15
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__decode_cache_image_header(const H5F_t *f, H5C_t *cache_ptr,
    const uint8_t **buf)
{
    uint8_t		version;
    uint8_t		flags;
    hbool_t		have_resize_status = FALSE;
    size_t 		actual_header_len;
    size_t		expected_header_len;
    const uint8_t *	p;
    herr_t		ret_value = SUCCEED;      /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(buf);
    HDassert(*buf);

    /* Point to buffer to decode */
    p = *buf;

    /* Check signature */
    if(HDmemcmp(p, H5C__MDCI_BLOCK_SIGNATURE, (size_t)H5C__MDCI_BLOCK_SIGNATURE_LEN))
	HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "Bad metadata cache image header signature")
    p += H5C__MDCI_BLOCK_SIGNATURE_LEN;

    /* Check version */
    version = *p++;
    if(version != (uint8_t)H5C__MDCI_BLOCK_VERSION_0)
	HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "Bad metadata cache image version")

    /* Decode flags */
    flags = *p++;
    if(flags & H5C__MDCI_HEADER_HAVE_RESIZE_STATUS)	
	have_resize_status = TRUE;
    if(have_resize_status)
	HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "MDC resize status not yet supported")

    /* Read image data length */
    H5F_DECODE_LENGTH(f, p, cache_ptr->image_data_len);

    /* For now -- will become <= eventually */
    if(cache_ptr->image_data_len != cache_ptr->image_len)
	HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "Bad metadata cache image data length")

    /* Read num entries */
    UINT32DECODE(p, cache_ptr->num_entries_in_image);
    if(cache_ptr->num_entries_in_image == 0) 
	HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "Bad metadata cache entry count")

    /* Verify expected length of header */
    actual_header_len = (size_t)(p - *buf);
    expected_header_len = H5C__cache_image_block_header_size(f);
    if(actual_header_len != expected_header_len)
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "Bad header image len")

    /* Update buffer pointer */
    *buf = p;

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__decode_cache_image_header() */

#ifndef NDEBUG

/*-------------------------------------------------------------------------
 * Function:    H5C__decode_cache_image_entry()
 *
 * Purpose:     Decode the metadata cache image entry from the supplied 
 *		buffer into the supplied instance of H5C_image_entry_t.
 *		This includes allocating a buffer for the entry image,
 *		loading it, and seting ie_ptr->image_ptr to point to 
 *		the buffer.
 *
 *		Advances the buffer pointer to the first byte 
 *		after the entry, or unchanged on failure.
 *
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              8/6/15
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__decode_cache_image_entry(const H5F_t *f, const H5C_t *cache_ptr,
    const uint8_t **buf, unsigned entry_num)
{
    hbool_t		is_dirty = FALSE;
    hbool_t		in_lru = FALSE;         /* Only used in assertions */
    hbool_t		is_fd_parent = FALSE;   /* Only used in assertions */
    hbool_t		is_fd_child = FALSE;    /* Only used in assertions */
    haddr_t 		addr;
    hsize_t		size = 0;
    void *		image_ptr;
    uint8_t             flags = 0;
    uint8_t		type_id;
    uint8_t		ring;
    uint8_t		age;
    uint16_t 		fd_child_count;
    uint16_t 		fd_dirty_child_count;
    uint16_t 		fd_parent_count;
    haddr_t           * fd_parent_addrs = NULL;
    int32_t		lru_rank;
    H5C_image_entry_t * ie_ptr = NULL;
    const uint8_t *	p;
    herr_t		ret_value = SUCCEED;      /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    HDassert(cache_ptr == f->shared->cache);
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(buf);
    HDassert(*buf);
    HDassert(entry_num < cache_ptr->num_entries_in_image);
    ie_ptr = &((cache_ptr->image_entries)[entry_num]);
    HDassert(ie_ptr);
    HDassert(ie_ptr->magic == H5C_IMAGE_ENTRY_T_MAGIC);

    /* Get pointer to buffer */
    p = *buf;

    /* Decode type id */
    type_id = *p++;

    /* Decode flags */
    flags = *p++;
    if(flags & H5C__MDCI_ENTRY_DIRTY_FLAG)
        is_dirty = TRUE;
    if(flags & H5C__MDCI_ENTRY_IN_LRU_FLAG)
        in_lru = TRUE;
    if(flags & H5C__MDCI_ENTRY_IS_FD_PARENT_FLAG)
        is_fd_parent = TRUE;
    if(flags & H5C__MDCI_ENTRY_IS_FD_CHILD_FLAG)
        is_fd_child = TRUE;

    /* Decode ring */
    ring = *p++;
    HDassert(ring > (uint8_t)(H5C_RING_UNDEFINED));
    HDassert(ring < (uint8_t)(H5C_RING_NTYPES));

    /* Decode age */
    age = *p++;

    /* Decode dependency child count */
    UINT16DECODE(p, fd_child_count);
    HDassert((is_fd_parent && fd_child_count > 0) || (!is_fd_parent && fd_child_count == 0));

    /* Decode dirty dependency child count */
    UINT16DECODE(p, fd_dirty_child_count);
    if(fd_dirty_child_count > fd_child_count)
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "invalid dirty flush dependency child count")

    /* Decode dependency parent count */
    UINT16DECODE(p, fd_parent_count);
    HDassert((is_fd_child && fd_parent_count > 0) || (!is_fd_child && fd_parent_count == 0));

    /* Decode index in LRU */
    INT32DECODE(p, lru_rank);
    HDassert((in_lru && lru_rank >= 0) || (!in_lru && lru_rank == -1));

    /* Decode entry offset */
    H5F_addr_decode(f, &p, &addr);
    if(!H5F_addr_defined(addr))
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "invalid entry offset")

    /* Decode entry length */
    H5F_DECODE_LENGTH(f, p, size);
    if(size == 0)
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "invalid entry size")

    /* Verify expected length of entry image */
    if((size_t)(p - *buf) != H5C__cache_image_block_entry_header_size(f))
        HGOTO_ERROR(H5E_CACHE, H5E_BADSIZE, FAIL, "Bad entry image len")
    
    /* If parent count greater than zero, allocate array for parent 
     * addresses, and decode addresses into the array.
     */
    if(fd_parent_count > 0) {
        int i;          /* Local index variable */

        if(NULL == (fd_parent_addrs = (haddr_t *)H5MM_malloc((size_t)(fd_parent_count) * H5F_SIZEOF_ADDR(f))))
	    HGOTO_ERROR(H5E_CACHE, H5E_CANTALLOC, FAIL, "memory allocation failed for fd parent addrs buffer")

	for(i = 0; i < fd_parent_count; i++) {
            H5F_addr_decode(f, &p, &(fd_parent_addrs[i]));
            if(!H5F_addr_defined(fd_parent_addrs[i]))
                HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "invalid flush dependency parent offset")
        } /* end for */
    } /* end if */

    /* Allocate buffer for entry image */
    if(NULL == (image_ptr = H5MM_malloc(size + H5C_IMAGE_EXTRA_SPACE)))
	HGOTO_ERROR(H5E_CACHE, H5E_CANTALLOC, FAIL, "memory allocation failed for on disk image buffer")

#if H5C_DO_MEMORY_SANITY_CHECKS
    HDmemcpy(((uint8_t *)image_ptr) + size, H5C_IMAGE_SANITY_VALUE, H5C_IMAGE_EXTRA_SPACE);
#endif /* H5C_DO_MEMORY_SANITY_CHECKS */

    /* Copy the entry image from the cache image block */
    HDmemcpy(image_ptr, p, size);
    p += size;

    /* Copy data into target */
    ie_ptr->addr                 = addr;
    ie_ptr->size                 = size;
    ie_ptr->ring                 = (H5C_ring_t)ring;
    ie_ptr->age                  = (int32_t)age;
    ie_ptr->type_id              = (int32_t)type_id;
    ie_ptr->lru_rank             = lru_rank;
    ie_ptr->is_dirty             = is_dirty;
    ie_ptr->fd_child_count       = (uint64_t)fd_child_count;
    ie_ptr->fd_dirty_child_count = (uint64_t)fd_dirty_child_count;
    ie_ptr->fd_parent_count      = (uint64_t)fd_parent_count;
    ie_ptr->fd_parent_addrs      = fd_parent_addrs;
    ie_ptr->image_ptr            = image_ptr;

    /* Update buffer pointer */
    *buf = p;

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__decode_cache_image_entry() */
#endif /* NDEBUG */


/*-------------------------------------------------------------------------
 * Function:    H5C__destroy_pf_entry_child_flush_deps()
 *
 * Purpose:     Destroy all flush dependencies in this the supplied 
 *		prefetched entry is the parent.  Note that the children
 *		in these flush dependencies must be prefetched entries as 
 *		well.
 *
 *		As this action is part of the process of transferring all
 *		such flush dependencies to the deserialized version of the
 *		prefetched entry, ensure that the data necessary to complete
 *		the transfer is retained.
 *
 *		Note: The current implementation of this function is 
 *		      quite inefficient -- mostly due to the current 
 *		      implementation of flush dependencies.  This should
 *		      be fixed at some point.
 *
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              8/11/15
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__destroy_pf_entry_child_flush_deps(H5C_t *cache_ptr, 
    H5C_cache_entry_t *pf_entry_ptr, H5C_cache_entry_t **fd_children)
{
    H5C_cache_entry_t * entry_ptr;
    unsigned		entries_visited = 0;
    int			fd_children_found = 0;
    hbool_t		found;
    herr_t		ret_value = SUCCEED;    /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(pf_entry_ptr);
    HDassert(pf_entry_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
    HDassert(pf_entry_ptr->type);
    HDassert(pf_entry_ptr->type->id == H5AC_PREFETCHED_ENTRY_ID);
    HDassert(pf_entry_ptr->prefetched);
    HDassert(pf_entry_ptr->fd_child_count > 0);
    HDassert(fd_children);

    /* Scan each entry on the index list */
    entry_ptr = cache_ptr->il_head;
    while(entry_ptr != NULL) {
	HDassert(entry_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);

	/* Here we look at entry_ptr->flush_dep_nparents and not 
         * entry_ptr->fd_parent_count as it is possible that some 
	 * or all of the prefetched flush dependency child relationships
	 * have already been destroyed.
         */
	if(entry_ptr->prefetched && (entry_ptr->flush_dep_nparents > 0)) {
            unsigned u;         /* Local index variable */

            /* Re-init */
	    u = 0;
	    found = FALSE;

            /* Sanity checks */
            HDassert(entry_ptr->type);
            HDassert(entry_ptr->type->id == H5AC_PREFETCHED_ENTRY_ID);
	    HDassert(entry_ptr->fd_parent_count >= entry_ptr->flush_dep_nparents);
	    HDassert(entry_ptr->fd_parent_addrs);
	    HDassert(entry_ptr->flush_dep_parent);

            /* Look for correct entry */
	    while(!found && (u < entry_ptr->fd_parent_count)) {
                /* Sanity check entry */
		HDassert(entry_ptr->flush_dep_parent[u]);
		HDassert(entry_ptr->flush_dep_parent[u]->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);

                /* Correct entry? */
		if(pf_entry_ptr == entry_ptr->flush_dep_parent[u])
		    found = TRUE;

		u++;
            } /* end while */

	    if(found) {
	        HDassert(NULL == fd_children[fd_children_found]);

                /* Remove flush dependency */
	        fd_children[fd_children_found] = entry_ptr;
	        fd_children_found++;
                if(H5C_destroy_flush_dependency(pf_entry_ptr, entry_ptr) < 0)
	            HGOTO_ERROR(H5E_CACHE, H5E_CANTUNDEPEND, FAIL, "can't destroy pf entry child flush dependency")

#ifndef NDEBUG
		/* Sanity check -- verify that the address of the parent 
                 * appears in entry_ptr->fd_parent_addrs.  Must do a search,
                 * as with flush dependency creates and destroys, 
                 * entry_ptr->fd_parent_addrs and entry_ptr->flush_dep_parent
                 * can list parents in different order.
                 */
		found = FALSE;
		u = 0;
	        while(!found && u < entry_ptr->fd_parent_count) {
		    if(pf_entry_ptr->addr == entry_ptr->fd_parent_addrs[u])
		        found = TRUE;
		    u++;
                } /* end while */
		HDassert(found);
#endif /* NDEBUG */
	    } /* end if */
	} /* end if */

        entries_visited++;
        entry_ptr = entry_ptr->il_next;
    } /* end while */

    /* Post-op sanity checks */
    HDassert(NULL == fd_children[fd_children_found]);
    HDassert((unsigned)fd_children_found == pf_entry_ptr->fd_child_count);
    HDassert(entries_visited == cache_ptr->index_len);
    HDassert(!pf_entry_ptr->is_pinned);

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__destroy_pf_entry_child_flush_deps() */


/*-------------------------------------------------------------------------
 * Function:    H5C__encode_cache_image_header()
 *
 * Purpose:     Encode the metadata cache image buffer header in the 
 *		supplied buffer.  Updates buffer pointer to the first byte 
 *		after the header image in the buffer, or unchanged on failure.
 *
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              8/6/15
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__encode_cache_image_header(const H5F_t *f, const H5C_t *cache_ptr,
    uint8_t **buf)
{
    size_t 	actual_header_len;
    size_t	expected_header_len;
    uint8_t     flags = 0;
    uint8_t *	p;                      /* Pointer into cache image buffer */
    herr_t	ret_value = SUCCEED;    /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(cache_ptr->close_warning_received);
    HDassert(cache_ptr->image_ctl.generate_image);
    HDassert(cache_ptr->index_len == 0);
    HDassert(cache_ptr->image_data_len > 0);
    HDassert(cache_ptr->image_data_len <= cache_ptr->image_len);
    HDassert(buf);
    HDassert(*buf);

    /* Set pointer into buffer */
    p = *buf;

    /* write signature */
    HDmemcpy(p, H5C__MDCI_BLOCK_SIGNATURE, (size_t)H5C__MDCI_BLOCK_SIGNATURE_LEN);
    p += H5C__MDCI_BLOCK_SIGNATURE_LEN;

    /* write version */
    *p++ = (uint8_t)H5C__MDCI_BLOCK_VERSION_0;

    /* setup and write flags */

    /* at present we don't support saving resize status */
    HDassert(!cache_ptr->image_ctl.save_resize_status);
    if(cache_ptr->image_ctl.save_resize_status)
	flags |= H5C__MDCI_HEADER_HAVE_RESIZE_STATUS;

    *p++ = flags;

    /* Encode image data length */
    /* this must be true at present */
    HDassert(cache_ptr->image_len == cache_ptr->image_data_len);
    H5F_ENCODE_LENGTH(f, p, cache_ptr->image_data_len);

    /* write num entries */
    UINT32ENCODE(p, cache_ptr->num_entries_in_image);

    /* verify expected length of header */
    actual_header_len = (size_t)(p - *buf);
    expected_header_len = H5C__cache_image_block_header_size(f);
    if(actual_header_len != expected_header_len)
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "Bad header image len")

    /* Update buffer pointer */
    *buf = p;

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__encode_cache_image_header() */


/*-------------------------------------------------------------------------
 * Function:    H5C__encode_cache_image_entry()
 *
 * Purpose:     Encode the metadata cache image buffer header in the 
 *		supplied buffer.  Updates buffer pointer to the first byte 
 *		after the entry in the buffer, or unchanged on failure.
 *
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              8/6/15
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__encode_cache_image_entry(H5F_t *f, H5C_t *cache_ptr, uint8_t **buf, 
    unsigned entry_num)
{
    H5C_image_entry_t *	ie_ptr;                 /* Pointer to entry to encode */
    uint8_t             flags = 0;              /* Flags for entry */
    uint8_t           *	p;                      /* Pointer into cache image buffer */
    unsigned            u;                      /* Local index value */
    herr_t		ret_value = SUCCEED;    /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    HDassert(cache_ptr == f->shared->cache);
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(cache_ptr->close_warning_received);
    HDassert(cache_ptr->image_ctl.generate_image);
    HDassert(cache_ptr->index_len == 0);
    HDassert(buf);
    HDassert(*buf);
    HDassert(entry_num < cache_ptr->num_entries_in_image);
    ie_ptr = &((cache_ptr->image_entries)[entry_num]);
    HDassert(ie_ptr->magic == H5C_IMAGE_ENTRY_T_MAGIC);

    /* Get pointer to buffer to encode into */
    p = *buf;

    /* Encode type */
    if((ie_ptr->type_id < 0) || (ie_ptr->type_id > 255))
        HGOTO_ERROR(H5E_CACHE, H5E_BADRANGE, FAIL, "type_id out of range.")
    *p++ = (uint8_t)(ie_ptr->type_id);

    /* Compose and encode flags */
    if(ie_ptr->is_dirty) 
	flags |= H5C__MDCI_ENTRY_DIRTY_FLAG;
    if(ie_ptr->lru_rank > 0) 
	flags |= H5C__MDCI_ENTRY_IN_LRU_FLAG;
    if(ie_ptr->fd_child_count > 0)
	flags |= H5C__MDCI_ENTRY_IS_FD_PARENT_FLAG;
    if(ie_ptr->fd_parent_count > 0) 
	flags |= H5C__MDCI_ENTRY_IS_FD_CHILD_FLAG;
    *p++ = flags;

    /* Encode ring */
    *p++ = (uint8_t)(ie_ptr->ring);

    /* Encode age */
    *p++ = (uint8_t)(ie_ptr->age);

    /* Validate and encode dependency child count */
    if(ie_ptr->fd_child_count > H5C__MDCI_MAX_FD_CHILDREN)
        HGOTO_ERROR(H5E_CACHE, H5E_BADRANGE, FAIL, "fd_child_count out of range")
    UINT16ENCODE(p, (uint16_t)(ie_ptr->fd_child_count));

    /* Validate and encode dirty dependency child count */
    if(ie_ptr->fd_dirty_child_count > H5C__MDCI_MAX_FD_CHILDREN)
        HGOTO_ERROR(H5E_CACHE, H5E_BADRANGE, FAIL, "fd_dirty_child_count out of range")
    UINT16ENCODE(p, (uint16_t)(ie_ptr->fd_dirty_child_count));

    /* Validate and encode dependency parent count */
    if(ie_ptr->fd_parent_count > H5C__MDCI_MAX_FD_PARENTS)
        HGOTO_ERROR(H5E_CACHE, H5E_BADRANGE, FAIL, "fd_parent_count out of range")
    UINT16ENCODE(p, (uint16_t)(ie_ptr->fd_parent_count));

    /* Encode index in LRU */
    INT32ENCODE(p, ie_ptr->lru_rank);

    /* Encode entry offset */
    H5F_addr_encode(f, &p, ie_ptr->addr);

    /* Encode entry length */
    H5F_ENCODE_LENGTH(f, p, ie_ptr->size);

    /* Verify expected length of entry image */
    if((size_t)(p - *buf) != H5C__cache_image_block_entry_header_size(f))
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, FAIL, "Bad entry image len")

    /* Encode dependency parent offsets -- if any */
    for(u = 0; u < ie_ptr->fd_parent_count; u++)
	H5F_addr_encode(f, &p, ie_ptr->fd_parent_addrs[u]);

    /* Copy entry image */
    HDmemcpy(p, ie_ptr->image_ptr, ie_ptr->size);
    p += ie_ptr->size;

    /* Update buffer pointer */
    *buf = p;

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__encode_cache_image_entry() */


/*-------------------------------------------------------------------------
 * Function:    H5C__prep_for_file_close__compute_fd_heights
 *
 * Purpose:     Recent modifications to flush dependency support in the
 *		metadata cache have removed the notion of flush dependency
 *		height.  This is a problem for the cache image feature,
 *		as flush dependency height is used to order entries in the
 *		cache image so that flush dependency parents appear before
 *		flush dependency children. (Recall that the flush dependency
 *		height of an entry in a flush dependency relationship is the
 *		length of the longest path from the entry to a leaf entry --
 *		that is an entry with flush dependency parents, but no 
 *		flush dependency children.  With the introduction of the 
 *		possibility of multiple flush dependency parents, we have
 *		a flush partial dependency latice, not a flush dependency 
 *		tree.  But since the partial latice is acyclic, the concept 
 *		of flush dependency height still makes sense.
 *
 *		The purpose of this function is to compute the flush 
 *		dependency height of all entries that appear in the cache
 *		image.  
 *
 *		At present, entries are included or excluded from the 
 *		cache image depending upon the ring in which they reside.
 *		Thus there is no chance that one side of a flush dependency
 *		will be in the cache image, and the other side not.
 *
 *		However, once we start placing a limit on the size of the
 *		cache image, or start excluding prefetched entries from
 *		the cache image if they haven't been accessed in some 
 *		number of file close / open cycles, this will no longer 
 *		be the case.  
 *
 *		In particular, if a flush dependency child is dirty, and
 *		one of its flush dependency parents is dirty and not in
 *		the cache image, then the flush dependency child cannot
 *		be in the cache image without violating flush ordering.
 *
 *		Observe that a clean flush dependency child can be either 
 *		in or out of the cache image without effect on flush 
 *		dependencies.
 *
 *		Similarly, a flush dependency parent can always be part 
 *		of a cache image, regardless of whether it is clean or 
 *		dirty -- but remember that a flush dependency parent can
 *		also be a flush dependency child.
 *		
 *		Finally, note that for purposes of the cache image, flush
 *		dependency height ends when a flush dependecy relation 
 *		passes off the cache image.
 *
 *		On exit, the flush dependency height of each entry in the 
 *		cache image should be calculated and stored in the cache
 *		entry.  Entries will be removed from the cache image if
 *		necessary to maintain flush ordering.
 *		
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              9/6/16
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__prep_for_file_close__compute_fd_heights(const H5C_t *cache_ptr)
{
    H5C_cache_entry_t * entry_ptr;
    H5C_cache_entry_t * parent_ptr;
    unsigned		entries_removed_from_image = 0;
    unsigned		external_parent_fd_refs_removed = 0;
    unsigned		external_child_fd_refs_removed = 0;
    hbool_t 		done = FALSE;
    unsigned		u;              /* Local index variable */
    herr_t              ret_value = SUCCEED;

    FUNC_ENTER_STATIC

    /* sanity checks */
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);

    /* Remove from the cache image all dirty entries that are 
     * flush dependency children of dirty entries that are not in the
     * cache image.  Must do this, as if we fail to do so, the parent 
     * will be written to file before the child.  Since it is possible 
     * that the child will have dirty children of its own, this may take
     * multiple passes through the index list.
     */
    done = FALSE;
    while(!done) {
	done = TRUE;
	entry_ptr = cache_ptr->il_head;
        while(entry_ptr != NULL) {
	    HDassert(entry_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);

            /* Should this entry be in the image */
	    if(entry_ptr->image_dirty && entry_ptr->include_in_image &&
                    (entry_ptr->fd_parent_count > 0)) {
		HDassert(entry_ptr->flush_dep_parent != NULL);
		for(u = 0; u < entry_ptr->flush_dep_nparents; u++ ) {
		    parent_ptr = entry_ptr->flush_dep_parent[u];

                    /* Sanity check parent */
		    HDassert(parent_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
		    HDassert(entry_ptr->ring == parent_ptr->ring);

		    if(parent_ptr->is_dirty && !parent_ptr->include_in_image &&
                             entry_ptr->include_in_image) {

			/* Must remove child from image -- only do this once */
			entries_removed_from_image++;
			entry_ptr->include_in_image = FALSE;
		    } /* end if */
		} /* for */
            } /* end if */

	    entry_ptr = entry_ptr->il_next;
        } /* while ( entry_ptr != NULL ) */
    } /* while ( ! done ) */ 

    /* at present, entries are included in the cache image if they reside
     * in a specified set of rings.  Thus it should be impossible for 
     * entries_removed_from_image to be positive.  Assert that this is 
     * so.  Note that this will change when we start aging entries out 
     * of the cache image.
     */
    HDassert(entries_removed_from_image == 0);

    /* Next, remove from entries in the cache image, references to 
     * flush dependency parents or children that are not in the cache image.
     */
    entry_ptr = cache_ptr->il_head;
    while(entry_ptr != NULL) {
	if(!entry_ptr->include_in_image && entry_ptr->flush_dep_nparents > 0) {
	    HDassert(entry_ptr->flush_dep_parent != NULL);

	    for(u = 0; u < entry_ptr->flush_dep_nparents; u++ ) {
	        parent_ptr = entry_ptr->flush_dep_parent[u];

                /* Sanity check parent */
		HDassert(parent_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
		HDassert(entry_ptr->ring == parent_ptr->ring);

		if(parent_ptr->include_in_image) {
		    /* Must remove reference to child */
		    HDassert(parent_ptr->fd_child_count > 0);
		    parent_ptr->fd_child_count--;

		    if(entry_ptr->is_dirty) {
			HDassert(parent_ptr->fd_dirty_child_count > 0);
			parent_ptr->fd_dirty_child_count--;
		    } /* end if */

		    external_child_fd_refs_removed++;
		} /* end if */
	    } /* for */
	} /* end if */
        else if(entry_ptr->include_in_image && entry_ptr->flush_dep_nparents > 0) {
            /* Sanity checks */
	    HDassert(entry_ptr->flush_dep_parent != NULL);
	    HDassert(entry_ptr->flush_dep_nparents == entry_ptr->fd_parent_count);
	    HDassert(entry_ptr->fd_parent_addrs);

	    for(u = 0; u < entry_ptr->flush_dep_nparents; u++ ) {
	        parent_ptr = entry_ptr->flush_dep_parent[u];

                /* Sanity check parent */
		HDassert(parent_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
		HDassert(entry_ptr->ring == parent_ptr->ring);

		if(!parent_ptr->include_in_image) {
		    /* Must remove reference to parent */
		    HDassert(entry_ptr->fd_parent_count > 0);
		    parent_ptr->fd_child_count--;

		    HDassert(parent_ptr->addr == entry_ptr->fd_parent_addrs[u]);

		    entry_ptr->fd_parent_addrs[u] = HADDR_UNDEF;
		    external_parent_fd_refs_removed++;
		} /* end if */
	    } /* for */

	    /* Touch up fd_parent_addrs array if necessary */
	    if(entry_ptr->fd_parent_count == 0) {
		H5MM_xfree(entry_ptr->fd_parent_addrs);
		entry_ptr->fd_parent_addrs = NULL;
	    } /* end if */
            else if(entry_ptr->flush_dep_nparents > entry_ptr->fd_parent_count) {
		haddr_t * old_fd_parent_addrs = entry_ptr->fd_parent_addrs;
                unsigned v;

		if(NULL == (entry_ptr->fd_parent_addrs = (haddr_t *)H5MM_calloc(sizeof(haddr_t) * (size_t)(entry_ptr->fd_parent_addrs))))
		    HGOTO_ERROR(H5E_CACHE, H5E_CANTALLOC, FAIL, "memory allocation failed for fd parent addr array")

		v = 0;
	        for(u = 0; u < entry_ptr->flush_dep_nparents; u++) {
		    if(old_fd_parent_addrs[u] != HADDR_UNDEF) {
			entry_ptr->fd_parent_addrs[v] = old_fd_parent_addrs[u];
			v++;
		    } /* end if */
		} /* end for */

		HDassert(v == entry_ptr->fd_parent_count);
	    } /* end else-if */
	} /* end else-if */

        entry_ptr = entry_ptr->il_next;
    } /* while (entry_ptr != NULL) */

    /* At present, no extenal parent or child flush dependency links 
     * should exist -- hence the following assertions.  This will change
     * if we support ageout of entries in the cache image.
     */
    HDassert(external_child_fd_refs_removed == 0);
    HDassert(external_parent_fd_refs_removed == 0);

    /* At this point we should have removed all flush dependencies that 
     * cross cache image boundaries.  Now compute the flush dependency
     * heights for all entries in the image.
     *
     * Until I can think of a better way, do this via a depth first
     * search implemented via a recursive function call.
     *
     * Note that entry_ptr->image_fd_height has already been initialized to 0
     * for all entries that may appear in the cache image.
     */
    entry_ptr = cache_ptr->il_head;
    while(entry_ptr != NULL) {
	if(entry_ptr->include_in_image && entry_ptr->fd_child_count == 0 &&
                entry_ptr->fd_parent_count > 0) {
	    for(u = 0; u < entry_ptr->fd_parent_count; u++) {
	        parent_ptr = entry_ptr->flush_dep_parent[u];

	        HDassert(parent_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
	        if(parent_ptr->include_in_image && parent_ptr->image_fd_height <= 0) 
		    H5C__prep_for_file_close__compute_fd_heights_real(parent_ptr, 1);
	    } /* end for */
        } /* end if */

        entry_ptr = entry_ptr->il_next;
    } /* while (entry_ptr != NULL) */

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__prep_for_file_close__compute_fd_heights() */


/*-------------------------------------------------------------------------
 * Function:    H5C__prep_for_file_close__compute_fd_heights_real
 *
 * Purpose:     H5C__prep_for_file_close__compute_fd_heights() prepares
 *		for the computation of flush dependency heights of all
 *		entries in the cache image, this function actually does
 *		it.
 *
 *		The basic observation behind this function is as follows:
 *
 *		Suppose you have an entry E with a flush dependency 
 *		height of X.  Then the parents of E must all have 
 *		flush dependency X + 1 or greater.
 *
 *		Use this observation to compute flush dependency height
 *		of all entries in the cache image via the following
 *		recursive algorithm:
 *
 *		1) On entry, set the flush dependency height of the 
 *		   supplied cache entry to the supplied value.
 *
 *		2) Examine all the flush dependency parents of the 
 *		   supplied entry.  
 *
 *		   If the parent is in the cache image, and has flush 
 *		   dependency height less than or equal to the flush
 *		   dependency height of the current entry, call the 
 *		   recursive routine on the parent with flush dependency
 *		   height equal to the flush dependency height of the 
 *		   child plus 1.
 *
 *		   Otherwise do nothing.
 *
 *		Observe that if the flush dependency height of all entries
 *		in the image is initialized to zero, and if this recursive 
 *		function is called with flush dependency height 0 on all 
 *		entries in the cache image with FD parents in the image, 
 *		but without FD children in the image, the correct flush 
 *		dependency height should be set for all entries in the 
 *		cache image.
 *		
 * Return:      void
 *
 * Programmer:  John Mainzer
 *              9/6/16
 *
 *-------------------------------------------------------------------------
 */
static void
H5C__prep_for_file_close__compute_fd_heights_real(H5C_cache_entry_t  *entry_ptr,
    uint32_t fd_height)
{
    FUNC_ENTER_STATIC_NOERR

    /* Sanity checks */
    HDassert(entry_ptr);
    HDassert(entry_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
    HDassert(entry_ptr->include_in_image);
    HDassert((entry_ptr->image_fd_height == 0) || (entry_ptr->image_fd_height < fd_height));
    HDassert(((fd_height == 0) && (entry_ptr->fd_child_count == 0)) || ((fd_height > 0) && (entry_ptr->fd_child_count > 0)));

    entry_ptr->image_fd_height = fd_height;
    if(entry_ptr->flush_dep_nparents > 0) {
        unsigned u;

	HDassert(entry_ptr->flush_dep_parent);
	for(u = 0; u < entry_ptr->fd_parent_count; u++) {
            H5C_cache_entry_t *parent_ptr;

	    parent_ptr = entry_ptr->flush_dep_parent[u];
	    HDassert(parent_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);

	    if(parent_ptr->include_in_image && parent_ptr->image_fd_height <= fd_height)
		H5C__prep_for_file_close__compute_fd_heights_real(parent_ptr, fd_height + 1);
        } /* end for */
    } /* end if */

    FUNC_LEAVE_NOAPI_VOID
} /* H5C__prep_for_file_close__compute_fd_heights_real() */


/*-------------------------------------------------------------------------
 * Function:    H5C__prep_for_file_close__setup_image_entries_array
 *
 * Purpose:     Allocate space for the image_entries array, and load
 *		each instance of H5C_image_entry_t in the array with 
 *		the data necessary to construct the metadata cache image.
 *		
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              8/4/15
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__prep_for_file_close__setup_image_entries_array(H5C_t *cache_ptr)
{
    H5C_cache_entry_t * entry_ptr;
    H5C_image_entry_t * image_entries = NULL;
    uint32_t		entries_visited = 0;
    unsigned            u;                      /* Local index variable */
    herr_t		ret_value = SUCCEED;    /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(cache_ptr->close_warning_received);
    HDassert(cache_ptr->pl_len == 0);
    HDassert(cache_ptr->num_entries_in_image > 0);
    HDassert(cache_ptr->image_entries == NULL);

    /* Allocate and initialize image_entries array */
    if(NULL == (image_entries = (H5C_image_entry_t *)H5MM_calloc(sizeof(H5C_image_entry_t) * (size_t)(cache_ptr->num_entries_in_image + 1))))
        HGOTO_ERROR(H5E_CACHE, H5E_CANTALLOC, FAIL, "memory allocation failed for image_entries")

    /* Initialize (non-zero/NULL/FALSE) fields */
    for(u = 0; u <= cache_ptr->num_entries_in_image; u++) {
	image_entries[u].magic                = H5C_IMAGE_ENTRY_T_MAGIC;
	image_entries[u].addr                 = HADDR_UNDEF;
        image_entries[u].ring		      = H5C_RING_UNDEFINED;
	image_entries[u].type_id              = -1;
    } /* end for */

    /* Scan each entry on the index list and populate the image_entries array */
    u = 0;
    entry_ptr = cache_ptr->il_head;
    while(entry_ptr != NULL) {
	HDassert(entry_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);

        if(entry_ptr->include_in_image) {
	    /* Since we have already serialized the cache, the following
             * should hold.
             */
            HDassert(entry_ptr->image_up_to_date);
            HDassert(entry_ptr->image_ptr);
	    HDassert(entry_ptr->type);

	    image_entries[u].addr                 = entry_ptr->addr;
	    image_entries[u].size                 = entry_ptr->size;
	    image_entries[u].ring                 = entry_ptr->ring;

	    /* When a prefetched entry is included in the image, store
             * its underlying type id in the image entry, not 
             * H5AC_PREFETCHED_ENTRY_ID.  In passing, also increment
	     * the age (up to H5AC__CACHE_IMAGE__ENTRY_AGEOUT__MAX).
             */
	    if(entry_ptr->type->id == H5AC_PREFETCHED_ENTRY_ID) {
		image_entries[u].type_id          = entry_ptr->prefetch_type_id;
		image_entries[u].age              = entry_ptr->age + 1;
		
		if(image_entries[u].age > H5AC__CACHE_IMAGE__ENTRY_AGEOUT__MAX)
		    image_entries[u].age = H5AC__CACHE_IMAGE__ENTRY_AGEOUT__MAX;
	    } /* end if */
            else {
                image_entries[u].type_id          = entry_ptr->type->id;
		image_entries[u].age              = 0;
	    } /* end else */

	    image_entries[u].lru_rank             = entry_ptr->lru_rank;
	    image_entries[u].is_dirty             = entry_ptr->is_dirty;
	    image_entries[u].image_fd_height      = entry_ptr->image_fd_height;
	    image_entries[u].fd_parent_count      = entry_ptr->fd_parent_count;
	    image_entries[u].fd_parent_addrs      = entry_ptr->fd_parent_addrs;
	    image_entries[u].fd_child_count       = entry_ptr->fd_child_count;
	    image_entries[u].fd_dirty_child_count = 
						entry_ptr->fd_dirty_child_count;
	    image_entries[u].image_ptr            = entry_ptr->image_ptr;

	    /* Null out entry_ptr->fd_parent_addrs and set 
             * entry_ptr->fd_parent_count to zero so that ownership of the
             * flush dependency parents address array is transferred to the 
	     * image entry.
             */
	    entry_ptr->fd_parent_count = 0;
	    entry_ptr->fd_parent_addrs = NULL;

            u++;

	    HDassert(u <= cache_ptr->num_entries_in_image);
        } /* end if */

        entries_visited++;

        entry_ptr = entry_ptr->il_next;
    } /* end while */

    /* Sanity checks */
    HDassert(entries_visited == cache_ptr->index_len);
    HDassert(u == cache_ptr->num_entries_in_image);

    HDassert(image_entries[u].fd_parent_addrs == NULL);
    HDassert(image_entries[u].image_ptr == NULL);

    cache_ptr->image_entries = image_entries;

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__prep_for_file_close__setup_image_entries_array() */


/*-------------------------------------------------------------------------
 * Function:    H5C__prep_for_file_close__scan_entries
 *
 * Purpose:     Scan all entries in the metadata cache, and store all 
 *		entry specific data required for construction of the 
 *		metadata cache image block and likely to be discarded
 *		or modified during the cache flush on file close.
 *
 *		In particular, make note of:
 *			entry rank in LRU
 *			whether the entry is dirty
 *			base address of entry flush dependency parent,
 *			        if it exists.
 *			number of flush dependency children, if any.
 *
 *		Also, determine which entries are to be included in the
 *		metadata cache image.  At present, all entries other than
 *		the superblock, the superblock extension object header and
 *		its associated chunks (if any) are included.
 *
 *		Finally, compute the size of the metadata cache image
 *		block.
 *		
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              7/21/15
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__prep_for_file_close__scan_entries(const H5F_t *f, H5C_t *cache_ptr)
{
    H5C_cache_entry_t * entry_ptr;
    hbool_t		include_in_image;
    unsigned		entries_visited = 0;
    int			lru_rank = 1;
    uint32_t		num_entries_tentatively_in_image = 0;
    uint32_t		num_entries_in_image = 0;
    size_t		image_len;
    size_t		entry_header_len;
    size_t		fd_parents_list_len;
    int			i;
    unsigned            j;
    herr_t		ret_value = SUCCEED;      /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    HDassert(f->shared->sblock);
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(cache_ptr->close_warning_received);
    HDassert(cache_ptr->pl_len == 0);

    /* Initialize image len to the size of the metadata cache image block
     * header.
     */
    image_len        = H5C__cache_image_block_header_size(f);
    entry_header_len = H5C__cache_image_block_entry_header_size(f);

    /* Scan each entry on the index list */
    entry_ptr = cache_ptr->il_head;
    while(entry_ptr != NULL) {
	HDassert(entry_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);

	/* Since we have already serialized the cache, the following
         * should hold.
         */
        HDassert(entry_ptr->image_up_to_date);
        HDassert(entry_ptr->image_ptr);

	/* Initially, we mark all entries in the rings included
         * in the cache image as being included in the in the 
         * image.  Depending on circumstances, we may exclude some
         * of these entries later.
         */
	if(entry_ptr->ring > H5C_MAX_RING_IN_IMAGE)
	    include_in_image = FALSE;
        else
	    include_in_image = TRUE;
        entry_ptr->include_in_image = include_in_image;

        if(include_in_image) {
	    entry_ptr->lru_rank = -1;
            entry_ptr->image_dirty = entry_ptr->is_dirty;
	    entry_ptr->image_fd_height = 0;     /* will compute this later */

	    /* Initially, include all flush dependency parents in the
             * the list of flush dependencies to be stored in the 
             * image.  We may remove some or all of these later.
             */
	    if(entry_ptr->flush_dep_nparents > 0) {
	        /* The parents addresses array may already exist -- reallocate
                 * as needed.
                 */
		if(entry_ptr->flush_dep_nparents == entry_ptr->fd_parent_count ) {
		    /* parent addresses array should already be allocated 
                     * and of the correct size.
                     */
		    HDassert(entry_ptr->fd_parent_addrs);
		} /* end if */
                else if(entry_ptr->fd_parent_count > 0) {
		    HDassert(entry_ptr->fd_parent_addrs);
                    entry_ptr->fd_parent_addrs = (haddr_t *)H5MM_xfree(entry_ptr->fd_parent_addrs);
		} /* end else-if */
                else {
		    HDassert(entry_ptr->fd_parent_count == 0);
		    HDassert(entry_ptr->fd_parent_addrs == NULL);
		} /* end else */

		entry_ptr->fd_parent_count = entry_ptr->flush_dep_nparents;
		if(NULL == entry_ptr->fd_parent_addrs)
		    if(NULL == (entry_ptr->fd_parent_addrs = (haddr_t *)H5MM_malloc(sizeof(haddr_t) * (size_t)(entry_ptr->fd_parent_count))))
        	        HGOTO_ERROR(H5E_CACHE, H5E_CANTALLOC, FAIL, "memory allocation failed for fd parent addrs buffer")

		for(i = 0; i < (int)(entry_ptr->fd_parent_count); i++) {
		    entry_ptr->fd_parent_addrs[i] = entry_ptr->flush_dep_parent[i]->addr;
		    HDassert(H5F_addr_defined(entry_ptr->fd_parent_addrs[i]));
		} /* end for */
            } /* end if */
            else if(entry_ptr->fd_parent_count > 0) {
		HDassert(entry_ptr->fd_parent_addrs);
		entry_ptr->fd_parent_addrs = (haddr_t *)H5MM_xfree(entry_ptr->fd_parent_addrs);
	    } /* end else-if */
            else
		HDassert(entry_ptr->fd_parent_addrs == NULL);

	    /* Initially, all flush dependency children are included int
             * the count of flush dependency child relationships to be 
             * represented in the cache image.  Some or all of these 
             * may be dropped from the image later.
             */
	    if(entry_ptr->flush_dep_nchildren > 0) {
		if(!entry_ptr->is_pinned)
                    HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "encountered unpinned fd parent?!?")

		entry_ptr->fd_child_count = entry_ptr->flush_dep_nchildren;
		entry_ptr->fd_dirty_child_count = entry_ptr->flush_dep_ndirty_children;
	    } /* end if */

	    num_entries_tentatively_in_image++;
        } /* end if */

        entries_visited++;
        entry_ptr = entry_ptr->il_next;
    } /* end while */
    HDassert(entries_visited == cache_ptr->index_len);

    /* Now compute the flush dependency heights of all flush dependency
     * relationships to be represented in the image.
     *
     * If all entries in the target rings are included in the 
     * image, the flush dependency heights are simply the heights 
     * of all flush dependencies in the target rings.
     *
     * However, if we restrict appearance in the cache image either 
     * by number of entries in the image, restrictions on the number 
     * of times a prefetched entry can appear in an image, or image 
     * size, it is possible that flush dependency parents or children
     * of entries that are in the image may not be included in the
     * the image.  In this case, we must prune all flush dependency 
     * relationships that cross the image boundary, and all exclude 
     * from the image all dirty flush dependency children that have 
     * a dirty flush dependency parent that is not in the image. 
     * This is necessary to preserve the required flush ordering.
     * 
     * These details are tended to by the following call to 
     * H5C__prep_for_file_close__compute_fd_heights().  Because the 
     * exact contents of the image cannot be known until after this
     * call, computation of the image size is delayed.
     */
    if(H5C__prep_for_file_close__compute_fd_heights(cache_ptr) < 0)
        HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "computation of flush dependency heights failed?!?")

    /* At this point, all entries that will appear in the cache
     * image should be marked correctly.  Compute the size of the 
     * cache image.
     */
    entries_visited = 0;
    entry_ptr = cache_ptr->il_head;
    while(entry_ptr != NULL) {
        HDassert(entry_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);

        if(entry_ptr->include_in_image) {
            if(entry_ptr->fd_parent_count > 0)
                fd_parents_list_len = (size_t)(H5F_SIZEOF_ADDR(f) * entry_ptr->fd_parent_count);
            else
                fd_parents_list_len = (size_t)0;

            image_len += entry_header_len + fd_parents_list_len + entry_ptr->size;
            num_entries_in_image++;
        } /* end if */

        entries_visited++;
        entry_ptr = entry_ptr->il_next;
    } /* end while */
    HDassert(entries_visited == cache_ptr->index_len);
    HDassert(num_entries_in_image <= num_entries_tentatively_in_image);

    j = 0;
    for(i = H5C_MAX_RING_IN_IMAGE + 1; i <= H5C_RING_SB; i++)
        j += cache_ptr->index_ring_len[i];

    /* This will change */
    HDassert(entries_visited == (num_entries_tentatively_in_image + j));

    cache_ptr->num_entries_in_image = num_entries_in_image;
    entries_visited = 0;

    /* Now scan the LRU list to set the lru_rank fields of all entries
     * on the LRU.
     *
     * Note that we start with rank 1, and increment by 1 with each 
     * entry on the LRU.  
     *
     * Note that manually pinned entryies will have lru_rank -1,
     * and no flush dependency.  Putting these entries at the head of 
     * the reconstructed LRU should be appropriate.
     */
    entry_ptr = cache_ptr->LRU_head_ptr;
    while(entry_ptr != NULL) {
	HDassert(entry_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
        HDassert(entry_ptr->type != NULL);

        /* to avoid confusion, don't set lru_rank on epoch markers.
         * Note that we still increment the lru_rank, so that the holes
         * in the sequence of entries on the LRU will indicate the 
         * locations of epoch markers (if any) when we reconstruct 
         * the LRU.
         *
         * Do not set lru_rank or increment lru_rank for entries 
         * that will not be included in the cache image.
         */
	if(entry_ptr->type->id == H5AC_EPOCH_MARKER_ID)
	    lru_rank++;
	else if(entry_ptr->include_in_image) {
	    entry_ptr->lru_rank = lru_rank;
            lru_rank++;
        } /* end else-if */

        entries_visited++;
        entry_ptr = entry_ptr->next;
    } /* end while */
    HDassert(entries_visited == cache_ptr->LRU_list_len);

    image_len += H5F_SIZEOF_CHKSUM;
    cache_ptr->image_data_len = image_len;

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__prep_for_file_close__scan_entries() */


/*-------------------------------------------------------------------------
 * Function:    H5C__reconstruct_cache_contents()
 *
 * Purpose:     Scan the image buffer, and create a prefetched
 *		cache entry for every entry in the buffer.  Insert the 
 *		prefetched entries in the index and the LRU, and 
 *		reconstruct any flush dependencies.  Order the entries 
 *		in the LRU as indicated by the stored lru_ranks.
 *
 * Return:      SUCCEED on success, and FAIL on failure.
 *
 * Programmer:  John Mainzer
 *              8/14/15
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__reconstruct_cache_contents(H5F_t *f, hid_t dxpl_id, H5C_t *cache_ptr)
{
    H5C_cache_entry_t *	pf_entry_ptr;   /* Pointer to prefetched entry */
    H5C_cache_entry_t *	parent_ptr;     /* Pointer to parent of prefetched entry */
    const uint8_t *	p;              /* Pointer into image buffer */
    unsigned		u, v;           /* Local index variable */
    herr_t 		ret_value = SUCCEED;      /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    HDassert(cache_ptr == f->shared->cache);
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(cache_ptr->image_buffer);
    HDassert(cache_ptr->image_len > 0);

    /* Decode metadata cache image header */
    p = (uint8_t *)cache_ptr->image_buffer;
    if(H5C__decode_cache_image_header(f, cache_ptr, &p) < 0)
        HGOTO_ERROR(H5E_CACHE, H5E_CANTDECODE, FAIL, "cache image header decode failed")
    HDassert((size_t)(p - (uint8_t *)cache_ptr->image_buffer) < cache_ptr->image_len);

    /* The image_data_len and # of entries should be defined now */
    HDassert(cache_ptr->image_data_len > 0);
    HDassert(cache_ptr->image_data_len <= cache_ptr->image_len);
    HDassert(cache_ptr->num_entries_in_image > 0);

    /* Reconstruct entries in image */
    for(u = 0; u < cache_ptr->num_entries_in_image; u++) {
	/* Create the prefetched entry described by the ith
         * entry in cache_ptr->image_entrise.
         */
	if(NULL == (pf_entry_ptr = H5C__reconstruct_cache_entry(f, cache_ptr, &p)))
            HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "reconstruction of cache entry failed")

	/* Note that we make no checks on available cache space before 
         * inserting the reconstructed entry into the metadata cache.
         *
         * This is OK since the cache must be almost empty at the beginning
         * of the process, and since we check cache size at the end of the 
         * reconstruction process.
         */

	/* Insert the prefetched entry in the index */
	H5C__INSERT_IN_INDEX(cache_ptr, pf_entry_ptr, FAIL)

	/* If dirty, insert the entry into the slist. */
	if(pf_entry_ptr->is_dirty)
	    H5C__INSERT_ENTRY_IN_SLIST(cache_ptr, pf_entry_ptr, FAIL)

        /* Append the entry to the LRU */
	H5C__UPDATE_RP_FOR_INSERT_APPEND(cache_ptr, pf_entry_ptr, FAIL)

	H5C__UPDATE_STATS_FOR_PREFETCH(cache_ptr, pf_entry_ptr->is_dirty)

	/* If the prefetched entry is the child in one or more flush 
         * dependency relationships, recreate those flush dependencies.
         */
	for(v = 0; v < pf_entry_ptr->fd_parent_count; v++) {
            /* Sanity checks */
	    HDassert(pf_entry_ptr->fd_parent_addrs);
	    HDassert(H5F_addr_defined(pf_entry_ptr->fd_parent_addrs[v]));

            /* Find the parent entry */
	    parent_ptr = NULL;
	    H5C__SEARCH_INDEX(cache_ptr, pf_entry_ptr->fd_parent_addrs[v], parent_ptr, FAIL)
	    if(parent_ptr == NULL)
                HGOTO_ERROR(H5E_CACHE, H5E_NOTFOUND, FAIL, "fd parent not in cache?!?")

            /* Sanity checks */
	    HDassert(parent_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
            HDassert(parent_ptr->addr == pf_entry_ptr->fd_parent_addrs[v]);
            HDassert(parent_ptr->lru_rank == -1);

	    /* Must protect parent entry to set up a flush dependency.
             * Do this now, and then uprotect when done.
             */
            H5C__UPDATE_RP_FOR_PROTECT(cache_ptr, parent_ptr, FAIL)
            parent_ptr->is_protected = TRUE;
	    
	    /* Setup the flush dependency */
	    if(H5C_create_flush_dependency(parent_ptr, pf_entry_ptr) < 0)
		HGOTO_ERROR(H5E_CACHE, H5E_CANTDEPEND, FAIL, "Can't restore flush dependency")

	    /* And now unprotect */
	    H5C__UPDATE_RP_FOR_UNPROTECT(cache_ptr, parent_ptr, FAIL)
	    parent_ptr->is_protected = FALSE;
        } /* end for */
    } /* end for */

#ifndef NDEBUG
    /* Scan the cache entries, and verify that each entry has
     * the expected flush dependency status.
     */
    pf_entry_ptr = cache_ptr->il_head;
    while(pf_entry_ptr != NULL) {
        HDassert(pf_entry_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
        HDassert((pf_entry_ptr->prefetched && pf_entry_ptr->type == H5AC_PREFETCHED_ENTRY)
            || (!pf_entry_ptr->prefetched && pf_entry_ptr->type != H5AC_PREFETCHED_ENTRY));
        if(pf_entry_ptr->type == H5AC_PREFETCHED_ENTRY)
            HDassert(pf_entry_ptr->fd_parent_count == pf_entry_ptr->flush_dep_nparents);

        for(v = 0; v < pf_entry_ptr->fd_parent_count; v++) {
            parent_ptr = pf_entry_ptr->flush_dep_parent[v];
            HDassert(parent_ptr);
            HDassert(parent_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
            HDassert(pf_entry_ptr->fd_parent_addrs);
            HDassert(pf_entry_ptr->fd_parent_addrs[v] == parent_ptr->addr);
            HDassert(parent_ptr->flush_dep_nchildren > 0);
        } /* end for */

        if(pf_entry_ptr->type == H5AC_PREFETCHED_ENTRY) {
            HDassert(pf_entry_ptr->fd_child_count == pf_entry_ptr->flush_dep_nchildren);
            HDassert(pf_entry_ptr->fd_dirty_child_count == pf_entry_ptr->flush_dep_ndirty_children);
        } /* end if */

        pf_entry_ptr = pf_entry_ptr->il_next;
    } /* end while */

    /* Scan the LRU, and verify the expected ordering of the 
     * prefetched entries.
     */
    {
	int lru_rank_holes = 0;
        H5C_cache_entry_t *entry_ptr;
        int     i;              /* Local index variable */

        i = -1;
        entry_ptr = cache_ptr->LRU_head_ptr;

        while(entry_ptr != NULL) {

            HDassert(entry_ptr->magic == H5C__H5C_CACHE_ENTRY_T_MAGIC);
            HDassert(entry_ptr->type != NULL);

            if ( entry_ptr->prefetched ) {

                HDassert(entry_ptr->lru_rank != 0);
                HDassert((entry_ptr->lru_rank == -1) ||
                         (entry_ptr->lru_rank > i));

                if ( ( entry_ptr->lru_rank > 1 ) && 
                     ( entry_ptr->lru_rank > i + 1 ) )

                    lru_rank_holes += entry_ptr->lru_rank - (i + 1);

                i = entry_ptr->lru_rank;

            } /* end if */

            entry_ptr = entry_ptr->next;
        } /* end while */

	/* Holes in the sequences of LRU ranks can appear due to epoch 
         * markers.  They are left in to allow re-insertion of the
         * epoch markers on reconstruction of the cache -- thus 
         * the following sanity check will have to be revised when
         * we add code to store and restore adaptive resize status.
         */
	HDassert(lru_rank_holes <= H5C__MAX_EPOCH_MARKERS);
    } /* end block */
#endif /* NDEBUG */

    /* Check to see if the cache is oversize, and evict entries as 
     * necessary to remain within limits.
     */
    if(cache_ptr->index_size >= cache_ptr->max_cache_size) {
	/* cache is oversized -- call H5C__make_space_in_cache() with zero
         * space needed to repair the situation if possible.
         */
        hbool_t write_permitted = FALSE;

	if(cache_ptr->check_write_permitted != NULL) {
	    if((cache_ptr->check_write_permitted)(f, &write_permitted) < 0)
                HGOTO_ERROR(H5E_CACHE, H5E_CANTPROTECT, FAIL, "Can't get write_permitted")
        } /* end if */
        else
            write_permitted = cache_ptr->write_permitted;

	if(H5C__make_space_in_cache(f, dxpl_id, 0, write_permitted) < 0)
	    HGOTO_ERROR(H5E_CACHE, H5E_CANTPROTECT, FAIL, "H5C__make_space_in_cache failed")
    } /* end if */

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__reconstruct_cache_contents() */


/*-------------------------------------------------------------------------
 * Function:    H5C__reconstruct_cache_entry()
 *
 * Purpose:     Allocate a prefetched metadata cache entry and initialize
 *		it from image buffer.
 *
 *		Return a pointer to the newly allocated cache entry,
 *		or NULL on failure.
 *
 * Return:      Pointer to the new instance of H5C_cache_entry on success, 
 *		or NULL on failure.
 *
 * Programmer:  John Mainzer
 *              8/14/15
 *
 *-------------------------------------------------------------------------
 */
static H5C_cache_entry_t *
H5C__reconstruct_cache_entry(const H5F_t *f, H5C_t *cache_ptr,
    const uint8_t **buf)
{
    H5C_cache_entry_t *pf_entry_ptr = NULL;     /* Reconstructed cache entry */
    uint8_t             flags = 0;
    hbool_t		is_dirty = FALSE;
#ifndef NDEBUG	/* only used in assertions */
    hbool_t		in_lru = FALSE;
    hbool_t		is_fd_parent = FALSE;
    hbool_t		is_fd_child = FALSE;
#endif /* NDEBUG */ /* only used in assertions */
    const uint8_t *	p;
    hbool_t            file_is_rw;
    H5C_cache_entry_t *ret_value = NULL;        /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(cache_ptr->num_entries_in_image > 0);
    HDassert(buf && *buf);

    /* Key R/W access off of whether the image will be deleted */
    file_is_rw = cache_ptr->delete_image;

    /* Allocate space for the prefetched cache entry */
    if(NULL == (pf_entry_ptr = H5FL_CALLOC(H5C_cache_entry_t)))
	HGOTO_ERROR(H5E_CACHE, H5E_CANTALLOC, NULL, "memory allocation failed for prefetched cache entry")

    /* Get pointer to buffer */
    p = *buf;

    /* Decode type id */
    pf_entry_ptr->prefetch_type_id = *p++;

    /* Decode flags */
    flags = *p++;
    if(flags & H5C__MDCI_ENTRY_DIRTY_FLAG)
        is_dirty = TRUE;
#ifndef NDEBUG	/* only used in assertions */
    if(flags & H5C__MDCI_ENTRY_IN_LRU_FLAG)
        in_lru = TRUE;
    if(flags & H5C__MDCI_ENTRY_IS_FD_PARENT_FLAG)
        is_fd_parent = TRUE;
    if(flags & H5C__MDCI_ENTRY_IS_FD_CHILD_FLAG)
        is_fd_child = TRUE;
#endif /* NDEBUG */ /* only used in assertions */

    /* Force dirty entries to clean if the file read only -- must do 
     * this as otherwise the cache will attempt to write them on file
     * close.  Since the file is R/O, the metadata cache image superblock
     * extension message and the cache image block will not be removed.
     * Hence no danger in this for subsequent opens.
     *
     * However, if the dirty entry (marked clean for purposes of the R/O
     * file open) is evicted and then referred to, the cache will read
     * either invalid or obsolete data from the file.  Handle this by 
     * setting the prefetched_dirty field, and hiding such entries from
     * the eviction candidate selection algorithm.
     */
    pf_entry_ptr->is_dirty = (is_dirty && file_is_rw);

    /* Decode ring */
    pf_entry_ptr->ring = *p++;
    HDassert(pf_entry_ptr->ring > (uint8_t)(H5C_RING_UNDEFINED));
    HDassert(pf_entry_ptr->ring < (uint8_t)(H5C_RING_NTYPES));

    /* Decode age */
    pf_entry_ptr->age = *p++;

    /* Decode dependency child count */
    UINT16DECODE(p, pf_entry_ptr->fd_child_count);
    HDassert((is_fd_parent && pf_entry_ptr->fd_child_count > 0) || (!is_fd_parent && pf_entry_ptr->fd_child_count == 0));

    /* Decode dirty dependency child count */
    UINT16DECODE(p, pf_entry_ptr->fd_dirty_child_count);
    if(!file_is_rw) 
        pf_entry_ptr->fd_dirty_child_count      = 0;
    if(pf_entry_ptr->fd_dirty_child_count > pf_entry_ptr->fd_child_count)
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, NULL, "invalid dirty flush dependency child count")

    /* Decode dependency parent count */
    UINT16DECODE(p, pf_entry_ptr->fd_parent_count);
    HDassert((is_fd_child && pf_entry_ptr->fd_parent_count > 0) || (!is_fd_child && pf_entry_ptr->fd_parent_count == 0));

    /* Decode index in LRU */
    INT32DECODE(p, pf_entry_ptr->lru_rank);
    HDassert((in_lru && pf_entry_ptr->lru_rank >= 0) || (!in_lru && pf_entry_ptr->lru_rank == -1));

    /* Decode entry offset */
    H5F_addr_decode(f, &p, &pf_entry_ptr->addr);
    if(!H5F_addr_defined(pf_entry_ptr->addr))
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, NULL, "invalid entry offset")

    /* Decode entry length */
    H5F_DECODE_LENGTH(f, p, pf_entry_ptr->size);
    if(pf_entry_ptr->size == 0)
        HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, NULL, "invalid entry size")

    /* Verify expected length of entry image */
    if((size_t)(p - *buf) != H5C__cache_image_block_entry_header_size(f))
        HGOTO_ERROR(H5E_CACHE, H5E_BADSIZE, NULL, "Bad entry image len")
    
    /* If parent count greater than zero, allocate array for parent 
     * addresses, and decode addresses into the array.
     */
    if(pf_entry_ptr->fd_parent_count > 0) {
        unsigned u;          /* Local index variable */

        if(NULL == (pf_entry_ptr->fd_parent_addrs = (haddr_t *)H5MM_malloc((size_t)(pf_entry_ptr->fd_parent_count) * H5F_SIZEOF_ADDR(f))))
	    HGOTO_ERROR(H5E_CACHE, H5E_CANTALLOC, NULL, "memory allocation failed for fd parent addrs buffer")

	for(u = 0; u < pf_entry_ptr->fd_parent_count; u++) {
            H5F_addr_decode(f, &p, &(pf_entry_ptr->fd_parent_addrs[u]));
            if(!H5F_addr_defined(pf_entry_ptr->fd_parent_addrs[u]))
                HGOTO_ERROR(H5E_CACHE, H5E_BADVALUE, NULL, "invalid flush dependency parent offset")
        } /* end for */
    } /* end if */

    /* Allocate buffer for entry image */
    if(NULL == (pf_entry_ptr->image_ptr = H5MM_malloc(pf_entry_ptr->size + H5C_IMAGE_EXTRA_SPACE)))
	HGOTO_ERROR(H5E_CACHE, H5E_CANTALLOC, NULL, "memory allocation failed for on disk image buffer")
#if H5C_DO_MEMORY_SANITY_CHECKS
    HDmemcpy(((uint8_t *)pf_entry_ptr->image_ptr) + size, H5C_IMAGE_SANITY_VALUE, H5C_IMAGE_EXTRA_SPACE);
#endif /* H5C_DO_MEMORY_SANITY_CHECKS */

    /* Copy the entry image from the cache image block */
    HDmemcpy(pf_entry_ptr->image_ptr, p, pf_entry_ptr->size);
    p += pf_entry_ptr->size;

    /* Initialize the rest of the fields in the prefetched entry */
    /* (Only need to set non-zero/NULL/FALSE fields, due to calloc() above) */
    pf_entry_ptr->magic				= H5C__H5C_CACHE_ENTRY_T_MAGIC;
    pf_entry_ptr->cache_ptr 			= cache_ptr;
    pf_entry_ptr->image_up_to_date		= TRUE;
    pf_entry_ptr->type				= H5AC_PREFETCHED_ENTRY;
    pf_entry_ptr->prefetched			= TRUE;
    pf_entry_ptr->prefetched_dirty              = is_dirty && (!file_is_rw);

    /* Sanity checks */
    HDassert(pf_entry_ptr->size > 0 && pf_entry_ptr->size < H5C_MAX_ENTRY_SIZE);

    /* Update buffer pointer */
    *buf = p;

    ret_value = pf_entry_ptr;

done:
    if(NULL == ret_value && pf_entry_ptr)
        pf_entry_ptr = H5FL_FREE(H5C_cache_entry_t, pf_entry_ptr);

    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__reconstruct_cache_entry() */


/*-------------------------------------------------------------------------
 * Function:    H5C__write_cache_image_superblock_msg
 *
 * Purpose:     Write the cache image superblock extension message, 
 *		creating if specified.
 *
 *		In general, the size and location of the cache image block
 *		will be unknow at the time that the cache image superblock
 *		message is created.  A subsequent call to this routine will
 *		be used to write the correct data.
 *
 * Return:      Non-negative on success/Negative on failure.
 *
 * Programmer:  John Mainzer, 7/4/15
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__write_cache_image_superblock_msg(H5F_t *f, hid_t dxpl_id, hbool_t create)
{
    H5C_t *		cache_ptr;
    H5O_mdci_t 	        mdci_msg;	/* metadata cache image message */
					/* to insert in the superblock  */
					/* extension.			*/
    unsigned	   	mesg_flags = H5O_MSG_FLAG_FAIL_IF_UNKNOWN_ALWAYS;
    herr_t              ret_value = SUCCEED;      /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(f);
    HDassert(f->shared);
    HDassert(f->shared->cache);
    cache_ptr = f->shared->cache;
    HDassert(cache_ptr);
    HDassert(cache_ptr->magic == H5C__H5C_T_MAGIC);
    HDassert(cache_ptr->close_warning_received);

    /* Write data into the metadata cache image superblock extension message.
     * Note that this data will be bogus when we first create the message.
     * We will overwrite this data later in a second call to this function.
     */
    mdci_msg.addr = cache_ptr->image_addr;
#ifdef H5_HAVE_PARALLEL
    if(cache_ptr->aux_ptr) { /* we have multiple processes */
        H5AC_aux_t * aux_ptr;

        aux_ptr = (H5AC_aux_t *)cache_ptr->aux_ptr;
        HDassert(aux_ptr->magic == H5AC__H5AC_AUX_T_MAGIC);
        mdci_msg.size = aux_ptr->p0_image_len;
    } /* end if */
    else
#endif /* H5_HAVE_PARALLEL */
        mdci_msg.size = cache_ptr->image_len;

    /* Write metadata cache image message to superblock extension */
    if(H5F_super_ext_write_msg(f, dxpl_id, H5O_MDCI_MSG_ID, &mdci_msg, create, mesg_flags) < 0)
        HGOTO_ERROR(H5E_CACHE, H5E_WRITEERROR, FAIL, "can't write metadata cache image message to superblock extension")

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__write_cache_image_superblock_msg() */


/*-------------------------------------------------------------------------
 * Function:    H5C__write_cache_image
 *
 * Purpose:	Write the supplied metadata cache image to the specified
 *		location in file.
 *
 * Return:      Non-negative on success/Negative on failure
 *
 * Programmer:  John Mainzer
 *              8/26/15
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5C__write_cache_image(H5F_t *f, hid_t dxpl_id, const H5C_t *cache_ptr)
{
    herr_t              ret_value = SUCCEED;    /* Return value */

    FUNC_ENTER_STATIC

    /* Sanity checks */
    HDassert(f);
    HDassert(cache_ptr);
    HDassert(H5F_addr_defined(cache_ptr->image_addr));
    HDassert(cache_ptr->image_len > 0);
    HDassert(cache_ptr->image_buffer);

#ifdef H5_HAVE_PARALLEL
{
    H5AC_aux_t *aux_ptr = (H5AC_aux_t *)cache_ptr->aux_ptr;

    if((NULL == aux_ptr) || (aux_ptr->mpi_rank == 0)) {
	HDassert((NULL == aux_ptr) || (aux_ptr->magic == H5AC__H5AC_AUX_T_MAGIC));
#endif /* H5_HAVE_PARALLEL */

	/* Write the buffer (if serial access, or rank 0 for parallel access) */
	if(H5F_block_write(f, H5FD_MEM_SUPER, cache_ptr->image_addr, cache_ptr->image_len, dxpl_id, cache_ptr->image_buffer) < 0)
            HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "can't write metadata cache image block to file")
#ifdef H5_HAVE_PARALLEL
    } /* end if */
} /* end block */
#endif /* H5_HAVE_PARALLEL */
	
done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* H5C__write_cache_image() */

