/*  double/dbck.c	checks consistency of DouBle file  20 Dec 1993
 *
 *  Overhauled changes in Dec 1994.
 *
 *  Copyright (C) 1994 Jean-Marc Verbavatz  <verbavatz@achaz.saclay.cea.fr>
 *
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <errno.h>
#include <malloc.h>
#include <linux/fs.h>
#include <linux/double.h>

#define AUTO	1
#define VERBOSE 2
#define BCHECK	4
#define ASK	8
#define FORCE	16

struct dble_device db;
extern struct db_header dbh;
extern unsigned char mask[];

unsigned char memory[65536];	/* memory for (de)compression */
unsigned char *bitmap = NULL;

int options = 0;
unsigned long errors;

void read_opt(char *name, char *s)
{
	while(*s) {
		switch(*s) {
			case 'a': options |= AUTO; /* Auto repair */
				  break;
			case 'v': options |= VERBOSE; /* Verbose */
				  break;
			case 't': options |= BCHECK; /* Full check (blocks also) */
				  break;
			case 'r': options |= ASK; /* interactive */
				  break;
			case 'f': options |= FORCE; /* force checking */
				  break;
			case 0:	  return;
			default:  usage(name);
		}
		s++;
	}
}


/* Get Yes/No answer either from user or from -a option in arg list
 * return 'y' or 'n'
 */

char get_answer(int options)
{
int c,d;

printf(" Fix ? ");
if(options & AUTO)		/* always yes */
	c = 'y';
else if(!(options & ASK))		/* always no */
	c = 'n';
else {			/* interactive */
	fflush(stdout);
	while((c=tolower(getchar())) && c!= -1 && c!='n' && c!='y');
	while((d=getchar())!=-1 && d!='\n');
	return (c == -1 ? 'n' : c);
}
if(c == 'n') printf("No\n");
else printf("Yes\n");
return c;
}

u_char buffer[DB_MAXSZ], buffer1[DB_MAXSZ];


/* Check consistency of cluster n, with options from arglist
 * return value:
	 0 = OK
	-1 = logical error
        -2 = compression error
        -4 = BAT is to short
        -5 = size of raw data is not correct
*/
int get_cluster(struct dble_device *db, u_long n, int options)
{
u_long *p;
int i,j;
struct BAT *b;
u_char *s;

if(!get_BAT(db, n)) return -1;
b = (struct BAT *)(db->BAT_block + db->BATsize*(n%(db->isize/db->BATsize)));

p=&b->iblock;
s = buffer;

/* cleck block list for cluster */
for(i=j=0; i<db->ratio; i++, j++, p++) {
	if(*p==0) {
/* End of list; make sure next are clear as well */
		while(++i<db->ratio)
			if(*(++p)!=0) {
				printf("Error in cluster map, cluster %ld.", n);
				if(get_answer(options)=='y') {
					*p=0;
					db->BAT_mod = 1; /* data change in BAT */
				} else errors++;
			}
		if(j==0)
			return 0;	/* Unused */
		break;
	}
/* make sure block number is valid */
	if(*p >= db->iblocks) {
		printf("\nBlock %ld is out of range, cluster %ld.", *p, n);
		if(get_answer(options)=='y') {
			while(i++<db->ratio)
				*p++=0;
			db->BAT_mod = 1;
		} else errors++;
		break;
	}
/* make sure bit is not already used in bitmap */
	if(!(bitmap[(*p)>>3]&mask[*p&7])) {
		printf("\nBlock %ld multiply allocated, cluster %ld.", *p, n);
		if(get_answer(options)=='y') {
			while(i++<db->ratio)
				*p++=0;
			db->BAT_mod = 1;
		} else errors++;
		break;
	}
/* update memory bitmap copy */
	bitmap[(*p)>>3] &= ~mask[*p&7];

/* read block if check cluster option (-t) was selected */
	if(options & BCHECK) db_rw_block(db, READ, *p, s, db->isize);
	s += db->isize;
}

if(options & BCHECK) {
/* Check data in blocks */
	switch(b->type) {
/* check decompression, i is size of raw data update old values if necessary */
		case -1: /* cluster is free, block list must be empty */
			for(p=&b->iblock, i=0; i<db->ratio; i++, p++)
				if(*p) {
					*p = 0;
					db->BAT_mod = 1;
				}
			break;
		case 0:	/* no compression block list must be full */
			if(i<db->ratio) return -4;
			i = db->osize;
			break;
		case 1: /* LZW, try to decompress cluster */
			 i = RLZW((code *) buffer, buffer1, db->osize);
			break;
		case 10: /* pred, try to decompress cluster */
			 i = rpred(buffer, buffer1, j*db->isize, db->osize);
			 break;
		case 101: /* used for testing algorithms */
			 break;
		case 102: /* old value for LZV */
			 b->type = 11; db->BAT_mod = 1;
		case 11: /* LZV, try to decompress cluster */
			 i = rLZV(buffer, buffer1, j*db->isize, db->osize);
			 break;
		case 212: /* old value for LZRW2 */
			 b->type = 12; db->BAT_mod = 1;
		case 12: /* LZRW2, try to decompress cluster */
			 i = rlzrw2(buffer, buffer1, j*db->isize, db->osize);
			 break;
		case 312: /* old value for LZRW3a */
			 b->type = 13; db->BAT_mod = 1;
		case 13: /* LZRW3a, try to decompress cluster */
			 i = rlzrw3a(buffer, buffer1, j*db->isize, db->osize);
			 break;
		default: printf("Compression %d unknown.", b->type);
			 if(get_answer(options) == 'y') { /* clear cluster */
			 	b->type = -1; db->BAT_mod = 1;
				for(p=&b->iblock, i=0; i<db->ratio; i++, p++)
					if(*p) {
						*p = 0;
						db->BAT_mod = 1;
					}
			 } else errors++;
			 return -2;
	}
	/* Is i compatible with j ? */
	if(i <= (j-1)*db->isize || i > j*db->isize) { /* wrong size */
		return -5;
	}
} else	/* Just check algorithm */
	switch(b->type) { 
		case -1: /* clear */
		case 0: /* no compression */
		case 1:
		case 10:
		case 11:
		case 12:
		case 13:
		case 101: 
			  break;
/* update older values (version <0.3) */
		case 102: b->type = 11; db->BAT_mod = 1; break;
		case 212: b->type = 12; db->BAT_mod = 1; break;
		case 312: b->type = 13; db->BAT_mod = 1; break;
		default: printf("Compression %d unknown.", b->type);
			 if(get_answer(options) == 'y') {
			 	b->type = -1; db->BAT_mod = 1;
			 } else errors++;
			 return -2;
	}

return j;					/* j iblocks in use, OK */
}


main(argc, argv)
int argc;
char **argv;
{
u_long	n;
u_long	empty, total, blocks;
float	factor;
char	*file = NULL;
int	i, j;

/* read args */
for(i = 1; i < argc; i++)
	switch(*argv[i]) {
		case '-': read_opt(argv[0], argv[i]+1);
			  break;
		default: if(file != NULL) usage(argv[0]);
			 file = argv[i];
	}
/* open device/file */
if(file == NULL || !db_open(&db, file)) usage(argv[0]);

/* exit if clean */
if(!(options & FORCE) && dbh.clean) {
	printf("%s is clean, no check\n", file);
	db_close(&db, 0);
	exit(0);
}

/* allocate memory copy of bitmap */
n = (dbh.iblocks+7)>>3;
bitmap = malloc(n);
if(bitmap == NULL) {
	fprintf(stderr, "%s: not enough memory\n", argv[0]);
	db_close(&db, 0);
	exit(0);
}

/* initialize bitmap */
memset(bitmap, 255, n);
for(n=0; n < db.addr_blocks; n++)
	bitmap[n>>3] &= ~mask[n&7];

if(options & BCHECK) { /* We'll need the decomp functions */
	i = iLZW(memory);
	if(i > 65536) options &= ~BCHECK; /* can't use decomp clear option */
	i = iLZV(memory);
	if(i > 65536) options &= ~BCHECK;
	i = ilzrw2(memory);
	if(i > 65536) options &= ~BCHECK;
	i = ilzrw3a(memory);
	if(i > 65536) options &= ~BCHECK;
	i = ipred(memory);
	if(i > 65536) options &= ~BCHECK;
}

/* Hi there */
printf("DouBle device version %d.%d\n", dbh.version>>8, dbh.version&0xff);

if(dbh.version < 3)
	printf("Old DouBle device version - check documentation\n");

if(options & VERBOSE) printf("Default compression algorithm: %d\n", db.code);

if(options & VERBOSE) printf("Checking clusters\n");
total = blocks = empty = 0;
for(n = errors = 0; n<db.oblocks; n++) {
	i = get_cluster(&db, n, options);
	if((options & VERBOSE) && !(n&0x7f)) { /* hash printing */
		putchar('.');
		fflush(stdout);
	}
	switch(i) { /* display error */
		case  0: empty++; break;
		case -1: printf("\nCannot read cluster map");
			 break;
		case -2: break;
		case -4: printf("\nCluster truncated");
			 break;
		case -5: printf("\nDecompression error");
			 break;
		default: if(i > 0) total += i, blocks++;
	}
	if(i < 0) {
		errors++;
		printf(", cluster %ld ", n);
		fflush(stdout);
	}
}
total += db.addr_blocks; /* add header blocks to total count of used blocks */

if(options & VERBOSE) {
	printf("\nChecking bitmap ...");
	fflush(stdout);
}
/* check bitmap allocation - i counts clear errors, j, counts mark errors */
for(i = j = n = 0; n < db.iblocks; n++)
	if(!get_bit(&db, n) && (bitmap[n>>3]&mask[n&7])) {
/* block is not used, but marked used */
		if(!i && !j) putchar('\n'); /* first error */
		i++;
		printf("block %ld is clear but marked used in bitmap.", n);
		if(get_answer(options)=='y')
			set_bit(&db, n);
		else errors++;
	} else
	if(get_bit(&db, n) && !(bitmap[n>>3]&mask[n&7])) {
/* block is used but marked clear */
		if(!i && !j) putchar('\n');
		j++;
		printf("block %ld is used but marked clear in bitmap.", n);
		if(get_answer(options)=='y')
			clr_bit(&db, n);
		else errors++;
	}

if(options & VERBOSE)
	if(i == 0 && j == 0) printf("OK\n");
	else putchar('\n');

if(errors && (options & VERBOSE)) printf("\n%lu Error(s) found\n", errors);

if(total <= db.addr_blocks) { /* empty */
	factor = 0;
	if(options & VERBOSE) printf("\nDevice is empty\n");
} else { /* compute compression factor */
	factor = (float)db.ratio/((float)(total-db.addr_blocks)/blocks);
	printf("\nAverage compression factor (excluding headers) = %2.2f\n", factor);
	printf("Approximately %.0fK still available\n", factor*db.isize*(db.iblocks-total)/1024);
}

/* report usage */
printf("%ld/%ld (%2d%%) clusters (%d) used, %ld/%ld (%2d%%) blocks (%d) used.\n",
	blocks, db.oblocks, (int)(100*blocks/db.oblocks), db.osize, total, db.iblocks,
	(int)(100*total/db.iblocks), db.isize);

/* report advice on compression */
if((options & VERBOSE) && factor > 0) {
	printf("\nConclusion: ");
	if(db.iblocks > total) {
		factor = empty*db.ratio/factor/(db.iblocks-total);
		if(factor < 0.75)
			printf("Compression ratio better than expected !\n\n");
		else if(factor > 1.3)
			printf("Poor compression ratio; could run out of space (beware) !\n\n");
		else printf("So far, the compression ratio looks good enough.\n\n");
	}
	else printf("!! compressed device is FULL !! Action required immediately!\n\n");
}

/* adjust clean bit */
if(errors) dbh.clean = 0;
else dbh.clean = 1;
db_close(&db, 1);

if(errors) exit(4);
exit(0);
}


usage(char *s)
{
if(errno) perror(s);
printf("usage: %s [options] device_file\n", s);
printf("\tcheck consistency of \"DouBle\" device_file\n");
printf("\toptions:\n\t\t-v:\tverbose.\n\t\t-t:\tcheck clusters (slooow).\n");
printf("\t\t-f:\tforce checking even if clean.\n");
printf("\t\t-a:\tautomatic repair.\n\t\t-r:\tinteractive repair (not tested).\n");
if(errno) exit(8);
exit(16);
}
