Path: tut!enea!mcvax!botter!tjalk!bunscho
From: bunscho@cs.vu.nl (Bunschoten Jacob Pieter)
Newsgroups: comp.os.minix
Subject: Badblock handler
Message-ID: <892@tjalk.cs.vu.nl>
Date: 5 Nov 87 18:57:24 GMT
Reply-To: bunscho@cs.vu.nl (Bunschoten Jacob Pieter)
Organization: VU Informatica, Amsterdam
Lines: 541

/* Badblocks:				Written by Jacob P. Bunschoten */

/* This program is written to handle BADBLOCKS on a device (harddisk 
 * or floppydisk).
 * The program ask several things (badblock-filename, device, block-
 * numbers) and you are supposed to answer with (y/n/q) <return>.
 *
 * BUG:
 *	This program assumes that the zone_size == block_size.

 *	The calculation is done without conversion to zone_size.

 *	When the zone_size > block_size it can happen that 
 *	the zone is already allocated. This means some other
 *	file is using this zone and may use all the blocks including
 *	the bad one. This can be cured by inspecting the zone_bitmap 
 *	(is already done) and change a file if this zone is used.
 *	This means that another zone must be allocated and
 *	the inode who claims this zone must be found and changed.
 *
 */

#include <stat.h>
#include <stdio.h>
NOTE NOTE NOTE include your local h/type.h
#include "/usr/src/h/type.h"


/* 		Super block table.  
 *
 * 	The disk layout is:
 *
 *      Item        # blocks
 *    boot block      1
 *    super block     1
 *    inode map     s_imap_blocks
 *    zone map      s_zmap_blocks
 *    inodes        (s_ninodes + 1 + INODES_PER_BLOCK - 1)/INODES_PER_BLOCK
 *    unused        
 *    data zones    (s_nzones - s_firstdatazone) << s_log_zone_size
 *
 */


struct super_block {
  inode_nr s_ninodes;	/* # usable inodes on the minor device */
  zone_nr s_nzones;	/* total device size, including bit maps etc */
  unshort s_imap_blocks;	/* # of blocks used by inode bit map */
  unshort s_zmap_blocks;	/* # of blocks used by zone bit map */
  zone_nr s_firstdatazone;	/* number of first data zone */
  short int s_log_zone_size;	/* log2 of blocks/zone */
  file_pos s_max_size;		/* maximum file size on this device */
  int s_magic;		/* magic number to recognize super-blocks */
} super_block; 

#define SUPER_MAGIC	0x137F


#define NR_ZONE_NUMS	9
#define NR_DZONE_NUMS	(NR_ZONE_NUMS -2 )

struct d_inode {		/* disk inode. */
  mask_bits i_mode;		/* file type, protection, etc. */
  uid i_uid;			/* user id of the file's owner */
  file_pos i_size;		/* current file size in bytes */
  real_time i_modtime;		/* when was file data last changed */
  gid i_gid;			/* group number */
  links i_nlinks;		/* how many links to this file */
  zone_nr i_zone[NR_ZONE_NUMS];	/* block nums for direct, ind, and dbl ind */
} d_inode;



#define OK	0
#define NOT_OK	1
#define QUIT	2

#define READ 	0
#define WRITE	1

#define BLOCK_SIZE	1024
#define INODES_PER_BLOCK	(BLOCK_SIZE/sizeof(struct d_inode))

#define INODE_SIZE (sizeof (struct d_inode) )
#define SUPER_SIZE (sizeof (struct super_block) )
#define INT_SIZE   (sizeof (int) )

char dev_name[20];	/* space for names	*/
char f_name[20];
char file_name[50];
char dir_name[30];

FILE *fp, *fopen();
int fd;


struct stat stat_buf;
struct d_inode *ip;
struct super_block *sp;

extern char *strcat();
extern char *itoa();
char *utobin();

rw_super(flag)	/* read or write a super_block */
{
	int rwd;

	lseek(fd, 0L, 0);
	lseek(fd, (long) BLOCK_SIZE, 0);

	if (flag == READ ) 
		rwd = read( fd, sp, SUPER_SIZE );
	else
		rwd = write (fd, sp, SUPER_SIZE );
	if (rwd != SUPER_SIZE ) {
		printf("Bad %s in rw_super() (should be %d was %d)\n",
			flag == READ ? "read" : "write",
			SUPER_SIZE, rwd );
		done(1);
	}
}

get_super()
/* get super_block. global pointer sp is used */
{
	rw_super(READ);	/* fetch the super_block	*/

	if (sp->s_magic != SUPER_MAGIC) {
		printf("Bad magic word in super_block?!\n");
		done(1);
	}
#ifdef DEBUG
/* give some information of the super_block */

	printf("\n");
	printf("# Inodes %d\n",sp->s_ninodes);
	printf("# Zones %d\n",sp->s_nzones);
	printf("# inode map blocks %d\n",sp->s_imap_blocks);
	printf("# zone map blocks %d\n",sp->s_zmap_blocks);
	printf("  Firstdatazone %d\n\n",sp->s_firstdatazone);
#endif
}


wr_super()
{
	rw_super(WRITE);
}

get_inode( stat_ptr)
struct stat *stat_ptr;
{
#ifdef DEBUG
	int tst;
	int cnt;
#endif

	rw_inode(stat_ptr, READ);

#ifdef DEBUG
	tst = 0;
	printf("\ni_zone[0]=%d, st_rdev=%d\n",	(int) ip->i_zone[0],
						(int) stat_ptr->st_rdev);
	printf("File size %D\n",(long) ip->i_size);
	cnt = 0;
	while (cnt < NR_ZONE_NUMS ) {
		if (ip->i_zone[cnt++] != 0 )
			tst++;
		printf("zone[%d] contains %d\n",cnt-1,
					(int) ip->i_zone[cnt-1]);
	}
	if ( tst )
		printf("Possible wrong inode. Inode must be clean");
	printf("\n");
#endif

}

wr_inode(stat_ptr)
struct stat *stat_ptr;
{
	rw_inode(stat_ptr, WRITE);
}

rw_inode(stat_ptr, rw_mode)
struct stat *stat_ptr;
{
	int blk, offset, rwd, i_num;

	i_num = stat_ptr->st_ino;
	
	blk = 2 + sp->s_imap_blocks + sp->s_zmap_blocks;
	blk += (i_num -1 ) / INODES_PER_BLOCK;
	blk *= BLOCK_SIZE;

	offset = (i_num -1 ) % INODES_PER_BLOCK;
	offset *= INODE_SIZE; 

	lseek(fd, 0L, 0);	/* rewind */
	lseek(fd, (long) (blk + offset), 0);

		/* pointer is at the inode, read it */
	if (rw_mode == READ) {
		rwd = read(fd, ip, INODE_SIZE );
	} else {
		rwd = write (fd, ip, INODE_SIZE );
	}
	if (rwd != INODE_SIZE ) {
		printf("Bad %s in get_inode()\n",(rw_mode == READ)? "read":
								    "write");
		done(1);
	}

}

main()
{
	int cnt, blk_nr;

	sp = &super_block;
	ip = &d_inode;

	/* Start asking several items from the user */

		/* First device name */
	rd_name(dev_name,"Give device path_name (e.g. /dev/at0 ) ");
	if (umount(dev_name) == -1 ) {
		printf("Could not umount device %s\n",dev_name);
		done(1);
	}
		/* Second directory name where device is 
		   to be mounted on 
		 */
	rd_name(dir_name,
		"Give directory name\nwhere device is to be mounted on (e.g. /user) ");
	if (mount(dev_name, dir_name, 0) == -1 ) {
		printf("Could not mount device \n");
		done(1);
	}
		/* Third the name of the badblock file */
	rd_name(f_name,
		"Give badblock file name (path relative from device (e.g. .BAD_1) ");
	strcat(file_name,dir_name);
	strcat(file_name,"/");
	strcat(file_name,f_name);
		/* another confirmation is needed */
	ask_ok(file_name);

	if (stat( file_name, &stat_buf) != -1 ) {
		printf("File %s does allready exists\n",file_name);
		done(1);
	}
	if ( (fp = fopen( file_name, "w" )) == NULL) {
		printf("Cant create file %s\n", file_name);
		done(1);
	}
	chmod(file_name, 0);	/* "useless" file */
	if (stat( file_name, &stat_buf) == -1 ) {
		printf("What? second call from stat failed\n");
		done(1);
	}
	/* stat buf must be safed. We can now calculate the inode on disk */
	fclose(fp);
	/* the badblock file is created */

	if (umount(dev_name) == -1 ) {
		printf("Can not umount device anymore??? \n");
		done(1);
	}
	if ( (fd = open(dev_name, 2)) == -1 ) {
		printf("cant open device %s\n",dev_name);
		done(1);
	}

	get_super();
	get_inode(&stat_buf);

	/* just did the preliminairy things, must we continue */
	ask_ok("The file system will change, continue? ");

	/* all systems go */
	cnt = 0;	/* cnt keep track of the zone's */
	while (cnt < NR_DZONE_NUMS ) {
#ifdef DEBUG
		printf("Zone[%d]: ",cnt);
#endif
		blk_nr = rd_num("Give block_number (negative to stop) ");
		if (blk_ok(blk_nr) == OK ) {
			set_bit(blk_nr);
			ip->i_zone[cnt++] = blk_nr;
		}
		else if (blk_nr == -1 )
			break;
		else {
			printf("Bad number %d\n",blk_nr);
			printf("This may be a typo or zone_size != block_size\n");
		}
	}

	ip->i_size = (long) (BLOCK_SIZE * cnt) ;
	wr_inode(&stat_buf);
	wr_super();
	close(fd);
	done(0);

}


		/*	===== bitmap handling ======	*/

#define BIT_MAP_SHIFT	13
#define INT_BITS	(INT_SIZE << 3)

blk_ok(num)
	/* is this zone free (y/n) */
int num;
{
	long blk;
	int rd;
	int block, offset, words, bit, tst_word;

	if (num < 0 ) 
		return NOT_OK;	/* negative number is not allowed */

	num += 1 - sp->s_firstdatazone;	/* account offset */

		/* calculate the word in the bitmap */
	block = num >> BIT_MAP_SHIFT;			/* which block */
	offset = num - (block << BIT_MAP_SHIFT);	/* offset in block */
	words = offset/INT_BITS;			/* which word */

	blk = (long) (2 + sp->s_imap_blocks) ;
	blk *= (long) BLOCK_SIZE;
	blk += (long) (words * INT_SIZE) ;


	lseek(fd, 0L, 0);	/* rewind */
	lseek(fd, blk, 0);	/* set pointer at word */

	rd = read(fd, &tst_word, INT_SIZE );
	if (rd != INT_SIZE ) {
		printf("Read error in bitmap\n");
		done(1);
	}

		/* we have the tst_word, check if bit was off */
	bit = offset % INT_BITS;

	if ( (( tst_word >> bit ) & 01 ) == 0)	/* free */
		return OK;
	else 
		return NOT_OK;
}

set_bit(num)
	/* write in the bitmap */
int num;
{
	int rwd;
	long blk;
	int block, offset, words, tst_word, bit;
	char wrd_str[17], bit_str[17];

	num += 1 - sp->s_firstdatazone;

	block = num >> BIT_MAP_SHIFT;			/* which block */
	offset = num - (block << BIT_MAP_SHIFT);	/* offset in block */
	words = offset/INT_BITS;			/* which word */

	blk = (long) (2 + sp->s_imap_blocks) ;
	blk *= (long) BLOCK_SIZE ;
	blk += (long) (words * INT_SIZE);


	lseek(fd, 0L, 0);	/* rewind */
	lseek(fd, blk, 0);

	rwd = read(fd, &tst_word, INT_SIZE );
	if (rwd != INT_SIZE ) {
		printf("Read error in bitmap\n");
		done(1);
	}

	bit = offset % INT_BITS;
	if ( ((tst_word >> bit) & 01 ) == 0) {	/* free */
		lseek(fd, 0L, 0);	/* rewind */
		lseek(fd, blk, 0);

printf("blk= %D, block= %D, offset= %d, word= %s, bit_nr= %d.\n", 
	blk, (long) (blk/BLOCK_SIZE), offset, 
					utobin(tst_word, wrd_str), bit);

		tst_word |= (1 << bit);

printf("Word is now %s\n", utobin(tst_word, wrd_str) );

		rwd = write (fd, &tst_word, INT_SIZE );
		if (rwd != INT_SIZE ) {
			printf("Bad write in zone map\n");
			printf("Check file system \n");
			done(1);
		}
		return;
	}
	printf("Bit was on==> error\n");
	done(1);
}


	/* =======    interactive part ====== */

rd_num(str)
	/* read a number from stdin (+ confirmation) */
char *str;
{
	int num;

	for(;;) {
		printf("%s",str);
		scanf("%d\n",&num);
		switch (is_ok( itoa(num)) ) {
		case OK:	return(num);
		case QUIT:	return -1;
		}
	}
}


rd_name(name, str)
	/* raed a string from stdin (+ confirmation) */
char *name, *str;
{
	for(;;) {
		printf("%s",str);
		scanf("%s\n",name);
		switch (is_ok(name) ) {
		case OK:	return OK;
		case QUIT:	done(1);
		}
	}
}

is_ok(str)
char *str;
{
	char buf[80];
	
	buf[0] = '\0';
	strcat(buf,"Is \"");
	strcat(buf,str);
	strcat(buf,"\" ok (y/n/q) ?");
	return( ok(buf) );
}

ok(str)
char *str;
{
	char c[2];	/* need more than 1 char '%1s' instead of '%c' */
	
	for (;;) {
		printf("%s",str);
		scanf("%1s\n",c);	/* NOTE */
		switch ( *c ) {
		case 'y':
		case 'Y':
			return OK;
		case 'n':
		case 'N':
			return NOT_OK;
		case 'q':
		case 'Q':
			return QUIT;
		default:
			printf("Unknown option %c\n\t",*c);
		}
	}
}

ask_ok(name)
char *name;
{
	char buf[80];

	buf[0] = '\0';
	strcat(buf,name);
	strcat(buf," (y/n/q) ?");

	for(;;) {
		switch( ok(buf)) {
		case OK:
			return;
		case NOT_OK:
		case QUIT:
			done(1);		
		}
	}
}

done(nr)
int nr;
{
	_cleanup();
	sync();
	exit(nr);
}

	/* ====== routine for binairy printing of words == */

char *chtobin(num, str)
	/* char to binair. convert the numerical value num 
	   to a binair string (0|1) 
	 */
unsigned char num;
char *str;
{
	int i, tst;
	
	str[8] = '\0';	/* terminat the string	*/

	for (i=7; i>=0; i-- ) {	/* NOTE char is 8 bits !! */
		if ((tst = (num & 01)))
			str[i] = '1';
		else
			str[i] = '0';
		num >>= 1;
		num &= 0xFF;
	}
	return str;
}

	
char *utobin(num, str)
	/* unsigned to binair. Used to print a binairy word */
unsigned num;
char *str;
{		/* NOTE int is 2 char == 16 bits */
	chtobin( (num >> 8 ) & 0xFF, str);
	chtobin( num & 0xFF , &str[8]);
	return str;
}
