/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 *
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

#include <afsconfig.h>
#include <afs/param.h>


#include <afs/stds.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/file.h>
#include <string.h>

#include <netinet/in.h>
#include <afs/venus.h>
#include <afs/cmd.h>
#include <afs/afsutil.h>
#include <afs/fileutil.h>

#include "vlserver.h"
#include "cnvldb.h"		/* CHANGEME! */

#define	BADSERVERID	255	/* XXX */


static char pn[] = "cnvldb";
static char tempname[] = "XXnewvldb";
static int MaxServers[3] = { 30, 254, 254 };	/* max server # permitted in this version */

static afs_int32 Conv4to3(afs_uint32 addr);

static void convert_vlentry(int, int, int, struct vlheader_1 *,
			    struct vlheader_1 *, struct vlentry_1 *);
static void rewrite_header(int, int, void *);
static void readheader(int fd, int version, void *addr);
static int readentry(int fd, int version, void *addr);
static void printentry(int version, void *addr);

static char tspace[1024];	/* chdir can't handle anything bigger, anyway */

void read_mhentries(afs_uint32 mh_addr, int oldfd);
void convert_mhentries(int oldfd, int newfd, struct vlheader_2 *header, int fromver, int tover);

static int convert_header(int ofd, int fd, int fromv, int tov, void *fromaddr,
			  void *toaddr);

/* return a static pointer to a buffer */
static char *
Parent(const char *apath)
{
    char *tp;
    strcpy(tspace, apath);
    tp = strrchr(tspace, '/');
    if (tp) {
	*tp = 0;
    } else
	strcpy(tspace, ".");
    return tspace;
}

int oldpos = 0;
int fromvers = 0, tovers = 0, showversion = 0;
afs_uint32 mhaddr;
afs_int32 dbsize;
const char *pathname = NULL;
const char *dbPath;

static int
handleit(struct cmd_syndesc *as, void *arock)
{
    int w, old, new, rc, dump = 0, fromv = 0;
    char ubik[80];		/* space for some ubik header */
    union {
	struct vlheader_1 header1;
	struct vlheader_2 header2;
	struct vlheader_3 header3;
    } oldheader, newheader;	/* large enough for either */

    union {
	struct vlentry_1 entry1;
	struct vlentry_2 entry2;
	struct vlentry_3 entry3;
	char mhinfo_block[VL_ADDREXTBLK_SIZE];
    } xvlentry;

    pathname = (as->parms[2].items ? as->parms[2].items->data : dbPath);	/* -name */
    showversion = (as->parms[3].items ? 1 : 0);	/* -showversion */
    dump = (as->parms[4].items ? 1 : 0);	/* -dumpvldb */
    fromvers = (as->parms[1].items ? atoi(as->parms[1].items->data) : 0);	/* -fromversion */
    tovers = (as->parms[0].items ? atoi(as->parms[0].items->data) : 0);	/* -toversion */

    /* should stat() the old vldb, get its size, and see if there's */
    /* room for another.  It might be in AFS, so check the quota, too */
    old = open(pathname, O_RDONLY);
    if (old < 0) {
	perror(pn);
	exit(-1);
    }

    /* Read the version */
    lseek(old, 64, L_SET);
    read(old, &fromv, sizeof(int));
    fromv = ntohl(fromv);
    if ((fromv < 1) || (fromv > 4)) {
	fprintf(stderr, "%s", pn);
	fprintf(stderr, ": Unrecognized VLDB version %d.\n", fromv);
	exit(-1);
    }

    /* Sequentially read the database converting the entries as we go */
    lseek(old, 0, L_SET);
    read(old, ubik, 64);
    readheader(old, fromv, &oldheader);
    if (fromv == 1) {
	dbsize = ntohl(oldheader.header1.vital_header.eofPtr);
	fromv = ntohl(oldheader.header1.vital_header.vldbversion);
	mhaddr = 0;
    } else if (fromv == 2) {
	dbsize = ntohl(oldheader.header2.vital_header.eofPtr);
	fromv = ntohl(oldheader.header2.vital_header.vldbversion);
	mhaddr = 0;
    } else {
	int pos;

	dbsize = ntohl(oldheader.header3.vital_header.eofPtr);
	fromv = ntohl(oldheader.header3.vital_header.vldbversion);
	mhaddr = ntohl(oldheader.header3.SIT);

	/* Read the multihomed extent blocks in */
	pos = oldpos;
	read_mhentries(mhaddr, old);

	/* Position back to this after header */
	lseek(old, pos + 64, L_SET);
	oldpos = pos;
    }

    if (showversion || dump) {
	if (showversion)
	    fprintf(stdout, "%s has a version of %d\n", pathname, fromv);
	if (dump) {
	    while (oldpos < dbsize) {
		rc = readentry(old, fromv, &xvlentry);
		if ((rc == 0) || (rc == EOF))
		    break;
		printentry(fromv, &xvlentry);
	    }
	}
	exit(0);
    }

    if (!fromvers) {		/* not set */
	fromvers = fromv;
    } else if (fromvers != fromv) {
	fprintf(stdout,
		"%s has a version of %d while the -fromversion specified was %d - aborting\n",
		pathname, fromv, fromvers);
	exit(0);
    }

    if ((fromvers < 1) || (fromvers > 4)) {
	fprintf(stderr, "%s", pn);
	fprintf(stderr, ": VLDB version %d is not supported.\n", fromvers);
	fprintf(stderr, "%s", pn);
	fprintf(stderr, ": Only versions 1-4 are currently supported.\n");
	exit(-1);
    }

    if (!tovers)
	tovers = fromvers + 1;

    if (tovers < 1 || tovers > 4) {
	fprintf(stderr, "%s", pn);
	fprintf(stderr, ": VLDB version %d is not supported.\n", tovers);
	fprintf(stderr, "%s", pn);
	fprintf(stderr, ": Only versions 1 - 4 are currently supported.\n");
	exit(-1);
    }

    if (mhaddr && (tovers < 3)) {
	fprintf(stderr, "%s", pn);
	fprintf(stderr, ": Cannot convert. VLDB contains multihome info.\n");
	exit(-1);
    }

    /* OK! let's get down to business... */

    if (chdir(Parent(pathname))) {
	perror(pn);
	exit(-1);
    }

    new = open(tempname, O_WRONLY | O_CREAT | O_TRUNC, 0600);
    if (new < 0) {
	perror(pn);
	exit(-1);
    }

    /* Write the UBIK data */
    w = write(new, ubik, 64);
    if (w != 64) {
	printf("Write of ubik header failed %d; error %u\n", w, errno);
	exit(1);
    }

    /* Because we know that all the vldb entries are the same size and type we
     * can just read them sequentially, fiddle with the fields, and write
     * them out again.  If we invent a vldb format that has different
     * types of entries, then we're going to have to invent new logic for
     * converting the vldb-- we'll probably have to chase down the various
     * linked lists in turn, doing lseeks and the like.
     */

    convert_header(old, new, fromvers, tovers, &oldheader, &newheader);
    while (oldpos < dbsize) {
	rc = readentry(old, fromvers, &xvlentry);
	if ((rc == 0) || (rc == EOF))
	    break;
	convert_vlentry(new, fromvers, tovers,
			(struct vlheader_1 *)&oldheader,
			(struct vlheader_1 *)&newheader,
			(struct vlentry_1 *)&xvlentry);
    }

    /* We have now finished sequentially reading and writing the database.
     * Now randomly offset into database and update multihome entries.
     */
    convert_mhentries(old, new, (struct vlheader_2 *)&newheader,
		      fromvers, tovers);
    rewrite_header(new, tovers, &newheader);

    close(old);
    if (fsync(new)) {
	perror(pn);
	exit(-1);
    }
    close(new);

    renamefile(tempname, pathname);
    sleep(5);
    exit(0);
}


static void
readheader(int fd, int version, void *addr)
{
    int hdrsize, size = 0;

    oldpos = 0;
    if (version == 1)
	hdrsize = sizeof(struct vlheader_1);
    else
	hdrsize = sizeof(struct vlheader_2);

    size = read(fd, addr, hdrsize);
    if (size > 0)
	oldpos += size;

    return;
}

static int
readentry(int fd, int version, void *addr)
{
    int rc, rc1;
    struct vlentry_3 *vl3p = (struct vlentry_3 *)addr;
    int toread;
    char *caddr = (char *)addr;

    toread =
	((version ==
	  1) ? sizeof(struct vlentry_1) : sizeof(struct vlentry_2));
    rc = read(fd, addr, toread);
    if (rc != toread)
	printf("Partial read of vlentry at pos %u: %d\n", oldpos, rc);
    if (rc > 0)
	oldpos += rc;

    /* Read a mhblock entry if there is one */
    if ((rc > 0) && (vl3p->flags == VLCONTBLOCK)) {
	if (!mhaddr)		/* Remember first mh block */
	    mhaddr = oldpos - rc;

	rc1 = read(fd, &caddr[rc], VL_ADDREXTBLK_SIZE - rc);
	if (rc1 != VL_ADDREXTBLK_SIZE - rc)
	    printf("Partial read of mhblock at pos %u: %d\n", oldpos + rc,
		   rc1);
	if (rc1 > 0) {
	    oldpos += rc1;
	    rc += rc1;
	}
    }

    return rc;
}

static void
printentry(int version, void *addr)
{
    struct vlentry_2 *vl2p = (struct vlentry_2 *)addr;
    struct vlentry_3 *vl3p = (struct vlentry_3 *)addr;
    int i;

    /* Don't print anything if the entry is a mh info block */
    if (vl3p->flags == VLCONTBLOCK) {
	return;
    }

    if (version == 1 || version == 2) {
	printf("%s\t%5d [%10d:%10d:%10d]%8X%8d\n", vl2p->name, vl2p->spares3,
	       vl2p->volumeId[0], vl2p->volumeId[1], vl2p->volumeId[2],
	       vl2p->flags, vl2p->LockAfsId);
	printf("\t%8d%8d%8d [%7d%7d%7d]%7d [%4d%4d%4d%4d][%4d%4d%4d%4d]\n",
	       vl2p->LockTimestamp, vl2p->cloneId, vl2p->spares0,
	       vl2p->nextIdHash[0], vl2p->nextIdHash[1], vl2p->nextIdHash[2],
	       vl2p->nextNameHash, vl2p->serverNumber[0],
	       vl2p->serverNumber[1], vl2p->serverNumber[2],
	       vl2p->serverNumber[3], vl2p->serverPartition[0],
	       vl2p->serverPartition[1], vl2p->serverPartition[2],
	       vl2p->serverPartition[3]);
	printf("\t[%4d%4d%4d%4d]\n", vl2p->serverFlags[0],
	       vl2p->serverFlags[1], vl2p->serverFlags[2],
	       vl2p->serverFlags[3]);
    } else {			/* if (version >= 3) */

	if (vl3p->flags == VLFREE)
	    return;
	printf("%s\tPos=%" AFS_SIZET_FMT " NextIdHash=[%d:%d:%d] NextNameHash=%d\n",
	       vl3p->name, (oldpos - sizeof(struct vlentry_3)),
	       vl3p->nextIdHash[0], vl3p->nextIdHash[1], vl3p->nextIdHash[2],
	       vl3p->nextNameHash);
	printf("\tRW=%u RO=%u BK=%u CL=%u flags=0x%X lockBy=%d lockTime=%u\n",
	       vl3p->volumeId[0], vl3p->volumeId[1], vl3p->volumeId[2],
	       vl3p->cloneId, vl3p->flags, vl3p->LockAfsId,
	       vl3p->LockTimestamp);
	for (i = 0; i < OMAXNSERVERS; i++) {
	    if ((vl3p->serverNumber[i] & 0xff) != 0xff) {
		printf("\tServer=%d Partition=%d flags=%X\n",
		       vl3p->serverNumber[i], vl3p->serverPartition[i],
		       vl3p->serverFlags[i]);
	    }
	}
    }
    return;
}

int readmhentries = 0;
struct extentaddr *base[VL_MAX_ADDREXTBLKS];

/* Read the multihome extent blocks in. Check if they are good by
 * verifying their address is not pass the EOF and the flags are good.
 * If it's not good, then don't read the block in.
 */
void
read_mhentries(afs_uint32 mh_addr, int oldfd)
{
    afs_uint32 sit, a;
    afs_int32 code;
    int j;

    if (readmhentries)
	return;
    readmhentries = 1;

    /* Initialize base pointers */
    for (j = 0; j < VL_MAX_ADDREXTBLKS; j++)
	base[j] = 0;

    if (!mh_addr)
	return;

    /* Check if the first extent block is beyond eof. If
     * it is, it's not real.
     */
    if (mh_addr > dbsize - VL_ADDREXTBLK_SIZE)
	return;

    /* Now read the first mh extent block */
    code = lseek(oldfd, mh_addr + 64, L_SET);
    if (code < 0) {
	perror("seek MH block");
	exit(1);
    }
    base[0] = (struct extentaddr *)malloc(VL_ADDREXTBLK_SIZE);
    if (!base[0]) {
	perror("malloc1");
	exit(1);
    }
    code = read(oldfd, (char *)base[0], VL_ADDREXTBLK_SIZE);
    if (code != VL_ADDREXTBLK_SIZE) {
	perror("read MH block");
	free(base[0]);
	base[0] = 0;
	exit(1);
    }

    /* Verify that this block is the right one */
    if (ntohl(base[0]->ex_flags) != VLCONTBLOCK) {	/* check if flag is correct */
	free(base[0]);
	base[0] = 0;
	return;
    }

    /* The first block contains pointers to the other extent blocks.
     * Check to see if the pointers are good and read them in if they are.
     */
    a = mh_addr;
    for (j = 1; j < VL_MAX_ADDREXTBLKS; j++) {
	if (!base[0]->ex_contaddrs[j])
	    continue;

	sit = ntohl(base[0]->ex_contaddrs[j]);

	/* Every time we allocate a new extent block, it is allocated after
	 * the previous ones. But it must be before the EOF.
	 */
	if ((sit < (a + VL_ADDREXTBLK_SIZE))
	    || (sit > dbsize - VL_ADDREXTBLK_SIZE)) {
	    continue;
	}

	/* Read the extent block in */
	sit += 64;
	code = lseek(oldfd, sit, L_SET);
	if (code < 0) {
	    perror("seek MH block");
	    exit(1);
	}
	base[j] = (struct extentaddr *)malloc(VL_ADDREXTBLK_SIZE);
	if (!base[j]) {
	    perror("malloc1");
	    exit(1);
	}
	code = read(oldfd, (char *)base[j], VL_ADDREXTBLK_SIZE);
	if (code != VL_ADDREXTBLK_SIZE) {
	    perror("read MH block");
	    exit(1);
	}

	/* Verify that this block knows its an extent block */
	if (ntohl(base[j]->ex_flags) != VLCONTBLOCK) {
	    free(base[j]);
	    base[j] = 0;
	    continue;
	}

	/* The extent block passed our tests */
	a = ntohl(base[0]->ex_contaddrs[j]);
    }
}

/* Follow the SIT pointer in the header (mhaddr) to the multihomed
 * extent blocks and verify that the pointers are good. And fix.
 * Then convert the multihomed addresses to single address if we
 * are converting back from version 4.
 *
 * Before this can be called, the routine read_mhentries must be called.
 */
void
convert_mhentries(int oldfd, int newfd, struct vlheader_2 *header,
		  int fromver, int tover)
{
    afs_int32 code;
    int i, j, modified = 0, w;
    afs_uint32 raddr, addr;
    struct extentaddr *exp;
    int basei, index;

    /* Check if the header says the extent block exists. If
     * it does, then read_mhentries should have read it in.
     */
    if (mhaddr && !base[0]) {
	printf("Fix Bad base extent block pointer\n");
	header->SIT = mhaddr = 0;
    } else if (mhaddr && base[0]) {

	if ((ntohl(header->SIT) != mhaddr) && (tover == 4)) {
	    printf
		("Fix pointer to first base extent block. Was 0x%x, now 0x%x\n",
		 ntohl(header->SIT), mhaddr);
	    header->SIT = htonl(mhaddr);
	}

	/* Check if the first block points to itself. If not, then fix it */
	if (ntohl(base[0]->ex_contaddrs[0]) != mhaddr) {
	    printf("Fix bad pointer in base extent block: Base 0\n");
	    base[0]->ex_contaddrs[0] = htonl(mhaddr);
	    modified = 1;
	}

	/* The first block contains pointers to the other extent blocks.
	 * Check to see if the pointers are good.
	 */
	for (j = 1; j < VL_MAX_ADDREXTBLKS; j++) {
	    /* Check if the base extent block says the extent blocks exist.
	     * If it does, then read_mhentries should have read it in.
	     */
	    if (base[0]->ex_contaddrs[j] && !base[j]) {
		printf("Fix bad pointer in base extent block: Base %d\n", j);
		base[0]->ex_contaddrs[j] = 0;
		modified = 1;
	    }
	}

	/* Now write out the base extent blocks if it changed */
	if (modified) {
	    code = lseek(newfd, mhaddr + 64, L_SET);
	    if (code < 0) {
		perror("seek MH Block");
		exit(1);
	    }
	    w = write(newfd, (char *)base[0], VL_ADDREXTBLK_SIZE);
	    if (w != VL_ADDREXTBLK_SIZE) {
		perror("write MH Block");
		exit(1);
	    }
	}
    }

    /* If we are converting from version 4 to version 3, then
     * translate any multihome ptrs in the IpMappedAddr array
     * to true IP addresses.
     */
    if ((fromver == 4) && (tover == 3)) {
	/* Step through the fileserver addresses in the VLDB header
	 * and convert the pointers back to IP addresses.
	 */
	for (i = 0; i < 254; i++) {
	    addr = ntohl(header->IpMappedAddr[i]);
	    if (addr && ((addr & 0xff000000) == 0xff000000)) {
		basei = (addr >> 16) & 0xff;
		index = addr & 0xffff;

		if ((basei >= VL_ADDREXTBLK_SIZE) || !base[basei]) {
		    fprintf(stderr,
			    "Warning: mh entry %d has no IP address; ignored!!\n",
			    i);
		    header->IpMappedAddr[i] = 0;
		    continue;
		}
		exp = &base[basei][index];

		/* For now return the first ip address back */
		for (j = 0; j < VL_MAXIPADDRS_PERMH; j++) {
		    if (exp->ex_addrs[j]) {
			raddr = ntohl(exp->ex_addrs[j]);
			break;
		    }
		}
		if (j >= VL_MAXIPADDRS_PERMH) {
		    fprintf(stderr,
			    "Warning: mh entry %d has no ip address; ignored!!\n",
			    i);
		    raddr = 0;
		} else {
		    printf
			("Multi-homed addr: converting to single ip address %d.%d.%d.%d\n",
			 (raddr >> 24 & 0xff), (raddr >> 16 & 0xff),
			 (raddr >> 8 & 0xff), (raddr & 0xff));
		}
		header->IpMappedAddr[i] = htonl(raddr);
	    }
	}
	header->SIT = mhaddr = 0;	/* mhinfo block has been removed */

	/* Now step through the hash tables in header updating them.
	 * Because we removed the mh info blocks and some entries they
	 * point to may have changed position.
	 * The VolnameHash
	 */
	for (i = 0; i < 8191; i++) {
	    header->VolnameHash[i] = Conv4to3(header->VolnameHash[i]);
	}
	/* The VolidHash */
	for (i = 0; i < 3; i++) {
	    for (j = 0; j < 8191; j++) {
		header->VolidHash[i][j] = Conv4to3(header->VolidHash[i][j]);
	    }
	}

	/* Update eofptr to take into account the removal of the mhinfo blocks */
	header->vital_header.eofPtr = htonl(Conv4to3(dbsize));
    }
}


int
convert_header(int ofd, int fd, int fromv, int tov, void *fromaddr,
	       void *toaddr)
{
    struct vlheader_1 *tvp1;
    struct vlheader_2 *tvp2;
    int i, j, diff, w;

    if (fromv == 1) {
	if (tov == 1) {
	    memcpy(toaddr, fromaddr, sizeof(struct vlheader_1));
	    tvp1 = (struct vlheader_1 *)toaddr;

	    w = write(fd, tvp1, sizeof(struct vlheader_1));
	    if (w != sizeof(struct vlheader_1)) {
		printf("Write of header failed %d; error %u\n", w, errno);
		exit(1);
	    }

	    /* for garbage-collecting... */
	    for (i = 0; i < 31; i++)
		tvp1->IpMappedAddr[i] = 0;

	} else if (tov == 2 || tov == 3) {
	    tvp1 = (struct vlheader_1 *)fromaddr;
	    tvp2 = (struct vlheader_2 *)toaddr;
	    memset(tvp2, 0, sizeof(struct vlheader_2));
	    tvp2->vital_header.vldbversion = htonl(tov);
	    tvp2->vital_header.headersize = htonl(sizeof(struct vlheader_2));
	    diff =
		ntohl(tvp2->vital_header.headersize) -
		ntohl(tvp1->vital_header.headersize);
	    if (ntohl(tvp1->vital_header.freePtr))
		tvp2->vital_header.freePtr =
		    htonl(ntohl(tvp1->vital_header.freePtr) + diff);
	    if (ntohl(tvp1->vital_header.eofPtr))
		tvp2->vital_header.eofPtr =
		    htonl(ntohl(tvp1->vital_header.eofPtr) + diff);
	    tvp2->vital_header.allocs = tvp1->vital_header.allocs;
	    tvp2->vital_header.frees = tvp1->vital_header.frees;
	    tvp2->vital_header.MaxVolumeId = tvp1->vital_header.MaxVolumeId;
	    for (i = 0; i < 3; i++)
		tvp2->vital_header.totalEntries[i] =
		    tvp1->vital_header.totalEntries[i];

	    for (i = 0; i < 31; i++)
		tvp2->IpMappedAddr[i] = tvp1->IpMappedAddr[i];

	    for (i = 0; i < 8191; i++) {
		if (ntohl(tvp1->VolnameHash[i]))
		    tvp2->VolnameHash[i] =
			htonl(ntohl(tvp1->VolnameHash[i]) + diff);
	    }

	    for (i = 0; i < 3; i++) {
		for (j = 0; j < 8191; j++) {
		    if (ntohl(tvp1->VolidHash[i][j]))
			tvp2->VolidHash[i][j] =
			    htonl(ntohl(tvp1->VolidHash[i][j]) + diff);
		}
	    }

	    w = write(fd, tvp2, sizeof(struct vlheader_2));
	    if (w != sizeof(struct vlheader_2)) {
		printf("Write of header failed %d; error %u\n", w, errno);
		exit(1);
	    }

	    /* for garbage-collecting... */
	    for (i = 0; i < 31; i++)
		tvp2->IpMappedAddr[i] = 0;
	} else
	    return EINVAL;
    } else if (fromv == 2 || fromv == 3 || fromv == 4) {
	if (tov == 2 || tov == 3 || tov == 4) {
	    memcpy(toaddr, fromaddr, sizeof(struct vlheader_2));
	    tvp2 = (struct vlheader_2 *)toaddr;
	    tvp2->vital_header.vldbversion = htonl(tov);
	    w = write(fd, tvp2, sizeof(struct vlheader_2));
	    if (w != sizeof(struct vlheader_2)) {
		printf("Write of header failed %d; error %u\n", w, errno);
		exit(1);
	    }

	} else if (tov == 1) {
	    tvp2 = (struct vlheader_2 *)fromaddr;
	    tvp1 = (struct vlheader_1 *)toaddr;
	    memset(tvp1, 0, sizeof(struct vlheader_1));
	    tvp1->vital_header.vldbversion = htonl(1);
	    tvp1->vital_header.headersize = htonl(sizeof(struct vlheader_1));
	    diff =
		ntohl(tvp1->vital_header.headersize) -
		ntohl(tvp2->vital_header.headersize);
	    if (ntohl(tvp2->vital_header.freePtr))
		tvp1->vital_header.freePtr =
		    htonl(ntohl(tvp2->vital_header.freePtr) + diff);
	    if (ntohl(tvp2->vital_header.eofPtr))
		tvp1->vital_header.eofPtr =
		    htonl(ntohl(tvp2->vital_header.eofPtr) + diff);
	    tvp1->vital_header.allocs = tvp2->vital_header.allocs;
	    tvp1->vital_header.frees = tvp2->vital_header.frees;
	    tvp1->vital_header.MaxVolumeId = tvp2->vital_header.MaxVolumeId;
	    for (i = 0; i < 3; i++)
		tvp1->vital_header.totalEntries[i] =
		    tvp2->vital_header.totalEntries[i];

	    for (i = 0; i < 31; i++)
		tvp1->IpMappedAddr[i] = tvp2->IpMappedAddr[i];

	    for (i = 0; i < 8191; i++) {
		if (ntohl(tvp2->VolnameHash[i]))
		    tvp1->VolnameHash[i] =
			htonl(ntohl(tvp2->VolnameHash[i]) + diff);
	    }

	    for (i = 0; i < 3; i++) {
		for (j = 0; j < 8191; j++) {
		    if (ntohl(tvp2->VolidHash[i][j]))
			tvp1->VolidHash[i][j] =
			    htonl(ntohl(tvp2->VolidHash[i][j]) + diff);
		}
	    }

	    w = write(fd, tvp1, sizeof(struct vlheader_1));
	    if (w != sizeof(struct vlheader_2)) {
		printf("Write of header failed %d; error %u\n", w, errno);
		exit(1);
	    }

	    /* for garbage-collecting... */
	    for (i = 0; i < 31; i++)
		tvp1->IpMappedAddr[i] = 0;
	} else
	    return EINVAL;
    } else
	return EINVAL;
    return 0;
}


/* Convert an address pointer to a vlentry from version 4 to version 3.
 * This involves checking if the address is after any of the four
 * MH block and if it is, subtract the size of the MH block.
 *
 * In going from version 4 to 3, the mh blocks go away and all entries
 * move up in their place. The adresses then need to be updated.
 *
 * Before this can be called, the routine read_mhentries must be called.
 */
static afs_int32
Conv4to3(afs_uint32 addr)
{
    afs_uint32 raddr;
    int i;

    if (!base[0] || !addr)
	return (addr);

    raddr = addr;
    for (i = 0; i < VL_MAX_ADDREXTBLKS; i++) {
	if (base[i] && base[0]->ex_contaddrs[i]
	    && (addr > base[0]->ex_contaddrs[i]))
	    raddr -= VL_ADDREXTBLK_SIZE;
    }

    return (raddr);
}

/* this only works because the vlheader struct is essentially the same
 * from version 1 to version 2 -- that is, the first bunch of fields
 * aren't any more or any larger, so they match up pretty well.
*/

static void
convert_vlentry(int new, int fromvers, int tovers,
		struct vlheader_1 *oldheader, struct vlheader_1 *newheader,
		struct vlentry_1 *vlentryp)
{
    int diff, i, s, w;
    struct vlentry_3 *vl3p = (struct vlentry_3 *)vlentryp;

    /* For mh information blocks,
     * If going to version 4 or greater, keep the mh info block.
     * Otherwise, don't keep it (version 3 and earlier don't have them).
     */
    if (vl3p->flags == VLCONTBLOCK) {
	if (tovers >= 4) {
	    w = write(new, vlentryp, VL_ADDREXTBLK_SIZE);
	    if (w != VL_ADDREXTBLK_SIZE) {
		printf("Write of mh info block failed %d; error %u\n", w,
		       errno);
		exit(1);
	    }
	}
	return;
    }

    if (fromvers == 2 && tovers == 3) {
	struct vlentry_3 vl;

	vl.volumeId[0] = vlentryp->volumeId[0];
	vl.volumeId[1] = vlentryp->volumeId[1];
	vl.volumeId[2] = vlentryp->volumeId[2];
	vl.flags = vlentryp->flags;
	vl.LockAfsId = vlentryp->LockAfsId;
	vl.LockTimestamp = vlentryp->LockTimestamp;
	vl.cloneId = vlentryp->cloneId;
	vl.nextIdHash[0] = vlentryp->nextIdHash[0];
	vl.nextIdHash[1] = vlentryp->nextIdHash[1];
	vl.nextIdHash[2] = vlentryp->nextIdHash[2];
	vl.nextNameHash = vlentryp->nextNameHash;
	memcpy(vl.name, vlentryp->name, 65);
	for (i = 0; i < 8; i++) {
	    vl.serverNumber[i] = vlentryp->serverNumber[i];
	    vl.serverPartition[i] = vlentryp->serverPartition[i];
	    vl.serverFlags[i] = vlentryp->serverFlags[i];
	}
	for (; i < 13; i++)
	    vl.serverNumber[i] = vl.serverPartition[i] = vl.serverFlags[i] =
		BADSERVERID;
	w = write(new, &vl, sizeof(struct vlentry_3));
	if (w != sizeof(struct vlentry_3)) {
	    printf("Write of entry failed %d; error %u\n", w, errno);
	    exit(1);
	}

	return;
    } else if (fromvers == 3 && tovers == 2) {
	struct vlentry_2 vl;
	struct vlentry_3 *xnvlentry = (struct vlentry_3 *)vlentryp;

	memset(&vl, 0, sizeof(struct vlentry_2));
	vl.volumeId[0] = xnvlentry->volumeId[0];
	vl.volumeId[1] = xnvlentry->volumeId[1];
	vl.volumeId[2] = xnvlentry->volumeId[2];
	vl.flags = xnvlentry->flags;
	vl.LockAfsId = xnvlentry->LockAfsId;
	vl.LockTimestamp = xnvlentry->LockTimestamp;
	vl.cloneId = xnvlentry->cloneId;
	for (i = 0; i < 3; i++) {
	    if (ntohl(xnvlentry->nextIdHash[i]))
		vl.nextIdHash[i] = xnvlentry->nextIdHash[i];
	}
	if (ntohl(xnvlentry->nextNameHash))
	    vl.nextNameHash = xnvlentry->nextNameHash;
	memcpy(vl.name, xnvlentry->name, 65);
	for (i = 0; i < 8; i++) {
	    vl.serverNumber[i] = xnvlentry->serverNumber[i];
	    vl.serverPartition[i] = xnvlentry->serverPartition[i];
	    vl.serverFlags[i] = xnvlentry->serverFlags[i];
	}
	w = write(new, &vl, sizeof(struct vlentry_2));
	if (w != sizeof(struct vlentry_2)) {
	    printf("Write of entry failed %d; error %u\n", w, errno);
	    exit(1);
	}
	return;
    } else if (fromvers == 3 && tovers == 1) {
	struct vlentry_1 vl;
	struct vlentry_3 *xnvlentry = (struct vlentry_3 *)vlentryp;

	diff =
	    (tovers ==
	     1 ? sizeof(struct vlheader_1) : sizeof(struct vlheader_2))
	    - (fromvers ==
	       1 ? sizeof(struct vlheader_1) : sizeof(struct vlheader_2));
	memset(&vl, 0, sizeof(struct vlentry_1));
	vl.volumeId[0] = xnvlentry->volumeId[0];
	vl.volumeId[1] = xnvlentry->volumeId[1];
	vl.volumeId[2] = xnvlentry->volumeId[2];
	vl.flags = xnvlentry->flags;
	vl.LockAfsId = xnvlentry->LockAfsId;
	vl.LockTimestamp = xnvlentry->LockTimestamp;
	vl.cloneId = xnvlentry->cloneId;
	for (i = 0; i < 3; i++) {
	    if (ntohl(xnvlentry->nextIdHash[i]))
		vl.nextIdHash[i] =
		    htonl(ntohl(xnvlentry->nextIdHash[i]) + diff);
	}
	if (ntohl(xnvlentry->nextNameHash))
	    vl.nextNameHash = htonl(ntohl(xnvlentry->nextNameHash) + diff);

	memcpy(vl.name, xnvlentry->name, 65);
	for (i = 0; i < 8; i++) {
	    vl.serverNumber[i] = xnvlentry->serverNumber[i];
	    vl.serverPartition[i] = xnvlentry->serverPartition[i];
	    vl.serverFlags[i] = xnvlentry->serverFlags[i];
	}
	for (i = 0; i < 8; i++) {
	    s = xnvlentry->serverNumber[i];
	    if (s != 255) {
		if (s > MaxServers[tovers - 1]) {
		    fprintf(stderr,
			    "%s: Too Many Servers (%d) for this version!\n",
			    pn, s + 1);
		    exit(-1);
		} else
		    newheader->IpMappedAddr[s] = oldheader->IpMappedAddr[s];
	    }
	}
	w = write(new, &vl, sizeof(struct vlentry_1));
	if (w != sizeof(struct vlentry_1)) {
	    printf("Write of entry failed %d; error %u\n", w, errno);
	    exit(1);
	}
	return;
    } else if (fromvers == 4 && tovers == 3) {
	struct vlentry_3 vl;
	/* We are converting from version 4 to 3. In this conversion, mh info
	 * blocks go away and all vlentries after them move up in the vldb file.
	 * When this happens, the linked list pointers need to be updated.
	 */
	memcpy(&vl, vlentryp, sizeof(vl));
	for (i = 0; i < 3; i++) {
	    vl.nextIdHash[i] = Conv4to3(vl.nextIdHash[i]);
	}
	vl.nextNameHash = Conv4to3(vl.nextNameHash);

	w = write(new, &vl, sizeof(vl));
	if (w != sizeof(vl)) {
	    printf("Write of entry failed %d; error %u\n", w, errno);
	    exit(1);
	}
	return;
    }

    if (tovers == 1) {
	w = write(new, vlentryp, sizeof(struct vlentry_1));
	if (w != sizeof(struct vlentry_1)) {
	    printf("Write of entry failed %d; error %u\n", w, errno);
	    exit(1);
	}
    } else if (tovers == 2) {
	w = write(new, vlentryp, sizeof(struct vlentry_2));
	if (w != sizeof(struct vlentry_2)) {
	    printf("Write of entry failed %d; error %u\n", w, errno);
	    exit(1);
	}
    } else if (tovers == 3 || tovers == 4) {
	w = write(new, vlentryp, sizeof(struct vlentry_3));
	if (w != sizeof(struct vlentry_3)) {
	    printf("Write of entry failed %d; error %u\n", w, errno);
	    exit(1);
	}
    } else {
	perror(pn);
	fprintf(stderr,
		"Skipping vlentry write - db corrupted - bad toversion %d\n",
		tovers);
    }

    return;
}

static void
rewrite_header(int new, int tovers, void *newheader)
{
    int pos, w, towrite;

    pos = lseek(new, 64, L_SET);	/* leave room for ubik */
    if (pos == -1) {
	perror(pn);
	fprintf(stderr, "%s: no garbage colection\n", pn);
	return;
    } else if (pos != 64) {
	fprintf(stderr, "%s: Can't rewind: no garbage collection\n", pn);
	return;
    }

    towrite =
	((tovers ==
	  1) ? sizeof(struct vlheader_1) : sizeof(struct vlheader_2));
    w = write(new, newheader, towrite);
    if (w != towrite) {
	printf("Write of entry failed %d; error %u\n", w, errno);
	exit(1);
    }

    return;
}


#include "AFS_component_version_number.c"

int
main(int argc, char **argv)
{
    struct cmd_syndesc *ts;
    afs_int32 code;

    ts = cmd_CreateSyntax("initcmd", handleit, NULL, "optional");
    cmd_AddParm(ts, "-to", CMD_SINGLE, CMD_OPTIONAL, "goal version");
    cmd_AddParm(ts, "-from", CMD_SINGLE, CMD_OPTIONAL, "current version");
    cmd_AddParm(ts, "-path", CMD_SINGLE, CMD_OPTIONAL, "pathname");
    cmd_AddParm(ts, "-showversion", CMD_FLAG, CMD_OPTIONAL,
		"Just display version of current vldb");
    cmd_AddParm(ts, "-dumpvldb", CMD_FLAG, CMD_OPTIONAL,
		"display all vldb entries");

#ifdef DEBUG
    cmd_AddParm(ts, "-noGC", CMD_FLAG, CMD_OPTIONAL,
		"Don't do garbage collection");
#endif /* DEBUG */

    dbPath = AFSDIR_SERVER_VLDB_FILEPATH;

    code = cmd_Dispatch(argc, argv);
    exit(code);
}
