fs: fat: fix reading non-cluster-aligned root directory
authorAnssi Hannula <anssi.hannula@bitwise.fi>
Wed, 27 Feb 2019 10:55:57 +0000 (12:55 +0200)
committerTom Rini <trini@konsulko.com>
Wed, 10 Apr 2019 00:04:04 +0000 (20:04 -0400)
A FAT12/FAT16 root directory location is specified by a sector offset and
it might not start at a cluster boundary. It also resides before the
data area (before cluster 2).

However, the current code assumes that the root directory is located at
a beginning of a cluster, causing no files to be found if that is not
the case.

Since the FAT12/FAT16 root directory is located before the data area
and is not aligned to clusters, using unsigned cluster numbers to refer
to the root directory does not work well (the "cluster number" may be
negative, and even allowing it be signed would not make it properly
aligned).

Modify the code to not use the normal cluster numbering when referring to
the root directory of FAT12/FAT16 and instead use a cluster-sized
offsets counted from the root directory start sector.

This is a relatively common case as at least the filesystem formatter on
Win7 seems to create such filesystems by default on 2GB USB sticks when
"FAT" is selected (cluster size 64 sectors, rootdir size 32 sectors,
rootdir starts at half a cluster before cluster 2).

dosfstools mkfs.vfat does not seem to create affected filesystems.

Signed-off-by: Anssi Hannula <anssi.hannula@bitwise.fi>
Reviewed-by: Bernhard Messerklinger <bernhard.messerklinger@br-automation.com>
Tested-by: Bernhard Messerklinger <bernhard.messerklinger@br-automation.com>
fs/fat/fat.c

index 6ade4ea54ecd8e227196c13c73e077863801828a..c5997c21735f9714aae7c63486750837cac87688 100644 (file)
@@ -602,8 +602,13 @@ static int get_fs_info(fsdata *mydata)
                mydata->data_begin = mydata->rootdir_sect +
                                        mydata->rootdir_size -
                                        (mydata->clust_size * 2);
-               mydata->root_cluster =
-                       sect_to_clust(mydata, mydata->rootdir_sect);
+
+               /*
+                * The root directory is not cluster-aligned and may be on a
+                * "negative" cluster, this will be handled specially in
+                * next_cluster().
+                */
+               mydata->root_cluster = 0;
        }
 
        mydata->fatbufnum = -1;
@@ -733,20 +738,38 @@ static void fat_itr_child(fat_itr *itr, fat_itr *parent)
        itr->last_cluster = 0;
 }
 
-static void *next_cluster(fat_itr *itr)
+static void *next_cluster(fat_itr *itr, unsigned *nbytes)
 {
        fsdata *mydata = itr->fsdata;  /* for silly macros */
        int ret;
        u32 sect;
+       u32 read_size;
 
        /* have we reached the end? */
        if (itr->last_cluster)
                return NULL;
 
-       sect = clust_to_sect(itr->fsdata, itr->next_clust);
+       if (itr->is_root && itr->fsdata->fatsize != 32) {
+               /*
+                * The root directory is located before the data area and
+                * cannot be indexed using the regular unsigned cluster
+                * numbers (it may start at a "negative" cluster or not at a
+                * cluster boundary at all), so consider itr->next_clust to be
+                * a offset in cluster-sized units from the start of rootdir.
+                */
+               unsigned sect_offset = itr->next_clust * itr->fsdata->clust_size;
+               unsigned remaining_sects = itr->fsdata->rootdir_size - sect_offset;
+               sect = itr->fsdata->rootdir_sect + sect_offset;
+               /* do not read past the end of rootdir */
+               read_size = min_t(u32, itr->fsdata->clust_size,
+                                 remaining_sects);
+       } else {
+               sect = clust_to_sect(itr->fsdata, itr->next_clust);
+               read_size = itr->fsdata->clust_size;
+       }
 
-       debug("FAT read(sect=%d), clust_size=%d, DIRENTSPERBLOCK=%zd\n",
-             sect, itr->fsdata->clust_size, DIRENTSPERBLOCK);
+       debug("FAT read(sect=%d), clust_size=%d, read_size=%u, DIRENTSPERBLOCK=%zd\n",
+             sect, itr->fsdata->clust_size, read_size, DIRENTSPERBLOCK);
 
        /*
         * NOTE: do_fat_read_at() had complicated logic to deal w/
@@ -757,18 +780,17 @@ static void *next_cluster(fat_itr *itr)
         * dent at a time and iteratively constructing the vfat long
         * name.
         */
-       ret = disk_read(sect, itr->fsdata->clust_size,
-                       itr->block);
+       ret = disk_read(sect, read_size, itr->block);
        if (ret < 0) {
                debug("Error: reading block\n");
                return NULL;
        }
 
+       *nbytes = read_size * itr->fsdata->sect_size;
        itr->clust = itr->next_clust;
        if (itr->is_root && itr->fsdata->fatsize != 32) {
                itr->next_clust++;
-               sect = clust_to_sect(itr->fsdata, itr->next_clust);
-               if (sect - itr->fsdata->rootdir_sect >=
+               if (itr->next_clust * itr->fsdata->clust_size >=
                    itr->fsdata->rootdir_size) {
                        debug("nextclust: 0x%x\n", itr->next_clust);
                        itr->last_cluster = 1;
@@ -787,9 +809,8 @@ static void *next_cluster(fat_itr *itr)
 static dir_entry *next_dent(fat_itr *itr)
 {
        if (itr->remaining == 0) {
-               struct dir_entry *dent = next_cluster(itr);
-               unsigned nbytes = itr->fsdata->sect_size *
-                       itr->fsdata->clust_size;
+               unsigned nbytes;
+               struct dir_entry *dent = next_cluster(itr, &nbytes);
 
                /* have we reached the last cluster? */
                if (!dent) {