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

#include "config.h"

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


/*----------------------------------------------------------------------*/
/*  init_idb                                                            */
/*                                                                      */
/*  Returns -1 if we are done with initialization, the fd if not.	*/
/*  The idb_token indicates whether there was an error or not, if the 	*/
/*  return value is -1.                                                 */
/*----------------------------------------------------------------------*/

int
init_idb(void *pred, inv_predicate_t bywhat, inv_oflag_t forwhat,
	  inv_idbtoken_t *tok)
{
	char fname[INV_STRLEN];
	char uuname[INV_STRLEN];
	int fd;

	*tok = INV_TOKEN_NULL;
	/* make sure INV_DIRPATH exists, and is writable */
	if (make_invdirectory(forwhat) < 0)
		return I_DONE;

	/* come up with the unique file suffix that refers to this
	   filesystem */
	if (fstab_get_fname(pred, uuname, bywhat, forwhat) < 0) {
		return I_DONE;
	}

	(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, INV_OFLAG(forwhat))) == -1) {
		if (errno != ENOENT) {
			INV_PERROR (fname);
		} else if (forwhat == INV_SEARCH_N_MOD) {
			*tok = idx_create(fname, forwhat);
		} else {
			/* this happens when the inv is empty and the user
			   wants to do a search. this is legal - not an error */
			return I_EMPTYINV;
		}
		return I_DONE; /* we are done whether token's NULL or not */
	}

	/* we've got to do more housekeeping stuff. so signal that */
	return fd;
}





inv_idbtoken_t
get_token(int invfd, int stobjfd)
{
	invt_desc_entry_t *desc;

	desc = (invt_desc_entry_t *) malloc
		(sizeof(invt_desc_entry_t));

	desc->d_invindex_fd = invfd;
	desc->d_stobj_fd  = stobjfd;
	desc->d_update_flag = 0;
	desc->d_invindex_off = -1;

	return (inv_idbtoken_t) desc; /* yukky, but ok for the time being */
}






void
destroy_token(inv_idbtoken_t tok)
{
	free ((invt_desc_entry_t *) tok);
}



inv_sestoken_t
get_sesstoken(inv_idbtoken_t tok)
{
	inv_sestoken_t stok;

	stok = (inv_sestoken_t) malloc(sizeof(invt_sesdesc_entry_t));
	stok->sd_invtok = tok;
	stok->sd_session_off = stok->sd_sesshdr_off = -1;
	stok->sd_sesstime = (time32_t) 0;
	return stok;
}






/*---------------------------------------------------------------------------*/
/*                                                                           */
/*                                                                           */
/*                                                                           */
/*---------------------------------------------------------------------------*/
bool_t
invmgr_query_all_sessions (
	uuid_t		  *fsidp,
	void 		  *inarg,
	void 		  **outarg,
	search_callback_t func)
{
	invt_counter_t *cnt;
	invt_fstab_t *arr;
	int numfs, i, fd, invfd;
	char fname[INV_STRLEN];
	int result;
	inv_oflag_t forwhat = INV_SEARCH_ONLY;
	void *objectfound;
	bool_t ret = BOOL_FALSE;

	/* if on return, this is still null, the search failed */
	*outarg = NULL;
	assert(inarg);

	fd = fstab_getall(&arr, &cnt, &numfs, forwhat);
	/* special case missing file: ok, outarg says zero */
	if (fd < 0 && errno == ENOENT) {
		return BOOL_TRUE;
	}
	if (fd < 0 || numfs <= 0) {
		mlog(MLOG_NORMAL | MLOG_INV, _("INV: Error in fstab\n"));
		return ret;
	}

	close(fd);

	for (i = 0; i < numfs; i++) {
		if (fstab_get_fname(&arr[i].ft_uuid, fname,
				     (inv_predicate_t)INV_BY_UUID,
				     forwhat) < 0) {
			mlog(MLOG_NORMAL | MLOG_INV, _(
			     "INV: Cant get inv-name for uuid\n")
			     );
			continue;
		}
		strcat(fname, INV_INVINDEX_PREFIX);
		invfd = open(fname, INV_OFLAG(forwhat));
		if (invfd < 0) {
			mlog(MLOG_NORMAL | MLOG_INV, _(
			     "INV: Open failed on %s\n"),
			     fname
			     );
			continue;
		}
		result = search_invt(fsidp, invfd, inarg, &objectfound, func);
		close(invfd);

		/* if error return BOOL_FALSE */
		if (result < 0) {
			return BOOL_FALSE;
		} else if ((result == 1) && *outarg) {
			/* multiple entries found,  more info needed */
			*outarg = NULL;
			return BOOL_TRUE;
		} else if (result == 1) {
			*outarg = objectfound;
			ret = BOOL_TRUE;
		}
	}

	/* return val indicates if there was an error or not. *buf
	   says whether the search was successful */
	return ret;
}




/*----------------------------------------------------------------------*/
/* search_invt                                                          */
/*                                                                      */
/* Used by the toplevel (inv layer) to do common searches on the inven-*/
/* tory. Caller supplies a callback routine that performs the real      */
/* comparison/check.                                                    */
/*----------------------------------------------------------------------*/

int
search_invt(
	uuid_t			*fsidp,
	int			invfd,
	void			*arg,
	void			**buf,
	search_callback_t	do_chkcriteria)
{

	int 		fd, i;
	invt_entry_t	*iarr = NULL;
	invt_counter_t	*icnt = NULL;
	int	     	nindices;


	if (invfd == I_EMPTYINV)
		return -1;

	/* set the caller's buffer pointer to NULL initially.
	 * if no session found, the caller will expect to see
	 * NULL.
	 */
	*(char **)buf = NULL;

	if ((nindices = GET_ALLHDRS_N_CNTS(invfd, (void **)&iarr,
					      (void **)&icnt,
					      sizeof(invt_entry_t),
					      sizeof(invt_counter_t))
	      ) <= 0) {
		return -1;
	}

	free(icnt);

	/* we need to get all the invindex headers and seshdrs in reverse
	   order */
	for (i = nindices - 1; i >= 0; i--) {
		int			nsess, j;
		invt_sescounter_t 	*scnt = NULL;
		invt_seshdr_t		*harr = NULL;
		bool_t                  found;

		fd = open (iarr[i].ie_filename, O_RDONLY);
		if (fd < 0) {
			INV_PERROR(iarr[i].ie_filename);
			continue;
		}
		INVLOCK(fd, LOCK_SH);

		/* Now see if we can find the session we're looking for */
		if ((nsess = GET_ALLHDRS_N_CNTS_NOLOCK(fd, (void **)&harr,
						  (void **)&scnt,
						  sizeof(invt_seshdr_t),
						 sizeof(invt_sescounter_t))
		     ) < 0) {
			INV_PERROR(iarr[i].ie_filename);
			INVLOCK(fd, LOCK_UN);
			close(fd);
			continue;
		}
		free (scnt);

		for (j = nsess - 1; j >= 0; j--) {
			invt_session_t ses;

			/* fd is kept locked until we return from the
			   callback routine */

			/* Check to see if this session has been pruned
			 * by xfsinvutil before checking it.
			 */
			if (harr[j].sh_pruned) {
				continue;
			}

			/* if we need to check the fs uuid's and they don't
			 * match or we fail to get the session record,
			 * then keep looking
			 */
			if (fsidp) {
				int ret = GET_REC_NOLOCK(fd, &ses,
						         sizeof(invt_session_t),
						         harr[j].sh_sess_off);
				if (ret < 0)
					return ret;
				if (uuid_compare(ses.s_fsid, *fsidp))
					continue;
			}

			found = (*do_chkcriteria)(fd, &harr[j], arg, buf);
			if (!found) continue;

			/* we found what we need; just return */
			INVLOCK(fd, LOCK_UN);
			close(fd);
			free(harr);

			return found; /* == -1 or 1 */
		}

		INVLOCK(fd, LOCK_UN);
		close(fd);
	}

	return 0;
}



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


int
invmgr_inv_print(
	int 		invfd,
	invt_pr_ctx_t 	*prctx)
{

	int 		fd, i;
	invt_entry_t	*iarr = NULL;
	invt_counter_t	*icnt = NULL;
	int	     	nindices;
	uint           ref = 0;

	if (invfd == I_EMPTYINV)
		return 0;


	if ((nindices = GET_ALLHDRS_N_CNTS(invfd, (void **)&iarr,
					      (void **)&icnt,
					      sizeof(invt_entry_t),
					      sizeof(invt_counter_t))
	      ) <= 0) {
		return -1;
	}

	free(icnt);

	if (prctx->invidx) {
		idx_DEBUG_printinvindices(iarr, (uint) nindices);
		free(iarr);
		return (0);
	}



	for (i = 0; i < nindices; i++) {
		int 			nsess;
		invt_sescounter_t 	*scnt = NULL;
		invt_seshdr_t		*harr = NULL;
		int                     s;

		fd = open (iarr[i].ie_filename, O_RDONLY);
		if (fd < 0) {
			INV_PERROR(iarr[i].ie_filename);
			continue;
		}
		INVLOCK(fd, LOCK_SH);

		/* Now see if we can find the session we're looking for */
		if ((nsess = GET_ALLHDRS_N_CNTS_NOLOCK(fd, (void **)&harr,
						  (void **)&scnt,
						  sizeof(invt_seshdr_t),
						 sizeof(invt_sescounter_t))
		     ) < 0) {
			INV_PERROR(iarr[i].ie_filename);
			INVLOCK(fd, LOCK_UN);
			close(fd);
			continue;
		}
		free (scnt);
		for(s = 0; s < nsess; s++) {
			/* fd is kept locked until we return from the
			   callback routine */

			/* Check to see if this session has been pruned
			 * by xfsinvutil before returning it.
			 */
			if (harr[s].sh_pruned) {
				continue;
			}

			(void)DEBUG_displayallsessions(fd, &harr[s],
						        ref++, prctx);
		}

		INVLOCK(fd, LOCK_UN);
		close(fd);
	}

	free (iarr);
	return 0;
}



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


int
invmgr_inv_check(
	int invfd)
{

	int 		fd, i;
	invt_entry_t	*iarr = NULL;
	invt_counter_t	*icnt = NULL;
	int	     	nindices;

	if (invfd == I_EMPTYINV)
		return 0;

	if ((nindices = GET_ALLHDRS_N_CNTS(invfd, (void **)&iarr,
					      (void **)&icnt,
					      sizeof(invt_entry_t),
					      sizeof(invt_counter_t))
	      ) <= 0) {
		return -1;
	}

	free(icnt);


	for (i = 0; i < nindices; i++) {
		int 			nsess;
		invt_sescounter_t 	*scnt = NULL;
		invt_seshdr_t		*harr = NULL;
		int                     s;

		fd = open (iarr[i].ie_filename, O_RDONLY);
		if (fd < 0) {
			INV_PERROR(iarr[i].ie_filename);
			continue;
		}
		INVLOCK(fd, LOCK_SH);

		/* Now see if we can find the session we're looking for */
		if ((nsess = GET_ALLHDRS_N_CNTS_NOLOCK(fd, (void **)&harr,
						  (void **)&scnt,
						  sizeof(invt_seshdr_t),
						 sizeof(invt_sescounter_t))
		     ) < 0) {
			INV_PERROR(iarr[i].ie_filename);
			INVLOCK(fd, LOCK_UN);
			close(fd);
			continue;
		}
		free (scnt);

		if ((iarr[i].ie_timeperiod.tp_start != harr[0].sh_time) ||
		    (iarr[i].ie_timeperiod.tp_end != harr[nsess-1].sh_time)) {
			printf(_("INV: Check %d failed.\n"), i+1);
			printf(_("invidx (%d)\t%d - %d\n"),
			       i+1,
			       iarr[i].ie_timeperiod.tp_start,
			       iarr[i].ie_timeperiod.tp_end);
			for(s = 0; s < nsess; s++) {
				printf(_("tm (%d)\t%d\n"), s, harr[s].sh_time);
			}
		}
		else {
			printf(_("INV: Check %d out of %d succeeded\n"),
			       i+1, nindices);
		}
		INVLOCK(fd, LOCK_UN);
		close(fd);
	}

	return 0;
}


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

/* ARGSUSED */
bool_t
tm_level_lessthan(int fd, invt_seshdr_t *hdr, void *arg,
		   void **tm)
{
	u_char level = *(u_char *)arg;
	*tm = NULL;
	if (IS_PARTIAL_SESSION(hdr))
		return 0;
	if (hdr->sh_level < level) {
#ifdef INVT_DEBUG
		mlog(MLOG_DEBUG | MLOG_INV, "$ found level %d < %d\n", hdr->sh_level,
		     level);
#endif
		*tm = calloc(1, sizeof(time32_t));
		memcpy(*tm, &hdr->sh_time, sizeof(time32_t));
		return 1;
	}
	return 0;
}




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

bool_t
lastsess_level_lessthan(int fd, invt_seshdr_t *hdr, void *arg,
			 void **buf)
{
	u_char level = *(u_char *)arg;
	*buf = NULL;
	if (IS_PARTIAL_SESSION(hdr))
		return 0;
	if (hdr->sh_level < level) {
#ifdef INVT_DEBUG
		mlog(MLOG_DEBUG | MLOG_INV, "$ found (ses) level %d < %d \n",
		     hdr->sh_level, level);
#endif
		return stobj_make_invsess(fd, (inv_session_t **) buf, hdr);
	}
	return 0;

}



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

bool_t
lastsess_level_equalto(int fd, invt_seshdr_t *hdr,
		       void *arg, void **buf)
{
	u_char level = *(u_char *)arg;
	*buf = NULL;
	if (IS_PARTIAL_SESSION(hdr))
		return 0;
	if (hdr->sh_level == level) {
#ifdef INVT_DEBUG
	mlog(MLOG_DEBUG | MLOG_INV, "$ found (ses) level %d == %d \n", hdr->sh_level,
	     level);
#endif
		return stobj_make_invsess(fd, (inv_session_t **) buf, hdr);
	}
	return 0;


}






/*----------------------------------------------------------------------*/
/* insert_session                                                       */
/*                                                                      */
/* insert a session that may or may not be already in the inventory.    */
/* this is used in reconstructing the database.                         */
/*----------------------------------------------------------------------*/
bool_t
insert_session(invt_sessinfo_t *s)
{
	inv_idbtoken_t tok = INV_TOKEN_NULL;
	int invfd, stobjfd = -1;
	invt_idxinfo_t	  idx;
	bool_t		  ret = BOOL_FALSE;
	inv_oflag_t       forwhat = INV_SEARCH_N_MOD;

	/* initialize the inventory */
	if ((invfd = init_idb ((void *) s->ses->s_fsid,
				  (inv_predicate_t) INV_BY_UUID,
 				  forwhat,
				  &tok)) < 0) {
		if (tok == INV_TOKEN_NULL) {
#ifdef INVT_DEBUG
			mlog(MLOG_DEBUG | MLOG_INV, "INV: insert_session: init_db "
			      "failed\n");
#endif
			return BOOL_FALSE;
		}

		invfd = tok->d_invindex_fd;
		close(tok->d_stobj_fd);
		destroy_token(tok);
	}

	/* at this point we know that invindex has at least one entry

	   First we need to find out if this session is in the inventory
	   already. To find the storage object that can possibly
	   contain this session, it suffices to sequentially search the
	   inventory indices of this filesystem for the particular invt-entry
	 */
	INVLOCK(invfd, LOCK_EX);
	idx.invfd = invfd;
	stobjfd = idx_find_stobj(&idx, s->seshdr->sh_time);
	if (stobjfd < 0) {
		INVLOCK(invfd, LOCK_UN);
		free(idx.icnt);
		free(idx.iarr);
		return BOOL_FALSE;
	}

	/* Now put the session in the storage-object */
	INVLOCK(stobjfd, LOCK_EX);
	if ((stobj_insert_session(&idx, stobjfd, s) < 0) ||
		(idx_recons_time (s->seshdr->sh_time, &idx) < 0))
			ret = BOOL_TRUE;

	INVLOCK(stobjfd, LOCK_UN);
	INVLOCK(invfd, LOCK_UN);

	free(idx.icnt);
	free(idx.iarr);

	if (ret) return BOOL_FALSE;

	/* make sure the fstab is uptodate too */
	if (fstab_put_entry(&s->ses->s_fsid, s->ses->s_mountpt, s->ses->s_devpath,
			      forwhat) < 0)
		return BOOL_FALSE;

	/* and we are done */
	return BOOL_TRUE;

}



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

int
make_invdirectory(inv_oflag_t forwhat)
{
	struct stat64 st;
	char path[PATH_MAX];
	char *p;

	p = strcpy(path, INV_DIRPATH);

	if (stat64(path, &st) == 0)
		return 1;

	if (forwhat == INV_SEARCH_ONLY || errno != ENOENT)
		return -1;

	do {
		p++;
		if (*p == '/') {
			*p = '\0';
			if (mkdir(path, (mode_t)0755) < 0) {
				if (errno != EEXIST) {
					INV_PERROR(path);
					return -1;
				}
			}
			*p = '/';
		}
	} while (*p);

	if (mkdir(path, (mode_t)0755) < 0) {
		if (errno != EEXIST) {
			INV_PERROR(path);
			return -1;
		}
	}

	mlog(MLOG_VERBOSE | MLOG_INV, _("%s created\n"), path);
	return 1;
}

#ifdef NOTDEF

bool_t
invmgr_lockinit(void)
{
	if (invlock_fd == -1) {
		if ((invlock_fd = open(INV_LOCKFILE,
					O_RDONLY | O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
			INV_PERROR(INV_LOCKFILE);
			return BOOL_FALSE;
		}
		fchmod (invlock_fd, INV_PERMS);
	}
	return BOOL_TRUE;
}


bool_t
invmgr_trylock(invt_mode_t mode)
{
	int md;
	assert(invlock_fd >= 0);

	md = (mode == INVT_RECONSTRUCT) ? LOCK_EX: LOCK_SH;

	if (INVLOCK(invlock_fd, md | LOCK_NB) < 0)
		return BOOL_FALSE;

	return BOOL_TRUE;
}

void
invmgr_unlock(void)
{
	assert(invlock_fd >= 0);

	INVLOCK(invlock_fd, LOCK_UN);

}

#endif

