ast@cs.vu.nl (Andy Tanenbaum) (12/02/87)
Here is a program to handle bad sectors. It was written by Jacob P. Bunschoten. What it does is ask you for a list of bad blocks. It then creates a file by hand crafting an i-node containing these blocks. Once they are out of the way, they won't be included in any data file. Andy Tanenbaum (ast@cs.vu.nl) ---------------------------------- badblocks.c ---------------------------- /* badblocks - collect bad blocks in a file Author: Jacob Bunschoten */ /* Usage "badblocks block_special" */ /* * This program is written to handle BADBLOCKS on a hard or floppy disk. * The program asks for block_numbers. These numbers can be obtained with * the program disk_check; written by A. Tanenbaum. It then creates a * file on the disk containing up to 7 bad blocks. * * BUG: * * When the zone_size > block_size it can happen that * the zone is already allocated. This means some * 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 the file if this zone is used. * This means that another zone must be allocated and * the inode wich claims this zone must be found and changed. * */ #include <stat.h> #include <stdio.h> #include "../h/type.h" /* Super block table. * * The disk layout is: * * Item # block * 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_block; /* # of block used by inode bit map */ unshort s_zmap_block; /* # of block used by zone bit map */ zone_nr s_firstdatazone; /* number of first data zone */ short int s_log_zone_size; /* log2 of block/zone */ file_pos s_max_size; /* maximum file size on this device */ int s_magic; /* magic number to recognize super-block */ } 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]; /* blocks 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 HARMLESS 0 #define DIR_CREATED 1 #define DEV_MOUNTED 2 #define FILE_EXISTS 3 #define SUCCESS 4 #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) ) /* ====== globals ======= */ char *dev_name; char f_name[] = ".Bad_XXXXXX"; char file_name[50]; char dir_name[] = "/tmpXXXXXX"; int block[NR_DZONE_NUMS+1]; /* last block contains zero */ FILE *fp, *fopen(); int fd; int eofseen; /* set if '\n' seen */ struct stat stat_buf; struct d_inode *ip; struct super_block *sp; extern char *strcat(); #ifdef DEBUG extern char *itoa(); char *utobin(); #endif /* ====== super block routines ======= */ rw_super(flag) /* read or write a superblock */ { int rwd; lseek(fd, 0L, 0); /* rewind */ lseek(fd, (long) BLOCK_SIZE, 0); /* seek */ if (flag == READ ) rwd = read( fd, sp, SUPER_SIZE ); else rwd = write (fd, sp, SUPER_SIZE ); if (rwd != SUPER_SIZE ) { /* ok ? */ printf("Bad %s in get_super() (should be %d is %d)\n", flag == READ ? "read" : "write", SUPER_SIZE, rwd ); done(DIR_CREATED); } } get_super() /* get super_block. global pointer sp is used */ { rw_super(READ); if (sp->s_magic != SUPER_MAGIC) { /* check */ printf("Bad magic number in super_block?!\n"); done(DIR_CREATED); } #ifdef DEBUG /* give some information of the super_block */ printf("# Inodes %d\n",sp->s_ninodes); printf("# Zones %d\n",sp->s_nzones); printf("# inode map block %d\n",sp->s_imap_block); printf("# zone map block %d\n",sp->s_zmap_block); printf(" Firstdatazone %d\n\n",sp->s_firstdatazone); #endif } put_super() { rw_super(WRITE); } /* ========== inode routines =========== */ rw_inode(stat_ptr, rw_mode) struct stat *stat_ptr; { int rwd, i_num; long blk, offset; i_num = stat_ptr->st_ino; blk = (long ) (2 + sp->s_imap_block + sp->s_zmap_block); blk += (long) ((i_num -1 ) / INODES_PER_BLOCK ); blk *= (long) (BLOCK_SIZE); /* this block */ offset = (long) ((i_num -1 ) % INODES_PER_BLOCK); offset *= (long) (INODE_SIZE); /* and this offset */ lseek(fd, 0L, 0); /* rewind */ lseek(fd, (long) (blk + offset), 0); /* seek */ /* pointer is at the inode */ if (rw_mode == READ) { /* read it */ rwd = read(fd, ip, INODE_SIZE ); } else { /* write it */ rwd = write (fd, ip, INODE_SIZE ); } if (rwd != INODE_SIZE ) { /* ok ? */ printf("Bad %s in get_inode()\n",(rw_mode == READ)? "read": "write"); done(DIR_CREATED); } } get_inode( stat_ptr) struct stat *stat_ptr; { #ifdef DEBUG int tst; #endif int cnt; 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); for (cnt =0 ; cnt < NR_ZONE_NUMS; cnt++ ) if (ip->i_zone[cnt] != 0 ) { tst = 1; printf("zone[%d] contains %d\n",cnt, (int) ip->i_zone[cnt]); } if ( tst ) { printf("Possible wrong inode. Inode must be clean"); done(DIR_CREATED); } printf("\n"); #endif for (cnt=0; cnt < NR_ZONE_NUMS; cnt++) ip->i_zone[cnt] = 0; /* Just to be safe */ } put_inode(stat_ptr) struct stat *stat_ptr; { rw_inode(stat_ptr, WRITE); } /* ============== main program ================= */ main(argc, argv) char *argv[]; { int cnt, blk_nr, finished; struct stat dev_stat; sp = &super_block; ip = &d_inode; if (argc != 2) { fprintf(stderr,"Usage: %s block_special\n",argv[0]); done(HARMLESS); } /* Do some test. */ if (geteuid()) { printf("Sorry, not in superuser mode \n"); printf("Set_uid bit must be on or you must become super_user\n"); done(HARMLESS); } dev_name = argv[1]; mktemp(dir_name); #ifdef DEBUG printf("Dir_name is %s\n",dir_name); #endif if (mknod(dir_name,040777,0) == -1) { fprintf(stderr,"%s is already used in system\n",dir_name); done(HARMLESS); } /* Mount device. This call may fail. */ mount(dev_name, dir_name, 0); /* succes. dev was mounted, try to umount */ /* Umount device. Playing with the file system while * other processes have access to this device * is asking for trouble */ if (umount(dev_name) == -1 ) { printf("Could not umount device %s.\n",dev_name); done(HARMLESS); } mktemp(f_name); /* create "/tmpXXXXpid/.BadXXpid" */ strcat(file_name, dir_name); strcat(file_name, "/"); strcat(file_name, f_name); if (mount(dev_name, dir_name, 0) == -1) { /* this call should work */ fprintf(stderr,"Could not mount device anymore\n"); done(HARMLESS); } if (stat( file_name, &stat_buf) != -1 ) { printf("File %s does already exists\n",file_name); done(DEV_MOUNTED); } if ( (fp = fopen( file_name, "w" )) == NULL) { printf("Can not create file %s\n", file_name); done(DEV_MOUNTED); } chmod(file_name, 0); /* "useless" file */ if (stat( file_name, &stat_buf) == -1 ) { printf("What? Second call from stat failed\n"); done(FILE_EXISTS); } /* 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(DIR_CREATED); } if ( (fd = open(dev_name, 2)) == -1 ) { printf("Can not open device %s\n",dev_name); done(DEV_MOUNTED); } if (fstat(fd, &dev_stat) == -1 ) { printf("fstat on device %s failed\n",dev_name); done(DEV_MOUNTED); } if ( (dev_stat.st_mode & S_IFMT ) != S_IFBLK ) { printf("Device \"%s\" is not a block_special.\n",dev_name); done(DEV_MOUNTED); } get_super(); if (sp->s_log_zone_size ) { printf("Block_size != zone_size."); printf("This program can not handle it\n"); done(DIR_CREATED); } get_inode(&stat_buf); for(finished = 0; !finished; ) { printf("Give up to %d bad block numbers separated by spaces\n", NR_DZONE_NUMS); reset_blks(); cnt = 0; /* cnt keep track of the zone's */ while (cnt < NR_DZONE_NUMS ) { int tst; blk_nr = rd_num(); tst = blk_ok(blk_nr); /* test if this block is free */ if (tst == OK ) { cnt++; save_blk( blk_nr ); } else if (tst == QUIT ) break; } show_blks(); if (!cnt) done(FILE_EXISTS); switch( ok("All these blocks ok <y/n/q> (y:Device will change) ") ) { case OK: finished = 1; break; case NOT_OK: break; case QUIT: done(FILE_EXISTS); } } modify(cnt); close(fd); /* free device */ done(FILE_EXISTS); } modify(nr_blocks) { int i; if (nr_blocks == 0) return; for (i=0; i<nr_blocks; i++) { set_bit(block[i]); ip->i_zone[i] = block[i]; } ip->i_size = (long) (BLOCK_SIZE * nr_blocks); /* give file size */ put_inode(&stat_buf); /* save the inode on disk */ put_super(); /* bit_maps too */ } static blk_cnt=0; save_blk(blk_num) int blk_num; { block[blk_cnt++] = blk_num; } reset_blks() { int i; for (i=0;i<=NR_DZONE_NUMS;i++) block[i] = 0; /* Note: Last block_number is set to zero */ blk_cnt =0; } show_blks() { int i; for (i=0; i<blk_cnt; i++) printf("Block[%d] = %d\n",i,block[i]); } blk_is_used(blk_num) int blk_num; { /* return TRUE(1) if used */ int i; for (i=0; block[i] && block[i] != blk_num; i++); return (block[i] != 0)? 1: 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_offset; int rd; int block, offset, words, bit, tst_word; int z_num; if ( num < 0 ) { return QUIT; /* negative number is not allowed */ } if (blk_is_used (num)) { printf("Duplicate block (%d) given\n",num); return NOT_OK; } /* assumption zone_size == block_size */ z_num = num + 1 - sp->s_firstdatazone; /* account offset */ /* calculate the word in the bitmap */ block = z_num >> BIT_MAP_SHIFT; /* which block */ offset = z_num - (block << BIT_MAP_SHIFT); /* offset */ words = offset/INT_BITS; /* which word */ blk_offset = (long) (2 + sp->s_imap_block); /* zone map */ blk_offset *= (long) BLOCK_SIZE; /* of course in block */ blk_offset += (long) (words * INT_SIZE) ; /* offset */ lseek(fd, 0L, 0); /* rewind */ lseek(fd, blk_offset, 0); /* set pointer at word */ rd = read(fd, &tst_word, INT_SIZE ); if (rd != INT_SIZE ) { printf("Read error in bitmap\n"); done(DIR_CREATED); } /* we have the tst_word, check if bit was off */ bit = offset % INT_BITS; if ( ((tst_word >> bit )&01 ) == 0) /* free */ return OK; else { printf("Bad number %d. ",num); printf("This zone (block) is marked in bitmap\n"); return NOT_OK; } } set_bit(num) /* write in the bitmap */ int num; { int rwd; long blk_offset; int block, offset, words, tst_word, bit; int z_num; char wrd_str[17], bit_str[17]; z_num = num + 1 - sp->s_firstdatazone; block = z_num >> BIT_MAP_SHIFT; /* which block */ offset = z_num - (block << BIT_MAP_SHIFT); /* offset in block */ words = offset/INT_BITS; /* which word */ blk_offset = (long) (2 + sp->s_imap_block) ; blk_offset *= (long) BLOCK_SIZE ; blk_offset += (long) (words * INT_SIZE); lseek(fd, 0L, 0); /* rewind */ lseek(fd, blk_offset, 0); rwd = read(fd, &tst_word, INT_SIZE ); if (rwd != INT_SIZE ) { printf("Read error in bitmap\n"); done(DEV_MOUNTED); } bit = offset % INT_BITS; if ( ((tst_word >> bit) & 01 ) == 0) { /* free */ lseek(fd, 0L, 0); /* rewind */ lseek(fd, blk_offset, 0); #ifdef DEBUG printf("blk= %D ", blk_offset); printf("block= %D ",(long) blk_offset/BLOCK_SIZE); printf("offset= %d\n",offset); printf("\t\tword= %s ", utobin(tst_word, wrd_str)); printf("bit_nr = %d\n", bit); #endif tst_word |= (1 << bit); /* not free anymore */ #ifdef DEBUG printf("The word is now %s\n", utobin(tst_word, wrd_str) ); #endif rwd = write (fd, &tst_word, INT_SIZE ); if (rwd != INT_SIZE ) { printf("Bad write in zone map\n"); printf("Check file system \n"); done(DIR_CREATED); } return; } printf("Bit was on block %d; ==> internal error\n",num); done(DIR_CREATED); } /* ======= interactive interface ======= */ rd_num() /* read a number from stdin */ { static num; int c; if (eofseen) return(-1); do { c = getchar(); if (c == EOF || c == '\n') return(-1); } while ( c != '-' && (c < '0' || c > '9') ); if (c == '-') { printf("Block numbers must be positive\n"); exit(1); } num = 0; while (c >= '0' && c <= '9' ) { num *= 10; num += c - '0'; c = getchar(); if (c == '\n') eofseen = 1; } return num; } ok(str) char *str; { int c; for (;;) { printf("%s",str); while ( ( c = getchar() ) != EOF && c != 'y' && c != 'n' && c != 'q' ) if (c != '\n' ) printf(" Bad character %c\n", (char) c); switch ( c ) { case EOF: return QUIT; case 'y': return OK; case 'n': return NOT_OK; case 'q': return QUIT; #ifdef DEBUG default: printf("Unknown option %c\n\t",*c); #endif } printf("\n"); } } done(nr) int nr; { switch(nr) { case FILE_EXISTS: unlink(file_name); case SUCCESS: case DEV_MOUNTED: umount(dev_name); case DIR_CREATED: unlink(dir_name); case HARMLESS: ; } _cleanup(); sync(); exit(nr == SUCCESS ? 0: 1); } #ifdef DEBUG /* ===== print integer in binairy format ====== */ char *chtobin(num, str) /* char to binair. Convert the numerical value num to a binairy string (0|1) str[] is at least 8 chars wide */ unsigned char num; char *str; { int i; for (i=7; i>=0; i-- ) { str[i] = (num&01)? '1':'0'; num >>= 1; num &= 0xFF; } str[8] = '\0'; return str; } char *utobin(num, str) /* unsigned to binair. Used to print a binairy word */ unsigned num; char *str; { chtobin( (num >> 8 ) & 0xFF, str); chtobin( num & 0xFF , &str[8]); return str; } #endif DEBUG