/*
 * e2undo.c - Replay an undo log onto an ext2/3/4 filesystem
 *
 * Copyright IBM Corporation, 2007
 * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
 *
 * %Begin-Header%
 * This file may be redistributed under the terms of the GNU Public
 * License.
 * %End-Header%
 */

#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#include <fcntl.h>
#if HAVE_ERRNO_H
#include <errno.h>
#endif
#include "ext2fs/tdb.h"
#include "ext2fs/ext2fs.h"
#include "nls-enable.h"

unsigned char mtime_key[] = "filesystem MTIME";
unsigned char uuid_key[] = "filesystem UUID";
unsigned char blksize_key[] = "filesystem BLKSIZE";

char *prg_name;

static void usage(void)
{
	fprintf(stderr,
		_("Usage: %s <transaction file> <filesystem>\n"), prg_name);
	exit(1);
}

static int check_filesystem(TDB_CONTEXT *tdb, io_channel channel)
{
	__u32   s_mtime;
	__u8    s_uuid[16];
	errcode_t retval;
	TDB_DATA tdb_key, tdb_data;
	struct ext2_super_block super;

	io_channel_set_blksize(channel, SUPERBLOCK_OFFSET);
	retval = io_channel_read_blk64(channel, 1, -SUPERBLOCK_SIZE, &super);
	if (retval) {
		com_err(prg_name,
			retval, _("Failed to read the file system data \n"));
		return retval;
	}

	tdb_key.dptr = mtime_key;
	tdb_key.dsize = sizeof(mtime_key);
	tdb_data = tdb_fetch(tdb, tdb_key);
	if (!tdb_data.dptr) {
		retval = EXT2_ET_TDB_SUCCESS + tdb_error(tdb);
		com_err(prg_name, retval,
			_("Failed tdb_fetch %s\n"), tdb_errorstr(tdb));
		return retval;
	}

	s_mtime = *(__u32 *)tdb_data.dptr;
	if (super.s_mtime != s_mtime) {

		com_err(prg_name, 0,
			_("The file system Mount time didn't match %u\n"),
			s_mtime);

		return  -1;
	}


	tdb_key.dptr = uuid_key;
	tdb_key.dsize = sizeof(uuid_key);
	tdb_data = tdb_fetch(tdb, tdb_key);
	if (!tdb_data.dptr) {
		retval = EXT2_ET_TDB_SUCCESS + tdb_error(tdb);
		com_err(prg_name, retval,
			_("Failed tdb_fetch %s\n"), tdb_errorstr(tdb));
		return retval;
	}
	memcpy(s_uuid, tdb_data.dptr, sizeof(s_uuid));
	if (memcmp(s_uuid, super.s_uuid, sizeof(s_uuid))) {
		com_err(prg_name, 0,
			_("The file system UUID didn't match \n"));
		return -1;
	}

	return 0;
}

static int set_blk_size(TDB_CONTEXT *tdb, io_channel channel)
{
	int block_size;
	errcode_t retval;
	TDB_DATA tdb_key, tdb_data;

	tdb_key.dptr = blksize_key;
	tdb_key.dsize = sizeof(blksize_key);
	tdb_data = tdb_fetch(tdb, tdb_key);
	if (!tdb_data.dptr) {
		retval = EXT2_ET_TDB_SUCCESS + tdb_error(tdb);
		com_err(prg_name, retval,
			_("Failed tdb_fetch %s\n"), tdb_errorstr(tdb));
		return retval;
	}

	block_size = *(int *)tdb_data.dptr;
#ifdef DEBUG
	printf("Block size %d\n", block_size);
#endif
	io_channel_set_blksize(channel, block_size);

	return 0;
}

int main(int argc, char *argv[])
{
	int c,force = 0;
	TDB_CONTEXT *tdb;
	TDB_DATA key, data;
	io_channel channel;
	errcode_t retval;
	int  mount_flags;
	blk64_t  blk_num;
	char *device_name, *tdb_file;
	io_manager manager = unix_io_manager;

#ifdef ENABLE_NLS
	setlocale(LC_MESSAGES, "");
	setlocale(LC_CTYPE, "");
	bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
	textdomain(NLS_CAT_NAME);
	set_com_err_gettext(gettext);
#endif
	add_error_table(&et_ext2_error_table);

	prg_name = argv[0];
	while((c = getopt(argc, argv, "f")) != EOF) {
		switch (c) {
			case 'f':
				force = 1;
				break;
			default:
				usage();
		}
	}

	if (argc != optind + 2)
		usage();

	tdb_file = argv[optind];
	device_name = argv[optind+1];

	tdb = tdb_open(tdb_file, 0, 0, O_RDONLY, 0600);

	if (!tdb) {
		com_err(prg_name, errno,
				_("Failed tdb_open %s\n"), tdb_file);
		exit(1);
	}

	retval = ext2fs_check_if_mounted(device_name, &mount_flags);
	if (retval) {
		com_err(prg_name, retval, _("Error while determining whether "
				"%s is mounted.\n"), device_name);
		exit(1);
	}

	if (mount_flags & EXT2_MF_MOUNTED) {
		com_err(prg_name, retval, _("e2undo should only be run on "
				"unmounted file system\n"));
		exit(1);
	}

	retval = manager->open(device_name,
				IO_FLAG_EXCLUSIVE | IO_FLAG_RW,  &channel);
	if (retval) {
		com_err(prg_name, retval,
				_("Failed to open %s\n"), device_name);
		exit(1);
	}

	if (!force && check_filesystem(tdb, channel)) {
		exit(1);
	}

	if (set_blk_size(tdb, channel)) {
		exit(1);
	}

	for (key = tdb_firstkey(tdb); key.dptr; key = tdb_nextkey(tdb, key)) {
		if (!strcmp((char *) key.dptr, (char *) mtime_key) ||
		    !strcmp((char *) key.dptr, (char *) uuid_key) ||
		    !strcmp((char *) key.dptr, (char *) blksize_key)) {
			continue;
		}

		data = tdb_fetch(tdb, key);
		if (!data.dptr) {
			com_err(prg_name, 0,
				_("Failed tdb_fetch %s\n"), tdb_errorstr(tdb));
			exit(1);
		}
		blk_num = *(blk64_t *)key.dptr;
		printf(_("Replayed transaction of size %zd at location %llu\n"),
							data.dsize, blk_num);
		retval = io_channel_write_blk64(channel, blk_num,
						-data.dsize, data.dptr);
		if (retval == -1) {
			com_err(prg_name, retval,
					_("Failed write %s\n"),
					strerror(errno));
			exit(1);
		}
	}
	io_channel_close(channel);
	tdb_close(tdb);

	return 0;
}
