/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 1996, 1997, 1998, 1999
 *	Sleepycat Software.  All rights reserved.
 */
#include "db_config.h"

#ifndef lint
static const char sccsid[] = "@(#)hash_upgrade.c	11.7 (Sleepycat) 10/20/99";
#endif /* not lint */

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

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

#include "db_int.h"
#include "db_page.h"
#include "db_swap.h"
#include "hash.h"

static int CDB___ham_upgrade5 __P((DB *, int, char *, DB_FH *));

/*
 * CDB___ham_upgrade --
 *	Upgrade Hash databases.
 *
 * PUBLIC: int CDB___ham_upgrade __P((DB *, int, char *, DB_FH *, char *));
 */
int
CDB___ham_upgrade(dbp, swapped, real_name, fhp, mbuf)
	DB *dbp;
	int swapped;
	char *real_name, *mbuf;
	DB_FH *fhp;
{
	DB_ENV *dbenv;
	int ret;

	dbenv = dbp->dbenv;

	/* Check the version. */
	switch (((DBMETA *)mbuf)->version) {
	case 4:
	case 5:
		if ((ret = CDB___ham_upgrade5(dbp, swapped, real_name, fhp)) != 0)
			return (ret);
		/* FALLTHROUGH */
	case 6:
		break;
	default:
		CDB___db_err(dbenv, "%s: unsupported hash version: %lu",
		    real_name, (u_long)((DBMETA *)mbuf)->version);
		return (DB_OLD_VERSION);
	}
	return (0);
}

/*
 * CDB___ham_upgrade5 --
 *      Upgrade the database from version 4/5 to version 6.
 */
static int
CDB___ham_upgrade5(dbp, swapped, real_name, fhp)
	DB *dbp;
	int swapped;
	char *real_name;
	DB_FH *fhp;
{
	DB_ENV *dbenv;
	ssize_t n;
	u_int32_t *o_spares, *n_spares, version;
	u_int32_t fillf, maxb, nelem;
	int i, non_zero, ret;
	u_int8_t nbuf[256], *new, obuf[256];

	dbenv = dbp->dbenv;

	if (dbp->db_feedback != NULL)
		dbp->db_feedback(dbp, DB_UPGRADE, 0);

	/*
	 * Seek to the beginning of the file and read the metadata page.  We
	 * read 256 bytes, which is larger than any access method's metadata
	 * page.
	 */
	if ((ret = CDB___os_seek(fhp, 0, 0, 0, 0, DB_OS_SEEK_SET)) != 0)
		return (ret);
	if ((ret = CDB___os_read(fhp, obuf, sizeof(obuf), &n)) != 0)
		return (ret);

	/*
	 * Upgrade a Hash meta-data page.
	 *	Version 5:	byte range:	Version 6:	byte range:
	 *	lsn		00-07		lsn		00-07
	 *	pgno		08-11		pgno		08-11
	 *	magic		12-15		magic		12-15
	 *	version		16-19		version		16-19
	 *	pagesize	20-23		pagesize	20-23
	 *	ovfl_point	24-27		unused		   24
	 *					type		   25
	 *					unused		26-27
	 *	last_freed	28-31		free		28-31
	 *	max_bucket	32-35		flags		32-35
	 *	high_mask	36-39		uid		36-55
	 *	low_mask	40-43		max_bucket	56-59
	 *	ffactor		44-47		high_mask	60-63
	 *	nelem		48-51		low_mask	64-67
	 *	h_charkey	52-55		ffactor		68-71
	 *	flags		56-59		nelem		72-75
	 *	spares		60-187		h_charkey	76-79
	 *	uid		188-207		spares		80-207
	 *
	 */

	/*
	 * The first 32 bytes are similar.  The only change is the version
	 * and that we removed the ovfl_point and have the page type now.
	 */
	memcpy(nbuf, obuf, 32);

	/* Update the version. */
	version = 6;
	if (swapped)
		M_32_SWAP(version);
	memcpy(nbuf + 16, &version, sizeof(u_int32_t));

	/* Assign unused and type fields. */
	new = nbuf + 24;
	*new++ = '\0';
	*new++ = P_HASHMETA;
	*new++ = '\0';
	*new = '\0';

	/* Move flags */
	memcpy(nbuf + 32, obuf + 56, 4);

	/* Copy: max_bucket, high_mask, low-mask, ffactor, nelem, h_charkey */
	memcpy(nbuf + 56, obuf + 32, 24);

	/*
	 * There was a bug in 2.X versions where the nelem could go negative.
	 * In general, this is considered "bad."  If it does go negative
	 * (that is, very large and positive), we'll die trying to dump and
	 * load this database.  So, let's see if we can fix it here.
	 */
	memcpy(&nelem, nbuf + 72, sizeof(u_int32_t));
	memcpy(&fillf, nbuf + 68, sizeof(u_int32_t));
	memcpy(&maxb, nbuf + 56, sizeof(u_int32_t));
	if (swapped) {
		M_32_SWAP(nelem);
		M_32_SWAP(fillf);
		M_32_SWAP(maxb);
	}

	if ((fillf != 0 && fillf * maxb < 2 * nelem) ||
	    (fillf == 0 && nelem > 0x8000000)) {
		nelem = 0;
		memcpy(nbuf + 72, &nelem, sizeof(u_int32_t));
	}

	/*
	 * We now have to convert the spares array.  The old spares array
	 * contained the total number of extra pages allocated prior to
	 * the bucket that begins the next doubling.  The new spares array
	 * contains the page number of the first bucket in the next doubling
	 * MINUS the bucket number of that bucket.
	 */
	o_spares = (u_int32_t *)(obuf + 60);
	n_spares = (u_int32_t *)(nbuf + 80);
	non_zero = 0;
	n_spares[0] = 1;
	for (i = 1; i < NCACHED; i++) {
		if (swapped)
			M_32_SWAP(o_spares[i -1]);
		non_zero = non_zero || o_spares[i - 1] != 0;
		if (o_spares[i - 1] == 0 && non_zero)
			n_spares[i] = 0;
		else
			n_spares[i] = 1 + o_spares[i - 1];
	}

	if (swapped)
		for (i = 0; i < NCACHED; i++)
			M_32_SWAP(n_spares[i]);

					/* Replace the unique ID. */
	if ((ret = CDB___os_fileid(dbenv, real_name, 1, nbuf + 36)) != 0)
		return (ret);

	if ((ret = CDB___os_seek(fhp, 0, 0, 0, 1, DB_OS_SEEK_SET)) != 0)
		return (ret);
	if ((ret = CDB___os_write(fhp, nbuf, 256, &n)) != 0)
		return (ret);
	if ((ret = CDB___os_fsync(fhp)) != 0)
		return (ret);

	if (dbp->db_feedback != NULL)
		dbp->db_feedback(dbp, DB_UPGRADE, 100);

	return (0);
}
