/*
 * 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/file.h>
#include <afs/venus.h>
#include <afs/cmd.h>
#include <afs/afs_consts.h>

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

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

#ifdef notdef			/* postpone this... */
static int
saferead(fd, addr, osize)
     int fd, osize;
     char *addr;
{
    int rc, size;
    char *ptr;

    if (size == EOF)
	return (EOF);

    ptr = addr;
    rc = osize = size;

    while (rc != EOF) {
	rc = read(fd, ptr, size)) {
	    if (rc == size)
		return osize;
	    else {
		if (errno != EINTR) {
		    perror(pn);
		    exit(-1);
		}
	    }
	}
    }


    static int
      saferead(fd, addr, osize)
    int fd, osize;
    char *addr;
    {
	int rc;

	rc = read(fd, addr, osize);
	if (rc != osize && rc != EOF) {
	    perror(pn);
	    exit(-1);
	}
	return (rc);
    }
#else
#define saferead(fd,addr,siz) read((fd),(addr),(siz))
#endif

static char tspace[1024];	/* chdir can't handle anything bigger, anyway */
/* return a static pointer to a buffer */
static char *
Parent(apath)
     char *apath;
{
    char *tp;
    strcpy(tspace, apath);
    tp = strrchr(tspace, '/');
    if (tp) {
	*tp = 0;
    } else
	strcpy(tspace, ".");
    return tspace;
}

#ifdef notdef

/* this function returns TRUE (1) if the file is in AFS, otherwise false (0) */
static int
InAFS(apath)
     char *apath;
{
    struct ViceIoctl blob;
    afs_int32 code;

    blob.in_size = 0;
    blob.out_size = AFS_PIOCTL_MAXSIZE;
    blob.out = space;

    code = pioctl(apath, VIOC_FILE_CELL_NAME, &blob, 1);
    if (code) {
	if ((errno == EINVAL) || (errno == ENOENT))
	    return 0;
    }
    return 1;
}

QuickPrintStatus(status, name)
     struct VolumeStatus *status;
     char *name;
{
    double QuotaUsed = 0.0;
    double PartUsed = 0.0;
    int WARN = 0;

    if (status->MaxQuota != 0) {
	QuotaUsed =
	    ((((double)status->BlocksInUse) / status->MaxQuota) * 100.0);
    } else {
	/* no limit */
    }
    PartUsed =
	(100.0 -
	 ((((double)status->PartBlocksAvail) / status->PartMaxBlocks) *
	  100.0));
}


static int
ListQuotaCmd(struct cmd_syndesc *as, void *arock)
{
    afs_int32 code;
    struct ViceIoctl blob;
    struct cmd_item *ti;
    struct VolumeStatus *status;
    char *name;

    for (ti = as->parms[0].items; ti; ti = ti->next) {
	/* once per file */
	blob.out_size = AFS_PIOCTL_MAXSIZE;
	blob.in_size = 0;
	blob.out = space;
	code = pioctl(ti->data, VIOCGETVOLSTAT, &blob, 1);
	if (code) {
	    Die(code, ti->data);
	    continue;
	}
	status = (struct VolumeStatus *)space;
	name = (char *)status + sizeof(*status);
	QuickPrintStatus(status, name);
    }
    return 0;
}
#endif /* notdef */

int gc = 1, fromvers = 1, tovers = 2;
char *pathname = NULL, *defaultpath = "/usr/afs/db/vl.DB0";

usage()
{
    fprintf(stderr, "usage: %s ", pn);
    fprintf(stderr, "[-name <pathname>] [-help]\n");
}

getargs(argc, argv)
     int argc;
     char **argv;
{
    int pos, i;
    pos = 0;

    for (i = 1; i < argc; i++) {
	if (!argv[i])
	    break;
	else if (*(argv[i]) != '-') {	/* positional params */
	    if (!pathname)
		pathname = argv[i];
	    else {
		fprintf(stderr, "%s: Too many parameters!\n");
		usage();
		exit(-1);
	    }
	} else			/* keyword params */
	    switch (argv[i][1]) {

	    case 't':		/* -to */
		fprintf(stderr, "%s: can't specify version with this tool!\n",
			pn);
		exit(-1);
		break;

	    case 'f':		/* -from */
		fprintf(stderr, "%s: can't specify version with this tool!\n",
			pn);
		break;

	    case 'n':		/* -name */
		if (pathname) {
		    fprintf(stderr,
			    "%s: -name specified (or implied) twice!\n", pn);
		    exit(-1);
		}
		pathname = argv[++i];
		break;

	    case 'h':		/* -help */
		usage();
		exit(0);
		break;

	    case 'g':		/* -gc == No GC */
		gc = 0;
		break;

	    default:
		usage();
		exit(EINVAL);
	    }
    }

    if (!pathname)
	pathname = defaultpath;
}

#include "AFS_component_version_number.c"

main(argc, argv)
     int argc;
     char **argv;
{
    afs_int32 code;
    int old, new, rc;
    short uvers;
    char ubik[80];		/* space for some ubik header */

    union {
	struct vlheader_1 header1;
	struct vlheader_2 header2;
    } oldheader, newheader;	/* large enough for either */

    union {
	struct vlentry_1 entry1;
	struct vlentry_2 entry2;
    } vlentry;


    getargs(argc, argv);

    /* 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 */
    if (!(old = open(pathname, O_RDONLY))) {
	perror(pn);
	exit(-1);
    }

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

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

    if (fromvers == 0) {	/* not set */
	lseek(old, 64, L_SET);
	read(old, &fromvers, sizeof(int));
	fromvers = ntohl(fromvers);
	lseek(old, 0, L_SET);	/* go back to beginning */
    }

    /* skip the UBIK data */
    read(old, ubik, 64);
#ifdef notdef
    uvers = ntohs(uvers);
    uvers += 10;		/* hey, if you screw with the VLDB, you lose */
    uvers = htons(uvers);
#endif
    write(new, ubik, 64);

    readheader(old, fromvers, &oldheader);
    convert_header(new, fromvers, tovers, &oldheader, &newheader);
    while ((rc = read(old, &vlentry, sizeof(struct vlentry_1))) && rc != EOF) {
	convert_vlentry(new, fromvers, tovers, &oldheader, &newheader,
			&vlentry);
    }


    if (gc)
	rewrite_header(new, tovers, &newheader);

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

    close(new);
    rename(tempname, pathname);
    exit(0);
}

readheader(fd, version, addr)
     int fd;
     int version;
     char *addr;
{
    if (version == 1) {
	read(fd, addr, sizeof(struct vlheader_2));	/* it's not a bug, it's SAS */
    } else if (version == 2) {
	read(fd, addr, sizeof(struct vlheader_2));
    } else
	return EINVAL;

    return 0;
}

/* SAS special */
convert_header(fd, fromv, tov, fromaddr, toaddr)
     int fd, fromv, tov;
     char *fromaddr, *toaddr;
{
    struct vlheader_2 *tvp1;
    struct vlheader_2 *tvp2;
    int i, j, diff;

    memcpy(toaddr, fromaddr, sizeof(struct vlheader_2));
    tvp2 = (struct vlheader_2 *)toaddr;
    tvp2->vital_header.vldbversion = htonl(2);

    write(fd, tvp2, sizeof(struct vlheader_2));

    /* for garbage-collecting... */
    if (gc)
	for (i = 0; i < 254; i++)
	    tvp2->IpMappedAddr[i] = 0;

    return 0;
}

static int
convert_vlentry(new, fromvers, tovers, oldheader, newheader, vlentry)
     int new, fromvers, tovers;
     struct vlheader_1 *oldheader, *newheader;	/* close enough */
     struct vlentry_1 *vlentry;	/* 1 and 2 are identical */
{
    int diff, i, s;

#ifndef DEBUG
    if (fromvers != tovers) {	/* only supports 1 and 2 currently */
#endif

	diff =
	    (tovers ==
	     1 ? sizeof(struct vlheader_1) : sizeof(struct vlheader_2))
	    - (fromvers ==
	       1 ? sizeof(struct vlheader_1) : sizeof(struct vlheader_2));

	for (i = 0; i < 3; i++)
	    vlentry->nextIdHash[i] =
		htonl(ntohl(vlentry->nextIdHash[i]) + diff);

	vlentry->nextNameHash = htonl(ntohl(vlentry->nextNameHash) + diff);

#ifndef DEBUG
    } else {
	;			/* no change, we're just in it for the GC */
    }
#endif

    for (i = 0; i < 8; i++) {
	s = vlentry->serverNumber[i];
	if (s != 255) {
	    if (s > 254) {
		fprintf(stderr,
			"%s: Too Many Servers (%d) for this version!\n", pn,
			s + 1);
		exit(-1);
	    } else {
		newheader->IpMappedAddr[s] = oldheader->IpMappedAddr[s];
	    }
	}
    }
    write(new, vlentry, sizeof(struct vlentry_2));

    return;
}

static int
rewrite_header(new, tovers, newheader)
     int new, tovers;
     char *newheader;
{
    int pos;

    pos = lseek(new, 64, L_SET);	/* leave room for ubik */

    if (pos == -1) {
	perror(pn);
	fprintf(stderr, "%s: no garbage collection\n", pn);
	return;
    } else if (pos != 64) {
	fprintf(stderr, "%s: Can't rewind: no garbage collection\n", pn);
	return;
    }

    if (tovers = 1) {
	write(new, newheader, sizeof(struct vlheader_1));
    } else {
	write(new, newheader, sizeof(struct vlheader_2));
    }

    return;
}
