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

#include "types.h"
#include "inventory_priv.h"

int sesslock_fd = -1;


/*----------------------------------------------------------------------*/
/* inventory_open                                                       */
/*                                                                      */
/* INV_BY_MOUNTPT, INV_BY_UUID or INV_BY_DEVPATH                        */
/*----------------------------------------------------------------------*/

inv_idbtoken_t
inv_open( inv_predicate_t bywhat, void *pred )
{

	int fd, stobjfd, num;
	char uuname[ INV_STRLEN ];
	inv_idbtoken_t tok = INV_TOKEN_NULL;
	invt_sescounter_t *sescnt = 0;
	
	int index = 0;
	
	assert ( pred );
	if ((fd = init_idb ( pred, bywhat, uuname, &tok )) < 0 )
		return tok;

	/* XXX also, see if it is too full. if so, make another and leave a
	   reference to the new file in the old one */

	stobjfd = get_storageobj( fd, &index );
	if ( stobjfd < 0 ) {
		close( fd );
		close( sesslock_fd );
		sesslock_fd = -1;
		return INV_TOKEN_NULL;
	}

	assert ( index > 0 );

	/* Now we need to make sure that this has enough space */
	num = GET_SESCOUNTERS( stobjfd, &sescnt );
	if ( num < 0 ) {
		close( fd );
		close( stobjfd );
		close( sesslock_fd );
		sesslock_fd = -1;
		return INV_TOKEN_NULL;
	}
	/* create another storage object ( and, an inv_index entry for it too )
	   if we've filled this one up */
	if ( (uint) num >= sescnt->ic_maxnum ) {
#ifdef INVT_DEBUG
		printf("$ creating a new storage obj & index entry. \n" );
#endif
		close (stobjfd);
		
		stobjfd = create_invindex_entry( &tok, fd, uuname, BOOL_FALSE );
		free ( sescnt );		
		if ( stobjfd < 0 ) {
			close( fd );
			close( sesslock_fd );
			sesslock_fd = -1;
			return INV_TOKEN_NULL;
		}
		return tok;
	}
	
	free ( sescnt );
	tok = get_token( fd, stobjfd );
	tok->d_invindex_off = INVINDEX_HDR_OFFSET( index - 1 );
	
	return tok;
	
}




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


bool_t
inv_close( inv_idbtoken_t tok )
{
	close ( tok->d_invindex_fd );
	close ( tok->d_stobj_fd );
	destroy_token( tok );
	close( sesslock_fd );
	sesslock_fd = -1;
	
	return BOOL_TRUE;
}




/*----------------------------------------------------------------------*/
/* inventory_lasttime_level_lessthan					*/
/*                                                                      */
/* Given a token that refers to a file system, and a level, this returns*/
/* the last time when a session of a lesser level was done.             */
/*                                                                      */
/* returns -1 on error.                                                 */
/*----------------------------------------------------------------------*/

bool_t
inv_lasttime_level_lessthan( 
	inv_idbtoken_t  tok,
	u_char level,
	time32_t **tm )
{
	int 	rval;
	assert ( tok != INV_TOKEN_NULL );

	rval =  search_invt( tok, level, (void **) tm,
			 (search_callback_t) tm_level_lessthan );

	return ( rval < 0) ? BOOL_FALSE: BOOL_TRUE;
}





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

bool_t
inv_lastsession_level_lessthan( 
	inv_idbtoken_t 	tok,
	u_char		level,
	inv_session_t 	**ses )
{
	int 	rval;
	assert ( tok != INV_TOKEN_NULL );

	rval = search_invt( tok, level, (void **) ses, 
			  (search_callback_t) lastsess_level_lessthan );

	return ( rval < 0) ? BOOL_FALSE: BOOL_TRUE;

}




/*----------------------------------------------------------------------*/
/*                                                                      */
/*                                                                      */
/* Return FALSE on an error, TRUE if not. If (*ses) is NULL, then the   */
/* search failed.                                                       */
/*----------------------------------------------------------------------*/


bool_t
inv_lastsession_level_equalto( 
	inv_idbtoken_t 	tok,			    
	u_char		level,
	inv_session_t	**ses )
{
	int 	rval;
	assert ( tok != INV_TOKEN_NULL );
	rval = search_invt( tok, level, (void **) ses, 
			  (search_callback_t) lastsess_level_equalto );

	return ( rval < 0) ? BOOL_FALSE: BOOL_TRUE;

}







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

inv_sestoken_t
inv_writesession_open( 
	inv_idbtoken_t tok, 	/* token obtained by inventory_open() */
	uuid_t		*fsid,
	uuid_t		*sesid,
	char		*label,
	u_char		level,
	uint		nstreams,
	time32_t	time,
	char		*mntpt,
	char		*devpath )
{
	invt_session_t *ses;
	int		fd;
	int	rval;
	invt_sescounter_t *sescnt = NULL;
	invt_seshdr_t  	hdr;
	inv_sestoken_t	sestok;

	assert ( tok != INV_TOKEN_NULL );
	assert ( sesid && fsid && mntpt && devpath );

	if ( ! ( tok->d_update_flag & FSTAB_UPDATED ) ) {
		if ( put_fstab_entry( fsid, mntpt, devpath ) < 0 ) {
			printf ("put_fstab_entry :(\n");
			return INV_TOKEN_NULL;
		}
		tok->d_update_flag |= FSTAB_UPDATED;
	}



	ses = (invt_session_t *) calloc( 1, sizeof( invt_session_t ) );

	/* copy the session information to store */
	memcpy( &ses->s_sesid, sesid, sizeof( uuid_t ) );
	memcpy( &ses->s_fsid, fsid, sizeof( uuid_t ) );
	strcpy( ses->s_label, label );	
	strcpy( ses->s_mountpt, mntpt );	
	strcpy( ses->s_devpath, devpath );	
	ses->s_max_nstreams = nstreams;


	/* s_curstream_off will be set in create_session() */

	fd = tok->d_stobj_fd;
	
	assert ( fd > 0 );

	hdr.sh_time = time;
	hdr.sh_level = level;	
	/* sh_streams_off and sh_sess_off will be set in create_session() */
	
	sestok = get_sesstoken( tok );

	/* we need to put the new session in the appropriate place in 
	   storage object. So first find out howmany sessions are there */

	INVLOCK( fd, LOCK_EX );
	if ( GET_SESCOUNTERS( fd, &sescnt) < 0 ) {
		free ( ses );
		free ( sestok );
		INVLOCK( fd, LOCK_UN );
		return INV_TOKEN_NULL;
	}

	/* create the writesession, and get ready for the streams to come 
	   afterwards */
	rval = create_session( sestok, fd, sescnt, ses, &hdr );
	assert (rval > 0);


	INVLOCK( fd, LOCK_UN );
 
	sestok->sd_sesstime = time;

	if ( tok->d_update_flag & NEW_INVINDEX ) {
		if ( put_sesstime( sestok, INVT_STARTTIME ) < 0 ) {
			printf ("put_starttime :(\n");
			return INV_TOKEN_NULL;
		}
		tok->d_update_flag &= ~(NEW_INVINDEX);
	}

	free ( ses );
	free ( sescnt );


	return ( rval < 0 )? INV_TOKEN_NULL: sestok;
}





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


bool_t
inv_writesession_close( inv_sestoken_t tok )
{
	int		rval;
	
	assert ( tok != INV_TOKEN_NULL );

	/* now update end_time in the inv index header */
	rval = put_sesstime( tok, INVT_ENDTIME );

	memset( tok, 0, sizeof( invt_sesdesc_entry_t ) );

	free ( tok );

	return ( rval < 0 ) ? BOOL_FALSE: BOOL_TRUE;

}



/*----------------------------------------------------------------------*/
/* inventory_stream_open                                                */
/*                                                                      */
/* Opens a stream for mediafiles to be put in.                          */
/*----------------------------------------------------------------------*/
inv_stmtoken_t
inv_stream_open(
	inv_sestoken_t tok )
{
	inv_stmtoken_t stok;
	invt_stream_t  stream;
	invt_session_t ses;
	invt_seshdr_t  seshdr;
	int fd;

	assert ( tok != INV_TOKEN_NULL );
	 
	stream.st_nmediafiles = 0;
	stream.st_interrupted = BOOL_FALSE;

	
	/* XXX yukk... make the token descriptors not pointers */
	stok = ( inv_stmtoken_t ) malloc( sizeof( invt_strdesc_entry_t ) );
	
	stok->md_sesstok = tok;
	stok->md_lastmfile = 0;
	
	/* get the session to find out where the stream is going to go */
	fd = tok->sd_invtok->d_stobj_fd; 

	sess_lock();
	
	INVLOCK( fd, LOCK_SH );
	/* get the session header first */
	if ( GET_REC_NOLOCK( fd, &seshdr, sizeof( invt_seshdr_t ),
			     tok->sd_sesshdr_off ) <= 0 ) {
		free ( stok );
		INVLOCK( fd, LOCK_UN );
		sess_unlock();
		return INV_TOKEN_NULL;
	}



	/* XXX Have one func that gives both seshdr and session */
	if ( GET_REC_NOLOCK( fd, &ses, sizeof( invt_session_t ),
			     tok->sd_session_off ) <= 0 ) {
		free ( stok );
		INVLOCK( fd, LOCK_UN );
		sess_unlock();
		return INV_TOKEN_NULL;
	}
	INVLOCK( fd, LOCK_UN );

	if ( ses.s_cur_nstreams < ses.s_max_nstreams ) {
		/* this is where this stream header will be written to */
		stok->md_stream_off = (off64_t) (sizeof( invt_stream_t ) * 
					         ses.s_cur_nstreams )
			                         + seshdr.sh_streams_off;
		ses.s_cur_nstreams++;
				
		/* write it back. this locks and unlocks fd EXclusively */
		if ( PUT_REC( fd, &ses, sizeof( ses ), 
			      tok->sd_session_off ) < 0 ) {
			free ( stok );
			sess_unlock();
			return INV_TOKEN_NULL;
		}
	} else {
		fprintf(stderr, "Cant create more than %d streams. Max'd out..\n",
			ses.s_cur_nstreams );
		free ( stok );
		sess_unlock();
		return INV_TOKEN_NULL;
	}
	sess_unlock();

	stream.st_firstmfile = stream.st_lastmfile = stok->md_stream_off;
	
	/* now put the stream header on to the disk */
	if ( PUT_REC( fd, &stream, sizeof( invt_stream_t ),
		      stok->md_stream_off ) < 0 ) {
		free ( stok );
		return INV_TOKEN_NULL;
	}

	return stok;
}




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

bool_t
inv_stream_close(
		inv_stmtoken_t	tok,
		bool_t wasinterrupted )
{
	invt_stream_t strm;
	int fd = tok->md_sesstok->sd_invtok->d_stobj_fd;
	int rval;
	bool_t dowrite = BOOL_FALSE;

	INVLOCK( fd, LOCK_EX );
	if ((rval = GET_REC_NOLOCK( fd, &strm, sizeof( invt_stream_t ), 
			       tok->md_stream_off )) < 0 )
		goto end;   /* eek :-)   */
	
	if ( strm.st_interrupted != wasinterrupted ) {
		strm.st_interrupted = wasinterrupted;
		dowrite = BOOL_TRUE;
	}

	/* get the last media file to figure out what our last ino was. 
	   we have a pointer to that in the stream token */
	if ( tok->md_lastmfile ){
		if ( strm.st_endino.ino != tok->md_lastmfile->mf_endino.ino ||
		     strm.st_endino.offset != tok->md_lastmfile->mf_endino.offset){
			printf("Warning: endinos dont match ! \n");
			dowrite = BOOL_TRUE;
			strm.st_endino = tok->md_lastmfile->mf_endino;
		}
	}
			
	if (dowrite) {
		rval = PUT_REC_NOLOCK_SEEKCUR( fd, &strm, sizeof( invt_stream_t ),
					       (off64_t) -(sizeof( invt_stream_t )) );
	}
 end:
	INVLOCK( fd, LOCK_UN );

	free ( tok->md_lastmfile );
	memset( tok, 0, sizeof( invt_strdesc_entry_t ) );
	free ( tok );

	return ( rval < 0 ) ? BOOL_FALSE: BOOL_TRUE;
}
 



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

bool_t
inv_put_mediafile( 
	inv_stmtoken_t 	tok, 
	uuid_t 		*moid, 
	char 		*label,
	xfs_ino_t	startino,
	off64_t		startino_offset,
	xfs_ino_t	endino,
	off64_t		endino_offset )
{
	invt_mediafile_t *mf;
	int 		 rval;


	assert ( tok != INV_TOKEN_NULL );
	assert ( tok->md_sesstok->sd_invtok->d_update_flag & FSTAB_UPDATED );
	assert ( tok->md_sesstok->sd_invtok->d_stobj_fd >= 0 );

	mf = (invt_mediafile_t *) calloc( 1, sizeof( invt_mediafile_t ) );
	
	/* copy the media file information */
	memcpy( &mf->mf_moid, moid, sizeof( uuid_t ) );
	strcpy( mf->mf_label, label );	
	mf->mf_startino.ino = startino;
	mf->mf_startino.offset = startino_offset;
	mf->mf_endino.ino = endino;
	mf->mf_endino.offset = endino_offset;
	
	INVLOCK( tok->md_sesstok->sd_invtok->d_stobj_fd, LOCK_EX );
	rval = put_mediafile( tok, mf );
	INVLOCK( tok->md_sesstok->sd_invtok->d_stobj_fd, LOCK_UN );

	/* we dont free the mfile here. we always keep the last mfile
	   around, inside the inv_stmtoken, and when we add a new mfile,
	   we free the previous one. The last one is freed in stream_close()
	   */

	return ( rval < 0 ) ? BOOL_FALSE: BOOL_TRUE;

}



/*----------------------------------------------------------------------*/
/* inv_get_inolist                                                      */
/*                                                                      */
/* This returns all the database files that belong to the inventory, so */
/* that the client (dump) knows that these shouldn't be dumped alongwith*/
/* regular files.                                                       */
/*                                                                      */
/* foreach ( fs in fstab )                                              */
/* 	foreach ( index in InvIndex )                                 */
/*          get                                                         */
/*----------------------------------------------------------------------*/

int
inv_get_inolist(
	inv_inolist_t 	**inolist )
{
	invt_entry_t	*iarr = NULL;
	invt_counter_t	*icnt = NULL;
	int	     	nindices, i;
	struct stat64	statbuf;	
	inv_inolist_t 	*curitem;

#ifdef NOTDEF	
	*inolist = NULL;
	curitem = malloc( sizeof( inv_inolist_t ) );
	
	/* get the array of all indices in the invindex */
	if ( ( nindices = GET_ALLHDRS_N_CNTS( invfd, (void **)&iarr, 
					      (void **)&icnt, 
					      sizeof( invt_entry_t ),
					      sizeof( invt_counter_t )) 
	      ) <= 0 ) {
		return -1;
	}
	free( icnt );
	
	/* attach all the StObjs */
	for (i = nindices - 1; i >= 0; i--) {
		if ( stat64( iarr[i].ie_filename, &statbuf ) < 0 ) {
			perror( iarr[i].ie_filename );
			return -1;
		}
		
		create_inolist_item( curitem, statbuf.st_ino );
	}
	/* The inventory index */
	if ( fstat64( invfd, &statbuf ) ){
		perror( "InvIndex file" );
		return -1;
	}
	create_inolist_item( curitem, statbuf.st_ino );

	/* fstab */
	if ( stat64( INV_FSTAB, &statbuf ) < 0 ) {
			perror( INV_FSTAB );
			return -1;
		}
	create_inolist_item( curitem, statbuf.st_ino );
	
	/* sesslock file */
	if ( stat64( SESSLOCK_FILE, &statbuf ) < 0 ) {
			perror( SESSLOCK_FILE );
			return -1;
		}
	create_inolist_item( curitem, statbuf.st_ino );
#endif

	return 1;
		
}





/*----------------------------------------------------------------------*/
/* inv_get_session                                                      */
/*                                                                      */
/* This is to be called after a write-session is complete, but before it*/
/* is closed. ie. the token must still be valid, and all the streams    */
/* and their mediafiles put in the inventory.                           */
/*                                                                      */
/* On return, the buffer will be filled with all the data pertinent to  */
/* the session referred to by the session token. The application of this*/
/* is to dump the inventory of a session to a media object.             */
/*----------------------------------------------------------------------*/

bool_t
inv_get_session(
	inv_sestoken_t		tok,
	void		      **bufpp,	/* buf to fill */
	size_t		       *bufszp )/* size of that buffer */
{
	assert( tok != INV_TOKEN_NULL );
	assert( tok->sd_invtok );

	/* First get the session header, and the session information. Then
	   we can figure out how much space to allocate */
	
}




#ifdef DEBUG

/* This prints out all the sessions of a filesystem that are in the inventory */
bool_t
inv_DEBUG_printallsessions( 
	inv_idbtoken_t 	tok,			    
	inv_session_t	**ses )
{
	int 	rval;
	rval = search_invt( tok, 0, (void **) ses, 
			  (search_callback_t) DEBUG_displayallsessions );

	return ( rval < 0) ? BOOL_FALSE: BOOL_TRUE;

}

#endif
