/*  Copyright 1995-1999,2001-2003,2007,2009,2011 Alain Knaff.
 *  This file is part of mtools.
 *
 *  Mtools is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Mtools is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Mtools.  If not, see <http://www.gnu.org/licenses/>.
 *
 * mbadblocks.c
 * Mark bad blocks on disk
 *
 */

#include "sysincludes.h"
#include "msdos.h"
#include "mtools.h"
#include "mainloop.h"
#include "fsP.h"

#define N_PATTERN 311

static void usage(int ret) NORETURN;
static void usage(int ret)
{
	fprintf(stderr, "Mtools version %s, dated %s\n",
		mversion, mdate);
	fprintf(stderr, "Usage: %s: [-c clusterList] [-s sectorList] [-c] [-V] device\n",
		progname);
	exit(ret);
}

static void checkListTwice(char *filename) {
	if(filename != NULL) {
		fprintf(stderr, "Only one of the -c or -s options may be given\n");
		exit(1);
	}
}

/**
 * Marks given cluster as bad, but prints error instead if cluster already used
 */
static void mark(Fs_t *Fs, long offset, unsigned int badClus) {
	unsigned int old = Fs->fat_decode((Fs_t*)Fs, offset);
	if(old == 0) {
		fatEncode((Fs_t*)Fs, offset, badClus);
		return;
	}
	if(old == badClus) {
		fprintf(stderr, "Cluster %ld already marked\n", offset);
	} else {
		fprintf(stderr, "Cluster %ld is busy\n", offset);
	}
}

static char *in_buf;
static char *pat_buf;
static int in_len;


static void progress(unsigned int i, unsigned int total) {
	if(i % 10 == 0)
		fprintf(stderr,	"                     \r%d/%d\r", i, total);
}

static int scan(Fs_t *Fs, Stream_t *dev,
		long cluster, unsigned int badClus,
		char *buffer, int doWrite) {
	off_t start;
	off_t ret;
	off_t pos;
	int bad=0;

	if(Fs->fat_decode((Fs_t*)Fs, cluster))
		/* cluster busy, or already marked */
		return 0;
	start = (cluster - 2) * Fs->cluster_size + Fs->clus_start;
	pos = sectorsToBytes((Stream_t*)Fs, start);
	if(doWrite) {
		ret = force_write(dev, buffer, pos, in_len);
		if(ret < in_len )
			bad = 1;
	} else {
		ret = force_read(dev, in_buf, pos, in_len);
		if(ret < in_len )
			bad = 1;
		else if(buffer) {
			int i;
			for(i=0; i<in_len; i++)
				if(in_buf[i] != buffer[i]) {
					bad = 1;
					break;
				}
		}
	}

	if(bad) {
		printf("Bad cluster %ld found\n", cluster);
		fatEncode((Fs_t*)Fs, cluster, badClus);
		return 1;
	}
	return 0;
}

void mbadblocks(int argc, char **argv, int type UNUSEDP)
{
	unsigned int i;
	unsigned int startSector=2;
	unsigned int endSector=0;
	struct MainParam_t mp;
	Fs_t *Fs;
	Stream_t *Dir;
	int ret;
	char *filename = NULL;
	int c;
	unsigned int badClus;
	int sectorMode=0;
	int writeMode=0;

	while ((c = getopt(argc, argv, "i:s:cwS:E:")) != EOF) {
		switch(c) {
		case 'i':
			set_cmd_line_image(optarg);
			break;
		case 'c':
			checkListTwice(filename);
			filename = strdup(optarg);
			break;
		case 's':
			checkListTwice(filename);
			filename = strdup(optarg);
			sectorMode = 1;
			break;
		case 'S':
			startSector = atol(optarg); 
			break;
		case 'E':
			endSector = atol(optarg); 
			break;
		case 'w':
			writeMode = 1;
			break;
		case 'h':
			usage(0);
		default:
			usage(1);
		}
	}

	if (argc != optind+1 ||
	    !argv[optind][0] || argv[optind][1] != ':' || argv[optind][2]) {
		usage(1);
	}

	init_mp(&mp);

	Dir = open_root_dir(argv[optind][0], O_RDWR, NULL);
	if (!Dir) {
		fprintf(stderr,"%s: Cannot initialize drive\n", argv[0]);
		exit(1);
	}

	Fs = (Fs_t *)GetFs(Dir);
	in_len = Fs->cluster_size * Fs->sector_size;
	in_buf = malloc(in_len);
	if(!in_buf) {
		printOom();
		ret = 1;
		goto exit_0;
	}
	if(writeMode) {
		pat_buf=malloc(in_len * N_PATTERN);
		if(!pat_buf) {
			printOom();
			ret = 1;
			goto exit_0;
		}
		srandom(time(NULL));
		for(i=0; i < in_len * N_PATTERN; i++) {
			pat_buf[i] = random();
		}
	}
	for(i=0; i < Fs->clus_start; i++ ){
		ret = READS(Fs->Next, in_buf, 
			    sectorsToBytes((Stream_t*)Fs, i), Fs->sector_size);
		if( ret < 0 ){
			perror("early error");
			goto exit_0;
		}
		if(ret < (signed int) Fs->sector_size){
			fprintf(stderr,"end of file in file_read\n");
			ret = 1;
			goto exit_0;
		}
	}
	ret = 0;

	badClus = Fs->last_fat + 1;

	if(startSector < 2)
		startSector = 2;
	if(endSector > Fs->num_clus + 2 || endSector <= 0) 
		endSector = Fs->num_clus + 2;

	if(filename) {
		char line[80];

		FILE *f = fopen(filename, "r");
		if(f == NULL) {
			fprintf(stderr, "Could not open %s (%s)\n",
				filename, strerror(errno));
			ret = 1;
			goto exit_0;
		}
		while(fgets(line, sizeof(line), f)) {
			char *ptr = line + strspn(line, " \t");
			long offset = strtoul(ptr, 0, 0);
			if(sectorMode)
				offset = (offset-Fs->clus_start)/Fs->cluster_size + 2;
			if(offset < 2) {
				fprintf(stderr, "Sector before start\n");
			} else if(offset >= Fs->num_clus) {
				fprintf(stderr, "Sector beyond end\n");
			} else {
				mark(Fs, offset, badClus);
				ret = 1;
			}
		}
	} else {
		Stream_t *dev;
		dev = Fs->Next;
		if(dev->Next)
			dev = dev->Next;

		in_len = Fs->cluster_size * Fs->sector_size;
		if(writeMode) {
			/* Write pattern */
			for(i=startSector; i< endSector; i++){
				if(got_signal)
					break;
				progress(i, Fs->num_clus);
				ret |= scan(Fs, dev, i, badClus, 
					    pat_buf + in_len * (i % N_PATTERN),
					    1);
			}

			/* Flush cache, so that we are sure we read the data
			   back from disk, rather than from the cache */
			if(!got_signal)
				DISCARD(dev);

			/* Read data back, and compare to pattern */
			for(i=startSector; i< endSector; i++){
				if(got_signal)
					break;
				progress(i, Fs->num_clus);
				ret |= scan(Fs, dev, i, badClus, 
					    pat_buf + in_len * (i % N_PATTERN),
					    0);
			}

		} else {

			for(i=startSector; i< endSector; i++){
				if(got_signal)
					break;
				progress(i, Fs->num_clus);
				ret |= scan(Fs, dev, i, badClus, NULL, 0);
			}
		}
	}
 exit_0:
	FREE(&Dir);
	exit(ret);
}
