Conrad Meyer <cse....@gmail.com> writes:

> Add structure for parsed BPB information, struct fat_bios_param_block,
> and move all of the deserialization and validation logic from
> fat_fill_super() into fat_read_bpb().
>
> Add a 'dos1xfloppy' mount option to infer DOS 2.x BIOS Parameter Block
> defaults from block device geometry for ancient floppies and floppy
> images, as a fall-back from the default BPB parsing logic.
>
> When fat_read_bpb() finds an invalid FAT filesystem and dos1xfloppy is
> set, fall back to fat_read_static_bpb(). fat_read_static_bpb() validates
> that the entire BPB is zero, and that the floppy has a DOS-style 8086
> code bootstrapping header. Then it fills in default BPB values from
> media size and a table.[0]
>
> Media size is assumed to be static for archaic FAT volumes. See also:
> [1].
>
> Fixes kernel.org bug #42617.
>
> [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....@gmail.com>

Looks good.

Acked-by: OGAWA Hirofumi <hirof...@mail.parknet.co.jp>

Thanks.

> ---
> Changes from v7:
>   * Move struct fat_bios_param_block from fat.h to fat/inode.c
> ---
>  Documentation/filesystems/vfat.txt |   5 +
>  fs/fat/fat.h                       |   3 +-
>  fs/fat/inode.c                     | 342 
> ++++++++++++++++++++++++++++---------
>  3 files changed, 273 insertions(+), 77 deletions(-)
>
> diff --git a/Documentation/filesystems/vfat.txt 
> b/Documentation/filesystems/vfat.txt
> index 5cf57b3..223c321 100644
> --- a/Documentation/filesystems/vfat.txt
> +++ b/Documentation/filesystems/vfat.txt
> @@ -172,6 +172,11 @@ nfs=stale_rw|nostale_ro
>               To maintain backward compatibility, '-o nfs' is also accepted,
>               defaulting to stale_rw
>  
> +dos1xfloppy  -- If set, use a fallback default BIOS Parameter Block
> +             configuration, determined by backing device size. These static
> +             parameters match defaults assumed by DOS 1.x for 160 kiB,
> +             180 kiB, 320 kiB, and 360 kiB floppies and floppy images.
> +
>  
>  <bool>: 0,1,yes,no,true,false
>  
> 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..d44c510 100644
> --- a/fs/fat/inode.c
> +++ b/fs/fat/inode.c
> @@ -35,9 +35,71 @@
>  #define CONFIG_FAT_DEFAULT_IOCHARSET ""
>  #endif
>  
> +#define KB_IN_SECTORS 2
> +
> +/*
> + * A deserialized copy of the on-disk structure laid out in struct
> + * fat_boot_sector.
> + */
> +struct fat_bios_param_block {
> +     u16     fat_sector_size;
> +     u8      fat_sec_per_clus;
> +     u16     fat_reserved;
> +     u8      fat_fats;
> +     u16     fat_dir_entries;
> +     u16     fat_sectors;
> +     u16     fat_fat_length;
> +     u32     fat_total_sect;
> +
> +     u8      fat16_state;
> +     u32     fat16_vol_id;
> +
> +     u32     fat32_length;
> +     u32     fat32_root_cluster;
> +     u16     fat32_info_sector;
> +     u8      fat32_state;
> +     u32     fat32_vol_id;
> +};
> +
>  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,
> +},
> +};
>  
>  static int fat_add_cluster(struct inode *inode)
>  {
> @@ -931,6 +993,8 @@ static int fat_show_options(struct seq_file *m, struct 
> dentry *root)
>               seq_puts(m, ",nfs=stale_rw");
>       if (opts->discard)
>               seq_puts(m, ",discard");
> +     if (opts->dos1xfloppy)
> +             seq_puts(m, ",dos1xfloppy");
>  
>       return 0;
>  }
> @@ -945,7 +1009,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 +1042,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 +1245,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:
> @@ -1326,6 +1394,169 @@ static unsigned long calc_fat_clusters(struct 
> super_block *sb)
>       return sbi->fat_length * sb->s_blocksize * 8 / sbi->fat_bits;
>  }
>  
> +static bool fat_bpb_is_zero(struct fat_boot_sector *b)
> +{
> +     if (get_unaligned_le16(&b->sector_size))
> +             return false;
> +     if (b->sec_per_clus)
> +             return false;
> +     if (b->reserved)
> +             return false;
> +     if (b->fats)
> +             return false;
> +     if (get_unaligned_le16(&b->dir_entries))
> +             return false;
> +     if (get_unaligned_le16(&b->sectors))
> +             return false;
> +     if (b->media)
> +             return false;
> +     if (b->fat_length)
> +             return false;
> +     if (b->secs_track)
> +             return false;
> +     if (b->heads)
> +             return false;
> +     return true;
> +}
> +
> +static int fat_read_bpb(struct super_block *sb, struct fat_boot_sector *b,
> +     int silent, struct fat_bios_param_block *bpb)
> +{
> +     int error = -EINVAL;
> +
> +     /* Read in BPB ... */
> +     memset(bpb, 0, sizeof(*bpb));
> +     bpb->fat_sector_size = get_unaligned_le16(&b->sector_size);
> +     bpb->fat_sec_per_clus = b->sec_per_clus;
> +     bpb->fat_reserved = le16_to_cpu(b->reserved);
> +     bpb->fat_fats = b->fats;
> +     bpb->fat_dir_entries = get_unaligned_le16(&b->dir_entries);
> +     bpb->fat_sectors = get_unaligned_le16(&b->sectors);
> +     bpb->fat_fat_length = le16_to_cpu(b->fat_length);
> +     bpb->fat_total_sect = le32_to_cpu(b->total_sect);
> +
> +     bpb->fat16_state = b->fat16.state;
> +     bpb->fat16_vol_id = get_unaligned_le32(b->fat16.vol_id);
> +
> +     bpb->fat32_length = le32_to_cpu(b->fat32.length);
> +     bpb->fat32_root_cluster = le32_to_cpu(b->fat32.root_cluster);
> +     bpb->fat32_info_sector = le16_to_cpu(b->fat32.info_sector);
> +     bpb->fat32_state = b->fat32.state;
> +     bpb->fat32_vol_id = get_unaligned_le32(b->fat32.vol_id);
> +
> +     /* Validate this looks like a FAT filesystem BPB */
> +     if (!bpb->fat_reserved) {
> +             if (!silent)
> +                     fat_msg(sb, KERN_ERR,
> +                             "bogus number of reserved sectors");
> +             goto out;
> +     }
> +     if (!bpb->fat_fats) {
> +             if (!silent)
> +                     fat_msg(sb, KERN_ERR, "bogus number of FAT structure");
> +             goto out;
> +     }
> +
> +     /*
> +      * Earlier we checked here that b->secs_track and b->head are nonzero,
> +      * but it turns out valid FAT filesystems can have zero there.
> +      */
> +
> +     if (!fat_valid_media(b->media)) {
> +             if (!silent)
> +                     fat_msg(sb, KERN_ERR, "invalid media value (0x%02x)",
> +                             (unsigned)b->media);
> +             goto out;
> +     }
> +
> +     if (!is_power_of_2(bpb->fat_sector_size)
> +         || (bpb->fat_sector_size < 512)
> +         || (bpb->fat_sector_size > 4096)) {
> +             if (!silent)
> +                     fat_msg(sb, KERN_ERR, "bogus logical sector size %u",
> +                            (unsigned)bpb->fat_sector_size);
> +             goto out;
> +     }
> +
> +     if (!is_power_of_2(bpb->fat_sec_per_clus)) {
> +             if (!silent)
> +                     fat_msg(sb, KERN_ERR, "bogus sectors per cluster %u",
> +                             (unsigned)bpb->fat_sec_per_clus);
> +             goto out;
> +     }
> +
> +     error = 0;
> +
> +out:
> +     return error;
> +}
> +
> +static int fat_read_static_bpb(struct super_block *sb,
> +     struct fat_boot_sector *b, int silent,
> +     struct fat_bios_param_block *bpb)
> +{
> +     static const char *notdos1x = "This doesn't look like a DOS 1.x volume";
> +
> +     struct fat_floppy_defaults *fdefaults = NULL;
> +     int error = -EINVAL;
> +     sector_t bd_sects;
> +     unsigned i;
> +
> +     bd_sects = i_size_read(sb->s_bdev->bd_inode) / SECTOR_SIZE;
> +
> +     /* 16-bit DOS 1.x reliably wrote bootstrap short-jmp code */
> +     if (b->ignored[0] != 0xeb || b->ignored[2] != 0x90) {
> +             if (!silent)
> +                     fat_msg(sb, KERN_ERR,
> +                             "%s; no bootstrapping code", notdos1x);
> +             goto out;
> +     }
> +
> +     /*
> +      * If any value in this region is non-zero, it isn't archaic
> +      * DOS.
> +      */
> +     if (!fat_bpb_is_zero(b)) {
> +             if (!silent)
> +                     fat_msg(sb, KERN_ERR,
> +                             "%s; DOS 2.x BPB is non-zero", notdos1x);
> +             goto out;
> +     }
> +
> +     for (i = 0; i < ARRAY_SIZE(floppy_defaults); i++) {
> +             if (floppy_defaults[i].nr_sectors == bd_sects) {
> +                     fdefaults = &floppy_defaults[i];
> +                     break;
> +             }
> +     }
> +
> +     if (fdefaults == NULL) {
> +             if (!silent)
> +                     fat_msg(sb, KERN_WARNING,
> +                             "This looks like a DOS 1.x volume, but isn't a 
> recognized floppy size (%llu sectors)",
> +                             (u64)bd_sects);
> +             goto out;
> +     }
> +
> +     if (!silent)
> +             fat_msg(sb, KERN_INFO,
> +                     "This looks like a DOS 1.x volume; assuming default BPB 
> values");
> +
> +     memset(bpb, 0, sizeof(*bpb));
> +     bpb->fat_sector_size = SECTOR_SIZE;
> +     bpb->fat_sec_per_clus = fdefaults->sec_per_clus;
> +     bpb->fat_reserved = 1;
> +     bpb->fat_fats = 2;
> +     bpb->fat_dir_entries = fdefaults->dir_entries;
> +     bpb->fat_sectors = fdefaults->nr_sectors;
> +     bpb->fat_fat_length = fdefaults->fat_length;
> +
> +     error = 0;
> +
> +out:
> +     return error;
> +}
> +
>  /*
>   * Read the super block of an MS-DOS FS.
>   */
> @@ -1335,12 +1566,11 @@ int fat_fill_super(struct super_block *sb, void 
> *data, int silent, int isvfat,
>       struct inode *root_inode = NULL, *fat_inode = NULL;
>       struct inode *fsinfo_inode = NULL;
>       struct buffer_head *bh;
> -     struct fat_boot_sector *b;
> +     struct fat_bios_param_block bpb;
>       struct msdos_sb_info *sbi;
>       u16 logical_sector_size;
>       u32 total_sectors, total_clusters, fat_clusters, rootdir_sectors;
>       int debug;
> -     unsigned int media;
>       long error;
>       char buf[50];
>  
> @@ -1377,100 +1607,71 @@ 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;
> -     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;
> -     }
> -
> -     /*
> -      * Earlier we checked here that b->secs_track and b->head are nonzero,
> -      * but it turns out valid FAT filesystems can have zero there.
> -      */
> +     error = fat_read_bpb(sb, (struct fat_boot_sector *)bh->b_data, silent,
> +             &bpb);
> +     if (error == -EINVAL && sbi->options.dos1xfloppy)
> +             error = fat_read_static_bpb(sb,
> +                     (struct fat_boot_sector *)bh->b_data, silent, &bpb);
> +     brelse(bh);
>  
> -     media = b->media;
> -     if (!fat_valid_media(media)) {
> -             if (!silent)
> -                     fat_msg(sb, KERN_ERR, "invalid media value (0x%02x)",
> -                            media);
> -             brelse(bh);
> -             goto out_invalid;
> -     }
> -     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)) {
> -             if (!silent)
> -                     fat_msg(sb, KERN_ERR, "bogus logical sector size %u",
> -                            logical_sector_size);
> -             brelse(bh);
> -             goto out_invalid;
> -     }
> -     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",
> -                            sbi->sec_per_clus);
> -             brelse(bh);
> +     if (error == -EINVAL)
>               goto out_invalid;
> -     }
> +     else if (error)
> +             goto out_fail;
> +
> +     logical_sector_size = bpb.fat_sector_size;
> +     sbi->sec_per_clus = bpb.fat_sec_per_clus;
>  
>       if (logical_sector_size < sb->s_blocksize) {
>               fat_msg(sb, KERN_ERR, "logical sector size too small for device"
>                      " (logical sector size = %u)", logical_sector_size);
> -             brelse(bh);
>               goto out_fail;
>       }
> +
>       if (logical_sector_size > sb->s_blocksize) {
> -             brelse(bh);
> +             struct buffer_head *bh_resize;
>  
>               if (!sb_set_blocksize(sb, logical_sector_size)) {
>                       fat_msg(sb, KERN_ERR, "unable to set blocksize %u",
>                              logical_sector_size);
>                       goto out_fail;
>               }
> -             bh = sb_bread(sb, 0);
> -             if (bh == NULL) {
> +
> +             /* Verify that the larger boot sector is fully readable */
> +             bh_resize = sb_bread(sb, 0);
> +             if (bh_resize == NULL) {
>                       fat_msg(sb, KERN_ERR, "unable to read boot sector"
>                              " (logical sector size = %lu)",
>                              sb->s_blocksize);
>                       goto out_fail;
>               }
> -             b = (struct fat_boot_sector *) bh->b_data;
> +             brelse(bh_resize);
>       }
>  
>       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;
> +     sbi->fats = bpb.fat_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);
> +     sbi->fat_start = bpb.fat_reserved;
> +     sbi->fat_length = bpb.fat_fat_length;
>       sbi->root_cluster = 0;
>       sbi->free_clusters = -1;        /* Don't know yet */
>       sbi->free_clus_valid = 0;
>       sbi->prev_free = FAT_START_ENT;
>       sb->s_maxbytes = 0xffffffff;
>  
> -     if (!sbi->fat_length && b->fat32.length) {
> +     if (!sbi->fat_length && bpb.fat32_length) {
>               struct fat_boot_fsinfo *fsinfo;
>               struct buffer_head *fsinfo_bh;
>  
>               /* Must be FAT32 */
>               sbi->fat_bits = 32;
> -             sbi->fat_length = le32_to_cpu(b->fat32.length);
> -             sbi->root_cluster = le32_to_cpu(b->fat32.root_cluster);
> +             sbi->fat_length = bpb.fat32_length;
> +             sbi->root_cluster = bpb.fat32_root_cluster;
>  
>               /* MC - if info_sector is 0, don't multiply by 0 */
> -             sbi->fsinfo_sector = le16_to_cpu(b->fat32.info_sector);
> +             sbi->fsinfo_sector = bpb.fat32_info_sector;
>               if (sbi->fsinfo_sector == 0)
>                       sbi->fsinfo_sector = 1;
>  
> @@ -1478,7 +1679,6 @@ int fat_fill_super(struct super_block *sb, void *data, 
> int silent, int isvfat,
>               if (fsinfo_bh == NULL) {
>                       fat_msg(sb, KERN_ERR, "bread failed, FSINFO block"
>                              " (sector = %lu)", sbi->fsinfo_sector);
> -                     brelse(bh);
>                       goto out_fail;
>               }
>  
> @@ -1501,35 +1701,28 @@ int fat_fill_super(struct super_block *sb, void 
> *data, int silent, int isvfat,
>  
>       /* interpret volume ID as a little endian 32 bit integer */
>       if (sbi->fat_bits == 32)
> -             sbi->vol_id = (((u32)b->fat32.vol_id[0]) |
> -                                     ((u32)b->fat32.vol_id[1] << 8) |
> -                                     ((u32)b->fat32.vol_id[2] << 16) |
> -                                     ((u32)b->fat32.vol_id[3] << 24));
> +             sbi->vol_id = bpb.fat32_vol_id;
>       else /* fat 16 or 12 */
> -             sbi->vol_id = (((u32)b->fat16.vol_id[0]) |
> -                                     ((u32)b->fat16.vol_id[1] << 8) |
> -                                     ((u32)b->fat16.vol_id[2] << 16) |
> -                                     ((u32)b->fat16.vol_id[3] << 24));
> +             sbi->vol_id = bpb.fat16_vol_id;
>  
>       sbi->dir_per_block = sb->s_blocksize / sizeof(struct msdos_dir_entry);
>       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);
> +     sbi->dir_entries = bpb.fat_dir_entries;
>       if (sbi->dir_entries & (sbi->dir_per_block - 1)) {
>               if (!silent)
>                       fat_msg(sb, KERN_ERR, "bogus directory-entries per 
> block"
>                              " (%u)", sbi->dir_entries);
> -             brelse(bh);
>               goto out_invalid;
>       }
>  
>       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);
> +     total_sectors = bpb.fat_sectors;
>       if (total_sectors == 0)
> -             total_sectors = le32_to_cpu(b->total_sect);
> +             total_sectors = bpb.fat_total_sect;
>  
>       total_clusters = (total_sectors - sbi->data_start) / sbi->sec_per_clus;
>  
> @@ -1538,9 +1731,9 @@ int fat_fill_super(struct super_block *sb, void *data, 
> int silent, int isvfat,
>  
>       /* some OSes set FAT_STATE_DIRTY and clean it on unmount. */
>       if (sbi->fat_bits == 32)
> -             sbi->dirty = b->fat32.state & FAT_STATE_DIRTY;
> +             sbi->dirty = bpb.fat32_state & FAT_STATE_DIRTY;
>       else /* fat 16 or 12 */
> -             sbi->dirty = b->fat16.state & FAT_STATE_DIRTY;
> +             sbi->dirty = bpb.fat16_state & FAT_STATE_DIRTY;
>  
>       /* check that FAT table does not overflow */
>       fat_clusters = calc_fat_clusters(sb);
> @@ -1549,7 +1742,6 @@ int fat_fill_super(struct super_block *sb, void *data, 
> int silent, int isvfat,
>               if (!silent)
>                       fat_msg(sb, KERN_ERR, "count of clusters too big (%u)",
>                              total_clusters);
> -             brelse(bh);
>               goto out_invalid;
>       }
>  
> @@ -1562,8 +1754,6 @@ int fat_fill_super(struct super_block *sb, void *data, 
> int silent, int isvfat,
>       if (sbi->prev_free < FAT_START_ENT)
>               sbi->prev_free = FAT_START_ENT;
>  
> -     brelse(bh);
> -
>       /* set up enough so that it can read an inode */
>       fat_hash_init(sb);
>       dir_hash_init(sb);

-- 
OGAWA Hirofumi <hirof...@mail.parknet.co.jp>
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to