/* BLURB gpl

                           Coda File System
                              Release 8

          Copyright (c) 1987-2021 Carnegie Mellon University
                  Additional copyrights listed below

This  code  is  distributed "AS IS" without warranty of any kind under
the terms of the GNU General Public Licence Version 2, as shown in the
file  LICENSE.  The  technical and financial  contributors to Coda are
listed in the file CREDITS.

                        Additional copyrights
                           none currently

#*/

#ifdef __cplusplus
extern "C" {
#endif

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include "coda_string.h"
#include <sys/file.h>
#include <struct.h>
#include <partition.h>

#ifdef __cplusplus
}
#endif

//#include <reslog.h>
//#include <remotelog.h>
#include <rsle.h>
#include <parselog.h>
#include <cvnode.h>
#include <volume.h>
#include <index.h>
#include <recov.h>
#include <codadir.h>
#include <camprivate.h>
#include <coda_globals.h>
#include <vice_file.h>

#include <rvm/rvm.h>
#include "norton.h"

void usage(char *name)
{
    fprintf(stderr, "Usage: %s -rvm <log_device> <data_device> <length>\n",
            name);
    fprintf(
        stderr,
        "       [-dump <file> [skip <volid1> <volid2>...] | -load <file>]\n");
}

static int DumpGlobalState(int fd)
{
    int maxvolid = VGetMaxVolumeId();

    if (write(fd, (void *)&maxvolid, (int)sizeof(int)) == -1) {
        perror("Writing max volume ID\n");
        return 0;
    }
    return 1;
}

static int ReadGlobalState(int fd) EXCLUDES_TRANSACTION
{
    VolumeId maxvolid;
    rvm_return_t status;

    if (read(fd, &maxvolid, (int)sizeof(int)) == -1) {
        perror("Reading max volume ID\n");
        return 0;
    }

    if (norton_debug)
        printf("Read maximum vol ID: %08x\n", maxvolid);

    rvmlib_begin_transaction(restore);
    VSetMaxVolumeId(maxvolid);
    rvmlib_end_transaction(flush, &(status));
    if (norton_debug)
        printf("Set max vol id to %08x.  RVM Status = %d\n", maxvolid, status);
    return 1;
}

static int DumpVolHead(int fd, VolHead *vol)
{
    if (write(fd, (void *)vol, (int)sizeof(VolHead)) == -1) {
        perror("Writing volume header");
        return 0;
    }
    return 1;
}

static int SkipToNextVolHead(int fd)
{
    unsigned int magic = 0;
    int n;

    while (magic != VOLUMEHEADERMAGIC) {
        n = read(fd, (void *)&magic, sizeof(unsigned int));
        if (n < (int)sizeof(unsigned int))
            return 0;
    }
    lseek(fd, -sizeof(unsigned int), SEEK_CUR);
    return 1;
}

static int ReadVolHead(int fd, VolHead *vol)
{
    int ret;

    if ((ret = read(fd, (void *)vol, (int)sizeof(VolHead))) == -1) {
        /* An error occurred */
        perror("Reading volume header");
        return 0;
    } /* We have a header or EOF */
    return ret;
}

int DumpVolDiskData(int fd, VolumeDiskData *data)
{
    if (write(fd, (void *)data, (int)sizeof(VolumeDiskData)) == -1) {
        perror("Writing volume disk data");
        return 0;
    }

    /* Check that the resolution flag is on before writing any log information */
    if (data->ResOn & 4) {
        if (write(fd, (void *)&data->log->admin_limit, (int)sizeof(int)) ==
            -1) {
            perror("Writing resolution log admin limit");
            return 0;
        }
    }

    return 1;
}

static int ReadVolDiskData(int fd, VolumeDiskData *data, int *adm_limit)
{
    int ret;

    if ((ret = read(fd, (void *)data, (int)sizeof(VolumeDiskData))) == -1) {
        perror("Reading volume disk data\n");
        return 0;
    }

    if (data->ResOn & 4) {
        if ((ret = read(fd, (void *)adm_limit, (int)sizeof(int))) == -1) {
            perror("Reading resolution log admin limit");
            return 0;
        }
    }

    return ret;
}

static int DumpResLog(int fd, struct VolumeData *voldata,
                      VnodeDiskObject *vnode)
{
    Volume vol, *vp; // We need to fake out the DumpLog routine
    volHeader vh;
    char *buf = NULL;
    int size = 0, nentries = 0, ret = 1;

    /* Check that resolution is on, otherwise return error */
    if (!(voldata->volumeInfo->ResOn & 4)) {
        fprintf(stderr, "DumpResLog called on volume that doesn't ");
        fprintf(stderr, "have resolution turned on.");
        return 0;
    }

    /* Mush the data into the right data structure */
    memcpy(&vh.diskstuff, voldata->volumeInfo, sizeof(VolumeDiskData));
    vp         = &vol;
    vp->header = &vh;

    DumpLog(vnode->log, vp, &buf, &size, &nentries);

    /* Write out the number of entries and the size */
    if (write(fd, &nentries, (int)sizeof(nentries)) == -1) {
        perror("Writing number of entries");
        if (buf)
            free(buf);
        return 0;
    }

    if (write(fd, &size, (int)sizeof(size)) == -1) {
        perror("Writing resolution log buffer size");
        if (buf)
            free(buf);
        return 0;
    }

    if (write(fd, buf, size) == -1) {
        perror("Writing resolution log");
        ret = 0;
    }

    if (norton_debug)
        printf("    Wrote resolution log of %d entries for %d bytes\n",
               nentries, size);

    if (buf)
        free(buf);
    return ret;
}

// Must be called within a transaction.
static recle *AllocLogEntry(Volume *vp, rsle *entry) REQUIRES_TRANSACTION
{
    recle *rle;
    int index = -1;
    int seqno = -1;

    if (V_VolLog(vp)->AllocRecord(&index, &seqno) != 0) {
        fprintf(stderr, "Can't allocate a vollog record! Aborting.\n");
        return NULL;
    }

    rle = V_VolLog(vp)->RecovPutRecord(index);
    if (!rle) {
        fprintf(stderr, "Can't put a vollog record! Aborting.\n");
        return NULL;
    }

    // Cram the correct index into the log entery.
    entry->index = index;
    rle->InitFromsle(entry);
    if (norton_debug)
        rle->print();

    return rle;
}

// If an error occures, set *err = 1, otherwise *err = 0
static int BuildResLog(Volume *vp, rec_dlist *log,
                       VnodeId pvn, /*Parent vnode */
                       Unique_t pu, /*parent uniquifier */
                       int nentries, int start, rsle *logentries,
                       int *err) REQUIRES_TRANSACTION
{
    recle *rle;
    int i = start;

    *err = 0;

    while (i < nentries && ((pvn == 0) || ((logentries[i].dvn == pvn) &&
                                           (logentries[i].du == pu)))) {
        if (!(rle = AllocLogEntry(vp, &logentries[i]))) {
            *err = 1;
            return -1;
        }

        log->append(rle);
        i++;

        if ((rle->opcode == RES_RemoveDir_OP) ||
            (rle->opcode == ResolveViceRemoveDir_OP)) {
            // Should I check that there is actually a list?
            // Should I allocate on entry to BuildResLog?
            rmdir_rle *r = (rmdir_rle *)(&(rle->vle->vfld[0]));
            r->childlist = new rec_dlist();

            i += BuildResLog(vp, r->childlist, r->cvnode, r->cunique, nentries,
                             i, logentries, err);
            if (err)
                return -1;
        }
    }
    return i - start;
}

/* Warning: ReadResLog better not be called for a volume that does not
 * have resolution turned on.
 */
static int ReadResLog(int fd, Volume *vp, Vnode *vnp) REQUIRES_TRANSACTION
{
    int nentries, size, err;
    char *buf;
    olist *hlist;
    rsle *logentries;

    /* Read in the number of entries and the size */
    if (read(fd, &nentries, (int)sizeof(nentries)) == -1) {
        perror("Reading number of resolution log entries");
        return 0;
    }

    if (read(fd, &size, (int)sizeof(size)) == -1) {
        perror("Reading size of resolution log");
        return 0;
    }

    if (!(buf = (char *)malloc(size))) {
        fprintf(stderr, "Unable to malloc resolution log buffer\n");
        return 0;
    }

    if (read(fd, buf, size) == -1) {
        perror("Reading resolution log");
        free(buf);
        return 0;
    }
    if (norton_debug) {
        printf("    Read resolution log of %d entries for %d bytes\n", nentries,
               size);
    }

    ParseRemoteLogs(buf, size, nentries, &hlist, &logentries);

    // Initialize the log list.
    VnLog(vnp) = new rec_dlist();

    // Now copy it to RVM.
    BuildResLog(vp, VnLog(vnp), 0, 0, nentries, 0, logentries, &err);

    free(buf);

    return err ? 0 : 1;
}

#define VNODESIZE(vclass) \
    ((vclass) == vLarge ? SIZEOF_LARGEDISKVNODE : SIZEOF_SMALLDISKVNODE)

#define VNODECLASS(vclass) ((vclass) == vLarge ? "large" : "small")

static int DumpVnodeList(int fd, struct VolumeData *vol, int vol_index,
                         VnodeClass vclass)
{
    char buf[SIZEOF_LARGEDISKVNODE];
    VnodeDiskObject *vnode = (VnodeDiskObject *)buf;
    int nvnodes, vnode_index, vnode_num;
    PDirInode inode;
    int npages;

    /* Send in a 0 for device, since noone uses it anyway. */
    vindex v_index(vol->volumeInfo->id, vclass, 0, VNODESIZE(vclass),
                   vol_index);
    vindex_iterator vnext(v_index);

    nvnodes = v_index.vnodes();
    if (norton_debug) {
        printf("Volume %08x, has %d %s vnodes\n", vol->volumeInfo->id, nvnodes,
               VNODECLASS(vclass));
    }

    if (write(fd, (void *)&nvnodes, (int)sizeof(nvnodes)) == -1) {
        perror("Writing number of vnodes\n");
        return 0;
    }

    while ((vnode_index = vnext(vnode)) != -1) {
        /* Write out the vnode number */
        vnode_num = bitNumberToVnodeNumber(vnode_index, vclass);
        if (norton_debug)
            printf("Writing vnode number 0x%x\n", vnode_num);

        if (write(fd, (void *)&vnode_num, (int)sizeof(vnode_num)) == -1) {
            perror("Writing vnode number\n");
            return 0;
        }

        if (write(fd, (void *)vnode, VNODESIZE(vclass)) == -1) {
            perror("Writing vnode\n");
            return 0;
        }

        if (vclass == vLarge) {
            /* Now write the inode information and directory pages. */
            inode = vnode->node.dirNode;
            for (npages = 0; inode->di_pages[npages]; npages++)
                ;

            if (norton_debug) {
                printf("    Inode %p has %d pages\n", inode, npages);
            }

            if (write(fd, (void *)&npages, (int)sizeof(npages)) == -1) {
                perror("Writing inode\n");
                return 0;
            }

            /* Write the reference count */
            if (write(fd, (void *)&inode->di_refcount,
                      (int)sizeof(inode->di_refcount)) == -1) {
                perror("Writing inode reference count\n");
                return 0;
            }

            for (npages = 0; inode->di_pages[npages]; npages++) {
                if (write(fd, (void *)inode->di_pages[npages], DIR_PAGESIZE) ==
                    -1) {
                    perror("Writing directory pages\n");
                    return 0;
                }
            }

            /* Finally write the resolution log */
            if (vol->volumeInfo->ResOn & 4) {
                if (!DumpResLog(fd, vol, vnode))
                    return 0;
            }
        }
    }
    return 1;
}

static int DumpVolVnodes(int fd, struct VolumeData *vol, int vol_index)
{
    if (!DumpVnodeList(fd, vol, vol_index, vLarge))
        return 0;
    if (!DumpVnodeList(fd, vol, vol_index, vSmall))
        return 0;
    return 1;
}

int CopyDirInode(PDirInode oldinode, PDirInode *newinode) REQUIRES_TRANSACTION
{
    struct DirInode shadowInode;

    if (!oldinode) {
        LogMsg(29, DirDebugLevel, stdout, "CopyDirInode: Null oldinode");
        return -1;
    }
    *newinode = (PDirInode)rvmlib_rec_malloc(sizeof(struct DirInode));
    memset((void *)&shadowInode, 0, sizeof(struct DirInode));
    for (int i = 0; i < DIR_MAXPAGES; i++)
        if (oldinode->di_pages[i]) {
            shadowInode.di_pages[i] = (long *)rvmlib_rec_malloc(DIR_PAGESIZE);
            rvmlib_modify_bytes(shadowInode.di_pages[i], oldinode->di_pages[i],
                                DIR_PAGESIZE);
        }
    shadowInode.di_refcount = oldinode->di_refcount;
    rvmlib_modify_bytes(*newinode, &shadowInode, sizeof(struct DirInode));
    return 0;
}

static int ReadVnodeList(int fd, Volume *vp, VnodeClass vclass,
                         int ResOn) EXCLUDES_TRANSACTION
{
    char buf[SIZEOF_LARGEDISKVNODE];
    VnodeDiskObject *vnode = (VnodeDiskObject *)buf;
    Vnode *vnp;
    int nvnodes;
    VnodeId vnode_num;
    int npages;
    int i;
    rvm_return_t status;
    Error err;

    /* We need to allocate this in RVM! */
    PDirInode inode = (PDirInode)malloc(sizeof(struct DirInode));
    PDirInode newinode;

    if (!inode) {
        fprintf(stderr, "Cannot allocate VM inode.\n");
        return 0;
    }

    if (read(fd, (void *)&nvnodes, sizeof(nvnodes)) == -1) {
        sprintf(buf, "Reading number of %s vnodes\n", VNODECLASS(vclass));
        perror(buf);
        return 0;
    }
    if (norton_debug)
        printf("Reading %d vnodes...\n", nvnodes);

    while (nvnodes--) {
        if (read(fd, (void *)&vnode_num, sizeof(vnode_num)) == -1) {
            perror("Reading vnode number\n");
            return 0;
        }
        if (norton_debug)
            printf("Reading vnode number %08x\n", vnode_num);

        memset(vnode, 0, VNODESIZE(vclass));

        if (read(fd, (void *)vnode, VNODESIZE(vclass)) == -1) {
            perror("Reading vnode\n");
            return 0;
        }

        /* Sanity check what we just read by looking for the magic number */
        if (vnode->vnodeMagic !=
            (vclass == vLarge ? LARGEVNODEMAGIC : SMALLVNODEMAGIC)) {
            fprintf(stderr, "ERROR!  Bad %s magic number for vnode 0x%x\n",
                    VNODECLASS(vclass), vnode_num);
            return 0;
        }

        vnp = VAllocVnode(&err, vp, vnode->type, vnode_num, vnode->uniquifier);
        if (err) {
            fprintf(stderr, "Error code %d, while allocating vnode 0x%x\n", err,
                    vnode_num);
            return (0);
        }

        // Save the volume index
        vnode->vol_index = vnp->disk.vol_index;

        /* Now copy the disk data into the vnode */
        memcpy(&vnp->disk, vnode, VNODESIZE(vclass));
        vnp->changed   = 1;
        vnp->delete_me = 0;

        rvmlib_begin_transaction(restore);

        if (vclass == vLarge) {
            memset(inode, 0, sizeof(struct DirInode));

            /* Read in the directory pages */
            if (read(fd, (void *)&npages, (int)sizeof(npages)) == -1) {
                perror("Reading inode");
                rvmlib_abort(VFAIL);
                return 0;
            }

            if (read(fd, (void *)&inode->di_refcount,
                     (int)sizeof(inode->di_refcount)) == -1) {
                perror("Reading inode reference count");
                rvmlib_abort(VFAIL);
                return 0;
            }

            for (i = 0; i < npages; i++) {
                inode->di_pages[i] = (long *)malloc(DIR_PAGESIZE);
                if (!inode->di_pages[i]) {
                    fprintf(stderr,
                            "Unable to allocate a new directory page.\n");
                    rvmlib_abort(VFAIL);
                    return 0;
                }

                if (read(fd, (void *)inode->di_pages[i], DIR_PAGESIZE) == -1) {
                    perror("Reading directory page\n");
                    rvmlib_abort(VFAIL);
                    return 0;
                }
            }

            if (CopyDirInode(inode, &newinode)) {
                fprintf(stderr, "Cannot copy inode to RVM, aborting\n");
                rvmlib_abort(VFAIL);
                return 0;
            }

            vnp->disk.node.dirNode = newinode;

            /* Read in the resolution log */
            if (ResOn) {
                if (!ReadResLog(fd, vp, vnp)) {
                    rvmlib_abort(VFAIL);
                    return 0;
                }
            } else {
                /* Create an empty log.  DO NOT ALLOW A RES LOG TO BE
		 * CREATED for read only volumes.  at this point we
		 * think they are read/write.
		 */
                fprintf(stderr, "WARNING: Volume ResOn = FALSE, fix norton ");
                fprintf(stderr, "to create empty log.\n");
            }
        }
        if (norton_debug)
            PrintVnodeDiskObject(vnode);

        /* Write the vnode to disk */
        VPutVnode(&err, vnp);
        if (err) {
            fprintf(stderr, "VPutVnode Error: %d, on vnode 0x%x\n", err,
                    vnp->vnodeNumber);
            rvmlib_abort(VFAIL);
            return 0;
        }
        rvmlib_end_transaction(flush, &(status));
        if (status != 0) {
            fprintf(stderr, "Transaction exited with status %d!, aborting\n",
                    status);
            return 0;
        }
    }
    return 1;
}

static int ReadVolVnodes(int fd, Volume *vp, int ResOn) EXCLUDES_TRANSACTION
{
    if (!ReadVnodeList(fd, vp, vLarge, ResOn))
        return 0;
    if (!ReadVnodeList(fd, vp, vSmall, ResOn))
        return 0;
    return 1;
}

#ifdef UNUSED
static void NortonSetupVolume(VolHead *vh, Volume *vp, int volindex)
{
    // This turns out to be a global since its static, but if the
    // compiler semantics ever change, we need to move it.
    static struct volHeader header;

    memset(&header, 0, sizeof(struct volHeader));
    memcpy(&header.diskstuff, vh->data.volumeInfo, sizeof(VolumeDiskData));
    header.back = vp;

    memset(vp, 0, sizeof(Volume));
    vp->hashid    = vh->header.id;
    vp->header    = &header;
    vp->vol_index = volindex;
}
#endif

// Can't re-use code in vol-salvage since this comes from command line.
static void GetSkipVols(int num, VolumeId *ids, char *vol_nums[])
{
    while (num > 0) {
        sscanf(*vol_nums, "%x", ids);
        printf("Skipping volume: %08x\n", *ids);
        vol_nums++;
        ids++;
        num--;
    }
}

// In vol-salvage.c  GetSkipVols above builds the correct list.
extern int InSkipVolumeList(VolumeId v, VolumeId *vl, int nvols);

static int HasBackVols(VolumeId *skipvols, int nskipvols)
{
    VolumeHeader *header;
    VolHead *vol;
    int i, maxid = GetMaxVolId();
    int ret = FALSE;

    /* Iterate over all of the volumes */
    for (i = 0; (i < maxid) && (i < MAXVOLS); i++) {
        if ((header = VolHeaderByIndex(i)) == NULL) {
            printf("WARNING: Unable to get volume header at index: %d\n", i);
            continue;
        }
        if (header->stamp.magic != VOLUMEHEADERMAGIC) {
            continue;
        }

        if ((vol = VolByIndex(i)) == NULL) {
            printf("WARNING: Unable to get volume at index: %d\n", i);
            continue;
        }

        if (vol->data.volumeInfo->type == BACKVOL) {
            // Check if the parent volume is being skipped.  If it is,
            // the we dont care about it.
            if (InSkipVolumeList(vol->data.volumeInfo->parentId, skipvols,
                                 nskipvols)) {
                continue;
            } else {
                ret = TRUE;
                printf("backup volume: %s (%08x)\n", vol->data.volumeInfo->name,
                       vol->data.volumeInfo->id);
            }
        }
    }

    return ret;
}

// So we can add volume headers back to LRU.
extern void FreeVolumeHeader(Volume *vp);

static int load_server_state(char *dump_file, VolumeId *skipvols,
                             int nskipvols) EXCLUDES_TRANSACTION
{
    int dump_fd, volindex;
    int res_adm_limit;
    rvm_return_t status;
    VolumeDiskData data;
    VolHead vol_head;
    Volume *vp;
    Error err;
    int vol_type;
    char myname[256];

    if ((dump_fd = open(dump_file, O_RDONLY, 0x600)) < 0) {
        perror(dump_file);
        return 0;
    }

    /* First thing, initiliaze the global server state */
    DP_Init(vice_config_path("db/vicetab"), hostname(myname));
    coda_init();

    if (!ReadGlobalState(dump_fd)) {
        fprintf(stderr, "Error reading reinit file, Aborting...\n");
        return 0;
    }

    memset((void *)&vol_head, 0, (int)sizeof(VolHead));
    while (ReadVolHead(dump_fd, &vol_head)) {
        /* Check the magic number */
        if (vol_head.header.stamp.magic != VOLUMEHEADERMAGIC) {
            fprintf(stderr,
                    "ERROR! Bad volume header magic number, Aborting.\n");
            return 0;
        }

        if (InSkipVolumeList(vol_head.header.id, skipvols, nskipvols)) {
            printf("Skipping volume %08x\n", vol_head.header.id);
            SkipToNextVolHead(dump_fd);
            continue;
        } else
            printf("Reading volume %08x\n", vol_head.header.id);

        rvmlib_begin_transaction(restore);

        if ((vol_type = vol_head.header.type) != RWVOL) {
            // Pretend that this is a read-write volume, restore
            // correct type later.
            vol_head.header.type = RWVOL;
        }

        volindex = NewVolHeader(&vol_head.header, &err);
        if (volindex == -1) {
            if (err == VVOLEXISTS) {
                fprintf(stderr, "Volume %08x already exists!  Aborting\n",
                        vol_head.header.id);
            } else {
                fprintf(stderr,
                        "Error code %d while creating volume %08x, Aborting\n",
                        err, vol_head.header.id);
            }
            rvmlib_abort(VFAIL);
            return 0;
        }

        memset((void *)&data, 0, (int)sizeof(VolumeDiskData));
        if (!ReadVolDiskData(dump_fd, &data, &res_adm_limit)) {
            fprintf(stderr, "Aborting...\n");
            rvmlib_abort(VFAIL);
            return 0;
        }

        /* Check the magic number */
        if (data.stamp.magic != VOLUMEINFOMAGIC) {
            fprintf(stderr, "ERROR! Bad volume disk data magic number.\n");
            rvmlib_abort(VFAIL);
            return 0;
        }

        /* Allocate space for the volume resolution log if needed */
        if (data.ResOn & 4) {
            data.log = new recov_vol_log(vol_head.header.id, res_adm_limit);
        }

        if (vol_type != RWVOL) {
            // Pretend we are RW.
            data.type = RWVOL;
        }

        vol_head.data.volumeInfo = &data;

        ReplaceVolDiskInfo(&err, volindex, &data);
        if (err != 0) {
            fprintf(stderr, "ReplaceVolDiskInfo returned %d, aborting\n", err);
            rvmlib_abort(VFAIL);
            return 0;
        }

        vp = VAttachVolume(&err, vol_head.header.id, V_SECRETLY);
        if (err) {
            fprintf(stderr,
                    "VAttachVolume returns error %d when attaching 0x%x\n", err,
                    vol_head.header.id);
            rvmlib_abort(VFAIL);
            return 0;
        }

        rvmlib_end_transaction(flush, &(status));
        if (status != 0) {
            fprintf(stderr, "Transaction exited with status %d!, aborting\n",
                    status);
            return 0;
        }

        // If the volume isnt in use, VPutVnode gets upset.
        V_inUse(vp) = TRUE;

        if (norton_debug) {
            printf("\n\n");
            print_volume(&vol_head);
            print_volume_details(&vol_head);
        }
        if (!ReadVolVnodes(dump_fd, vp, data.ResOn & 4)) {
            fprintf(stderr, "Aborting...\n");
            FreeVolumeHeader(vp);
            return 0;
        }

        if (vol_type != RWVOL) {
            // Need to change the volume type.
            rvmlib_begin_transaction(restore);

            vol_head.header.type       = vol_type;
            vp->header->diskstuff.type = vol_type;
            rvmlib_modify_bytes(&(SRV_RVM(VolumeList[volindex]).header),
                                &vol_head.header, sizeof(struct VolumeHeader));
            ReplaceVolDiskInfo(&err, volindex, &vp->header->diskstuff);
            if (err != 0) {
                fprintf(stderr, "ReplaceVolDiskInfo returned %d, aborting\n",
                        err);
                rvmlib_abort(VFAIL);
                return 0;
            }
            rvmlib_end_transaction(flush, &(status));
            if (status != 0) {
                fprintf(stderr,
                        "Transaction exited with status %d!, aborting\n",
                        status);
                return 0;
            }
        }

        FreeVolumeHeader(vp);
        memset((void *)&vol_head, 0, (int)sizeof(VolHead));

        // Truncate the RVM log.
        rvm_truncate();
    }

    return 1;
}

static int dump_server_state(char *dump_file, VolumeId *skipvols, int nskipvols)
{
    VolumeHeader *header;
    VolHead *vol;
    int i, maxid = GetMaxVolId();
    int dump_fd;

    if (HasBackVols(skipvols, nskipvols)) {
        fprintf(
            stderr,
            "This server has backup volumes. They must be purged before proceeding.\n");
        return 0;
    }

    if ((dump_fd = open(dump_file, O_CREAT | O_EXCL | O_WRONLY, 0600)) < 0) {
        perror(dump_file);
        return 0;
    }

    if (!DumpGlobalState(dump_fd)) {
        fprintf(stderr, "Error writing reinit file, Aborting...\n");
        return 0;
    }

    /* Iterate over all of the volumes */
    for (i = 0; (i < maxid) && (i < MAXVOLS); i++) {
        if ((header = VolHeaderByIndex(i)) == NULL) {
            printf("WARNING: Unable to get volume header at index: %d\n", i);
            continue;
        }
        if (header->stamp.magic != VOLUMEHEADERMAGIC) {
            continue;
        }

        if ((vol = VolByIndex(i)) == NULL) {
            printf("WARNING: Unable to get volume at index: %d\n", i);
            continue;
        }

        // Do we need to skip this volume?
        if (InSkipVolumeList(vol->data.volumeInfo->id, skipvols, nskipvols))
            continue;

        // Check that its not a backup of something we are skipping
        // This check should probably go in InSkipVolumeList()
        if ((vol->data.volumeInfo->type == BACKVOL) &&
            InSkipVolumeList(vol->data.volumeInfo->parentId, skipvols,
                             nskipvols)) {
            continue;
        }

        printf("Writing volume %08x\n", vol->data.volumeInfo->id);

        if (!DumpVolHead(dump_fd, vol) ||
            !DumpVolDiskData(dump_fd, vol->data.volumeInfo) ||
            !DumpVolVnodes(dump_fd, &vol->data, i)) {
            fprintf(stderr, "Aborting...\n");
            return 0;
        }
    }

    return 1;
}

int main(int argc, char *argv[]) EXCLUDES_TRANSACTION
{
    char *rvm_log;
    char *rvm_data;
    int data_len;
    int ok             = 0;
    VolumeId *skipvols = NULL;
    int nskipvols      = 0;

    if (argc < 7) {
        usage(argv[0]);
        exit(EXIT_FAILURE);
    }

    if (strcmp(argv[1], "-rvm")) {
        usage(argv[0]);
        exit(EXIT_FAILURE);
    } else {
        rvm_log  = argv[2];
        rvm_data = argv[3];
        data_len = atoi(argv[4]);
    }

    vice_dir_init("/vice");

    if (argc > 8) {
        if (strcmp(argv[7], "skip")) {
            usage(argv[0]);
            exit(EXIT_FAILURE);
        }
        nskipvols = argc - 8;
        skipvols  = (VolumeId *)malloc(nskipvols * sizeof(VolumeId));
        GetSkipVols(nskipvols, skipvols, &argv[8]);
    }

    if (!strcmp(argv[5], "-dump")) {
        NortonInit(rvm_log, rvm_data, data_len);
        ok = dump_server_state(argv[6], skipvols, nskipvols);
        rvm_terminate();
    } else if (!strcmp(argv[5], "-load")) {
        NortonInit(rvm_log, rvm_data, data_len);
        ok = load_server_state(argv[6], skipvols, nskipvols);
        rvm_terminate();
    } else
        usage(argv[0]);

    if (skipvols)
        free(skipvols);
    exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
}
