/*
 * 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
 */

/* TBD
 *	ht_lookupEntry - tape ids
 *	Truncate Tape - tape id's
 *	usetape - tape id's
 */

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


#ifdef AFS_NT40_ENV
#include <winsock2.h>
#else
#include <netinet/in.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/resource.h>
#endif

#include <string.h>
#include <sys/types.h>
#include <afs/stds.h>
#include <afs/bubasics.h>
#include <stdio.h>
#include <lock.h>
#include <ubik.h>
#include <lwp.h>
#include <rx/xdr.h>
#include <rx/rx.h>
#include <rx/rxkad.h>
#include <des.h>
#include <afs/cellconfig.h>
#include <afs/auth.h>
#include <errno.h>
#include "budb.h"
#include "budb_errs.h"
#include "database.h"
#include "budb_internal.h"
#include "error_macros.h"
#include "globals.h"
#include "afs/audit.h"
#include <afs/afsutil.h>

#undef min
#undef max


extern struct ubik_dbase *BU_dbase;
extern struct afsconf_dir *BU_conf;	/* for getting cell info */

afs_int32 AddVolume(struct rx_call *, struct budb_volumeEntry *);
afs_int32 AddVolumes(struct rx_call *, struct budb_volumeList *);
afs_int32 CreateDump(struct rx_call *, struct budb_dumpEntry *);
afs_int32 DoDeleteDump(struct rx_call *, dumpId, Date, Date, budb_dumpsList *);
afs_int32 DoDeleteTape(struct rx_call *, struct budb_tapeEntry *);
afs_int32 ListDumps(struct rx_call *, afs_int32, afs_int32, Date, Date,
		    budb_dumpsList *, budb_dumpsList *);
afs_int32 DeleteVDP(struct rx_call *, char *, char *, afs_int32);
afs_int32 FindClone(struct rx_call *, afs_int32, char *, afs_int32 *);
afs_int32 FindDump(struct rx_call *, char *, afs_int32,
		   struct budb_dumpEntry *);
afs_int32 FindLatestDump(struct rx_call *, char *, char *,
			 struct budb_dumpEntry *);
afs_int32 FinishDump(struct rx_call *, struct budb_dumpEntry *);
afs_int32 FinishTape(struct rx_call *, struct budb_tapeEntry *);
afs_int32 GetDumps(struct rx_call *, afs_int32, afs_int32, char *,
		   afs_int32, afs_int32, afs_int32, afs_int32 *,
		   afs_int32 *, budb_dumpList *);
afs_int32 getExpiration(struct ubik_trans *ut, struct tape *);
afs_int32 makeAppended(struct ubik_trans *ut, afs_int32, afs_int32,
		       afs_int32);
afs_int32 MakeDumpAppended(struct rx_call *, afs_int32, afs_int32,
			   afs_int32);
afs_int32 FindLastTape(struct rx_call *, afs_int32, struct budb_dumpEntry *,
		       struct budb_tapeEntry *, struct budb_volumeEntry *);
afs_int32 GetTapes(struct rx_call *, afs_int32, afs_int32, char *, afs_int32,
		   afs_int32, afs_int32, afs_int32 *, afs_int32 *,
		   budb_tapeList *);
afs_int32 GetVolumes(struct rx_call *, afs_int32, afs_int32, char *,
		     afs_int32, afs_int32, afs_int32, afs_int32 *,
		     afs_int32 *, budb_volumeList *);
afs_int32 UseTape(struct rx_call *, struct budb_tapeEntry *, int *);
afs_int32 T_DumpHashTable(struct rx_call *, int, char *);
afs_int32 T_GetVersion(struct rx_call *, int *);
afs_int32 T_DumpDatabase(struct rx_call *, char *);

int volFragsDump(struct ubik_trans *, FILE *, dbadr);

/* Text block management */

struct memTextBlock {
    struct memTextBlock *mtb_next;	/* next in chain */
    afs_int32 mtb_nbytes;	/* # of bytes in this block */
    struct blockHeader mtb_blkHeader;	/* in memory header */
    dbadr mtb_addr;		/* disk address of block */
};

typedef struct memTextBlock memTextBlockT;
typedef memTextBlockT *memTextBlockP;

/* These variable are for returning debugging info about the state of the
   server.  If they get trashed during multi-threaded operation it doesn't
   matter. */

/* This is global so COUNT_REQ in krb_udp.c can refer to it. */
char *lastOperation;		/* name of last operation */
static Date lastTrans;		/* time of last transaction */

/* procsInited is sort of a lock: during a transaction only one process runs
   while procsInited is false. */

static int procsInited = 0;

/* This variable is protected by the procsInited flag. */

static int (*rebuildDatabase) (struct ubik_trans *);

/* AwaitInitialization
 * Wait unitl budb has initialized (InitProcs). If it hasn't
 * within 5 seconds, then return no quorum.
 */
afs_int32
AwaitInitialization(void)
{
    afs_int32 start = 0;

    while (!procsInited) {
	if (!start)
	    start = time(0);
	else if (time(0) - start > 5)
	    return UNOQUORUM;
#ifdef AFS_PTHREAD_ENV
	sleep(1);
#else
	IOMGR_Sleep(1);
#endif
    }
    return 0;
}

/* tailCompPtr
 *      name is a pathname style name, determine trailing name and return
 *      pointer to it
 */

char *
tailCompPtr(char *pathNamePtr)
{
    char *ptr;
    ptr = strrchr(pathNamePtr, '/');
    if (ptr == 0) {
	/* this should never happen */
	LogError(0, "tailCompPtr: could not find / in name(%s)\n",
		 pathNamePtr);
	return (pathNamePtr);
    } else
	ptr++;			/* skip the / */
    return (ptr);
}

/* callPermitted
 *      Check to see if the caller is a SuperUser.
 * exit:
 *	0 - no
 *	1 - yes
 */

int
callPermitted(struct rx_call *call)
{
    int permitted = 0;
    struct afsconf_dir *acdir;

    acdir = afsconf_Open(AFSDIR_SERVER_ETC_DIRPATH);
    if (!acdir)
	return 0;

    if (afsconf_SuperUser(acdir, call, NULL))
	permitted = 1;

    if (acdir)
	afsconf_Close(acdir);
    return (permitted);
}

/* InitRPC
 *	This is called by every RPC interface to create a Ubik transaction
 *	and read the database header into core
 * entry:
 *	ut
 *	lock
 *	this_op
 * notes:
 *	sets a lock on byte 1 of the database. Looks like it enforces
 *	single threading by use of the lock.
 */

afs_int32
InitRPC(struct ubik_trans **ut,
	int lock,		/* indicate read/write transaction */
	int this_op)		/* opcode of RCP, for COUNT_ABO */
{
    int code;
    float wait = 0.91;		/* start waiting for 1 second */

  start:
    /* wait for server initialization to finish if this is not InitProcs calling */
    if (this_op)
	if ((code = AwaitInitialization()))
	    return code;

    for (code = UNOQUORUM; code == UNOQUORUM;) {
	code =
	    ubik_BeginTrans(BU_dbase,
			    ((lock ==
			      LOCKREAD) ? UBIK_READTRANS : UBIK_WRITETRANS),
			    ut);
	if (code == UNOQUORUM) {	/* no quorum elected */
	    if (wait < 1)
		Log("Waiting for quorum election\n");
	    if (wait < 15.0)
		wait *= 1.1;
#ifdef AFS_PTHREAD_ENV
	    sleep((int)wait);
#else
	    IOMGR_Sleep((int)wait);
#endif
	}
    }
    if (code)
	return code;
    if (wait > 1)
	Log("Have established quorum\n");

    /* set lock at posiion 1, for 1 byte of type lock */
    if ((code = ubik_SetLock(*ut, 1, 1, lock))) {
	ubik_AbortTrans(*ut);
	return code;
    }

    /* check that dbase is initialized and setup cheader */
    if (lock == LOCKREAD) {
	/* init but don't fix because this is read only */
	if ((code = CheckInit(*ut, 0))) {
	    ubik_AbortTrans(*ut);
	    if ((code = InitRPC(ut, LOCKWRITE, 0))) {	/* Now fix the database */
		LogError(code, "InitRPC: InitRPC failed\n");
		return code;
	    }
	    if ((code = ubik_EndTrans(*ut))) {
		LogError(code, "InitRPC: ubik_EndTrans failed\n");
		return code;
	    }
	    goto start;		/* now redo the read transaction */
	}
    } else {
	if ((code = CheckInit(*ut, rebuildDatabase))) {
	    ubik_AbortTrans(*ut);
	    return code;
	}
    }
    lastTrans = time(0);
    return 0;
}

/* This is called to initialize a newly created database */
static int
initialize_database(struct ubik_trans *ut)
{
    return 0;
}

static int noAuthenticationRequired;	/* global state */
static int recheckNoAuth;	/* global state */

afs_int32
InitProcs(void)
{
    struct ubik_trans *ut;
    afs_int32 code = 0;

    procsInited = 0;

    if ((globalConfPtr->myHost == 0) || (BU_conf == 0))
	ERROR(BUDB_INTERNALERROR);

    recheckNoAuth = 1;

    if (globalConfPtr->debugFlags & DF_NOAUTH)
	noAuthenticationRequired = 1;

    if (globalConfPtr->debugFlags & DF_RECHECKNOAUTH)
	recheckNoAuth = 0;

    if (recheckNoAuth)
	noAuthenticationRequired = afsconf_GetNoAuthFlag(BU_conf);

    if (noAuthenticationRequired)
	LogError(0, "Running server with security disabled\n");

    InitDB();

    rebuildDatabase = initialize_database;

    if ((code = InitRPC(&ut, LOCKREAD, 0))) {
	LogError(code, "InitProcs: InitRPC failed\n");
	return code;
    }
    code = ubik_EndTrans(ut);
    if (code) {
	LogError(code, "InitProcs: ubik_EndTrans failed\n");
	return code;
    }

    rebuildDatabase = 0;	/* only do this during init */
    procsInited = 1;

  error_exit:
    return (code);
}

struct returnList {
    int nElements;		/* number in list */
    int allocSize;		/* number of elements allocated */
    dbadr *elements;		/* array of addresses */
};

static void
InitReturnList(struct returnList *list)
{
    list->nElements = 0;
    list->allocSize = 0;
    list->elements = (dbadr *) 0;
}

static void
FreeReturnList(struct returnList *list)
{
    if (list->elements)
	free(list->elements);
    list->elements = (dbadr *) 0;
    list->nElements = 0;
}

/* As entries are collected, they are added to a return list. Once all
 * entries have been collected, it is then placed in the return buffer
 * with SendReturnList(). The first *to_skipP are not recorded.
 */
static afs_int32
AddToReturnList(struct returnList *list, dbadr a, afs_int32 *to_skipP)
{
    char *tmp;
    afs_int32 size;

    if (a == 0)
	return 0;
    if (*to_skipP > 0) {
	(*to_skipP)--;
	return 0;
    }

    /* Up to 5 plus a maximum so SendReturnList() knows if we
     * need to come back for more.
     */
    if (list->nElements >= BUDB_MAX_RETURN_LIST + 5)
	return BUDB_LIST2BIG;

    if (list->nElements >= list->allocSize) {
	if (list->elements == 0) {
	    size = 10;
	    tmp = (char *)malloc(sizeof(dbadr) * size);
	} else {
	    size = list->allocSize + 10;
	    tmp = (char *)realloc(list->elements, sizeof(dbadr) * size);
	}
	if (!tmp)
	    return BUDB_NOMEM;
	list->elements = (dbadr *) tmp;
	list->allocSize = size;
    }

    list->elements[list->nElements] = a;
    list->nElements++;
    return 0;
}

afs_int32
FillVolEntry(struct ubik_trans *ut, dbadr va, void *rock)
{
    struct budb_volumeEntry *vol = (struct budb_volumeEntry *) rock;
    struct dump d;
    struct tape t;
    struct volInfo vi;
    struct volFragment vf;

    if (dbread(ut, va, &vf, sizeof(vf)))
	return BUDB_IO;		/* The volFrag */
    if (dbread(ut, ntohl(vf.vol), &vi, sizeof(vi)))
	return BUDB_IO;		/* The volInfo */
    if (dbread(ut, ntohl(vf.tape), &t, sizeof(t)))
	return BUDB_IO;		/* The tape */
    if (dbread(ut, ntohl(t.dump), &d, sizeof(d)))
	return BUDB_IO;		/* The dump */

    strcpy(vol->name, vi.name);
    strcpy(vol->server, vi.server);
    strcpy(vol->tape, t.name);
    vol->tapeSeq = ntohl(t.seq);
    vol->dump = ntohl(d.id);
    vol->position = ntohl(vf.position);
    vol->clone = ntohl(vf.clone);
    vol->incTime = ntohl(vf.incTime);
    vol->nBytes = ntohl(vf.nBytes);
    vol->startByte = ntohl(vf.startByte);
    vol->flags = (ntohs(vf.flags) & VOLFRAGMENTFLAGS);	/* low  16 bits here */
    vol->flags |= (ntohl(vi.flags) & VOLINFOFLAGS);	/* high 16 bits here */
    vol->seq = ntohs(vf.sequence);
    vol->id = ntohl(vi.id);
    vol->partition = ntohl(vi.partition);

    return 0;
}

afs_int32
FillDumpEntry(struct ubik_trans *ut, dbadr da, void *rock)
{
    struct budb_dumpEntry *dump = (struct budb_dumpEntry *)rock;
    struct dump d, ad;

    if (dbread(ut, da, &d, sizeof(d)))
	return BUDB_IO;
    dump->id = ntohl(d.id);
    dump->flags = ntohl(d.flags);
    dump->created = ntohl(d.created);
    strncpy(dump->name, d.dumpName, sizeof(dump->name));
    strncpy(dump->dumpPath, d.dumpPath, sizeof(dump->dumpPath));
    strncpy(dump->volumeSetName, d.volumeSet, sizeof(dump->volumeSetName));

    dump->parent = ntohl(d.parent);
    dump->level = ntohl(d.level);
    dump->nVolumes = ntohl(d.nVolumes);

    tapeSet_ntoh(&d.tapes, &dump->tapes);

    if (strlen(d.dumper.name) < sizeof(dump->dumper.name))
	strcpy(dump->dumper.name, d.dumper.name);
    if (strlen(d.dumper.instance) < sizeof(dump->dumper.instance))
	strcpy(dump->dumper.instance, d.dumper.instance);
    if (strlen(d.dumper.cell) < sizeof(dump->dumper.cell))
	strcpy(dump->dumper.cell, d.dumper.cell);

    /* Get the initial dumpid and the appended dump id */
    dump->initialDumpID = ntohl(d.initialDumpID);
    if (d.appendedDumpChain) {
	if (dbread(ut, ntohl(d.appendedDumpChain), &ad, sizeof(ad)))
	    return BUDB_IO;
	dump->appendedDumpID = ntohl(ad.id);
    } else
	dump->appendedDumpID = 0;

    /* printf("dump name %s, parent %d, level %d\n",
     * d.dumpName, ntohl(d.parent), ntohl(d.level)); */

    return 0;
}

afs_int32
FillTapeEntry(struct ubik_trans *ut, dbadr ta, void *rock)
{
    struct budb_tapeEntry *tape=(struct budb_tapeEntry *) rock;
    struct tape t;
    struct dump d;
    afs_int32 code;

    if (dbread(ut, ta, &t, sizeof(t)))
	return BUDB_IO;

    /* Get the tape's expiration date */
    if ((code = getExpiration(ut, &t)))
	return (code);

    strcpy(tape->name, t.name);
    tape->flags = ntohl(t.flags);
    tape->written = ntohl(t.written);
    tape->expires = ntohl(t.expires);
    tape->nMBytes = ntohl(t.nMBytes);
    tape->nBytes = ntohl(t.nBytes);
    tape->nFiles = ntohl(t.nFiles);
    tape->nVolumes = ntohl(t.nVolumes);
    tape->seq = ntohl(t.seq);
    tape->labelpos = ntohl(t.labelpos);
    tape->useCount = ntohl(t.useCount);
    tape->useKBytes = ntohl(t.useKBytes);

    if (dbread(ut, ntohl(t.dump), &d, sizeof(d)))
	return BUDB_IO;
    tape->dump = ntohl(d.id);
    return 0;
}

#define returnList_t budb_dumpList *

/* SendReturnList
 *      A list of elements of size e_size is in list, collected
 *      with AddToReturnList(). We will move this to a correspoding
 *      return list, eList, via FillProc(). nextInodeP tells us
 *      if there are more and how many to skip on the next request.
 */
static afs_int32
SendReturnList(struct ubik_trans *ut,
	       struct returnList *list,	/* list of elements to return */
	       afs_int32(*FillProc) (struct ubik_trans *, dbadr da,
				     void *),
	       				/* proc to fill entry */
	       int e_size,		/* size of each element */
	       afs_int32 index,		/* index from previous call */
	       afs_int32 *nextIndexP,	/* if more elements are available */
	       afs_int32 *dbTimeP,	/* time of last db update */
	       budb_dumpList *eList)	/* rxgen list structure (e.g.) */
{
    afs_int32 code;
    int to_return;
    int i;
    char *e;

    *nextIndexP = -1;
    *dbTimeP = ntohl(db.h.lastUpdate);

    /* Calculate how many to return. Don't let if go over
     * BUDB_MAX_RETURN_LIST nor the size of our return list.
     */
    to_return = list->nElements;
    if (to_return > BUDB_MAX_RETURN_LIST)
	to_return = BUDB_MAX_RETURN_LIST;
    if (eList->budb_dumpList_len && (to_return > eList->budb_dumpList_len))
	to_return = eList->budb_dumpList_len;

    /* Allocate space for the return values if needed and zero it */
    if (eList->budb_dumpList_val == 0) {
	eList->budb_dumpList_val =
	    (struct budb_dumpEntry *)malloc(e_size * to_return);
	if (!eList->budb_dumpList_val)
	    return (BUDB_NOMEM);
    }
    memset(eList->budb_dumpList_val, 0, e_size * to_return);
    eList->budb_dumpList_len = to_return;

    e = (char *)(eList->budb_dumpList_val);
    for (i = 0; i < to_return; i++, e += e_size) {
	code = (*FillProc) (ut, list->elements[i], (budb_dumpEntry *) e);
	if (code)
	    return code;
    }

    if (list->nElements > i)
	*nextIndexP = index + i;
    return 0;
}

/* Come here to delete a volInfo structure. */

static afs_int32
DeleteVolInfo(struct ubik_trans *ut, dbadr via, struct volInfo *vi)
{
    afs_int32 code;
    dbadr hvia;
    struct volInfo hvi;

    if (vi->firstFragment)
	return 0;		/* still some frags, don't free yet */
    if (vi->sameNameHead == 0) {	/* this is the head */
	if (vi->sameNameChain)
	    return 0;		/* empty head, some non-heads left */

	code = ht_HashOut(ut, &db.volName, via, vi);
	if (code)
	    return code;
	code = FreeStructure(ut, volInfo_BLOCK, via);
	return code;
    }
    hvia = ntohl(vi->sameNameHead);
    if (dbread(ut, hvia, &hvi, sizeof(hvi)))
	return BUDB_IO;
    code =
	RemoveFromList(ut, hvia, &hvi, &hvi.sameNameChain, via, vi,
		       &vi->sameNameChain);
    if (code == -1)
	return BUDB_DATABASEINCONSISTENT;
    if (code == 0)
	code = FreeStructure(ut, volInfo_BLOCK, via);
    return code;
}

/* Detach a volume fragment from its volInfo structure.  Its tape chain is
   already freed.  This routine frees the structure and the caller must not
   write it out. */

static afs_int32
DeleteVolFragment(struct ubik_trans *ut, dbadr va, struct volFragment *v)
{
    afs_int32 code;
    struct volInfo vi;
    dbadr via;

    via = ntohl(v->vol);
    if (dbread(ut, via, &vi, sizeof(vi)))
	return BUDB_IO;
    code =
	RemoveFromList(ut, via, &vi, &vi.firstFragment, va, v,
		       &v->sameNameChain);
    if (code == -1)
	return BUDB_DATABASEINCONSISTENT;
    if (code)
	return code;
    if (vi.firstFragment == 0)
	if ((code = DeleteVolInfo(ut, via, &vi)))
	    return code;
    if ((code = FreeStructure(ut, volFragment_BLOCK, va)))
	return code;

    /* decrement frag counter */
    code =
	set_word_addr(ut, via, &vi, &vi.nFrags, htonl(ntohl(vi.nFrags) - 1));
    if (code)
	return code;
    return 0;
}

/* DeleteTape - by freeing all its volumes and removing it from its dump chain.
 * The caller will remove it from the hash table if necessary.  The caller is
 * also responsible for writing the tape out if necessary. */

static afs_int32
DeleteTape(struct ubik_trans *ut, dbadr ta, struct tape *t)
{
    afs_int32 code;
    struct dump d;
    dbadr da;

    da = ntohl(t->dump);
    if (da == 0)
	return BUDB_DATABASEINCONSISTENT;
    if (dbread(ut, da, &d, sizeof(d)))
	return BUDB_IO;
    if (d.firstTape == 0)
	return BUDB_DATABASEINCONSISTENT;

    code = RemoveFromList(ut, da, &d, &d.firstTape, ta, t, &t->nextTape);
    if (code == -1)
	return BUDB_DATABASEINCONSISTENT;
    if (code)
	return code;

    /* Since the tape should have been truncated there should never be any
     * volumes in the tape. */
    if (t->firstVol || t->nVolumes)
	return BUDB_DATABASEINCONSISTENT;

    return 0;
}

static afs_int32
DeleteDump(struct ubik_trans *ut, dbadr da, struct dump *d)
{
    afs_int32 code = 0;

    code = ht_HashOut(ut, &db.dumpIden, da, d);
    if (code)
	ERROR(code);

    code = ht_HashOut(ut, &db.dumpName, da, d);
    if (code)
	ERROR(code);

    /* Since the tape should have been truncated this should never happen. */
    if (d->firstTape || d->nVolumes)
	ERROR(BUDB_DATABASEINCONSISTENT);

    code = FreeStructure(ut, dump_BLOCK, da);
    if (code)
	ERROR(code);

  error_exit:
    return code;
}

/*
 * VolInfoMatch()
 *
 * This is called with a volumeEntry and a volInfo structure and compares
 * them.  It returns non-zero if they are equal.  It is used by GetVolInfo to
 * search volInfo structures once it has the head volInfo structure from the
 * volName hash table.
 *
 * When called from GetVolInfo the name compare is redundant.
 * Always AND the flags with VOLINFOFLAGS for backwards compatability (3.3).
 */

static int
VolInfoMatch(struct budb_volumeEntry *vol, struct volInfo *vi)
{
    return ((strcmp(vol->name, vi->name) == 0) &&	/* same volume name */
	    (vol->id == ntohl(vi->id)) &&	/* same volume id */
	    ((vol->flags & VOLINFOFLAGS) == (ntohl(vi->flags) & VOLINFOFLAGS)) &&	/* same flags */
	    (vol->partition == ntohl(vi->partition)) &&	/* same partition (N/A) */
	    (strcmp(vol->server, vi->server) == 0));	/* same server (N/A) */
}

/*
 * GetVolInfo()
 *     This routine takes a volumeEntry structure from an RPC interface and
 * returns the corresponding volInfo structure, creating it if necessary.
 *
 *     The caller must write the entry out.
 */

static afs_int32
GetVolInfo(struct ubik_trans *ut, struct budb_volumeEntry *volP, dbadr *viaP,
	   struct volInfo *viP)
{
    dbadr hvia, via;
    struct volInfo hvi;
    afs_int32 eval, code = 0;

    eval = ht_LookupEntry(ut, &db.volName, volP->name, &via, viP);
    if (eval)
	ERROR(eval);

    if (!via) {
	/* allocate a new volinfo structure */
	eval = AllocStructure(ut, volInfo_BLOCK, 0, &via, viP);
	if (eval)
	    ERROR(eval);

	strcpy(viP->name, volP->name);
	strcpy(viP->server, volP->server);
	viP->sameNameHead = 0;	/* The head of same name chain */
	viP->sameNameChain = 0;	/* Same name chain is empty */
	viP->firstFragment = 0;
	viP->nFrags = 0;
	viP->id = htonl(volP->id);
	viP->partition = htonl(volP->partition);
	viP->flags = htonl(volP->flags & VOLINFOFLAGS);

	/* Chain onto volname hash table */
	eval = ht_HashIn(ut, &db.volName, via, viP);
	if (eval)
	    ERROR(eval);

	LogDebug(4, "volume Info for %s placed at %d\n", volP->name, via);
    }

    else if (!VolInfoMatch(volP, viP)) {	/* Not the head volinfo struct */
	hvia = via;		/* remember the head volinfo struct */
	memcpy(&hvi, viP, sizeof(hvi));

	/* Search the same name chain for the correct volinfo structure */
	for (via = ntohl(viP->sameNameChain); via;
	     via = ntohl(viP->sameNameChain)) {
	    eval = dbread(ut, via, viP, sizeof(*viP));
	    if (eval)
		ERROR(eval);

	    if (VolInfoMatch(volP, viP))
		break;		/* found the one */
	}

	/* if the correct volinfo struct isn't found, create one */
	if (!via) {
	    eval = AllocStructure(ut, volInfo_BLOCK, 0, &via, viP);
	    if (eval)
		ERROR(eval);

	    strcpy(viP->name, volP->name);
	    strcpy(viP->server, volP->server);
	    viP->nameHashChain = 0;	/* not in hash table */
	    viP->sameNameHead = htonl(hvia);	/* chain to head of sameNameChain */
	    viP->sameNameChain = hvi.sameNameChain;
	    viP->firstFragment = 0;
	    viP->nFrags = 0;
	    viP->id = htonl(volP->id);
	    viP->partition = htonl(volP->partition);
	    viP->flags = htonl(volP->flags & VOLINFOFLAGS);

	    /* write the head entry's sameNameChain link */
	    eval =
		set_word_addr(ut, hvia, &hvi, &hvi.sameNameChain, htonl(via));
	    if (eval)
		ERROR(eval);
	}
    }

    *viaP = via;

  error_exit:
    return code;
}

/* deletesomevolumesfromtape
 *	Deletes a specified number of volumes from a tape. The tape
 *	and dump are modified to reflect the smaller number of volumes.
 *	The transaction is not terminated, it is up to the caller to
 *	finish the transaction and start a new one (if desired).
 * entry:
 *	maxvolumestodelete - don't delete more than this many volumes
 */

afs_int32
deleteSomeVolumesFromTape(struct ubik_trans *ut, dbadr tapeAddr,
			  struct tape *tapePtr, int maxVolumesToDelete)
{
    dbadr volFragAddr, nextVolFragAddr, dumpAddr;
    struct volFragment volFrag;
    struct dump dump;
    int volumesDeleted = 0;
    afs_int32 eval, code = 0;

    if (!tapePtr)
	ERROR(0);

    for (volFragAddr = ntohl(tapePtr->firstVol);
	 (volFragAddr && (maxVolumesToDelete > 0));
	 volFragAddr = nextVolFragAddr) {
	eval = dbread(ut, volFragAddr, &volFrag, sizeof(volFrag));
	if (eval)
	    ERROR(eval);

	nextVolFragAddr = ntohl(volFrag.sameTapeChain);

	eval = DeleteVolFragment(ut, volFragAddr, &volFrag);
	if (eval)
	    ERROR(eval);

	maxVolumesToDelete--;
	volumesDeleted++;
    }

    /* reset the volume fragment pointer in the tape */
    tapePtr->firstVol = htonl(volFragAddr);

    /* diminish the tape's volume count */
    tapePtr->nVolumes = htonl(ntohl(tapePtr->nVolumes) - volumesDeleted);

    eval = dbwrite(ut, tapeAddr, tapePtr, sizeof(*tapePtr));
    if (eval)
	ERROR(eval);

    /* diminish the dump's volume count */
    dumpAddr = ntohl(tapePtr->dump);
    eval = dbread(ut, dumpAddr, &dump, sizeof(dump));
    if (eval)
	ERROR(eval);

    dump.nVolumes = htonl(ntohl(dump.nVolumes) - volumesDeleted);
    eval = dbwrite(ut, dumpAddr, &dump, sizeof(dump));
    if (eval)
	ERROR(eval);

  error_exit:
    return (code);
}

/* deleteDump
 *	deletes a dump in stages, by repeatedly deleting a small number of
 *	volumes from the dump until none are left. The dump is then deleted.
 *
 *	In the case where multiple calls are made to delete the same
 *	dump, the operation will succeed but contention for structures
 *	will result in someone getting back an error.
 *
 * entry:
 *	id - id of dump to delete
 */

afs_int32
deleteDump(struct rx_call *call, dumpId id, budb_dumpsList *dumps)
{
    struct ubik_trans *ut;
    dbadr dumpAddr, tapeAddr, appendedDump;
    struct dump dump;
    struct tape tape;
    dumpId dumpid;
    afs_int32 eval, code = 0;
    int partialDel = 0;

    /* iterate until the dump is truly deleted */

    dumpid = id;
    while (dumpid) {
	partialDel = 0;
	while (1) {
	    eval = InitRPC(&ut, LOCKWRITE, 1);
	    if (eval)
		ERROR(eval);	/* can't start transaction */

	    eval =
		ht_LookupEntry(ut, &db.dumpIden, &dumpid, &dumpAddr, &dump);
	    if (eval)
		ABORT(eval);
	    if (!dumpAddr)
		ABORT(BUDB_NOENT);	/* can't find dump */

	    if ((dumpid == id) && (dump.initialDumpID))	/* can't be an appended dump */
		ABORT(BUDB_NOTINITIALDUMP);

	    tapeAddr = ntohl(dump.firstTape);
	    if (tapeAddr == 0)
		break;

	    /* there is a tape to delete */
	    eval = dbread(ut, tapeAddr, &tape, sizeof(tape));
	    if (eval)
		ABORT(eval);

	    if (ntohl(tape.nVolumes)) {
		/* tape is not empty */
		eval = deleteSomeVolumesFromTape(ut, tapeAddr, &tape, 10);
		if (eval)
		    ABORT(eval);
	    }

	    if (ntohl(tape.nVolumes) == 0) {
		/* tape is now empty, delete it */
		eval = DeleteTape(ut, tapeAddr, &tape);
		if (eval)
		    ABORT(eval);
		eval = ht_HashOut(ut, &db.tapeName, tapeAddr, &tape);
		if (eval)
		    ABORT(eval);
		eval = FreeStructure(ut, tape_BLOCK, tapeAddr);
		if (eval)
		    ABORT(eval);
	    }

	    eval = ubik_EndTrans(ut);
	    if (eval)
		ERROR(eval);
	    partialDel = 1;
	}			/* next deletion portion */

	/* Record the dump just deleted */
	if (dumps && (dumps->budb_dumpsList_len < BUDB_MAX_RETURN_LIST)) {
	    if (dumps->budb_dumpsList_len == 0)
		dumps->budb_dumpsList_val =
		    (afs_int32 *) malloc(sizeof(afs_int32));
	    else
		dumps->budb_dumpsList_val =
		    (afs_int32 *) realloc(dumps->budb_dumpsList_val,
					  (dumps->budb_dumpsList_len +
					   1) * sizeof(afs_int32));

	    if (!dumps->budb_dumpsList_val)
		ABORT(BUDB_NOMEM);

	    dumps->budb_dumpsList_val[dumps->budb_dumpsList_len] = dumpid;
	    dumps->budb_dumpsList_len++;
	}

	appendedDump = ntohl(dump.appendedDumpChain);

	/* finally done. No more tapes left in the dump. Delete the dump itself */
	eval = DeleteDump(ut, dumpAddr, &dump);
	if (eval)
	    ABORT(eval);

	/* Now delete the appended dump too */
	if (appendedDump) {
	    eval = dbread(ut, appendedDump, &dump, sizeof(dump));
	    if (eval)
		ABORT(eval);

	    dumpid = ntohl(dump.id);
	} else
	    dumpid = 0;

	eval = ubik_EndTrans(ut);
	if (eval)
	    ERROR(eval);

	Log("Delete dump %s (DumpID %u), path %s\n", dump.dumpName,
	    ntohl(dump.id), dump.dumpPath);
    }

  error_exit:
    if (code && partialDel) {
	Log("Delete dump %s (DumpID %u), path %s - INCOMPLETE (code = %u)\n",
	    dump.dumpName, ntohl(dump.id), dump.dumpPath, code);
    }
    return (code);

  abort_exit:
    ubik_AbortTrans(ut);
    goto error_exit;
}

/* --------------
 * dump selection routines - used by BUDB_GetDumps
 * --------------
 */

/* most recent dump selection */

struct chosenDump {
    struct chosenDump *next;
    dbadr addr;
    afs_uint32 date;
};

struct wantDumpRock {
    int maxDumps;		/* max wanted */
    int ndumps;			/* actual in chain */
    struct chosenDump *chain;
};


int
wantDump(dbadr dumpAddr, void *dumpParam, void *dumpListPtrParam)
{
    struct dump *dumpPtr;
    struct wantDumpRock *rockPtr;

    dumpPtr = (struct dump *)dumpParam;
    rockPtr = (struct wantDumpRock *)dumpListPtrParam;

    /* if we don't have our full complement, just add another */
    if (rockPtr->ndumps < rockPtr->maxDumps)
	return (1);

    /* got the number we need, select based on date */
    if ((afs_uint32) ntohl(dumpPtr->created) > rockPtr->chain->date)
	return (1);

    return (0);
}

int
rememberDump(dbadr dumpAddr, void *dumpParam, void *dumpListPtrParam)
{
    struct dump *dumpPtr;
    struct wantDumpRock *rockPtr;
    struct chosenDump *ptr, *deletedPtr, **nextPtr;

    dumpPtr = (struct dump *)dumpParam;
    rockPtr = (struct wantDumpRock *)dumpListPtrParam;

    ptr = (struct chosenDump *)malloc(sizeof(*ptr));
    if (!ptr)
	return (0);
    memset(ptr, 0, sizeof(*ptr));
    ptr->addr = dumpAddr;
    ptr->date = (afs_uint32) ntohl(dumpPtr->created);

    /* Don't overflow the max */
    while (rockPtr->ndumps >= rockPtr->maxDumps) {
	/* have to drop one */
	deletedPtr = rockPtr->chain;
	rockPtr->chain = deletedPtr->next;
	free(deletedPtr);
	rockPtr->ndumps--;
    }

    /* now insert in the right place */
    for (nextPtr = &rockPtr->chain; *nextPtr; nextPtr = &((*nextPtr)->next)) {
	if (ptr->date < (*nextPtr)->date)
	    break;
    }
    ptr->next = *nextPtr;
    *nextPtr = ptr;
    rockPtr->ndumps++;

    return (0);
}


/* ---------------------------------------------
 * general interface routines - alphabetic
 * ---------------------------------------------
 */

afs_int32
SBUDB_AddVolume(struct rx_call *call, struct budb_volumeEntry *vol)
{
    afs_int32 code;

    code = AddVolume(call, vol);
    osi_auditU(call, BUDB_AddVolEvent, code, AUD_LONG, (vol ? vol->id : 0),
	       AUD_END);
    return code;
}

afs_int32
AddVolume(struct rx_call *call, struct budb_volumeEntry *vol)
{
    struct ubik_trans *ut;
    dbadr da, ta, via, va;
    struct dump d;
    struct tape t;
    struct volInfo vi;
    struct volFragment v;
    afs_uint32 bytes;
    afs_int32 eval, code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    if ((strlen(vol->name) >= sizeof(vi.name))
	|| (strlen(vol->server) >= sizeof(vi.server))
	|| (strlen(vol->tape) >= sizeof(t.name)))
	return BUDB_BADARGUMENT;

    eval = InitRPC(&ut, LOCKWRITE, 1);
    if (eval)
	return eval;

    /* Find the dump in dumpid hash table */
    eval = ht_LookupEntry(ut, &db.dumpIden, &vol->dump, &da, &d);
    if (eval)
	ABORT(eval);
    if (!da)
	ABORT(BUDB_NODUMPID);

    /* search for the right tape in the dump */
    for (ta = ntohl(d.firstTape); ta; ta = ntohl(t.nextTape)) {
	/* read the tape entry */
	eval = dbread(ut, ta, &t, sizeof(t));
	if (eval)
	    ABORT(eval);

	/* Check if the right tape name */
	if (strcmp(t.name, vol->tape) == 0)
	    break;
    }
    if (!ta)
	ABORT(BUDB_NOTAPENAME);

    if ((t.dump != htonl(da)) ||	/* tape must belong to dump   */
	((ntohl(t.flags) & BUDB_TAPE_BEINGWRITTEN) == 0) ||	/* tape must be being written */
	((ntohl(d.flags) & BUDB_DUMP_INPROGRESS) == 0))	/* dump must be in progress   */
	ABORT(BUDB_BADPROTOCOL);

    /* find or create a volume info structure */
    eval = GetVolInfo(ut, vol, &via, &vi);
    if (eval)
	ABORT(eval);

    /* Create a volume fragment */
    eval = AllocStructure(ut, volFragment_BLOCK, 0, &va, &v);
    if (eval)
	ABORT(eval);

    v.vol = htonl(via);		/* vol frag points to vol info */
    v.sameNameChain = vi.firstFragment;	/* vol frag is chained to vol info */
    vi.firstFragment = htonl(va);
    vi.nFrags = htonl(ntohl(vi.nFrags) + 1);

    eval = dbwrite(ut, via, &vi, sizeof(vi));	/* write the vol info struct */
    if (eval)
	ABORT(eval);

    v.tape = htonl(ta);		/* vol frag points to tape */
    v.sameTapeChain = t.firstVol;	/* vol frag is chained to tape info */
    t.firstVol = htonl(va);
    t.nVolumes = htonl(ntohl(t.nVolumes) + 1);
    bytes = ntohl(t.nBytes) + vol->nBytes;	/* update bytes on tape */
    t.nMBytes = htonl(ntohl(t.nMBytes) + bytes / (1024 * 1024));
    t.nBytes = htonl(bytes % (1024 * 1024));

    eval = dbwrite(ut, ta, &t, sizeof(t));	/* write the tape structure */
    if (eval)
	ABORT(eval);

    d.nVolumes = htonl(ntohl(d.nVolumes) + 1);	/* one more volume on dump */

    eval = dbwrite(ut, da, &d, sizeof(d));	/* write out the dump structure */
    if (eval)
	ABORT(eval);

    v.position = htonl(vol->position);	/* vol frag info */
    v.clone = htonl(vol->clone);
    v.incTime = htonl(vol->incTime);
    v.startByte = htonl(vol->startByte);
    v.nBytes = htonl(vol->nBytes);
    v.flags = htons(vol->flags & VOLFRAGMENTFLAGS);
    v.sequence = htons(vol->seq);

    eval = dbwrite(ut, va, &v, sizeof(v));	/* write out the vol frag struct */
    if (eval)
	ABORT(eval);

    eval = set_header_word(ut, lastUpdate, htonl(time(0)));
    if (eval)
	ABORT(eval);

    LogDebug(4, "added volume %s at %d\n", vol->name, va);

    code = ubik_EndTrans(ut);
    return code;

  abort_exit:
    ubik_AbortTrans(ut);
    return code;
}


afs_int32
SBUDB_AddVolumes(struct rx_call *call, struct budb_volumeList *vols)
{
    afs_int32 code;

    code = AddVolumes(call, vols);
    osi_auditU(call, BUDB_AddVolEvent, code, AUD_LONG, 0, AUD_END);
    return code;
}

afs_int32
AddVolumes(struct rx_call *call, struct budb_volumeList *vols)
{
    struct budb_volumeEntry *vol, *vol1;
    struct ubik_trans *ut;
    dbadr da, ta, via, va;
    struct dump d;
    struct tape t;
    struct volInfo vi;
    struct volFragment v;
    afs_uint32 bytes;
    afs_int32 eval, e, code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    if (!vols || (vols->budb_volumeList_len <= 0)
	|| !vols->budb_volumeList_val)
	return BUDB_BADARGUMENT;

    /* The first volume in the list of volumes to add */
    vol1 = (struct budb_volumeEntry *)vols->budb_volumeList_val;

    eval = InitRPC(&ut, LOCKWRITE, 1);
    if (eval)
	return eval;

    /* Find the dump in dumpid hash table */
    eval = ht_LookupEntry(ut, &db.dumpIden, &vol1->dump, &da, &d);
    if (eval)
	ABORT(eval);
    if (!da)
	ABORT(BUDB_NODUMPID);

    /* search for the right tape in the dump */
    for (ta = ntohl(d.firstTape); ta; ta = ntohl(t.nextTape)) {
	/* read the tape entry */
	eval = dbread(ut, ta, &t, sizeof(t));
	if (eval)
	    ABORT(eval);

	/* Check if the right tape name */
	if (strcmp(t.name, vol1->tape) == 0)
	    break;
    }
    if (!ta)
	ABORT(BUDB_NOTAPENAME);

    if ((t.dump != htonl(da)) ||	/* tape must belong to dump   */
	((ntohl(t.flags) & BUDB_TAPE_BEINGWRITTEN) == 0) ||	/* tape must be being written */
	((ntohl(d.flags) & BUDB_DUMP_INPROGRESS) == 0))	/* dump must be in progress   */
	ABORT(BUDB_BADPROTOCOL);

    for (vol = vol1, e = 0; e < vols->budb_volumeList_len; vol++, e++) {
	/*v */
	if ((strlen(vol->name) >= sizeof(vi.name)) || (strcmp(vol->name, "") == 0) ||	/* no null volnames */
	    (strlen(vol->server) >= sizeof(vi.server))
	    || (strlen(vol->tape) >= sizeof(t.name))
	    || (strcmp(vol->tape, vol1->tape) != 0)) {
	    Log("Volume '%s' %u, tape '%s', dumpID %u is an invalid entry - not added\n", vol->name, vol->id, vol->tape, vol->dump);
	    continue;
	}

	/* find or create a volume info structure */
	eval = GetVolInfo(ut, vol, &via, &vi);
	if (eval)
	    ABORT(eval);
	if (*(afs_int32 *) (&vi) == 0) {
	    Log("Volume '%s', tape '%s', dumpID %u is an invalid entry - aborted\n", vol->name, vol->tape, vol->dump);
	    ABORT(BUDB_BADARGUMENT);
	}

	/* Create a volume fragment */
	eval = AllocStructure(ut, volFragment_BLOCK, 0, &va, &v);
	if (eval)
	    ABORT(eval);

	v.vol = htonl(via);	/* vol frag points to vol info */
	v.sameNameChain = vi.firstFragment;	/* vol frag is chained to vol info */
	vi.firstFragment = htonl(va);
	vi.nFrags = htonl(ntohl(vi.nFrags) + 1);
	eval = dbwrite(ut, via, &vi, sizeof(vi));	/* write the vol info struct */
	if (eval)
	    ABORT(eval);

	v.tape = htonl(ta);	/* vol frag points to tape */
	v.sameTapeChain = t.firstVol;	/* vol frag is chained to tape info */
	t.firstVol = htonl(va);
	t.nVolumes = htonl(ntohl(t.nVolumes) + 1);
	bytes = ntohl(t.nBytes) + vol->nBytes;	/* update bytes on tape */
	t.nMBytes = htonl(ntohl(t.nMBytes) + bytes / (1024 * 1024));
	t.nBytes = htonl(bytes % (1024 * 1024));

	d.nVolumes = htonl(ntohl(d.nVolumes) + 1);	/* one more volume on dump */

	v.position = htonl(vol->position);	/* vol frag info */
	v.clone = htonl(vol->clone);
	v.incTime = htonl(vol->incTime);
	v.startByte = htonl(vol->startByte);
	v.nBytes = htonl(vol->nBytes);
	v.flags = htons(vol->flags & VOLFRAGMENTFLAGS);
	v.sequence = htons(vol->seq);

	eval = dbwrite(ut, va, &v, sizeof(v));	/* write out the vol frag struct */
	if (eval)
	    ABORT(eval);

	LogDebug(4, "added volume %s at %d\n", vol->name, va);
    }				/*v */

    eval = dbwrite(ut, ta, &t, sizeof(t));	/* write the tape structure */
    if (eval)
	ABORT(eval);

    eval = dbwrite(ut, da, &d, sizeof(d));	/* write out the dump structure */
    if (eval)
	ABORT(eval);

    eval = set_header_word(ut, lastUpdate, htonl(time(0)));
    if (eval)
	ABORT(eval);

    code = ubik_EndTrans(ut);
    return code;

  abort_exit:
    ubik_AbortTrans(ut);
    return code;
}


/* BUDB_CreateDump
 *	records the existence of a dump in the database. This creates only
 *	the dump record, to which one must attach tape and volume records.
 * TBD
 *	1) record the volume set
 */

afs_int32
SBUDB_CreateDump(struct rx_call *call, struct budb_dumpEntry *dump)
{
    afs_int32 code;

    code = CreateDump(call, dump);
    osi_auditU(call, BUDB_CrDmpEvent, code, AUD_DATE, (dump ? dump->id : 0),
	       AUD_END);
    if (dump && !code) {
	Log("Create dump %s (DumpID %u), path %s\n", dump->name, dump->id,
	    dump->dumpPath);
    }
    return code;
}

afs_int32
CreateDump(struct rx_call *call, struct budb_dumpEntry *dump)
{
    struct ubik_trans *ut;
    dbadr findDumpAddr, da;
    struct dump findDump, d;
    afs_int32 eval, code = 0;

    rxkad_level level;
    afs_int32 kvno;
    Date expiration;		/* checked by Security Module */
    struct ktc_principal principal;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    if (strlen(dump->name) >= sizeof(d.dumpName))
	return BUDB_BADARGUMENT;

    eval = InitRPC(&ut, LOCKWRITE, 1);
    if (eval)
	return eval;

    eval =
	rxkad_GetServerInfo(rx_ConnectionOf(call), &level, &expiration,
			    principal.name, principal.instance,
			    principal.cell, &kvno);

    if (eval) {
	if (eval != RXKADNOAUTH)
	    ABORT(eval);

	strcpy(principal.name, "");
	strcpy(principal.instance, "");
	strcpy(principal.cell, "");
	expiration = 0;
    } else {
	/* authenticated. Take user supplied principal information */
	if (strcmp(dump->dumper.name, "") != 0)
	    strncpy(principal.name, dump->dumper.name,
		    sizeof(principal.name));

	if (strcmp(dump->dumper.instance, "") != 0)
	    strncpy(principal.instance, dump->dumper.instance,
		    sizeof(principal.instance));

	if (strcmp(dump->dumper.cell, "") != 0)
	    strncpy(principal.cell, dump->dumper.cell,
		    sizeof(principal.cell));
    }

    /* dump id's are time stamps */
    if (!dump->id) {
	while (1) {		/* allocate a unique dump id *//*w */
	    dump->id = time(0);

	    /* ensure it is unique - seach for dumpid in hash table */
	    eval =
		ht_LookupEntry(ut, &db.dumpIden, &dump->id, &findDumpAddr,
			       &findDump);
	    if (eval)
		ABORT(eval);

	    if (!findDumpAddr) {	/* dumpid not in use */
		/* update the last dump id allocated */
		eval = set_header_word(ut, lastDumpId, htonl(dump->id));
		if (eval)
		    ABORT(eval);
		break;
	    }

	    /* dump id is in use - wait a while */
#ifdef AFS_PTHREAD_ENV
	    sleep(1);
#else
	    IOMGR_Sleep(1);
#endif
	}			/*w */
    } else {
	/* dump id supplied (e.g. for database restore) */
	eval =
	    ht_LookupEntry(ut, &db.dumpIden, &dump->id, &findDumpAddr,
			   &findDump);
	if (eval)
	    ABORT(eval);

	/* Dump id must not already exist */
	if (findDumpAddr)
	    ABORT(BUDB_DUMPIDEXISTS);
    }

    /* Allocate a dump structure */
    memset(&d, 0, sizeof(d));
    eval = AllocStructure(ut, dump_BLOCK, 0, &da, &d);
    if (eval)
	ABORT(eval);

    strcpy(d.dumpName, dump->name);	/* volset.dumpname */
    strcpy(d.dumpPath, dump->dumpPath);	/* dump node path */
    strcpy(d.volumeSet, dump->volumeSetName);	/* volume set */
    d.id = htonl(dump->id);
    d.parent = htonl(dump->parent);	/* parent id */
    d.level = htonl(dump->level);

    LogDebug(4, "dump name %s, parent %d level %d\n", dump->name,
	     dump->parent, dump->level);

    /* if creation time specified, use that.  Else use the dumpid time */
    if (dump->created == 0)
	dump->created = dump->id;
    d.created = htonl(dump->created);

    d.dumper = principal;
    tapeSet_hton(&dump->tapes, &d.tapes);

    d.flags = htonl(dump->flags | BUDB_DUMP_INPROGRESS);

    eval = ht_HashIn(ut, &db.dumpName, da, &d);	/* Into dump name hash table */
    if (eval)
	ABORT(eval);

    eval = ht_HashIn(ut, &db.dumpIden, da, &d);	/* Into dumpid hash table */
    if (eval)
	ABORT(eval);

    eval = dbwrite(ut, da, (char *)&d, sizeof(d));	/* Write the dump structure */
    if (eval)
	ABORT(eval);

    eval = set_header_word(ut, lastUpdate, htonl(time(0)));
    if (eval)
	ABORT(eval);

    /* If to append this dump, then append it - will write the appended dump */
    eval = makeAppended(ut, dump->id, dump->initialDumpID, dump->tapes.b);
    if (eval)
	ABORT(eval);

    code = ubik_EndTrans(ut);
    LogDebug(5, "made dump %s, path %s\n", d.dumpName, d.dumpPath);
    return code;

  abort_exit:
    ubik_AbortTrans(ut);
    return code;
}

afs_int32
SBUDB_DeleteDump(struct rx_call *call, dumpId id, Date fromTime, Date toTime,
		 budb_dumpsList *dumps)
{
    afs_int32 code;

    code = DoDeleteDump(call, id, fromTime, toTime, dumps);
    osi_auditU(call, BUDB_DelDmpEvent, code, AUD_DATE, id, AUD_END);
    return code;
}

#define MAXOFFS 30

afs_int32
DoDeleteDump(struct rx_call *call, dumpId id, Date fromTime, Date toTime,
     	     budb_dumpsList *dumps)
{
    afs_int32 code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    if (id)
	code = deleteDump(call, id, dumps);
    return (code);
}

afs_int32
SBUDB_ListDumps(struct rx_call *call, afs_int32 sflags, char *name,
		afs_int32 groupid, Date fromTime, Date toTime,
		budb_dumpsList *dumps, budb_dumpsList *flags)
{
    afs_int32 code;

    code = ListDumps(call, sflags, groupid, fromTime, toTime, dumps, flags);
    osi_auditU(call, BUDB_LstDmpEvent, code, AUD_LONG, flags, AUD_END);
    return code;
}

afs_int32
ListDumps(struct rx_call *call, afs_int32 sflags, afs_int32 groupid,
	  Date fromTime, Date toTime, budb_dumpsList *dumps,
	  budb_dumpsList *flags)
{
    struct ubik_trans *ut;
    struct memoryHashTable *mht;
    struct dump diskDump, appDiskDump;
    dbadr dbAddr, dbAppAddr;

    afs_int32 eval, code = 0;
    int old, hash, length, entrySize, count = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    eval = InitRPC(&ut, LOCKREAD, 1);
    if (eval)
	return (eval);

    /* Search the database */
    mht = ht_GetType(HT_dumpIden_FUNCTION, &entrySize);
    if (!mht)
	return (BUDB_BADARGUMENT);

    for (old = 0; old <= 1; old++) {	/*o *//* old and new hash tables */
	length = (old ? mht->oldLength : mht->length);
	if (length == 0)
	    continue;

	for (hash = 0; hash < length; hash++) {	/*h *//* for each hash bucket */
	    for (dbAddr = ht_LookupBucket(ut, mht, hash, old); dbAddr; dbAddr = ntohl(diskDump.idHashChain)) {	/*d */

		/* read the entry */
		eval = dbread(ut, dbAddr, &diskDump, sizeof(diskDump));
		if (eval)
		    ABORT(eval);

		/* Skip appended dumps */
		if (ntohl(diskDump.initialDumpID) != 0) {
		    continue;
		}

		/* Skip dumps with different goup id */
		if ((sflags & BUDB_OP_GROUPID)
		    && (ntohl(diskDump.tapes.id) != groupid)) {
		    continue;	/*nope */
		}

		/* Look at this dump to see if it meets the criteria for listing */
		if (sflags & BUDB_OP_DATES) {
		    /* This and each appended dump should be in time */
		    for (dbAppAddr = dbAddr; dbAppAddr;
			 dbAppAddr = ntohl(appDiskDump.appendedDumpChain)) {
			eval =
			    dbread(ut, dbAppAddr, &appDiskDump,
				   sizeof(appDiskDump));
			if (eval)
			    ABORT(eval);

			if ((ntohl(appDiskDump.id) < fromTime)
			    || (ntohl(appDiskDump.id) > toTime))
			    break;	/*nope */
		    }
		    if (dbAppAddr)
			continue;	/*nope */
		}

		/* Add it and each of its appended dump to our list to return */
		for (dbAppAddr = dbAddr; dbAppAddr;
		     dbAppAddr = ntohl(appDiskDump.appendedDumpChain)) {
		    eval =
			dbread(ut, dbAppAddr, &appDiskDump,
			       sizeof(appDiskDump));
		    if (eval)
			ABORT(eval);

		    /* Make sure we have space to list it */
		    if (dumps->budb_dumpsList_len >= count) {
			count += 10;
			if (count == 10) {
			    dumps->budb_dumpsList_val =
				(afs_int32 *) malloc(count *
						     sizeof(afs_int32));
			    flags->budb_dumpsList_val =
				(afs_int32 *) malloc(count *
						     sizeof(afs_int32));
			} else {
			    dumps->budb_dumpsList_val =
				(afs_int32 *) realloc(dumps->
						      budb_dumpsList_val,
						      count *
						      sizeof(afs_int32));
			    flags->budb_dumpsList_val =
				(afs_int32 *) realloc(flags->
						      budb_dumpsList_val,
						      count *
						      sizeof(afs_int32));
			}
			if (!dumps->budb_dumpsList_val
			    || !dumps->budb_dumpsList_val)
			    ABORT(BUDB_NOMEM);
		    }

		    /* Add it to our list */
		    dumps->budb_dumpsList_val[dumps->budb_dumpsList_len] =
			ntohl(appDiskDump.id);
		    flags->budb_dumpsList_val[flags->budb_dumpsList_len] = 0;
		    if (ntohl(appDiskDump.initialDumpID) != 0) {
			flags->budb_dumpsList_val[flags->
						  budb_dumpsList_len] |=
			    BUDB_OP_APPDUMP;
		    }
		    if (strcmp(appDiskDump.dumpName, DUMP_TAPE_NAME) == 0) {
			flags->budb_dumpsList_val[flags->
						  budb_dumpsList_len] |=
			    BUDB_OP_DBDUMP;
		    }
		    dumps->budb_dumpsList_len++;
		    flags->budb_dumpsList_len++;
		}
	    }			/*d */
	}			/*h */
    }				/*o */

    code = ubik_EndTrans(ut);
    return (code);

  abort_exit:
    ubik_AbortTrans(ut);
    return (code);
}

afs_int32
SBUDB_DeleteTape(struct rx_call *call,
		 struct budb_tapeEntry *tape)	/* tape info */
{
    afs_int32 code;

    code = DoDeleteTape(call, tape);
    osi_auditU(call, BUDB_DelTpeEvent, code, AUD_DATE,
	       (tape ? tape->dump : 0), AUD_END);
    return code;
}

afs_int32
DoDeleteTape(struct rx_call *call,
	     struct budb_tapeEntry *tape)	/* tape info */
{
    struct ubik_trans *ut;
    struct tape t;
    dbadr a;
    afs_int32 eval, code;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    eval = InitRPC(&ut, LOCKWRITE, 1);
    if (eval)
	return eval;

    eval = ht_LookupEntry(ut, &db.tapeName, tape->name, &a, &t);
    if (eval)
	ABORT(eval);

    eval = DeleteTape(ut, a, &t);
    if (eval)
	ABORT(eval);

    eval = FreeStructure(ut, tape_BLOCK, a);
    if (eval)
	ABORT(eval);

    eval = set_header_word(ut, lastUpdate, htonl(time(0)));
    if (eval)
	ABORT(eval);

    code = ubik_EndTrans(ut);
    return code;

  abort_exit:
    ubik_AbortTrans(ut);
    return code;
}

/* BUDB_DeleteVDP
 *      Deletes old information from the database for a particular dump path
 *      and volumset. This supercedes the old policy implemented in
 *      UseTape, which simply matched on the volumeset.dump. Consequently
 *      it was unable to handle name re-use.
 * entry:
 *      dsname - dumpset name, i.e. volumeset.dumpname
 *      dumpPath - full path of dump node
 *      curDumpID - current dump in progress - so that is may be excluded
 * exit:
 *      0 - ok
 *      n - some error. May or may not have deleted information.
 */

afs_int32
SBUDB_DeleteVDP(struct rx_call *call, char *dsname, char *dumpPath,
		afs_int32 curDumpId)
{
    afs_int32 code;

    code = DeleteVDP(call, dsname, dumpPath, curDumpId);
    osi_auditU(call, BUDB_DelVDPEvent, code, AUD_STR, dsname, AUD_END);
    return code;
}

afs_int32
DeleteVDP(struct rx_call *call, char *dsname, char *dumpPath,
	  afs_int32 curDumpId)
{
    struct dump dump;
    dbadr dumpAddr;

    struct ubik_trans *ut;
    afs_int32 eval, code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    while (1) {
	eval = InitRPC(&ut, LOCKREAD, 1);
	if (eval)
	    return (eval);

	eval = ht_LookupEntry(ut, &db.dumpName, dsname, &dumpAddr, &dump);
	if (eval)
	    ABORT(eval);

	while (dumpAddr != 0) {	/*wd */
	    if ((strcmp(dump.dumpName, dsname) == 0)
		&& (strcmp(dump.dumpPath, dumpPath) == 0)
		&& (ntohl(dump.id) != curDumpId)) {
		eval = ubik_EndTrans(ut);
		if (eval)
		    return (eval);

		eval = deleteDump(call, ntohl(dump.id), 0);
		if (eval)
		    return (eval);

		/* start the traversal over since the various chains may
		 * have changed
		 */
		break;
	    }

	    dumpAddr = ntohl(dump.nameHashChain);
	    if (dumpAddr) {
		eval = dbread(ut, dumpAddr, &dump, sizeof(dump));
		if (eval)
		    ABORT(eval);
	    }
	}			/*wd */

	/* check if all the dumps have been examined - can terminate */
	if (!dumpAddr) {
	    eval = ubik_EndTrans(ut);
	    return (eval);
	}
    }

  abort_exit:
    ubik_AbortTrans(ut);
    return (code);
}

/* BUDB_FindClone
 * notes:
 *      Given a volume name, and a dumpID, find the volume in that dump and
 *      return the clone date of the volume (this is the clone date of the
 *      volume at the time it was dumped).
 *
 *      Hashes on the volume name and traverses the fragments. Will need to read
 *      the volumes tape entry to determine if it belongs to the dump. If the
 *      volume is not found in the dump, then look for it in its parent dump.
 */

afs_int32
SBUDB_FindClone(struct rx_call *call, afs_int32 dumpID, char *volName,
		afs_int32 *clonetime)
{
    afs_int32 code;

    code = FindClone(call, dumpID, volName, clonetime);
    osi_auditU(call, BUDB_FndClnEvent, code, AUD_STR, volName, AUD_END);
    return code;
}

afs_int32
FindClone(struct rx_call *call, afs_int32 dumpID, char *volName,
	  afs_int32 *clonetime)
{
    struct ubik_trans *ut;
    dbadr da, hvia, via, vfa;
    struct dump d;
    struct tape t;
    struct volFragment vf;
    struct volInfo vi;
    int rvi;			/* read the volInfo struct */
    afs_int32 eval, code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    eval = InitRPC(&ut, LOCKREAD, 1);
    if (eval)
	return (eval);

    *clonetime = 0;

    /* Search for the volume by name */
    eval = ht_LookupEntry(ut, &db.volName, volName, &hvia, &vi);
    if (eval)
	ABORT(eval);
    if (!hvia)
	ABORT(BUDB_NOVOLUMENAME);
    rvi = 0;

    /* Follw the dump levels up */
    for (; dumpID; dumpID = ntohl(d.parent)) {	/*d */
	/* Get the dump entry */
	eval = ht_LookupEntry(ut, &db.dumpIden, &dumpID, &da, &d);
	if (eval)
	    ABORT(eval);
	if (!da)
	    ABORT(BUDB_NODUMPID);

	/* seach all the volInfo entries on the sameNameChain */
	for (via = hvia; via; via = ntohl(vi.sameNameChain)) {	/*via */
	    if (rvi) {		/* Read the volInfo entry - except first time */
		eval = dbread(ut, via, &vi, sizeof(vi));
		if (eval)
		    ABORT(eval);
	    }
	    rvi = 1;

	    /* search all the volFrag entries on the volFrag */
	    for (vfa = ntohl(vi.firstFragment); vfa; vfa = ntohl(vf.sameNameChain)) {	/*vfa */
		eval = dbread(ut, vfa, &vf, sizeof(vf));	/* Read the volFrag entry */
		if (eval)
		    ABORT(eval);

		eval = dbread(ut, ntohl(vf.tape), &t, sizeof(t));	/* Read the tape */
		if (eval)
		    ABORT(eval);

		/* Now check to see if this fragment belongs to the dump we have */
		if (ntohl(t.dump) == da) {
		    *clonetime = ntohl(vf.clone);	/* return the clone */
		    ERROR(0);
		}
	    }			/*vfa */
	}			/*via */
    }				/*d */

  error_exit:
    code = ubik_EndTrans(ut);
    return (code);

  abort_exit:
    ubik_EndTrans(ut);
    return (code);
}

#ifdef notdef
/*
 *      Searches each tape and each volume in the dump until the volume is found.
 *      If the volume is not in the dump, then we search it's parent dump.
 *
 *	Re-write to do lookups by volume name.
 */
afs_int32
FindClone(struct rx_call *call, afs_int32 dumpID, char *volName,
	  afs_int32 *clonetime)
{
    struct ubik_trans *ut;
    dbadr diskAddr, tapeAddr, volFragmentAddr;
    struct dump dump;
    struct tape tape;
    struct volFragment volFragment;
    struct volInfo volInfo;
    afs_int32 eval, code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    eval = InitRPC(&ut, LOCKREAD, 1);
    if (eval)
	return (eval);

    *clonetime = 0;

    for (; dumpID; dumpID = ntohl(dump.parent)) {	/*d */
	/* Get the dump entry */
	eval = ht_LookupEntry(ut, &db.dumpIden, &dumpID, &diskAddr, &dump);
	if (eval)
	    ABORT(eval);
	if (!diskAddr)
	    ABORT(BUDB_NODUMPID);

	/* just to be sure */
	if (ntohl(dump.id) != dumpID) {
	    LogDebug(4, "BUDB_FindClone: requested %d, found %d\n", dumpID,
		     ntohl(dump.id));
	    ABORT(BUDB_INTERNALERROR);
	}

	/* search all the tapes in this dump */
	for (tapeAddr = ntohl(dump.firstTape); tapeAddr; tapeAddr = ntohl(tape.nextTape)) {	/*t */
	    /* Get the tape entry */
	    eval = dbread(ut, tapeAddr, &tape, sizeof(tape));
	    if (eval)
		ABORT(eval);

	    /* search all the volume fragments on this tape */
	    for (volFragmentAddr = ntohl(tape.firstVol); volFragmentAddr; volFragmentAddr = ntohl(volFragment.sameTapeChain)) {	/*vf */
		/* Get the volume fragment entry */
		eval =
		    dbread(ut, volFragmentAddr, &volFragment,
			   sizeof(volFragment));
		if (eval)
		    ABORT(eval);

		/* Get the volume info entry */
		eval =
		    dbread(ut, ntohl(volFragment.vol), &volInfo,
			   sizeof(volInfo));
		if (eval)
		    ABORT(eval);

		/* check if this volume is the one we want */
		if (strcmp(volInfo.name, volName) == 0) {
		    *clonetime = ntohl(volFragment.clone);
		    ERROR(0);
		}
	    }			/*vf */
	}			/*t */
    }				/*d */

  error_exit:
    code = ubik_EndTrans(ut);
    return (code);

  abort_exit:
    ubik_EndTrans(ut);
    return (code);
}
#endif

/* BUDB_FindDump
 *      Find latest volume dump before adate.
 *      Used by restore code when restoring a user requested volume(s)
 * entry:
 *      volumeName - name of volume to match on
 *      beforeDate - look for dumps older than this date
 * exit:
 *      deptr - descriptor of most recent dump
 */

afs_int32
SBUDB_FindDump(struct rx_call *call, char *volumeName, afs_int32 beforeDate,
	       struct budb_dumpEntry *deptr)
{
    afs_int32 code;

    code = FindDump(call, volumeName, beforeDate, deptr);
    osi_auditU(call, BUDB_FndDmpEvent, code, AUD_STR, volumeName, AUD_END);
    return code;
}

afs_int32
FindDump(struct rx_call *call, char *volumeName, afs_int32 beforeDate,
	 struct budb_dumpEntry *deptr)
{
    struct ubik_trans *ut;
    dbadr volInfoAddr, volFragmentAddr;
    struct tape tape;
    struct volInfo volInfo;
    struct volFragment volFragment;

    dbadr selectedDumpAddr = 0;
    afs_int32 selectedDate = 0;
    afs_int32 volCloned;
    int rvoli;
    afs_int32 eval, code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    eval = InitRPC(&ut, LOCKREAD, 1);
    if (eval)
	return eval;

    /* Find volinfo struct for volume name in hash table */
    eval =
	ht_LookupEntry(ut, &db.volName, volumeName, &volInfoAddr, &volInfo);
    if (eval)
	ABORT(eval);
    if (!volInfoAddr)
	ABORT(BUDB_NOVOLUMENAME);

    /* Step through all the volinfo structures on the same name chain.
     * No need to read the first - we read it above.
     */
    for (rvoli = 0; volInfoAddr;
	 rvoli = 1, volInfoAddr = ntohl(volInfo.sameNameChain)) {
	if (rvoli) {		/* read the volinfo structure */
	    eval = dbread(ut, volInfoAddr, &volInfo, sizeof(volInfo));
	    if (eval)
		ABORT(eval);
	}

	/* step through the volfrag structures */
	for (volFragmentAddr = ntohl(volInfo.firstFragment); volFragmentAddr;
	     volFragmentAddr = ntohl(volFragment.sameNameChain)) {
	    /* read the volfrag struct */
	    eval =
		dbread(ut, volFragmentAddr, &volFragment,
		       sizeof(volFragment));
	    if (eval)
		ABORT(eval);

	    volCloned = ntohl(volFragment.clone);

	    /* now we can examine the date for most recent dump */
	    if ((volCloned > selectedDate) && (volCloned < beforeDate)) {
		/* from the volfrag struct, read the tape struct */
		eval =
		    dbread(ut, ntohl(volFragment.tape), &tape, sizeof(tape));
		if (eval)
		    ABORT(eval);

		selectedDate = volCloned;
		selectedDumpAddr = ntohl(tape.dump);
	    }
	}
    }

    if (!selectedDumpAddr)
	ABORT(BUDB_NOENT);

    eval = FillDumpEntry(ut, selectedDumpAddr, deptr);
    if (eval)
	ABORT(eval);

    code = ubik_EndTrans(ut);
    return (code);

  abort_exit:
    ubik_EndTrans(ut);
    return (code);
}

/* BUDB_FindLatestDump
 *	Find the latest dump of volumeset vsname with dump name dname.
 * entry:
 *	vsname - volumeset name
 *	dname - dumpname
 */

afs_int32
SBUDB_FindLatestDump(struct rx_call *call, char *vsname, char *dumpPath,
		     struct budb_dumpEntry *dumpentry)
{
    afs_int32 code;

    code = FindLatestDump(call, vsname, dumpPath, dumpentry);
    osi_auditU(call, BUDB_FndLaDEvent, code, AUD_STR, vsname, AUD_END);
    return code;
}

afs_int32
FindLatestDump(struct rx_call *call, char *vsname, char *dumpPath,
	       struct budb_dumpEntry *dumpentry)
{
    struct ubik_trans *ut;
    dbadr curdbaddr, retdbaddr, firstdbaddr;
    struct dump d;
    Date latest;
    char dumpName[BU_MAXNAMELEN + 2];
    afs_int32 eval, code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    eval = InitRPC(&ut, LOCKREAD, 1);
    if (eval)
	return (eval);

    if ((strcmp(vsname, "") == 0) && (strcmp(dumpPath, "") == 0)) {
	/* Construct a database dump name */
	strcpy(dumpName, DUMP_TAPE_NAME);
    } else if (strchr(dumpPath, '/') == 0) {
	int level, old, length, hash;
	struct dump hostDump, diskDump;
	struct memoryHashTable *mht;
	int entrySize;
	dbadr dbAddr;
	afs_uint32 bestDumpId = 0;

	level = atoi(dumpPath);
	if (level < 0) {
	    ABORT(BUDB_BADARGUMENT);
	}

	/* Brute force search of all the dumps in the database - yuck! */

	retdbaddr = 0;
	mht = ht_GetType(HT_dumpIden_FUNCTION, &entrySize);
	if (!mht)
	    ABORT(BUDB_BADARGUMENT);

	for (old = 0; old <= 1; old++) {	/*fo */
	    length = (old ? mht->oldLength : mht->length);
	    if (!length)
		continue;

	    for (hash = 0; hash < length; hash++) {
		/*f */
		for (dbAddr = ht_LookupBucket(ut, mht, hash, old); dbAddr;
		     dbAddr = hostDump.idHashChain) {
		    /*w */
		    eval = dbread(ut, dbAddr, &diskDump, sizeof(diskDump));
		    if (eval)
			ABORT(eval);
		    dump_ntoh(&diskDump, &hostDump);

		    if ((strcmp(hostDump.volumeSet, vsname) == 0) &&	/* the volumeset */
			(hostDump.level == level) &&	/* same level */
			(hostDump.id > bestDumpId)) {	/* more recent */
			bestDumpId = hostDump.id;
			retdbaddr = dbAddr;
		    }
		}		/*w */
	    }			/*f */
	}			/*fo */
	if (!retdbaddr)
	    ABORT(BUDB_NODUMPNAME);

	goto finished;
    } else {
	/* construct the name of the dump */
	if ((strlen(vsname) + strlen(tailCompPtr(dumpPath))) > BU_MAXNAMELEN)
	    ABORT(BUDB_NODUMPNAME);

	strcpy(dumpName, vsname);
	strcat(dumpName, ".");
	strcat(dumpName, tailCompPtr(dumpPath));
    }

    LogDebug(5, "lookup on :%s:\n", dumpName);

    /* Lookup on dumpname in hash table */
    eval = ht_LookupEntry(ut, &db.dumpName, dumpName, &firstdbaddr, &d);
    if (eval)
	ABORT(eval);

    latest = 0;
    retdbaddr = 0;

    /* folow remaining dumps in hash chain, looking for most latest dump */
    for (curdbaddr = firstdbaddr; curdbaddr;
	 curdbaddr = ntohl(d.nameHashChain)) {
	if (curdbaddr != firstdbaddr) {
	    eval = dbread(ut, curdbaddr, &d, sizeof(d));
	    if (eval)
		ABORT(eval);
	}

	if ((strcmp(d.dumpPath, dumpPath) == 0) &&	/* Same dumppath */
	    (strcmp(d.dumpName, dumpName) == 0) &&	/* Same dumpname */
	    (ntohl(d.created) > latest)) {	/* most recent */
	    latest = ntohl(d.created);
	    retdbaddr = curdbaddr;
	}
    }
    if (!retdbaddr)
	ABORT(BUDB_NODUMPNAME);

  finished:
    /* return the dump found */
    FillDumpEntry(ut, retdbaddr, dumpentry);

    code = ubik_EndTrans(ut);
    return (code);

  abort_exit:
    ubik_AbortTrans(ut);
    return (code);
}


afs_int32
SBUDB_FinishDump(struct rx_call *call, struct budb_dumpEntry *dump)
{
    afs_int32 code;

    code = FinishDump(call, dump);
    osi_auditU(call, BUDB_FinDmpEvent, code, AUD_DATE, (dump ? dump->id : 0),
	       AUD_END);
    return code;
}

afs_int32
FinishDump(struct rx_call *call, struct budb_dumpEntry *dump)
{
    struct ubik_trans *ut;
    dbadr a;
    struct dump d;
    afs_int32 eval, code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    eval = InitRPC(&ut, LOCKWRITE, 1);
    if (eval)
	return eval;

    eval = ht_LookupEntry(ut, &db.dumpIden, &dump->id, &a, &d);
    if (eval)
	ABORT(eval);
    if (!a)
	ABORT(BUDB_NODUMPID);

    if ((ntohl(d.flags) & BUDB_DUMP_INPROGRESS) == 0)
	ABORT(BUDB_DUMPNOTINUSE);

    d.flags = htonl(dump->flags & ~BUDB_DUMP_INPROGRESS);

    /* if creation time specified set it */
    if (dump->created)
	d.created = htonl(dump->created);
    dump->created = ntohl(d.created);

    /* Write the dump entry out */
    eval = dbwrite(ut, a, &d, sizeof(d));
    if (eval)
	ABORT(eval);

    eval = set_header_word(ut, lastUpdate, htonl(time(0)));
    if (eval)
	ABORT(eval);

    code = ubik_EndTrans(ut);
    return code;

  abort_exit:
    ubik_AbortTrans(ut);
    return code;
}

afs_int32
SBUDB_FinishTape(struct rx_call *call, struct budb_tapeEntry *tape)
{
    afs_int32 code;

    code = FinishTape(call, tape);
    osi_auditU(call, BUDB_FinTpeEvent, code, AUD_DATE,
	       (tape ? tape->dump : 0), AUD_END);
    return code;
}

afs_int32
FinishTape(struct rx_call *call, struct budb_tapeEntry *tape)
{
    struct ubik_trans *ut;
    dbadr a;
    struct tape t;
    struct dump d;
    afs_int32 eval, code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    eval = InitRPC(&ut, LOCKWRITE, 1);
    if (eval)
	return eval;

    /* find the tape struct in the tapename hash chain */
    eval = ht_LookupEntry(ut, &db.tapeName, tape->name, &a, &t);
    if (eval)
	ABORT(eval);
    if (!a)
	ABORT(BUDB_NOTAPENAME);

    /* Read the dump structure */
    eval = dbread(ut, ntohl(t.dump), &d, sizeof(d));
    if (eval)
	ABORT(eval);

    /* search for the right tape on the rest of the chain */
    while (ntohl(d.id) != tape->dump) {
	a = ntohl(t.nameHashChain);
	if (!a)
	    ABORT(BUDB_NOTAPENAME);

	eval = dbread(ut, a, &t, sizeof(t));
	if (eval)
	    ABORT(eval);

	eval = dbread(ut, ntohl(t.dump), &d, sizeof(d));
	if (eval)
	    ABORT(eval);
    }

    if ((ntohl(t.flags) & BUDB_TAPE_BEINGWRITTEN) == 0)
	ABORT(BUDB_TAPENOTINUSE);

    /* t.nBytes = htonl(tape->nBytes); */
    t.nFiles = htonl(tape->nFiles);
    t.useKBytes = htonl(tape->useKBytes);
    t.flags = htonl(tape->flags & ~BUDB_TAPE_BEINGWRITTEN);

    eval = dbwrite(ut, a, &t, sizeof(t));
    if (eval)
	ABORT(BUDB_IO);

    eval = set_header_word(ut, lastUpdate, htonl(time(0)));
    if (eval)
	ABORT(eval);

    code = ubik_EndTrans(ut);
    return code;

  abort_exit:
    ubik_AbortTrans(ut);
    return code;
}

/* BUDB_GetDumps
 *	return a set of dumps that match the specified criteria
 * entry:
 *	call - rx call
 *	majorVersion - version of interface structures. Permits compatibility
 *		checks to be made
 *	flags - for search and select operations. Broken down into flags
 *		for name, start point, end point and time.
 *	name - name to search for. Interpretation based on flags
 *	end
 *	index
 *	nextIndexP
 *	dbTimeP
 * exit:
 *	nextIndexP
 *	dbTimeP - time at which the database was last modified. Up to
 *		caller (client) to take appropriate action if database
 *		modified between successive calls
 *	dumps - list of matching dumps
 * notes:
 *	currently supported are:
 *	BUDB_OP_DUMPNAME
 *	BUDB_OP_DUMPID
 */

afs_int32
SBUDB_GetDumps(struct rx_call *call,
	       afs_int32 majorVersion,	/* version of interface structures */
	       afs_int32 flags,		/* search & select controls */
	       char *name,		/* s&s parameters */
	       afs_int32 start,
	       afs_int32 end,
	       afs_int32 index,		/* start index of returned entries */
	       afs_int32 *nextIndexP,	/* output index for next call */
	       afs_int32 *dbTimeP,
	       budb_dumpList *dumps)	/* pointer to buffer */
{
    afs_int32 code;

    code =
	GetDumps(call, majorVersion, flags, name, start, end, index,
		 nextIndexP, dbTimeP, dumps);
    osi_auditU(call, BUDB_GetDmpEvent, code, AUD_END);
    return code;
}

afs_int32
GetDumps(struct rx_call *call,
	 afs_int32 majorVersion, /* version of interface structures */
	 afs_int32 flags,	 /* search & select controls */
	 char *name,		 /* s&s parameters */
	 afs_int32 start,
	 afs_int32 end,
	 afs_int32 index,	 /* start index of returned entries */
	 afs_int32 *nextIndexP,	 /* output index for next call */
	 afs_int32 *dbTimeP,
	 budb_dumpList *dumps)	 /* pointer to buffer */
{
    struct ubik_trans *ut;
    dbadr da;
    struct dump d;
    afs_int32 nameFlags, startFlags, endFlags, timeFlags;
    afs_int32 eval, code = 0;
    afs_int32 toskip;
    struct returnList list;

    /* Don't check permissions when we look up a specific dump id */
    if (((flags & BUDB_OP_STARTS) != BUDB_OP_DUMPID) && !callPermitted(call))
	return BUDB_NOTPERMITTED;

    if (majorVersion != BUDB_MAJORVERSION)
	return BUDB_OLDINTERFACE;
    if (index < 0)
	return BUDB_ENDOFLIST;

    eval = InitRPC(&ut, LOCKREAD, 1);
    if (eval)
	return eval;

    nameFlags = flags & BUDB_OP_NAMES;
    startFlags = flags & BUDB_OP_STARTS;
    endFlags = flags & BUDB_OP_ENDS;
    timeFlags = flags & BUDB_OP_TIMES;

    InitReturnList(&list);
    toskip = index;

    if (nameFlags == BUDB_OP_DUMPNAME) {
	/* not yet implemented */
	if (startFlags || endFlags || timeFlags)
	    ABORT(BUDB_BADFLAGS);

	eval = ht_LookupEntry(ut, &db.dumpName, name, &da, &d);
	if (eval)
	    ABORT(eval);
	if (!da)
	    ABORT(BUDB_NODUMPNAME);

	while (1) {
	    if (strcmp(d.dumpName, name) == 0) {
		eval = AddToReturnList(&list, da, &toskip);
		if (eval == BUDB_LIST2BIG)
		    break;
		if (eval)
		    ABORT(eval);
	    }

	    da = ntohl(d.nameHashChain);	/* get next dump w/ name */
	    if (!da)
		break;

	    eval = dbread(ut, da, &d, sizeof(d));
	    if (eval)
		ABORT(eval);
	}
    } else if (nameFlags == BUDB_OP_VOLUMENAME) {
#ifdef PA
	struct volInfo vi;

	LogError(0, "NYI, BUDB_OP_VOLUMENAME\n");
	ABORT(BUDB_BADFLAGS);


	if (startFlags != BUDB_OP_STARTTIME)
	    ABORT(BUDB_BADFLAGS);

	/* lookup a dump by volumename and time stamp. Find the most recent
	 * dump of the specified volumename, that occured before the supplied
	 * time
	 */

	/* get us a volInfo for name */
	eval = ht_LookupEntry(ut, &db.volName, name, &da, &vi);
	if (eval)
	    ABORT(eval);

	while (1) {
	    /* now iterate over all the entries of this name */
	    for (va = vi.firstFragment; va != 0; va = v.sameNameChain) {
		va = ntohl(va);
		eval = dbread(ut, va, &v, sizeof(v));
		if (eval)
		    ABORT(eval);

		if date
		    on fragment > date ignore it - too recent;

		if (date on fragment < date && date on fragment > bestfound)
		    bestfound = date on fragment;

	    }			/* for va */

	    da = vi.sameNameChain;
	    if (da == 0)
		break;
	    da = ntohl(da);
	    eval = dbread(ut, da, &vi, sizeof(vi));
	    if (eval)
		ABORT(eval);
	}

/*
	if nothing found
		return error

	from saved volfragment address, compute dump.
	otherwise, return dump found
*/

#endif /* PA */

    } else if (startFlags == BUDB_OP_DUMPID) {
	if (endFlags || timeFlags)
	    ABORT(BUDB_BADFLAGS);
	if (nameFlags)
	    ABORT(BUDB_BADFLAGS);	/* NYI */

	eval = ht_LookupEntry(ut, &db.dumpIden, &start, &da, &d);
	if (eval)
	    ABORT(eval);
	if (!da)
	    ABORT(BUDB_NODUMPID);

	eval = AddToReturnList(&list, da, &toskip);
	if (eval)
	    ABORT(eval);
    } else if (endFlags == BUDB_OP_NPREVIOUS) {
	struct wantDumpRock rock;
	struct chosenDump *ptr, *nextPtr;

	/* no other flags should be set */

	/* end specifies how many dumps */
	if (!end)
	    ABORT(BUDB_BADFLAGS);

	memset(&rock, 0, sizeof(rock));
	rock.maxDumps = end;

	scanHashTable(ut, &db.dumpName, wantDump, rememberDump,
		      (char *)&rock);

	for (ptr = rock.chain; ptr; ptr = nextPtr) {
	    nextPtr = ptr->next;
	    AddToReturnList(&list, ptr->addr, &toskip);	/* ignore error for free */
	    free(ptr);
	}
    } else {
	ABORT(BUDB_BADFLAGS);
    }

    eval =
	SendReturnList(ut, &list, FillDumpEntry,
		       sizeof(struct budb_dumpEntry), index, nextIndexP,
		       dbTimeP, (returnList_t) dumps);
    if (eval)
	ABORT(eval);

    FreeReturnList(&list);
    code = ubik_EndTrans(ut);
    return code;

  abort_exit:
    FreeReturnList(&list);
    ubik_AbortTrans(ut);
    return code;
}

/*
 * Get the expiration of a tape.  Since the dump could have appended dumps,
 * we should use the most recent expiration date. Put the most recent
 * expiration tape into the given tape structure.
 */
afs_int32
getExpiration(struct ubik_trans *ut, struct tape *tapePtr)
{
    dbadr ad;
    struct dump d;
    struct tape t;
    afs_int32 initDump;
    afs_int32 eval, code = 0;

    if (!tapePtr)
	ERROR(0);

    /* Get the dump for this tape */
    ad = ntohl(tapePtr->dump);
    eval = dbread(ut, ad, &d, sizeof(d));
    if (eval)
	ERROR(eval);

    /* If not an initial dump, get the initial dump */
    if (d.initialDumpID) {
	initDump = ntohl(d.initialDumpID);
	eval = ht_LookupEntry(ut, &db.dumpIden, &initDump, &ad, &d);
	if (eval)
	    ERROR(eval);
    }

    /* Cycle through the dumps and appended dumps */
    while (ad) {
	/* Get the first tape in this dump. No need to check the rest of the tapes */
	/* for this dump since they will all have the same expiration date */
	eval = dbread(ut, ntohl(d.firstTape), &t, sizeof(t));
	if (eval)
	    ERROR(eval);

	/* Take the greater of the expiration dates */
	if (ntohl(tapePtr->expires) < ntohl(t.expires))
	    tapePtr->expires = t.expires;

	/* Step to and read the next appended dump */
	if ((ad = ntohl(d.appendedDumpChain))) {
	    eval = dbread(ut, ad, &d, sizeof(d));
	    if (eval)
		ERROR(eval);
	}
    }

  error_exit:
    return (code);
}

/* Mark the following dump as appended to another, intial dump */
afs_int32
makeAppended(struct ubik_trans *ut, afs_int32 appendedDumpID,
	     afs_int32 initialDumpID, afs_int32 startTapeSeq)
{
    dbadr ada, da, lastDumpAddr;
    struct dump ad, d;
    afs_int32 eval, code = 0;

    if (!initialDumpID)
	ERROR(0);
    if (appendedDumpID == initialDumpID)
	ERROR(BUDB_INTERNALERROR);

    /* If there is an initial dump, append this dump to it */
    /* Find the appended dump via its id */
    eval = ht_LookupEntry(ut, &db.dumpIden, &appendedDumpID, &ada, &ad);
    if (eval)
	ERROR(eval);

    /* If the dump is already marked as appended,
     * then we have an internal error.
     */
    if (ad.initialDumpID) {
	if (ntohl(ad.initialDumpID) != initialDumpID)
	    ERROR(BUDB_INTERNALERROR);
    }

    /* Update the appended dump to point to the initial dump */
    ad.initialDumpID = htonl(initialDumpID);
    ad.tapes.b = htonl(startTapeSeq);

    /* find the initial dump via its id */
    eval = ht_LookupEntry(ut, &db.dumpIden, &initialDumpID, &da, &d);
    if (eval)
	ERROR(eval);

    /* Update the appended dump's tape format with that of the initial */
    strcpy(ad.tapes.format, d.tapes.format);

    /* starting with the initial dump step through its appended dumps till
     * we reach the last appended dump.
     */
    lastDumpAddr = da;
    while (d.appendedDumpChain) {
	lastDumpAddr = ntohl(d.appendedDumpChain);
	if (lastDumpAddr == ada)
	    ERROR(0);		/* Already appended */
	eval = dbread(ut, lastDumpAddr, &d, sizeof(d));
	if (eval)
	    ERROR(eval);
    }

    /* Update the last dump to point to our new appended dump.
     * The appended dump is the last one in the dump chain.
     */
    d.appendedDumpChain = htonl(ada);
    ad.appendedDumpChain = 0;

    /* Write the appended dump and the initial dump */
    eval = dbwrite(ut, ada, (char *)&ad, sizeof(ad));
    if (eval)
	ERROR(eval);

    eval = dbwrite(ut, lastDumpAddr, (char *)&d, sizeof(d));
    if (eval)
	ERROR(eval);

    eval = set_header_word(ut, lastUpdate, htonl(time(0)));
    if (eval)
	ERROR(eval);

  error_exit:
    return (code);
}

afs_int32
SBUDB_MakeDumpAppended(struct rx_call *call, afs_int32 appendedDumpID,
		       afs_int32 initialDumpID, afs_int32 startTapeSeq)
{
    afs_int32 code;

    code =
	MakeDumpAppended(call, appendedDumpID, initialDumpID, startTapeSeq);
    osi_auditU(call, BUDB_AppDmpEvent, code, AUD_LONG, appendedDumpID,
	       AUD_END);
    return code;
}

afs_int32
MakeDumpAppended(struct rx_call *call, afs_int32 appendedDumpID,
		 afs_int32 initialDumpID, afs_int32 startTapeSeq)
{
    struct ubik_trans *ut;
    afs_int32 eval, code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    eval = InitRPC(&ut, LOCKWRITE, 1);
    if (eval)
	return (eval);

    eval = makeAppended(ut, appendedDumpID, initialDumpID, startTapeSeq);
    if (eval)
	ABORT(eval);

    code = ubik_EndTrans(ut);
    return (code);

  abort_exit:
    ubik_AbortTrans(ut);
    return (code);
}

/* Find the last tape of a dump-set. This includes any appended dumps */
afs_int32
SBUDB_FindLastTape(struct rx_call *call, afs_int32 dumpID,
		   struct budb_dumpEntry *dumpEntry,
		   struct budb_tapeEntry *tapeEntry,
		   struct budb_volumeEntry *volEntry)
{
    afs_int32 code;

    code = FindLastTape(call, dumpID, dumpEntry, tapeEntry, volEntry);
    osi_auditU(call, BUDB_FndLTpeEvent, code, AUD_LONG, dumpID, AUD_END);
    return code;
}

afs_int32
FindLastTape(struct rx_call *call, afs_int32 dumpID,
	     struct budb_dumpEntry *dumpEntry,
	     struct budb_tapeEntry *tapeEntry,
	     struct budb_volumeEntry *volEntry)
{
    struct ubik_trans *ut;
    struct dump d;
    dbadr lastDump;
    struct tape t;
    dbadr lastTape, thisTape;
    afs_int32 lastTapeSeq;
    struct volFragment vf;
    dbadr lastVol, thisVol;
    afs_int32 lastVolPos;
    afs_int32 eval, code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    if (!dumpID)
	return (BUDB_BADARGUMENT);

    eval = InitRPC(&ut, LOCKREAD, 1);
    if (eval)
	return (eval);

    /* find and read its initial dump via its id */
    eval = ht_LookupEntry(ut, &db.dumpIden, &dumpID, &lastDump, &d);
    if (eval)
	ABORT(eval);
    if (!lastDump)
	ABORT(BUDB_NODUMPID);

    /* Follow the append dumps link chain until we reach the last dump */
    while (d.appendedDumpChain) {
	lastDump = ntohl(d.appendedDumpChain);
	eval = dbread(ut, lastDump, &d, sizeof(d));
	if (eval)
	    ABORT(eval);
    }

    /* We now have the last dump of the last appended dump */
    /* Copy this into our return structure */
    eval = FillDumpEntry(ut, lastDump, dumpEntry);
    if (eval)
	ABORT(eval);

    /* Fail if the last dump has no tapes */
    if (!d.firstTape)
	ABORT(BUDB_NOTAPENAME);

    /* Follow the tapes in this dump until we reach the last tape */
    eval = dbread(ut, ntohl(d.firstTape), &t, sizeof(t));
    if (eval)
	ABORT(eval);

    lastTape = ntohl(d.firstTape);
    lastTapeSeq = ntohl(t.seq);
    lastVol = ntohl(t.firstVol);

    while (t.nextTape) {
	thisTape = ntohl(t.nextTape);
	eval = dbread(ut, thisTape, &t, sizeof(t));
	if (eval)
	    ABORT(eval);

	if (ntohl(t.seq) > lastTapeSeq) {
	    lastTape = thisTape;
	    lastTapeSeq = ntohl(t.seq);
	    lastVol = ntohl(t.firstVol);
	}
    }

    /* We now have the last tape of the last appended dump */
    /* Copy this into our return structure */
    eval = FillTapeEntry(ut, lastTape, tapeEntry);
    if (eval)
	ABORT(eval);

    /* Zero volume entry if the last tape has no volumes */
    if (!lastVol) {
	memset(volEntry, 0, sizeof(*volEntry));
    } else {
	/* Follow the volumes until we reach the last volume */
	eval = dbread(ut, lastVol, &vf, sizeof(vf));
	if (eval)
	    ABORT(eval);

	lastVolPos = vf.position;

	while (vf.sameTapeChain) {
	    thisVol = ntohl(vf.sameTapeChain);
	    eval = dbread(ut, thisVol, &vf, sizeof(vf));
	    if (eval)
		ABORT(eval);

	    if (vf.position > lastVolPos) {
		lastVol = thisVol;
		lastVolPos = vf.position;
	    }
	}

	/* We now have the last volume of this tape */
	/* Copy this into our return structure */
	eval = FillVolEntry(ut, lastVol, volEntry);
	if (eval)
	    ABORT(eval);
    }

    eval = ubik_EndTrans(ut);
    if (!code)
	code = eval;
    return (code);

  abort_exit:
    ubik_AbortTrans(ut);
    return (code);
}


afs_int32
SBUDB_GetTapes(struct rx_call *call,
 	       afs_int32 majorVersion,	/* version of interface structures */
	       afs_int32 flags,		/* search & select controls */
	       char *name,		/* s&s parameters */
	       afs_int32 start,
	       afs_int32 end,		/* reserved: MBZ */
	       afs_int32 index,		/* start index of returned entries */
	       afs_int32 *nextIndexP,	/* output index for next call */
	       afs_int32 *dbTimeP,
	       budb_tapeList *tapes)	/* pointer to buffer */
{
    afs_int32 code;

    code =
	GetTapes(call, majorVersion, flags, name, start, end, index,
		 nextIndexP, dbTimeP, tapes);
    osi_auditU(call, BUDB_GetTpeEvent, code, AUD_END);
    return code;
}

afs_int32
GetTapes(struct rx_call *call,
	 afs_int32 majorVersion, /* version of interface structures */
	 afs_int32 flags,	 /* search & select controls */
	 char *name,		 /* s&s parameters */
	 afs_int32 start,
	 afs_int32 end,		 /* reserved: MBZ */
	 afs_int32 index,	 /* start index of returned entries */
	 afs_int32 *nextIndexP,  /* output index for next call */
	 afs_int32 *dbTimeP,
	 budb_tapeList *tapes)	 /* pointer to buffer */
{
    struct ubik_trans *ut;
    dbadr da, ta;
    struct dump d;
    struct tape t;
    afs_int32 nameFlags, startFlags, endFlags, timeFlags;
    struct returnList list;
    afs_int32 eval, code = 0;
    afs_int32 toskip;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    if (majorVersion != BUDB_MAJORVERSION)
	return BUDB_OLDINTERFACE;

    if (index < 0)
	return BUDB_ENDOFLIST;

    eval = InitRPC(&ut, LOCKREAD, 1);
    if (eval)
	return eval;

    nameFlags = flags & BUDB_OP_NAMES;
    startFlags = flags & BUDB_OP_STARTS;
    endFlags = flags & BUDB_OP_ENDS;
    timeFlags = flags & BUDB_OP_TIMES;

    InitReturnList(&list);
    toskip = index;

    if (nameFlags == BUDB_OP_TAPENAME) {	/*it */
	eval = ht_LookupEntry(ut, &db.tapeName, name, &ta, &t);
	if (eval)
	    ABORT(eval);
	if (!ta)
	    ABORT(BUDB_NOTAPENAME);

	/* NYI */
	if ((startFlags & ~BUDB_OP_DUMPID) || endFlags || timeFlags)
	    ABORT(BUDB_BADFLAGS);

	/* follow the hash chain to the end */
	while (ta) {		/*w */
	    if (startFlags & BUDB_OP_DUMPID) {
		/* read in the dump */
		eval = dbread(ut, ntohl(t.dump), &d, sizeof(d));
		if (eval)
		    ABORT(eval);

		/* check if both name and dump id match */
		if ((strcmp(name, t.name) == 0) && (ntohl(d.id) == start)) {
		    eval = AddToReturnList(&list, ta, &toskip);
		    if (eval && (eval != BUDB_LIST2BIG))
			ABORT(eval);
		    break;
		}
	    } else {
		/* Add to return list and continue search */
		if (strcmp(name, t.name) == 0) {
		    eval = AddToReturnList(&list, ta, &toskip);
		    if (eval == BUDB_LIST2BIG)
			break;
		    if (eval)
			ABORT(eval);
		}
	    }

	    ta = ntohl(t.nameHashChain);
	    if (ta)
		dbread(ut, ta, &t, sizeof(t));
	}			/*w */
    } /*it */
    else if (nameFlags == BUDB_OP_TAPESEQ) {
	eval = ht_LookupEntry(ut, &db.dumpIden, &start, &da, &d);
	if (eval)
	    ABORT(eval);
	if (!da)
	    ABORT(BUDB_NODUMPNAME);

	/* search for the right tape */
	ta = ntohl(d.firstTape);
	for (ta = ntohl(d.firstTape); ta; ta = ntohl(t.nextTape)) {
	    eval = dbread(ut, ta, &t, sizeof(t));
	    if (eval)
		ABORT(eval);

	    if (ntohl(t.seq) == end) {
		eval = AddToReturnList(&list, ta, &toskip);
		if (eval && (eval != BUDB_LIST2BIG))
		    ABORT(eval);
		break;
	    }
	}
    } else {
	ABORT(BUDB_BADFLAGS);
    }

    eval =
	SendReturnList(ut, &list, FillTapeEntry,
		       sizeof(struct budb_tapeEntry), index, nextIndexP,
		       dbTimeP, (returnList_t) tapes);
    if (eval)
	ABORT(eval);

    FreeReturnList(&list);
    code = ubik_EndTrans(ut);
    return code;

  abort_exit:
    FreeReturnList(&list);
    ubik_AbortTrans(ut);
    return (code);
}

/* BUDB_GetVolumes
 *	get a set of volumes according to the specified criteria.
 *	See BUDB_GetDumps for general information on parameters
 *	Currently supports:
 *	1) volume match - returns volumes based on volume name only.
 *	2) flags = BUDB_OP_DUMPID in which case name is a volume name
 *		and start is a dumpid. Returns all volumes of the specified
 *		name on the selected dumpid.
 */

afs_int32
SBUDB_GetVolumes(struct rx_call *call,
		 afs_int32 majorVersion, /* version of interface structures */
		 afs_int32 flags,	 /* search & select controls */
		 char *name,		 /*  - parameters for search */
		 afs_int32 start,	 /*  - usage depends which BUDP_OP */
		 afs_int32 end,		 /*  - bits are set */
		 afs_int32 index,	 /* start index of returned entries */
		 afs_int32 *nextIndexP,	 /* output index for next call */
		 afs_int32 *dbTimeP,
		 budb_volumeList *volumes) /* pointer to buffer */
{
    afs_int32 code;

    code =
	GetVolumes(call, majorVersion, flags, name, start, end, index,
		   nextIndexP, dbTimeP, volumes);
    osi_auditU(call, BUDB_GetVolEvent, code, AUD_END);
    return code;
}

afs_int32
GetVolumes(struct rx_call *call,
	   afs_int32 majorVersion,	/* version of interface structures */
	   afs_int32 flags,		/* search & select controls */
	   char *name,			/*  - parameters for search */
	   afs_int32 start,		/*  - usage depends which BUDP_OP_* */
	   afs_int32 end,		/*  - bits are set */
	   afs_int32 index,		/* start index of returned entries */
	   afs_int32 *nextIndexP,	/* output index for next call */
	   afs_int32 *dbTimeP,
	   budb_volumeList *volumes)	/* pointer to buffer */
{
    struct ubik_trans *ut;
    dbadr via;
    struct volInfo vi;
    afs_int32 nameFlags, startFlags, endFlags, timeFlags;
    afs_int32 eval, code = 0;
    struct returnList vollist;
    afs_int32 toskip;

    /* Don't check permissions when we look up a specific volume name */
    if (((flags & BUDB_OP_NAMES) != BUDB_OP_VOLUMENAME)
	&& !callPermitted(call))
	return BUDB_NOTPERMITTED;

    if (majorVersion != BUDB_MAJORVERSION)
	return BUDB_OLDINTERFACE;
    if (index < 0)
	return BUDB_ENDOFLIST;

    eval = InitRPC(&ut, LOCKREAD, 1);
    if (eval)
	return eval;

    nameFlags = flags & BUDB_OP_NAMES;
    startFlags = flags & BUDB_OP_STARTS;
    endFlags = flags & BUDB_OP_ENDS;
    timeFlags = flags & BUDB_OP_TIMES;

    InitReturnList(&vollist);
    toskip = index;

    /* lookup a the volume (specified by name) in the dump (specified by id) */
    if (nameFlags == BUDB_OP_VOLUMENAME) {
	/* dumpid permissible, all others off */
	if (((startFlags & ~BUDB_OP_DUMPID) != 0) || endFlags || timeFlags)
	    ABORT(BUDB_BADFLAGS);

	/* returns ptr to volinfo of requested name */
	eval = ht_LookupEntry(ut, &db.volName, name, &via, &vi);
	if (eval)
	    ABORT(eval);
	if (!via)
	    ABORT(BUDB_NOVOLUMENAME);

	/* Iterate over all volume fragments with this name */
	while (1) {
	    struct volFragment v;
	    afs_int32 va;

	    /* traverse all the volume fragments for this volume info structure */
	    for (va = vi.firstFragment; va; va = v.sameNameChain) {
		va = ntohl(va);
		eval = dbread(ut, va, &v, sizeof(v));
		if (eval)
		    ABORT(eval);

		if (startFlags & BUDB_OP_DUMPID) {
		    struct tape atape;
		    struct dump adump;

		    /* get the dump id for this fragment */
		    eval = dbread(ut, ntohl(v.tape), &atape, sizeof(atape));
		    if (eval)
			ABORT(eval);

		    eval =
			dbread(ut, ntohl(atape.dump), &adump, sizeof(adump));
		    if (eval)
			ABORT(BUDB_IO);

		    /* dump id does not match */
		    if (ntohl(adump.id) != start)
			continue;
		}

		eval = AddToReturnList(&vollist, va, &toskip);
		if (eval == BUDB_LIST2BIG)
		    break;
		if (eval)
		    ABORT(eval);
	    }
	    if (eval == BUDB_LIST2BIG)
		break;

	    via = vi.sameNameChain;
	    if (via == 0)
		break;
	    via = ntohl(via);

	    eval = dbread(ut, via, &vi, sizeof(vi));
	    if (eval)
		ABORT(eval);
	}
    } else if (((nameFlags == 0) || (nameFlags == BUDB_OP_TAPENAME))
	       && (startFlags == BUDB_OP_DUMPID)) {
	struct dump dump;
	dbadr dumpAddr;
	struct tape tape;
	dbadr tapeAddr;
	struct volFragment volFrag;
	dbadr volFragAddr;

	/* lookup all volumes for a specified dump id */

	/* no other flags should be set */
	if (endFlags || timeFlags)
	    ABORT(BUDB_BADFLAGS);

	/* find the dump */
	eval = ht_LookupEntry(ut, &db.dumpIden, &start, &dumpAddr, &dump);
	if (eval)
	    ABORT(eval);

	/* traverse all the tapes */
	for (tapeAddr = ntohl(dump.firstTape); tapeAddr; tapeAddr = ntohl(tape.nextTape)) {	/*w */
	    eval = dbread(ut, tapeAddr, &tape, sizeof(tape));
	    if (eval)
		ABORT(eval);

	    if ((nameFlags != BUDB_OP_TAPENAME)
		|| ((nameFlags == BUDB_OP_TAPENAME)
		    && (strcmp(tape.name, name) == 0))) {
		/* now return all the volumes */
		for (volFragAddr = ntohl(tape.firstVol); volFragAddr;
		     volFragAddr = ntohl(volFrag.sameTapeChain)) {
		    eval = dbread(ut, volFragAddr, &volFrag, sizeof(volFrag));
		    if (eval)
			ABORT(eval);

		    eval = AddToReturnList(&vollist, volFragAddr, &toskip);
		    if (eval == BUDB_LIST2BIG)
			break;
		    if (eval)
			ABORT(eval);
		}
	    }
	    if (eval == BUDB_LIST2BIG)
		break;
	}			/*w */
    } else {
	ABORT(BUDB_BADFLAGS);
    }

    eval =
	SendReturnList(ut, &vollist, FillVolEntry,
		       sizeof(struct budb_volumeEntry), index, nextIndexP,
		       dbTimeP, (returnList_t) volumes);
    if (eval)
	ABORT(eval);

  /* error_exit: */
    FreeReturnList(&vollist);
    code = ubik_EndTrans(ut);
    return code;

  abort_exit:
    FreeReturnList(&vollist);
    ubik_AbortTrans(ut);
    return code;
}

afs_int32
SBUDB_UseTape(struct rx_call *call,
	      struct budb_tapeEntry *tape,	/* tape info */
	      afs_int32 *new)			/* set if tape is new */
{
    afs_int32 code;

    code = UseTape(call, tape, new);
    osi_auditU(call, BUDB_UseTpeEvent, code, AUD_DATE,
	       (tape ? tape->dump : 0), AUD_END);
    return code;
}

afs_int32
UseTape(struct rx_call *call,
	struct budb_tapeEntry *tape,	/* tape info */
	int *new)			/* set if tape is new */
{
    struct ubik_trans *ut;
    dbadr da, a;
    struct dump d;
    struct tape t;
    afs_int32 eval, code;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    if (strlen(tape->name) >= sizeof(t.name))
	return BUDB_BADARGUMENT;

    eval = InitRPC(&ut, LOCKWRITE, 1);
    if (eval)
	return eval;

    *new = 0;

    memset(&t, 0, sizeof(t));
    eval = AllocStructure(ut, tape_BLOCK, 0, &a, &t);
    if (eval)
	ABORT(eval);

    strcpy(t.name, tape->name);

    eval = ht_HashIn(ut, &db.tapeName, a, &t);
    if (eval)
	ABORT(eval);

    *new = 1;

    /* Since deleting a tape may change the dump (if its the same one), read in
     * the dump after the call to DeleteTape. */

    eval = ht_LookupEntry(ut, &db.dumpIden, &tape->dump, &da, &d);
    if (eval)
	ABORT(eval);
    if (!da)
	ABORT(BUDB_NODUMPID);

    if (!tape->written)
	tape->written = time(0);	/* fill in tape struct */
    t.written = htonl(tape->written);
    t.expires = htonl(tape->expires);
    t.dump = htonl(da);
    t.seq = htonl(tape->seq);
    t.useCount = htonl(tape->useCount);
    t.labelpos = htonl(tape->labelpos);
    t.useKBytes = 0;
    t.flags = htonl(tape->flags | BUDB_TAPE_BEINGWRITTEN);

    t.nextTape = d.firstTape;	/* Chain the tape to the dump */
    d.firstTape = htonl(a);

    if (tape->seq >= ntohl(d.tapes.maxTapes))	/* inc # tapes in the dump */
	d.tapes.maxTapes = htonl(tape->seq);

    eval = dbwrite(ut, a, &t, sizeof(t));	/* write tape struct */
    if (eval)
	ABORT(eval);

    eval = dbwrite(ut, da, &d, sizeof(d));	/* write the dump struct */
    if (eval)
	ABORT(eval);

    eval = set_header_word(ut, lastUpdate, htonl(time(0)));
    if (eval)
	ABORT(eval);

    LogDebug(5, "added tape %s\n", tape->name);

    code = ubik_EndTrans(ut);
    return code;

  abort_exit:
    ubik_AbortTrans(ut);
    return code;

}


/* ---------------------------------------------
 * debug interface routines
 * ---------------------------------------------
 */

afs_int32
SBUDB_T_DumpHashTable(struct rx_call *call, afs_int32 type, char *filename)
{
    afs_int32 code;

    code = T_DumpHashTable(call, type, filename);
    osi_auditU(call, BUDB_TDmpHaEvent, code, AUD_STR, filename, AUD_END);
    return code;
}

afs_int32
T_DumpHashTable(struct rx_call *call, int type, char *filename)
{
    struct ubik_trans *ut;
    struct memoryHashTable *mht;
    int ent;
    afs_int32 eval, code = 0;
    char path[64];
    FILE *DUMP;

    int length;
    afs_uint32 hash;
    dbadr a, first_a;
    char e[sizeof(struct block)];	/* unnecessarily conservative */
    struct dump e_dump;
    struct tape e_tape;
    struct volInfo e_volinfo;
    int e_size;
    int old;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    if (strlen(filename) >= sizeof(path) - 5)
	return BUDB_BADARGUMENT;

    eval = InitRPC(&ut, LOCKWRITE, 1);
    if (eval)
	return eval;

    if ((mht = ht_GetType(type, &e_size)) == 0)
	return BUDB_BADARGUMENT;

    sprintf(path, "%s/%s", gettmpdir(), filename);

    DUMP = fopen(path, "w");
    if (!DUMP)
	ABORT(BUDB_BADARGUMENT);

    ent = 0;
    for (old = 0;; old++) {
	length = (old ? mht->oldLength : mht->length);
	if (length)
	    fprintf(DUMP, "Dumping %sHash Table:\n", (old ? "Old " : ""));

	for (hash = 0; hash < length; hash++) {
	    a = ht_LookupBucket(ut, mht, hash, old);
	    first_a = a;
	    while (a) {
		eval = dbread(ut, a, e, e_size);
		if (eval)
		    ABORT(eval);

		ent++;
		if (a == first_a)
		    fprintf(DUMP, "  in bucket %d at %d is ", hash, a);
		else
		    fprintf(DUMP, "    at %d is ", a);
		switch (type) {
		case HT_dumpIden_FUNCTION:
		    memcpy(&e_dump, e, sizeof(e_dump));
		    fprintf(DUMP, "%d\n", ntohl(e_dump.id));
		    break;
		case HT_dumpName_FUNCTION:
		    memcpy(&e_dump, e, sizeof(e_dump));
		    fprintf(DUMP, "%s\n", e_dump.dumpName);
		    break;
		case HT_tapeName_FUNCTION:
		    memcpy(&e_tape, e, sizeof(e_tape));
		    fprintf(DUMP, "%s\n", e_tape.name);
		    break;
		case HT_volName_FUNCTION:
		    memcpy(&e_volinfo, e, sizeof(e_volinfo));
		    fprintf(DUMP, "%s\n", e_volinfo.name);
		    break;
		}
		if ((ht_HashEntry(mht, e) % length) != hash)
		    ABORT(BUDB_DATABASEINCONSISTENT);
		a = ntohl(*(dbadr *) (e + mht->threadOffset));
	    }
	}
	if (old)
	    break;
    }

    fprintf(DUMP, "%d entries found\n", ent);
    if (ntohl(mht->ht->entries) != ent)
	ABORT(BUDB_DATABASEINCONSISTENT);

    code = ubik_EndTrans(ut);
    if (DUMP)
	fclose(DUMP);
    return code;

  abort_exit:
    ubik_AbortTrans(ut);
    if (DUMP)
	fclose(DUMP);
    return code;
}

afs_int32
SBUDB_T_GetVersion(struct rx_call *call, afs_int32 *majorVersion)
{
    afs_int32 code;

    code = T_GetVersion(call, majorVersion);
    osi_auditU(call, BUDB_TGetVrEvent, code, AUD_END);
    return code;
}

afs_int32
T_GetVersion(struct rx_call *call, int *majorVersion)
{
    struct ubik_trans *ut;
    afs_int32 code;

    code = InitRPC(&ut, LOCKREAD, 0);
    if (code)
	return (code);

    *majorVersion = BUDB_MAJORVERSION;

    code = ubik_EndTrans(ut);
    return (code);
}

/* BUDB_T_DumpDatabase
 *	dump as much of the database as possible int /tmp/<filename>
 */

afs_int32
SBUDB_T_DumpDatabase(struct rx_call *call, char *filename)
{
    afs_int32 code;

    code = T_DumpDatabase(call, filename);
    osi_auditU(call, BUDB_TDmpDBEvent, code, AUD_STR, filename, AUD_END);
    return code;
}

afs_int32
T_DumpDatabase(struct rx_call *call, char *filename)
{
    FILE *dumpfid;
    int entrySize;
    struct ubik_trans *ut;
    char *path = 0;
    dbadr dbAddr;
    int type, old, length, hash;
    struct memoryHashTable *mht;
    afs_int32 eval, code = 0;

    if (!callPermitted(call))
	return BUDB_NOTPERMITTED;

    path = (char *)malloc(strlen(gettmpdir()) + 1 + strlen(filename) + 1);
    if (!path)
	return (BUDB_INTERNALERROR);

    sprintf(path, "%s/%s", gettmpdir(), filename);

    dumpfid = fopen(path, "w");
    if (!dumpfid)
	return (BUDB_BADARGUMENT);

    eval = InitRPC(&ut, LOCKWRITE, 1);
    if (eval)
	return (eval);

    /* dump all items in the database */
    for (type = 1; type <= HT_MAX_FUNCTION; type++) {	/*ft */
	mht = ht_GetType(type, &entrySize);
	if (!mht)
	    ERROR(BUDB_BADARGUMENT);

	for (old = 0; old <= 1; old++) {	/*fo */
	    length = (old ? mht->oldLength : mht->length);
	    if (!length)
		continue;

	    fprintf(dumpfid, "Dumping %s Hash Table:\n", (old ? "Old " : ""));

	    for (hash = 0; hash < length; hash++) {	/*f */
		dbAddr = ht_LookupBucket(ut, mht, hash, old);

		while (dbAddr) {	/*w */
		    switch (type) {	/*s */
		    case HT_dumpIden_FUNCTION:
			{
			    struct dump hostDump, diskDump;

			    eval =
				cdbread(ut, dump_BLOCK, dbAddr, &diskDump,
					sizeof(diskDump));
			    if (eval)
				ERROR(eval);

			    fprintf(dumpfid,
				    "\ndumpId hash %d, entry at %u\n",
				    hash, dbAddr);
			    fprintf(dumpfid,
				    "----------------------------\n");
			    dump_ntoh(&diskDump, &hostDump);
			    printDump(dumpfid, &hostDump);
			    dbAddr = hostDump.idHashChain;
			}
			break;

		    case HT_dumpName_FUNCTION:
			{
			    struct dump hostDump, diskDump;

			    eval =
				cdbread(ut, dump_BLOCK, dbAddr, &diskDump,
					sizeof(diskDump));
			    if (eval)
				ERROR(eval);

			    fprintf(dumpfid,
				    "\ndumpname hash %d, entry at %u\n",
				    hash, dbAddr);
			    fprintf(dumpfid,
				    "----------------------------\n");
			    dump_ntoh(&diskDump, &hostDump);
			    printDump(dumpfid, &hostDump);
			    dbAddr = hostDump.nameHashChain;
			}
			break;

		    case HT_tapeName_FUNCTION:
			{
			    struct tape hostTape, diskTape;

			    eval =
				cdbread(ut, tape_BLOCK, dbAddr, &diskTape,
					sizeof(diskTape));
			    if (eval)
				ERROR(eval);

			    fprintf(dumpfid,
				    "\ntapename hash %d, entry at %u\n",
				    hash, dbAddr);
			    fprintf(dumpfid,
				    "----------------------------\n");
			    tape_ntoh(&diskTape, &hostTape);
			    printTape(dumpfid, &hostTape);
			    dbAddr = hostTape.nameHashChain;
			}
			break;

		    case HT_volName_FUNCTION:
			{
			    struct volInfo hostVolInfo, diskVolInfo;

			    eval =
				cdbread(ut, volInfo_BLOCK, dbAddr,
					&diskVolInfo, sizeof(diskVolInfo));
			    if (eval)
				ERROR(eval);

			    fprintf(dumpfid,
				    "\nvolname hash %d, entry at %u\n",
				    hash, dbAddr);
			    fprintf(dumpfid,
				    "----------------------------\n");
			    volInfo_ntoh(&diskVolInfo, &hostVolInfo);
			    printVolInfo(dumpfid, &hostVolInfo);
			    dbAddr = hostVolInfo.nameHashChain;

			    volFragsDump(ut, dumpfid,
					 hostVolInfo.firstFragment);
			}
			break;

		    default:
			fprintf(dumpfid, "unknown type %d\n", type);
			break;

		    }		/*s */
		}		/*w */
	    }			/*f */
	}			/*fo */
    }				/*ft */

  error_exit:
    code = ubik_EndTrans(ut);	/* is this safe if no ut started ? */
    if (dumpfid)
	fclose(dumpfid);
    if (path)
	free(path);
    return (code);
}

int
volFragsDump(struct ubik_trans *ut, FILE *dumpfid, dbadr dbAddr)
{
    struct volFragment hostVolFragment, diskVolFragment;
    afs_int32 code;

    while (dbAddr) {
	code =
	    cdbread(ut, volFragment_BLOCK, dbAddr, &diskVolFragment,
		    sizeof(diskVolFragment));
	if (code) {		/* don't be fussy about errors */
	    fprintf(dumpfid, "volFragsDump: Error reading database\n");
	    return (0);
	}

	fprintf(dumpfid, "\nvolfragment entry at %u\n", dbAddr);
	fprintf(dumpfid, "----------------------------\n");
	volFragment_ntoh(&diskVolFragment, &hostVolFragment);
	printVolFragment(dumpfid, &hostVolFragment);
	dbAddr = hostVolFragment.sameNameChain;
    }
    return (0);
}

#ifdef notdef
/* utilities - network to host conversion
 *	currently used for debug only
 */

void
volFragmentDiskToHost(struct volFragment *diskVfPtr,
		      struct volFragment *hostVfPtr)
{
    hostVfPtr->vol = ntohl(diskVfPtr->vol);
    hostVfPtr->sameNameChain = ntohl(diskVfPtr->sameNameChain);
    hostVfPtr->tape = ntohl(diskVfPtr->tape);
    hostVfPtr->sameTapeChain = ntohl(diskVfPtr->sameTapeChain);
    hostVfPtr->position = ntohl(diskVfPtr->position);
    hostVfPtr->clone = ntohl(diskVfPtr->clone);
    hostVfPtr->incTime = ntohl(diskVfPtr->incTime);
    hostVfPtr->startByte = ntohl(diskVfPtr->startByte);
    hostVfPtr->nBytes = ntohl(diskVfPtr->nBytes);
    hostVfPtr->flags = ntohs(diskVfPtr->flags);
    hostVfPtr->sequence = ntohs(diskVfPtr->sequence);
}

void
volInfoDiskToHost(struct volInfo *diskViPtr, struct volInfo *hostViPtr)
{
    strcpy(hostViPtr->name, diskViPtr->name);
    hostViPtr->nameHashChain = ntohl(diskViPtr->nameHashChain);
    hostViPtr->id = ntohl(diskViPtr->id);
    strcpy(hostViPtr->server, diskViPtr->server);
    hostViPtr->partition = ntohl(diskViPtr->partition);
    hostViPtr->flags = ntohl(diskViPtr->flags);
    hostViPtr->sameNameHead = ntohl(diskViPtr->sameNameHead);
    hostViPtr->sameNameChain = ntohl(diskViPtr->sameNameChain);
    hostViPtr->firstFragment = ntohl(diskViPtr->firstFragment);
    hostViPtr->nFrags = ntohl(diskViPtr->nFrags);
}

void
tapeDiskToHost(struct tape *diskTapePtr, struct tape *hostTapePtr)
{
    strcpy(hostTapePtr->name, diskTapePtr->name);
    hostTapePtr->nameHashChain = ntohl(diskTapePtr->nameHashChain);
    hostTapePtr->flags = ntohl(diskTapePtr->flags);

    /* tape id conversion here */
    hostTapePtr->written = ntohl(diskTapePtr->written);
    hostTapePtr->nBytes = ntohl(diskTapePtr->nBytes);
    hostTapePtr->nFiles = ntohl(diskTapePtr->nFiles);
    hostTapePtr->nVolumes = ntohl(diskTapePtr->nVolumes);
    hostTapePtr->seq = ntohl(diskTapePtr->seq);
    hostTapePtr->dump = ntohl(diskTapePtr->dump);
    hostTapePtr->nextTape = ntohl(diskTapePtr->nextTape);
    hostTapePtr->firstVol = ntohl(diskTapePtr->firstVol);
    hostTapePtr->useCount = ntohl(diskTapePtr->useCount);
}

void
dumpDiskToHost(struct dump *diskDumpPtr, struct dump *hostDumpPtr)
{
    hostDumpPtr->id = ntohl(diskDumpPtr->id);
    hostDumpPtr->idHashChain = ntohl(diskDumpPtr->idHashChain);
    strcpy(hostDumpPtr->dumpName, diskDumpPtr->dumpName);
    strcpy(hostDumpPtr->dumpPath, diskDumpPtr->dumpPath);
    strcpy(hostDumpPtr->volumeSet, diskDumpPtr->volumeSet);
    hostDumpPtr->nameHashChain = ntohl(diskDumpPtr->nameHashChain);
    hostDumpPtr->flags = ntohl(diskDumpPtr->flags);
    hostDumpPtr->parent = ntohl(diskDumpPtr->parent);
    hostDumpPtr->created = ntohl(diskDumpPtr->created);
/*  hostDumpPtr->incTime = ntohl(diskDumpPtr->incTime); */
    hostDumpPtr->nVolumes = ntohl(diskDumpPtr->nVolumes);

    /* tapeset conversion here */

    hostDumpPtr->firstTape = ntohl(diskDumpPtr->firstTape);

    /* principal conversion here */
}

#endif /* notdef */

int
checkHash(struct ubik_trans *ut, int hashType)
{
    struct memoryHashTable *mhtPtr;
    int entrySize, hashTableLength;
    int bucket;
    int old;
    afs_int32 code = 0;

    mhtPtr = ht_GetType(hashType, &entrySize);
    if (mhtPtr == 0)
	ERROR(-1);

    for (old = 0; old < 1; old++) {
	LogDebug(5, "\nold = %d\n", old);
	printMemoryHashTable(stdout, mhtPtr);
	LogDebug(5, "\n");
	hashTableLength = (old ? mhtPtr->oldLength : mhtPtr->length);

	for (bucket = 0; bucket < hashTableLength; bucket++) {
	    dbadr entryAddr;

	    entryAddr = ht_LookupBucket(ut, mhtPtr, bucket, old);
	    while (entryAddr != 0) {
		LogDebug(6, "bucket %d has disk addr %d\n", bucket,
			 entryAddr);
		switch (hashType) {
		case HT_dumpIden_FUNCTION:
		    {
			struct dump diskDump, hostDump;

			code = dbread(ut, entryAddr, &diskDump, entrySize);
			if (code)
			    ERROR(-1);

			dump_ntoh(&diskDump, &hostDump);
			printDump(stdout, &hostDump);
			entryAddr = hostDump.idHashChain;
		    }
		    break;

		case HT_dumpName_FUNCTION:
		    break;

		case HT_tapeName_FUNCTION:
		    break;

		case HT_volName_FUNCTION:
		    {
			struct volInfo diskVolInfo, hostVolInfo;

			code = dbread(ut, entryAddr, &diskVolInfo, entrySize);
			if (code)
			    ERROR(-1);

			volInfo_ntoh(&diskVolInfo, &hostVolInfo);
			printVolInfo(stdout, &hostVolInfo);
			entryAddr = hostVolInfo.nameHashChain;
			break;
		    }
		}
	    }
	}
    }
  error_exit:
    return (code);
}
