Hi, I've got an USB FAT32-formatted external drive that developed bad blocks in the area of the 0th FAT. So, I've developed a quick patch for that adds support for non-mirrored FATs in case of FAT32 (as per FS spec). If FAT mirroring is on, the behavior is unchanged: read/write the 0th FAT, and mirror changes to the remaining FATs. I've tested this patch on the said external drive as well as on some loop devices. Best regards, Roma --- cut here --- >From 869fb1b0ed81396c0a0d1a42f557ea5be2c39797 Mon Sep 17 00:00:00 2001 From: Roman Dubtsov <dubtsov@xxxxxxxxx> Date: Sat, 6 Nov 2010 12:21:49 +0100 Subject: [PATCH] fat: add support for FAT32 with non-mirrored FATs Use BPB_ExtFlags member of the FAT32 boot sector to detect volumes with only a single active FAT. Signed-off-by: Roman Dubtsov <dubtsov@xxxxxxxxx> --- fs/fat/fat.h | 6 ++++++ fs/fat/fatent.c | 6 +++++- fs/fat/inode.c | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletions(-) diff --git a/fs/fat/fat.h b/fs/fat/fat.h index d75a77f..d09191a 100644 --- a/fs/fat/fat.h +++ b/fs/fat/fat.h @@ -52,6 +52,9 @@ struct fat_mount_options { #define FAT_HASH_BITS 8 #define FAT_HASH_SIZE (1UL << FAT_HASH_BITS) +#define FAT_FLAG_DONT_MIRROR 0x0080 +#define FAT_FLAG_USE_FAT_NR 0x000F + /* * MS-DOS file system in-core superblock data */ @@ -87,6 +90,9 @@ struct msdos_sb_info { spinlock_t inode_hash_lock; struct hlist_head inode_hashtable[FAT_HASH_SIZE]; + + int active_fat; /* Active FAT number (FAT32 only) */ + int mirror_fat; /* Fat mirroring flag (FAT32 only) */ }; #define FAT_CACHE_VALID 0 /* special case for valid cache */ diff --git a/fs/fat/fatent.c b/fs/fat/fatent.c index b47d2c9..7932921 100644 --- a/fs/fat/fatent.c +++ b/fs/fat/fatent.c @@ -36,9 +36,10 @@ static void fat_ent_blocknr(struct super_block *sb, int entry, { struct msdos_sb_info *sbi = MSDOS_SB(sb); int bytes = (entry << sbi->fatent_shift); + int fat_start = sbi->fat_start + sbi->active_fat * sbi->fat_length; WARN_ON(entry < FAT_START_ENT || sbi->max_cluster <= entry); *offset = bytes & (sb->s_blocksize - 1); - *blocknr = sbi->fat_start + (bytes >> sb->s_blocksize_bits); + *blocknr = fat_start + (bytes >> sb->s_blocksize_bits); } static void fat12_ent_set_ptr(struct fat_entry *fatent, int offset) @@ -372,6 +373,9 @@ static int fat_mirror_bhs(struct super_block *sb, struct buffer_head **bhs, struct buffer_head *c_bh; int err, n, copy; + if (!sbi->mirror_fat) + return 0; + err = 0; for (copy = 1; copy < sbi->fats; copy++) { sector_t backup_fat = sbi->fat_length * copy; diff --git a/fs/fat/inode.c b/fs/fat/inode.c index ad6998a..a3401f8 100644 --- a/fs/fat/inode.c +++ b/fs/fat/inode.c @@ -1361,10 +1361,13 @@ int fat_fill_super(struct super_block *sb, void *data, int silent, sbi->free_clusters = -1; /* Don't know yet */ sbi->free_clus_valid = 0; sbi->prev_free = FAT_START_ENT; + sbi->mirror_fat = 1; /* FAT mirroring is ON by default, */ + sbi->active_fat = 0; /* and the default active FAT # is 0 */ if (!sbi->fat_length && b->fat32_length) { struct fat_boot_fsinfo *fsinfo; struct buffer_head *fsinfo_bh; + u16 flags; /* Must be FAT32 */ sbi->fat_bits = 32; @@ -1378,6 +1381,20 @@ int fat_fill_super(struct super_block *sb, void *data, int silent, if (sbi->fsinfo_sector == 0) sbi->fsinfo_sector = 1; + flags = le16_to_cpu(b->flags); + if (flags & FAT_FLAG_DONT_MIRROR) { + sbi->active_fat = flags & FAT_FLAG_USE_FAT_NR; + sbi->mirror_fat = 0; + printk(KERN_INFO "FAT: FAT mirroring is off." + " Using FAT #%d\n", + sbi->active_fat); + if (sbi->active_fat >= sbi->fats) { + printk(KERN_ERR "FAT: invalid active fat number\n"); + brelse(bh); + goto out_fail; + } + } + fsinfo_bh = sb_bread(sb, sbi->fsinfo_sector); if (fsinfo_bh == NULL) { printk(KERN_ERR "FAT: bread failed, FSINFO block" -- 1.7.3.2 -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html