/*
 * Copyright (c) 2000-2001 Silicon Graphics, Inc.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <xfs/xfs.h>
#include <xfs/jdm.h>
#include <assert.h>

#include "config.h"

#include "inv_priv.h"
#include "inv_oref.h"


/*
 * Resolve a stobj, invidx or fstab
 */
int
oref_resolve_(
	invt_oref_t 	*obj,
	invt_objtype_t	type)
{
	int rval;

	type &= INVT_OTYPE_MASK;
	assert(type);
	assert(!OREF_ISRESOLVED(obj, INVT_OTYPE_MASK));

	switch (type) {
	      case INVT_OTYPE_INVIDX:
		rval = oref_resolve_invidx(obj);
		break;

	      case INVT_OTYPE_STOBJ:
		rval = oref_resolve_stobj(obj);
		break;

	      case INVT_OTYPE_FSTAB:
		rval = oref_resolve_fstab(obj);
		break;

	      default:
		assert(0);
		break;
	}

	return rval;
}





/*
 * Resolve an object reference upto a specified point
 */

int
oref_resolve_upto(
	invt_oref_t 	*obj,
	invt_objtype_t	type)
{
	int rval = INV_OK;

	assert (OREF_ISRESOLVED(obj, INVT_OTYPE_MASK));
	assert (OREF_ISLOCKED(obj));

	/* we arent interested in anything else */
	type &= INVT_RES_MASK;
	assert(type);

	if (type >= INVT_RES_COUNTERS) {
		rval = oref_resolve_counters(obj);
	}
	if (type >= INVT_RES_ENTRIES && rval != INV_ERR) {
		rval = oref_resolve_entries(obj);
	}
	if (type >= INVT_RES_HDRS && rval != INV_ERR) {
		rval = oref_resolve_hdrs(obj);
	}
	if (type >= INVT_RES_SESSIONS && rval != INV_ERR) {
		rval = oref_resolve_sessions(obj);
	}
	if (type >= INVT_RES_STRMS && rval != INV_ERR) {
		rval = oref_resolve_strms(obj);
	}
	if (type >= INVT_RES_MFILES && rval != INV_ERR) {
		rval = oref_resolve_mfiles(obj);
	}

	return rval;
}



int
oref_resolve_entries(
	invt_oref_t 	*obj)
{
	if (OREF_ISRESOLVED(obj, INVT_RES_ENTRIES))
		return INV_OK;

	assert(!OREF_ISRESOLVED(INVT_OTYPE_STOBJ));

	if (OREF_ISRESOLVED(INVT_OTYPE_INVIDX)) {
		invt_entry_t *ent;
		assert(OREF_CNT_CURNUM(obj));

		if (GET_ENTRIES(obj->fd, &ent, OREF_CNT_CURNUM(obj),
				sizeof(invt_entry_t)) < 0){
			return INV_ERR;
		}
		obj->eu_ent = ent;
	}
	else {
		invt_fstab_t *ent;
		assert(OREF_CNT_CURNUM(obj));
		if (GET_ENTRIES(obj->fd, &ent, OREF_CNT_CURNUM(obj),
				sizeof(invt_fstab_t)) < 0){
			return INV_ERR;
		}
		obj->eu_fstabent = ent;
	}

	OREF_SET_TYPE(obj, INVT_RES_COUNTERS);

	return INV_OK;
}






int
oref_resolve_counters(
	invt_oref_t 	*obj)
{
	if (OREF_ISRESOLVED(obj, INVT_RES_COUNTERS))
		return INV_OK;

	if (OREF_ISRESOLVED(INVT_OTYPE_STOBJ)) {
		invt_sescounter_t *sescnt;
		sescnt = calloc(1, sizeof(invt_sescounter_t));
		if (GET_SESCOUNTERS(obj->fd, sescnt) < 0){
			free(sescnt);
			return INV_ERR;
		}
		obj->cu_sescnt = sescnt;
	}
	else {
		invt_counter_t *cnt;
		cnt = calloc(1, sizeof(invt_counter_t));
		if (GET_COUNTERS(obj->fd, cnt) < 0){
			free(cnt);
			return INV_ERR;
		}
		obj->cu_cnt = cnt;
	}

	OREF_SET_TYPE(obj, INVT_RES_COUNTERS);

	return INV_OK;
}



int
oref_sync(
	invt_oref_t 	*obj,
	invt_objtype_t	type)
{
	int rval;

	type &= INVT_RES_MASK;
	assert(type);
	assert(OREF_ISRESOLVED(obj, INVT_OTYPE_MASK));
	assert(OREF_ISRESOLVED(obj, type));
	assert(OREF_ISLOCKED(obj));

	switch (type) {
	      case INVT_RES_COUNTERS:
		rval = PUT_REC_NOLOCK(obj->fd,
				      OREF_CNT(obj),
				      sizeof(*OREF_CNT(obj)),
				      (off64_t) 0);

		break;

	      case INVT_RES_ENTRIES:
		assert(!OREF_ISRESOLVED(obj, INVT_OTYPE_STOBJ));

		rval = PUT_REC_NOLOCK(obj->fd,
				      OREF_ENTRIES(obj),
				      sizeof(*OREF_ENTRIES(obj)),
				      (off64_t) sizeof(*OREF_CNT(obj)));

		break;

	      default:
		assert(0);
		break;
	}

	return rval;
}

int
oref_sync_append(
	invt_oref_t 	*obj,
	invt_objtype_t	type,
	void		*entry,
	size_t		entsz)
{
	int rval;

	type &= INVT_RES_MASK;
	assert(type);
	assert(OREF_ISRESOLVED(obj, INVT_OTYPE_MASK));
	assert(OREF_ISLOCKED(obj));

	switch (type) {
	      case INVT_RES_ENTRIES:
		assert(!OREF_ISRESOLVED(obj, INVT_OTYPE_STOBJ));

		rval = PUT_REC_NOLOCK(obj->fd,
				      entry,
				      entsz,
				      IDX_HDR_OFFSET(OREF_CNT_CURNUM(obj) - 1));
		if (OREF_ISRESOLVED(obj, INVT_RES_ENTRIES))
			free((oref)->eu_ent);
		OREF_UNSET_TYPE(obj, INVT_RES_ENTRIES);

		break;

	      default:
		assert(0);
		break;
	}

	return rval;
}



void
_oref_free(
	invt_oref_t 	*obj,
	bool_t 		freethis)
{
	/*
	 * Unlock the object *if* it is locked.
	 * We dont want to actually close the fd because,
	 * the tokens still need it.
	 */
	OREF_UNLOCK(obj);

	if (OREF_ISRESOLVED(obj, INVT_OTYPE_STOBJ)){
		if (OREF_ISRESOLVED(obj, INVT_RES_COUNTERS))
			free((oref)->cu_sescnt);
		if (OREF_ISRESOLVED(obj, INVT_RES_HDRS))
			free((oref)->eu_hdr);
		if (OREF_ISRESOLVED(obj, INVT_RES_SESSIONS))
			free((oref)->eu_ses);
		if (OREF_ISRESOLVED(obj, INVT_RES_STRMS))
			free((oref)->eu_strm);
		if (OREF_ISRESOLVED(obj, INVT_RES_MFILES))
			free((oref)->eu_mfile);
	}
	else if (OREF_ISRESOLVED(obj, INVT_OTYPE_INVIDX)) {
		if (OREF_ISRESOLVED(obj, INVT_RES_COUNTERS))
			free((oref)->cu_cnt);
		if (OREF_ISRESOLVED(obj, INVT_RES_ENTRIES))
			free((oref)->eu_ent);
	}
	else if (OREF_ISRESOLVED(obj, INVT_OTYPE_FSTAB)) {
		if (OREF_ISRESOLVED(obj, INVT_RES_COUNTERS))
			free((oref)->cu_cnt);
		if (OREF_ISRESOLVED(obj, INVT_RES_ENTRIES))
			free((oref)->eu_fstabent);
	}
	OREF_INIT(obj);
	if (freethis)
		free(obj);
}



/*
 * Resolve the invidx and a suitable stobj.
 * Also resolves an idb_token as a side effect.
 */

int
oref_resolve(
	invt_oref_t	*invidx,
	inv_predicate_t bywhat,
	void 		*pred)
{
	char 		fname[INV_STRLEN];
	char 		uuname[INV_STRLEN];
	int 		fd;
	invt_oref_t	*stobj;
	int		index;

	assert(!OREF_ISRESOLVED(invidx, INVT_OTYPE_MASK));

	OREF_SET_TYPE(invidx, INVT_OTYPE_INVIDX);

	/* come up with the unique file suffix that refers to this
	   filesystem */
	if (fstab_get_fname(pred, uuname, bywhat) < 0) {
		return INV_ERR;
	}
	(void) strcpy(fname, uuname);
	strcat(fname, INV_INVINDEX_PREFIX);

	/* first check if the inv_index file exists: if not create it */
	if ((fd = open(fname, O_RDWR)) == -1) {
		if (errno != ENOENT) {
			INV_PERROR (fname);
			return INV_ERR;
		}
		/* create the invidx */
		return oref_resolve_new_invidx(invidx, fname);
	}

	invidx->fd = fd;
	OREF_LOCK(invidx, LOCK_EX);

	if (oref_resolve_child(invidx, &index) == INV_ERR) {
		OREF_UNLOCK(invidx);
		return INV_ERR;
	}

	stobj = OREF_CHILD(invidx);

	/* Now we need to make sure that this has enough space */
	OREF_LOCK(stobj, LOCK_SH);

	if (oref_resolve_upto(stobj, INVT_RES_COUNTERS) == INV_ERR) {
		OREF_UNLOCK(stobj);
		OREF_UNLOCK(invidx);
		return INV_ERR;
	}

	/* create another storage object (and, an inv_index entry for it
	   too) if we've filled this one up */
	if (OREF_CNT_CURNUM(stobj) >= OREF_CNT_MAXNUM(stobj)) {
		int 	rval;
#ifdef INVT_DEBUG
		mlog(MLOG_DEBUG | MLOG_INV, "$ INV: creating a new storage obj & "
		      "index entry. \n");
#endif
		/* Close(), Destroy() and mark unresolved */
		OREF_UNRESOLVE_CHILD(invidx);
		rval = oref_resolve_new_stobj(invidx, BOOL_FALSE);
		/* rval = oref_idx_create_entry(invidx, BOOL_FALSE); */
		OREF_UNLOCK(invidx);

		return rval;
	}

	OREF_UNLOCK(stobj);
	OREF_UNLOCK(invidx);

	tok = get_token(invidx->fd, stobj->fd);
	tok->d_invindex_off = IDX_HDR_OFFSET(index - 1);

	OREF_SET_TOKEN(tok);
	return INV_OK;

}

/*
 * Resolve the invidx entirely, and open the StObj.
 * Invidx is kept locked by caller
 */
int
oref_resolve_child(
	invt_oref_t 	*invidx,
	int		*index)
{
	invt_entry_t 	*ent;
	assert(OREF_IS_LOCKED(invidx));

	if (oref_resolve_upto(invidx, INVT_RES_ENTRIES) == INV_ERR)
		return INV_ERR;

	ent = OREF_ENTRIES(invidx);

	/* at this point we know that there should be at least one invindex
	   entry present */
	assert (ent != NULL);
	assert (ent->ie_filename);

	fd = open(ent->ie_filename, O_RDWR);
	if (fd < 0) {
		INV_PERROR(ent->ie_filename);
		return INV_ERR;
	}

	stobj = calloc(1, sizeof(invt_oref_t));
	OREF_SET_CHILD(invidx, stobj);
	stobj->fd = fd;

	return INV_OK;
}



/* used to be idx_create */
int
oref_resolve_new_invidx(
	invt_oref_t 	*invidx,
	char		*fname)
{
	int stobjfd, fd;
	inv_idbtoken_t tok;

	if ((fd = open (fname, O_RDWR | O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
		INV_PERROR (fname);
		return INV_ERR;
	}
	invidx->fd = fd;
	OREF_LOCK(invidx, LOCK_EX);
	fchmod(fd, INV_PERMS);

#ifdef INVT_DEBUG
	mlog(MLOG_NITTY | MLOG_INV, "creating InvIndex %s\n", fname);
#endif
	/* create the new stobj as its first entry */
	rval = oref_resolve_new_stobj(invidx, IS_EMPTY);

	OREF_UNLOCK(invidx);
	return rval;

}



/* formerly idx_create_entry() */
int
oref_resolve_new_stobj(
	invt_oref_t	*invidx,
	bool_t		firstentry)
{
	invt_entry_t   	ent;
	int	      	fd;
	off64_t 	hoff;
	invt_oref_t	*stobj;
	inv_idbtoken_t	tok;

	assert(OREF_ISLOCKED(invidx));

	memset (&ent, 0, sizeof(ent));
	stobj = calloc(1, sizeof(invt_oref_t));
	OREF_SET_CHILD(invidx, stobj);

	/* initialize the start and end times to be the same */
	ent.ie_timeperiod.tp_start = ent.ie_timeperiod.tp_end = (time32_t)0;
	stobj_makefname(ent.ie_filename);

	if (firstentry) {
		invt_counter_t *cnt;
		cnt = malloc(sizeof(invt_counter_t));

		cnt->ic_maxnum = INVT_MAX_INVINDICES;
		cnt->ic_curnum = 1;
		cnt->ic_vernum = INV_VERSION;

		fd = stobj_create(ent.ie_filename);
		if (fd < 0) {
			free(cnt);
			return INV_ERR;
		}
		stobj->fd = fd;
		OREF_SET_COUNTERS(invidx, cnt);
		OREF_SET_ENTRIES(invidx, &ent);
		if ((oref_sync(invidx, INVT_RES_COUNTERS) == INV_ERR)
		    ||
		    (oref_sync(invidx, INVT_RES_ENTRIES) == INV_ERR))
			return INV_ERR;
	}
	else {
		if (oref_resolve_upto(invidx, INVT_RES_COUNTERS) == INV_ERR)
			return INV_ERR;

		/* create the new storage object */
		fd = stobj_create(ent.ie_filename);
		if (fd < 0) {
			return -1;
		}

		stobj->fd = fd;
		OREF_CNT_CURNUM(invidx)++;

		if ((oref_sync(invidx, INVT_RES_COUNTERS) == INV_ERR)
		    ||
		    (oref_sync_append(invidx, INVT_RES_ENTRIES,
				      &ent, sizeof(ent)) == INV_ERR))
			return INV_ERR;

	}
	tok = get_token(invfd, fd);
	tok->d_invindex_off = IDX_HDR_OFFSET(OREF_CNT_CURNUM(invidx) - 1);
	tok->d_update_flag |= NEW_INVINDEX;

	OREF_SET_TOKEN(invidx, tok);
	return INV_OK;

}


