/*
 * COPYRIGHT NOTICE
 * Copyright (c) 1997 Carnegie Mellon University
 * All Rights Reserved.
 *
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 *
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 *
 * Carnegie Mellon requests users of this software to return to
 *
 *  Software Distribution Coordinator  or  Software_Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 *
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */

/* UB - Unified Backups */
/* methods/afs/dumpscan/afsdump_scan.c - General-purpose dump scanner */

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

#include <roken.h>

#include "dumpscan.h"

extern int opterr, optind;
extern char *optarg;

extern XFILE repair_output;
extern afs_uint32 repair_dumphdr_cb(afs_dump_header *, XFILE *, void *);
extern afs_uint32 repair_volhdr_cb(afs_vol_header *, XFILE *, void *);
extern afs_uint32 repair_vnode_cb(afs_vnode *, XFILE *, void *);

char *argv0;
static char *input_path, *gendump_path;
static afs_uint32 printflags, repairflags, add_admin;
static int quiet, verbose, error_count;

static path_hashinfo phi;
static dump_parser dp;


/* Print a usage message and exit */
static void
usage(int status, char *msg)
{
    if (msg)
	fprintf(stderr, "%s: %s\n", argv0, msg);
    fprintf(stderr, "Usage: %s [options] [file]\n", argv0);
    fprintf(stderr, "  -Pxxx  Set print options:\n");
    fprintf(stderr, "          B = Print backup system header (if any)\n");
    fprintf(stderr, "          H = Print AFS dump header\n");
    fprintf(stderr, "          V = Print AFS volume header\n");
    fprintf(stderr, "          v = List vnodes\n");
    fprintf(stderr, "          p = Include path to each vnode\n");
    fprintf(stderr, "          i = Include info for each vnode\n");
    fprintf(stderr, "          d = List directory contents\n");
    fprintf(stderr, "          a = List access control lists\n");
    fprintf(stderr, "          g = Print debugging info\n");
    fprintf(stderr, "  -Rxxx  Set repair options:\n");
    fprintf(stderr, "          0 = Skip null tags\n");
    fprintf(stderr, "          b = Seek backward to find skipped tags\n");
    fprintf(stderr, "          d = Resync after vnode data\n");
    fprintf(stderr, "          v = Resync after corrupted vnodes\n");
    fprintf(stderr,
	    "  -Annn  Add all rights for ID nnn to every directory\n");
    fprintf(stderr, "  -h     Print this help message\n");
    fprintf(stderr, "  -gxxx  Generate a new dump in file xxx\n");
    fprintf(stderr, "  -q     Quiet mode (don't print errors)\n");
    fprintf(stderr, "  -v     Verbose mode\n");
    exit(status);
}


/* Parse the argument given to the -P option.
 * Returns the resulting * dumpscan print flags (DSPRINT_*).
 * If an unrecognized flag is used, prints an error message and exits.
 */
static afs_uint32
parse_printflags(char *flags)
{
    afs_uint32 result = 0;
    char *x;

    for (x = flags; *x; x++)
	switch (*x) {
	case 'B':
	    result |= DSPRINT_BCKHDR;
	    continue;
	case 'H':
	    result |= DSPRINT_DUMPHDR;
	    continue;
	case 'V':
	    result |= DSPRINT_VOLHDR;
	    continue;
	case 'v':
	    result |= DSPRINT_ITEM;
	    continue;
	case 'p':
	    result |= DSPRINT_PATH;
	    continue;
	case 'i':
	    result |= DSPRINT_VNODE;
	    continue;
	case 'd':
	    result |= DSPRINT_DIR;
	    continue;
	case 'a':
	    result |= DSPRINT_ACL;
	    continue;
	case 'g':
	    result |= DSPRINT_DEBUG;
	    continue;
	default:
	    usage(1, "Invalid print options!");
	}
    return result;
}


/* Parse the argument given to the -R option.
 * Returns the resulting * dumpscan repair flags (DSFIX_*).
 * If an unrecognized flag is used, prints an error message and exits.
 */
static afs_uint32
parse_repairflags(char *flags)
{
    afs_uint32 result = 0;
    char *x;

    for (x = flags; *x; x++)
	switch (*x) {
	case '0':
	    result |= DSFIX_SKIP;
	    continue;
	case 'b':
	    result |= DSFIX_RSKIP;
	    continue;
	case 'd':
	    result |= DSFIX_VDSYNC;
	    continue;
	case 'v':
	    result |= DSFIX_VFSYNC;
	    continue;
	default:
	    usage(1, "Invalid repair options!");
	}
    return result;
}


/* Parse the command-line options */
static void
parse_options(int argc, char **argv)
{
    int c;

    /* Set the program name */
    if (argv0 = strrchr(argv[0], '/'))
	argv0++;
    else
	argv0 = argv[0];

    /* Initialize options */
    input_path = gendump_path = 0;
    printflags = repairflags = add_admin = 0;
    quiet = verbose = 0;

    /* Initialize other stuff */
    error_count = 0;

    /* Parse the options */
    while ((c = getopt(argc, argv, "A:P:R:g:hv")) != EOF) {
	switch (c) {
	case 'A':
	    add_admin = atoi(optarg);
	    continue;
	case 'P':
	    printflags = parse_printflags(optarg);
	    continue;
	case 'R':
	    repairflags = parse_repairflags(optarg);
	    continue;
	case 'g':
	    gendump_path = optarg;
	    continue;
	case 'q':
	    quiet = 1;
	    continue;
	case 'v':
	    verbose = 1;
	    continue;
	case 'h':
	    usage(0, 0);
	default:
	    usage(1, "Invalid option!");
	}
    }

    if (quiet && verbose)
	usage(1, "Can't specify both -q and -v");

    /* Parse non-option arguments */
    if (argc - optind > 1)
	usage(1, "Too many arguments!");
    input_path = (argc == optind) ? "-" : argv[optind];
    if (add_admin && !gendump_path)
	add_admin = 0;
}


/* A callback to count and print errors */
static afs_uint32
my_error_cb(afs_uint32 code, int fatal, void *ref, char *msg, ...)
{
    va_list alist;

    error_count++;
    if (!quiet) {
	va_start(alist, msg);
	afs_com_err_va(argv0, code, msg, alist);
	va_end(alist);
    }
}


/* A callback to print the path of a vnode. */
static afs_uint32
print_vnode_path(afs_vnode * v, XFILE * X, void *refcon)
{
    afs_uint32 r;
    char *name = 0;

    /* Do repair, but only for known vnode types */
    if (gendump_path && (!(v->field_mask & F_VNODE_TYPE)
			 || v->type != vFile || v->type != vDirectory
			 || v->type != vSymlink)) {
	r = repair_vnode_cb(v, X, refcon);
	if (r)
	    return r;
    }
    r = Path_Build(X, &phi, v->vnode, &name, 0);
    if (!r && name)
	printf(" Path: %s\n", name);
    if (name)
	free(name);
    return r;
}


static afs_uint32
munge_admin_acl(afs_vnode * v, XFILE * X, void *refcon)
{
    struct acl_accessList *acl;
    int add_entry = 1, remove_entry = -1;
    int i, o, n;

    acl = (struct acl_accessList *)(v->acl);
    o = n = ntohl(acl->positive);
    for (i = 0; i < n; i++)
	if (ntohl(acl->entries[i].id) == add_admin)
	    add_entry = 0;
    n = ntohl(acl->negative);
    for (i = o; i < n + o; i++)
	if (ntohl(acl->entries[i].id) == add_admin)
	    remove_entry = i;

    if (add_entry) {
	for (i = (remove_entry < 0) ? o + n : remove_entry; i > o; i--) {
	    acl->entries[i].id = acl->entries[i - 1].id;
	    acl->entries[i].rights = acl->entries[i - 1].rights;
	}
	acl->entries[o].id = htonl(add_admin);
	acl->entries[o].rights =
	    htonl((PRSFS_READ | PRSFS_LOOKUP | PRSFS_INSERT | PRSFS_DELETE |
		   PRSFS_WRITE | PRSFS_LOCK | PRSFS_ADMINISTER));
	acl->positive = htonl(o + 1);
	if (remove_entry < 0)
	    acl->total = htonl(o + n + 1);
	else
	    acl->negative = htonl(n - 1);
    } else if (remove_entry >= 0) {
	for (i = remove_entry; i < o + n - 1; i++) {
	    acl->entries[i].id = acl->entries[i + 1].id;
	    acl->entries[i].rights = acl->entries[i + 1].rights;
	}
	acl->negative = htonl(n - 1);
	acl->total = htonl(o + n - 1);
    }
    return repair_vnode_cb(v, X, refcon);
}


/* Setup for generating a repaired dump */
static afs_uint32
setup_repair(void)
{
    afs_uint32 r;

    r = xfopen(&repair_output, gendump_path, O_RDWR, 0644);
    if (r)
	return r;

    dp.cb_dumphdr = repair_dumphdr_cb;
    dp.cb_volhdr = repair_volhdr_cb;
    dp.cb_vnode_dir = repair_vnode_cb;
    dp.cb_vnode_file = repair_vnode_cb;
    dp.cb_vnode_link = repair_vnode_cb;
    dp.cb_vnode_empty = repair_vnode_cb;
    return 0;
}


/* Main program */
void
main(int argc, char **argv)
{
    XFILE *X;
    afs_uint32 r;

    parse_options(argc, argv);
    initialize_UB_error_table();
    initialize_UBsp_error_table();
    initialize_AVds_error_table();
    r = xfopen(&X, input_path, O_RDONLY, 0);
    if (r) {
	afs_com_err(argv0, r, "opening %s", input_path);
	exit(2);
    }

    memset(&dp, 0, sizeof(dp));
    dp.cb_error = my_error_cb;
    dp.repair_flags = repairflags;
    if (X->is_seekable)
	dp.flags |= DSFLAG_SEEK;
    else {
	if (repairflags)
	    fprintf(stderr,
		    "Repair modes available only for seekable dumps\n");
	if (printflags & DSPRINT_PATH)
	    fprintf(stderr,
		    "Path-printing available only for seekable dumps\n");
	if (repairflags || (printflags & DSPRINT_PATH))
	    exit(1);
    }

    if (gendump_path && (r = setup_repair())) {
	afs_com_err(argv0, r, "setting up repair output");
	xfclose(X);
	exit(2);
    }

    if (printflags & DSPRINT_PATH) {
	dt_uint64 where;

	dp.print_flags = printflags & DSPRINT_DEBUG;
	memset(&phi, 0, sizeof(phi));
	phi.p = &dp;

	if ((r = xftell(X, &where))
	    || (r = Path_PreScan(X, &phi, 0))
	    || (r = xfseek(X, &where))) {
	    afs_com_err(argv0, r, "- path initialization failed");
	    xfclose(X);
	    exit(2);
	}

	dp.cb_vnode_dir = print_vnode_path;
	dp.cb_vnode_file = print_vnode_path;
	dp.cb_vnode_link = print_vnode_path;
	dp.cb_vnode_empty = print_vnode_path;
	dp.cb_vnode_wierd = print_vnode_path;
    }

    if (add_admin) {
	dp.cb_vnode_dir = munge_admin_acl;
    }

    dp.print_flags = printflags;
    r = ParseDumpFile(X, &dp);
    if (gendump_path) {
	if (!r)
	    r = DumpDumpEnd(&repair_output);
	if (!r)
	    r = xfclose(&repair_output);
	else
	    xfclose(&repair_output);
    }

    if (verbose && error_count)
	fprintf(stderr, "*** %d errors\n", error_count);
    if (r && !quiet)
	fprintf(stderr, "*** FAILED: %s\n", afs_error_message(r));
    exit(0);
}
