/*
 * zap.c --- zap block
 *
 * Copyright (C) 2012 Theodore Ts'o.  This file may be redistributed
 * under the terms of the GNU Public License.
 */

#include "config.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#include <sys/types.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
extern int optind;
extern char *optarg;
#endif

#include "debugfs.h"

void do_zap_block(int argc, char *argv[])
{
	unsigned long	pattern = 0;
	unsigned char	*buf;
	ext2_ino_t	inode;
	errcode_t	errcode;
	blk64_t		block;
	char		*file = NULL;
	int		c, err;
	int		offset = -1;
	int		length = -1;
	int		bit = -1;

	if (check_fs_open(argv[0]))
		return;
	if (check_fs_read_write(argv[0]))
		return;

	reset_getopt();
	while ((c = getopt (argc, argv, "b:f:l:o:p:")) != EOF) {
		switch (c) {
		case 'f':
			file = optarg;
			break;
		case 'b':
			bit = parse_ulong(optarg, argv[0],
					  "bit", &err);
			if (err)
				return;
			if (bit >= (int) current_fs->blocksize * 8) {
				com_err(argv[0], 0, "The bit to flip "
					"must be within a %d block\n",
					current_fs->blocksize);
				return;
			}
			break;
		case 'p':
			pattern = parse_ulong(optarg, argv[0],
					      "pattern", &err);
			if (err)
				return;
			if (pattern >= 256) {
				com_err(argv[0], 0, "The fill pattern must "
					"be an 8-bit value\n");
				return;
			}
			break;
		case 'o':
			offset = parse_ulong(optarg, argv[0],
					     "offset", &err);
			if (err)
				return;
			if (offset >= (int) current_fs->blocksize) {
				com_err(argv[0], 0, "The offset must be "
					"within a %d block\n",
					current_fs->blocksize);
				return;
			}
			break;

			break;
		case 'l':
			length = parse_ulong(optarg, argv[0],
					     "length", &err);
			if (err)
				return;
			break;
		default:
			goto print_usage;
		}
	}

	if (bit > 0 && offset > 0) {
		com_err(argv[0], 0, "The -o and -b options can not be mixed.");
		return;
	}

	if (offset < 0)
		offset = 0;
	if (length < 0)
		length = current_fs->blocksize - offset;
	if ((offset + length) > (int) current_fs->blocksize) {
		com_err(argv[0], 0, "The specified length is too bug\n");
		return;
	}

	if (argc != optind+1) {
	print_usage:
		com_err(0, 0, "Usage:\tzap_block [-f file] [-o offset] "
			"[-l length] [-p pattern] block_num");
		com_err(0, 0, "\tzap_block [-f file] [-b bit] "
			"block_num");
		return;
	}

	block = parse_ulonglong(argv[optind], argv[0], "block", &err);
	if (err)
		return;

	if (file) {
		inode = string_to_inode(file);
		if (!inode)
			return;
		errcode = ext2fs_bmap2(current_fs, inode, 0, 0, 0,
				       block, 0, &block);
		if (errcode) {
			com_err(argv[0], errcode,
				"while mapping logical block %llu\n", block);
			return;
		}
	}

	buf = malloc(current_fs->blocksize);
	if (!buf) {
		com_err(argv[0], 0, "Couldn't allocate block buffer");
		return;
	}

	errcode = io_channel_read_blk64(current_fs->io, block, 1, buf);
	if (errcode) {
		com_err(argv[0], errcode,
			"while reading block %llu\n", block);
		goto errout;
	}

	if (bit >= 0)
		buf[bit >> 3] ^= 1 << (bit & 7);
	else
		memset(buf+offset, pattern, length);

	errcode = io_channel_write_blk64(current_fs->io, block, 1, buf);
	if (errcode) {
		com_err(argv[0], errcode,
			"while write block %llu\n", block);
		goto errout;
	}

errout:
	free(buf);
	return;
}

void do_block_dump(int argc, char *argv[])
{
	unsigned char	*buf;
	ext2_ino_t	inode;
	errcode_t	errcode;
	blk64_t		block;
	char		*file = NULL;
	int		xattr_dump = 0;
	int		c, err;

	if (check_fs_open(argv[0]))
		return;

	reset_getopt();
	while ((c = getopt (argc, argv, "f:x")) != EOF) {
		switch (c) {
		case 'f':
			file = optarg;
			break;
		case 'x':
			xattr_dump = 1;
			break;
		default:
			goto print_usage;
		}
	}

	if (argc != optind + 1) {
	print_usage:
		com_err(0, 0, "Usage: block_dump [-x] [-f inode] block_num");
		return;
	}

	block = parse_ulonglong(argv[optind], argv[0], "block", &err);
	if (err)
		return;

	if (file) {
		inode = string_to_inode(file);
		if (!inode)
			return;
		errcode = ext2fs_bmap2(current_fs, inode, 0, 0, 0,
				       block, 0, &block);
		if (errcode) {
			com_err(argv[0], errcode,
				"while mapping logical block %llu\n", block);
			return;
		}
	}

	buf = malloc(current_fs->blocksize);
	if (!buf) {
		com_err(argv[0], 0, "Couldn't allocate block buffer");
		return;
	}

	errcode = io_channel_read_blk64(current_fs->io, block, 1, buf);
	if (errcode) {
		com_err(argv[0], errcode,
			"while reading block %llu\n", block);
		goto errout;
	}

	if (xattr_dump)
		block_xattr_dump(stdout, buf, current_fs->blocksize);
	else
		do_byte_hexdump(stdout, buf, current_fs->blocksize);
errout:
	free(buf);
}

void do_byte_hexdump(FILE *fp, unsigned char *buf, size_t bufsize)
{
	size_t		i, j, max;
	int		suppress = -1;

	for (i = 0; i < bufsize; i += 16) {
		max = (bufsize - i > 16) ? 16 : bufsize - i;
		if (suppress < 0) {
			if (i && memcmp(buf + i, buf + i - max, max) == 0) {
				suppress = i;
				fprintf(fp, "*\n");
				continue;
			}
		} else {
			if (memcmp(buf + i, buf + suppress, max) == 0)
				continue;
			suppress = -1;
		}
		fprintf(fp, "%04o  ", (unsigned int)i);
		for (j = 0; j < 16; j++) {
			if (j < max)
				fprintf(fp, "%02x", buf[i+j]);
			else
				fprintf(fp, "  ");
			if ((j % 2) == 1)
				fprintf(fp, " ");
		}
		fprintf(fp, " ");
		for (j = 0; j < max; j++)
			fprintf(fp, "%c", isprint(buf[i+j]) ? buf[i+j] : '.');
		fprintf(fp, "\n");
	}
	fprintf(fp, "\n");
}
