/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 1999, 2000
 *	Sleepycat Software.  All rights reserved.
 */

#include "db_config.h"

#ifndef lint
static const char revid[] = "$Id: qam_files.c,v 1.3 2000/10/23 17:51:35 bostic Exp $";
#endif /* not lint */

#ifndef NO_SYSTEM_INCLUDES
#include <sys/types.h>

#include <errno.h>
#include <string.h>
#endif

#include "db_int.h"
#include "db_page.h"
#include "db_shash.h"
#include "db_am.h"
#include "lock.h"
#include "btree.h"
#include "qam.h"
#include "mp.h"

/*
 * __qam_fprobe -- calculate and open extent
 *
 * Calculate which extent the page is in, open and create
 * if necessary.
 *
 * PUBLIC: int __qam_fprobe __P((DB *, db_pgno_t *, DB_MPOOLFILE **, int));
 *
 */
int
__qam_fprobe(dbp, pgnoaddr, mpfp, flags)
	DB *dbp;
	db_pgno_t *pgnoaddr;
	DB_MPOOLFILE **mpfp;
	int flags;
{
	DB_ENV *dbenv;
	QUEUE *qp;
	u_int32_t extid;
	int numext, offset, oldext, openflags, ret;
	char buf[256];

	qp = (QUEUE *)dbp->q_internal;
	if (qp->page_ext == 0) {
		*mpfp = dbp->mpf;
		return (0);
	}
	dbenv = dbp->dbenv;
	ret = 0;

	/*
	 * Need to lock long enough to find the mpf or create the file.
	 * The file cannot go away because we must have a record locked
	 * in that file.
	 */
	MUTEX_THREAD_LOCK(dbp->mutexp);
	extid = (*pgnoaddr - 1) / qp->page_ext;
	offset = extid - qp->low_extent;
	/*
	 * Check to see if the requested extent is outside the range of
	 * extents in the array.  This is true by default if there are
	 * no extents here yet.
	 */
	if (qp->n_extent == 0 ||
	    offset < 0 || (unsigned) offset >= qp->n_extent) {
		oldext = qp->n_extent;
		numext =  qp->hi_extent - qp->low_extent  + 1;
		if (qp->n_extent == 0) {
			/* Start with 4 extents */
			qp->n_extent = 4;
			qp->low_extent = extid;
			offset = 0;
			goto alloc;
		} else if (offset < 0
		    && (unsigned) -offset + numext <= qp->n_extent) {
			/* If we can fit this one in, move the array up */
			memmove(&qp->mpfarray[-offset],
			    qp->mpfarray, numext * sizeof(qp->mpfarray[0]));
			memset(qp->mpfarray,
			    0, -offset * sizeof(qp->mpfarray[0]));
			offset = 0;
		} else {
			/*
			 * Increase the size to at least include the new one
			 * and double it.
			 */
			qp->n_extent += abs(offset);
			qp->n_extent <<= 2;

alloc:			if ((ret = __os_realloc(dbenv,
			    qp->n_extent * sizeof(DB_MPOOLFILE *),
			    NULL, &qp->mpfarray)) != 0)
				goto err;

			if (offset < 0) {
				offset = -offset;
				memmove(&qp->mpfarray[offset], qp->mpfarray,
				    numext * sizeof(qp->mpfarray[0]));
				memset(qp->mpfarray, 0,
				    offset * sizeof(qp->mpfarray[0]));
				memset(&qp->mpfarray[numext + offset], 0,
				     (qp->n_extent - (numext + offset))
				     * sizeof(qp->mpfarray[0]));
				offset = 0;
			}
			else
				memset(&qp->mpfarray[oldext], 0,
				    (qp->n_extent - oldext) *
				    sizeof(qp->mpfarray[0]));
		}
	}

	if (extid < qp->low_extent)
		qp->low_extent = extid;
	if (extid > qp->hi_extent)
		qp->hi_extent = extid;
	if (qp->mpfarray[offset] == NULL) {
		snprintf(buf,
		    sizeof(buf), QUEUE_EXTENT, qp->dir, qp->name, extid);
		openflags = DB_EXTENT;
		if (LF_ISSET(DB_MPOOL_CREATE))
			openflags |= DB_CREATE;
		if (F_ISSET(dbp, DB_AM_RDONLY))
			openflags |= DB_RDONLY;
		qp->finfo.fileid = NULL;
		if ((ret = __memp_fopen(dbenv->mp_handle,
		    NULL, buf, openflags, qp->mode, dbp->pgsize,
		    1, &qp->finfo, &qp->mpfarray[offset])) != 0)
			goto err;
	}

	*mpfp = qp->mpfarray[offset];
	(*pgnoaddr)--;
	*pgnoaddr %= qp->page_ext;
err:
	MUTEX_THREAD_UNLOCK(dbp->mutexp);
	return (ret);
}

/*
 * __qam_fremove -- remove an extent.
 *
 * Calculate which extent the page is in and remove it.  There is no way to
 * remove an extent without probing it first and seeing that is is empty so
 * we assume the mpf entry is present.
 *
 * PUBLIC: int __qam_fremove __P((DB *, db_pgno_t));
 */
int
__qam_fremove(dbp, pgnoaddr)
	DB *dbp;
	db_pgno_t pgnoaddr;
{
	DB_ENV *dbenv;
	DB_MPOOLFILE *mpf;
	QUEUE *qp;
	u_int32_t extid;
	int offset, ret;
	char buf[256], *real_name;

	qp = (QUEUE *)dbp->q_internal;
	dbenv = dbp->dbenv;
	ret = 0;
	real_name = NULL;

	MUTEX_THREAD_LOCK(dbp->mutexp);

	extid = (pgnoaddr - 1) / qp->page_ext;
	offset = extid - qp->low_extent;

	DB_ASSERT(offset >= 0 && (unsigned) offset < qp->n_extent);

	mpf = qp->mpfarray[offset];
	qp->mpfarray[offset] = NULL;
	if ((ret = memp_fsync(mpf)) != 0)
		goto err;
	if ((ret = __memp_fremove(mpf)) != 0)
		goto err;
	if ((ret = memp_fclose(mpf)) != 0)
		goto err;

	/* Find the real name of the file. */
	snprintf(buf, sizeof(buf),
	    QUEUE_EXTENT, qp->dir, qp->name, extid);
	if ((ret = __db_appname(dbenv,
	    DB_APP_DATA, NULL, buf, 0, NULL, &real_name)) != 0)
		goto err;
	if ((ret = __os_unlink(dbenv, real_name)) != 0)
		goto err;

	if (offset == 0) {
		memmove(qp->mpfarray, &qp->mpfarray[1],
		    (qp->hi_extent - qp->low_extent) * sizeof(qp->mpfarray[0]));
		qp->mpfarray[qp->hi_extent - qp->low_extent] = NULL;
		if (qp->low_extent != qp->hi_extent)
			qp->low_extent++;
	} else {
		qp->mpfarray[offset] = NULL;
		if (extid == qp->hi_extent)
			qp->hi_extent--;
	}

err:
	MUTEX_THREAD_UNLOCK(dbp->mutexp);
	if (real_name != NULL)
		__os_freestr(real_name);
	return (ret);
}

/*
 * __qam_fget -- get a page
 *
 * PUBLIC: int __qam_fget __P((DB *, db_pgno_t *, u_int32_t, void *));
 */
int
__qam_fget(dbp, pgnoaddr, flags, addrp)
	DB *dbp;
	db_pgno_t *pgnoaddr;
	u_int32_t flags;
	void *addrp;
{
	DB_MPOOLFILE *mfp;
	db_pgno_t pageno;
	int ret;

	pageno = *pgnoaddr;
	if ((ret = __qam_fprobe(dbp, &pageno, &mfp, flags)) != 0)
		return (ret);

	return (memp_fget(mfp, &pageno, flags | DB_MPOOL_EXTENT, addrp));
}

/*
 * __qam_fput -- put a page
 *
 * PUBLIC: int __qam_fput __P((DB *, db_pgno_t, void *, u_int32_t));
 *
 */
int
__qam_fput(dbp, pageno, addrp, flags)
	DB *dbp;
	db_pgno_t pageno;
	void *addrp;
	u_int32_t flags;
{
	DB_MPOOLFILE *mfp;
	int ret;

	if ((ret = __qam_fprobe(dbp, &pageno, &mfp, flags)) != 0)
		return (ret);

	return (memp_fput(mfp, addrp, flags));
}

/*
 * __qam_sync --
 *	Flush the database cache.
 *
 * PUBLIC: int __qam_sync __P((DB *, u_int32_t));
 */
int
__qam_sync(dbp, flags)
	DB *dbp;
	u_int32_t flags;
{
	DB_MPOOLFILE **mpfp;
	QUEUE *qp;
	QUEUE_FILELIST *filelist;
	u_int32_t i;
	int ret;

	PANIC_CHECK(dbp->dbenv);
	DB_ILLEGAL_BEFORE_OPEN(dbp, "DB->sync");

	if ((ret = __db_syncchk(dbp, flags)) != 0)
		return (ret);

	/* Read-only trees never need to be sync'd. */
	if (F_ISSET(dbp, DB_AM_RDONLY))
		return (0);

	/* If the tree was never backed by a database file, we're done. */
	if (F_ISSET(dbp, DB_AM_INMEM))
		return (0);

	/* Flush any dirty pages from the cache to the backing file. */
	if ((ret = memp_fsync(dbp->mpf)) != 0)
		return (ret);

	/* This will grab all extents. */
	if ((__qam_gen_filelist(dbp, &filelist)) != 0)
		return (ret);

	if (filelist == NULL)
		return (0);

	__os_free(filelist, 0);

	qp = (QUEUE *)dbp->q_internal;
	mpfp = qp->mpfarray;
	for (i = qp->low_extent; i <= qp->hi_extent; i++, mpfp++)
		if (*mpfp != NULL && (ret = memp_fsync(*mpfp)) != 0)
			return (ret);
	return (0);
}

/*
 * __qam_gen_filelist -- generate a list of extent files.
 *
 * PUBLIC: int __qam_gen_filelist __P(( DB *, QUEUE_FILELIST **));
 */
int
__qam_gen_filelist(dbp, filelistp)
	DB *dbp;
	QUEUE_FILELIST **filelistp;
{
	DB_ENV *dbenv;
	QMETA *meta;
	QUEUE *qp;
	QUEUE_FILELIST *fp;
	db_pgno_t i, last, pg;
	int ret;

	dbenv = dbp->dbenv;
	qp = (QUEUE *)dbp->q_internal;
	*filelistp = NULL;
	if (qp->page_ext == 0)
		return (0);

	/* Find out the page number of the last page in the database. */
	i = PGNO_BASE_MD;
	if ((ret = memp_fget(dbp->mpf, &i, 0, &meta)) != 0) {
		(void) dbp->close(dbp, 0);
		return (ret);
	}

	last = QAM_RECNO_PAGE(dbp, meta->start, meta->cur_recno);
	i = QAM_RECNO_PAGE(dbp, meta->start,
	    meta->first_recno < meta->start ? meta->start : meta->first_recno);

	if ((ret = memp_fput(dbp->mpf, meta, 0)) != 0) {
		(void) dbp->close(dbp, 0);
		return (ret);
	}

	if (meta->cur_recno < meta->start)
		return (0);

	/* Allocate the worst case plus 1 for null termination. */
	if ((ret = __os_calloc(dbenv,
	    (last - i + 2), sizeof(QUEUE_FILELIST), filelistp)) != 0)
		return (ret);
	fp = *filelistp;
	for (; i <= last; i += qp->page_ext) {
		pg = i;
		if ((ret = __qam_fprobe(dbp, &pg, &fp->mpf, 0)) != 0) {
			if (ret == ENOENT)
				continue;
			return (ret);
		}
		fp->id = (i - 1) / qp->page_ext;
		fp++;
	}

	return (0);
}
