*
* R/W (V)FAT 12/16/32 filesystem implementation by Donggeun Kim
*
- * See file CREDITS for list of people who contributed to this
- * project.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
- * MA 02111-1307 USA
+ * SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <fat.h>
#include <asm/byteorder.h>
#include <part.h>
+#include <linux/ctype.h>
+#include <div64.h>
+#include <linux/math64.h>
#include "fat.c"
static void uppercase(char *str, int len)
int i;
for (i = 0; i < len; i++) {
- TOUPPER(*str);
+ *str = toupper(*str);
str++;
}
}
static int total_sector;
static int disk_write(__u32 block, __u32 nr_blocks, void *buf)
{
- if (!cur_dev || !cur_dev->block_write)
+ ulong ret;
+
+ if (!cur_dev)
return -1;
if (cur_part_info.start + block + nr_blocks >
return -1;
}
- return cur_dev->block_write(cur_dev->dev,
- cur_part_info.start + block, nr_blocks, buf);
+ ret = blk_dwrite(cur_dev, cur_part_info.start + block, nr_blocks, buf);
+ if (nr_blocks && ret == 0)
+ return -1;
+
+ return ret;
}
/*
if (len == 0)
return;
- memcpy(s_name, filename, len);
+ strcpy(s_name, filename);
uppercase(s_name, len);
period = strchr(s_name, '.');
/*
* Write fat buffer into block device
*/
-static int flush_fat_buffer(fsdata *mydata)
+static int flush_dirty_fat_buffer(fsdata *mydata)
{
int getsize = FATBUFBLOCKS;
__u32 fatlength = mydata->fatlength;
__u8 *bufptr = mydata->fatbuf;
__u32 startblock = mydata->fatbufnum * FATBUFBLOCKS;
- fatlength *= mydata->sect_size;
+ debug("debug: evicting %d, dirty: %d\n", mydata->fatbufnum,
+ (int)mydata->fat_dirty);
+
+ if ((!mydata->fat_dirty) || (mydata->fatbufnum == -1))
+ return 0;
+
startblock += mydata->fat_sect;
if (getsize > fatlength)
return -1;
}
}
+ mydata->fat_dirty = 0;
return 0;
}
__u32 ret = 0x00;
__u16 val1, val2;
+ if (CHECK_CLUST(entry, mydata->fatsize)) {
+ printf("Error: Invalid FAT entry: 0x%08x\n", entry);
+ return ret;
+ }
+
switch (mydata->fatsize) {
case 32:
bufnum = entry / FAT32BUFSIZE;
if (getsize > fatlength)
getsize = fatlength;
- fatlength *= mydata->sect_size; /* We want it in bytes now */
startblock += mydata->fat_sect; /* Offset from start of disk */
/* Write back the fatbuf to the disk */
- if (mydata->fatbufnum != -1) {
- if (flush_fat_buffer(mydata) < 0)
- return -1;
- }
+ if (flush_dirty_fat_buffer(mydata) < 0)
+ return -1;
if (disk_read(startblock, getsize, bufptr) < 0) {
debug("Error reading FAT blocks\n");
return ret;
}
-#ifdef CONFIG_SUPPORT_VFAT
/*
* Set the file name information from 'name' into 'slotptr',
*/
static void
fill_dir_slot(fsdata *mydata, dir_entry **dentptr, const char *l_name)
{
- dir_slot *slotptr = (dir_slot *)get_contents_vfatname_block;
+ __u8 temp_dir_slot_buffer[MAX_LFN_SLOT * sizeof(dir_slot)];
+ dir_slot *slotptr = (dir_slot *)temp_dir_slot_buffer;
__u8 counter = 0, checksum;
int idx = 0, ret;
- char s_name[16];
- /* Get short file name and checksum value */
- strncpy(s_name, (*dentptr)->name, 16);
+ /* Get short file name checksum value */
checksum = mkcksum((*dentptr)->name, (*dentptr)->ext);
do {
return 0;
}
-#endif
-
/*
* Set the entry at index 'entry' in a FAT (16/32) table.
*/
if (getsize > fatlength)
getsize = fatlength;
- if (mydata->fatbufnum != -1) {
- if (flush_fat_buffer(mydata) < 0)
- return -1;
- }
+ if (flush_dirty_fat_buffer(mydata) < 0)
+ return -1;
if (disk_read(startblock, getsize, bufptr) < 0) {
debug("Error reading FAT blocks\n");
mydata->fatbufnum = bufnum;
}
+ /* Mark as dirty */
+ mydata->fat_dirty = 1;
+
/* Set the actual entry */
switch (mydata->fatsize) {
case 32:
}
/*
- * Determine the entry value at index 'entry' in a FAT (16/32) table
+ * Determine the next free cluster after 'entry' in a FAT (16/32) table
+ * and link it to 'entry'. EOC marker is not set on returned entry.
*/
static __u32 determine_fatent(fsdata *mydata, __u32 entry)
{
while (1) {
next_fat = get_fatent_value(mydata, next_entry);
if (next_fat == 0) {
+ /* found free entry, link to entry */
set_fatent_value(mydata, entry, next_entry);
break;
}
set_cluster(fsdata *mydata, __u32 clustnum, __u8 *buffer,
unsigned long size)
{
- int idx = 0;
+ __u32 idx = 0;
__u32 startsect;
+ int ret;
if (clustnum > 0)
startsect = mydata->data_begin +
debug("clustnum: %d, startsect: %d\n", clustnum, startsect);
- if (disk_write(startsect, size / mydata->sect_size, buffer) < 0) {
- debug("Error writing data\n");
- return -1;
- }
+ if ((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1)) {
+ ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size);
- if (size % mydata->sect_size) {
- __u8 tmpbuf[mydata->sect_size];
+ printf("FAT: Misaligned buffer address (%p)\n", buffer);
- idx = size / mydata->sect_size;
- buffer += idx * mydata->sect_size;
- memcpy(tmpbuf, buffer, size % mydata->sect_size);
+ while (size >= mydata->sect_size) {
+ memcpy(tmpbuf, buffer, mydata->sect_size);
+ ret = disk_write(startsect++, 1, tmpbuf);
+ if (ret != 1) {
+ debug("Error writing data (got %d)\n", ret);
+ return -1;
+ }
- if (disk_write(startsect + idx, 1, tmpbuf) < 0) {
- debug("Error writing data\n");
+ buffer += mydata->sect_size;
+ size -= mydata->sect_size;
+ }
+ } else if (size >= mydata->sect_size) {
+ idx = size / mydata->sect_size;
+ ret = disk_write(startsect, idx, buffer);
+ if (ret != idx) {
+ debug("Error writing data (got %d)\n", ret);
return -1;
}
- return 0;
+ startsect += idx;
+ idx *= mydata->sect_size;
+ buffer += idx;
+ size -= idx;
+ }
+
+ if (size) {
+ ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size);
+
+ memcpy(tmpbuf, buffer, size);
+ ret = disk_write(startsect, 1, tmpbuf);
+ if (ret != 1) {
+ debug("Error writing data (got %d)\n", ret);
+ return -1;
+ }
}
return 0;
dir_curclust = dir_newclust;
- if (flush_fat_buffer(mydata) < 0)
+ if (flush_dirty_fat_buffer(mydata) < 0)
return;
memset(get_dentfromdir_block, 0x00,
}
/* Flush fat buffer */
- if (flush_fat_buffer(mydata) < 0)
+ if (flush_dirty_fat_buffer(mydata) < 0)
return -1;
return 0;
/*
* Write at most 'maxsize' bytes from 'buffer' into
* the file associated with 'dentptr'
- * Return the number of bytes read or -1 on fatal errors.
+ * Update the number of bytes written in *gotsize and return 0
+ * or return -1 on fatal errors.
*/
static int
set_contents(fsdata *mydata, dir_entry *dentptr, __u8 *buffer,
- unsigned long maxsize)
+ loff_t maxsize, loff_t *gotsize)
{
- unsigned long filesize = FAT2CPU32(dentptr->size), gotsize = 0;
+ loff_t filesize = FAT2CPU32(dentptr->size);
unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
__u32 curclust = START(dentptr);
__u32 endclust = 0, newclust = 0;
- unsigned long actsize;
+ loff_t actsize;
- debug("Filesize: %ld bytes\n", filesize);
+ *gotsize = 0;
+ debug("Filesize: %llu bytes\n", filesize);
if (maxsize > 0 && filesize > maxsize)
filesize = maxsize;
- debug("%ld bytes\n", filesize);
+ debug("%llu bytes\n", filesize);
+
+ if (!curclust) {
+ if (filesize) {
+ debug("error: nonempty clusterless file!\n");
+ return -1;
+ }
+ return 0;
+ }
actsize = bytesperclust;
endclust = curclust;
goto getit;
if (CHECK_CLUST(newclust, mydata->fatsize)) {
- debug("curclust: 0x%x\n", newclust);
+ debug("newclust: 0x%x\n", newclust);
debug("Invalid FAT entry\n");
- return gotsize;
+ return 0;
}
endclust = newclust;
actsize += bytesperclust;
}
- /* actsize >= file size */
- actsize -= bytesperclust;
- /* set remaining clusters */
- if (set_cluster(mydata, curclust, buffer, (int)actsize) != 0) {
- debug("error: writing cluster\n");
- return -1;
- }
/* set remaining bytes */
- gotsize += (int)actsize;
- filesize -= actsize;
- buffer += actsize;
actsize = filesize;
-
- if (set_cluster(mydata, endclust, buffer, (int)actsize) != 0) {
+ if (set_cluster(mydata, curclust, buffer, (int)actsize) != 0) {
debug("error: writing cluster\n");
return -1;
}
- gotsize += actsize;
+ *gotsize += actsize;
/* Mark end of file in FAT */
if (mydata->fatsize == 16)
newclust = 0xfffffff;
set_fatent_value(mydata, endclust, newclust);
- return gotsize;
+ return 0;
getit:
if (set_cluster(mydata, curclust, buffer, (int)actsize) != 0) {
debug("error: writing cluster\n");
return -1;
}
- gotsize += (int)actsize;
+ *gotsize += actsize;
filesize -= actsize;
buffer += actsize;
- if (CHECK_CLUST(curclust, mydata->fatsize)) {
- debug("curclust: 0x%x\n", curclust);
+ if (CHECK_CLUST(newclust, mydata->fatsize)) {
+ debug("newclust: 0x%x\n", newclust);
debug("Invalid FAT entry\n");
- return gotsize;
+ return 0;
}
actsize = bytesperclust;
curclust = endclust = newclust;
}
/*
- * Fill dir_entry
+ * Set start cluster in directory entry
*/
-static void fill_dentry(fsdata *mydata, dir_entry *dentptr,
- const char *filename, __u32 start_cluster, __u32 size, __u8 attr)
+static void set_start_cluster(const fsdata *mydata, dir_entry *dentptr,
+ __u32 start_cluster)
{
if (mydata->fatsize == 32)
dentptr->starthi =
cpu_to_le16((start_cluster & 0xffff0000) >> 16);
dentptr->start = cpu_to_le16(start_cluster & 0xffff);
+}
+
+/*
+ * Fill dir_entry
+ */
+static void fill_dentry(fsdata *mydata, dir_entry *dentptr,
+ const char *filename, __u32 start_cluster, __u32 size, __u8 attr)
+{
+ set_start_cluster(mydata, dentptr, start_cluster);
dentptr->size = cpu_to_le32(size);
dentptr->attr = attr;
* exceed the size of the block device
* Return -1 when overflow occurs, otherwise return 0
*/
-static int check_overflow(fsdata *mydata, __u32 clustnum, unsigned long size)
+static int check_overflow(fsdata *mydata, __u32 clustnum, loff_t size)
{
- __u32 startsect, sect_num;
+ __u32 startsect, sect_num, offset;
if (clustnum > 0) {
startsect = mydata->data_begin +
startsect = mydata->rootdir_sect;
}
- sect_num = size / mydata->sect_size;
- if (size % mydata->sect_size)
+ sect_num = div_u64_rem(size, mydata->sect_size, &offset);
+
+ if (offset != 0)
sect_num++;
if (startsect + sect_num > cur_part_info.start + total_sector)
return -1;
-
return 0;
}
continue;
}
if ((dentptr->attr & ATTR_VOLUME)) {
-#ifdef CONFIG_SUPPORT_VFAT
- if ((dentptr->attr & ATTR_VFAT) &&
+ if (vfat_enabled &&
+ (dentptr->attr & ATTR_VFAT) &&
(dentptr->name[0] & LAST_LONG_ENTRY_MASK)) {
get_long_file_name(mydata, curclust,
get_dentfromdir_block,
&dentptr, l_name);
debug("vfatname: |%s|\n", l_name);
- } else
-#endif
- {
+ } else {
/* Volume label or VFAT entry */
dentptr++;
if (is_next_clust(mydata, dentptr))
return dentptr;
}
+ /*
+ * In FAT16/12, the root dir is locate before data area, shows
+ * in following:
+ * -------------------------------------------------------------
+ * | Boot | FAT1 & 2 | Root dir | Data (start from cluster #2) |
+ * -------------------------------------------------------------
+ *
+ * As a result if curclust is in Root dir, it is a negative
+ * number or 0, 1.
+ *
+ */
+ if (mydata->fatsize != 32 && (int)curclust <= 1) {
+ /* Current clust is in root dir, set to next clust */
+ curclust++;
+ if ((int)curclust <= 1)
+ continue; /* continue to find */
+
+ /* Reach the end of root dir */
+ empty_dentptr = dentptr;
+ return NULL;
+ }
+
curclust = get_fatent_value(mydata, dir_curclust);
- if ((curclust >= 0xffffff8) || (curclust >= 0xfff8)) {
+ if (IS_LAST_CLUST(curclust, mydata->fatsize)) {
empty_dentptr = dentptr;
return NULL;
}
return NULL;
}
-static int do_fat_write(const char *filename, void *buffer,
- unsigned long size)
+static int do_fat_write(const char *filename, void *buffer, loff_t size,
+ loff_t *actwrite)
{
dir_entry *dentptr, *retdent;
__u32 startsect;
int cursect;
int ret = -1, name_len;
char l_filename[VFAT_MAXLEN_BYTES];
- int write_size = size;
+ *actwrite = size;
dir_curclust = 0;
if (read_bootsectandvi(&bs, &volinfo, &mydata->fatsize)) {
total_sector = bs.total_sect;
if (total_sector == 0)
- total_sector = cur_part_info.size;
+ total_sector = (int)cur_part_info.size; /* cast of lbaint_t */
if (mydata->fatsize == 32)
mydata->fatlength = bs.fat32_length;
}
mydata->fatbufnum = -1;
- mydata->fatbuf = malloc(FATBUFSIZE);
+ mydata->fat_dirty = 0;
+ mydata->fatbuf = memalign(ARCH_DMA_MINALIGN, FATBUFSIZE);
if (mydata->fatbuf == NULL) {
debug("Error: allocating memory\n");
return -1;
if (retdent) {
/* Update file size and start_cluster in a directory entry */
retdent->size = cpu_to_le32(size);
- start_cluster = FAT2CPU16(retdent->start);
- if (mydata->fatsize == 32)
- start_cluster |=
- (FAT2CPU16(retdent->starthi) << 16);
-
- ret = check_overflow(mydata, start_cluster, size);
- if (ret) {
- printf("Error: %ld overflow\n", size);
- goto exit;
- }
+ start_cluster = START(retdent);
+
+ if (start_cluster) {
+ if (size) {
+ ret = check_overflow(mydata, start_cluster,
+ size);
+ if (ret) {
+ printf("Error: %llu overflow\n", size);
+ goto exit;
+ }
+ }
- ret = clear_fatent(mydata, start_cluster);
- if (ret) {
- printf("Error: clearing FAT entries\n");
- goto exit;
- }
+ ret = clear_fatent(mydata, start_cluster);
+ if (ret) {
+ printf("Error: clearing FAT entries\n");
+ goto exit;
+ }
- ret = set_contents(mydata, retdent, buffer, size);
- if (ret < 0) {
- printf("Error: writing contents\n");
- goto exit;
- }
- write_size = ret;
- debug("attempt to write 0x%x bytes\n", write_size);
-
- /* Flush fat buffer */
- ret = flush_fat_buffer(mydata);
- if (ret) {
- printf("Error: flush fat buffer\n");
- goto exit;
- }
+ if (!size)
+ set_start_cluster(mydata, retdent, 0);
+ } else if (size) {
+ ret = start_cluster = find_empty_cluster(mydata);
+ if (ret < 0) {
+ printf("Error: finding empty cluster\n");
+ goto exit;
+ }
- /* Write directory table to device */
- ret = set_cluster(mydata, dir_curclust,
- get_dentfromdir_block,
- mydata->clust_size * mydata->sect_size);
- if (ret) {
- printf("Error: writing directory entry\n");
- goto exit;
+ ret = check_overflow(mydata, start_cluster, size);
+ if (ret) {
+ printf("Error: %llu overflow\n", size);
+ goto exit;
+ }
+
+ set_start_cluster(mydata, retdent, start_cluster);
}
} else {
/* Set short name to set alias checksum field in dir_slot */
set_name(empty_dentptr, filename);
fill_dir_slot(mydata, &empty_dentptr, filename);
- ret = start_cluster = find_empty_cluster(mydata);
- if (ret < 0) {
- printf("Error: finding empty cluster\n");
- goto exit;
- }
+ if (size) {
+ ret = start_cluster = find_empty_cluster(mydata);
+ if (ret < 0) {
+ printf("Error: finding empty cluster\n");
+ goto exit;
+ }
- ret = check_overflow(mydata, start_cluster, size);
- if (ret) {
- printf("Error: %ld overflow\n", size);
- goto exit;
+ ret = check_overflow(mydata, start_cluster, size);
+ if (ret) {
+ printf("Error: %llu overflow\n", size);
+ goto exit;
+ }
+ } else {
+ start_cluster = 0;
}
/* Set attribute as archieve for regular file */
fill_dentry(mydata, empty_dentptr, filename,
start_cluster, size, 0x20);
- ret = set_contents(mydata, empty_dentptr, buffer, size);
- if (ret < 0) {
- printf("Error: writing contents\n");
- goto exit;
- }
- write_size = ret;
- debug("attempt to write 0x%x bytes\n", write_size);
-
- /* Flush fat buffer */
- ret = flush_fat_buffer(mydata);
- if (ret) {
- printf("Error: flush fat buffer\n");
- goto exit;
- }
+ retdent = empty_dentptr;
+ }
- /* Write directory table to device */
- ret = set_cluster(mydata, dir_curclust,
- get_dentfromdir_block,
- mydata->clust_size * mydata->sect_size);
- if (ret) {
- printf("Error: writing directory entry\n");
- goto exit;
- }
+ ret = set_contents(mydata, retdent, buffer, size, actwrite);
+ if (ret < 0) {
+ printf("Error: writing contents\n");
+ goto exit;
+ }
+ debug("attempt to write 0x%llx bytes\n", *actwrite);
+
+ /* Flush fat buffer */
+ ret = flush_dirty_fat_buffer(mydata);
+ if (ret) {
+ printf("Error: flush fat buffer\n");
+ goto exit;
}
+ /* Write directory table to device */
+ ret = set_cluster(mydata, dir_curclust, get_dentfromdir_block,
+ mydata->clust_size * mydata->sect_size);
+ if (ret)
+ printf("Error: writing directory entry\n");
+
exit:
free(mydata->fatbuf);
- return ret < 0 ? ret : write_size;
+ return ret;
}
-int file_fat_write(const char *filename, void *buffer, unsigned long maxsize)
+int file_fat_write(const char *filename, void *buffer, loff_t offset,
+ loff_t maxsize, loff_t *actwrite)
{
+ if (offset != 0) {
+ printf("Error: non zero offset is currently not supported.\n");
+ return -1;
+ }
+
printf("writing %s\n", filename);
- return do_fat_write(filename, buffer, maxsize);
+ return do_fat_write(filename, buffer, maxsize, actwrite);
}