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

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

#include <roken.h>

#include <afs/cmd.h>
#include <lock.h>
#include <afs/tcdata.h>
#include <afs/usd.h>

usd_handle_t fd;
usd_handle_t ofd;
int ofdIsOpen = 0;
afs_int32 nrestore, nskip;
int ask, printlabels, printheaders, verbose;
char filename[100];
char *outfile;

#define TAPE_MAGIC  1100000009	/* Defined in file_tm.c */
#define BLOCK_MAGIC 1100000005
#define FILE_MAGIC  1000000007
#define FILE_BEGIN     0
#define FILE_END       1
#define FILE_EOD      -1

struct tapeLabel {		/* also in file_tm.c */
    afs_int32 magic;
    struct butm_tapeLabel label;
};
struct fileMark {		/* also in file_tm.c */
    afs_int32 magic;
    afs_uint32 nBytes;
};

/* Read a tape block of size 16K */
afs_int32
readblock(char *buffer)
{
    u_int nread, total = 0;
    int rc, fmcount = 0;

    while (total < BUTM_BLOCKSIZE) {
	rc = USD_READ(fd, buffer + total, BUTM_BLOCKSIZE - total, &nread);
	if (rc != 0) {
	    return (rc);
	} else if ((nread == 0) && (total == 0)) {
	    if (verbose)
		fprintf(stderr, "Hardware file mark\n");
	    if (++fmcount > 3) {
		if (verbose)
		    fprintf(stderr,
			    "Greater than 3 hardware file marks in a row - done\n");
		return -1;
	    }
	} else if (nread == 0) {
	    fprintf(stderr, "Reached unexpected end of dump file\n");
	    return -1;
	} else {
	    total += nread;
	}
    }
    return 0;
}

void
printLabel(struct tapeLabel *tapeLabelPtr)
{
    tapeLabelPtr->label.dumpid = ntohl(tapeLabelPtr->label.dumpid);
    tapeLabelPtr->label.creationTime =
	ntohl(tapeLabelPtr->label.creationTime);
    tapeLabelPtr->label.expirationDate =
	ntohl(tapeLabelPtr->label.expirationDate);
    tapeLabelPtr->label.structVersion =
	ntohl(tapeLabelPtr->label.structVersion);
    tapeLabelPtr->label.useCount = ntohl(tapeLabelPtr->label.useCount);
    tapeLabelPtr->label.size = ntohl(tapeLabelPtr->label.size);

    fprintf(stderr, "\nDUMP       %u\n", tapeLabelPtr->label.dumpid);
    if (printlabels) {
	time_t t;

	fprintf(stderr, "   AFS Tape  Name   : %s\n",
		tapeLabelPtr->label.AFSName);
	fprintf(stderr, "   Permanent Name   : %s\n",
		tapeLabelPtr->label.pName);
	fprintf(stderr, "   Dump Id          : %u\n",
		tapeLabelPtr->label.dumpid);
	t = tapeLabelPtr->label.dumpid;
	fprintf(stderr, "   Dump Id Time     : %.24s\n",
		ctime(&t));
	t = tapeLabelPtr->label.creationTime;
	fprintf(stderr, "   Date Created     : %.24s\n",
		ctime(&t));
	t = tapeLabelPtr->label.expirationDate;
	fprintf(stderr, "   Date Expires     : %.24s\n",
		ctime(&t));
	fprintf(stderr, "   Version Number   : %d\n",
		tapeLabelPtr->label.structVersion);
	fprintf(stderr, "   Tape Use Count   : %d\n",
		tapeLabelPtr->label.useCount);
	fprintf(stderr, "   Tape Size        : %u\n",
		tapeLabelPtr->label.size);
	fprintf(stderr, "   Comment          : %s\n",
		tapeLabelPtr->label.comment);
	fprintf(stderr, "   Dump Path        : %s\n",
		tapeLabelPtr->label.dumpPath);
	fprintf(stderr, "   Cell Name        : %s\n",
		tapeLabelPtr->label.cell);
	fprintf(stderr, "   Creator Name     : %s\n",
		tapeLabelPtr->label.creator.name);
	fprintf(stderr, "   Creator Instance : %s\n",
		tapeLabelPtr->label.creator.instance);
	fprintf(stderr, "   Creator Cell     : %s\n",
		tapeLabelPtr->label.creator.cell);
    }
}

void
printHeader(struct volumeHeader *headerPtr, afs_int32 *isvolheader)
{
    static int volcount = 0;

    *isvolheader = 0;
    headerPtr->volumeID = ntohl(headerPtr->volumeID);
    headerPtr->server = ntohl(headerPtr->server);
    headerPtr->part = ntohl(headerPtr->part);
    headerPtr->from = ntohl(headerPtr->from);
    headerPtr->frag = ntohl(headerPtr->frag);
    headerPtr->magic = ntohl(headerPtr->magic);
    headerPtr->contd = ntohl(headerPtr->contd);
    headerPtr->dumpID = ntohl(headerPtr->dumpID);
    headerPtr->level = ntohl(headerPtr->level);
    headerPtr->parentID = ntohl(headerPtr->parentID);
    headerPtr->endTime = ntohl(headerPtr->endTime);
    headerPtr->versionflags = ntohl(headerPtr->versionflags);
    headerPtr->cloneDate = ntohl(headerPtr->cloneDate);

    if (headerPtr->magic == TC_VOLBEGINMAGIC) {
	time_t t;

	*isvolheader = 1;
	if (verbose)
	    fprintf(stderr, "Volume header\n");
	t = headerPtr->from;
	fprintf(stderr,
		"VOLUME %3d %s (%u) - %s dump from %.24s",
		++volcount, headerPtr->volumeName, headerPtr->volumeID,
		(headerPtr->level ? "Incr" : "Full"),
		(t ? (char *)ctime(&t) : "0"));
        /* do not include two ctime() calls in the same fprintf call as
         * the same string buffer will be returned by each call. */
	t = headerPtr->cloneDate;
        fprintf(stderr, " till %.24s\n", ctime(&t));
	if (printheaders) {
	    fprintf(stderr, "   Volume Name    = %s\n",
		    headerPtr->volumeName);
	    fprintf(stderr, "   Volume ID      = %u\n", headerPtr->volumeID);
	    t = headerPtr->cloneDate;
	    fprintf(stderr, "   Clone Date     = %.24s\n",
		    ctime(&t));
	    fprintf(stderr, "   Vol Fragment   = %d\n", headerPtr->frag);
	    fprintf(stderr, "   Vol Continued  = 0x%x\n", headerPtr->contd);
	    fprintf(stderr, "   DumpSet Name   = %s\n",
		    headerPtr->dumpSetName);
	    fprintf(stderr, "   Dump ID        = %u\n", headerPtr->dumpID);
	    fprintf(stderr, "   Dump Level     = %d\n", headerPtr->level);
	    t = headerPtr->from;
	    fprintf(stderr, "   Dump Since     = %.24s\n",
		    ctime(&t));
	    fprintf(stderr, "   parent Dump ID = %u\n", headerPtr->parentID);
	}
    } else if (headerPtr->magic == TC_VOLENDMAGIC) {
	if (verbose)
	    fprintf(stderr, "Volume Trailer\n");
    } else {
	fprintf(stderr, "Unrecognized Volume Header/Trailer\n");
    }
}

int
openOutFile(struct volumeHeader *headerPtr)
{
    afs_int32 len;
    int ch;
    int rc;
    int oflag;
    int skip, first;
    afs_int64 size;

    /* If we were asked to skip this volume, then skip it */
    if (nskip) {
	nskip--;
	return 0;
    }
    /* Skip if we are not to restore any */
    if (!nrestore)
	return 0;

    /* Get the volume name and strip off the BK or RO extension */
    if (outfile) {
	strcpy(filename, outfile);
    } else {
	strcpy(filename, headerPtr->volumeName);
	len = strlen(filename);
	if ((len > 7) && (strcmp(".backup", filename + len - 7) == 0)) {
	    filename[len - 7] = 0;
	} else if ((len > 9)
		   && (strcmp(".readonly", filename + len - 9) == 0)) {
	    filename[len - 9] = 0;
	}
    }

    if (ask) {
	first = 1;
	skip = 0;
	printf("Press return to retrieve volume %s (%u) to file %s; " "s"
	       " to skip\n", headerPtr->volumeName, headerPtr->volumeID,
	       filename);
	do {
	    ch = getchar();
	    if ((first == 1) && (ch == 's'))
		skip = 1;
	    if ((first == 1) && (ch == 'q'))
		exit(0);
	    first = 0;
	} while (ch != '\n');
	if (skip) {
	    printf("Will not restore volume %s\n", headerPtr->volumeName);
	    return 0;
	}
    } else {
	printf("Retrieve volume %s (%u) to file %s\n", headerPtr->volumeName,
	       headerPtr->volumeID, filename);
    }

    /* Should I append the date onto the end of the name? */

    /* Open the file to write to */
    if (headerPtr->contd == TC_VOLCONTD) {
	/* Continuation of dump */
	oflag = USD_OPEN_RDWR;
    } else {
	/* An all new dump */
	oflag = USD_OPEN_RDWR | USD_OPEN_CREATE;
    }
    rc = usd_Open(filename, oflag, 0664, &ofd);
    if (rc != 0) {
	fprintf(stderr, "Unable to open file %s. Skipping. Code = %d\n",
		filename, rc);
	nrestore--;
	return 0;
    }
    if (headerPtr->contd != TC_VOLCONTD) {
	size = 0;
	rc = USD_IOCTL(ofd, USD_IOCTL_SETSIZE, &size);
	if (rc != 0) {
	    fprintf(stderr, "Unable to open file %s. Skipping. Code = %d\n",
		    filename, rc);
	    USD_CLOSE(ofd);
	    nrestore--;
	    return 0;
	}
    }
    ofdIsOpen = 1;
    return 0;
}

static void
writeData(char *data, afs_int32 size)
{
    int rc;
    u_int nwritten;

    if (!ofdIsOpen)
	return;
    rc = USD_WRITE(ofd, data, (u_int) size, &nwritten);
    if (rc != 0) {
	fprintf(stderr, "Unable to write volume data to file. Code = %d\n",
		rc);
    }
    return;
}

int
writeLastBlocks(char *lastblock, char *lastblock2)
{
    char trailer[12];
    struct blockMark *bmark, *bmark2;
    char *data;
    char *data2 = NULL;
    int count, count2;
    int tlen, skip, pos;

    if (!ofdIsOpen)
	return 0;

    bmark = (struct blockMark *)lastblock;
    data = &lastblock[sizeof(struct blockMark)];
    count = ntohl(bmark->count);
    if (lastblock2) {
	bmark2 = (struct blockMark *)lastblock2;
	data2 = &lastblock2[sizeof(struct blockMark)];
	count2 = ntohl(bmark2->count);
    } else {
	count2 = 0;
    }

    /*
     * Strip off all but the last twelve bytes of the volume trailer
     */
    skip = sizeof(struct volumeHeader) - 12;
    if (count >= skip) {
	count = count - skip;
    } else if (count + count2 >= skip) {
	count2 = count2 - (skip - count);
	count = 0;
    } else {
	fprintf(stderr, "Failed to strip off volume trailer (1).\n");
	return 0;
    }

    /* volume trailer is somewhere in the last 12 bytes of the tape file.
     * The volume trailer may span tape blocks. */
    if (count >= 12) {
	tlen = 0;
	memcpy(trailer, data + (count - 12), 12);
    } else {
	tlen = 12 - count;
	memcpy(trailer, data2 + (count2 - tlen), tlen);
	if (count != 0) {
	    memcpy(trailer + tlen, data, count);
	}
    }

    for (pos = 0; pos <= 2; pos++) {
	if (strncmp(&trailer[pos], "H++NAME#", 8) == 0) {
	    break;
	}
	if (tlen > 0) {
	    tlen--;
	}
    }

    if (pos == 3) {
	fprintf(stderr, "Failed to strip off volume trailer (2).\n");
    } else {
	if (count2 - tlen > 0) {
	    writeData(data2, count2 - tlen);
	}
	if ((tlen == 0) && (count > 12 - pos)) {
	    writeData(data, count - (12 - pos));
	}
    }
    return 0;
}

int
closeOutFile(void)
{
    if (!ofdIsOpen)
	return 0;

    USD_CLOSE(ofd);
    ofdIsOpen = 0;

    /* Decrement the number of volumes to restore */
    nrestore--;
    return 0;
}

static int
WorkerBee(struct cmd_syndesc *as, void *arock)
{
    char *tapedev;
    struct tapeLabel *label;
    struct fileMark *fmark;
    afs_int32 fmtype;
    struct blockMark *bmark;
    afs_int32 isheader, isdatablock;
    char *data;
    char *tblock;
    afs_int32 code;
    struct volumeHeader *volheaderPtr = NULL;
    int eod = 1;
    int rc;
    char *nextblock;		/* We cycle through three tape blocks so we  */
    char *lastblock;		/* can trim off the volume trailer from the  */
    char *lastblock2;		/* end of each volume without having to back */
    char *tapeblock1;		/* up the output stream.                     */
    char *tapeblock2;
    char *tapeblock3;

    tapedev = as->parms[0].items->data;	/* -tape     */
    nrestore =
	(as->parms[1].items ? atol(as->parms[1].items->data) : 0x7fffffff);
    nskip = (as->parms[2].items ? atol(as->parms[2].items->data) : 0);
    if (as->parms[4].items)
	nskip = 0x7fffffff;	/* -scan     */
    outfile = (as->parms[3].items ? as->parms[3].items->data : 0);
    ask = (as->parms[5].items ? 0 : 1);	/* -noask    */
    printlabels = (as->parms[6].items ? 1 : 0);	/* -label    */
    printheaders = (as->parms[7].items ? 1 : 0);	/* -vheaders */
    verbose = (as->parms[8].items ? 1 : 0);	/* -verbose  */

    /* Open the tape device */
    rc = usd_Open(tapedev, USD_OPEN_RDONLY, 0, &fd);
    if (rc != 0) {
	printf("Failed to open tape device %s. Code = %d\n", tapedev, rc);
	exit(1);
    }

    /*
     * Initialize the tape block buffers
     */
    tapeblock1 = malloc(3 * 16384);
    if (tapeblock1 == NULL) {
	printf("Failed to allocate I/O buffers.\n");
	exit(1);
    }
    tapeblock2 = tapeblock1 + 16384;
    tapeblock3 = tapeblock2 + 16384;

    nextblock = tapeblock1;
    lastblock = NULL;
    lastblock2 = NULL;

    /* Read each tape block deciding what to do with it */
    do {			/* while ((nskip!=0) && (nrestore!=0)) */
	code = readblock(nextblock);
	if (code) {
	    if (!eod)
		fprintf(stderr, "Tape device read error: %d\n", code);
	    break;
	}
	isdatablock = 0;

	/* A data block can be either a volume header, volume trailer,
	 * or actual data from a dump.
	 */
	bmark = (struct blockMark *)nextblock;
	label = (struct tapeLabel *)nextblock;
	fmark = (struct fileMark *)nextblock;
	if (ntohl(bmark->magic) == BLOCK_MAGIC) {
	    if (verbose)
		printf("Data block\n");
	    isdatablock = 1;
	    isheader = 0;
	    data = &nextblock[sizeof(struct blockMark)];
	    if (strncmp(data, "H++NAME#", 8) == 0) {
		volheaderPtr = (struct volumeHeader *)data;
		printHeader(volheaderPtr, &isheader);
	    }
	    if (isheader) {
		code = openOutFile(volheaderPtr);
		nextblock = tapeblock1;
		lastblock = NULL;
		lastblock2 = NULL;
	    } else {
		if (lastblock2 != NULL) {
		    data = &lastblock2[sizeof(struct blockMark)];
		    bmark = (struct blockMark *)lastblock2;
		    writeData(data, ntohl(bmark->count));
		    tblock = lastblock2;
		} else if (lastblock != NULL) {
		    tblock = tapeblock2;
		} else {
		    tblock = tapeblock3;
		}
		lastblock2 = lastblock;
		lastblock = nextblock;
		nextblock = tblock;
	    }
	}

	/* Filemarks come in 3 forms: BEGIN, END, and EOD.
	 * There is no information stored in filemarks.
	 */
	else if (ntohl(fmark->magic) == FILE_MAGIC) {
	    fmtype = ntohl(fmark->nBytes);
	    if (fmtype == FILE_BEGIN) {
		if (verbose)
		    fprintf(stderr, "File mark volume begin\n");
	    } else if (fmtype == FILE_END) {
		if (verbose)
		    fprintf(stderr, "File mark volume end\n");
	    } else if (fmtype == FILE_EOD) {
		if (verbose)
		    fprintf(stderr, "File mark end-of-dump\n");
		eod = 1;
	    }
	}

	/* A dump label */
	else if (ntohl(label->magic) == TAPE_MAGIC) {
	    if (verbose)
		fprintf(stderr, "Dump label\n");
	    printLabel(label);
	    eod = 0;
	}

	else {
	    if (verbose) {
		fprintf(stderr, "Unrecognized tape block - end\n");
	    }
	}

	/* Anything other than a data block closes the out file.
	 * At this point nextblock contains the end of tape file mark,
	 * lastblock contains the last data block for the current volume,
	 * and lastblock2 contains the second to last block for the current
	 * volume. If the volume fits in a single block, lastblock2 will
	 * be NULL. Call writeLastBlocks to strip off the dump trailer before
	 * writing the last of the volume data to the dump file. The dump
	 * trailer may span block boundaries.
	 */
	if (!isdatablock && lastblock) {
	    writeLastBlocks(lastblock, lastblock2);
	    closeOutFile();
	    nextblock = tapeblock1;
	    lastblock = NULL;
	    lastblock2 = NULL;
	}
    } while ((nskip != 0) || (nrestore != 0));

    free(tapeblock1);

    return 0;
}

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

    setlinebuf(stdout);

    ts = cmd_CreateSyntax(NULL, WorkerBee, NULL, 0,
			  "Restore volumes from backup tape");
    cmd_AddParm(ts, "-tape", CMD_SINGLE, CMD_REQUIRED, "tape device");
    cmd_AddParm(ts, "-restore", CMD_SINGLE, CMD_OPTIONAL,
		"# volumes to restore");
    cmd_AddParm(ts, "-skip", CMD_SINGLE, CMD_OPTIONAL, "# volumes to skip");
    cmd_AddParm(ts, "-file", CMD_SINGLE, CMD_OPTIONAL, "filename");
    cmd_AddParm(ts, "-scan", CMD_FLAG, CMD_OPTIONAL, "Scan the tape");
    cmd_AddParm(ts, "-noask", CMD_FLAG, CMD_OPTIONAL,
		"Prompt for each volume");
    cmd_AddParm(ts, "-label", CMD_FLAG, CMD_OPTIONAL, "Display dump label");
    cmd_AddParm(ts, "-vheaders", CMD_FLAG, CMD_OPTIONAL,
		"Display volume headers");
    cmd_AddParm(ts, "-verbose", CMD_FLAG, CMD_OPTIONAL, "verbose");

    return cmd_Dispatch(argc, argv);
}
