/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 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 files COPYING and Copyright.html.  COPYING can be found at the root   *
 * of the source code distribution tree; Copyright.html can be found at the  *
 * root level of an installed copy of the electronic HDF5 document set and   *
 * is linked from the top-level documents page.  It can also be found at     *
 * http://hdf.ncsa.uiuc.edu/HDF5/doc/Copyright.html.  If you do not have     *
 * access to either file, you may request a copy from hdfhelp@ncsa.uiuc.edu. *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>
#include <stdlib.h>
#include "h5tools_ref.h"
#include "H5private.h"
#include "H5SLprivate.h"
#include "h5tools.h"
#include "h5tools_utils.h"


/*
 *  Table to look up a path name for an object
 *  reference.
 *
 *  This table stores mappings of reference -> path
 *  for all objects in the file that may be the target of
 *  an object reference.
 *
 *  The 'path' is an absolute path by which the object
 *  can be accessed.  When an object has > 1 such path,
 *  only one will be used in the table, with no particular
 *  method of selecting which one.
 */

typedef struct {
    haddr_t objno;      /* Object ID (i.e. address) */
    const char *path;   /* Object path */
} ref_path_node_t;

static H5SL_t *ref_path_table = NULL;   /* the "table" (implemented with a skip list) */
static hid_t thefile;

extern char  *progname;
extern int   d_status;

static int ref_path_table_put(const char *, haddr_t objno);
static hbool_t ref_path_table_find(haddr_t objno);

/*-------------------------------------------------------------------------
 * Function:    init_ref_path_table
 *
 * Purpose:     Enter the root group ("/") into the path table
 *
 * Return:      Non-negative on success, negative on failure
 *
 * Programmer:  Quincey Koziol
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
int
init_ref_path_table(hid_t fid)
{
    H5G_stat_t sb;
    haddr_t objno;              /* Compact form of object's location */
    char *root_path;

    /* Set file ID for later queries (XXX: this should be fixed) */
    thefile = fid;

    /* Create skip list to store reference path information */
    if((ref_path_table = H5SL_create(H5SL_TYPE_HADDR, 0.5, (size_t)16))==NULL)
	return (-1);

    if((root_path = HDstrdup("/")) == NULL)
	return (-1);

    if(H5Gget_objinfo(fid, "/", TRUE, &sb)<0) {
	/* fatal error? */
	HDfree(root_path);
	return (-1);
    }
    objno = (haddr_t)sb.objno[0] | ((haddr_t)sb.objno[1] << (8 * sizeof(long)));

    /* Insert into table (takes ownership of path) */
    ref_path_table_put(root_path, objno);

    return(0);
}

/*-------------------------------------------------------------------------
 * Function:    free_ref_path_info
 *
 * Purpose:     Free the key for a reference path table node
 *
 * Return:      Non-negative on success, negative on failure
 *
 * Programmer:  Quincey Koziol
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
static herr_t
free_ref_path_info(void *item, void UNUSED *key, void UNUSED *operator_data/*in,out*/)
{
    ref_path_node_t *node = (ref_path_node_t *)item;

    HDfree((void *)node->path);
    HDfree(node);

    return(0);
}

/*-------------------------------------------------------------------------
 * Function:    term_ref_path_table
 *
 * Purpose:     Terminate the reference path table
 *
 * Return:      Non-negative on success, negative on failure
 *
 * Programmer:  Quincey Koziol
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
int
term_ref_path_table(void)
{
    /* Destroy reference path table, freeing all memory */
    if(ref_path_table)
        H5SL_destroy(ref_path_table, free_ref_path_info, NULL);

    return(0);
}

/*-------------------------------------------------------------------------
 * Function:    ref_path_table_lookup
 *
 * Purpose:     Looks up a table entry given a path name.
 *              Used during construction of the table.
 *
 * Return:      The table entre (pte) or NULL if not in the
 *              table.
 *
 * Programmer:  REMcG
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
haddr_t
ref_path_table_lookup(const char *thepath)
{
    H5G_stat_t  sb;
    haddr_t objno;              /* Compact form of object's location */

    /* Check for external link first, so we don't return the OID of an object in another file */
    if(H5Gget_objinfo(thefile, thepath, FALSE, &sb)<0)
	return HADDR_UNDEF;
    if(sb.type == H5G_LINK) {
        /* Get object ID for object at path */
        /* (If the object is not a soft link, we've already retrieved the
         *      correct information and don't have to perform this call. -QAK
         */
        if(H5Gget_objinfo(thefile, thepath, TRUE, &sb)<0)
            /*  fatal error ? */
            return HADDR_UNDEF;
    } else if(sb.type == H5G_UDLINK)
    {
        /* UD links can't be followed, so they always "dangle" like
         * soft links.
         */
        return HADDR_UNDEF;
    } /* end if */
    objno = (haddr_t)sb.objno[0] | ((haddr_t)sb.objno[1] << (8 * sizeof(long)));


    /* All existing objects in the file had better be in the table */
    HDassert(ref_path_table_find(objno));

    /* Return OID */
    return(objno);
}

/*-------------------------------------------------------------------------
 * Function:    ref_path_table_find
 *
 * Purpose:     Looks up a table entry given a object number.
 *              Used during construction of the table.
 *
 * Return:      TRUE/FALSE on success, can't fail
 *
 * Programmer:  Quincey Koziol
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
static hbool_t
ref_path_table_find(haddr_t objno)
{
    HDassert(ref_path_table);

    if(H5SL_search(ref_path_table, &objno) == NULL)
        return FALSE;
    else
        return TRUE;
}

/*-------------------------------------------------------------------------
 * Function:    ref_path_table_put
 *
 * Purpose:     Enter the 'obj' with 'path' in the table (assumes its not
 *              already there)
 *
 *              Create an object reference, pte, and store them
 *              in the table.
 *
 *              NOTE: Takes ownership of the path name string passed in!
 *
 * Return:      Non-negative on success, negative on failure
 *
 * Programmer:  REMcG
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
static int
ref_path_table_put(const char *path, haddr_t objno)
{
    ref_path_node_t *new_node;

    HDassert(ref_path_table);
    HDassert(path);

    if((new_node = HDmalloc(sizeof(ref_path_node_t))) == NULL)
        return(-1);

    new_node->objno = objno;
    new_node->path = path;

    return(H5SL_insert(ref_path_table, new_node, &(new_node->objno)));
}

/*
 *  counter used to disambiguate multiple instances of same object.
 */
int xid = 1;

int get_next_xid(void) {
    return xid++;
}

/*
 *  This counter is used to create fake object ID's
 *  The idea is to set it to the largest possible offest, which
 *  minimizes the chance of collision with a real object id.
 *
 */
haddr_t fake_xid = HADDR_MAX;
haddr_t
get_fake_xid (void) {
    return (fake_xid--);
}

/*
 * for an object that does not have an object id (e.g., soft link),
 * create a table entry with a fake object id as the key.
 *
 * Assumes 'path' is for an object that is not in the table.
 *
 */

haddr_t
ref_path_table_gen_fake(const char *path)
{
    const char *dup_path;
    haddr_t fake_objno;

    if((dup_path = HDstrdup(path)) == NULL)
        return HADDR_UNDEF;

    /* Generate fake ID for string */
    fake_objno = get_fake_xid();

    /* Insert "fake" object into table (takes ownership of path) */
    ref_path_table_put(dup_path, fake_objno);

    return(fake_objno);
}

/*-------------------------------------------------------------------------
 * Function:    lookup_ref_path
 *
 * Purpose:     Lookup the path to the object with refernce 'ref'.
 *
 * Return:      Return a path to the object, or NULL if not found.
 *
 * Programmer:  REMcG
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
const char *
lookup_ref_path(haddr_t ref)
{
    ref_path_node_t *node;

    /* Be safer for h5ls */
    if(!ref_path_table)
        return(NULL);

    node = H5SL_search(ref_path_table, &ref);

    return(node ? node->path : NULL);
}

/*-------------------------------------------------------------------------
 * Function:    fill_ref_path_table
 *
 * Purpose:     Called by interator to create references for
 *              all objects and enter them in the table.
 *
 * Return:      Error status.
 *
 * Programmer:  REMcG
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
herr_t
fill_ref_path_table(hid_t group, const char *obj_name, void *op_data)
{
    const char *obj_prefix = (const char *)op_data;
    H5G_stat_t              statbuf;
    haddr_t objno;              /* Compact form of object's location */

    H5Gget_objinfo(group, obj_name, FALSE, &statbuf);
    objno = (haddr_t)statbuf.objno[0] | ((haddr_t)statbuf.objno[1] << (8 * sizeof(long)));

    /* Check if the object is in the path table */
    if (!ref_path_table_find(objno)) {
        size_t                  tmp_len;
        char                   *thepath;

        /* Compute length for this object's path */
        tmp_len = HDstrlen(obj_prefix) + HDstrlen(obj_name) + 2;

        /* Allocate room for the path for this object */
        if ((thepath = (char *) HDmalloc(tmp_len)) == NULL)
            return FAIL;

        /* Build the name for this object */
        HDstrcpy(thepath, obj_prefix);
        HDstrcat(thepath, "/");
        HDstrcat(thepath, obj_name);

        /* Insert the object into the path table */
        ref_path_table_put(thepath, objno);

        if(statbuf.type == H5G_GROUP) {
            /* Iterate over objects in this group, using this group's
             * name as their prefix
             */
            if(H5Giterate(group, obj_name, NULL, fill_ref_path_table, thepath) < 0) {
                error_msg(progname, "unable to dump group \"%s\"\n", obj_name);
                d_status = EXIT_FAILURE;
            }
        }
    }

    return 0;
}