From 20503b61a7f73fad8ee97219f7a1e74de3a8a2ac Mon Sep 17 00:00:00 2001 From: Michael Brandt Date: Tue, 20 Jul 2010 15:08:22 +0200 Subject: 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 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 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/3009 --- fs/fat/Makefile | 4 + fs/fat/fat.c | 139 ++- fs/fat/file.c | 18 +- fs/fat/rockbox_debug.h | 39 + fs/fat/rockbox_dir.c | 329 ++++++ fs/fat/rockbox_fat.c | 2801 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fat/rockbox_file.c | 831 ++++++++++++++ fs/fat/rockbox_wrapper.c | 657 +++++++++++ 8 files changed, 4811 insertions(+), 7 deletions(-) create mode 100644 fs/fat/rockbox_debug.h create mode 100644 fs/fat/rockbox_dir.c create mode 100644 fs/fat/rockbox_fat.c create mode 100644 fs/fat/rockbox_file.c create mode 100644 fs/fat/rockbox_wrapper.c (limited to 'fs') 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 #include +/*- 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,10 +1088,16 @@ 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"); @@ -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 #include +#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 +#include + +#include +#include +#include +#include "rockbox_debug.h" + +extern int errno; /* see 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; ddfatdir.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; ddbusy) + 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 +#include + +#include +#include +#include +#include "rockbox_debug.h" +#include + +/* + * 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; istartsector = 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; ifatsize; 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; ifatsize; 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; ifatsize; 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; ifatsize; 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, §or); + 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 +#include + +#include +#include +#include +#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//.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; fdwrite = 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; fdfatfile.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 +#include + +/* + * FIXME: The pwd, cd and file path builder code needs to be fixed/implemented. + */ + +#include +#include +#include +#include +#include "rockbox_debug.h" +#include + +/* Routines, exported or not... */ +long file_fat_size(const char *filename); + +extern int errno; /* see 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 +} + + -- cgit v1.2.3