/*
 * CMUCS AFStools
 * dumpscan - routines for scanning and manipulating AFS volume dumps
 *
 * Copyright (c) 1998 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.
 */

/* afsdump_extract.c - Extract files from an AFS dump */

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

#include <roken.h>

#include <afs/com_err.h>
#include <afs/cellconfig.h>
#include <afs/vlserver.h>
#include <afs/afsint.h>
#include <afs/volser.h>
#include <rx/rxkad.h>

#include "dumpscan.h"
#include "dumpscan_errs.h"
#include "xf_errs.h"

#define COPYBUFSIZE (256*1024)

extern int optind;
extern char *optarg;

char *argv0;
static char **file_names;
static int *file_vnums, name_count, vnum_count;

static char *input_path, *target;
static int quiet, verbose, error_count, dirs_done, extract_all;
static int nomode, use_realpath, use_vnum;
static int do_acls, do_headers;

static path_hashinfo phi;
static dump_parser dp;

/* Print a usage message and exit */
static void AFS_NORETURN
usage(int status, char *msg)
{
    if (msg)
	fprintf(stderr, "%s: %s\n", argv0, msg);
    fprintf(stderr, "Usage: %s [options] dumpfile [dest [files...]]\n",
	    argv0);
    fprintf(stderr, "  -A     Save ACL's\n");
    fprintf(stderr, "  -H     Save headers\n");
    fprintf(stderr, "  -h     Print this help message\n");
    fprintf(stderr, "  -i     Use vnode numbers\n");
    fprintf(stderr, "  -n     Don't actually create files\n");
    fprintf(stderr, "  -p     Use real pathnames internally\n");
    fprintf(stderr, "  -q     Quiet mode (don't print errors)\n");
    fprintf(stderr, "  -v     Verbose mode\n");
    fprintf(stderr, "The destination directory defaults to .\n");
    fprintf(stderr, "Files may be vnode numbers or volume-relative paths;\n");
    fprintf(stderr, "If vnode numbers are used, files will be extracted\n");
    fprintf(stderr,
	    "a name generated from the vnode number and uniqifier.\n");
    fprintf(stderr, "If paths are used, -p is implied and files will be\n");
    fprintf(stderr, "into correctly-named files.\n");
    exit(status);
}


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

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

    /* Initialize options */
    input_path = 0;
    quiet = verbose = nomode = 0;
    use_realpath = use_vnum = do_acls = do_headers = extract_all = 0;

    /* Initialize other stuff */
    error_count = 0;

    /* Parse the options */
    while ((c = getopt(argc, argv, "AHhinpqv")) != EOF) {
	switch (c) {
	case 'A':
	    do_acls = 1;
	    continue;
	case 'H':
	    do_headers = 1;
	    continue;
	case 'i':
	    use_vnum = 1;
	    continue;
	case 'n':
	    nomode = 1;
	    continue;
	case 'p':
	    use_realpath = 1;
	    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, "Dumpfile name required!");
    input_path = argv[optind];

    if (argc - optind < 2)
	target = ".";
    target = argv[optind + 1];

    vnum_count = name_count = 0;
    if (argc - optind < 3)
	extract_all = 1;
    else {
	argv += optind + 2;
	argc -= optind + 2;
	for (i = 0; i < argc; i++) {
	    if (argv[i][0] == '/')
		name_count++;
	    else
		vnum_count++;
	}
	file_names = malloc(name_count + sizeof(char *));
	file_vnums = malloc(vnum_count + sizeof(afs_uint32));
	if (name_count)
	    use_realpath = 1;

	i_name = i_vnum = 0;
	for (i = 0; i < argc; i++) {
	    if (argv[i][0] == '/')
		file_names[i_name++] = argv[i];
	    else
		file_vnums[i_vnum++] = strtol(argv[i], 0, 0);
	}
    }
}


static int
mkdirp(char *path)
{
    char *x = path, slash;
    struct stat statbuf;

    for (;;) {
	while (*x && *x != '/')
	    x++;
	slash = *x;
	*x = 0;

	if (stat(path, &statbuf)) {
	    if (errno == ENOENT) {
		if (verbose)
		    printf("> mkdir %s\n", path);
		if (!mkdir(path, 0755))
		    errno = 0;
	    }
	}
	if (!slash)
	    break;
	*x++ = '/';
	if (errno)
	    return errno;
    }

    return 0;
}


static char *
modestr(int mode)
{
    static char str[10];
    int i;

    strcpy(str, "rwxrwxrwx");
    for (i = 0; i < 9; i++) {
	if (!(mode & (1 << i)))
	    str[8 - i] = '-';
    }
    if (mode & 01000)
	str[8] = (str[8] == '-') ? 'T' : 't';
    if (mode & 02000)
	str[5] = (str[5] == '-') ? 'S' : 's';
    if (mode & 04000)
	str[2] = (str[2] == '-') ? 'S' : 's';
    return str;
}


static char *month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static char *
datestr(time_t date)
{
    static char str[20];
    time_t clock = time(0);
    struct tm *now, *then;
    int diff;

    now = localtime(&clock);
    then = localtime(&date);

    diff = now->tm_mon - then->tm_mon;
    if (then->tm_year == now->tm_year - 1)
	diff += 12;
    if (then->tm_year == now->tm_year + 1)
	diff -= 12;

    if (diff < 5 || diff > 5)
	sprintf(str, "%3s %2d  %4d", month[then->tm_mon], then->tm_mday,
		then->tm_year + 1900);
    else
	sprintf(str, "%3s %2d %2d:%2d", month[then->tm_mon], then->tm_mday,
		then->tm_hour, then->tm_min);
    return str;
}


/* Should we use this vnode?
 * Return 0 if no, non-0 if yes
 */
static int
usevnode(XFILE * X, afs_uint32 vnum, char *vnodepath)
{
    int vl, vpl, r, i;

    /* Special case */
    if (extract_all || !strcmp(vnodepath, "/"))
	return 1;

    for (i = 0; i < vnum_count; i++)
	if (vnum == file_vnums[i])
	    return 2;

    vl = strlen(vnodepath);
/*fprintf(stderr, "++ checking %s\n", vnodepath);*/
    for (i = 0; i < name_count; i++) {
	vpl = strlen(file_names[i]);
/*  fprintf(stderr, "   %s\n", file_names[i]);*/

	if (vl > vpl) {
	    r = !strncmp(file_names[i], vnodepath, vpl)
		&& vnodepath[vpl] == '/';
	} else if (vl < vpl) {
	    r = !strncmp(file_names[i], vnodepath, vl)
		&& file_names[i][vl] == '/';
	} else {
	    r = !strcmp(file_names[i], vnodepath);
	}
	if (r)
	    return 1;
    }
    return 0;
}


static int
copyfile(XFILE * in, XFILE * out, int size)
{
    static char buf[COPYBUFSIZE];
    int nr, r;

    while (size) {
	nr = (size > COPYBUFSIZE) ? COPYBUFSIZE : size;
	if ((r = xfread(in, buf, nr)))
	    return r;
	if ((r = xfwrite(out, buf, nr)))
	    return r;
	size -= nr;
    }
    return 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);
    }
    return 0;
}


static afs_uint32
dumphdr_cb(afs_dump_header * hdr, XFILE * X, void *refcon)
{
    return 0;
}


static afs_uint32
volhdr_cb(afs_vol_header * hdr, XFILE * X, void *refcon)
{
    return 0;
}


static afs_uint32
directory_cb(afs_vnode * v, XFILE * X, void *refcon)
{
    char *vnodepath;
    int r, use = 0;

    /* Should we even use this? */
    if (!use_vnum) {
	if ((r = Path_Build(X, &phi, v->vnode, &vnodepath, !use_realpath)))
	    return r;
	if (!(use = usevnode(X, v->vnode, vnodepath))) {
	    free(vnodepath);
	    return 0;
	}
    }

    /* Print it out */
    if (verbose) {
	if (use_vnum)
	    printf("d%s %3d %-11d %11d %s #%d:%d\n", modestr(v->mode),
		   v->nlinks, v->owner, v->size, datestr(v->server_date),
		   v->vnode, v->vuniq);
	else
	    printf("d%s %3d %-11d %11d %s %s\n", modestr(v->mode), v->nlinks,
		   v->owner, v->size, datestr(v->server_date), vnodepath);
    } else if (!quiet && !use_vnum)
	printf("%s\n", vnodepath);

    /* Make the directory, if needed */
    if (!nomode && !use_vnum && use != 2) {
	if (strcmp(vnodepath, "/")
	    && (r = mkdirp(vnodepath + 1))) {
	    free(vnodepath);
	    return r;
	}
	if (do_acls) {
	    /* XXX do ACL's later */
	}
    }
    if (!use_vnum)
	free(vnodepath);
    return 0;
}


static afs_uint32
file_cb(afs_vnode * v, XFILE * X, void *refcon)
{
    char *vnodepath, vnpx[30];
    dt_uint64 where;
    XFILE OX;
    int r, use = 0;

    if (!dirs_done) {
	dirs_done = 1;
	if (verbose)
	    printf("* Extracting files...\n");
    }

    /* Should we even use this? */
    if (!use_vnum) {
	if ((r = Path_Build(X, &phi, v->vnode, &vnodepath, !use_realpath)))
	    return r;
	if (!(use = usevnode(X, v->vnode, vnodepath))) {
	    free(vnodepath);
	    return 0;
	}
	if (use == 2) {
	    free(vnodepath);
	    sprintf(vnpx, "#%d:%d", v->vnode, v->vuniq);
	    vnodepath = vnpx;
	}
    } else {
	sprintf(vnpx, "#%d:%d", v->vnode, v->vuniq);
	vnodepath = vnpx;
    }

    /* Print it out */
    if (verbose) {
	printf("-%s %3d %-11d %11d %s %s\n", modestr(v->mode), v->nlinks,
	       v->owner, v->size, datestr(v->server_date), vnodepath);
    } else if (!quiet) {
	printf("%s\n", vnodepath);
    }

    if (!nomode) {
	if ((r = xftell(X, &where))
	    || (r = xfseek(X, &v->d_offset))
	    || (r =
		xfopen_path(&OX, O_RDWR | O_CREAT | O_TRUNC, vnodepath + 1,
			    0644))) {
	    if (vnodepath != vnpx)
		free(vnodepath);
	    return r;
	}
	r = copyfile(X, &OX, v->size);
	xfclose(&OX);
	xfseek(X, &where);
    } else
	r = 0;

    if (vnodepath != vnpx)
	free(vnodepath);
    return r;
}


static afs_uint32
symlink_cb(afs_vnode * v, XFILE * X, void *refcon)
{
    char *vnodepath, *linktarget, vnpx[30];
    dt_uint64 where;
    int r, use = 0;

    if (!dirs_done) {
	dirs_done = 1;
	if (verbose)
	    printf("* Extracting files...\n");
    }

    /* Should we even use this? */
    if (!use_vnum) {
	if ((r = Path_Build(X, &phi, v->vnode, &vnodepath, !use_realpath)))
	    return r;
	if (!(use = usevnode(X, v->vnode, vnodepath))) {
	    free(vnodepath);
	    return 0;
	}
	if (use == 2) {
	    free(vnodepath);
	    sprintf(vnpx, "#%d:%d", v->vnode, v->vuniq);
	    vnodepath = vnpx;
	}
    } else {
	sprintf(vnpx, "#%d:%d", v->vnode, v->vuniq);
	vnodepath = vnpx;
    }

    if (!(linktarget = malloc(v->size + 1))) {
	if (vnodepath != vnpx)
	    free(vnodepath);
	return DSERR_MEM;
    }
    if ((r = xftell(X, &where))
	|| (r = xfseek(X, &v->d_offset))
	|| (r = xfread(X, linktarget, v->size))) {
	if (vnodepath != vnpx)
	    free(vnodepath);
	free(linktarget);
	return r;
    }
    xfseek(X, &where);
    linktarget[v->size] = 0;

    /* Print it out */
    if (verbose)
	printf("l%s %3d %-11d %11d %s %s -> %s\n", modestr(v->mode),
	       v->nlinks, v->owner, v->size, datestr(v->server_date),
	       vnodepath, linktarget);
    else if (!quiet)
	printf("%s\n", vnodepath);

    r = 0;
    if (!nomode) {
	if (symlink(linktarget, vnodepath + 1))
	    r = errno;
    }

    free(linktarget);
    if (vnodepath != vnpx)
	free(vnodepath);
    return r;
}


static afs_uint32
lose_cb(afs_vnode * v, XFILE * F, void *refcon)
{
    if (!dirs_done) {
	dirs_done = 1;
	if (verbose)
	    printf("* Extracting files...\n");
    }

    return 0;
}


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

    parse_options(argc, argv);
    initialize_acfg_error_table();
    initialize_AVds_error_table();
    initialize_rxk_error_table();
    initialize_u_error_table();
    initialize_vl_error_table();
    initialize_vols_error_table();
    initialize_xFil_error_table();
    r = xfopen(&input_file, O_RDONLY, input_path);
    if (r) {
	afs_com_err(argv0, r, "opening %s", input_path);
	exit(2);
    }

    memset(&dp, 0, sizeof(dp));
    dp.cb_error = my_error_cb;
    if (input_file.is_seekable)
	dp.flags |= DSFLAG_SEEK;
    dirs_done = 0;

    if (!use_vnum) {
	dt_uint64 where;

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

	if (verbose)
	    printf("* Building pathname info...\n");
	if ((r = xftell(&input_file, &where))
	    || (r = Path_PreScan(&input_file, &phi, 1))
	    || (r = xfseek(&input_file, &where))) {
	    afs_com_err(argv0, r, "- path initialization failed");
	    xfclose(&input_file);
	    exit(1);
	}
    }

    dp.cb_vnode_dir = directory_cb;
    dp.cb_vnode_file = file_cb;
    dp.cb_vnode_link = symlink_cb;
    dp.cb_vnode_empty = lose_cb;
    dp.cb_vnode_wierd = lose_cb;
    if (do_headers) {
	dp.cb_dumphdr = dumphdr_cb;
	dp.cb_volhdr = volhdr_cb;
    }

    if (!nomode) {
	mkdir(target, 0755);
	if (chdir(target)) {
	    fprintf(stderr, "chdir %s failed: %s\n", target, strerror(errno));
	    exit(1);
	}
    }
    r = ParseDumpFile(&input_file, &dp);

    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);
}
