u-boot/fs/fat/fat_write.c
AKASHI Takahiro cd2d727fff fs: fat: allocate a new cluster for root directory of fat32
Contrary to fat12/16, fat32 can have root directory at any location
and its size can be expanded.
Without this patch, root directory won't grow properly and so we will
eventually fail to add files under root directory. Please note that this
can happen even if you delete many files as deleted directory entries
are not reclaimed but just marked as "deleted" under the current
implementation.

Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
Tested-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
2019-05-28 18:55:09 -04:00

1487 lines
32 KiB
C

// 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 <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++) {
*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;
__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);
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;
wsize = min(cur_pos + actsize, filesize) - pos;
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;
/* CHECK: newclust = get_fatent(mydata, endclust); */
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;
}