/*
 * 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 <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <assert.h>
#include <string.h>

#include "config.h"

#include "types.h"
#include "mlog.h"
#include "inv_priv.h"



/*----------------------------------------------------------------------*/
/* insert_invindexentry                                                 */
/*                                                                      */
/* given a time, find the invindex that has the time-period it can fit  */
/* into.                                                                */
/*----------------------------------------------------------------------*/
uint
idx_insert_newentry(int fd, /* kept locked EX by caller */
		     int *stobjfd, /* OUT */
		     invt_entry_t *iarr, invt_counter_t *icnt,
		     time32_t tm)
{
	uint i;
	inv_oflag_t forwhat = INV_SEARCH_N_MOD;
/*	invt_entry_t ient;
	ient.ie_timeperiod.tp_start = ient.ie_timeperiod.tp_end = tm; */

	/* If time period of the new entry is before our first invindex,
	   we have to insert a new invindex in the first slot */
	if (iarr[0].ie_timeperiod.tp_start > tm) {
		/* *stobjfd = idx_put_newentry(fd, 0, iarr, icnt, &ient); */
		*stobjfd = open(iarr[0].ie_filename, INV_OFLAG(forwhat));
		return 0;
	}

	for (i = 0; i < icnt->ic_curnum; i++) {
		/* if our time is nicely within an existing entry's time
		   period, hellalujah */
		if (IS_WITHIN(&iarr[i].ie_timeperiod, tm)) {
#ifdef INVT_DEBUG
			mlog(MLOG_DEBUG | MLOG_INV, "INV: is_within %d\n", i);
#endif
			*stobjfd = open(iarr[i].ie_filename, INV_OFLAG(forwhat));
			return i;
		}
		if (iarr[i].ie_timeperiod.tp_end == 0  &&
		     iarr[i].ie_timeperiod.tp_start  == 0) {
#ifdef INVT_DEBUG
			mlog(MLOG_DEBUG | MLOG_INV, "INV: end = start \n");
			mlog(MLOG_DEBUG | MLOG_INV,"BEF: st %ld end %ld\n",
			     iarr[i].ie_timeperiod.tp_start,
			     iarr[i].ie_timeperiod.tp_end);
#endif

			iarr[i].ie_timeperiod.tp_start =
				iarr[i].ie_timeperiod.tp_end = tm;
			PUT_REC_NOLOCK(fd, iarr,
				       icnt->ic_curnum * sizeof(invt_entry_t),
				       (off64_t) sizeof(invt_counter_t));
#ifdef INVT_DEBUG
			mlog(MLOG_DEBUG | MLOG_INV,"AFT: st %ld end %ld\n",
			     iarr[i].ie_timeperiod.tp_start,
			     iarr[i].ie_timeperiod.tp_end);
#endif
			*stobjfd = open(iarr[i].ie_filename, INV_OFLAG(forwhat));
			return i;
		}



		/* if it is beyond the end of this timeperiod, see if we
		   belong to a timeperiod that doesn't have an entry */
		if (iarr[i].ie_timeperiod.tp_end < tm) {
			/* see if we're the last entry here */
			if (i == icnt->ic_curnum - 1) {
				/* our slot is (i+1)th entry. Make the
				   timeperiod's the same as it was. As far
				   as I can see there is no way that
				   timeperiods can overlap.

				   insert the new entry and write back
				   icnt and invindex entry */
				/* *stobjfd = idx_put_newentry(fd, i+1, iarr,
							     icnt, &ient); */
			      *stobjfd = open(iarr[i].ie_filename, INV_OFLAG(forwhat));
			      return i;
			}
			/* see if the next entry starts later than us */
			if (iarr[i+1].ie_timeperiod.tp_start > tm) {


				/* We have the option of pushing entries
				   after (i) forward by one slot, and
				   taking the (i+1)th slot, OR just hooking
				   up with the next entry.
				   We choose the former. */

				/* the timeperiods had better not overlap */
				assert((tm > iarr[i].ie_timeperiod.tp_end) &&
				       (tm < iarr[i+1].ie_timeperiod.tp_start));

				/* shift everything from (i+1) onwards by
				   one. Then insert the new entry and write
				   back icnt and invindex entries */
				/* *stobjfd = idx_put_newentry(fd, i+1, iarr,
							     icnt, &ient); */
			      *stobjfd = open(iarr[i].ie_filename, INV_OFLAG(forwhat));
				return i;
			}
		}
	}

	/* We couldnt find anything that fits */
	assert(0);	/* We can't get here ! */
	return -1;


}


/*----------------------------------------------------------------------*/
/* idx_put_newentry                                                     */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/

int
idx_put_newentry(
	invt_idxinfo_t *idx,
	invt_entry_t *ient)
{
	invt_entry_t	*idxarr;
	int stobjfd;

	int fd = idx->invfd; 	/* kept locked EX by caller */
	uint index = idx->index + 1;
	invt_entry_t *iarr = idx->iarr;
	invt_counter_t *icnt = idx->icnt;

	stobj_makefname(ient->ie_filename);
	if ((stobjfd = stobj_create(ient->ie_filename)) < 0)
		return -1;

	icnt->ic_curnum++; /* there is no maximum */

	idxarr = (invt_entry_t *) calloc (icnt->ic_curnum,
					     sizeof(invt_entry_t));
	memcpy(idxarr, iarr, (size_t) (sizeof(invt_entry_t) * index));

	/* shift everything from (i+1) onwards by one */
	if (index <  icnt->ic_curnum - 1)
		memcpy(&idxarr[index + 1], &iarr[index],
		       (size_t) ((icnt->ic_curnum - index - 1) *
				    sizeof(invt_entry_t)));
	/* insert the new entry */
	memcpy(&idxarr[index], ient, sizeof(invt_entry_t));


	if ((PUT_COUNTERS(fd, icnt) < 0) ||
		(PUT_REC_NOLOCK(fd, idxarr,
				  icnt->ic_curnum * sizeof(invt_entry_t),
				  sizeof(invt_counter_t)) < 0)) {
			/* XXX delete the stobj that we just created */

			memset(ient->ie_filename, 0, INV_STRLEN);
			free(idxarr);
			return -1;
		}

	free(iarr);
	idx->iarr = idxarr;
	return stobjfd;

}




/*----------------------------------------------------------------------*/
/* idx_find_stobj                                                       */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/


int
idx_find_stobj(invt_idxinfo_t *idx,
	        time32_t tm)
{

	int 		stobjfd;

	/* since sessions can be inserted in random order, the invindex
	   table can contain time-periods that don't have corresponding
	   entries for */
	if (GET_ALLHDRS_N_CNTS_NOLOCK(idx->invfd, (void **)&idx->iarr,
						     (void **)&idx->icnt,
						     sizeof(invt_entry_t),
				sizeof(invt_counter_t)) < 0) {
		return -1;
	}

#ifdef INVT_DEBUG
	printf("idx_find_stobj Time: %ld\n", tm);
	idx_DEBUG_printinvindices(idx->iarr, idx->icnt->ic_curnum);
#endif

	/* Now figure out where we are going to insert this stobj among the
	   invindices and put it there */
	idx->index = idx_insert_newentry(idx->invfd, &stobjfd, idx->iarr,
						 idx->icnt, tm);

	return stobjfd;
}






/*----------------------------------------------------------------------*/
/*                                                                      */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/

inv_idbtoken_t
idx_create(char *fname, inv_oflag_t forwhat)
{
	int stobjfd, fd;
	inv_idbtoken_t tok;

	/* This is not to be called when the user wants to open
	   the db for SEARCH_ONLY. */
	assert(forwhat != INV_SEARCH_ONLY);

	if ((fd = open (fname, INV_OFLAG(forwhat) | O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
		INV_PERROR (fname);
		return INV_TOKEN_NULL;
	}

	INVLOCK(fd, LOCK_EX);
	fchmod(fd, INV_PERMS);

#ifdef INVT_DEBUG
	mlog(MLOG_NITTY | MLOG_INV, "creating InvIndex %s\n", fname);
#endif

	/* create the first entry in the new inv_index */
	stobjfd = idx_create_entry(&tok, fd, BOOL_TRUE);

	INVLOCK(fd, LOCK_UN);

	if (stobjfd < 0)
		return INV_TOKEN_NULL;
	return tok;
}


/*----------------------------------------------------------------------*/
/* idx_recons_time                                                      */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/
int
idx_recons_time(time32_t tm, invt_idxinfo_t *idx)
{
	invt_timeperiod_t *tp = &idx->iarr[idx->index].ie_timeperiod;
	if (tp->tp_start && IS_WITHIN(tp, tm))
		return 1;

	if (tm > tp->tp_end || tp->tp_end == 0)
		tp->tp_end =  tm;
	if (tm < tp->tp_start || tp->tp_start == 0)
		tp->tp_start = tm;
	PUT_REC_NOLOCK(idx->invfd,  &idx->iarr[idx->index],
		        sizeof(invt_entry_t), IDX_HDR_OFFSET(idx->index));
	return 1;
}




/*----------------------------------------------------------------------*/
/* put_sesstime                                                         */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/

int
idx_put_sesstime(inv_sestoken_t tok, bool_t whichtime)
{
	int rval;
	invt_entry_t ent;
	int fd = tok->sd_invtok->d_invindex_fd;

	INVLOCK(fd, LOCK_EX);

	rval = GET_REC_NOLOCK(fd, &ent, sizeof(invt_entry_t),
			        tok->sd_invtok->d_invindex_off);
	if (rval < 0) {
		INVLOCK(fd, LOCK_UN);
		return -1;
	}
	ent.ie_timeperiod.tp_end = tok->sd_sesstime;

	if (whichtime == INVT_STARTTIME || ent.ie_timeperiod.tp_start == 0) {
		ent.ie_timeperiod.tp_start = tok->sd_sesstime;
	}
#ifdef INVT_DEBUG
	mlog(MLOG_DEBUG | MLOG_INV,"Putsestime: st %ld end %ld\n",
			      ent.ie_timeperiod.tp_start,
			      ent.ie_timeperiod.tp_end);
#endif
	rval = PUT_REC_NOLOCK(fd, &ent, sizeof(invt_entry_t),
			      tok->sd_invtok->d_invindex_off);

#ifdef INVT_DEBUG
	{
		int nindices;
		invt_entry_t *iarr = NULL;
		invt_counter_t *icnt = NULL;
		if ((nindices = GET_ALLHDRS_N_CNTS_NOLOCK(fd,
						     (void **)&iarr,
						     (void **)&icnt,
						sizeof(invt_entry_t),
				sizeof(invt_counter_t))) < 0) {
			return -1;
		}
		idx_DEBUG_printinvindices(iarr, (uint) nindices);
		free(iarr);
		free(icnt);
	}
#endif

	INVLOCK(fd, LOCK_UN);
	return rval;
}



/* an inventory index entry keeps track of a single storage object;
   it knows about its name (ie filename) and the timeperiod that the
   it contains dump sessions for.
   note that each file system has its own (set of) inventory indices.
*/

/*----------------------------------------------------------------------*/
/*                                                                      */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/

int
idx_create_entry(
	inv_idbtoken_t *tok,
	int invfd, 	/* kept locked EX  by caller */
	bool_t firstentry)
{
	invt_entry_t   	ent;
	int	      	fd;
	off64_t 	hoff;


	memset (&ent, 0, sizeof(ent));

	/* 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.ic_maxnum = INVT_MAX_INVINDICES;
		cnt.ic_curnum = 1;
		cnt.ic_vernum = INV_VERSION;

		fd = stobj_create(ent.ie_filename);
		if (fd < 0) {
			return -1;
		}

		if (PUT_REC_NOLOCK(invfd, &cnt, sizeof(cnt), (off64_t)0) < 0)
			return -1;

		hoff = sizeof(invt_counter_t);

		if (PUT_REC_NOLOCK(invfd, &ent, sizeof(ent), hoff) < 0)
			return -1;
	} else {
		invt_counter_t *cnt = NULL;

		if (GET_COUNTERS(invfd, &cnt)  < 0) {
			return -1;
		}

		/* XXX check if there are too many indices. if so, create
		   another and leave a pointer to that in here */

		/* create the new storage object */
		fd = stobj_create(ent.ie_filename);
		if (fd < 0) {
			return -1;
		}
		++(cnt->ic_curnum);
		if (PUT_COUNTERS(invfd, cnt) < 0) {
			return -1;
		}

		/* add the new index entry to the array, at the end */

		hoff = IDX_HDR_OFFSET(cnt->ic_curnum - 1);
		free (cnt);
#ifdef INVT_DEBUG
		mlog(MLOG_NITTY | MLOG_INV, "new stobj name %s @ offset %d\n",
		      ent.ie_filename,(int)hoff);
#endif
		if (PUT_REC_NOLOCK(invfd, &ent, sizeof(ent), hoff) < 0)
			return -1;

	}

	*tok = get_token(invfd, fd);
	(*tok)->d_invindex_off = hoff;
	(*tok)->d_update_flag |= NEW_INVINDEX;
	(*tok)->d_oflag = INV_SEARCH_N_MOD;
	return fd;

}






/*----------------------------------------------------------------------*/
/*                                                                      */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/

int
idx_get_stobj(int invfd, inv_oflag_t forwhat, int *index)
{
	invt_entry_t 	*ent = 0;
	int	     	 fd;

	/* if there's space anywhere at all, then it must be in the last
	   entry. get_lastheader() does the locking */

	if ((*index = get_lastheader(invfd, (void **)&ent,
				       sizeof(invt_entry_t),
				       sizeof(invt_counter_t))) < 0)
		return -1;
	/* 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, INV_OFLAG(forwhat));
	if (fd < 0)
		INV_PERROR(ent->ie_filename);
	free (ent);

	return fd;
}


int
idx_DEBUG_printinvindices(invt_entry_t *iarr, uint num)
{
	uint i;
	uint k;

	char s[9];
	printf("\n ==================================\n"
	        " InvIndex\n # StObjs\t%d\n", num);
#define INV_UUID_STR_LEN	36 /* not exported via uuid.h */
	for (i = 0; i < num; i++) {
		k = strlen(iarr[i].ie_filename);
		strncpy(s, (char *) iarr[i].ie_filename + k -
			(INV_UUID_STR_LEN + strlen(INV_STOBJ_PREFIX)), 8);
		s[8]= 0;
		printf("%d. %s \t( %d - %d )\n", i, s,
		       iarr[i].ie_timeperiod.tp_start,
		       iarr[i].ie_timeperiod.tp_end);
	}
#undef INV_UUID_STR_LEN
	printf("\n ==================================\n");
	return 1;

}

int
idx_DEBUG_print (int fd)
{
	int nindices;
	invt_entry_t *iarr = NULL;
	invt_counter_t *icnt = NULL;
	if ((nindices = GET_ALLHDRS_N_CNTS_NOLOCK(fd,
						    (void **)&iarr,
						    (void **)&icnt,
						    sizeof(invt_entry_t),
				         sizeof(invt_counter_t))) < 0) {
		return -1;
	}
	idx_DEBUG_printinvindices(iarr, (uint) nindices);
	free(iarr);
	free(icnt);

	return 1;
}



int
DEBUG_displayallsessions(int fd, invt_seshdr_t *hdr, uint ref,
			  invt_pr_ctx_t *prctx)
{
	inv_session_t *ses;
	if (stobj_make_invsess(fd, &ses, hdr) < 1)
		return -1;

	DEBUG_sessionprint(ses, ref, prctx);
	free(ses->s_streams);
	free(ses);

	return 0;
}

