summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorMichael Brandt <michael.brandt@stericsson.com>2010-07-20 15:08:22 +0200
committerMichael BRANDT <michael.brandt@stericsson.com>2010-08-30 20:03:48 +0200
commit20503b61a7f73fad8ee97219f7a1e74de3a8a2ac (patch)
treeec58c1f830639a45462fe07219304a06a16b7b54 /fs
parent3a5b27b58258ff61dfae3167bc46fed0146ddffd (diff)
Read/write VFAT support from Rockbox-3.3 FAT stack
Add read/write VFAT support from Rockbox-3.3 FAT stack. It should be also applicable to the unmodified 2009.11 U-Boot release. Note that is was taken as is from Rockbox and from a older U-Boot Rockbox patch. "checkpatch" shows very many coding style errors and warnings, but it is tedious work to clean this up. To make this patch work an additional mmc_block_write() board support routine and the errno variable are needed. Furthermore following defines in the board config header file: #define CONFIG_ROCKBOX_FAT 1 #define CONFIG_U_BOOT 1 #define CONFIG_SUPPORT_VFAT 1 #define CONFIG_CMD_TREE_FAT This will be added in a follow-up patch. This patch is based on the patch from Etienne Carriere <etienne.carriere@stericsson.com> for the U671x U-Boot: This commit adds FAT write support to u-boot native read-only FAT code. Commit initially applied on u-boot-v2009.01 (SHA1: 72d15e705bc3983884105cb7755c7ba80e74a0a5) Based on FAT stack dumped from Rockbox package v3.1 (www.rockbox.org). Based on initial Rockbox FAT stack integration in u-boot by Keith Outwater (outwater@comcast.net). Current porting is aligned with Rockbox v3.3 FAT stack. Enable upon config switches: CONFIG_CMD_FAT CONFIG_ROCKBOX_FAT CONFIG_CMD_TREE_FAT (recommended) CONFIG_SUPPORT_VFAT (recommended) C code APIs (from U-boot native FAT support): int fat_register_device(block_dev_desc_t *dev_desc, int part_no); long file_fat_read(const char *path, void *buf, unsigned long maxsize); int file_fat_ls(const char *dirname); int file_fat_detectfs(void); C code APIs (added by Rockbox FAT support): long file_fat_write(const char *path, void *buf, unsigned long maxsize); int file_fat_rm(const char *path); int file_fat_rmdir(const char *path); int file_fat_mkdir(const char *path); int file_fat_cd(const char *path); int file_fat_pwd(void); int file_fat_mv(const char *oldpath, const char *newpath); unsigned int rockbox_fat_free(unsigned long size_kbyte); unsigned int rockbox_fat_size(void); Use "help fat" from u-boot console to see available commands. ST-Ericsson ID: WP264488 Change-Id: I9afc29ecb80f9152bd8534bbf11e47e54cfad796 Signed-off-by: Michael Brandt <michael.brandt@stericsson.com> Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/3009
Diffstat (limited to 'fs')
-rw-r--r--fs/fat/Makefile4
-rw-r--r--fs/fat/fat.c139
-rw-r--r--fs/fat/file.c18
-rw-r--r--fs/fat/rockbox_debug.h39
-rw-r--r--fs/fat/rockbox_dir.c329
-rw-r--r--fs/fat/rockbox_fat.c2801
-rw-r--r--fs/fat/rockbox_file.c831
-rw-r--r--fs/fat/rockbox_wrapper.c657
8 files changed, 4811 insertions, 7 deletions
diff --git a/fs/fat/Makefile b/fs/fat/Makefile
index b711460f3..f61500da9 100644
--- a/fs/fat/Makefile
+++ b/fs/fat/Makefile
@@ -24,7 +24,11 @@ include $(TOPDIR)/config.mk
LIB = $(obj)libfat.a
AOBJS =
+
+COBJS-y :=
COBJS-$(CONFIG_CMD_FAT) := fat.o file.o
+COBJS-$(CONFIG_ROCKBOX_FAT) += rockbox_fat.o
+COBJS-$(CONFIG_ROCKBOX_FAT) += rockbox_dir.o rockbox_file.o rockbox_wrapper.o
SRCS := $(AOBJS:.o=.S) $(COBJS-y:.o=.c)
OBJS := $(addprefix $(obj),$(AOBJS) $(COBJS-y))
diff --git a/fs/fat/fat.c b/fs/fat/fat.c
index 2445f1e78..086f3a6a5 100644
--- a/fs/fat/fat.c
+++ b/fs/fat/fat.c
@@ -5,6 +5,8 @@
*
* 2002-07-28 - rjones@nexus-tech.net - ported to ppcboot v1.1.6
* 2003-03-10 - kharris@nexus-tech.net - ported to uboot
+ * 2006-01-18 - Keith Outwater (outwater4@comcast.net) - add support for
+ * rockbox FAT (www.rockbox.org) filesystem driver.
*
* See file CREDITS for list of people who contributed to this
* project.
@@ -31,6 +33,24 @@
#include <asm/byteorder.h>
#include <part.h>
+/*- exported routines ------------------------------------*/
+int fat_register_device(block_dev_desc_t *dev_desc, int part_no);
+int file_fat_detectfs(void);
+int disk_read (__u32 startblock, __u32 getsize, __u8 * bufptr);
+#if defined(CONFIG_ROCKBOX_FAT)
+int disk_write (__u32 startblock, __u32 putsize, __u8 * bufptr);
+#else
+long do_fat_read (const char *filename, void *buffer, unsigned long maxsize, int dols);
+int file_fat_ls(const char *dir);
+long file_fat_read(const char *filename, void *buffer, unsigned long maxsize);
+#endif
+
+/*- imported routines ------------------------------------*/
+#if defined(CONFIG_ROCKBOX_FAT)
+extern int rockbox_fat_mount(long startsector);
+#endif
+
+#if !defined(CONFIG_ROCKBOX_FAT)
/*
* Convert a string to lowercase.
*/
@@ -42,17 +62,45 @@ downcase(char *str)
str++;
}
}
-
+#endif /* !defined CONFIG_ROCKBOX_FAT */
+
+#if defined(CONFIG_ROCKBOX_FAT)
+cur_block_dev_t cur_block_dev = {
+ cur_dev : NULL,
+ part_offset : 0,
+ cur_part : 1
+ };
+#else
static block_dev_desc_t *cur_dev = NULL;
static unsigned long part_offset = 0;
static int cur_part = 1;
+#endif
#define DOS_PART_TBL_OFFSET 0x1be
#define DOS_PART_MAGIC_OFFSET 0x1fe
#define DOS_FS_TYPE_OFFSET 0x36
+#define DOS_FS_TYPE_OFFSET2 0x52
int disk_read (__u32 startblock, __u32 getsize, __u8 * bufptr)
{
+#if defined(CONFIG_ROCKBOX_FAT)
+ int rc;
+ if (cur_block_dev.cur_dev == NULL) {
+ FAT_DPRINT("Error: %s:%d:%s: cur_dev is NULL\n",
+ __FILE__, __LINE__, __FUNCTION__);
+ return -1;
+ }
+ if (cur_block_dev.cur_dev->block_read) {
+ /*
+ * The block read functions return zero on error, but the FAT
+ * filesystem drivers expect a negative return on error.
+ */
+ rc = (int)cur_block_dev.cur_dev->block_read(cur_block_dev.cur_dev->dev,
+ startblock, getsize, (unsigned long *)bufptr);
+ if (rc == 0) return -1;
+ else return rc;
+ }
+#else
startblock += part_offset;
if (cur_dev == NULL)
return -1;
@@ -60,9 +108,32 @@ int disk_read (__u32 startblock, __u32 getsize, __u8 * bufptr)
return cur_dev->block_read (cur_dev->dev
, startblock, getsize, (unsigned long *)bufptr);
}
+#endif
return -1;
}
+#if defined(CONFIG_ROCKBOX_FAT)
+int disk_write (__u32 startblock, __u32 putsize, __u8 * bufptr)
+{
+ int rc;
+
+ if (cur_block_dev.cur_dev == NULL) {
+ FAT_DPRINT("Error: %s:%d:%s: cur_dev is NULL\n",
+ __FILE__, __LINE__, __FUNCTION__);
+ return -1;
+ }
+ if (cur_block_dev.cur_dev->block_write) {
+ rc = (int)cur_block_dev.cur_dev->block_write(cur_block_dev.cur_dev->dev,
+ startblock, putsize,
+ (unsigned long *)bufptr);
+ if (rc == 0)
+ return -1;
+ else
+ return rc;
+ }
+ return -1;
+}
+#endif /* #if defined(CONFIG_ROCKBOX_FAT) */
int
fat_register_device(block_dev_desc_t *dev_desc, int part_no)
@@ -72,7 +143,11 @@ fat_register_device(block_dev_desc_t *dev_desc, int part_no)
if (!dev_desc->block_read)
return -1;
+#if defined(CONFIG_ROCKBOX_FAT)
+ cur_block_dev.cur_dev=dev_desc;
+#else
cur_dev = dev_desc;
+#endif
/* check if we have a MBR (on floppies we have only a PBR) */
if (dev_desc->block_read (dev_desc->dev, 0, 1, (ulong *) buffer) != 1) {
printf ("** Can't read from device %d **\n", dev_desc->dev);
@@ -92,12 +167,25 @@ fat_register_device(block_dev_desc_t *dev_desc, int part_no)
defined(CONFIG_SYSTEMACE) )
/* First we assume, there is a MBR */
if (!get_partition_info (dev_desc, part_no, &info)) {
+#if defined(CONFIG_ROCKBOX_FAT)
+ cur_block_dev.cur_part = part_no;
+ cur_block_dev.part_offset=info.start;
+#else
part_offset = info.start;
cur_part = part_no;
- } else if (!strncmp((char *)&buffer[DOS_FS_TYPE_OFFSET], "FAT", 3)) {
+#endif
+ } else if ( (!strncmp((char *)&buffer[DOS_FS_TYPE_OFFSET], "FAT", 3)) /* FAT16 */
+ || (!strncmp((char *)&buffer[DOS_FS_TYPE_OFFSET2], "FAT", 3)) /* FAT32 */
+ )
+ {
/* ok, we assume we are on a PBR only */
+#if defined(CONFIG_ROCKBOX_FAT)
+ cur_block_dev.cur_part = 1;
+ cur_block_dev.part_offset = 0;
+#else
cur_part = 1;
part_offset = 0;
+#endif
} else {
printf ("** Partition %d not valid on device %d **\n",
part_no, dev_desc->dev);
@@ -107,23 +195,42 @@ fat_register_device(block_dev_desc_t *dev_desc, int part_no)
#else
if (!strncmp((char *)&buffer[DOS_FS_TYPE_OFFSET],"FAT",3)) {
/* ok, we assume we are on a PBR only */
+#if defined(CONFIG_ROCKBOX_FAT)
+ cur_block_dev.cur_part = 1;
+ cur_block_dev.part_offset = 0;
+ info.start = cur_block_dev.part_offset;
+#else
cur_part = 1;
part_offset = 0;
info.start = part_offset;
+#endif
} else {
/* FIXME we need to determine the start block of the
* partition where the DOS FS resides. This can be done
* by using the get_partition_info routine. For this
* purpose the libpart must be included.
*/
+#if defined(CONFIG_ROCKBOX_FAT)
+ cur_block_dev.cur_part = 1;
+ cur_block_dev.part_offset = 32;
+#else
part_offset = 32;
cur_part = 1;
+#endif
+ }
+#endif
+
+#if defined(CONFIG_ROCKBOX_FAT)
+ if (rockbox_fat_mount((long)cur_block_dev.part_offset) < 0) {
+ printf("Error: Failed to mount filesystem\n");
+ return -1;
}
#endif
return 0;
}
+#if !defined(CONFIG_ROCKBOX_FAT)
/*
* Get the first occurence of a directory delimiter ('/' or '\') in a string.
* Return index into string if found, -1 otherwise.
@@ -641,7 +748,7 @@ static dir_entry *get_dentfromdir (fsdata * mydata, int startsect,
return NULL;
}
-
+#endif /* #if !defined(CONFIG_ROCKBOX_FAT) */
/*
* Read boot sector and volume info from a FAT filesystem
@@ -698,10 +805,11 @@ read_bootsectandvi(boot_sector *bs, volume_info *volinfo, int *fatsize)
}
}
- FAT_DPRINT("Error: broken fs_type sign\n");
+ FAT_DPRINT("Error: broken fs_type signature\n");
return -1;
}
+#if !defined(CONFIG_ROCKBOX_FAT)
__attribute__ ((__aligned__(__alignof__(dir_entry))))
__u8 do_fat_read_block[MAX_CLUSTSIZE];
long
@@ -943,7 +1051,7 @@ do_fat_read (const char *filename, void *buffer, unsigned long maxsize,
return ret;
}
-
+#endif /* #if !defined(CONFIG_ROCKBOX_FAT) */
int
file_fat_detectfs(void)
@@ -953,7 +1061,11 @@ file_fat_detectfs(void)
int fatsize;
char vol_label[12];
+#if defined(CONFIG_ROCKBOX_FAT)
+ if(cur_block_dev.cur_dev==NULL) {
+#else
if(cur_dev==NULL) {
+#endif
printf("No current device\n");
return 1;
}
@@ -964,7 +1076,11 @@ file_fat_detectfs(void)
defined(CONFIG_CMD_USB) || \
defined(CONFIG_MMC)
printf("Interface: ");
+#if defined(CONFIG_ROCKBOX_FAT)
+ switch(cur_block_dev.cur_dev->if_type) {
+#else
switch(cur_dev->if_type) {
+#endif
case IF_TYPE_IDE : printf("IDE"); break;
case IF_TYPE_SATA : printf("SATA"); break;
case IF_TYPE_SCSI : printf("SCSI"); break;
@@ -972,11 +1088,17 @@ file_fat_detectfs(void)
case IF_TYPE_USB : printf("USB"); break;
case IF_TYPE_DOC : printf("DOC"); break;
case IF_TYPE_MMC : printf("MMC"); break;
+ case IF_TYPE_SD : printf("SD"); break;
default : printf("Unknown");
}
+#if defined(CONFIG_ROCKBOX_FAT)
+ printf("\n Device %d: ",cur_block_dev.cur_dev->dev);
+ dev_print(cur_block_dev.cur_dev);
+#else
printf("\n Device %d: ",cur_dev->dev);
dev_print(cur_dev);
#endif
+#endif
if(read_bootsectandvi(&bs, &volinfo, &fatsize)) {
printf("\nNo valid FAT fs found\n");
return 1;
@@ -984,12 +1106,18 @@ file_fat_detectfs(void)
memcpy (vol_label, volinfo.volume_label, 11);
vol_label[11] = '\0';
volinfo.fs_type[5]='\0';
+#if defined(CONFIG_ROCKBOX_FAT)
+ printf("Partition %d: Filesystem: %s \"%s\"\n"
+ ,cur_block_dev.cur_part,volinfo.fs_type,vol_label);
+#else
printf("Partition %d: Filesystem: %s \"%s\"\n"
,cur_part,volinfo.fs_type,vol_label);
+#endif
return 0;
}
+#if !defined(CONFIG_ROCKBOX_FAT)
int
file_fat_ls(const char *dir)
{
@@ -1003,3 +1131,4 @@ file_fat_read(const char *filename, void *buffer, unsigned long maxsize)
printf("reading %s\n",filename);
return do_fat_read(filename, buffer, maxsize, LS_NO);
}
+#endif /* #if !defined(CONFIG_ROCKBOX_FAT) */
diff --git a/fs/fat/file.c b/fs/fat/file.c
index e87073440..7fd3191a7 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -5,6 +5,7 @@
*
* 2002-07-28 - rjones@nexus-tech.net - ported to ppcboot v1.1.6
* 2003-03-10 - kharris@nexus-tech.net - ported to uboot
+ * 2006-01-18 - Keith Outwater (outwater@comcast.net) - added write support
*
* See file CREDITS for list of people who contributed to this
* project.
@@ -32,10 +33,22 @@
#include <linux/stat.h>
#include <linux/time.h>
+#include "rockbox_debug.h"
+
+#ifdef CONFIG_ROCKBOX_FAT
+extern cur_block_dev_t cur_block_dev;
+#endif
+
+#if !defined(CONFIG_ROCKBOX_FAT)
/* Supported filesystems */
static const struct filesystem filesystems[] = {
- { file_fat_detectfs, file_fat_ls, file_fat_read, "FAT" },
+ { detect: file_fat_detectfs,
+ ls: file_fat_ls,
+ read: file_fat_read,
+ "FAT"
+ },
};
+
#define NUM_FILESYS (sizeof(filesystems)/sizeof(struct filesystem))
/* The filesystem which was last detected */
@@ -138,10 +151,10 @@ file_cd(const char *path)
pathcpy(tmpstr+1, path);
}
+ fat_dprintf("New CWD is '%s'\n", file_cwd);
return 0;
}
-
int
file_detectfs(void)
{
@@ -202,3 +215,4 @@ file_read(const char *filename, void *buffer, unsigned long maxsize)
return filesystems[current_filesystem].read(arg, buffer, maxsize);
}
+#endif /* #if !defined(CONFIG_ROCKBOX_FAT) */
diff --git a/fs/fat/rockbox_debug.h b/fs/fat/rockbox_debug.h
new file mode 100644
index 000000000..10d4a6e34
--- /dev/null
+++ b/fs/fat/rockbox_debug.h
@@ -0,0 +1,39 @@
+/*------------------------------------------------------------------------------
+ * (C) Copyright 2006
+ * Keith Outwater, (outwater4@comcast.net)
+ *
+ * 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 eve8 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
+ */
+
+#define fat_eprintf(fmt,args...) fprintf(stderr, "Error: %s:%d:%s(): "fmt"", \
+ __FILE__, __LINE__,__FUNCTION__,##args)
+#define fat_pprintf(fmt,args...) fprintf(stderr, "Fatal: %s:%d:%s(): "fmt"", \
+ __FILE__, __LINE__,__FUNCTION__,##args)
+#define fat_wprintf(fmt,args...) fprintf(stderr, "Warning: %s:%d:%s(): "fmt"", \
+ __FILE__, __LINE__,__FUNCTION__,##args)
+
+#if (defined(DEBUG) || defined(FAT_DEBUG))
+#define fat_dprintf(fmt,args...) printf("Debug: %s:%d:%s(): "fmt"", \
+ __FILE__, __LINE__,__FUNCTION__,##args)
+#define fat_deprintf(fmt,args...) fprintf(stderr, "Error: %s:%d:%s(): "fmt"",\
+ __FILE__, __LINE__,__FUNCTION__,##args)
+#else
+#define fat_dprintf(fmt,args...)
+#define fat_deprintf(fmt,args...)
+#endif
diff --git a/fs/fat/rockbox_dir.c b/fs/fat/rockbox_dir.c
new file mode 100644
index 000000000..24c0ac2cc
--- /dev/null
+++ b/fs/fat/rockbox_dir.c
@@ -0,0 +1,329 @@
+/***************************************************************************
+ * Copyright (C) 2002 by Björn Stenberg
+ *
+ * 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.
+ *
+ * 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
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************
+ * Source file dumped from rockbox-3.1 distribution.
+ * $Id: dir.c 13741 2007-06-30 02:08:27Z jethead71 $
+ * Copyright (C) 2002 by Björn Stenberg
+ ****************************************************************************
+ * See file CREDITS for list of people who contributed to the U-boot
+ * project.
+ *
+ * 01/17/2006 Keith Outwater (outwater4@comcast.net) - port to U-Boot using
+ * CVS version 1.29 of 'firmware/common/dir.c' from rockbox CVS server.
+ * 01/07/2009 etienne.carriere@stnwireless.com - update u-boot port from rockbox-3.1
+ ****************************************************************************
+ */
+#include <common.h>
+#include <config.h>
+
+#include <asm/errno.h>
+#include <linux/string.h>
+#include <fat.h>
+#include "rockbox_debug.h"
+
+extern int errno; /* see board/<board>/<board>.c */
+extern int strcasecmp(const char *s1, const char *s2); /* from rockbox_wrapper.c */
+
+#ifndef __HAVE_ARCH_STRNICMP
+extern int strnicmp(const char *s1, const char *s2, size_t len);
+#endif
+
+#if !defined(CONFIG_ROCKBOX_FAT_MAX_OPEN_DIRS)
+#define CONFIG_ROCKBOX_FAT_MAX_OPEN_DIRS 8
+#endif
+
+static DIR_UNCACHED opendirs[CONFIG_ROCKBOX_FAT_MAX_OPEN_DIRS];
+
+#define DEBUGF fat_dprintf /* map debug on old fat_dprintf from rockbox_debug.h */
+
+#ifdef HAVE_MULTIVOLUME
+/* @brief returns on which volume this is, and copies the reduced name
+ (sortof a preprocessor for volume-decorated pathnames)
+*/
+int strip_volume(const char* name, char* namecopy)
+{
+ int volume = 0;
+ const char *temp = name;
+
+ while (*temp == '/') /* skip all leading slashes */
+ ++temp;
+
+ if (*temp && !strncmp(temp, VOL_NAMES, VOL_ENUM_POS))
+ {
+ temp += VOL_ENUM_POS; /* behind special name */
+ volume = atoi(temp); /* number is following */
+ temp = strchr(temp, '/'); /* search for slash behind */
+ if (temp != NULL)
+ name = temp; /* use the part behind the volume */
+ else
+ name = "/"; /* else this must be the root dir */
+ }
+
+ strncpy(namecopy, name, MAX_PATH);
+ namecopy[MAX_PATH-1] = '\0';
+
+ return volume;
+}
+#endif /* #ifdef HAVE_MULTIVOLUME */
+
+
+#ifdef HAVE_HOTSWAP
+// release all dir handles on a given volume "by force", to avoid leaks
+int release_dirs(int volume)
+{
+ DIR_UNCACHED* pdir = opendirs;
+ int dd;
+ int closed = 0;
+ for ( dd=0; dd<CONFIG_ROCKBOX_FAT_MAX_OPEN_DIRS; dd++, pdir++)
+ {
+ if (pdir->fatdir.file.volume == volume)
+ {
+ pdir->busy = false; /* mark as available, no further action */
+ closed++;
+ }
+ }
+ return closed; /* return how many we did */
+}
+#endif /* #ifdef HAVE_HOTSWAP */
+
+DIR_UNCACHED* opendir_uncached(const char* name)
+{
+ char namecopy[MAX_PATH];
+ char *part;
+ char* end;
+ struct fat_direntry entry;
+ int dd;
+ DIR_UNCACHED* pdir = opendirs;
+#ifdef HAVE_MULTIVOLUME
+ int volume;
+#endif
+
+ if (name[0] != '/') {
+ DEBUGF("Only absolute paths supported right now\n");
+ return NULL;
+ }
+
+ /* find a free dir descriptor */
+ for ( dd=0; dd<CONFIG_ROCKBOX_FAT_MAX_OPEN_DIRS; dd++, pdir++)
+ if (!pdir->busy)
+ break;
+
+ if (dd == CONFIG_ROCKBOX_FAT_MAX_OPEN_DIRS) {
+ DEBUGF("Too many dirs open\n");
+ errno = EMFILE;
+ return NULL;
+ }
+
+ pdir->busy = true;
+
+#ifdef HAVE_MULTIVOLUME
+ /* try to extract a heading volume name, if present */
+ volume = strip_volume(name, namecopy);
+ pdir->volumecounter = 0;
+#else
+ strncpy(namecopy, name, sizeof (namecopy)); /* just copy */
+ namecopy[sizeof (namecopy) - 1] = '\0';
+#endif
+
+ if ( fat_opendir(IF_MV2(volume,) &pdir->fatdir, 0, NULL) < 0 ) {
+ DEBUGF("Failed opening root dir\n");
+ pdir->busy = false;
+ return NULL;
+ }
+
+ for ( part = strtok_r(namecopy, "/", &end); part;
+ part = strtok_r(NULL, "/", &end)) {
+ /* scan dir for name */
+ while (1) {
+ if ((fat_getnext(&pdir->fatdir, &entry) < 0) ||
+ (!entry.name[0])) {
+ pdir->busy = false;
+ return NULL;
+ }
+ if ((entry.attr & FAT_ATTR_DIRECTORY) &&
+ (!strcasecmp(part, (char*)entry.name)) ) {
+ pdir->parent_dir = pdir->fatdir;
+ if ( fat_opendir(IF_MV2(volume,)
+ &pdir->fatdir,
+ entry.firstcluster,
+ &pdir->parent_dir) < 0) {
+ DEBUGF("Failed opening dir '%s' (%ld)\n",
+ part, entry.firstcluster);
+ pdir->busy = false;
+ return NULL;
+ }
+#ifdef HAVE_MULTIVOLUME
+ pdir->volumecounter = -1; /* n.a. to subdirs */
+#endif
+ break;
+ }
+ }
+ }
+
+ return pdir;
+}
+
+int closedir_uncached(DIR_UNCACHED* dir)
+{
+ dir->busy = false;
+ return 0;
+}
+
+struct dirent_uncached* readdir_uncached(DIR_UNCACHED* dir)
+{
+ struct fat_direntry entry;
+ struct dirent_uncached* theent = &(dir->theent);
+
+ if (!dir->busy)
+ return NULL;
+
+#ifdef HAVE_MULTIVOLUME
+ /* Volumes (secondary file systems) get inserted into the root directory
+ of the first volume, since we have no separate top level. */
+ if (dir->volumecounter >= 0 /* on a root dir */
+ && dir->volumecounter < NUM_VOLUMES /* in range */
+ && dir->fatdir.file.volume == 0) /* at volume 0 */
+ { /* fake special directories, which don't really exist, but
+ will get redirected upon opendir_uncached() */
+ while (++dir->volumecounter < NUM_VOLUMES)
+ {
+ if (fat_ismounted(dir->volumecounter))
+ {
+ memset(theent, 0, sizeof(*theent));
+ theent->attribute = FAT_ATTR_DIRECTORY | FAT_ATTR_VOLUME;
+ snprintf(theent->d_name, sizeof(theent->d_name),
+ VOL_NAMES, dir->volumecounter);
+ return theent;
+ }
+ }
+ }
+#endif
+ /* normal directory entry fetching follows here */
+ if (fat_getnext(&(dir->fatdir), &entry) < 0)
+ return NULL;
+
+ if (!entry.name[0])
+ return NULL;
+
+ strncpy((char*)theent->d_name, (char*)entry.name, sizeof(theent->d_name));
+ theent->attribute = entry.attr;
+ theent->size = entry.filesize;
+ theent->startcluster = entry.firstcluster;
+ theent->wrtdate = entry.wrtdate;
+ theent->wrttime = entry.wrttime;
+
+ return theent;
+}
+
+int mkdir_uncached(const char *name)
+{
+ DIR_UNCACHED *dir;
+ char namecopy[MAX_PATH];
+ char *end;
+ char *basename;
+ char *parent;
+ struct dirent_uncached *entry;
+ struct fat_dir newdir;
+ int rc;
+
+ if (name[0] != '/') {
+ DEBUGF("mkdir: Only absolute paths supported right now\n");
+ return -1;
+ }
+
+ strncpy(namecopy, name, sizeof (namecopy));
+ namecopy[sizeof (namecopy) - 1] = 0;
+
+ /* Split the base name and the path */
+ end = strrchr(namecopy, '/');
+ *end = 0;
+ basename = end + 1;
+
+ if (namecopy == end) /* Root dir? */
+ parent = "/";
+ else
+ parent = namecopy;
+
+ DEBUGF("mkdir: parent: %s, name: %s\n", parent, basename);
+
+ dir = opendir_uncached(parent);
+
+ if (!dir) {
+ DEBUGF("mkdir: can't open parent dir\n");
+ return -2;
+ }
+
+ if (basename[0] == 0) {
+ DEBUGF("mkdir: Empty dir name\n");
+ errno = EINVAL;
+ return -3;
+ }
+
+ /* Now check if the name already exists */
+ while ((entry = readdir_uncached(dir))) {
+ if ( !strcasecmp(basename, (char*)entry->d_name) ) {
+ DEBUGF("mkdir error: file exists\n");
+ errno = EEXIST;
+ closedir_uncached(dir);
+ return -4;
+ }
+ }
+
+ memset(&newdir, 0, sizeof (struct fat_dir));
+
+ rc = fat_create_dir(basename, &newdir, &(dir->fatdir));
+ closedir_uncached(dir);
+
+ return rc;
+}
+
+int rmdir_uncached(const char* name)
+{
+ int rc;
+ DIR_UNCACHED* dir;
+ struct dirent_uncached* entry;
+
+ dir = opendir_uncached(name);
+ if (!dir)
+ {
+ errno = ENOENT; /* open error */
+ return -1;
+ }
+
+ /* check if the directory is empty */
+ while ((entry = readdir_uncached(dir)))
+ {
+ if (strcmp((char*)entry->d_name, ".") &&
+ strcmp((char*)entry->d_name, ".."))
+ {
+ DEBUGF("rmdir error: not empty\n");
+ errno = ENOTEMPTY;
+ closedir_uncached(dir);
+ return -2;
+ }
+ }
+
+ rc = fat_remove(&(dir->fatdir.file));
+ if (rc < 0) {
+ DEBUGF("Failed removing dir: %d\n", rc);
+ errno = EIO;
+ rc = rc * 10 - 3;
+ }
+
+ closedir_uncached(dir);
+ return rc;
+}
diff --git a/fs/fat/rockbox_fat.c b/fs/fat/rockbox_fat.c
new file mode 100644
index 000000000..093c5a1b7
--- /dev/null
+++ b/fs/fat/rockbox_fat.c
@@ -0,0 +1,2801 @@
+/***************************************************************************
+ * Copyright (C) 2002 by Linus Nielsen Feltzing
+ *
+ * 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.
+ *
+ * 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
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************
+ * Source file dumped from rockbox-3.1 distribution.
+ * $Id: fat.c 18975 2008-11-02 01:14:46Z gevaerts $
+ * Copyright (C) 2002 by Linus Nielsen Feltzing
+ ****************************************************************************
+ * See file CREDITS for list of people who contributed to the U-boot
+ * project.
+ *
+ * 01/17/2006 Keith Outwater (outwater4@comcast.net) - port to U-Boot using
+ * CVS version 1.123 of 'firmware/drivers/fat.c' from rockbox CVS server.
+ * 01/07/2009 etienne.carriere@stnwireless.com - update u-boot port from rockbox-3.1
+ ****************************************************************************
+ */
+#include <common.h>
+#include <config.h>
+
+#include <linux/ctype.h>
+#include <fat.h>
+#include <rbunicode.h>
+#include "rockbox_debug.h"
+#include <rtc.h>
+
+/*
+ * Override this parameter to boost performance ONLY if you are sure your driver
+ * can handle requests of more than 256 sectors!
+ */
+#if !defined(CONFIG_ROCKBOX_FAT_MAX_SECS_PER_XFER)
+#define CONFIG_ROCKBOX_FAT_MAX_SECS_PER_XFER 256
+#endif
+
+#define letoh16(x) FAT2CPU16(x)
+#define letoh32(x) FAT2CPU32(x)
+#define htole16(x) FAT2CPU16(x)
+#define htole32(x) FAT2CPU32(x)
+
+//#define NO_ROCKBOX_FAT16_SUPPORT
+#if !defined(NO_ROCKBOX_FAT16_SUPPORT)
+#define HAVE_FAT16SUPPORT
+#else
+#undef HAVE_FAT16SUPPORT
+#endif
+
+extern int atoi(const char *s); /* from common/vsprintf.c */
+
+#ifdef CONFIG_U_BOOT
+/* @brief Call disk_read() (fs/fat/fat.c) for media block access.
+ * @return negative on failure, else 0.
+ */
+static int storage_read_sectors(IF_MV2(int drive,) unsigned long start, int count, void* buf)
+{
+ int rc;
+ extern int disk_read(__u32 startblock, __u32 getsize, __u8 * bufptr);
+ rc =disk_read(start, count, buf);
+ return (rc<0) ? rc : 0;
+}
+/* @brief Call disk_write() (fs/fat/fat.c) for media block access.
+ * @return negative on failure, else 0.
+ */
+static int storage_write_sectors(IF_MV2(int drive,) unsigned long start, int count, void* buf)
+{
+ int rc;
+ extern int disk_write(__u32 startblock, __u32 putsize, __u8 * bufptr);
+ rc = disk_write(start, count, buf);
+ return (rc<0) ? rc : 0;
+}
+#endif
+
+#ifdef DEBUGF
+#error "Check DEBUGF macro"
+#else
+#define DEBUGF fat_dprintf
+#endif
+
+#ifdef LDEBUGF
+#error "Check LDEBUGF macro"
+#else
+#define LDEBUGF fat_dprintf
+#endif
+
+extern int strcasecmp(const char *s1, const char *s2); /* from rockbox_wrapper.c */
+
+/*
+ * The current working directory.
+ */
+extern char file_cwd[];
+
+/*
+ * The current device and partition information. Updated by
+ * fat_register_device().
+ */
+extern cur_block_dev_t cur_block_dev;
+
+#define BYTES2INT16(array,pos) \
+ (array[pos] | (array[pos+1] << 8 ))
+#define BYTES2INT32(array,pos) \
+ ((long)array[pos] | ((long)array[pos+1] << 8 ) | \
+ ((long)array[pos+2] << 16 ) | ((long)array[pos+3] << 24 ))
+
+#define FATTYPE_FAT12 0
+#define FATTYPE_FAT16 1
+#define FATTYPE_FAT32 2
+
+/* BPB offsets; generic */
+#define BS_JMPBOOT 0
+#define BS_OEMNAME 3
+#define BPB_BYTSPERSEC 11
+#define BPB_SECPERCLUS 13
+#define BPB_RSVDSECCNT 14
+#define BPB_NUMFATS 16
+#define BPB_ROOTENTCNT 17
+#define BPB_TOTSEC16 19
+#define BPB_MEDIA 21
+#define BPB_FATSZ16 22
+#define BPB_SECPERTRK 24
+#define BPB_NUMHEADS 26
+#define BPB_HIDDSEC 28
+#define BPB_TOTSEC32 32
+
+/* fat12/16 */
+#define BS_DRVNUM 36
+#define BS_RESERVED1 37
+#define BS_BOOTSIG 38
+#define BS_VOLID 39
+#define BS_VOLLAB 43
+#define BS_FILSYSTYPE 54
+
+/* fat32 */
+#define BPB_FATSZ32 36
+#define BPB_EXTFLAGS 40
+#define BPB_FSVER 42
+#define BPB_ROOTCLUS 44
+#define BPB_FSINFO 48
+#define BPB_BKBOOTSEC 50
+#define BS_32_DRVNUM 64
+#define BS_32_BOOTSIG 66
+#define BS_32_VOLID 67
+#define BS_32_VOLLAB 71
+#define BS_32_FILSYSTYPE 82
+
+#define BPB_LAST_WORD 510
+
+
+/* attributes */
+#define FAT_ATTR_LONG_NAME (FAT_ATTR_READ_ONLY | FAT_ATTR_HIDDEN | \
+ FAT_ATTR_SYSTEM | FAT_ATTR_VOLUME_ID)
+#define FAT_ATTR_LONG_NAME_MASK (FAT_ATTR_READ_ONLY | FAT_ATTR_HIDDEN | \
+ FAT_ATTR_SYSTEM | FAT_ATTR_VOLUME_ID | \
+ FAT_ATTR_DIRECTORY | FAT_ATTR_ARCHIVE )
+
+/* NTRES flags */
+#define FAT_NTRES_LC_NAME 0x08
+#define FAT_NTRES_LC_EXT 0x10
+
+#define FATDIR_NAME 0
+#define FATDIR_ATTR 11
+#define FATDIR_NTRES 12
+#define FATDIR_CRTTIMETENTH 13
+#define FATDIR_CRTTIME 14
+#define FATDIR_CRTDATE 16
+#define FATDIR_LSTACCDATE 18
+#define FATDIR_FSTCLUSHI 20
+#define FATDIR_WRTTIME 22
+#define FATDIR_WRTDATE 24
+#define FATDIR_FSTCLUSLO 26
+#define FATDIR_FILESIZE 28
+
+#define FATLONG_ORDER 0
+#define FATLONG_TYPE 12
+#define FATLONG_CHKSUM 13
+
+#define CLUSTERS_PER_FAT_SECTOR (SECTOR_SIZE / 4)
+#define CLUSTERS_PER_FAT16_SECTOR (SECTOR_SIZE / 2)
+#define DIR_ENTRIES_PER_SECTOR (SECTOR_SIZE / DIR_ENTRY_SIZE)
+#define DIR_ENTRY_SIZE 32
+#define NAME_BYTES_PER_ENTRY 13
+#define FAT_BAD_MARK 0x0ffffff7
+#define FAT_EOF_MARK 0x0ffffff8
+#define FAT_LONGNAME_PAD_BYTE 0xff
+#define FAT_LONGNAME_PAD_UCS 0xffff
+
+/* filename charset conversion table */
+static const unsigned char unicode2iso8859_2[] = {
+ 0x00, 0x00, 0xc3, 0xe3, 0xa1, 0xb1, 0xc6, 0xe6, /* 0x0100 */
+ 0x00, 0x00, 0x00, 0x00, 0xc8, 0xe8, 0xcf, 0xef, /* 0x0108 */
+ 0xd0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0110 */
+ 0xca, 0xea, 0xcc, 0xec, 0x00, 0x00, 0x00, 0x00, /* 0x0118 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0120 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0128 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0130 */
+ 0x00, 0xc5, 0xe5, 0x00, 0x00, 0xa5, 0xb5, 0x00, /* 0x0138 */
+ 0x00, 0xa3, 0xb3, 0xd1, 0xf1, 0x00, 0x00, 0xd2, /* 0x0140 */
+ 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0148 */
+ 0xd5, 0xf5, 0x00, 0x00, 0xc0, 0xe0, 0x00, 0x00, /* 0x0150 */
+ 0xd8, 0xf8, 0xa6, 0xb6, 0x00, 0x00, 0xaa, 0xba, /* 0x0158 */
+ 0xa9, 0xb9, 0xde, 0xfe, 0xab, 0xbb, 0x00, 0x00, /* 0x0160 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd9, 0xf9, /* 0x0168 */
+ 0xdb, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0170 */
+ 0x00, 0xac, 0xbc, 0xaf, 0xbf, 0xae, 0xbe, 0x00, /* 0x0178 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0180 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0188 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0190 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0198 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x01a0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x01a8 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x01b0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x01b8 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x01c0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x01c8 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x01d0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x01d8 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x01e0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x01e8 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x01f0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x01f8 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0200 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0208 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0210 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0218 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0220 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0228 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0230 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0238 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0240 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0248 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0250 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0258 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0260 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0268 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0270 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0278 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0280 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0288 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0290 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0298 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x02a0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x02a8 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x02b0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x02b8 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, /* 0x02c0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x02c8 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x02d0 */
+ 0xa2, 0xff, 0x00, 0xb2, 0x00, 0xbd, 0x00, 0x00, /* 0x02d8 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x02e0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x02e8 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x02f0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 /* 0x02f8 */
+};
+
+struct fsinfo {
+ unsigned long freecount; /* last known free cluster count */
+ unsigned long nextfree; /* first cluster to start looking for free
+ * clusters, or 0xffffffff for no hint */
+};
+
+/* fsinfo offsets */
+#define FSINFO_FREECOUNT 488
+#define FSINFO_NEXTFREE 492
+
+/* Note: This struct doesn't hold the raw values after mounting if
+ * bpb_bytspersec isn't 512. All sector counts are normalized to 512 byte
+ * physical sectors. */
+struct bpb
+{
+ int bpb_bytspersec; /* Bytes per sector, typically 512 */
+ unsigned int bpb_secperclus; /* Sectors per cluster */
+ int bpb_rsvdseccnt; /* Number of reserved sectors */
+ int bpb_numfats; /* Number of FAT structures, typically 2 */
+ int bpb_totsec16; /* Number of sectors on the volume (old 16-bit) */
+ int bpb_media; /* Media type (typically 0xf0 or 0xf8) */
+ int bpb_fatsz16; /* Number of used sectors per FAT structure */
+ unsigned long bpb_totsec32; /* Number of sectors on the volume
+ (new 32-bit) */
+ unsigned int last_word; /* 0xAA55 */
+
+ /**** FAT32 specific *****/
+ long bpb_fatsz32;
+ long bpb_rootclus;
+ long bpb_fsinfo;
+
+ /* variables for internal use */
+ unsigned long fatsize;
+ unsigned long totalsectors;
+ unsigned long rootdirsector;
+ unsigned long firstdatasector;
+ unsigned long startsector;
+ unsigned long dataclusters;
+ struct fsinfo fsinfo;
+#ifdef HAVE_FAT16SUPPORT
+ int bpb_rootentcnt; /* Number of dir entries in the root */
+ /* internals for FAT16 support */
+ bool is_fat16; /* true if we mounted a FAT16 partition, false if FAT32 */
+ unsigned int rootdiroffset; /* sector offset of root dir relative to start
+ * of first pseudo cluster */
+#endif /* #ifdef HAVE_FAT16SUPPORT */
+#ifdef HAVE_MULTIVOLUME
+ int drive; /* on which physical device is this located */
+ bool mounted; /* flag if this volume is mounted */
+#endif
+};
+
+static struct bpb fat_bpbs[NUM_VOLUMES]; /* mounted partition info */
+static bool initialized = false;
+
+static int update_fsinfo(IF_MV_NONVOID(struct bpb* fat_bpb));
+static int flush_fat(IF_MV_NONVOID(struct bpb* fat_bpb));
+static int bpb_is_sane(IF_MV_NONVOID(struct bpb* fat_bpb));
+static void *cache_fat_sector(IF_MV2(struct bpb* fat_bpb,)
+ long secnum, bool dirty);
+static void create_dos_name(const unsigned char *name, unsigned char *newname);
+static void randomize_dos_name(unsigned char *name);
+static unsigned long find_free_cluster(IF_MV2(struct bpb* fat_bpb,)
+ unsigned long start);
+static int transfer(IF_MV2(struct bpb* fat_bpb,) unsigned long start,
+ long count, char* buf, bool write );
+
+#define FAT_CACHE_SIZE 0x20
+#define FAT_CACHE_MASK (FAT_CACHE_SIZE-1)
+
+struct fat_cache_entry
+{
+ long secnum;
+ bool inuse;
+ bool dirty;
+#ifdef HAVE_MULTIVOLUME
+ struct bpb* fat_vol ; /* shared cache for all volumes */
+#endif
+};
+
+static char fat_cache_sectors[FAT_CACHE_SIZE][SECTOR_SIZE];
+static struct fat_cache_entry fat_cache[FAT_CACHE_SIZE];
+#ifndef CONFIG_U_BOOT
+static struct mutex cache_mutex SHAREDBSS_ATTR;
+#endif
+
+#if defined(HAVE_HOTSWAP) && !(CONFIG_STORAGE & STORAGE_MMC) /* A better condition ?? */
+void fat_lock(void)
+{
+ mutex_lock(&cache_mutex);
+}
+
+void fat_unlock(void)
+{
+ mutex_unlock(&cache_mutex);
+}
+#endif
+
+static long cluster2sec(IF_MV2(struct bpb* fat_bpb,) long cluster)
+{
+#ifndef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+#ifdef HAVE_FAT16SUPPORT
+ /* negative clusters (FAT16 root dir) don't get the 2 offset */
+ int zerocluster = cluster < 0 ? 0 : 2;
+#else
+ const long zerocluster = 2;
+#endif
+
+ if (cluster > (long)(fat_bpb->dataclusters + 1))
+ {
+ DEBUGF( "cluster2sec() - Bad cluster number (%ld)\n", cluster);
+ return -1;
+ }
+
+ return (cluster - zerocluster) * fat_bpb->bpb_secperclus
+ + fat_bpb->firstdatasector;
+}
+
+/* @brief Get partition size and free space (all in kByte)
+ */
+void fat_size(IF_MV2(int volume,) unsigned long* size, unsigned long* free)
+{
+#ifndef HAVE_MULTIVOLUME
+ const int volume = 0;
+#endif
+ struct bpb* fat_bpb = &fat_bpbs[volume];
+ if (size)
+ *size = fat_bpb->dataclusters * fat_bpb->bpb_secperclus / 2;
+ if (free)
+ *free = fat_bpb->fsinfo.freecount * fat_bpb->bpb_secperclus / 2;
+}
+
+void fat_init(void)
+{
+ unsigned int i;
+
+ if (!initialized)
+ {
+ initialized = true;
+#ifndef CONFIG_U_BOOT
+ mutex_init(&cache_mutex);
+#endif
+ }
+
+#ifdef HAVE_PRIORITY_SCHEDULING
+ /* Disable this because it is dangerous due to the assumption that
+ * mutex_unlock won't yield */
+ mutex_set_preempt(&cache_mutex, false);
+#endif
+
+ /* mark the FAT cache as unused */
+ for(i = 0;i < FAT_CACHE_SIZE;i++)
+ {
+ fat_cache[i].secnum = 8; /* We use a "safe" sector just in case */
+ fat_cache[i].inuse = false;
+ fat_cache[i].dirty = false;
+#ifdef HAVE_MULTIVOLUME
+ fat_cache[i].fat_vol = NULL;
+#endif
+ }
+#ifdef HAVE_MULTIVOLUME
+ /* mark the possible volumes as not mounted */
+ for (i=0; i<NUM_VOLUMES;i++)
+ {
+ fat_bpbs[i].mounted = false;
+ }
+#endif
+}
+
+int fat_mount(IF_MV2(int volume,) IF_MV2(int drive,) long startsector)
+{
+#ifndef HAVE_MULTIVOLUME
+ const int volume = 0;
+#endif
+ struct bpb* fat_bpb = &fat_bpbs[volume];
+ unsigned char buf[SECTOR_SIZE];
+ int rc;
+ int secmult;
+ long datasec;
+#ifdef HAVE_FAT16SUPPORT
+ int rootdirsectors;
+#endif
+
+ if (!initialized) fat_init();
+
+
+ /* Read the sector */
+ fat_dprintf("[ECE-info] mounting on startsector %ld\n", startsector);
+ //fat_init();
+ fat_dprintf("[ECE-info] fat_init() not called: expected to be called before...\n");
+
+ rc = storage_read_sectors(IF_MV2(drive,) startsector,1,buf);
+ if(rc)
+ {
+ DEBUGF( "fat_mount() - Couldn't read BPB (error code %d)\n", rc);
+ return rc * 10 - 1;
+ }
+
+ memset(fat_bpb, 0, sizeof(struct bpb));
+ fat_bpb->startsector = startsector;
+#ifdef HAVE_MULTIVOLUME
+ fat_bpb->drive = drive;
+#endif
+
+ fat_bpb->bpb_bytspersec = BYTES2INT16(buf,BPB_BYTSPERSEC);
+ secmult = fat_bpb->bpb_bytspersec / SECTOR_SIZE;
+ /* Sanity check is performed later */
+
+ fat_bpb->bpb_secperclus = secmult * buf[BPB_SECPERCLUS];
+ fat_bpb->bpb_rsvdseccnt = secmult * BYTES2INT16(buf,BPB_RSVDSECCNT);
+ fat_bpb->bpb_numfats = buf[BPB_NUMFATS];
+ fat_bpb->bpb_media = buf[BPB_MEDIA];
+ fat_bpb->bpb_fatsz16 = secmult * BYTES2INT16(buf,BPB_FATSZ16);
+ fat_bpb->bpb_fatsz32 = secmult * BYTES2INT32(buf,BPB_FATSZ32);
+ fat_bpb->bpb_totsec16 = secmult * BYTES2INT16(buf,BPB_TOTSEC16);
+ fat_bpb->bpb_totsec32 = secmult * BYTES2INT32(buf,BPB_TOTSEC32);
+ fat_bpb->last_word = BYTES2INT16(buf,BPB_LAST_WORD);
+
+ /* calculate a few commonly used values */
+ if (fat_bpb->bpb_fatsz16 != 0)
+ fat_bpb->fatsize = fat_bpb->bpb_fatsz16;
+ else
+ fat_bpb->fatsize = fat_bpb->bpb_fatsz32;
+
+ if (fat_bpb->bpb_totsec16 != 0)
+ fat_bpb->totalsectors = fat_bpb->bpb_totsec16;
+ else
+ fat_bpb->totalsectors = fat_bpb->bpb_totsec32;
+
+#ifdef HAVE_FAT16SUPPORT
+ fat_bpb->bpb_rootentcnt = BYTES2INT16(buf,BPB_ROOTENTCNT);
+ if (!fat_bpb->bpb_bytspersec)
+ return -2;
+ rootdirsectors = secmult * ((fat_bpb->bpb_rootentcnt * DIR_ENTRY_SIZE
+ + fat_bpb->bpb_bytspersec - 1) / fat_bpb->bpb_bytspersec);
+#endif /* #ifdef HAVE_FAT16SUPPORT */
+
+ fat_bpb->firstdatasector = fat_bpb->bpb_rsvdseccnt
+#ifdef HAVE_FAT16SUPPORT
+ + rootdirsectors
+#endif
+ + fat_bpb->bpb_numfats * fat_bpb->fatsize;
+
+ /* Determine FAT type */
+ datasec = fat_bpb->totalsectors - fat_bpb->firstdatasector;
+ if (fat_bpb->bpb_secperclus)
+ fat_bpb->dataclusters = datasec / fat_bpb->bpb_secperclus;
+ else
+ return -2;
+
+#ifdef TEST_FAT
+ /*
+ we are sometimes testing with "illegally small" fat32 images,
+ so we don't use the proper fat32 test case for test code
+ */
+ if ( fat_bpb->bpb_fatsz16 )
+#else
+ if ( fat_bpb->dataclusters < 65525 )
+#endif
+ { /* FAT16 */
+#ifdef HAVE_FAT16SUPPORT
+ fat_bpb->is_fat16 = true;
+ if (fat_bpb->dataclusters < 4085)
+ { /* FAT12 */
+ DEBUGF("This is FAT12. Go away!\n");
+ return -2;
+ }
+#else /* #ifdef HAVE_FAT16SUPPORT */
+ DEBUGF("This is not FAT32. Go away!\n");
+ return -2;
+#endif /* #ifndef HAVE_FAT16SUPPORT */
+ }
+
+#ifdef HAVE_FAT16SUPPORT
+ if (fat_bpb->is_fat16)
+ { /* FAT16 specific part of BPB */
+ int dirclusters;
+ fat_bpb->rootdirsector = fat_bpb->bpb_rsvdseccnt
+ + fat_bpb->bpb_numfats * fat_bpb->bpb_fatsz16;
+ dirclusters = ((rootdirsectors + fat_bpb->bpb_secperclus - 1)
+ / fat_bpb->bpb_secperclus); /* rounded up, to full clusters */
+ /* I assign negative pseudo cluster numbers for the root directory,
+ their range is counted upward until -1. */
+ fat_bpb->bpb_rootclus = 0 - dirclusters; /* backwards, before the data*/
+ fat_bpb->rootdiroffset = dirclusters * fat_bpb->bpb_secperclus
+ - rootdirsectors;
+ }
+ else
+#endif /* #ifdef HAVE_FAT16SUPPORT */
+ { /* FAT32 specific part of BPB */
+ fat_bpb->bpb_rootclus = BYTES2INT32(buf,BPB_ROOTCLUS);
+ fat_bpb->bpb_fsinfo = secmult * BYTES2INT16(buf,BPB_FSINFO);
+ fat_bpb->rootdirsector = cluster2sec(IF_MV2(fat_bpb,)
+ fat_bpb->bpb_rootclus);
+ }
+
+ rc = bpb_is_sane(IF_MV(fat_bpb));
+ if (rc < 0)
+ {
+ DEBUGF( "fat_mount() - BPB is not sane\n");
+ return rc * 10 - 3;
+ }
+
+#ifdef HAVE_FAT16SUPPORT
+ if (fat_bpb->is_fat16)
+ {
+ fat_bpb->fsinfo.freecount = 0xffffffff; /* force recalc below */
+ fat_bpb->fsinfo.nextfree = 0xffffffff;
+ }
+ else
+#endif /* #ifdef HAVE_FAT16SUPPORT */
+ {
+ /* Read the fsinfo sector */
+ rc = storage_read_sectors(IF_MV2(drive,)
+ startsector + fat_bpb->bpb_fsinfo, 1, buf);
+ if (rc < 0)
+ {
+ DEBUGF( "fat_mount() - Couldn't read FSInfo (error code %d)\n", rc);
+ return rc * 10 - 4;
+ }
+ fat_bpb->fsinfo.freecount = BYTES2INT32(buf, FSINFO_FREECOUNT);
+ fat_bpb->fsinfo.nextfree = BYTES2INT32(buf, FSINFO_NEXTFREE);
+ }
+
+ /* calculate freecount if unset */
+ if ( fat_bpb->fsinfo.freecount == 0xffffffff )
+ {
+ fat_recalc_free(IF_MV(volume));
+ }
+
+ LDEBUGF("Freecount: %ld\n",fat_bpb->fsinfo.freecount);
+ LDEBUGF("Nextfree: 0x%lx\n",fat_bpb->fsinfo.nextfree);
+ LDEBUGF("Cluster count: 0x%lx\n",fat_bpb->dataclusters);
+ LDEBUGF("Sectors per cluster: %d\n",fat_bpb->bpb_secperclus);
+ LDEBUGF("FAT sectors: 0x%lx\n",fat_bpb->fatsize);
+
+#ifdef HAVE_MULTIVOLUME
+ fat_bpb->mounted = true;
+#endif
+
+ return 0;
+}
+
+#ifdef HAVE_HOTSWAP
+int fat_unmount(int volume, bool flush)
+{
+ int rc;
+ struct bpb* fat_bpb = &fat_bpbs[volume];
+
+ if(flush)
+ {
+ rc = flush_fat(fat_bpb); /* the clean way, while still alive */
+ }
+ else
+ { /* volume is not accessible any more, e.g. MMC removed */
+ int i;
+ mutex_lock(&cache_mutex);
+ for(i = 0;i < FAT_CACHE_SIZE;i++)
+ {
+ struct fat_cache_entry *fce = &fat_cache[i];
+ if(fce->inuse && fce->fat_vol == fat_bpb)
+ {
+ fce->inuse = false; /* discard all from that volume */
+ fce->dirty = false;
+ }
+ }
+ mutex_unlock(&cache_mutex);
+ rc = 0;
+ }
+ fat_bpb->mounted = false;
+ return rc;
+}
+#endif /* #ifdef HAVE_HOTSWAP */
+
+/* @brief Scan FAT partition for free clusters until enougth found
+ * @param[1] size target size in kByte.
+ * @internals
+ * FSINFO struct is NOT updated has full scan not done.
+ */
+static unsigned long fat_check_free_gbl = 0;
+int fat_check_free(IF_MV2(int volume,) unsigned long size)
+{
+#ifndef HAVE_MULTIVOLUME
+ const int volume = 0;
+#endif
+ struct bpb* fat_bpb = &fat_bpbs[volume];
+ unsigned int ret;
+
+ if (size==0) return 1;
+ if (size >= 4*1024*1024) { /* size is in kB */
+ DEBUGF( "fat_check_free() - Error: in arg size exceeds 4Gb\n");
+ return 0;
+ }
+ /* Warning: FAT stack uses 512byte sector, lower layer adapts to partition cfg */
+ fat_check_free_gbl = size / fat_bpb->bpb_secperclus * 2;
+ if (fat_check_free_gbl==0) fat_check_free_gbl = 1;
+ fat_recalc_free(IF_MV(volume));
+ ret = fat_check_free_gbl;
+ fat_check_free_gbl = 0;
+ return ret == 1;
+}
+
+/* @brief Scan full FAT partition for free clusters and update FSINFO struct
+ */
+void fat_recalc_free(IF_MV_NONVOID(int volume))
+{
+#ifndef HAVE_MULTIVOLUME
+ const int volume = 0;
+#endif
+ struct bpb* fat_bpb = &fat_bpbs[volume];
+ long free = 0;
+ unsigned long i;
+#ifdef HAVE_FAT16SUPPORT
+ if (fat_bpb->is_fat16)
+ {
+ for (i = 0; i<fat_bpb->fatsize; i++) {
+ unsigned int j;
+ unsigned short* fat = cache_fat_sector(IF_MV2(fat_bpb,) i, false);
+ for (j = 0; j < CLUSTERS_PER_FAT16_SECTOR; j++) {
+ unsigned int c = i * CLUSTERS_PER_FAT16_SECTOR + j;
+ if ( c > fat_bpb->dataclusters+1 ) /* nr 0 is unused */
+ break;
+
+ if (letoh16(fat[j]) == 0x0000) {
+ free++;
+ if (fat_check_free_gbl) {
+ if (free >= fat_check_free_gbl) { fat_check_free_gbl=1; return; }
+ } else if ( fat_bpb->fsinfo.nextfree == 0xffffffff ) {
+ fat_bpb->fsinfo.nextfree = c;
+ }
+ }
+ }
+ }
+ }
+ else
+#endif /* #ifdef HAVE_FAT16SUPPORT */
+ {
+ for (i = 0; i<fat_bpb->fatsize; i++) {
+ unsigned int j;
+ unsigned long* fat = cache_fat_sector(IF_MV2(fat_bpb,) i, false);
+ for (j = 0; j < CLUSTERS_PER_FAT_SECTOR; j++) {
+ unsigned long c = i * CLUSTERS_PER_FAT_SECTOR + j;
+ if ( c > fat_bpb->dataclusters+1 ) /* nr 0 is unused */
+ break;
+
+ if (!(letoh32(fat[j]) & 0x0fffffff)) {
+ free++;
+ if (fat_check_free_gbl) {
+ if (free >= fat_check_free_gbl) { fat_check_free_gbl=1; return; }
+ } else if ( fat_bpb->fsinfo.nextfree == 0xffffffff ) {
+ fat_bpb->fsinfo.nextfree = c;
+ }
+ }
+ }
+ }
+ }
+ if (fat_check_free_gbl) { fat_check_free_gbl=0; return; }
+ fat_bpb->fsinfo.freecount = free;
+ update_fsinfo(IF_MV(fat_bpb));
+}
+
+static int bpb_is_sane(IF_MV_NONVOID(struct bpb* fat_bpb))
+{
+#ifndef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ if(fat_bpb->bpb_bytspersec % SECTOR_SIZE)
+ {
+ DEBUGF( "bpb_is_sane() - Error: sector size is not sane (%d)\n",
+ fat_bpb->bpb_bytspersec);
+ return -1;
+ }
+ if((long)fat_bpb->bpb_secperclus * (long)fat_bpb->bpb_bytspersec
+ > 128L*1024L)
+ {
+ DEBUGF( "bpb_is_sane() - Error: cluster size is larger than 128K "
+ "(%d * %d = %d)\n",
+ fat_bpb->bpb_bytspersec, fat_bpb->bpb_secperclus,
+ fat_bpb->bpb_bytspersec * fat_bpb->bpb_secperclus);
+ return -2;
+ }
+ if(fat_bpb->bpb_numfats != 2)
+ {
+ DEBUGF( "bpb_is_sane() - Warning: NumFATS is not 2 (%d)\n",
+ fat_bpb->bpb_numfats);
+ }
+ if(fat_bpb->bpb_media != 0xf0 && fat_bpb->bpb_media < 0xf8)
+ {
+ DEBUGF( "bpb_is_sane() - Warning: Non-standard "
+ "media type (0x%02x)\n",
+ fat_bpb->bpb_media);
+ }
+ if(fat_bpb->last_word != 0xaa55)
+ {
+ DEBUGF( "bpb_is_sane() - Error: Last word is not "
+ "0xaa55 (0x%04x)\n", fat_bpb->last_word);
+ return -3;
+ }
+
+ if (fat_bpb->fsinfo.freecount >
+ (fat_bpb->totalsectors - fat_bpb->firstdatasector)/
+ fat_bpb->bpb_secperclus)
+ {
+ DEBUGF( "bpb_is_sane() - Error: FSInfo.Freecount > disk size "
+ "(0x%04lx)\n", fat_bpb->fsinfo.freecount);
+ return -4;
+ }
+
+ return 0;
+}
+
+static void flush_fat_sector(struct fat_cache_entry *fce,
+ unsigned char *sectorbuf)
+{
+ int rc;
+ long secnum;
+
+ /* With multivolume, use only the FAT info from the cached sector! */
+#ifdef HAVE_MULTIVOLUME
+ secnum = fce->secnum + fce->fat_vol->startsector;
+#else
+ secnum = fce->secnum + fat_bpbs[0].startsector;
+#endif
+
+ /* Write to the first FAT */
+ rc = storage_write_sectors(IF_MV2(fce->fat_vol->drive,)
+ secnum, 1,
+ sectorbuf);
+ if(rc < 0)
+ {
+ fat_pprintf("PANIC: flush_fat_sector() - Could not write sector %ld"
+ " (error %d)\n",
+ secnum, rc);
+ }
+#ifdef HAVE_MULTIVOLUME
+ if(fce->fat_vol->bpb_numfats > 1)
+#else
+ if(fat_bpbs[0].bpb_numfats > 1)
+#endif
+ {
+ /* Write to the second FAT */
+#ifdef HAVE_MULTIVOLUME
+ secnum += fce->fat_vol->fatsize;
+#else
+ secnum += fat_bpbs[0].fatsize;
+#endif
+
+ rc = storage_write_sectors(IF_MV2(fce->fat_vol->drive,)
+ secnum, 1, sectorbuf);
+ if(rc < 0)
+ {
+ fat_pprintf("PANIC: flush_fat_sector() - Could not write sector %ld"
+ " (error %d)\n",
+ secnum, rc);
+ }
+ }
+ fce->dirty = false;
+}
+
+/* Note: The returned pointer is only safely valid until the next
+ task switch! (Any subsequent ata read/write may yield.) */
+static void *cache_fat_sector(IF_MV2(struct bpb* fat_bpb,)
+ long fatsector, bool dirty)
+{
+#ifndef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ long secnum = fatsector + fat_bpb->bpb_rsvdseccnt;
+ int cache_index = secnum & FAT_CACHE_MASK;
+ struct fat_cache_entry *fce = &fat_cache[cache_index];
+ unsigned char *sectorbuf = (unsigned char*) &fat_cache_sectors[cache_index][0];
+ int rc;
+
+#ifndef CONFIG_U_BOOT
+ mutex_lock(&cache_mutex); /* make changes atomic */
+#endif
+
+ /* Delete the cache entry if it isn't the sector we want */
+ if(fce->inuse && (fce->secnum != secnum
+#ifdef HAVE_MULTIVOLUME
+ || fce->fat_vol != fat_bpb
+#endif
+ ))
+ {
+ /* Write back if it is dirty */
+ if(fce->dirty)
+ {
+ flush_fat_sector(fce, sectorbuf);
+ }
+ fce->inuse = false;
+ }
+
+ /* Load the sector if it is not cached */
+ if (!fce->inuse)
+ {
+ rc = storage_read_sectors(IF_MV2(fat_bpb->drive,)
+ secnum + fat_bpb->startsector,1,
+ sectorbuf);
+ if(rc < 0)
+ {
+ DEBUGF( "cache_fat_sector() - Could not read sector %ld"
+ " (error %d)\n", secnum, rc);
+#ifndef CONFIG_U_BOOT
+ mutex_unlock(&cache_mutex);
+#endif
+ return NULL;
+ }
+ fce->inuse = true;
+ fce->secnum = secnum;
+#ifdef HAVE_MULTIVOLUME
+ fce->fat_vol = fat_bpb;
+#endif
+ }
+ if (dirty)
+ fce->dirty = true; /* dirt remains, sticky until flushed */
+#ifndef CONFIG_U_BOOT
+ mutex_unlock(&cache_mutex);
+#endif
+ return sectorbuf;
+}
+
+static unsigned long find_free_cluster(IF_MV2(struct bpb* fat_bpb,)
+ unsigned long startcluster)
+{
+#ifndef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ unsigned long sector;
+ unsigned long offset;
+ unsigned long i;
+
+#ifdef HAVE_FAT16SUPPORT
+ if (fat_bpb->is_fat16)
+ {
+ sector = startcluster / CLUSTERS_PER_FAT16_SECTOR;
+ offset = startcluster % CLUSTERS_PER_FAT16_SECTOR;
+
+ for (i = 0; i<fat_bpb->fatsize; i++) {
+ unsigned int j;
+ unsigned int nr = (i + sector) % fat_bpb->fatsize;
+ unsigned short* fat = cache_fat_sector(IF_MV2(fat_bpb,) nr, false);
+ if (!fat)
+ break;
+ for (j = 0; j < CLUSTERS_PER_FAT16_SECTOR; j++) {
+ int k = (j + offset) % CLUSTERS_PER_FAT16_SECTOR;
+ if (letoh16(fat[k]) == 0x0000) {
+ unsigned int c = nr * CLUSTERS_PER_FAT16_SECTOR + k;
+ /* Ignore the reserved clusters 0 & 1, and also
+ cluster numbers out of bounds */
+ if ( c < 2 || c > fat_bpb->dataclusters+1 )
+ continue;
+ LDEBUGF("find_free_cluster(%x) == %x\n",startcluster,c);
+ fat_bpb->fsinfo.nextfree = c;
+ return c;
+ }
+ }
+ offset = 0;
+ }
+ }
+ else
+#endif /* #ifdef HAVE_FAT16SUPPORT */
+ {
+ sector = startcluster / CLUSTERS_PER_FAT_SECTOR;
+ offset = startcluster % CLUSTERS_PER_FAT_SECTOR;
+
+ for (i = 0; i<fat_bpb->fatsize; i++) {
+ unsigned int j;
+ unsigned long nr = (i + sector) % fat_bpb->fatsize;
+ unsigned long* fat = cache_fat_sector(IF_MV2(fat_bpb,) nr, false);
+ if (!fat)
+ break;
+ for (j = 0; j < CLUSTERS_PER_FAT_SECTOR; j++) {
+ int k = (j + offset) % CLUSTERS_PER_FAT_SECTOR;
+ if (!(letoh32(fat[k]) & 0x0fffffff)) {
+ unsigned long c = nr * CLUSTERS_PER_FAT_SECTOR + k;
+ /* Ignore the reserved clusters 0 & 1, and also
+ cluster numbers out of bounds */
+ if ( c < 2 || c > fat_bpb->dataclusters+1 )
+ continue;
+ LDEBUGF("find_free_cluster(%lx) == %lx\n",startcluster,c);
+ fat_bpb->fsinfo.nextfree = c;
+ return c;
+ }
+ }
+ offset = 0;
+ }
+ }
+
+ LDEBUGF("find_free_cluster(%lx) == 0\n",startcluster);
+ return 0; /* 0 is an illegal cluster number */
+}
+
+static int update_fat_entry(IF_MV2(struct bpb* fat_bpb,) unsigned long entry,
+ unsigned long val)
+{
+#ifndef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+#ifdef HAVE_FAT16SUPPORT
+ if (fat_bpb->is_fat16)
+ {
+ int sector = entry / CLUSTERS_PER_FAT16_SECTOR;
+ int offset = entry % CLUSTERS_PER_FAT16_SECTOR;
+ unsigned short *sec;
+
+ val &= 0xFFFF;
+
+ LDEBUGF("update_fat_entry(%x,%x)\n",entry,val);
+
+ if (entry == val)
+ fat_pprintf("PANIC: Creating FAT loop: entry = %lx, val = %lx\n", entry, val);
+
+ if (entry < 2)
+ fat_pprintf("PANIC: Updating reserved FAT entry %ld\n", entry);
+
+ sec = cache_fat_sector(IF_MV2(fat_bpb,) sector, true);
+ if (!sec)
+ {
+ DEBUGF( "update_fat_entry() - Could not cache sector %d\n", sector);
+ return -1;
+ }
+
+ if (val) {
+ if (letoh16(sec[offset]) == 0x0000 && fat_bpb->fsinfo.freecount > 0)
+ fat_bpb->fsinfo.freecount--;
+ }
+ else {
+ if (letoh16(sec[offset]))
+ fat_bpb->fsinfo.freecount++;
+ }
+
+ LDEBUGF("update_fat_entry: %d free clusters\n",
+ fat_bpb->fsinfo.freecount);
+
+ sec[offset] = htole16(val);
+ }
+ else
+#endif /* #ifdef HAVE_FAT16SUPPORT */
+ {
+ long sector = entry / CLUSTERS_PER_FAT_SECTOR;
+ int offset = entry % CLUSTERS_PER_FAT_SECTOR;
+ unsigned long *sec;
+
+ LDEBUGF("update_fat_entry(%lx,%lx)\n",entry,val);
+
+ if (entry == val)
+ fat_pprintf("PANIC: Creating FAT loop: entry = %lx, val = %lx\n",entry, val);
+
+ if (entry < 2)
+ fat_pprintf("PANIC: Updating reserved FAT entry %ld\n", entry);
+
+ sec = cache_fat_sector(IF_MV2(fat_bpb,) sector, true);
+ if (!sec)
+ {
+ DEBUGF("update_fat_entry() - Could not cache sector %ld\n", sector);
+ return -1;
+ }
+
+ if (val) {
+ if (!(letoh32(sec[offset]) & 0x0fffffff) &&
+ fat_bpb->fsinfo.freecount > 0)
+ fat_bpb->fsinfo.freecount--;
+ }
+ else {
+ if (letoh32(sec[offset]) & 0x0fffffff)
+ fat_bpb->fsinfo.freecount++;
+ }
+
+ LDEBUGF("update_fat_entry: %ld free clusters\n",
+ fat_bpb->fsinfo.freecount);
+
+ /* don't change top 4 bits */
+ sec[offset] &= htole32(0xf0000000);
+ sec[offset] |= htole32(val & 0x0fffffff);
+ }
+
+ return 0;
+}
+
+static long read_fat_entry(IF_MV2(struct bpb* fat_bpb,) unsigned long entry)
+{
+#ifdef HAVE_FAT16SUPPORT
+#ifndef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ if (fat_bpb->is_fat16)
+ {
+ int sector = entry / CLUSTERS_PER_FAT16_SECTOR;
+ int offset = entry % CLUSTERS_PER_FAT16_SECTOR;
+ unsigned short *sec;
+
+ sec = cache_fat_sector(IF_MV2(fat_bpb,) sector, false);
+ if (!sec)
+ {
+ DEBUGF( "read_fat_entry() - Could not cache sector %d\n", sector);
+ return -1;
+ }
+
+ return letoh16(sec[offset]);
+ }
+ else
+#endif /* #ifdef HAVE_FAT16SUPPORT */
+ {
+ long sector = entry / CLUSTERS_PER_FAT_SECTOR;
+ int offset = entry % CLUSTERS_PER_FAT_SECTOR;
+ unsigned long *sec;
+
+ sec = cache_fat_sector(IF_MV2(fat_bpb,) sector, false);
+ if (!sec)
+ {
+ DEBUGF( "read_fat_entry() - Could not cache sector %ld\n", sector);
+ return -1;
+ }
+
+ return letoh32(sec[offset]) & 0x0fffffff;
+ }
+}
+
+static long get_next_cluster(IF_MV2(struct bpb* fat_bpb,) long cluster)
+{
+ long next_cluster;
+ long eof_mark = FAT_EOF_MARK;
+
+#ifdef HAVE_FAT16SUPPORT
+#ifndef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ if (fat_bpb->is_fat16)
+ {
+ eof_mark &= 0xFFFF; /* only 16 bit */
+ if (cluster < 0) /* FAT16 root dir */
+ return cluster + 1; /* don't use the FAT */
+ }
+#endif
+ next_cluster = read_fat_entry(IF_MV2(fat_bpb,) cluster);
+
+ /* is this last cluster in chain? */
+ if (next_cluster >= eof_mark)
+ return 0;
+ else
+ return next_cluster;
+}
+
+static int update_fsinfo(IF_MV_NONVOID(struct bpb* fat_bpb))
+{
+#ifndef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ unsigned char fsinfo[SECTOR_SIZE];
+ unsigned long *intptr;
+ int rc;
+
+#ifdef HAVE_FAT16SUPPORT
+ if (fat_bpb->is_fat16)
+ return 0; /* FAT16 has no FsInfo */
+#endif /* #ifdef HAVE_FAT16SUPPORT */
+
+ /* update fsinfo */
+ rc = storage_read_sectors(IF_MV2(fat_bpb->drive,)
+ fat_bpb->startsector + fat_bpb->bpb_fsinfo, 1,fsinfo);
+ if (rc < 0)
+ {
+ DEBUGF( "flush_fat() - Couldn't read FSInfo (error code %d)\n", rc);
+ return rc * 10 - 1;
+ }
+ intptr = (unsigned long*)&(fsinfo[FSINFO_FREECOUNT]);
+ *intptr = htole32(fat_bpb->fsinfo.freecount);
+
+ intptr = (unsigned long*)&(fsinfo[FSINFO_NEXTFREE]);
+ *intptr = htole32(fat_bpb->fsinfo.nextfree);
+
+ rc = storage_write_sectors(IF_MV2(fat_bpb->drive,)
+ fat_bpb->startsector + fat_bpb->bpb_fsinfo,1,fsinfo);
+ if (rc < 0)
+ {
+ DEBUGF( "flush_fat() - Couldn't write FSInfo (error code %d)\n", rc);
+ return rc * 10 - 2;
+ }
+
+ return 0;
+}
+
+static int flush_fat(IF_MV_NONVOID(struct bpb* fat_bpb))
+{
+ int i;
+ int rc;
+ unsigned char *sec;
+ LDEBUGF("flush_fat()\n");
+
+#ifndef CONFIG_U_BOOT
+ mutex_lock(&cache_mutex);
+#endif
+ for(i = 0;i < FAT_CACHE_SIZE;i++)
+ {
+ struct fat_cache_entry *fce = &fat_cache[i];
+ if(fce->inuse
+#ifdef HAVE_MULTIVOLUME
+ && fce->fat_vol == fat_bpb
+#endif
+ && fce->dirty)
+ {
+ sec = (unsigned char*) fat_cache_sectors[i];
+ flush_fat_sector(fce, sec);
+ }
+ }
+#ifndef CONFIG_U_BOOT
+ mutex_unlock(&cache_mutex);
+#endif
+
+ rc = update_fsinfo(IF_MV(fat_bpb));
+ if (rc < 0)
+ return rc * 10 - 3;
+
+ return 0;
+}
+
+static void fat_time(unsigned short* date,
+ unsigned short* time,
+ unsigned short* tenth )
+{
+#ifdef HAVE_RTC
+ struct rtc_time tm;
+
+ if (rtc_get(&tm) == 1)
+ return;
+
+ if (date)
+ *date = ((tm.tm_year - 1980) << 9) |
+ ((tm.tm_mon) << 5) |
+ tm.tm_mday;
+
+ if (time)
+ *time = (tm.tm_hour << 11) |
+ (tm.tm_min << 5) |
+ (tm.tm_sec >> 1);
+
+ if (tenth)
+ *tenth = (tm.tm_sec & 1) * 100;
+#else
+ /* non-RTC version returns an increment from the supplied time, or a
+ * fixed standard time/date if no time given as input */
+ bool next_day = false;
+
+ if (time)
+ {
+ if (0 == *time)
+ {
+ /* set to 00:15:00 */
+ *time = (15 << 5);
+ }
+ else
+ {
+ unsigned short mins = (*time >> 5) & 0x003F;
+ unsigned short hours = (*time >> 11) & 0x001F;
+ if ((mins += 10) >= 60)
+ {
+ mins = 0;
+ hours++;
+ }
+ if ((++hours) >= 24)
+ {
+ hours = hours - 24;
+ next_day = true;
+ }
+ *time = (hours << 11) | (mins << 5);
+ }
+ }
+
+ if (date)
+ {
+ if (0 == *date)
+ {
+/* Macros to convert a 2-digit string to a decimal constant.
+ (YEAR), MONTH and DAY are set by the date command, which outputs
+ DAY as 00..31 and MONTH as 01..12. The leading zero would lead to
+ misinterpretation as an octal constant. */
+
+/* FIXME: Tie into U-Boot's date stuff */
+#define YEAR 2006
+#define MONTH 01
+#define DAY 19
+
+#define S100(x) 1 ## x
+#define C2DIG2DEC(x) (S100(x)-100)
+ /* set to build date */
+ *date = ((YEAR - 1980) << 9) | (C2DIG2DEC(MONTH) << 5)
+ | C2DIG2DEC(DAY);
+ }
+ else
+ {
+ unsigned short day = *date & 0x001F;
+ unsigned short month = (*date >> 5) & 0x000F;
+ unsigned short year = (*date >> 9) & 0x007F;
+ if (next_day)
+ {
+ /* do a very simple day increment - never go above 28 days */
+ if (++day > 28)
+ {
+ day = 1;
+ if (++month > 12)
+ {
+ month = 1;
+ year++;
+ }
+ }
+ *date = (year << 9) | (month << 5) | day;
+ }
+ }
+ }
+ if (tenth)
+ *tenth = 0;
+#endif /* HAVE_RTC */
+}
+
+static int write_long_name(struct fat_file* file,
+ unsigned int firstentry,
+ unsigned int numentries,
+ const unsigned char *name,
+ const unsigned char* shortname,
+ bool is_directory)
+{
+ unsigned char buf[SECTOR_SIZE];
+ unsigned char *entry;
+ unsigned int idx = firstentry % DIR_ENTRIES_PER_SECTOR;
+ unsigned int sector = firstentry / DIR_ENTRIES_PER_SECTOR;
+ unsigned char chksum = 0;
+ unsigned int i, j = 0;
+ unsigned int nameidx=0, namelen = utf8length(name);
+ int rc;
+ unsigned short name_utf16[namelen + 1];
+
+ LDEBUGF("write_long_name(file:%lx, first:%d, num:%d, name:%s)\n",
+ file->firstcluster, firstentry, numentries, name);
+
+ rc = fat_seek(file, sector);
+ if (rc < 0)
+ return rc * 10 - 1;
+
+ rc = fat_readwrite(file, 1, buf, false);
+ if (rc < 1) {
+ return rc * 10 - 2;
+ }
+
+ /* calculate shortname checksum */
+ for (i = 11; i > 0; i--)
+ chksum = ((chksum & 1) ? 0x80 : 0) + (chksum >> 1) + shortname[j++];
+
+ /* calc position of last name segment */
+ if (namelen > NAME_BYTES_PER_ENTRY)
+ for (nameidx = 0;
+ nameidx < (namelen - NAME_BYTES_PER_ENTRY);
+ nameidx += NAME_BYTES_PER_ENTRY) ;
+
+ /* we need to convert the name first */
+ /* since it is written in reverse order */
+ for (i = 0; i <= namelen; i++)
+ name = utf8decode(name, &name_utf16[i]);
+
+ for (i = 0; i < numentries; i++) {
+ /* new sector? */
+ if (idx >= DIR_ENTRIES_PER_SECTOR) {
+ /* update current sector */
+ rc = fat_seek(file, sector);
+ if (rc < 0)
+ return rc * 10 - 3;
+
+ rc = fat_readwrite(file, 1, buf, true);
+ if (rc < 1) {
+ return rc * 10 - 4;
+ }
+
+ /* read next sector */
+ rc = fat_readwrite(file, 1, buf, false);
+ if (rc < 0) {
+ LDEBUGF("Failed writing new sector: %d\n",rc);
+ return rc * 10 - 5;
+ }
+ if (rc == 0)
+ /* end of dir */
+ memset(buf, 0, sizeof buf);
+
+ sector++;
+ idx = 0;
+ }
+
+ entry = buf + idx * DIR_ENTRY_SIZE;
+
+ /* verify this entry is free */
+ if (entry[0] && entry[0] != 0xe5)
+ /*
+ * FIXME: Should we halt instead of plowing on?
+ */
+ fat_pprintf("PANIC: Dir entry %d in sector %x is not free! "
+ "%02x %02x %02x %02x",
+ idx, sector,
+ entry[0], entry[1], entry[2], entry[3]);
+
+ memset(entry, 0, DIR_ENTRY_SIZE);
+ if (i + 1 < numentries) {
+ /* longname entry */
+ unsigned int k, l = nameidx;
+
+ entry[FATLONG_ORDER] = numentries - i - 1;
+ if (i == 0) {
+ /* mark this as last long entry */
+ entry[FATLONG_ORDER] |= 0x40;
+
+ /* pad name with 0xffff */
+ for (k=1; k<11; k++) entry[k] = FAT_LONGNAME_PAD_BYTE;
+ for (k=14; k<26; k++) entry[k] = FAT_LONGNAME_PAD_BYTE;
+ for (k=28; k<32; k++) entry[k] = FAT_LONGNAME_PAD_BYTE;
+ };
+ /* set name */
+ for (k = 0; k < 5 && l <= namelen; k++) {
+ entry[k*2 + 1] = (unsigned char)(name_utf16[l] & 0xff);
+ entry[k*2 + 2] = (unsigned char)(name_utf16[l++] >> 8);
+ }
+ for (k = 0; k < 6 && l <= namelen; k++) {
+ entry[k*2 + 14] = (unsigned char)(name_utf16[l] & 0xff);
+ entry[k*2 + 15] = (unsigned char)(name_utf16[l++] >> 8);
+ }
+ for (k = 0; k < 2 && l <= namelen; k++) {
+ entry[k*2 + 28] = (unsigned char)(name_utf16[l] & 0xff);
+ entry[k*2 + 29] = (unsigned char)(name_utf16[l++] >> 8);
+ }
+
+ entry[FATDIR_ATTR] = FAT_ATTR_LONG_NAME;
+ entry[FATDIR_FSTCLUSLO] = 0;
+ entry[FATLONG_TYPE] = 0;
+ entry[FATLONG_CHKSUM] = chksum;
+ LDEBUGF("Longname entry %d: %s\n", idx, name+nameidx);
+ }
+ else {
+ /* shortname entry */
+ unsigned short date = 0, time = 0, tenth = 0;
+ LDEBUGF("Shortname entry: %s\n", shortname);
+ strncpy((char*)(entry + FATDIR_NAME), (char*)shortname, 11);
+ entry[FATDIR_ATTR] = is_directory?FAT_ATTR_DIRECTORY:0;
+ entry[FATDIR_NTRES] = 0;
+
+ fat_time(&date, &time, &tenth);
+ entry[FATDIR_CRTTIMETENTH] = tenth;
+ *(unsigned short*)(entry + FATDIR_CRTTIME) = htole16(time);
+ *(unsigned short*)(entry + FATDIR_WRTTIME) = htole16(time);
+ *(unsigned short*)(entry + FATDIR_CRTDATE) = htole16(date);
+ *(unsigned short*)(entry + FATDIR_WRTDATE) = htole16(date);
+ *(unsigned short*)(entry + FATDIR_LSTACCDATE) = htole16(date);
+ }
+ idx++;
+ nameidx -= NAME_BYTES_PER_ENTRY;
+ }
+
+ /* update last sector */
+ rc = fat_seek(file, sector);
+ if (rc < 0)
+ return rc * 10 - 6;
+
+ rc = fat_readwrite(file, 1, buf, true);
+ if (rc < 1) {
+ return rc * 10 - 7;
+ }
+
+ return 0;
+}
+
+static int fat_checkname(const unsigned char* newname)
+{
+ static const char invalid_chars[] = "\"*/:<>?\\|";
+ int len = strlen((char*)newname);
+ /* More sanity checks are probably needed */
+ if (len > 255 || newname[len - 1] == '.')
+ {
+ return -1;
+ }
+ while (*newname)
+ {
+ if (*newname < ' ' || strchr(invalid_chars, *newname) != NULL)
+ return -1;
+ newname++;
+ }
+ /* check trailing space(s) */
+ if(*(--newname) == ' ')
+ return -1;
+
+ return 0;
+}
+
+static int add_dir_entry(struct fat_dir* dir,
+ struct fat_file *file,
+ const char* name,
+ bool is_directory,
+ bool dotdir)
+{
+#ifdef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[dir->file.volume];
+#else
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ unsigned char buf[SECTOR_SIZE];
+ unsigned char shortname[12];
+ int rc;
+ unsigned int sector;
+ bool done = false;
+ int entries_needed, entries_found = 0;
+ int firstentry;
+
+ LDEBUGF( "add_dir_entry(%s,%lx)\n",
+ name, file->firstcluster);
+
+ /* Don't check dotdirs name for validity */
+ if (dotdir == false) {
+ rc = fat_checkname((unsigned char*)name);
+ if (rc < 0) {
+ /* filename is invalid */
+ return rc * 10 - 1;
+ }
+ }
+
+#ifdef HAVE_MULTIVOLUME
+ file->volume = dir->file.volume; /* inherit the volume, to make sure */
+#endif
+
+ /* The "." and ".." directory entries must not be long names */
+ if (dotdir) {
+ int i;
+ strncpy((char*)shortname, (char*)name, 12);
+ for(i = strlen((char*)shortname); i < 12; i++)
+ shortname[i] = ' ';
+
+ entries_needed = 1;
+ } else {
+ create_dos_name((unsigned char*)name, (unsigned char*)shortname);
+
+ /* one dir entry needed for every 13 bytes of filename,
+ plus one entry for the short name */
+ entries_needed = (utf8length((unsigned char*)name) + (NAME_BYTES_PER_ENTRY-1))
+ / NAME_BYTES_PER_ENTRY + 1;
+ }
+
+ restart:
+ firstentry = -1;
+
+ rc = fat_seek(&dir->file, 0);
+ if (rc < 0)
+ return rc * 10 - 2;
+
+ /* step 1: search for free entries and check for duplicate shortname */
+ for (sector = 0; !done; sector++)
+ {
+ unsigned int i;
+
+ rc = fat_readwrite(&dir->file, 1, buf, false);
+ if (rc < 0) {
+ DEBUGF( "add_dir_entry() - Couldn't read dir"
+ " (error code %d)\n", rc);
+ return rc * 10 - 3;
+ }
+
+ if (rc == 0) { /* current end of dir reached */
+ LDEBUGF("End of dir on cluster boundary\n");
+ break;
+ }
+
+ /* look for free slots */
+ for (i = 0; i < DIR_ENTRIES_PER_SECTOR; i++)
+ {
+ switch (buf[i * DIR_ENTRY_SIZE]) {
+ case 0:
+ entries_found += DIR_ENTRIES_PER_SECTOR - i;
+ LDEBUGF("Found end of dir %d\n",
+ sector * DIR_ENTRIES_PER_SECTOR + i);
+ i = DIR_ENTRIES_PER_SECTOR - 1;
+ done = true;
+ break;
+
+ case 0xe5:
+ entries_found++;
+ LDEBUGF("Found free entry %d (%d/%d)\n",
+ sector * DIR_ENTRIES_PER_SECTOR + i,
+ entries_found, entries_needed);
+ break;
+
+ default:
+ entries_found = 0;
+
+ /* check that our intended shortname doesn't already exist */
+ if (!strncmp((char*)shortname, (char*)(buf + i * DIR_ENTRY_SIZE), 11)) {
+ /* shortname exists already, make a new one */
+ randomize_dos_name(shortname);
+ LDEBUGF("Duplicate shortname, changing to %s\n",
+ shortname);
+
+ /* name has changed, we need to restart search */
+ goto restart;
+ }
+ break;
+ }
+ if (firstentry < 0 && (entries_found >= entries_needed))
+ firstentry = sector * DIR_ENTRIES_PER_SECTOR + i + 1
+ - entries_found;
+ }
+ }
+
+ /* step 2: extend the dir if necessary */
+ if (firstentry < 0)
+ {
+ LDEBUGF("Adding new sector(s) to dir\n");
+ rc = fat_seek(&dir->file, sector);
+ if (rc < 0)
+ return rc * 10 - 4;
+ memset(buf, 0, sizeof buf);
+
+ /* we must clear whole clusters */
+ for (; (entries_found < entries_needed) ||
+ (dir->file.sectornum < (int)fat_bpb->bpb_secperclus); sector++)
+ {
+ if (sector >= (65536 / DIR_ENTRIES_PER_SECTOR))
+ return -5; /* dir too large -- FAT specification */
+
+ rc = fat_readwrite(&dir->file, 1, buf, true);
+ if (rc < 1) { /* No more room or something went wrong */
+ return rc * 10 - 6;
+ }
+
+ entries_found += DIR_ENTRIES_PER_SECTOR;
+ }
+
+ firstentry = sector * DIR_ENTRIES_PER_SECTOR - entries_found;
+ }
+
+ /* step 3: add entry */
+ sector = firstentry / DIR_ENTRIES_PER_SECTOR;
+ LDEBUGF("Adding longname to entry %d in sector %d\n",
+ firstentry, sector);
+
+ rc = write_long_name(&dir->file, firstentry,
+ entries_needed, (unsigned char*)name, shortname, is_directory);
+ if (rc < 0)
+ return rc * 10 - 7;
+
+ /* remember where the shortname dir entry is located */
+ file->direntry = firstentry + entries_needed - 1;
+ file->direntries = entries_needed;
+ file->dircluster = dir->file.firstcluster;
+ LDEBUGF("Added new dir entry %d, using %d slots.\n",
+ file->direntry, file->direntries);
+
+ return 0;
+}
+
+static unsigned char char2dos(unsigned char c, int* randomize)
+{
+ static const char invalid_chars[] = "\"*+,./:;<=>?[\\]|";
+
+ if (c <= 0x20)
+ c = 0; /* Illegal char, remove */
+ else if (strchr(invalid_chars, c) != NULL)
+ {
+ /* Illegal char, replace */
+ c = '_';
+ *randomize = 1; /* as per FAT spec */
+ }
+ else
+ c = toupper(c);
+
+ return c;
+}
+
+static void create_dos_name(const unsigned char *name, unsigned char *newname)
+{
+ int i;
+ unsigned char *ext;
+ int randomize = 0;
+
+ /* Find extension part */
+ ext = (unsigned char*) strrchr((char*)name, '.');
+ if (ext == name) /* handle .dotnames */
+ ext = NULL;
+
+ /* needs to randomize? */
+ if((ext && (strlen((char*)ext) > 4)) ||
+ ((ext ? (unsigned int)(ext-name) : strlen((char*)name)) > 8) )
+ randomize = 1;
+
+ /* Name part */
+ for (i = 0; *name && (!ext || name < ext) && (i < 8); name++)
+ {
+ unsigned char c = char2dos(*name, &randomize);
+ if (c)
+ newname[i++] = c;
+ }
+
+ /* Pad both name and extension */
+ while (i < 11)
+ newname[i++] = ' ';
+
+ if (newname[0] == 0xe5) /* Special kanji character */
+ newname[0] = 0x05;
+
+ if (ext)
+ { /* Extension part */
+ ext++;
+ for (i = 8; *ext && (i < 11); ext++)
+ {
+ unsigned char c = char2dos(*ext, &randomize);
+ if (c)
+ newname[i++] = c;
+ }
+ }
+
+ if(randomize)
+ randomize_dos_name(newname);
+}
+
+static void randomize_dos_name(unsigned char *name)
+{
+ unsigned char* tilde = NULL; /* ~ location */
+ unsigned char* lastpt = NULL; /* last point of filename */
+ unsigned char* nameptr = name; /* working copy of name pointer */
+ unsigned char num[32]; /* holds number as string */
+ int i = 0;
+ int cnt = 1;
+ int numlen;
+ int offset;
+
+ while(i++ < 8)
+ {
+ /* hunt for ~ and where to put it */
+ if((!tilde) && (*nameptr == '~'))
+ tilde = nameptr;
+ if((!lastpt) && ((*nameptr == ' ' || *nameptr == '~')))
+ lastpt = nameptr;
+ nameptr++;
+ }
+ if(tilde)
+ {
+ /* extract current count and increment */
+ memcpy(num,tilde+1,7-(unsigned int)(tilde-name));
+ num[7-(unsigned int)(tilde-name)] = 0;
+ cnt = atoi((char*)num) + 1;
+ }
+ cnt %= 10000000; /* protection */
+ sprintf((char*)num, "~%d", cnt); /* allow room for trailing zero */
+ numlen = strlen((char*)num); /* required space */
+ offset = (unsigned int)(lastpt ? lastpt - name : 8); /* prev startpoint */
+ if(offset > (8-numlen)) offset = 8-numlen; /* correct for new numlen */
+
+ memcpy(&name[offset], num, numlen);
+
+ /* in special case of counter overflow: pad with spaces */
+ for(offset = offset+numlen; offset < 8; offset++)
+ name[offset] = ' ';
+}
+
+static int update_short_entry( struct fat_file* file, long size, int attr )
+{
+ unsigned char buf[SECTOR_SIZE];
+ int sector = file->direntry / DIR_ENTRIES_PER_SECTOR;
+ unsigned char *entry =
+ buf + DIR_ENTRY_SIZE * (file->direntry % DIR_ENTRIES_PER_SECTOR);
+ unsigned long *sizeptr;
+ unsigned short *clusptr;
+ struct fat_file dir;
+ int rc;
+
+ LDEBUGF("update_file_size(cluster:%lx entry:%d size:%ld)\n",
+ file->firstcluster, file->direntry, size);
+
+ /* create a temporary file handle for the dir holding this file */
+ rc = fat_open(IF_MV2(file->volume,) file->dircluster, &dir, NULL);
+ if (rc < 0)
+ return rc * 10 - 1;
+
+ rc = fat_seek(&dir, sector);
+ if (rc < 0)
+ return rc * 10 - 2;
+
+ rc = fat_readwrite(&dir, 1, buf, false);
+ if (rc < 1) {
+ return rc * 10 - 3;
+ }
+
+ if (!entry[0] || entry[0] == 0xe5)
+ fat_pprintf("PANIC: Updating size on empty dir entry %d\n",
+ file->direntry);
+
+ entry[FATDIR_ATTR] = attr & 0xFF;
+
+ clusptr = (unsigned short*)(entry + FATDIR_FSTCLUSHI);
+ *clusptr = htole16(file->firstcluster >> 16);
+
+ clusptr = (unsigned short*)(entry + FATDIR_FSTCLUSLO);
+ *clusptr = htole16(file->firstcluster & 0xffff);
+
+ sizeptr = (unsigned long*)(entry + FATDIR_FILESIZE);
+ *sizeptr = htole32(size);
+
+ {
+#ifdef HAVE_RTC
+ unsigned short time = 0;
+ unsigned short date = 0;
+#else
+ /* get old time to increment from */
+ unsigned short time = htole16(*(unsigned short*)(entry+FATDIR_WRTTIME));
+ unsigned short date = htole16(*(unsigned short*)(entry+FATDIR_WRTDATE));
+#endif
+ fat_time(&date, &time, NULL);
+ *(unsigned short*)(entry + FATDIR_WRTTIME) = htole16(time);
+ *(unsigned short*)(entry + FATDIR_WRTDATE) = htole16(date);
+ *(unsigned short*)(entry + FATDIR_LSTACCDATE) = htole16(date);
+ }
+
+ rc = fat_seek(&dir, sector);
+ if (rc < 0)
+ return rc * 10 - 4;
+
+ rc = fat_readwrite(&dir, 1, buf, true);
+ if (rc < 1) {
+ return rc * 10 - 5;
+ }
+ return 0;
+}
+
+static int parse_direntry(struct fat_direntry *de, const unsigned char *buf)
+{
+ int i = 0, j = 0;
+ unsigned char c;
+ bool lowercase;
+
+ memset(de, 0, sizeof (struct fat_direntry));
+ de->attr = buf[FATDIR_ATTR];
+ de->crttimetenth = buf[FATDIR_CRTTIMETENTH];
+ de->crtdate = BYTES2INT16(buf, FATDIR_CRTDATE);
+ de->crttime = BYTES2INT16(buf, FATDIR_CRTTIME);
+ de->wrtdate = BYTES2INT16(buf, FATDIR_WRTDATE);
+ de->wrttime = BYTES2INT16(buf, FATDIR_WRTTIME);
+ de->filesize = BYTES2INT32(buf, FATDIR_FILESIZE);
+ de->firstcluster = ((long)(unsigned)BYTES2INT16(buf,FATDIR_FSTCLUSLO)) |
+ ((long) (unsigned) BYTES2INT16(buf, FATDIR_FSTCLUSHI) << 16);
+ /* The double cast is to prevent a sign-extension to be done on CalmRISC16.
+ (the result of the shift is always considered signed) */
+
+ /* fix the name */
+ lowercase = (buf[FATDIR_NTRES] & FAT_NTRES_LC_NAME);
+ c = buf[FATDIR_NAME];
+ if (c == 0x05) /* special kanji char */
+ c = 0xe5;
+ i = 0;
+ while (c != ' ') {
+ de->name[j++] = lowercase ? tolower(c) : c;
+ if (++i >= 8)
+ break;
+ c = buf[FATDIR_NAME + i];
+ }
+ if (buf[FATDIR_NAME + 8] != ' ') {
+ lowercase = (buf[FATDIR_NTRES] & FAT_NTRES_LC_EXT);
+ de->name[j++] = '.';
+ for (i = 8; (i < 11) && ((c = buf[FATDIR_NAME+i]) != ' '); i++)
+ de->name[j++] = lowercase ? tolower(c) : c;
+ }
+ return 1;
+}
+
+int fat_open(IF_MV2(int volume,)
+ long startcluster,
+ struct fat_file *file,
+ const struct fat_dir* dir)
+{
+ file->firstcluster = startcluster;
+ file->lastcluster = startcluster;
+ file->lastsector = 0;
+ file->clusternum = 0;
+ file->sectornum = 0;
+ file->eof = false;
+#ifdef HAVE_MULTIVOLUME
+ file->volume = volume;
+ /* fixme: remove error check when done */
+ if (volume >= NUM_VOLUMES || !fat_bpbs[volume].mounted)
+ {
+ LDEBUGF("fat_open() illegal volume %d\n", volume);
+ return -1;
+ }
+#endif
+
+ /* remember where the file's dir entry is located */
+ if (dir) {
+ file->direntry = dir->entry - 1;
+ file->direntries = dir->entrycount;
+ file->dircluster = dir->file.firstcluster;
+ }
+ LDEBUGF("fat_open(%lx), entry %d\n",startcluster,file->direntry);
+ return 0;
+}
+
+int fat_create_file(const char* name,
+ struct fat_file* file,
+ struct fat_dir* dir)
+{
+ int rc;
+
+ LDEBUGF("fat_create_file(\"%s\",%lx,%lx)\n",name,(long)file,(long)dir);
+ rc = add_dir_entry(dir, file, name, false, false);
+ if (!rc) {
+ file->firstcluster = 0;
+ file->lastcluster = 0;
+ file->lastsector = 0;
+ file->clusternum = 0;
+ file->sectornum = 0;
+ file->eof = false;
+ }
+
+ return rc;
+}
+
+int fat_create_dir(const char* name,
+ struct fat_dir* newdir,
+ struct fat_dir* dir)
+{
+#ifdef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[dir->file.volume];
+#else
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ unsigned char buf[SECTOR_SIZE];
+ int i;
+ long sector;
+ int rc;
+ struct fat_file dummyfile;
+
+ LDEBUGF("fat_create_dir(\"%s\",%lx,%lx)\n",name,(long)newdir,(long)dir);
+
+ memset(newdir, 0, sizeof (struct fat_dir));
+ memset(&dummyfile, 0, sizeof (struct fat_file));
+
+ /* First, add the entry in the parent directory */
+ rc = add_dir_entry(dir, &newdir->file, name, true, false);
+ if (rc < 0)
+ return rc * 10 - 1;
+
+ /* Allocate a new cluster for the directory */
+ newdir->file.firstcluster = find_free_cluster(IF_MV2(fat_bpb,)
+ fat_bpb->fsinfo.nextfree);
+ if (newdir->file.firstcluster == 0)
+ return -1;
+
+ update_fat_entry(IF_MV2(fat_bpb,) newdir->file.firstcluster, FAT_EOF_MARK);
+
+ /* Clear the entire cluster */
+ memset(buf, 0, sizeof buf);
+ sector = cluster2sec(IF_MV2(fat_bpb,) newdir->file.firstcluster);
+ for(i = 0;i < (int)fat_bpb->bpb_secperclus;i++) {
+ rc = transfer(IF_MV2(fat_bpb,) sector + i, 1, (char*)buf, true );
+ if (rc < 0)
+ return rc * 10 - 2;
+ }
+
+ /* Then add the "." entry */
+ rc = add_dir_entry(newdir, &dummyfile, ".", true, true);
+ if (rc < 0)
+ return rc * 10 - 3;
+ dummyfile.firstcluster = newdir->file.firstcluster;
+ update_short_entry(&dummyfile, 0, FAT_ATTR_DIRECTORY);
+
+ /* and the ".." entry */
+ rc = add_dir_entry(newdir, &dummyfile, "..", true, true);
+ if (rc < 0)
+ return rc * 10 - 4;
+
+ /* The root cluster is cluster 0 in the ".." entry */
+ if(dir->file.firstcluster == fat_bpb->bpb_rootclus)
+ dummyfile.firstcluster = 0;
+ else
+ dummyfile.firstcluster = dir->file.firstcluster;
+ update_short_entry(&dummyfile, 0, FAT_ATTR_DIRECTORY);
+
+ /* Set the firstcluster field in the direntry */
+ update_short_entry(&newdir->file, 0, FAT_ATTR_DIRECTORY);
+
+ rc = flush_fat(IF_MV(fat_bpb));
+ if (rc < 0)
+ return rc * 10 - 5;
+
+ return rc;
+}
+
+int fat_truncate(const struct fat_file *file)
+{
+ /* truncate trailing clusters */
+ long next;
+ long last = file->lastcluster;
+#ifdef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[file->volume];
+#endif
+
+ LDEBUGF("fat_truncate(%lx, %lx)\n", file->firstcluster, last);
+
+ for ( last = get_next_cluster(IF_MV2(fat_bpb,) last); last; last = next ) {
+ next = get_next_cluster(IF_MV2(fat_bpb,) last);
+ update_fat_entry(IF_MV2(fat_bpb,) last,0);
+ }
+ if (file->lastcluster)
+ update_fat_entry(IF_MV2(fat_bpb,) file->lastcluster,FAT_EOF_MARK);
+
+ return 0;
+}
+
+int fat_closewrite(struct fat_file *file, long size, int attr)
+{
+ int rc;
+#ifdef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[file->volume];
+#endif
+ LDEBUGF("fat_closewrite(size=%ld)\n",size);
+
+ if (!size) {
+ /* empty file */
+ if (file->firstcluster) {
+ update_fat_entry(IF_MV2(fat_bpb,) file->firstcluster, 0);
+ file->firstcluster = 0;
+ }
+ }
+
+ if (file->dircluster) {
+ rc = update_short_entry(file, size, attr);
+ if (rc < 0)
+ return rc * 10 - 1;
+ }
+
+ flush_fat(IF_MV(fat_bpb));
+
+#ifdef TEST_FAT
+ if (file->firstcluster) {
+ /* debug */
+#ifdef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[file->volume];
+#else
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ long count = 0;
+ long len;
+ long next;
+ for (next = file->firstcluster; next;
+ next = get_next_cluster(IF_MV2(fat_bpb,) next) ) {
+ LDEBUGF("cluster %ld: %lx\n", count, next);
+ count++;
+ }
+ len = count * fat_bpb->bpb_secperclus * SECTOR_SIZE;
+ LDEBUGF("File is %ld clusters (chainlen=%ld, size=%ld)\n",
+ count, len, size);
+ if ( len > size + fat_bpb->bpb_secperclus * SECTOR_SIZE)
+ fat_pprintf("PANIC: Cluster chain is too long\n");
+ if (len < size)
+ fat_pprintf("PANIC: Cluster chain is too short\n");
+ }
+#endif
+
+ return 0;
+}
+
+static int free_direntries(struct fat_file* file)
+{
+ unsigned char buf[SECTOR_SIZE];
+ struct fat_file dir;
+ int numentries = file->direntries;
+ unsigned int entry = file->direntry - numentries + 1;
+ unsigned int sector = entry / DIR_ENTRIES_PER_SECTOR;
+ int i;
+ int rc;
+
+ /* create a temporary file handle for the dir holding this file */
+ rc = fat_open(IF_MV2(file->volume,) file->dircluster, &dir, NULL);
+ if (rc < 0)
+ return rc * 10 - 1;
+
+ rc = fat_seek(&dir, sector);
+ if (rc < 0)
+ return rc * 10 - 2;
+
+ rc = fat_readwrite(&dir, 1, buf, false);
+ if (rc < 1)
+ return rc * 10 - 3;
+
+ for (i = 0; i < numentries; i++) {
+ LDEBUGF("Clearing dir entry %d (%d/%d)\n",
+ entry, i + 1, numentries);
+ buf[(entry % DIR_ENTRIES_PER_SECTOR) * DIR_ENTRY_SIZE] = 0xe5;
+ entry++;
+
+ if ((entry % DIR_ENTRIES_PER_SECTOR) == 0) {
+ /* flush this sector */
+ rc = fat_seek(&dir, sector);
+ if (rc < 0)
+ return rc * 10 - 4;
+
+ rc = fat_readwrite(&dir, 1, buf, true);
+ if (rc < 1)
+ return rc * 10 - 5;
+
+ if (i + 1 < numentries) {
+ /* read next sector */
+ rc = fat_readwrite(&dir, 1, buf, false);
+ if (rc < 1)
+ return rc * 10 - 6;
+ }
+ sector++;
+ }
+ }
+
+ if (entry % DIR_ENTRIES_PER_SECTOR) {
+ /* flush this sector */
+ rc = fat_seek(&dir, sector);
+ if (rc < 0)
+ return rc * 10 - 7;
+
+ rc = fat_readwrite(&dir, 1, buf, true);
+ if (rc < 1)
+ return rc * 10 - 8;
+ }
+
+ return 0;
+}
+
+int fat_remove(struct fat_file* file)
+{
+ long next, last = file->firstcluster;
+ int rc;
+#ifdef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[file->volume];
+#endif
+
+ LDEBUGF("fat_remove(%lx)\n",last);
+
+ while (last) {
+ next = get_next_cluster(IF_MV2(fat_bpb,) last);
+ update_fat_entry(IF_MV2(fat_bpb,) last,0);
+ last = next;
+ }
+
+ if (file->dircluster) {
+ rc = free_direntries(file);
+ if (rc < 0)
+ return rc * 10 - 1;
+ }
+
+ file->firstcluster = 0;
+ file->dircluster = 0;
+
+ rc = flush_fat(IF_MV(fat_bpb));
+ if (rc < 0)
+ return rc * 10 - 2;
+
+ return 0;
+}
+
+int fat_rename(struct fat_file* file,
+ struct fat_dir *dir,
+ const unsigned char* newname,
+ long size,
+ int attr)
+{
+ int rc;
+ struct fat_dir olddir;
+ struct fat_file newfile = *file;
+ unsigned char buf[SECTOR_SIZE];
+ unsigned char* entry = NULL;
+ unsigned short* clusptr = NULL;
+ unsigned int parentcluster;
+#ifdef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[file->volume];
+
+ if (file->volume != dir->file.volume) {
+ DEBUGF("No rename across volumes!\n");
+ return -1;
+ }
+#else
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+
+ if (!file->dircluster) {
+ DEBUGF("File has no dir cluster!\n");
+ return -2;
+ }
+
+ /* create a temporary file handle */
+ rc = fat_opendir(IF_MV2(file->volume,) &olddir, file->dircluster, NULL);
+ if (rc < 0)
+ return rc * 10 - 1;
+
+ /* create new name */
+ rc = add_dir_entry(dir, &newfile, (char*) newname, false, false);
+ if (rc < 0)
+ return rc * 10 - 2;
+
+ /* write size and cluster link */
+ rc = update_short_entry(&newfile, size, attr);
+ if (rc < 0)
+ return rc * 10 - 3;
+
+ /* remove old name */
+ rc = free_direntries(file);
+ if (rc < 0)
+ return rc * 10 - 4;
+
+ rc = flush_fat(IF_MV(fat_bpb));
+ if (rc < 0)
+ return rc * 10 - 5;
+
+ /* if renaming a directory, update the .. entry to make sure
+ it points to its parent directory (we don't check if it was a move) */
+ if(FAT_ATTR_DIRECTORY == attr) {
+ /* open the dir that was renamed, we re-use the olddir struct */
+ rc = fat_opendir(IF_MV2(file->volume,) &olddir, newfile.firstcluster,
+ NULL);
+ if (rc < 0)
+ return rc * 10 - 6;
+
+ /* get the first sector of the dir */
+ rc = fat_seek(&olddir.file, 0);
+ if (rc < 0)
+ return rc * 10 - 7;
+
+ rc = fat_readwrite(&olddir.file, 1, buf, false);
+ if (rc < 0)
+ return rc * 10 - 8;
+
+ /* parent cluster is 0 if parent dir is the root - FAT spec (p.29) */
+ if(dir->file.firstcluster == fat_bpb->bpb_rootclus)
+ parentcluster = 0;
+ else
+ parentcluster = dir->file.firstcluster;
+
+ entry = buf + DIR_ENTRY_SIZE;
+ if(strncmp(".. ", (char*)entry, 11))
+ {
+ /* .. entry must be second entry according to FAT spec (p.29) */
+ DEBUGF("Second dir entry is not double-dot!\n");
+ return rc * 10 - 9;
+ }
+ clusptr = (unsigned short*)(entry + FATDIR_FSTCLUSHI);
+ *clusptr = htole16(parentcluster >> 16);
+
+ clusptr = (unsigned short*)(entry + FATDIR_FSTCLUSLO);
+ *clusptr = htole16(parentcluster & 0xffff);
+
+ /* write back this sector */
+ rc = fat_seek(&olddir.file, 0);
+ if (rc < 0)
+ return rc * 10 - 7;
+
+ rc = fat_readwrite(&olddir.file, 1, buf, true);
+ if (rc < 1)
+ return rc * 10 - 8;
+ }
+
+ return 0;
+}
+
+static long next_write_cluster(struct fat_file* file,
+ long oldcluster,
+ long* newsector)
+{
+#ifdef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[file->volume];
+#else
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ long cluster = 0;
+ long sector;
+
+ LDEBUGF("next_write_cluster(%lx,%lx)\n",file->firstcluster, oldcluster);
+
+ if (oldcluster)
+ cluster = get_next_cluster(IF_MV2(fat_bpb,) oldcluster);
+
+ if (!cluster) {
+ if (oldcluster > 0)
+ cluster = find_free_cluster(IF_MV2(fat_bpb,) oldcluster+1);
+ else if (oldcluster == 0)
+ cluster = find_free_cluster(IF_MV2(fat_bpb,)
+ fat_bpb->fsinfo.nextfree);
+#ifdef HAVE_FAT16SUPPORT
+ else /* negative, pseudo-cluster of the root dir */
+ return 0; /* impossible to append something to the root */
+#endif
+
+ if (cluster) {
+ if (oldcluster)
+ update_fat_entry(IF_MV2(fat_bpb,) oldcluster, cluster);
+ else
+ file->firstcluster = cluster;
+ update_fat_entry(IF_MV2(fat_bpb,) cluster, FAT_EOF_MARK);
+ }
+ else {
+#ifdef TEST_FAT
+ if (fat_bpb->fsinfo.freecount>0)
+ fat_pprintf("PANIC: There is free space, but find_free_cluster() "
+ "didn't find it!\n");
+#endif
+ DEBUGF("next_write_cluster(): Disk full!\n");
+ return 0;
+ }
+ }
+ sector = cluster2sec(IF_MV2(fat_bpb,) cluster);
+ if (sector < 0)
+ return 0;
+
+ *newsector = sector;
+ return cluster;
+}
+
+static int transfer(IF_MV2(struct bpb* fat_bpb,)
+ unsigned long start, long count, char* buf, bool write )
+{
+#ifndef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ int rc;
+
+ LDEBUGF("transfer(s=%lx, c=%lx, %s)\n",
+ start+ fat_bpb->startsector, count, write?"write":"read");
+ if (write) {
+ unsigned long firstallowed;
+#ifdef HAVE_FAT16SUPPORT
+ if (fat_bpb->is_fat16)
+ firstallowed = fat_bpb->rootdirsector;
+ else
+#endif
+ firstallowed = fat_bpb->firstdatasector;
+
+ if (start < firstallowed)
+ fat_pprintf("Write %ld before data\n", firstallowed - start);
+ if (start + count > fat_bpb->totalsectors)
+ fat_pprintf("Write %ld after data\n",
+ start + count - fat_bpb->totalsectors);
+ rc = storage_write_sectors(IF_MV2(fat_bpb->drive,)
+ start + fat_bpb->startsector, count, buf);
+ }
+ else
+ rc = storage_read_sectors(IF_MV2(fat_bpb->drive,)
+ start + fat_bpb->startsector, count, buf);
+ if (rc < 0) {
+ DEBUGF( "transfer() - Couldn't %s sector %lx"
+ " (error code %d)\n",
+ write ? "write":"read", start, rc);
+ return rc;
+ }
+ return 0;
+}
+
+
+long fat_readwrite( struct fat_file *file, long sectorcount,
+ void* buf, bool write )
+{
+#ifdef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[file->volume];
+#else
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ long cluster = file->lastcluster;
+ long sector = file->lastsector;
+ long clusternum = file->clusternum;
+ long numsec = file->sectornum;
+ bool eof = file->eof;
+ long first = 0, last = 0;
+ long i;
+ int rc;
+
+ LDEBUGF( "fat_readwrite(file:%lx,count:0x%lx,buf:%lx,%s)\n",
+ file->firstcluster,sectorcount,(long)buf,write?"write":"read");
+ LDEBUGF( "fat_readwrite: sec=%lx numsec=%ld eof=%d\n",
+ sector,numsec, eof?1:0);
+
+ if (eof && !write)
+ return 0;
+
+ /* find sequential sectors and write them all at once */
+ for (i = 0; (i < sectorcount) && (sector > -1); i++) {
+ numsec++;
+ if ( numsec > (long)fat_bpb->bpb_secperclus || !cluster ) {
+ long oldcluster = cluster;
+ long oldsector = sector;
+ long oldnumsec = numsec;
+ if (write)
+ cluster = next_write_cluster(file, cluster, &sector);
+ else {
+ cluster = get_next_cluster(IF_MV2(fat_bpb,) cluster);
+ sector = cluster2sec(IF_MV2(fat_bpb,) cluster);
+ }
+
+ clusternum++;
+ numsec = 1;
+
+ if (!cluster) {
+ eof = true;
+ if (write) {
+ /* remember last cluster, in case
+ we want to append to the file */
+ sector = oldsector;
+ cluster = oldcluster;
+ numsec = oldnumsec;
+ clusternum--;
+ i = -1; /* Error code */
+ break;
+ }
+ }
+ else
+ eof = false;
+ }
+ else {
+ if (sector)
+ sector++;
+ else {
+ /* look up first sector of file */
+ sector = cluster2sec(IF_MV2(fat_bpb,) file->firstcluster);
+ numsec = 1;
+#ifdef HAVE_FAT16SUPPORT
+ if (file->firstcluster < 0)
+ { /* FAT16 root dir */
+ sector += fat_bpb->rootdiroffset;
+ numsec += fat_bpb->rootdiroffset;
+ }
+#endif
+ }
+ }
+
+ if (!first)
+ first = sector;
+
+ if ( ((sector != first) && (sector != last+1)) || /* not sequential */
+ (last-first+1 == 256) ) { /* max 256 sectors per ata request */
+ long count = last - first + 1;
+ rc = transfer(IF_MV2(fat_bpb,) first, count, buf, write );
+ if (rc < 0)
+ return rc * 10 - 1;
+
+ buf = (char *) buf + count * SECTOR_SIZE;
+ first = sector;
+ }
+
+ if ((i == sectorcount - 1) && /* last sector requested */
+ (!eof))
+ {
+ long count = sector - first + 1;
+ rc = transfer(IF_MV2(fat_bpb,) first, count, buf, write );
+ if (rc < 0)
+ return rc * 10 - 2;
+ }
+
+ last = sector;
+ }
+
+ file->lastcluster = cluster;
+ file->lastsector = sector;
+ file->clusternum = clusternum;
+ file->sectornum = numsec;
+ file->eof = eof;
+
+ /* if eof, don't report last block as read/written */
+ if (eof)
+ i--;
+
+ DEBUGF("Sectors written: %ld\n", i);
+ return i;
+}
+
+int fat_seek(struct fat_file *file, unsigned long seeksector )
+{
+#ifdef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[file->volume];
+#else
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ long clusternum = 0, numclusters = 0, sectornum = 0, sector = 0;
+ long cluster = file->firstcluster;
+ long i;
+
+#ifdef HAVE_FAT16SUPPORT
+ if (cluster < 0) /* FAT16 root dir */
+ seeksector += fat_bpb->rootdiroffset;
+#endif
+
+ file->eof = false;
+ if (seeksector) {
+ /* we need to find the sector BEFORE the requested, since
+ the file struct stores the last accessed sector */
+ seeksector--;
+ numclusters = clusternum = seeksector / fat_bpb->bpb_secperclus;
+ sectornum = seeksector % fat_bpb->bpb_secperclus;
+
+ if (file->clusternum && clusternum >= file->clusternum)
+ {
+ cluster = file->lastcluster;
+ numclusters -= file->clusternum;
+ }
+
+ for (i = 0; i < numclusters; i++) {
+ cluster = get_next_cluster(IF_MV2(fat_bpb,) cluster);
+ if (!cluster) {
+ DEBUGF("Seeking beyond the end of the file! "
+ "(sector %ld, cluster %ld)\n", seeksector, i);
+ return -1;
+ }
+ }
+
+ sector = cluster2sec(IF_MV2(fat_bpb,) cluster) + sectornum;
+ }
+ else {
+ sectornum = -1;
+ }
+
+ LDEBUGF("fat_seek(%lx, %lx) == %lx, %lx, %lx\n",
+ file->firstcluster, seeksector, cluster, sector, sectornum);
+
+ file->lastcluster = cluster;
+ file->lastsector = sector;
+ file->clusternum = clusternum;
+ file->sectornum = sectornum + 1;
+ return 0;
+}
+
+int fat_opendir(IF_MV2(int volume,)
+ struct fat_dir *dir, unsigned long startcluster,
+ const struct fat_dir *parent_dir)
+{
+#ifdef HAVE_MULTIVOLUME
+ struct bpb* fat_bpb = &fat_bpbs[volume];
+ /* fixme: remove error check when done */
+ if (volume >= NUM_VOLUMES || !fat_bpbs[volume].mounted)
+ {
+ LDEBUGF("fat_open() illegal volume %d\n", volume);
+ return -1;
+ }
+#else
+ struct bpb* fat_bpb = &fat_bpbs[0];
+#endif
+ int rc;
+
+ dir->entry = 0;
+ dir->sector = 0;
+
+ if (startcluster == 0)
+ startcluster = fat_bpb->bpb_rootclus;
+
+ rc = fat_open(IF_MV2(volume,) startcluster, &dir->file, parent_dir);
+ if(rc)
+ {
+ DEBUGF( "fat_opendir() - Couldn't open dir"
+ " (error code %d)\n", rc);
+ return rc * 10 - 1;
+ }
+
+ return 0;
+}
+
+#if 0 /* routine unicode2iso() is not used in current release */
+/* convert from unicode to a single-byte charset */
+static void
+unicode2iso(const unsigned char *unicode, unsigned char *iso, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ int x = i * 2;
+ switch (unicode[x + 1]) {
+ case 0x01: /* latin extended. convert to ISO 8859-2 */
+ case 0x02:
+ iso[i] = unicode2iso8859_2[unicode[x]];
+ break;
+
+ case 0x03: /* greek, convert to ISO 8859-7 */
+ iso[i] = unicode[x] + 0x30;
+ break;
+
+ /*
+ * Sergei says most russians use Win1251, so we will
+ * too. Win1251 differs from ISO 8859-5 by an offset of
+ * 0x10.
+ */
+ case 0x04: /* cyrillic, convert to Win1251 */
+ switch (unicode[x]) {
+ case 1:
+ iso[i] = 168;
+ break;
+
+ case 81:
+ iso[i] = 184;
+ break;
+
+ default:
+ iso[i] = unicode[x] + 0xb0; /* 0xa0 for ISO
+ * 8859-5 */
+ break;
+ }
+ break;
+
+ case 0x05: /* hebrew, convert to ISO 8859-8 */
+ iso[i] = unicode[x] + 0x10;
+ break;
+
+ case 0x06: /* arabic, convert to ISO 8859-6 */
+ case 0x0e: /* thai, convert to ISO 8859-11 */
+ iso[i] = unicode[x] + 0xa0;
+ break;
+
+ default:
+ iso[i] = unicode[x];
+ break;
+ }
+ }
+}
+#endif /* #if 0: routine unicode2iso() is not used in current release */
+
+/* Copies a segment of long file name (UTF-16 LE encoded) to the
+ * destination buffer (UTF-8 encoded). Copying is stopped when
+ * either 0x0000 or 0xffff (FAT pad char) is encountered.
+ * Trailing \0 is also appended at the end of the UTF8-encoded
+ * string.
+ *
+ * utf16src utf16 (little endian) segment to copy
+ * utf16count max number of the utf16-characters to copy
+ * utf8dst where to write UTF8-encoded string to
+ *
+ * returns the number of UTF-16 characters actually copied
+ */
+static int fat_copy_long_name_segment(unsigned char *utf16src,
+ int utf16count, unsigned char *utf8dst) {
+ int cnt = 0;
+ while ((utf16count--) > 0) {
+ unsigned short ucs = utf16src[0] | (utf16src[1] << 8);
+ if ((ucs == 0) || (ucs == FAT_LONGNAME_PAD_UCS)) {
+ break;
+ }
+ utf8dst = utf8encode(ucs, utf8dst);
+ utf16src += 2;
+ cnt++;
+ }
+ *utf8dst = 0;
+ return cnt;
+}
+
+int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry)
+{
+ bool done = false;
+ int i;
+ int rc;
+ unsigned char firstbyte;
+ /* Long file names are stored in special entries. Each entry holds
+ up to 13 characters. Names can be max 255 chars (not bytes!) long
+ hence max 20 entries are required. */
+ int longarray[20];
+ int longs = 0;
+ int sectoridx = 0;
+ unsigned char *cached_buf = dir->sectorcache[0];
+
+ dir->entrycount = 0;
+
+ while(!done)
+ {
+ if ( !(dir->entry % DIR_ENTRIES_PER_SECTOR) || !dir->sector )
+ {
+ rc = fat_readwrite(&dir->file, 1, cached_buf, false);
+ if (rc == 0) {
+ /* eof */
+ entry->name[0] = 0;
+ break;
+ }
+ if (rc < 0) {
+ DEBUGF( "fat_getnext() - Couldn't read dir"
+ " (error code %d)\n", rc);
+ return rc * 10 - 1;
+ }
+ dir->sector = dir->file.lastsector;
+ }
+
+ for (i = dir->entry % DIR_ENTRIES_PER_SECTOR;
+ i < DIR_ENTRIES_PER_SECTOR; i++)
+ {
+ unsigned int entrypos = i * DIR_ENTRY_SIZE;
+
+ firstbyte = cached_buf[entrypos];
+ dir->entry++;
+
+ if (firstbyte == 0xe5) {
+ /* free entry */
+ sectoridx = 0;
+ dir->entrycount = 0;
+ continue;
+ }
+
+ if (firstbyte == 0) {
+ /* last entry */
+ entry->name[0] = 0;
+ dir->entrycount = 0;
+ return 0;
+ }
+
+ dir->entrycount++;
+
+ /* longname entry? */
+ if ((cached_buf[entrypos + FATDIR_ATTR] &
+ FAT_ATTR_LONG_NAME_MASK) == FAT_ATTR_LONG_NAME) {
+ longarray[longs++] = entrypos + sectoridx;
+ }
+ else {
+ if ( parse_direntry(entry,
+ &cached_buf[entrypos]) ) {
+
+ /* don't return volume id entry */
+ if ( (entry->attr &
+ (FAT_ATTR_VOLUME_ID|FAT_ATTR_DIRECTORY))
+ == FAT_ATTR_VOLUME_ID)
+ continue;
+
+ /* replace shortname with longname? */
+ if (longs) {
+ int j;
+ /* This should be enough to hold any name segment
+ utf8-encoded */
+ unsigned char shortname[13]; /* 8+3+dot+\0 */
+ /* Add 1 for trailing \0 */
+ unsigned char longname_utf8segm[6*4 + 1];
+ int longname_utf8len = 0;
+ /* Temporarily store it */
+ strcpy((char*)shortname, (char*)entry->name);
+ entry->name[0] = 0;
+
+ /* iterate backwards through the dir entries */
+ for (j = longs - 1; j >= 0; j--) {
+ unsigned char* ptr = cached_buf;
+ int index = longarray[j];
+ /* current or cached sector? */
+ if ( sectoridx >= SECTOR_SIZE ) {
+ if ( sectoridx >= SECTOR_SIZE*2 ) {
+ if ( ( index >= SECTOR_SIZE ) &&
+ ( index < SECTOR_SIZE*2 ))
+ ptr = dir->sectorcache[1];
+ else
+ ptr = dir->sectorcache[2];
+ }
+ else {
+ if (index < SECTOR_SIZE)
+ ptr = dir->sectorcache[1];
+ }
+
+ index &= SECTOR_SIZE-1;
+ }
+
+ /* Try to append each segment of the long name.
+ Check if we'd exceed the buffer.
+ Also check for FAT padding characters 0xFFFF. */
+ if (fat_copy_long_name_segment(ptr + index + 1, 5,
+ longname_utf8segm) == 0) break;
+ /* logf("SG: %s, EN: %s", longname_utf8segm,
+ entry->name); */
+ longname_utf8len += strlen((char*)longname_utf8segm);
+ if (longname_utf8len < FAT_FILENAME_BYTES)
+ strcat((char*)entry->name, (char*)longname_utf8segm);
+ else
+ break;
+
+ if (fat_copy_long_name_segment(ptr + index + 14, 6,
+ longname_utf8segm) == 0) break;
+ /* logf("SG: %s, EN: %s", longname_utf8segm,
+ entry->name); */
+ longname_utf8len += strlen((char*)longname_utf8segm);
+ if (longname_utf8len < FAT_FILENAME_BYTES)
+ strcat((char*)entry->name, (char*)longname_utf8segm);
+ else
+ break;
+
+ if (fat_copy_long_name_segment(ptr + index + 28, 2,
+ longname_utf8segm) == 0) break;
+ /* logf("SG: %s, EN: %s", longname_utf8segm,
+ entry->name); */
+ longname_utf8len += strlen((char*)longname_utf8segm);
+ if (longname_utf8len < FAT_FILENAME_BYTES)
+ strcat((char*)entry->name, (char*)longname_utf8segm);
+ else
+ break;
+ }
+
+ /* Does the utf8-encoded name fit into the entry? */
+ if (longname_utf8len >= FAT_FILENAME_BYTES) {
+ /* Take the short DOS name. Need to utf8-encode it
+ since it may contain chars from the upper half of
+ the OEM code page which wouldn't be a valid utf8.
+ Beware: this file will be shown with strange
+ glyphs in file browser since unicode 0x80 to 0x9F
+ are control characters. */
+ fat_pprintf("SN-DOS: %s", shortname);
+ unsigned char *utf8;
+ utf8 = iso_decode(shortname, entry->name, -1,
+ strlen((char*)shortname));
+ *utf8 = 0;
+ fat_pprintf("SN: %s", entry->name);
+ } else {
+ /* logf("LN: %s", entry->name);
+ logf("LNLen: %d (%c)", longname_utf8len,
+ entry->name[0]); */
+ }
+ }
+ done = true;
+ sectoridx = 0;
+ i++;
+ break;
+ }
+ }
+ }
+
+ /* save this sector, for longname use */
+ if (sectoridx)
+ memcpy( dir->sectorcache[2], dir->sectorcache[0], SECTOR_SIZE );
+ else
+ memcpy( dir->sectorcache[1], dir->sectorcache[0], SECTOR_SIZE );
+ sectoridx += SECTOR_SIZE;
+
+ }
+ return 0;
+}
+
+unsigned int fat_get_cluster_size(IF_MV_NONVOID(int volume))
+{
+#ifndef HAVE_MULTIVOLUME
+ const int volume = 0;
+#endif
+ struct bpb* fat_bpb = &fat_bpbs[volume];
+ return fat_bpb->bpb_secperclus * SECTOR_SIZE;
+}
+
+#ifdef HAVE_MULTIVOLUME
+bool fat_ismounted(int volume)
+{
+ return (volume<NUM_VOLUMES && fat_bpbs[volume].mounted);
+}
+#endif
diff --git a/fs/fat/rockbox_file.c b/fs/fat/rockbox_file.c
new file mode 100644
index 000000000..452805dc9
--- /dev/null
+++ b/fs/fat/rockbox_file.c
@@ -0,0 +1,831 @@
+/***************************************************************************
+ * Copyright (C) 2002 by Björn Stenberg
+ *
+ * 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.
+ *
+ * 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
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************
+ * Source file dumped from rockbox-3.1 distribution.
+ * $Id: file.c 17847 2008-06-28 18:10:04Z bagder $
+ * Copyright (C) 2002 by Björn Stenberg
+ ****************************************************************************
+ * See file CREDITS for list of people who contributed to the U-boot
+ * project.
+ *
+ * 01/17/2006 Keith Outwater (outwater4@comcast.net) - port to U-Boot using
+ * CVS version 1.66 of 'firmware/common/file.c' from rockbox CVS server.
+ * 01/07/2009 etienne.carriere@stnwireless.com - update u-boot port from rockbox-3.1
+ ****************************************************************************
+ */
+#include <common.h>
+#include <config.h>
+
+#include <asm/errno.h>
+#include <asm/string.h>
+#include <fat.h>
+#include "rockbox_debug.h"
+
+/*- exported ressources ------------------------------------------*/
+/* FIXME: correct prototypes
+ * int creat(const char *pathname, mode_t mode);
+ * int open(const char *pathname, int flags);
+ * int close(int fd);
+ * int fsync(int fd);
+ * int remove(const char *name);
+ * int rename(const char *path, const char *newpath);
+ * int ftruncate(int fd, off_t size);
+ * ssize_t write(int fd, const void *buf, size_t count);
+ * ssize_t read(int fd, void *buf, size_t count);
+ * off_t lseek(int fd, off_t offset, int whence);
+ * off_t filesize(int fd);
+ */
+
+
+#define LDEBUGF fat_dprintf
+#define DEBUGF fat_dprintf
+
+/*- imported ressources ------------------------------------------*/
+extern int errno; /* see board/<board>/<board>.c */
+extern int strcasecmp(const char *s1, const char *s2); /* from rockbox_wrapper.c */
+
+#ifndef __HAVE_ARCH_STRNICMP
+//FIXME: useless
+//extern int strnicmp(const char *s1, const char *s2, size_t len);
+#endif
+
+/*
+ These functions provide a roughly POSIX-compatible file IO API.
+
+ Since the fat32 driver only manages sectors, we maintain a one-sector
+ cache for each open file. This way we can provide byte access without
+ having to re-read the sector each time.
+ The penalty is the RAM used for the cache and slightly more complex code.
+*/
+
+#if !defined(CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES)
+#define CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES 1
+#endif
+
+struct filedesc {
+ unsigned char cache[SECTOR_SIZE];
+ int cacheoffset; /* invariant: 0 <= cacheoffset <= SECTOR_SIZE */
+ long fileoffset;
+ long size;
+ int attr;
+ struct fat_file fatfile;
+ bool busy;
+ bool write;
+ bool dirty;
+ bool trunc;
+};
+
+static struct filedesc openfiles[CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES];
+
+static int fat_flush_cache(int fd);
+
+int creat(const char *pathname)
+{
+ return open(pathname, O_WRONLY | O_CREAT | O_TRUNC);
+}
+
+static int open_internal(const char* pathname, int flags, bool use_cache)
+{
+ DIR_UNCACHED* dir;
+ struct dirent_uncached* entry;
+ int fd;
+ char pathnamecopy[MAX_PATH];
+ char *name;
+ struct filedesc *file = NULL;
+ int rc;
+#ifndef HAVE_DIRCACHE
+ (void)use_cache;
+#endif
+
+ LDEBUGF("open(\"%s\",%d)\n",pathname,flags);
+
+ /* Only absolute paths are supported. */
+ if (pathname[0] != '/') {
+ DEBUGF("'%s' is not an absolute path.\n",pathname);
+ DEBUGF("Only absolute pathnames supported at the moment\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* find a free file descriptor */
+ for ( fd=0; fd<CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES; fd++ )
+ if (!openfiles[fd].busy)
+ break;
+
+ if ( fd == CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES ) {
+ DEBUGF("Too many files open\n");
+ errno = EMFILE;
+ return -2;
+ }
+
+ file = &openfiles[fd];
+ memset(file, 0, sizeof (struct filedesc));
+
+ if (flags & (O_RDWR | O_WRONLY)) {
+ file->write = true;
+
+ if (flags & O_TRUNC)
+ file->trunc = true;
+ }
+ file->busy = true;
+
+#ifdef HAVE_DIRCACHE
+ if (dircache_is_enabled() && !file->write && use_cache)
+ {
+ const struct dircache_entry *ce;
+# ifdef HAVE_MULTIVOLUME
+ int volume = strip_volume(pathname, pathnamecopy);
+# endif
+
+ ce = dircache_get_entry_ptr(pathname);
+ if (!ce)
+ {
+ errno = ENOENT;
+ file->busy = false;
+ return -7;
+ }
+
+ fat_open(IF_MV2(volume,)
+ ce->startcluster,
+ &(file->fatfile),
+ NULL);
+ file->size = ce->size;
+ file->attr = ce->attribute;
+ file->cacheoffset = -1;
+ file->fileoffset = 0;
+
+ return fd;
+ }
+#endif
+
+ strncpy(pathnamecopy, pathname, sizeof (pathnamecopy));
+ pathnamecopy[sizeof (pathnamecopy) - 1] = 0;
+
+ /* locate filename */
+ name = strrchr(pathnamecopy + 1, '/');
+ if (name) {
+ *name = 0;
+ dir = opendir_uncached(pathnamecopy);
+ *name = '/';
+ name++;
+ }
+ else {
+ dir = opendir_uncached("/");
+ name = pathnamecopy + 1;
+ }
+ if (!dir) {
+ DEBUGF("Failed opening dir\n");
+ errno = EIO;
+ file->busy = false;
+ return -4;
+ }
+
+ if (name[0] == 0) {
+ DEBUGF("Empty file name\n");
+ errno = EINVAL;
+ file->busy = false;
+ closedir_uncached(dir);
+ return -5;
+ }
+
+ /* scan dir for name */
+ while ((entry = readdir_uncached(dir))) {
+ if ( !strcasecmp(name, (char*)entry->d_name) ) {
+ fat_open(IF_MV2(dir->fatdir.file.volume,)
+ entry->startcluster,
+ &(file->fatfile),
+ &(dir->fatdir));
+ file->size = file->trunc ? 0 : entry->size;
+ file->attr = entry->attribute;
+ break;
+ }
+ }
+
+ if (!entry) {
+ LDEBUGF("Didn't find file %s\n",name);
+ if (file->write && (flags & O_CREAT)) {
+ rc = fat_create_file(name,
+ &(file->fatfile),
+ &(dir->fatdir));
+ if (rc < 0) {
+ DEBUGF("Couldn't create %s in %s\n",name,pathnamecopy);
+ errno = EIO;
+ file->busy = false;
+ closedir_uncached(dir);
+ return rc * 10 - 6;
+ }
+#ifdef HAVE_DIRCACHE
+ dircache_add_file(pathname, file->fatfile.firstcluster);
+#endif
+ file->size = 0;
+ file->attr = 0;
+ }
+ else {
+ DEBUGF("Couldn't find %s in %s\n",name,pathnamecopy);
+ errno = ENOENT;
+ file->busy = false;
+ closedir_uncached(dir);
+ return -7;
+ }
+ } else {
+ if (file->write && (file->attr & FAT_ATTR_DIRECTORY)) {
+ errno = EISDIR;
+ file->busy = false;
+ closedir_uncached(dir);
+ return -8;
+ }
+ }
+ closedir_uncached(dir);
+
+ file->cacheoffset = -1;
+ file->fileoffset = 0;
+
+ if (file->write && (flags & O_APPEND)) {
+ rc = lseek(fd, 0, SEEK_END);
+ if (rc < 0)
+ return rc * 10 - 9;
+ }
+
+#ifdef HAVE_DIRCACHE
+ if (file->write)
+ dircache_bind(fd, pathname);
+#endif
+
+ return fd;
+}
+
+int open(const char* pathname, int flags)
+{
+ /* By default, use the dircache if available. */
+ return open_internal(pathname, flags, true);
+}
+
+int close(int fd)
+{
+ struct filedesc *file = &openfiles[fd];
+ int rc = 0;
+
+ LDEBUGF("close(%d)\n", fd);
+
+ if (fd < 0 || fd > CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES - 1) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!file->busy) {
+ errno = EBADF;
+ return -2;
+ }
+ if (file->write) {
+ rc = fsync(fd);
+ if (rc < 0)
+ return rc * 10 - 3;
+#ifdef HAVE_DIRCACHE
+ dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
+ dircache_update_filetime(fd);
+#endif
+ }
+
+ file->busy = false;
+ return 0;
+}
+
+int fsync(int fd)
+{
+ struct filedesc *file = &openfiles[fd];
+ int rc = 0;
+
+ LDEBUGF("fsync(%d)\n", fd);
+
+ if (fd < 0 || fd > CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES - 1) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!file->busy) {
+ errno = EBADF;
+ return -2;
+ }
+ if (file->write) {
+ /* flush sector cache */
+ if (file->dirty) {
+ rc = fat_flush_cache(fd);
+ if (rc < 0)
+ {
+ /* when failing, try to close the file anyway */
+ fat_closewrite(&(file->fatfile), file->size, file->attr);
+ return rc * 10 - 3;
+ }
+ }
+
+ /* truncate? */
+ if (file->trunc) {
+ rc = ftruncate(fd, file->size);
+ if (rc < 0)
+ {
+ /* when failing, try to close the file anyway */
+ fat_closewrite(&(file->fatfile), file->size, file->attr);
+ return rc * 10 - 4;
+ }
+ }
+
+ /* tie up all loose ends */
+ rc = fat_closewrite(&(file->fatfile), file->size, file->attr);
+ if (rc < 0)
+ return rc * 10 - 5;
+ }
+ return 0;
+}
+
+int remove(const char* name)
+{
+ int rc;
+ struct filedesc *file;
+ /* Can't use dircache now, because we need to access the fat structures. */
+ int fd = open_internal(name, O_WRONLY, false);
+ if (fd < 0)
+ return fd * 10 - 1;
+
+ file = &openfiles[fd];
+#ifdef HAVE_DIRCACHE
+ dircache_remove(name);
+#endif
+ rc = fat_remove(&(file->fatfile));
+ if (rc < 0) {
+ DEBUGF("Failed removing file: %d\n", rc);
+ errno = EIO;
+ return rc * 10 - 3;
+ }
+
+ file->size = 0;
+
+ rc = close(fd);
+ if (rc < 0)
+ return rc * 10 - 4;
+
+ return 0;
+}
+
+int rename(const char* path, const char* newpath)
+{
+ int rc, fd;
+ DIR_UNCACHED* dir;
+ char *nameptr;
+ char *dirptr;
+ struct filedesc *file;
+ char newpath2[MAX_PATH];
+
+ /* verify new path does not already exist */
+ /* If it is a directory, errno == EISDIR if the name exists */
+ fd = open(newpath, O_RDONLY);
+ if (fd >= 0 || errno == EISDIR) {
+ close(fd);
+ errno = EBUSY;
+ return -1;
+ }
+ close(fd);
+
+ fd = open_internal(path, O_RDONLY, false);
+ if (fd < 0) {
+ errno = EIO;
+ return fd * 10 - 2;
+ }
+
+ /* extract new file name */
+ nameptr = strrchr(newpath, '/');
+ if (nameptr)
+ nameptr++;
+ else
+ return -3;
+
+ /* Extract new path */
+ strcpy(newpath2, newpath);
+
+ dirptr = strrchr(newpath2, '/');
+ if (dirptr)
+ *dirptr = 0;
+ else
+ return -4;
+
+ dirptr = newpath2;
+
+ if (strlen(dirptr) == 0) {
+ dirptr = "/";
+ }
+
+ dir = opendir_uncached(dirptr);
+ if (!dir)
+ return -5;
+
+ file = &openfiles[fd];
+
+ rc = fat_rename(&file->fatfile, &dir->fatdir, (unsigned char*)nameptr,
+ file->size, file->attr);
+#ifdef HAVE_MULTIVOLUME
+ if ( rc == -1) {
+ DEBUGF("Failed renaming file across volumnes: %d\n", rc);
+ errno = EXDEV;
+ return -6;
+ }
+#endif
+ if (rc < 0) {
+ DEBUGF("Failed renaming file: %d\n", rc);
+ errno = EIO;
+ return rc * 10 - 7;
+ }
+#ifdef HAVE_DIRCACHE
+ dircache_rename(path, newpath);
+#endif
+
+ rc = close(fd);
+ if (rc < 0) {
+ errno = EIO;
+ return rc * 10 - 8;
+ }
+
+ rc = closedir_uncached(dir);
+ if (rc<0) {
+ errno = EIO;
+ return rc * 10 - 9;
+ }
+
+ return 0;
+}
+
+int ftruncate(int fd, off_t size)
+{
+ int rc, sector;
+ struct filedesc *file = &openfiles[fd];
+
+ sector = size / SECTOR_SIZE;
+ if (size % SECTOR_SIZE)
+ sector++;
+
+ rc = fat_seek(&(file->fatfile), sector);
+ if (rc < 0) {
+ errno = EIO;
+ return rc * 10 - 1;
+ }
+
+ rc = fat_truncate(&(file->fatfile));
+ if (rc < 0) {
+ errno = EIO;
+ return rc * 10 - 2;
+ }
+
+ file->size = size;
+#ifdef HAVE_DIRCACHE
+ dircache_update_filesize(fd, size, file->fatfile.firstcluster);
+#endif
+
+ return 0;
+}
+
+static int fat_flush_cache(int fd)
+{
+ int rc;
+ struct filedesc *file = &openfiles[fd];
+ long sector = file->fileoffset / SECTOR_SIZE;
+
+ DEBUGF("Flushing dirty sector cache\n");
+
+ /* make sure we are on correct sector */
+ rc = fat_seek(&(file->fatfile), sector);
+ if (rc < 0)
+ return rc * 10 - 3;
+
+ rc = fat_readwrite(&(file->fatfile), 1, file->cache, true);
+
+ if (rc < 0) {
+ if (file->fatfile.eof)
+ errno = ENOSPC;
+
+ return rc * 10 - 2;
+ }
+
+ file->dirty = false;
+
+ return 0;
+}
+
+static int readwrite(int fd, void* buf, long count, bool write)
+{
+ long sectors;
+ long nread = 0;
+ struct filedesc *file = &openfiles[fd];
+ int rc;
+
+ if (fd < 0 || fd > CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES-1) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!file->busy) {
+ errno = EBADF;
+ return -1;
+ }
+
+ LDEBUGF( "readwrite(%d,%lx,%ld,%s)\n",
+ fd, (long) buf, count, write ? "write" : "read");
+
+ /* attempt to read past EOF? */
+ if (!write && count > file->size - file->fileoffset)
+ count = file->size - file->fileoffset;
+
+ /* any head bytes? */
+ if (file->cacheoffset != -1) {
+ int offs = file->cacheoffset;
+ int headbytes = (count < (SECTOR_SIZE - offs)) ? count : (SECTOR_SIZE - offs);
+
+ if (write) {
+ memcpy(file->cache + offs, buf, headbytes);
+ file->dirty = true;
+ }
+ else {
+ memcpy( buf, file->cache + offs, headbytes );
+ }
+
+ if (offs + headbytes == SECTOR_SIZE) {
+ if (file->dirty) {
+ rc = fat_flush_cache(fd);
+ if (rc < 0) {
+ errno = EIO;
+ return rc * 10 - 2;
+ }
+ }
+ file->cacheoffset = -1;
+ }
+ else {
+ file->cacheoffset += headbytes;
+ }
+
+ nread = headbytes;
+ count -= headbytes;
+ }
+
+ /* If the buffer has been modified, either it has been flushed already
+ * (if (offs+headbytes == SECTOR_SIZE)...) or does not need to be (no
+ * more data to follow in this call). Do NOT flush here. */
+
+ /* read/write whole sectors right into/from the supplied buffer */
+ sectors = count / SECTOR_SIZE;
+ if (sectors) {
+ rc = fat_readwrite(&(file->fatfile), sectors,
+ (unsigned char *) buf + nread, write);
+ if (rc < 0) {
+ DEBUGF("Failed read/writing %ld sectors\n",sectors);
+ errno = EIO;
+ if (write && file->fatfile.eof) {
+ DEBUGF("No space left on device\n");
+ errno = ENOSPC;
+ } else {
+ file->fileoffset += nread;
+ }
+ file->cacheoffset = -1;
+ /* adjust file size to length written */
+ if ( write && file->fileoffset > file->size )
+ {
+ file->size = file->fileoffset;
+#ifdef HAVE_DIRCACHE
+ dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
+#endif
+ }
+ return nread ? nread : rc * 10 - 4;
+ }
+ else {
+ if (rc > 0) {
+ nread += rc * SECTOR_SIZE;
+ count -= sectors * SECTOR_SIZE;
+
+ /* if eof, skip tail bytes */
+ if (rc < sectors)
+ count = 0;
+ }
+ else {
+ /* eof */
+ count = 0;
+ }
+
+ file->cacheoffset = -1;
+ }
+ }
+
+ /* any tail bytes? */
+ if (count) {
+ if (write) {
+ if (file->fileoffset + nread < file->size) {
+ /* sector is only partially filled. copy-back from disk */
+ LDEBUGF("Copy-back tail cache\n");
+ rc = fat_readwrite(&(file->fatfile), 1, file->cache, false );
+ if ( rc < 0 ) {
+ DEBUGF("Failed writing\n");
+ errno = EIO;
+ file->fileoffset += nread;
+ file->cacheoffset = -1;
+ /* adjust file size to length written */
+ if ( file->fileoffset > file->size )
+ {
+ file->size = file->fileoffset;
+#ifdef HAVE_DIRCACHE
+ dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
+#endif
+ }
+ return nread ? nread : rc * 10 - 5;
+ }
+ /* seek back one sector to put file position right */
+ rc = fat_seek(&(file->fatfile),
+ (file->fileoffset + nread) /
+ SECTOR_SIZE);
+ if (rc < 0) {
+ DEBUGF("fat_seek() failed\n");
+ errno = EIO;
+ file->fileoffset += nread;
+ file->cacheoffset = -1;
+ /* adjust file size to length written */
+ if ( file->fileoffset > file->size )
+ {
+ file->size = file->fileoffset;
+#ifdef HAVE_DIRCACHE
+ dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
+#endif
+ }
+ return nread ? nread : rc * 10 - 6;
+ }
+ }
+ memcpy( file->cache, (unsigned char*)buf + nread, count );
+ file->dirty = true;
+ }
+ else {
+ rc = fat_readwrite(&(file->fatfile), 1, &(file->cache),false);
+ if (rc < 1) {
+ DEBUGF("Failed caching sector\n");
+ errno = EIO;
+ file->fileoffset += nread;
+ file->cacheoffset = -1;
+ return nread ? nread : rc * 10 - 7;
+ }
+ memcpy( (unsigned char*)buf + nread, file->cache, count );
+ }
+
+ nread += count;
+ file->cacheoffset = count;
+ }
+
+ file->fileoffset += nread;
+ LDEBUGF("fileoffset: %ld\n", file->fileoffset);
+
+ /* adjust file size to length written */
+ if (write && file->fileoffset > file->size)
+ {
+ file->size = file->fileoffset;
+#ifdef HAVE_DIRCACHE
+ dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
+#endif
+ }
+
+ return nread;
+}
+
+ssize_t write(int fd, const void* buf, size_t count)
+{
+ if (!openfiles[fd].write) {
+ errno = EACCES;
+ return -1;
+ }
+ return readwrite(fd, (void *) buf, count, true);
+}
+
+ssize_t read(int fd, void* buf, size_t count)
+{
+ return readwrite(fd, buf, count, false);
+}
+
+off_t lseek(int fd, off_t offset, int whence)
+{
+ off_t pos;
+ long newsector;
+ long oldsector;
+ int sectoroffset;
+ int rc;
+ struct filedesc *file = &openfiles[fd];
+
+ LDEBUGF("lseek(%d,%ld,%d)\n",fd,offset,whence);
+
+ if (fd < 0 || fd > CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES-1) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!file->busy) {
+ errno = EBADF;
+ return -1;
+ }
+
+ switch (whence) {
+ case SEEK_SET:
+ pos = offset;
+ break;
+
+ case SEEK_CUR:
+ pos = file->fileoffset + offset;
+ break;
+
+ case SEEK_END:
+ pos = file->size + offset;
+ break;
+
+ default:
+ errno = EINVAL;
+ return -2;
+ }
+ if ((pos < 0) || (pos > file->size)) {
+ errno = EINVAL;
+ return -3;
+ }
+
+ /* new sector? */
+ newsector = pos / SECTOR_SIZE;
+ oldsector = file->fileoffset / SECTOR_SIZE;
+ sectoroffset = pos % SECTOR_SIZE;
+
+ if ((newsector != oldsector) ||
+ ((file->cacheoffset == -1) && sectoroffset)) {
+
+ if (newsector != oldsector) {
+ if (file->dirty) {
+ rc = fat_flush_cache(fd);
+ if (rc < 0)
+ return rc * 10 - 5;
+ }
+
+ rc = fat_seek(&(file->fatfile), newsector);
+ if (rc < 0) {
+ errno = EIO;
+ return rc * 10 - 4;
+ }
+ }
+ if (sectoroffset) {
+ rc = fat_readwrite(&(file->fatfile), 1,
+ &(file->cache), false);
+ if (rc < 0) {
+ errno = EIO;
+ return rc * 10 - 6;
+ }
+ file->cacheoffset = sectoroffset;
+ }
+ else
+ file->cacheoffset = -1;
+ }
+ else
+ if ( file->cacheoffset != -1 )
+ file->cacheoffset = sectoroffset;
+
+ file->fileoffset = pos;
+
+ return pos;
+}
+
+off_t filesize(int fd)
+{
+ struct filedesc *file = &openfiles[fd];
+
+ if (fd < 0 || fd > CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES-1) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!file->busy) {
+ errno = EBADF;
+ return -1;
+ }
+
+ return file->size;
+}
+#ifdef HAVE_HOTSWAP
+/* release all file handles on a given volume "by force", to avoid leaks */
+int release_files(int volume)
+{
+ struct filedesc* pfile = openfiles;
+ int fd;
+ int closed = 0;
+ for ( fd=0; fd<MAX_OPEN_FILES; fd++, pfile++)
+ {
+ if (pfile->fatfile.volume == volume)
+ {
+ pfile->busy = false; /* mark as available, no further action */
+ closed++;
+ }
+ }
+ return closed; /* return how many we did */
+}
+#endif /* #ifdef HAVE_HOTSWAP */
+
diff --git a/fs/fat/rockbox_wrapper.c b/fs/fat/rockbox_wrapper.c
new file mode 100644
index 000000000..cfd46d602
--- /dev/null
+++ b/fs/fat/rockbox_wrapper.c
@@ -0,0 +1,657 @@
+/*------------------------------------------------------------------------------
+ * (C) Copyright 2006
+ * Keith Outwater, (outwater4@comcast.net)
+ *
+ * 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
+ */
+
+#include <common.h>
+#include <config.h>
+
+/*
+ * FIXME: The pwd, cd and file path builder code needs to be fixed/implemented.
+ */
+
+#include <asm/errno.h>
+#include <asm/string.h>
+#include <fat.h>
+#include <linux/string.h>
+#include "rockbox_debug.h"
+#include <rockbox_mv.h>
+
+/* Routines, exported or not... */
+long file_fat_size(const char *filename);
+
+extern int errno; /* see board/<board>/<board>.c */
+
+const char *month[] = {
+ "(null)",
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+/*
+ * The current working directory.
+ * Should we keep track of this on a per-partiton basis?
+ */
+char file_cwd[ROCKBOX_CWD_LEN + 1] = "/";
+
+#ifndef __HAVE_ARCH_STRNICMP
+/*
+ * Convert a character to lower case
+ */
+__inline__ static char
+_tolower(char c)
+{
+ if ((c >= 'A') && (c <= 'Z')) {
+ c = (c - 'A') + 'a';
+ }
+ return c;
+}
+
+/**
+ * strnicmp - Case insensitive, length-limited string comparison
+ * @s1: One string
+ * @s2: The other string
+ * @len: the maximum number of characters to compare
+ *
+ * FIXME: replaces strnicmp() in rockbox-3.1. check is keep best one
+ */
+int
+strnicmp(const char *s1, const char *s2, size_t len)
+{
+ /*
+ * Yes, Virginia, it had better be unsigned
+ */
+ unsigned char c1, c2;
+
+ c1 = 0;
+ c2 = 0;
+ if (len) {
+ do {
+ c1 = *s1;
+ c2 = *s2;
+ s1++;
+ s2++;
+ if (!c1)
+ break;
+ if (!c2)
+ break;
+ if (c1 == c2)
+ continue;
+ c1 = _tolower(c1);
+ c2 = _tolower(c2);
+ if (c1 != c2)
+ break;
+ } while (--len);
+ }
+ return (int) c1 - (int) c2;
+}
+#endif
+
+/**
+ * strcasecmp - Case insensitive string comparison
+ * @s1: One string
+ * @s2: The other string
+ */
+int strcasecmp(const char *s1, const char *s2)
+{
+ while (*s1 != '\0' && _tolower(*s1) == _tolower(*s2)) {
+ s1++;
+ s2++;
+ }
+
+ return _tolower(*(unsigned char *) s1) - _tolower(*(unsigned char *) s2);
+}
+
+
+/**
+ * strncasecmp - Case insensitive, length-limited string comparison
+ * @s1: One string
+ * @s2: The other string
+ * @len: the maximum number of characters to compare
+ *
+ * FIXME: replaces strnicmp() in rockbox-3.1. check is keep best one
+ */
+int strncasecmp(const char *s1, const char *s2, size_t n)
+{
+ if(!n)
+ return 0;
+
+ while (n-- != 0 && _tolower(*s1) == _tolower(*s2)) {
+ if(n == 0 || *s1 == '\0')
+ break;
+ s1++;
+ s2++;
+ }
+
+ return _tolower(*(unsigned char *) s1) - _tolower(*(unsigned char *) s2);
+}
+
+
+/*
+ * U-Boot 'fat ls' wrapper. Code lifted from rockbox "filetree.c"
+ * Return number of bytes read on success, -1 on error.
+ */
+int
+file_fat_ls(const char *dirname)
+{
+ uint i, max_entries;
+ int name_buffer_used = 0;
+ DIR *dir;
+
+ struct tree_context {
+ int filesindir;
+ int dirsindir;
+ int dirlength;
+ void *dircache; /* unused, see below */
+ char name_buffer[2048];
+ int name_buffer_size;
+ bool dirfull;
+ } c;
+
+ struct entry {
+ short attr; /* FAT attributes + file type flags */
+ unsigned long time_write; /* Last write time */
+ char *name;
+ };
+
+ if (*dirname == '/') {
+ while(*(dirname+1) == '/') dirname++;
+ }
+ if (strlen(dirname) > ROCKBOX_CWD_LEN) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ dir = opendir(dirname);
+ if (!dir) {
+ /* maybe it's a regular file */
+ i = file_fat_size(dirname);
+ if (i != -1) {
+ printf("%9lu %s\n", (ulong) i, dirname);
+ return 0;
+ }
+#ifdef CONFIG_STRERROR
+ fprintf(stderr, "Error: ls() of \"%s\" failed: %s\n",
+ dirname, strerror(errno));
+#endif
+ return -1;
+ }
+
+ c.dirsindir = 0;
+ c.dirfull = false;
+ c.name_buffer_size = sizeof (c.name_buffer) - 1;
+
+ /*
+ * FIXME: figure out max i
+ */
+ max_entries = (strncmp("/", dirname, 2)==0) ? 512 : 0xFFFFFFFF;
+ for (i = 0; i < max_entries; i++) {
+ int len;
+ struct dirent *entry = readdir(dir);
+#if 0 /* dircache unused and not initialized ! */
+ struct entry *dptr =
+ (struct entry *) (c.dircache + i * sizeof (struct entry));
+#endif
+ if (!entry)
+ break;
+
+ len = strlen((char*)entry->d_name);
+ printf("%9lu %s %02d %4d %02d:%02d:%02d [%s] %s\n",
+ entry->size,
+ month[(entry->wrtdate >> 5) & 0xf],
+ entry->wrtdate & 0xf,
+ ((entry->wrtdate >> 9) & 0x7f) + 1980,
+ (entry->wrttime >> 11) & 0x1f,
+ (entry->wrttime >> 5) & 0x3f, entry->wrttime & 0x1f,
+ (entry->attribute & FAT_ATTR_DIRECTORY) ? "dir " : "file",
+ entry->d_name);
+#if 0
+ /*
+ * skip directories . and ..
+ */
+ if ((entry->attribute & FAT_ATTR_DIRECTORY) &&
+ (((len == 1) &&
+ (!strncmp(entry->d_name, ".", 1))) ||
+ ((len == 2) && (!strncmp(entry->d_name, "..", 2))))) {
+ i--;
+ continue;
+ }
+
+ /*
+ * Skip FAT volume ID
+ */
+ if (entry->attribute & FAT_ATTR_VOLUME_ID) {
+ i--;
+ continue;
+ }
+#endif
+
+#if 0 /* dircache unused and not initialized ! */
+ dptr->attr = entry->attribute;
+#endif
+ if (len > c.name_buffer_size - name_buffer_used - 1) {
+ /*
+ * Tell the world that we ran out of buffer space
+ */
+ c.dirfull = true;
+ fat_dprintf("Dir buffer is full\n");
+ break;
+ }
+
+#if 0 /* dircache unused and not initialized ! */
+ dptr->name = &c.name_buffer[name_buffer_used];
+ dptr->time_write =
+ (long) entry->wrtdate << 16 | (long) entry->wrttime;
+ strcpy(dptr->name, (char*)entry->d_name);
+#endif
+ name_buffer_used += len + 1;
+
+ /*
+ * count the remaining dirs
+ */
+#if 0 /* dircache unused and not initialized ! */
+ if (dptr->attr & FAT_ATTR_DIRECTORY)
+#else
+ if (entry->attribute & FAT_ATTR_DIRECTORY)
+#endif
+ c.dirsindir++;
+ }
+
+ c.filesindir = i;
+ c.dirlength = i;
+ closedir(dir);
+ printf("\n%d file%s %d dir%s",
+ c.filesindir, c.filesindir == 1 ? "," : "s,",
+ c.dirsindir, c.dirsindir == 1 ? "\n" : "s\n");
+ return 0;
+}
+
+/*
+ * return target file size (negative if failure)
+ * Return number of bytes of the target file, -1 on error.
+ */
+long
+file_fat_size(const char *filename)
+{
+ int fd;
+ long file_size;
+
+ printf("Reading %s\n", filename);
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+#ifdef CONFIG_STRERROR
+ fprintf(stderr, "Error: open() of \"%s\" failed: %s\n",
+ filename, strerror(errno));
+#endif
+ return -1;
+ }
+
+ file_size = filesize(fd);
+ if (file_size < 0) {
+ fat_eprintf("Call to filesize() failed\n");
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+ return file_size;
+}
+
+
+/*
+ * U-Boot 'file read' wrapper.
+ * Return number of bytes read on success, -1 on error.
+ */
+long
+file_fat_read(const char *filename, void *buffer, unsigned long maxsize)
+{
+ int fd;
+ long bytes;
+ long file_size;
+
+ printf("Reading %s\n", filename);
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+#ifdef CONFIG_STRERROR
+ fprintf(stderr, "Error: open() of \"%s\" failed: %s\n",
+ filename, strerror(errno));
+#endif
+ return -1;
+ }
+
+ /*
+ * If the number of bytes to read is zero, read the entire file.
+ */
+ if (maxsize == 0) {
+ file_size = filesize(fd);
+ if (file_size < 0) {
+ fat_eprintf("Call to filesize() failed\n");
+ return -1;
+ }
+
+ maxsize = (unsigned long) file_size;
+ fat_dprintf("Reading entire file (%lu bytes)\n", maxsize);
+ }
+
+ bytes = (long) read(fd, buffer, (size_t) maxsize);
+ if ((unsigned long) bytes != maxsize) {
+#ifdef CONFIG_STRERROR
+ fprintf(stderr, "Read error: %s\n", strerror(errno));
+#endif
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+ return bytes;
+}
+
+/*
+ * U-Boot 'file write' wrapper.
+ * Return number of bytes written on success, -1 on error.
+ */
+long
+file_fat_write(const char *filename, void *buffer, unsigned long maxsize)
+{
+ int fd;
+ long bytes;
+
+ printf("Writing %s\n", filename);
+ fd = open(filename, O_WRONLY | O_CREAT);
+ if (fd < 0) {
+#ifdef CONFIG_STRERROR
+ fprintf(stderr, "Open of \"%s\" failed: %s\n",
+ filename, strerror(errno));
+#endif
+ return -1;
+ }
+
+ bytes = (long) write(fd, buffer, (size_t) maxsize);
+ if ((unsigned long) bytes != maxsize) {
+#ifdef CONFIG_STRERROR
+ fprintf(stderr, "Write failed: %s\n", strerror(errno));
+#endif
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+ return bytes;
+}
+
+int
+file_fat_pwd(void)
+{
+ printf("%s\n", file_cwd);
+ return 1;
+}
+
+/*
+ * U-Boot 'file rm' wrapper.
+ * Return 1 on success, -1 on error and set 'errno'.
+ */
+int
+file_fat_rm(const char *filename)
+{
+ int rc;
+
+ fat_dprintf("Remove \"%s\"\n", filename);
+ rc = remove(filename);
+ if (rc < 0) {
+#ifdef CONFIG_STRERROR
+ fprintf(stderr, "Removal of \"%s\" failed: %s\n",
+ filename, strerror(errno));
+#endif
+ return -1;
+ }
+
+ return 1;
+}
+
+/*
+ * U-Boot 'file mkdir' wrapper.
+ * Return 1 on success, -1 on error and set 'errno'.
+ */
+int
+file_fat_mkdir(const char *dirname)
+{
+ int rc;
+
+ fat_dprintf("Make dir \"%s\"\n", dirname);
+ rc = mkdir(dirname);
+ if (rc < 0) {
+#ifdef CONFIG_STRERROR
+ fprintf(stderr, "Creation of \"%s\" failed: %s\n",
+ dirname, strerror(errno));
+#endif
+ return -1;
+ }
+
+ return 1;
+}
+
+/*
+ * U-Boot 'file rmdir' wrapper.
+ * Return 1 on success, -1 on error and set 'errno'.
+ */
+int
+file_fat_rmdir(const char *dirname)
+{
+ int rc;
+
+ fat_dprintf("Remove dir \"%s\"\n", dirname);
+ rc = rmdir(dirname);
+ if (rc < 0) {
+#ifdef CONFIG_STRERROR
+ fprintf(stderr, "Removal of \"%s\" failed: %s\n",
+ dirname, strerror(errno));
+#endif
+ return -1;
+ }
+
+ return 1;
+}
+
+static void
+pathcpy(char *dest, const char *src)
+{
+ char *origdest = dest;
+
+ do {
+ if (dest - file_cwd >= CWD_LEN) {
+ *dest = '\0';
+ return;
+ }
+
+ *(dest) = *(src);
+ if (*src == '\0') {
+ if (dest-- != origdest && ISDIRDELIM(*dest)) {
+ *dest = '\0';
+ }
+ return;
+ }
+
+ ++dest;
+ if (ISDIRDELIM(*src)) {
+ while (ISDIRDELIM(*src))
+ src++;
+ } else {
+ src++;
+ }
+ } while (1);
+}
+
+/*
+ * U-Boot 'file cd' wrapper.
+ */
+int
+file_fat_cd(const char *path)
+{
+ if (ISDIRDELIM(*path)) {
+ while (ISDIRDELIM(*path))
+ path++;
+ strncpy(file_cwd + 1, path, CWD_LEN - 1);
+ } else {
+ const char *origpath = path;
+ char *tmpstr = file_cwd;
+ int back = 0;
+
+ while (*tmpstr != '\0')
+ tmpstr++;
+
+ do {
+ tmpstr--;
+ } while (ISDIRDELIM(*tmpstr));
+
+ while (*path == '.') {
+ path++;
+ while (*path == '.') {
+ path++;
+ back++;
+ }
+
+ if (*path != '\0' && !ISDIRDELIM(*path)) {
+ path = origpath;
+ back = 0;
+ break;
+ }
+
+ while (ISDIRDELIM(*path))
+ path++;
+
+ origpath = path;
+ }
+
+ while (back--) {
+ /*
+ * Strip off path component
+ */
+ while (!ISDIRDELIM(*tmpstr)) {
+ tmpstr--;
+ }
+
+ if (tmpstr == file_cwd) {
+ /*
+ * Incremented again right after the loop.
+ */
+ tmpstr--;
+ break;
+ }
+
+ /*
+ * Skip delimiters
+ */
+ while (ISDIRDELIM(*tmpstr))
+ tmpstr--;
+ }
+
+ tmpstr++;
+ if (*path == '\0') {
+ if (tmpstr == file_cwd) {
+ *tmpstr = '/';
+ tmpstr++;
+ }
+
+ *tmpstr = '\0';
+ return 0;
+ }
+
+ *tmpstr = '/';
+ pathcpy(tmpstr + 1, path);
+ }
+
+ printf("New CWD is '%s'\n", file_cwd);
+ return 0;
+}
+
+/*
+ * U-Boot 'file mv' wrapper.
+ * Return 1 on success, -1 on error and set 'errno'.
+ */
+int
+file_fat_mv(const char *oldname, const char *newpath)
+{
+ int rc;
+
+ fat_dprintf("Move \'%s\" to \"%s\"\n", oldname, newpath);
+ rc = rename(oldname, newpath);
+ if (rc < 0) {
+#ifdef CONFIG_STRERROR
+ fprintf(stderr, "Failed to move \"%s\" to \"%s\" : %s\n",
+ oldname, newpath, strerror(errno));
+#endif
+ return -1;
+ }
+
+ return 1;
+}
+
+
+extern int fat_mount(IF_MV2(int volume,) IF_MV2(int drive,) long startsector);
+int rockbox_fat_mount(long startsector);
+/* @brief Map rockbox_fat_mount() (fs/fat/fat.c) on fat_mount() (fs/fat/rockbox_fat.c)
+ */
+int rockbox_fat_mount(long startsector)
+{
+#ifdef HAVE_MULTIVOLUME
+#error "MULTIVOLUME from rockbox package not ported to U-boot"
+#else
+ return fat_mount(IF_MV2(volume,) IF_MV2(drive,) startsector);
+#endif
+}
+
+/* @brief Get filesystem size and free left space
+ * @return partition size in kByte
+ */
+unsigned int rockbox_fat_size(void)
+{
+#ifdef HAVE_MULTIVOLUME
+#error "MULTIVOLUME from rockbox package not ported to U-boot"
+#else
+ unsigned long tsize;
+ fat_size(IF_MV2(int volume,) &tsize, NULL);
+ return (unsigned int) tsize;
+#endif
+}
+
+/* @brief Return available free space.
+ * @param[in] if size==0, compute free space, else check if enougth free space
+ * @return if size==0, ret free space, else return true if enougth free space
+ * @internals
+ * Input and returned size values are formated in kByte.
+ */
+unsigned int rockbox_fat_free(unsigned long size)
+{
+#ifdef HAVE_MULTIVOLUME
+#error "MULTIVOLUME from rockbox package not ported to U-boot"
+#else
+ if (size) {
+ return fat_check_free(IF_MV2(int volume,) size);
+ } else {
+ unsigned long tfree;
+ fat_recalc_free(IF_MV2(int volume,));
+ fat_size(IF_MV2(int volume,) NULL, &tfree);
+ return (unsigned int) tfree;
+ }
+#endif
+}
+
+