Skip to contentSkip to conversation

fs: FAT: Add support for DOS 1.x formatted volumes

PATCH Superseded Conrad Meyer 2 participants 3 messages Mar 28, 2014 – Mar 29, 2014 VFAT/FAT/msdos filesystem

v1 of 4 Superseded Latest is v4 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 fs: FAT: Add support for DOS 1.x formatted volumes

When possible, infer DOS 2.x BIOS Parameter Block from block device geometry (for floppies and floppy images). Update in-memory only. We only perform this update when the entire BPB region is zeroed, like produced by DOS 1.x-era FORMAT (and other OEM variations on DOS).

Fixes kernel.org bug #42617.

BPB default 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

---
Diff is from Linus' v3.14-rc8.

I am a kernel newbie, apologies in advance for newbie mistakes. Let me know what is wrong and I will fix it and learn.

Thanks!

---
 fs/fat/inode.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 62 insertions(+)
fs/fat/inode.c +62 -0
index 854b578..f3686d6 100644
@@ -35,6 +35,8 @@
 #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;
 
@@ -1246,6 +1248,63 @@ static unsigned long calc_fat_clusters(struct super_block *sb)
 }
 
 /*
+ * If this FAT filesystem is archaic (lacking a BIOS Parameter Block, ca. DOS
+ * 1.x), fix it up in-place by creating a DOS 2.x BIOS Parameter Block from
+ * defaults for the media size.
+ */
+static void fat_update_archaic_boot_sector(struct super_block *sb,
+	struct fat_boot_sector *b)
+{
+	sector_t bd_sects;
+
+	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)
+		return;
+
+	bd_sects = part_nr_sects_read(sb->s_bdev->bd_part);
+	switch (bd_sects) {
+	case 160 * KB_IN_SECTORS:
+		b->sec_per_clus = 1;
+		put_unaligned_le16(64, &b->dir_entries);
+		b->media = 0xFE;
+		b->fat_length = cpu_to_le16(1);
+		break;
+	case 180 * KB_IN_SECTORS:
+		b->sec_per_clus = 1;
+		put_unaligned_le16(64, &b->dir_entries);
+		b->media = 0xFC;
+		b->fat_length = cpu_to_le16(2);
+		break;
+	case 320 * KB_IN_SECTORS:
+		b->sec_per_clus = 2;
+		put_unaligned_le16(112, &b->dir_entries);
+		b->media = 0xFF;
+		b->fat_length = cpu_to_le16(1);
+		break;
+	case 360 * KB_IN_SECTORS:
+		b->sec_per_clus = 2;
+		put_unaligned_le16(112, &b->dir_entries);
+		b->media = 0xFD;
+		b->fat_length = cpu_to_le16(2);
+		break;
+	default:
+		fat_msg(sb, KERN_WARNING,
+			"DOS volume lacks BPB and isn't a recognized floppy size (%ld sectors)",
+			(long)bd_sects);
+		return;
+	}
+
+	put_unaligned_le16(SECTOR_SIZE, &b->sector_size);
+	b->reserved = cpu_to_le16(1);
+	b->fats = 2;
+	put_unaligned_le16(bd_sects, &b->sectors);
+}
+
+/*
  * Read the super block of an MS-DOS FS.
  */
 int fat_fill_super(struct super_block *sb, void *data, int silent, int isvfat,
@@ -1297,6 +1356,8 @@ int fat_fill_super(struct super_block *sb, void *data, int silent, int isvfat,
 	}
 
 	b = (struct fat_boot_sector *) bh->b_data;
+	fat_update_archaic_boot_sector(sb, b);
+
 	if (!b->reserved) {
 		if (!silent)
 			fat_msg(sb, KERN_ERR, "bogus number of reserved sectors");
@@ -1364,6 +1425,7 @@ int fat_fill_super(struct super_block *sb, void *data, int silent, int isvfat,
 			goto out_fail;
 		}
 		b = (struct fat_boot_sector *) bh->b_data;
+		fat_update_archaic_boot_sector(sb, b);
 	}
 
 	mutex_init(&sbi->s_lock);
Signature · 1 line
1.9.0
maintainer
#1 lore raw

Conrad Meyer <[email protected]> writes:

Hi,

Quoted 12 lines

When possible, infer DOS 2.x BIOS Parameter Block from block device geometry (for floppies and floppy images). Update in-memory only. We only perform this update when the entire BPB region is zeroed, like produced by DOS 1.x-era FORMAT (and other OEM variations on DOS).

Fixes kernel.org bug #42617.

BPB default 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

[...]

Quoted 11 lines
fs/fat/inode.c +11 -0
+static void fat_update_archaic_boot_sector(struct super_block *sb,
+	struct fat_boot_sector *b)
+{
+	sector_t bd_sects;
+
+	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)

fs/fat/inode.c static unsigned long calc_fat_clusters(struct super_block *sb) · line 1275

Quoted 10 lines
fs/fat/inode.c +10 -0
+		return;
+
+	bd_sects = part_nr_sects_read(sb->s_bdev->bd_part);
+	switch (bd_sects) {
+	case 160 * KB_IN_SECTORS:
+		b->sec_per_clus = 1;
+		put_unaligned_le16(64, &b->dir_entries);
+		b->media = 0xFE;
+		b->fat_length = cpu_to_le16(1);
+		break;

[...]

Hm, this looks like check the volume size. But if there is newer fat format on same volume size, how to detect it? Or, it is conflicting?

[BTW, we should avoid to mount if it doesn't seem fatfs, to prevent mis-mount as fatfs (auto mount is depending on this detection).]

Thanks.

Signature · 1 line
OGAWA Hirofumi <[email protected]>
#2 lore raw
Quoted 52 lines · On Sun, 30 Mar 2014 02:56:46 +0900 OGAWA Hirofumi…

Conrad Meyer <[email protected]> writes:

Hi,

Quoted 15 lines

When possible, infer DOS 2.x BIOS Parameter Block from block device geometry (for floppies and floppy images). Update in-memory only. We only perform this update when the entire BPB region is zeroed, like produced by DOS 1.x-era FORMAT (and other OEM variations on DOS).

Fixes kernel.org bug #42617.

BPB default 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

[...]

Quoted 27 lines

+static void fat_update_archaic_boot_sector(struct super_block *sb,

fs/fat/inode.c +5 -0
+	struct fat_boot_sector *b)
+{
+	sector_t bd_sects;
+
+	if (get_unaligned_le16(&b->sector_size) != 0 ||

b->sec_per_clus != 0 ||

(quoted diff) +2 -0
+		b->reserved != 0 || b->fats != 0 ||
+		get_unaligned_le16(&b->dir_entries) != 0

||

(quoted diff) +1 -0
+		get_unaligned_le16(&b->sectors) != 0 ||

b->media != 0 ||

(quoted diff) +1 -0
+		b->fat_length != 0 || b->secs_track != 0

|| b->heads != 0 ||

fs/fat/inode.c +4 -0
+		b->secs_track != 0 || b->heads != 0)
+		return;
+
+	bd_sects =

part_nr_sects_read(sb->s_bdev->bd_part);

fs/fat/inode.c +7 -0
+	switch (bd_sects) {
+	case 160 * KB_IN_SECTORS:
+		b->sec_per_clus = 1;
+		put_unaligned_le16(64, &b->dir_entries);
+		b->media = 0xFE;
+		b->fat_length = cpu_to_le16(1);
+		break;

[...]

Quoted 3 lines

Hm, this looks like check the volume size. But if there is newer fat format on same volume size, how to detect it? Or, it is conflicting?

Newer fat volumes will have some non-zero values in the BPB -- see the early return at the top of the update function. So this code will ignore them.

Quoted 6 lines

[BTW, we should avoid to mount if it doesn't seem fatfs, to prevent mis-mount as fatfs (auto mount is depending on this detection).]

Thanks.

Hmm, good point. The checks for zero values in 0x0b through ~0x19 (BPB 2 with some of the BPB 3 fields) should help prevent conflicts, to some degree. We can also check the 3-byte field "ignored" in struct fat_boot_sector -- it is commonly "eb xx 90" (x86: JMP [rel8] ; NOP) but can also be "e9 xx xx xx xx" (x86: JMP [rel32]).

These old floppies were only ever created on 16-bit machines, so I think we can conditionalize on "eb xx 90". I will fix and resend.

The first three bytes are on all the images from 1985 I have
are: eb 1c 90.

Here's the full boot sector. Perhaps we can also conditionalize on the "Not system boot floppy" string? Probably not, we want to support mounting boot floppies as well.

0000000: eb1c 9000 0000 0000 0000 0000 0000 0000  ................
0000010: 0000 0000 0000 0000 0000 0000 0000 fa33  ...............3
0000020: c08e d0bc 0006 fb0e e846 00b4 06b0 00b7  .........F......
0000030: 07b9 0000 ba4f 18cd 10b4 02ba 0000 b700  .....O..........
0000040: cd10 beb8 102e 8a04 0ac0 74f9 56b4 0ebb  ..........t.V...
0000050: 0700 cd10 5e46 ebed 074e 6f74 2073 7973  ....^F...Not sys
0000060: 7465 6d20 626f 6f74 2066 6c6f 7070 792e  tem boot floppy.
0000070: 0058 5bba 8b10 8bca 2bd0 d1fa d1fa d1fa  .X[.....+.......
0000080: d1fa 2bda 5351 cb00 0000 0000 0000 0000  ..+.SQ..........

The rest (0x090-0x1ff) is zeroes.

And here's the disassembly of the address signified by "JMP +0x1c" (0x1e):

0x0000001e fa cli
0x0000001f 33c0 xor %ax,%ax
0x00000021 8ed0 mov %ax,%ss
0x00000023 bc0006 mov $0x600,%sp
0x00000026 fb sti
0x00000027 0e push %cs
0x00000028 e84600 call func_00000071

Not sure how common that is among FAT images; mkfs.fat does not appear to generate valid code, other than eb 3c 90 at the beginning of the sector.

Thanks,
Conrad