u-boot/fs/fat/fat_write.c

1493 lines
32 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0+
/*
* fat_write.c
*
* R/W (V)FAT 12/16/32 filesystem implementation by Donggeun Kim
*/
#include <common.h>
#include <command.h>
#include <config.h>
#include <fat.h>
#include <log.h>
#include <malloc.h>
#include <asm/byteorder.h>
#include <part.h>
#include <asm/cache.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++) {
*str = toupper(*str);
str++;
}
}
static int total_sector;
static int disk_write(__u32 block, __u32 nr_blocks, void *buf)
{
ulong ret;
if (!cur_dev)
return -1;
if (cur_part_info.start + block + nr_blocks >
cur_part_info.start + total_sector) {
printf("error: overflow occurs\n");
return -1;
}
ret = blk_dwrite(cur_dev, cur_part_info.start + block, nr_blocks, buf);
if (nr_blocks && ret == 0)
return -1;
return ret;
}
/*
* Set short name in directory entry
*/
static void set_name(dir_entry *dirent, const char *filename)
{
char s_name[VFAT_MAXLEN_BYTES];
char *period;
int period_location, len, i, ext_num;
if (filename == NULL)
return;
len = strlen(filename);
if (len == 0)
return;
strcpy(s_name, filename);
uppercase(s_name, len);
period = strchr(s_name, '.');
if (period == NULL) {
period_location = len;
ext_num = 0;
} else {
period_location = period - s_name;
ext_num = len - period_location - 1;
}
/* Pad spaces when the length of file name is shorter than eight */
if (period_location < 8) {
memcpy(dirent->name, s_name, period_location);
for (i = period_location; i < 8; i++)
dirent->name[i] = ' ';
} else if (period_location == 8) {
memcpy(dirent->name, s_name, period_location);
} else {
memcpy(dirent->name, s_name, 6);
dirent->name[6] = '~';
dirent->name[7] = '1';
}
if (ext_num < 3) {
memcpy(dirent->ext, s_name + period_location + 1, ext_num);
for (i = ext_num; i < 3; i++)
dirent->ext[i] = ' ';
} else
memcpy(dirent->ext, s_name + period_location + 1, 3);
debug("name : %s\n", dirent->name);
debug("ext : %s\n", dirent->ext);
}
/*
* Write fat buffer into block device
*/
static int flush_dirty_fat_buffer(fsdata *mydata)
{
int getsize = FATBUFBLOCKS;
__u32 fatlength = mydata->fatlength;
__u8 *bufptr = mydata->fatbuf;
__u32 startblock = mydata->fatbufnum * FATBUFBLOCKS;
debug("debug: evicting %d, dirty: %d\n", mydata->fatbufnum,
(int)mydata->fat_dirty);
if ((!mydata->fat_dirty) || (mydata->fatbufnum == -1))
return 0;
/* Cap length if fatlength is not a multiple of FATBUFBLOCKS */
if (startblock + getsize > fatlength)
getsize = fatlength - startblock;
startblock += mydata->fat_sect;
/* Write FAT buf */
if (disk_write(startblock, getsize, bufptr) < 0) {
debug("error: writing FAT blocks\n");
return -1;
}
if (mydata->fats == 2) {
/* Update corresponding second FAT blocks */
startblock += mydata->fatlength;
if (disk_write(startblock, getsize, bufptr) < 0) {
debug("error: writing second FAT blocks\n");
return -1;
}
}
mydata->fat_dirty = 0;
return 0;
}
/*
* Set the file name information from 'name' into 'slotptr',
*/
static int str2slot(dir_slot *slotptr, const char *name, int *idx)
{
int j, end_idx = 0;
for (j = 0; j <= 8; j += 2) {
if (name[*idx] == 0x00) {
slotptr->name0_4[j] = 0;
slotptr->name0_4[j + 1] = 0;
end_idx++;
goto name0_4;
}
slotptr->name0_4[j] = name[*idx];
(*idx)++;
end_idx++;
}
for (j = 0; j <= 10; j += 2) {
if (name[*idx] == 0x00) {
slotptr->name5_10[j] = 0;
slotptr->name5_10[j + 1] = 0;
end_idx++;
goto name5_10;
}
slotptr->name5_10[j] = name[*idx];
(*idx)++;
end_idx++;
}
for (j = 0; j <= 2; j += 2) {
if (name[*idx] == 0x00) {
slotptr->name11_12[j] = 0;
slotptr->name11_12[j + 1] = 0;
end_idx++;
goto name11_12;
}
slotptr->name11_12[j] = name[*idx];
(*idx)++;
end_idx++;
}
if (name[*idx] == 0x00)
return 1;
return 0;
/* Not used characters are filled with 0xff 0xff */
name0_4:
for (; end_idx < 5; end_idx++) {
slotptr->name0_4[end_idx * 2] = 0xff;
slotptr->name0_4[end_idx * 2 + 1] = 0xff;
}
end_idx = 5;
name5_10:
end_idx -= 5;
for (; end_idx < 6; end_idx++) {
slotptr->name5_10[end_idx * 2] = 0xff;
slotptr->name5_10[end_idx * 2 + 1] = 0xff;
}
end_idx = 11;
name11_12:
end_idx -= 11;
for (; end_idx < 2; end_idx++) {
slotptr->name11_12[end_idx * 2] = 0xff;
slotptr->name11_12[end_idx * 2 + 1] = 0xff;
}
return 1;
}
static int new_dir_table(fat_itr *itr);
static int flush_dir(fat_itr *itr);
/*
* Fill dir_slot entries with appropriate name, id, and attr
* 'itr' will point to a next entry
*/
static int
fill_dir_slot(fat_itr *itr, const char *l_name)
{
__u8 temp_dir_slot_buffer[MAX_LFN_SLOT * sizeof(dir_slot)];
dir_slot *slotptr = (dir_slot *)temp_dir_slot_buffer;
FAT: fix some issues in FAT write support code Writing a file to the FAT partition didn't work while a test using a CF card. The test was done on mpc5200 based board (powerpc). There is a number of problems in FAT write code: Compiler warning: fat_write.c: In function 'file_fat_write': fat_write.c:326: warning: 'counter' may be used uninitialized in this function fat_write.c:326: note: 'counter' was declared here 'l_filename' string is not terminated, so a file name with garbage at the end is used as a file name as shown by debug code. Return value of set_contents() is not checked properly so actually a file won't be written at all (as checked using 'fatls' after a write attempt with 'fatwrite' command). do_fat_write() doesn't return the number of written bytes if no error happened. However the return value of this function is used to show the number of written bytes in do_fat_fswrite(). The patch adds some debug code and fixes above mentioned problems and also fixes a typo in error output. NOTE: after a successful write to the FAT partition (under U-Boot) the partition was checked under Linux using fsck. The partition needed fixing FATs: -bash-3.2# fsck -a /dev/sda1 fsck 1.39 (29-May-2006) dosfsck 2.11, 12 Mar 2005, FAT32, LFN FATs differ but appear to be intact. Using first FAT. Performing changes. Signed-off-by: Anatolij Gustschin <agust@denx.de> Cc: Donggeun Kim <dg77.kim@samsung.com> Cc: Aaron Williams <Aaron.Williams@cavium.com> Acked-by: Donggeun Kim <dg77.kim@samsung.com>
2011-12-15 03:12:14 +00:00
__u8 counter = 0, checksum;
int idx = 0, ret;
/* Get short file name checksum value */
checksum = mkcksum(itr->dent->name, itr->dent->ext);
do {
memset(slotptr, 0x00, sizeof(dir_slot));
ret = str2slot(slotptr, l_name, &idx);
slotptr->id = ++counter;
slotptr->attr = ATTR_VFAT;
slotptr->alias_checksum = checksum;
slotptr++;
} while (ret == 0);
slotptr--;
slotptr->id |= LAST_LONG_ENTRY_MASK;
while (counter >= 1) {
memcpy(itr->dent, slotptr, sizeof(dir_slot));
slotptr--;
counter--;
if (itr->remaining == 0)
flush_dir(itr);
/* allocate a cluster for more entries */
if (!fat_itr_next(itr))
if (!itr->dent &&
(!itr->is_root || itr->fsdata->fatsize == 32) &&
new_dir_table(itr))
return -1;
}
return 0;
}
/*
* Set the entry at index 'entry' in a FAT (12/16/32) table.
*/
static int set_fatent_value(fsdata *mydata, __u32 entry, __u32 entry_value)
{
__u32 bufnum, offset, off16;
__u16 val1, val2;
switch (mydata->fatsize) {
case 32:
bufnum = entry / FAT32BUFSIZE;
offset = entry - bufnum * FAT32BUFSIZE;
break;
case 16:
bufnum = entry / FAT16BUFSIZE;
offset = entry - bufnum * FAT16BUFSIZE;
break;
case 12:
bufnum = entry / FAT12BUFSIZE;
offset = entry - bufnum * FAT12BUFSIZE;
break;
default:
/* Unsupported FAT size */
return -1;
}
/* Read a new block of FAT entries into the cache. */
if (bufnum != mydata->fatbufnum) {
int getsize = FATBUFBLOCKS;
__u8 *bufptr = mydata->fatbuf;
__u32 fatlength = mydata->fatlength;
__u32 startblock = bufnum * FATBUFBLOCKS;
/* Cap length if fatlength is not a multiple of FATBUFBLOCKS */
if (startblock + getsize > fatlength)
getsize = fatlength - startblock;
if (flush_dirty_fat_buffer(mydata) < 0)
return -1;
startblock += mydata->fat_sect;
if (disk_read(startblock, getsize, bufptr) < 0) {
debug("Error reading FAT blocks\n");
return -1;
}
mydata->fatbufnum = bufnum;
}
/* Mark as dirty */
mydata->fat_dirty = 1;
/* Set the actual entry */
switch (mydata->fatsize) {
case 32:
((__u32 *) mydata->fatbuf)[offset] = cpu_to_le32(entry_value);
break;
case 16:
((__u16 *) mydata->fatbuf)[offset] = cpu_to_le16(entry_value);
break;
case 12:
off16 = (offset * 3) / 4;
switch (offset & 0x3) {
case 0:
val1 = cpu_to_le16(entry_value) & 0xfff;
((__u16 *)mydata->fatbuf)[off16] &= ~0xfff;
((__u16 *)mydata->fatbuf)[off16] |= val1;
break;
case 1:
val1 = cpu_to_le16(entry_value) & 0xf;
val2 = (cpu_to_le16(entry_value) >> 4) & 0xff;
((__u16 *)mydata->fatbuf)[off16] &= ~0xf000;
((__u16 *)mydata->fatbuf)[off16] |= (val1 << 12);
((__u16 *)mydata->fatbuf)[off16 + 1] &= ~0xff;
((__u16 *)mydata->fatbuf)[off16 + 1] |= val2;
break;
case 2:
val1 = cpu_to_le16(entry_value) & 0xff;
val2 = (cpu_to_le16(entry_value) >> 8) & 0xf;
((__u16 *)mydata->fatbuf)[off16] &= ~0xff00;
((__u16 *)mydata->fatbuf)[off16] |= (val1 << 8);
((__u16 *)mydata->fatbuf)[off16 + 1] &= ~0xf;
((__u16 *)mydata->fatbuf)[off16 + 1] |= val2;
break;
case 3:
val1 = cpu_to_le16(entry_value) & 0xfff;
((__u16 *)mydata->fatbuf)[off16] &= ~0xfff0;
((__u16 *)mydata->fatbuf)[off16] |= (val1 << 4);
break;
default:
break;
}
break;
default:
return -1;
}
return 0;
}
/*
* Determine the next free cluster after 'entry' in a FAT (12/16/32) table
* and link it to 'entry'. EOC marker is not set on returned entry.
*/
static __u32 determine_fatent(fsdata *mydata, __u32 entry)
{
__u32 next_fat, next_entry = entry + 1;
while (1) {
next_fat = get_fatent(mydata, next_entry);
if (next_fat == 0) {
/* found free entry, link to entry */
set_fatent_value(mydata, entry, next_entry);
break;
}
next_entry++;
}
debug("FAT%d: entry: %08x, entry_value: %04x\n",
mydata->fatsize, entry, next_entry);
return next_entry;
}
/**
* set_sectors() - write data to sectors
*
* Write 'size' bytes from 'buffer' into the specified sector.
*
* @mydata: data to be written
* @startsect: sector to be written to
* @buffer: data to be written
* @size: bytes to be written (but not more than the size of a cluster)
* Return: 0 on success, -1 otherwise
*/
static int
set_sectors(fsdata *mydata, u32 startsect, u8 *buffer, u32 size)
{
u32 nsects = 0;
int ret;
debug("startsect: %d\n", startsect);
if ((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1)) {
ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size);
debug("FAT: Misaligned buffer address (%p)\n", buffer);
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;
}
buffer += mydata->sect_size;
size -= mydata->sect_size;
}
} else if (size >= mydata->sect_size) {
nsects = size / mydata->sect_size;
ret = disk_write(startsect, nsects, buffer);
if (ret != nsects) {
debug("Error writing data (got %d)\n", ret);
return -1;
}
startsect += nsects;
buffer += nsects * mydata->sect_size;
size -= nsects * mydata->sect_size;
}
if (size) {
ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size);
/* Do not leak content of stack */
memset(tmpbuf, 0, 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;
}
/**
* set_cluster() - write data to cluster
*
* Write 'size' bytes from 'buffer' into the specified cluster.
*
* @mydata: data to be written
* @clustnum: cluster to be written to
* @buffer: data to be written
* @size: bytes to be written (but not more than the size of a cluster)
* Return: 0 on success, -1 otherwise
*/
static int
set_cluster(fsdata *mydata, u32 clustnum, u8 *buffer, u32 size)
{
return set_sectors(mydata, clust_to_sect(mydata, clustnum),
buffer, size);
}
static int
flush_dir(fat_itr *itr)
{
fsdata *mydata = itr->fsdata;
u32 startsect, sect_offset, nsects;
if (!itr->is_root || mydata->fatsize == 32)
return set_cluster(mydata, itr->clust, itr->block,
mydata->clust_size * mydata->sect_size);
sect_offset = itr->clust * mydata->clust_size;
startsect = mydata->rootdir_sect + sect_offset;
/* do not write past the end of rootdir */
nsects = min_t(u32, mydata->clust_size,
mydata->rootdir_size - sect_offset);
return set_sectors(mydata, startsect, itr->block,
nsects * mydata->sect_size);
}
static __u8 tmpbuf_cluster[MAX_CLUSTSIZE] __aligned(ARCH_DMA_MINALIGN);
/*
* Read and modify data on existing and consecutive cluster blocks
*/
static int
get_set_cluster(fsdata *mydata, __u32 clustnum, loff_t pos, __u8 *buffer,
loff_t size, loff_t *gotsize)
{
unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
__u32 startsect;
loff_t wsize;
int clustcount, i, ret;
*gotsize = 0;
if (!size)
return 0;
assert(pos < bytesperclust);
startsect = clust_to_sect(mydata, clustnum);
debug("clustnum: %d, startsect: %d, pos: %lld\n",
clustnum, startsect, pos);
/* partial write at beginning */
if (pos) {
wsize = min(bytesperclust - pos, size);
ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster);
if (ret != mydata->clust_size) {
debug("Error reading data (got %d)\n", ret);
return -1;
}
memcpy(tmpbuf_cluster + pos, buffer, wsize);
ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster);
if (ret != mydata->clust_size) {
debug("Error writing data (got %d)\n", ret);
return -1;
}
size -= wsize;
buffer += wsize;
*gotsize += wsize;
startsect += mydata->clust_size;
if (!size)
return 0;
}
/* full-cluster write */
if (size >= bytesperclust) {
clustcount = lldiv(size, bytesperclust);
if (!((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1))) {
wsize = clustcount * bytesperclust;
ret = disk_write(startsect,
clustcount * mydata->clust_size,
buffer);
if (ret != clustcount * mydata->clust_size) {
debug("Error writing data (got %d)\n", ret);
return -1;
}
size -= wsize;
buffer += wsize;
*gotsize += wsize;
startsect += clustcount * mydata->clust_size;
} else {
for (i = 0; i < clustcount; i++) {
memcpy(tmpbuf_cluster, buffer, bytesperclust);
ret = disk_write(startsect,
mydata->clust_size,
tmpbuf_cluster);
if (ret != mydata->clust_size) {
debug("Error writing data (got %d)\n",
ret);
return -1;
}
size -= bytesperclust;
buffer += bytesperclust;
*gotsize += bytesperclust;
startsect += mydata->clust_size;
}
}
}
/* partial write at end */
if (size) {
wsize = size;
ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster);
if (ret != mydata->clust_size) {
debug("Error reading data (got %d)\n", ret);
return -1;
}
memcpy(tmpbuf_cluster, buffer, wsize);
ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster);
if (ret != mydata->clust_size) {
debug("Error writing data (got %d)\n", ret);
return -1;
}
size -= wsize;
buffer += wsize;
*gotsize += wsize;
}
assert(!size);
return 0;
}
/*
* Find the first empty cluster
*/
static int find_empty_cluster(fsdata *mydata)
{
__u32 fat_val, entry = 3;
while (1) {
fat_val = get_fatent(mydata, entry);
if (fat_val == 0)
break;
entry++;
}
return entry;
}
/*
* Allocate a cluster for additional directory entries
*/
static int new_dir_table(fat_itr *itr)
{
fsdata *mydata = itr->fsdata;
int dir_newclust = 0;
unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
dir_newclust = find_empty_cluster(mydata);
set_fatent_value(mydata, itr->clust, dir_newclust);
if (mydata->fatsize == 32)
set_fatent_value(mydata, dir_newclust, 0xffffff8);
else if (mydata->fatsize == 16)
set_fatent_value(mydata, dir_newclust, 0xfff8);
else if (mydata->fatsize == 12)
set_fatent_value(mydata, dir_newclust, 0xff8);
itr->clust = dir_newclust;
itr->next_clust = dir_newclust;
if (flush_dirty_fat_buffer(mydata) < 0)
return -1;
memset(itr->block, 0x00, bytesperclust);
itr->dent = (dir_entry *)itr->block;
itr->last_cluster = 1;
itr->remaining = bytesperclust / sizeof(dir_entry) - 1;
return 0;
}
/*
* Set empty cluster from 'entry' to the end of a file
*/
static int clear_fatent(fsdata *mydata, __u32 entry)
{
__u32 fat_val;
while (!CHECK_CLUST(entry, mydata->fatsize)) {
fat_val = get_fatent(mydata, entry);
if (fat_val != 0)
set_fatent_value(mydata, entry, 0);
else
break;
entry = fat_val;
}
/* Flush fat buffer */
if (flush_dirty_fat_buffer(mydata) < 0)
return -1;
return 0;
}
/*
* Set start cluster in directory entry
*/
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);
}
/*
* Check whether adding a file makes the file system to
* exceed the size of the block device
* Return -1 when overflow occurs, otherwise return 0
*/
static int check_overflow(fsdata *mydata, __u32 clustnum, loff_t size)
{
__u32 startsect, sect_num, offset;
if (clustnum > 0)
startsect = clust_to_sect(mydata, clustnum);
else
startsect = mydata->rootdir_sect;
sect_num = div_u64_rem(size, mydata->sect_size, &offset);
if (offset != 0)
sect_num++;
if (startsect + sect_num > total_sector)
return -1;
return 0;
}
/*
* Write at most 'maxsize' bytes from 'buffer' into
* the file associated with 'dentptr'
* 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, loff_t pos, __u8 *buffer,
loff_t maxsize, loff_t *gotsize)
{
unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
__u32 curclust = START(dentptr);
__u32 endclust = 0, newclust = 0;
u64 cur_pos, filesize;
loff_t offset, actsize, wsize;
*gotsize = 0;
filesize = pos + maxsize;
debug("%llu bytes\n", filesize);
if (!filesize) {
if (!curclust)
return 0;
if (!CHECK_CLUST(curclust, mydata->fatsize) ||
IS_LAST_CLUST(curclust, mydata->fatsize)) {
clear_fatent(mydata, curclust);
set_start_cluster(mydata, dentptr, 0);
return 0;
}
debug("curclust: 0x%x\n", curclust);
debug("Invalid FAT entry\n");
return -1;
}
if (!curclust) {
assert(pos == 0);
goto set_clusters;
}
/* go to cluster at pos */
cur_pos = bytesperclust;
while (1) {
if (pos <= cur_pos)
break;
if (IS_LAST_CLUST(curclust, mydata->fatsize))
break;
newclust = get_fatent(mydata, curclust);
if (!IS_LAST_CLUST(newclust, mydata->fatsize) &&
CHECK_CLUST(newclust, mydata->fatsize)) {
debug("curclust: 0x%x\n", curclust);
debug("Invalid FAT entry\n");
return -1;
}
cur_pos += bytesperclust;
curclust = newclust;
}
if (IS_LAST_CLUST(curclust, mydata->fatsize)) {
assert(pos == cur_pos);
goto set_clusters;
}
assert(pos < cur_pos);
cur_pos -= bytesperclust;
/* overwrite */
assert(IS_LAST_CLUST(curclust, mydata->fatsize) ||
!CHECK_CLUST(curclust, mydata->fatsize));
while (1) {
/* search for allocated consecutive clusters */
actsize = bytesperclust;
endclust = curclust;
while (1) {
if (filesize <= (cur_pos + actsize))
break;
newclust = get_fatent(mydata, endclust);
fat: write: fix broken write to fragmented files The code for handing file overwrite incorrectly assumed that the file on disk is always contiguous. This resulted in corrupting disk structure every time when write to existing fragmented file happened. Fix this by adding proper check for cluster discontinuity and adjust chunk size on each partial write. Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com> This patch partially fixes the issue revealed by the following test script: --->8-fat_test1.sh--- #!/bin/bash make sandbox_defconfig make dd if=/dev/zero of=/tmp/10M.img bs=1024 count=10k mkfs.vfat -v /tmp/10M.img cat >/tmp/cmds <<EOF x host bind 0 /tmp/10M.img fatls host 0 mw 0x1000000 0x0a434241 0x1000 # "ABC\n" mw 0x1100000 0x0a464544 0x8000 # "DEF\n" fatwrite host 0 0x1000000 file0001.raw 0x1000 fatwrite host 0 0x1000000 file0002.raw 0x1000 fatwrite host 0 0x1000000 file0003.raw 0x1000 fatwrite host 0 0x1000000 file0004.raw 0x1000 fatwrite host 0 0x1000000 file0005.raw 0x1000 fatrm host 0 file0002.raw fatrm host 0 file0004.raw fatls host 0 fatwrite host 0 0x1100000 file0007.raw 0x4000 fatwrite host 0 0x1100000 file0007.raw 0x4000 reset EOF ./u-boot </tmp/cmds #verify rm -r /tmp/result /tmp/model mkdir /tmp/result mkdir /tmp/model yes ABC | head -c 4096 >/tmp/model/file0001.raw yes ABC | head -c 4096 >/tmp/model/file0003.raw yes ABC | head -c 4096 >/tmp/model/file0005.raw yes DEF | head -c 16384 >/tmp/model/file0007.raw mcopy -n -i /tmp/10M.img ::file0001.raw /tmp/result mcopy -n -i /tmp/10M.img ::file0003.raw /tmp/result mcopy -n -i /tmp/10M.img ::file0005.raw /tmp/result mcopy -n -i /tmp/10M.img ::file0007.raw /tmp/result hd /tmp/10M.img if diff -urq /tmp/model /tmp/result then echo Test okay else echo Test fail fi --->8--- Overwritting a discontiguous test file (file0007.raw) no longer causes corruption to file0003.raw, which's data lies between the chunks of the test file. The amount of data written to disk is still incorrect, what causes damage to the file (file0005.raw), which's data lies next to the test file. This will be fixed by the next patch. Feel free to prepare a proper sandbox/py_test based tests based on the provided test scripts.
2019-12-02 11:11:13 +00:00
if (newclust != endclust + 1)
break;
if (IS_LAST_CLUST(newclust, mydata->fatsize))
break;
if (CHECK_CLUST(newclust, mydata->fatsize)) {
debug("curclust: 0x%x\n", curclust);
debug("Invalid FAT entry\n");
return -1;
}
actsize += bytesperclust;
endclust = newclust;
}
/* overwrite to <curclust..endclust> */
if (pos < cur_pos)
offset = 0;
else
offset = pos - cur_pos;
fat: write: adjust data written in each partial write The code for handing file overwrite incorrectly calculated the amount of data to write when writing to the last non-cluster aligned chunk. Fix this by ensuring that no more data than the 'filesize' is written to disk. While touching min()-based calculations, change it to type-safe min_t() function. Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com> This patch finally fixes the issue revealed by the test script from the previous patch. The correctness of the change has been also verified by the following additional test scripts: --->8-fat_test2.sh--- #!/bin/bash make sandbox_defconfig make dd if=/dev/zero of=/tmp/10M.img bs=1024 count=10k mkfs.vfat -v /tmp/10M.img cat >/tmp/cmds <<EOF x host bind 0 /tmp/10M.img fatls host 0 mw 0x1000000 0x0a434241 0x1000 # "ABC\n" mw 0x1100000 0x0a464544 0x8000 # "DEF\n" fatwrite host 0 0x1000000 file0001.raw 0x1000 fatwrite host 0 0x1000000 file0002.raw 0x1000 fatwrite host 0 0x1000000 file0003.raw 0x1000 fatwrite host 0 0x1000000 file0004.raw 0x1000 fatwrite host 0 0x1000000 file0005.raw 0x1000 fatrm host 0 file0002.raw fatrm host 0 file0004.raw fatls host 0 fatwrite host 0 0x1100000 file0007.raw 0x2000 fatwrite host 0 0x1100000 file0007.raw 0x1f00 reset EOF ./u-boot </tmp/cmds #verify rm -r /tmp/result /tmp/model mkdir /tmp/result mkdir /tmp/model yes ABC | head -c 4096 >/tmp/model/file0001.raw yes ABC | head -c 4096 >/tmp/model/file0003.raw yes ABC | head -c 4096 >/tmp/model/file0005.raw yes DEF | head -c 7936 >/tmp/model/file0007.raw mcopy -n -i /tmp/10M.img ::file0001.raw /tmp/result mcopy -n -i /tmp/10M.img ::file0003.raw /tmp/result mcopy -n -i /tmp/10M.img ::file0005.raw /tmp/result mcopy -n -i /tmp/10M.img ::file0007.raw /tmp/result hd /tmp/10M.img if diff -urq /tmp/model /tmp/result then echo Test okay else echo Test fail fi --->8-fat_test3.sh--- #!/bin/bash make sandbox_defconfig make dd if=/dev/zero of=/tmp/10M.img bs=1024 count=10k mkfs.vfat -v /tmp/10M.img cat >/tmp/cmds <<EOF x host bind 0 /tmp/10M.img fatls host 0 mw 0x1000000 0x0a434241 0x1000 # "ABC\n" mw 0x1100000 0x0a464544 0x8000 # "DEF\n" fatwrite host 0 0x1000000 file0001.raw 0x1000 fatwrite host 0 0x1000000 file0002.raw 0x1000 fatwrite host 0 0x1000000 file0003.raw 0x1000 fatwrite host 0 0x1000000 file0004.raw 0x1000 fatwrite host 0 0x1000000 file0005.raw 0x1000 fatrm host 0 file0002.raw fatrm host 0 file0004.raw fatls host 0 fatwrite host 0 0x1100000 file0007.raw 0x2000 fatwrite host 0 0x1100000 file0007.raw 0x2100 reset EOF ./u-boot </tmp/cmds #verify rm -r /tmp/result /tmp/model mkdir /tmp/result mkdir /tmp/model yes ABC | head -c 4096 >/tmp/model/file0001.raw yes ABC | head -c 4096 >/tmp/model/file0003.raw yes ABC | head -c 4096 >/tmp/model/file0005.raw yes DEF | head -c 8448 >/tmp/model/file0007.raw mcopy -n -i /tmp/10M.img ::file0001.raw /tmp/result mcopy -n -i /tmp/10M.img ::file0003.raw /tmp/result mcopy -n -i /tmp/10M.img ::file0005.raw /tmp/result mcopy -n -i /tmp/10M.img ::file0007.raw /tmp/result hd /tmp/10M.img if diff -urq /tmp/model /tmp/result then echo Test okay else echo Test fail fi --->8-fat_test4.sh--- #!/bin/bash make sandbox_defconfig make dd if=/dev/zero of=/tmp/10M.img bs=1024 count=10k mkfs.vfat -v /tmp/10M.img cat >/tmp/cmds <<EOF x host bind 0 /tmp/10M.img fatls host 0 mw 0x1000000 0x0a434241 0x1000 # "ABC\n" mw 0x1100000 0x0a464544 0x8000 # "DEF\n" mw 0x1200000 0x0a494847 0x8000 # "GHI\n" fatwrite host 0 0x1000000 file0001.raw 0x1000 fatwrite host 0 0x1000000 file0002.raw 0x1000 fatwrite host 0 0x1000000 file0003.raw 0x1000 fatwrite host 0 0x1000000 file0004.raw 0x1000 fatwrite host 0 0x1000000 file0005.raw 0x1000 fatrm host 0 file0002.raw fatrm host 0 file0004.raw fatls host 0 fatwrite host 0 0x1100000 file0007.raw 0x900 fatwrite host 0 0x1200000 file0007.raw 0x900 0x900 fatwrite host 0 0x1100000 file0007.raw 0x900 0x1200 fatwrite host 0 0x1200000 file0007.raw 0x900 0x1b00 reset EOF ./u-boot </tmp/cmds #verify rm -r /tmp/result /tmp/model mkdir /tmp/result mkdir /tmp/model yes ABC | head -c 4096 >/tmp/model/file0001.raw yes ABC | head -c 4096 >/tmp/model/file0003.raw yes ABC | head -c 4096 >/tmp/model/file0005.raw yes DEF | head -c 2304 >/tmp/model/file0007.raw yes GHI | head -c 2304 >>/tmp/model/file0007.raw yes DEF | head -c 2304 >>/tmp/model/file0007.raw yes GHI | head -c 2304 >>/tmp/model/file0007.raw mcopy -n -i /tmp/10M.img ::file0001.raw /tmp/result mcopy -n -i /tmp/10M.img ::file0003.raw /tmp/result mcopy -n -i /tmp/10M.img ::file0005.raw /tmp/result mcopy -n -i /tmp/10M.img ::file0007.raw /tmp/result hd /tmp/10M.img if diff -urq /tmp/model /tmp/result then echo Test okay else echo Test fail fi --->8--- Feel free to prepare a proper sandbox/py_test based tests based on the provided test scripts.
2019-12-02 11:11:14 +00:00
wsize = min_t(unsigned long long, actsize, filesize - cur_pos);
wsize -= offset;
if (get_set_cluster(mydata, curclust, offset,
buffer, wsize, &actsize)) {
printf("Error get-and-setting cluster\n");
return -1;
}
buffer += wsize;
*gotsize += wsize;
cur_pos += offset + wsize;
if (filesize <= cur_pos)
break;
if (IS_LAST_CLUST(newclust, mydata->fatsize))
/* no more clusters */
break;
curclust = newclust;
}
if (filesize <= cur_pos) {
/* no more write */
newclust = get_fatent(mydata, endclust);
if (!IS_LAST_CLUST(newclust, mydata->fatsize)) {
/* truncate the rest */
clear_fatent(mydata, newclust);
/* Mark end of file in FAT */
if (mydata->fatsize == 12)
newclust = 0xfff;
else if (mydata->fatsize == 16)
newclust = 0xffff;
else if (mydata->fatsize == 32)
newclust = 0xfffffff;
set_fatent_value(mydata, endclust, newclust);
}
return 0;
}
curclust = endclust;
filesize -= cur_pos;
assert(!do_div(cur_pos, bytesperclust));
set_clusters:
/* allocate and write */
assert(!pos);
/* Assure that curclust is valid */
if (!curclust) {
curclust = find_empty_cluster(mydata);
set_start_cluster(mydata, dentptr, curclust);
} else {
newclust = get_fatent(mydata, curclust);
if (IS_LAST_CLUST(newclust, mydata->fatsize)) {
newclust = determine_fatent(mydata, curclust);
set_fatent_value(mydata, curclust, newclust);
curclust = newclust;
} else {
debug("error: something wrong\n");
return -1;
}
}
/* TODO: already partially written */
if (check_overflow(mydata, curclust, filesize)) {
printf("Error: no space left: %llu\n", filesize);
return -1;
}
actsize = bytesperclust;
endclust = curclust;
do {
/* search for consecutive clusters */
while (actsize < filesize) {
newclust = determine_fatent(mydata, endclust);
if ((newclust - 1) != endclust)
/* write to <curclust..endclust> */
goto getit;
if (CHECK_CLUST(newclust, mydata->fatsize)) {
debug("newclust: 0x%x\n", newclust);
debug("Invalid FAT entry\n");
return 0;
}
endclust = newclust;
actsize += bytesperclust;
}
/* set remaining bytes */
actsize = filesize;
if (set_cluster(mydata, curclust, buffer, (u32)actsize) != 0) {
debug("error: writing cluster\n");
return -1;
}
*gotsize += actsize;
/* Mark end of file in FAT */
if (mydata->fatsize == 12)
newclust = 0xfff;
else if (mydata->fatsize == 16)
newclust = 0xffff;
else if (mydata->fatsize == 32)
newclust = 0xfffffff;
set_fatent_value(mydata, endclust, newclust);
return 0;
getit:
if (set_cluster(mydata, curclust, buffer, (u32)actsize) != 0) {
debug("error: writing cluster\n");
return -1;
}
*gotsize += actsize;
filesize -= actsize;
buffer += actsize;
if (CHECK_CLUST(newclust, mydata->fatsize)) {
debug("newclust: 0x%x\n", newclust);
debug("Invalid FAT entry\n");
return 0;
}
actsize = bytesperclust;
curclust = endclust = newclust;
} while (1);
return 0;
}
/*
* 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;
set_name(dentptr, filename);
}
/*
* Find a directory entry based on filename or start cluster number
* If the directory entry is not found,
* the new position for writing a directory entry will be returned
*/
static dir_entry *find_directory_entry(fat_itr *itr, char *filename)
{
int match = 0;
while (fat_itr_next(itr)) {
/* check both long and short name: */
if (!strcasecmp(filename, itr->name))
match = 1;
else if (itr->name != itr->s_name &&
!strcasecmp(filename, itr->s_name))
match = 1;
if (!match)
continue;
if (itr->dent->name[0] == '\0')
return NULL;
else
return itr->dent;
}
/* allocate a cluster for more entries */
if (!itr->dent &&
(!itr->is_root || itr->fsdata->fatsize == 32) &&
new_dir_table(itr))
/* indicate that allocating dent failed */
itr->dent = NULL;
return NULL;
}
static int split_filename(char *filename, char **dirname, char **basename)
{
char *p, *last_slash, *last_slash_cont;
again:
p = filename;
last_slash = NULL;
last_slash_cont = NULL;
while (*p) {
if (ISDIRDELIM(*p)) {
last_slash = p;
last_slash_cont = p;
/* continuous slashes */
while (ISDIRDELIM(*p))
last_slash_cont = p++;
if (!*p)
break;
}
p++;
}
if (last_slash) {
if (last_slash_cont == (filename + strlen(filename) - 1)) {
/* remove trailing slashes */
*last_slash = '\0';
goto again;
}
if (last_slash == filename) {
/* avoid ""(null) directory */
*dirname = "/";
} else {
*last_slash = '\0';
*dirname = filename;
}
*last_slash_cont = '\0';
*basename = last_slash_cont + 1;
} else {
*dirname = "/"; /* root by default */
*basename = filename;
}
return 0;
}
/**
* normalize_longname() - check long file name and convert to lower case
*
* We assume here that the FAT file system is using an 8bit code page.
* Linux typically uses CP437, EDK2 assumes CP1250.
*
* @l_filename: preallocated buffer receiving the normalized name
* @filename: filename to normalize
* Return: 0 on success, -1 on failure
*/
static int normalize_longname(char *l_filename, const char *filename)
{
const char *p, illegal[] = "<>:\"/\\|?*";
if (strlen(filename) >= VFAT_MAXLEN_BYTES)
return -1;
for (p = filename; *p; ++p) {
if ((unsigned char)*p < 0x20)
return -1;
if (strchr(illegal, *p))
return -1;
}
strcpy(l_filename, filename);
downcase(l_filename, VFAT_MAXLEN_BYTES);
return 0;
}
int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
loff_t size, loff_t *actwrite)
{
dir_entry *retdent;
fsdata datablock = { .fatbuf = NULL, };
fsdata *mydata = &datablock;
fat_itr *itr = NULL;
int ret = -1;
char *filename_copy, *parent, *basename;
char l_filename[VFAT_MAXLEN_BYTES];
debug("writing %s\n", filename);
filename_copy = strdup(filename);
if (!filename_copy)
return -ENOMEM;
split_filename(filename_copy, &parent, &basename);
if (!strlen(basename)) {
ret = -EINVAL;
goto exit;
}
filename = basename;
if (normalize_longname(l_filename, filename)) {
printf("FAT: illegal filename (%s)\n", filename);
ret = -EINVAL;
goto exit;
}
itr = malloc_cache_aligned(sizeof(fat_itr));
if (!itr) {
ret = -ENOMEM;
goto exit;
}
ret = fat_itr_root(itr, &datablock);
if (ret)
goto exit;
total_sector = datablock.total_sect;
ret = fat_itr_resolve(itr, parent, TYPE_DIR);
if (ret) {
printf("%s: doesn't exist (%d)\n", parent, ret);
goto exit;
}
retdent = find_directory_entry(itr, l_filename);
if (retdent) {
if (fat_itr_isdir(itr)) {
ret = -EISDIR;
goto exit;
}
/* A file exists */
if (pos == -1)
/* Append to the end */
pos = FAT2CPU32(retdent->size);
if (pos > retdent->size) {
/* No hole allowed */
ret = -EINVAL;
goto exit;
}
/* Update file size in a directory entry */
retdent->size = cpu_to_le32(pos + size);
} else {
/* Create a new file */
if (itr->is_root) {
/* root dir cannot have "." or ".." */
if (!strcmp(l_filename, ".") ||
!strcmp(l_filename, "..")) {
ret = -EINVAL;
goto exit;
}
}
if (!itr->dent) {
printf("Error: allocating new dir entry\n");
ret = -EIO;
goto exit;
}
if (pos) {
/* No hole allowed */
ret = -EINVAL;
goto exit;
}
memset(itr->dent, 0, sizeof(*itr->dent));
/* Calculate checksum for short name */
set_name(itr->dent, filename);
/* Set long name entries */
if (fill_dir_slot(itr, filename)) {
ret = -EIO;
goto exit;
}
/* Set short name entry */
fill_dentry(itr->fsdata, itr->dent, filename, 0, size, 0x20);
retdent = itr->dent;
}
ret = set_contents(mydata, retdent, pos, buffer, size, actwrite);
if (ret < 0) {
printf("Error: writing contents\n");
ret = -EIO;
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");
ret = -EIO;
goto exit;
}
/* Write directory table to device */
ret = flush_dir(itr);
if (ret) {
printf("Error: writing directory entry\n");
ret = -EIO;
}
exit:
free(filename_copy);
free(mydata->fatbuf);
free(itr);
return ret;
}
int file_fat_write(const char *filename, void *buffer, loff_t offset,
loff_t maxsize, loff_t *actwrite)
{
return file_fat_write_at(filename, offset, buffer, maxsize, actwrite);
}
static int fat_dir_entries(fat_itr *itr)
{
fat_itr *dirs;
fsdata fsdata = { .fatbuf = NULL, }, *mydata = &fsdata;
/* for FATBUFSIZE */
int count;
dirs = malloc_cache_aligned(sizeof(fat_itr));
if (!dirs) {
debug("Error: allocating memory\n");
count = -ENOMEM;
goto exit;
}
/* duplicate fsdata */
fat_itr_child(dirs, itr);
fsdata = *dirs->fsdata;
/* allocate local fat buffer */
fsdata.fatbuf = malloc_cache_aligned(FATBUFSIZE);
if (!fsdata.fatbuf) {
debug("Error: allocating memory\n");
count = -ENOMEM;
goto exit;
}
fsdata.fatbufnum = -1;
dirs->fsdata = &fsdata;
for (count = 0; fat_itr_next(dirs); count++)
;
exit:
free(fsdata.fatbuf);
free(dirs);
return count;
}
static int delete_dentry(fat_itr *itr)
{
fsdata *mydata = itr->fsdata;
dir_entry *dentptr = itr->dent;
/* free cluster blocks */
clear_fatent(mydata, START(dentptr));
if (flush_dirty_fat_buffer(mydata) < 0) {
printf("Error: flush fat buffer\n");
return -EIO;
}
/*
* update a directory entry
* TODO:
* - long file name support
* - find and mark the "new" first invalid entry as name[0]=0x00
*/
memset(dentptr, 0, sizeof(*dentptr));
dentptr->name[0] = 0xe5;
if (flush_dir(itr)) {
printf("error: writing directory entry\n");
return -EIO;
}
return 0;
}
int fat_unlink(const char *filename)
{
fsdata fsdata = { .fatbuf = NULL, };
fat_itr *itr = NULL;
int n_entries, ret;
char *filename_copy, *dirname, *basename;
filename_copy = strdup(filename);
if (!filename_copy) {
printf("Error: allocating memory\n");
ret = -ENOMEM;
goto exit;
}
split_filename(filename_copy, &dirname, &basename);
if (!strcmp(dirname, "/") && !strcmp(basename, "")) {
printf("Error: cannot remove root\n");
ret = -EINVAL;
goto exit;
}
itr = malloc_cache_aligned(sizeof(fat_itr));
if (!itr) {
printf("Error: allocating memory\n");
ret = -ENOMEM;
goto exit;
}
ret = fat_itr_root(itr, &fsdata);
if (ret)
goto exit;
total_sector = fsdata.total_sect;
ret = fat_itr_resolve(itr, dirname, TYPE_DIR);
if (ret) {
printf("%s: doesn't exist (%d)\n", dirname, ret);
ret = -ENOENT;
goto exit;
}
if (!find_directory_entry(itr, basename)) {
printf("%s: doesn't exist\n", basename);
ret = -ENOENT;
goto exit;
}
if (fat_itr_isdir(itr)) {
n_entries = fat_dir_entries(itr);
if (n_entries < 0) {
ret = n_entries;
goto exit;
}
if (n_entries > 2) {
printf("Error: directory is not empty: %d\n",
n_entries);
ret = -EINVAL;
goto exit;
}
}
ret = delete_dentry(itr);
exit:
free(fsdata.fatbuf);
free(itr);
free(filename_copy);
return ret;
}
int fat_mkdir(const char *new_dirname)
{
dir_entry *retdent;
fsdata datablock = { .fatbuf = NULL, };
fsdata *mydata = &datablock;
fat_itr *itr = NULL;
char *dirname_copy, *parent, *dirname;
char l_dirname[VFAT_MAXLEN_BYTES];
int ret = -1;
loff_t actwrite;
unsigned int bytesperclust;
dir_entry *dotdent = NULL;
dirname_copy = strdup(new_dirname);
if (!dirname_copy)
goto exit;
split_filename(dirname_copy, &parent, &dirname);
if (!strlen(dirname)) {
ret = -EINVAL;
goto exit;
}
if (normalize_longname(l_dirname, dirname)) {
printf("FAT: illegal filename (%s)\n", dirname);
ret = -EINVAL;
goto exit;
}
itr = malloc_cache_aligned(sizeof(fat_itr));
if (!itr) {
ret = -ENOMEM;
goto exit;
}
ret = fat_itr_root(itr, &datablock);
if (ret)
goto exit;
total_sector = datablock.total_sect;
ret = fat_itr_resolve(itr, parent, TYPE_DIR);
if (ret) {
printf("%s: doesn't exist (%d)\n", parent, ret);
goto exit;
}
retdent = find_directory_entry(itr, l_dirname);
if (retdent) {
printf("%s: already exists\n", l_dirname);
ret = -EEXIST;
goto exit;
} else {
if (itr->is_root) {
/* root dir cannot have "." or ".." */
if (!strcmp(l_dirname, ".") ||
!strcmp(l_dirname, "..")) {
ret = -EINVAL;
goto exit;
}
}
if (!itr->dent) {
printf("Error: allocating new dir entry\n");
ret = -EIO;
goto exit;
}
memset(itr->dent, 0, sizeof(*itr->dent));
/* Set short name to set alias checksum field in dir_slot */
set_name(itr->dent, dirname);
fill_dir_slot(itr, dirname);
/* Set attribute as archive for regular file */
fill_dentry(itr->fsdata, itr->dent, dirname, 0, 0,
ATTR_DIR | ATTR_ARCH);
retdent = itr->dent;
}
/* Default entries */
bytesperclust = mydata->clust_size * mydata->sect_size;
dotdent = malloc_cache_aligned(bytesperclust);
if (!dotdent) {
ret = -ENOMEM;
goto exit;
}
memset(dotdent, 0, bytesperclust);
memcpy(dotdent[0].name, ". ", 8);
memcpy(dotdent[0].ext, " ", 3);
dotdent[0].attr = ATTR_DIR | ATTR_ARCH;
memcpy(dotdent[1].name, ".. ", 8);
memcpy(dotdent[1].ext, " ", 3);
dotdent[1].attr = ATTR_DIR | ATTR_ARCH;
set_start_cluster(mydata, &dotdent[1], itr->start_clust);
ret = set_contents(mydata, retdent, 0, (__u8 *)dotdent,
bytesperclust, &actwrite);
if (ret < 0) {
printf("Error: writing contents\n");
goto exit;
}
/* Write twice for "." */
set_start_cluster(mydata, &dotdent[0], START(retdent));
ret = set_contents(mydata, retdent, 0, (__u8 *)dotdent,
bytesperclust, &actwrite);
if (ret < 0) {
printf("Error: writing contents\n");
goto exit;
}
/* 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 = flush_dir(itr);
if (ret)
printf("Error: writing directory entry\n");
exit:
free(dirname_copy);
free(mydata->fatbuf);
free(itr);
free(dotdent);
return ret;
}