/*  double/dbfrag.c	Check fragmentation of a DouBle device 19 Nov 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 <stdlib.h>
#include <linux/fs.h>
#include <linux/double.h>

struct dble_device db;
extern struct db_header dbh;

int verbose=0;
int block_frag = 0;
int cluster_frag = 0;
int block_gap = 0;
int cluster_gap = 0;
int block_count = 0;
int cluster_count = 0;
long last_block=0;
long cluster_deviation = 0;

void read_opt(char *name, char *s)
{
	while(*s) {
		switch(*s) {
			case 'v': verbose++; /* Verbose */
				  break;
			case 0:	  return;
			default:  usage(name);
		}
		s++;
	}
}

int do_one_cluster(struct dble_device *db, u_long n)
{
u_long *p;
int i;
int blocks=0;
long pos=0;
int status=0;
struct BAT *b;

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

p=&b->iblock;
cluster_count++;
if(*p) {
	if(*p != last_block -1 && *p != last_block +1 && last_block) {
		cluster_frag++;
		cluster_gap += abs(*p - last_block) -1;
	}
	blocks++;
	pos += *p;
	last_block = *p;
	block_count++;
	for(i=1; *(++p) && i<db->ratio; i++) {
		if(*p != last_block -1 && *p != last_block +1) {
			block_frag++;
			status++;
			block_gap += abs(*p - last_block) -1;
		}
		blocks++;
		pos += *p;
		last_block = *p;
		block_count++;
	}
	pos /= blocks;
	pos -= (0.5+n) * ((float)(db->iblocks-db->addr_blocks))/db->oblocks
		+db->addr_blocks;
	if(verbose > 2) printf("Cluster %d: %d blocks, deviation: %ld\n", n, blocks, pos);
	cluster_deviation += abs(pos);
}
return status;
}

main(argc, argv)
int argc;
char **argv;
{
u_long	i;
char *file = NULL;

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];
	}
if(file == NULL || !db_open(&db, file)) usage(argv[0]);

if(!dbh.clean) {
	printf("%s is not clean - use dbck first !\n", file);
	db_close(&db, 0);
	exit(0);
}

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

if(verbose) {
	printf("Default compression algorithm: %d\n", db.code);
	printf("Checking %ld %d-bytes clusters of %ld %d-bytes blocks...\n",
		db.oblocks, db.osize, db.iblocks, db.isize);
}

for(i = 0; i<db.oblocks; i++) {
	if(do_one_cluster(&db, i) && verbose > 1) 
		printf("Cluster %ld is fragmented\n", i);
}
db_close(&db, 0);
if(block_count) {
	printf("%d%% Cluster fragmentation. Average gap: %d block(s)\n",
		cluster_frag*100/cluster_count,
		cluster_gap/cluster_count*db.isize/1024);
	printf("%d%% Block fragmentation. Average gap: %d block(s)\n",
		block_frag*100/block_count,
		block_gap/block_count*db.isize/1024);
	printf("%d%% Average cluster deviation: %ld block(s)\n",
		cluster_deviation/cluster_count*100/db.iblocks,
		cluster_deviation/cluster_count*db.isize/1024);
} else printf("Device is empty !\n");
exit(0);
}

usage(char *s)
{
if(errno) perror(s);
printf("usage: %s [options] device_file\n", s);
printf("\tcheck fragmentation of \"DouBle\" device_file\n");
printf("\toptions:\n\t\t-v:\tverbose.\n");
exit(1);
}
