[PATCH v3] fs: FAT: Add support for DOS 1.x formatted volumes

From: Conrad Meyer
Date: Mon Mar 31 2014 - 11:23:07 EST


Add dos1xfloppy mount option to infer DOS 2.x BIOS Parameter Block
defaults from block device geometry for ancient floppies and floppy
images.

Validate that the entire BPB is zero like we expect, and that the floppy
has a DOS-style 8086 code bootstrapping header.

Fixes kernel.org bug #42617.

Values are inferred from media size and a table.[0] Media size is
assumed to be static for archaic FAT volumes. See also [1].

[0]: https://en.wikipedia.org/wiki/File_Allocation_Table#Exceptions
[1]: http://www.win.tue.nl/~aeb/linux/fs/fat/fat-1.html

Signed-off-by: Conrad Meyer <cse.cem@xxxxxxxxx>
---
Based on next-20140328.

Changes since v2:
* Only attempt to infer BPB values if mounted with -o dos1xfloppy
* Set sbi->* values directly instead of writing out BPB

Works fine in local tests. Can mount 1985 floppy image and explore files (with
dos1xfloppy option). Without option, mount fails (expected).

Thanks.
---
fs/fat/fat.h | 3 +-
fs/fat/inode.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 137 insertions(+), 20 deletions(-)

diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index 7270bdb..13b7202 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -52,7 +52,8 @@ struct fat_mount_options {
usefree:1, /* Use free_clusters for FAT32 */
tz_set:1, /* Filesystem timestamps' offset set */
rodir:1, /* allow ATTR_RO for directory */
- discard:1; /* Issue discard requests on deletions */
+ discard:1, /* Issue discard requests on deletions */
+ dos1xfloppy:1; /* Assume default BPB for DOS 1.x floppies */
};

#define FAT_HASH_BITS 8
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 992e8cb..d032e3c 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -35,9 +35,47 @@
#define CONFIG_FAT_DEFAULT_IOCHARSET ""
#endif

+#define KB_IN_SECTORS 2
+
static int fat_default_codepage = CONFIG_FAT_DEFAULT_CODEPAGE;
static char fat_default_iocharset[] = CONFIG_FAT_DEFAULT_IOCHARSET;

+static struct fat_floppy_defaults {
+ unsigned nr_sectors;
+ unsigned sec_per_clus;
+ unsigned dir_entries;
+ unsigned media;
+ unsigned fat_length;
+} floppy_defaults[] = {
+{
+ .nr_sectors = 160 * KB_IN_SECTORS,
+ .sec_per_clus = 1,
+ .dir_entries = 64,
+ .media = 0xFE,
+ .fat_length = 1,
+},
+{
+ .nr_sectors = 180 * KB_IN_SECTORS,
+ .sec_per_clus = 1,
+ .dir_entries = 64,
+ .media = 0xFC,
+ .fat_length = 2,
+},
+{
+ .nr_sectors = 320 * KB_IN_SECTORS,
+ .sec_per_clus = 2,
+ .dir_entries = 112,
+ .media = 0xFF,
+ .fat_length = 1,
+},
+{
+ .nr_sectors = 360 * KB_IN_SECTORS,
+ .sec_per_clus = 2,
+ .dir_entries = 112,
+ .media = 0xFD,
+ .fat_length = 2,
+},
+{ 0 } };

static int fat_add_cluster(struct inode *inode)
{
@@ -945,7 +983,7 @@ enum {
Opt_uni_xl_no, Opt_uni_xl_yes, Opt_nonumtail_no, Opt_nonumtail_yes,
Opt_obsolete, Opt_flush, Opt_tz_utc, Opt_rodir, Opt_err_cont,
Opt_err_panic, Opt_err_ro, Opt_discard, Opt_nfs, Opt_time_offset,
- Opt_nfs_stale_rw, Opt_nfs_nostale_ro, Opt_err,
+ Opt_nfs_stale_rw, Opt_nfs_nostale_ro, Opt_err, Opt_dos1xfloppy,
};

static const match_table_t fat_tokens = {
@@ -978,6 +1016,7 @@ static const match_table_t fat_tokens = {
{Opt_nfs_stale_rw, "nfs"},
{Opt_nfs_stale_rw, "nfs=stale_rw"},
{Opt_nfs_nostale_ro, "nfs=nostale_ro"},
+ {Opt_dos1xfloppy, "dos1xfloppy"},
{Opt_obsolete, "conv=binary"},
{Opt_obsolete, "conv=text"},
{Opt_obsolete, "conv=auto"},
@@ -1180,6 +1219,9 @@ static int parse_options(struct super_block *sb, char *options, int is_vfat,
case Opt_nfs_nostale_ro:
opts->nfs = FAT_NFS_NOSTALE_RO;
break;
+ case Opt_dos1xfloppy:
+ opts->dos1xfloppy = 1;
+ break;

/* msdos specific */
case Opt_dots:
@@ -1332,13 +1374,16 @@ static unsigned long calc_fat_clusters(struct super_block *sb)
int fat_fill_super(struct super_block *sb, void *data, int silent, int isvfat,
void (*setup)(struct super_block *))
{
+ static const char *notdos1x = "This doesn't look like a DOS 1.x volume";
struct inode *root_inode = NULL, *fat_inode = NULL;
+ struct fat_floppy_defaults *fdefaults = NULL;
struct inode *fsinfo_inode = NULL;
struct buffer_head *bh;
struct fat_boot_sector *b;
struct msdos_sb_info *sbi;
u16 logical_sector_size;
u32 total_sectors, total_clusters, fat_clusters, rootdir_sectors;
+ sector_t bd_sects;
int debug;
unsigned int media;
long error;
@@ -1378,17 +1423,66 @@ int fat_fill_super(struct super_block *sb, void *data, int silent, int isvfat,
}

b = (struct fat_boot_sector *) bh->b_data;
- if (!b->reserved) {
- if (!silent)
- fat_msg(sb, KERN_ERR, "bogus number of reserved sectors");
- brelse(bh);
- goto out_invalid;
- }
- if (!b->fats) {
- if (!silent)
- fat_msg(sb, KERN_ERR, "bogus number of FAT structure");
- brelse(bh);
- goto out_invalid;
+
+ bd_sects = part_nr_sects_read(sb->s_bdev->bd_part);
+ if (sbi->options.dos1xfloppy) {
+ /* 16-bit DOS 1.x reliably wrote bootstrap short-jmp code */
+ if (b->ignored[0] != 0xeb || b->ignored[2] != 0x90) {
+ fat_msg(sb, KERN_ERR, "%s; no bootstrapping code",
+ notdos1x);
+ brelse(bh);
+ goto out_invalid;
+ }
+
+ /*
+ * If any value in this region is non-zero, it isn't archaic
+ * DOS.
+ */
+ if (get_unaligned_le16(&b->sector_size) != 0 ||
+ b->sec_per_clus != 0 || b->reserved != 0 ||
+ b->fats != 0 ||
+ get_unaligned_le16(&b->dir_entries) != 0 ||
+ get_unaligned_le16(&b->sectors) != 0 || b->media != 0 ||
+ b->fat_length != 0 || b->secs_track != 0 ||
+ b->heads != 0 || b->secs_track != 0 || b->heads != 0) {
+
+ fat_msg(sb, KERN_ERR, "%s; DOS 2.x BPB is non-zero",
+ notdos1x);
+ brelse(bh);
+ goto out_invalid;
+ }
+
+ for (fdefaults = floppy_defaults; fdefaults->nr_sectors;
+ fdefaults++) {
+ if (fdefaults->nr_sectors == bd_sects)
+ break;
+ }
+
+ if (fdefaults->nr_sectors == 0) {
+ fat_msg(sb, KERN_WARNING,
+ "This looks like a DOS 1.x volume, but isn't a recognized floppy size (%ld sectors)",
+ (long)bd_sects);
+ brelse(bh);
+ goto out_invalid;
+ }
+
+ fat_msg(sb, KERN_INFO,
+ "This looks like a DOS 1.x volume; assuming default BPB values");
+ } else {
+ if (!b->reserved) {
+ if (!silent)
+ fat_msg(sb, KERN_ERR,
+ "bogus number of reserved sectors");
+ brelse(bh);
+ goto out_invalid;
+ }
+ if (!b->fats) {
+ if (!silent)
+ fat_msg(sb, KERN_ERR,
+ "bogus number of FAT structure");
+ brelse(bh);
+ goto out_invalid;
+ }
}

/*
@@ -1397,6 +1491,8 @@ int fat_fill_super(struct super_block *sb, void *data, int silent, int isvfat,
*/

media = b->media;
+ if (sbi->options.dos1xfloppy)
+ media = fdefaults->media;
if (!fat_valid_media(media)) {
if (!silent)
fat_msg(sb, KERN_ERR, "invalid media value (0x%02x)",
@@ -1404,7 +1500,10 @@ int fat_fill_super(struct super_block *sb, void *data, int silent, int isvfat,
brelse(bh);
goto out_invalid;
}
- logical_sector_size = get_unaligned_le16(&b->sector_size);
+ if (sbi->options.dos1xfloppy)
+ logical_sector_size = SECTOR_SIZE;
+ else
+ logical_sector_size = get_unaligned_le16(&b->sector_size);
if (!is_power_of_2(logical_sector_size)
|| (logical_sector_size < 512)
|| (logical_sector_size > 4096)) {
@@ -1414,7 +1513,10 @@ int fat_fill_super(struct super_block *sb, void *data, int silent, int isvfat,
brelse(bh);
goto out_invalid;
}
- sbi->sec_per_clus = b->sec_per_clus;
+ if (sbi->options.dos1xfloppy)
+ sbi->sec_per_clus = fdefaults->sec_per_clus;
+ else
+ sbi->sec_per_clus = b->sec_per_clus;
if (!is_power_of_2(sbi->sec_per_clus)) {
if (!silent)
fat_msg(sb, KERN_ERR, "bogus sectors per cluster %u",
@@ -1450,10 +1552,18 @@ int fat_fill_super(struct super_block *sb, void *data, int silent, int isvfat,
mutex_init(&sbi->s_lock);
sbi->cluster_size = sb->s_blocksize * sbi->sec_per_clus;
sbi->cluster_bits = ffs(sbi->cluster_size) - 1;
- sbi->fats = b->fats;
+ if (sbi->options.dos1xfloppy)
+ sbi->fats = 2;
+ else
+ sbi->fats = b->fats;
sbi->fat_bits = 0; /* Don't know yet */
- sbi->fat_start = le16_to_cpu(b->reserved);
- sbi->fat_length = le16_to_cpu(b->fat_length);
+ if (sbi->options.dos1xfloppy) {
+ sbi->fat_start = 1;
+ sbi->fat_length = fdefaults->fat_length;
+ } else {
+ sbi->fat_start = le16_to_cpu(b->reserved);
+ sbi->fat_length = le16_to_cpu(b->fat_length);
+ }
sbi->root_cluster = 0;
sbi->free_clusters = -1; /* Don't know yet */
sbi->free_clus_valid = 0;
@@ -1515,7 +1625,10 @@ int fat_fill_super(struct super_block *sb, void *data, int silent, int isvfat,
sbi->dir_per_block_bits = ffs(sbi->dir_per_block) - 1;

sbi->dir_start = sbi->fat_start + sbi->fats * sbi->fat_length;
- sbi->dir_entries = get_unaligned_le16(&b->dir_entries);
+ if (sbi->options.dos1xfloppy)
+ sbi->dir_entries = fdefaults->dir_entries;
+ else
+ sbi->dir_entries = get_unaligned_le16(&b->dir_entries);
if (sbi->dir_entries & (sbi->dir_per_block - 1)) {
if (!silent)
fat_msg(sb, KERN_ERR, "bogus directory-entries per block"
@@ -1527,7 +1640,10 @@ int fat_fill_super(struct super_block *sb, void *data, int silent, int isvfat,
rootdir_sectors = sbi->dir_entries
* sizeof(struct msdos_dir_entry) / sb->s_blocksize;
sbi->data_start = sbi->dir_start + rootdir_sectors;
- total_sectors = get_unaligned_le16(&b->sectors);
+ if (sbi->options.dos1xfloppy)
+ total_sectors = fdefaults->nr_sectors;
+ else
+ total_sectors = get_unaligned_le16(&b->sectors);
if (total_sectors == 0)
total_sectors = le32_to_cpu(b->total_sect);

--
1.9.0

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/