diff options
author | Michael Brandt <michael.brandt@stericsson.com> | 2010-07-20 15:08:22 +0200 |
---|---|---|
committer | Michael BRANDT <michael.brandt@stericsson.com> | 2010-08-30 20:03:48 +0200 |
commit | 20503b61a7f73fad8ee97219f7a1e74de3a8a2ac (patch) | |
tree | ec58c1f830639a45462fe07219304a06a16b7b54 /fs/fat/rockbox_file.c | |
parent | 3a5b27b58258ff61dfae3167bc46fed0146ddffd (diff) |
Read/write VFAT support from Rockbox-3.3 FAT stack
Add read/write VFAT support from Rockbox-3.3 FAT stack.
It should be also applicable to the unmodified 2009.11 U-Boot release.
Note that is was taken as is from Rockbox and from a older U-Boot
Rockbox patch. "checkpatch" shows very many coding style errors and
warnings, but it is tedious work to clean this up.
To make this patch work an additional mmc_block_write() board support
routine and the errno variable are needed.
Furthermore following defines in the board config header file:
#define CONFIG_ROCKBOX_FAT 1
#define CONFIG_U_BOOT 1
#define CONFIG_SUPPORT_VFAT 1
#define CONFIG_CMD_TREE_FAT
This will be added in a follow-up patch.
This patch is based on the patch from Etienne Carriere
<etienne.carriere@stericsson.com> for the U671x U-Boot:
This commit adds FAT write support to u-boot native read-only FAT code.
Commit initially applied on u-boot-v2009.01
(SHA1: 72d15e705bc3983884105cb7755c7ba80e74a0a5)
Based on FAT stack dumped from Rockbox package v3.1 (www.rockbox.org).
Based on initial Rockbox FAT stack integration in u-boot by Keith Outwater
(outwater@comcast.net).
Current porting is aligned with Rockbox v3.3 FAT stack.
Enable upon config switches:
CONFIG_CMD_FAT
CONFIG_ROCKBOX_FAT
CONFIG_CMD_TREE_FAT (recommended)
CONFIG_SUPPORT_VFAT (recommended)
C code APIs (from U-boot native FAT support):
int fat_register_device(block_dev_desc_t *dev_desc, int part_no);
long file_fat_read(const char *path, void *buf, unsigned long maxsize);
int file_fat_ls(const char *dirname);
int file_fat_detectfs(void);
C code APIs (added by Rockbox FAT support):
long file_fat_write(const char *path, void *buf, unsigned long maxsize);
int file_fat_rm(const char *path);
int file_fat_rmdir(const char *path);
int file_fat_mkdir(const char *path);
int file_fat_cd(const char *path);
int file_fat_pwd(void);
int file_fat_mv(const char *oldpath, const char *newpath);
unsigned int rockbox_fat_free(unsigned long size_kbyte);
unsigned int rockbox_fat_size(void);
Use "help fat" from u-boot console to see available commands.
ST-Ericsson ID: WP264488
Change-Id: I9afc29ecb80f9152bd8534bbf11e47e54cfad796
Signed-off-by: Michael Brandt <michael.brandt@stericsson.com>
Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/3009
Diffstat (limited to 'fs/fat/rockbox_file.c')
-rw-r--r-- | fs/fat/rockbox_file.c | 831 |
1 files changed, 831 insertions, 0 deletions
diff --git a/fs/fat/rockbox_file.c b/fs/fat/rockbox_file.c new file mode 100644 index 000000000..452805dc9 --- /dev/null +++ b/fs/fat/rockbox_file.c @@ -0,0 +1,831 @@ +/*************************************************************************** + * Copyright (C) 2002 by Björn Stenberg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + **************************************************************************** + * Source file dumped from rockbox-3.1 distribution. + * $Id: file.c 17847 2008-06-28 18:10:04Z bagder $ + * Copyright (C) 2002 by Björn Stenberg + **************************************************************************** + * See file CREDITS for list of people who contributed to the U-boot + * project. + * + * 01/17/2006 Keith Outwater (outwater4@comcast.net) - port to U-Boot using + * CVS version 1.66 of 'firmware/common/file.c' from rockbox CVS server. + * 01/07/2009 etienne.carriere@stnwireless.com - update u-boot port from rockbox-3.1 + **************************************************************************** + */ +#include <common.h> +#include <config.h> + +#include <asm/errno.h> +#include <asm/string.h> +#include <fat.h> +#include "rockbox_debug.h" + +/*- exported ressources ------------------------------------------*/ +/* FIXME: correct prototypes + * int creat(const char *pathname, mode_t mode); + * int open(const char *pathname, int flags); + * int close(int fd); + * int fsync(int fd); + * int remove(const char *name); + * int rename(const char *path, const char *newpath); + * int ftruncate(int fd, off_t size); + * ssize_t write(int fd, const void *buf, size_t count); + * ssize_t read(int fd, void *buf, size_t count); + * off_t lseek(int fd, off_t offset, int whence); + * off_t filesize(int fd); + */ + + +#define LDEBUGF fat_dprintf +#define DEBUGF fat_dprintf + +/*- imported ressources ------------------------------------------*/ +extern int errno; /* see board/<board>/<board>.c */ +extern int strcasecmp(const char *s1, const char *s2); /* from rockbox_wrapper.c */ + +#ifndef __HAVE_ARCH_STRNICMP +//FIXME: useless +//extern int strnicmp(const char *s1, const char *s2, size_t len); +#endif + +/* + These functions provide a roughly POSIX-compatible file IO API. + + Since the fat32 driver only manages sectors, we maintain a one-sector + cache for each open file. This way we can provide byte access without + having to re-read the sector each time. + The penalty is the RAM used for the cache and slightly more complex code. +*/ + +#if !defined(CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES) +#define CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES 1 +#endif + +struct filedesc { + unsigned char cache[SECTOR_SIZE]; + int cacheoffset; /* invariant: 0 <= cacheoffset <= SECTOR_SIZE */ + long fileoffset; + long size; + int attr; + struct fat_file fatfile; + bool busy; + bool write; + bool dirty; + bool trunc; +}; + +static struct filedesc openfiles[CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES]; + +static int fat_flush_cache(int fd); + +int creat(const char *pathname) +{ + return open(pathname, O_WRONLY | O_CREAT | O_TRUNC); +} + +static int open_internal(const char* pathname, int flags, bool use_cache) +{ + DIR_UNCACHED* dir; + struct dirent_uncached* entry; + int fd; + char pathnamecopy[MAX_PATH]; + char *name; + struct filedesc *file = NULL; + int rc; +#ifndef HAVE_DIRCACHE + (void)use_cache; +#endif + + LDEBUGF("open(\"%s\",%d)\n",pathname,flags); + + /* Only absolute paths are supported. */ + if (pathname[0] != '/') { + DEBUGF("'%s' is not an absolute path.\n",pathname); + DEBUGF("Only absolute pathnames supported at the moment\n"); + errno = EINVAL; + return -1; + } + + /* find a free file descriptor */ + for ( fd=0; fd<CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES; fd++ ) + if (!openfiles[fd].busy) + break; + + if ( fd == CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES ) { + DEBUGF("Too many files open\n"); + errno = EMFILE; + return -2; + } + + file = &openfiles[fd]; + memset(file, 0, sizeof (struct filedesc)); + + if (flags & (O_RDWR | O_WRONLY)) { + file->write = true; + + if (flags & O_TRUNC) + file->trunc = true; + } + file->busy = true; + +#ifdef HAVE_DIRCACHE + if (dircache_is_enabled() && !file->write && use_cache) + { + const struct dircache_entry *ce; +# ifdef HAVE_MULTIVOLUME + int volume = strip_volume(pathname, pathnamecopy); +# endif + + ce = dircache_get_entry_ptr(pathname); + if (!ce) + { + errno = ENOENT; + file->busy = false; + return -7; + } + + fat_open(IF_MV2(volume,) + ce->startcluster, + &(file->fatfile), + NULL); + file->size = ce->size; + file->attr = ce->attribute; + file->cacheoffset = -1; + file->fileoffset = 0; + + return fd; + } +#endif + + strncpy(pathnamecopy, pathname, sizeof (pathnamecopy)); + pathnamecopy[sizeof (pathnamecopy) - 1] = 0; + + /* locate filename */ + name = strrchr(pathnamecopy + 1, '/'); + if (name) { + *name = 0; + dir = opendir_uncached(pathnamecopy); + *name = '/'; + name++; + } + else { + dir = opendir_uncached("/"); + name = pathnamecopy + 1; + } + if (!dir) { + DEBUGF("Failed opening dir\n"); + errno = EIO; + file->busy = false; + return -4; + } + + if (name[0] == 0) { + DEBUGF("Empty file name\n"); + errno = EINVAL; + file->busy = false; + closedir_uncached(dir); + return -5; + } + + /* scan dir for name */ + while ((entry = readdir_uncached(dir))) { + if ( !strcasecmp(name, (char*)entry->d_name) ) { + fat_open(IF_MV2(dir->fatdir.file.volume,) + entry->startcluster, + &(file->fatfile), + &(dir->fatdir)); + file->size = file->trunc ? 0 : entry->size; + file->attr = entry->attribute; + break; + } + } + + if (!entry) { + LDEBUGF("Didn't find file %s\n",name); + if (file->write && (flags & O_CREAT)) { + rc = fat_create_file(name, + &(file->fatfile), + &(dir->fatdir)); + if (rc < 0) { + DEBUGF("Couldn't create %s in %s\n",name,pathnamecopy); + errno = EIO; + file->busy = false; + closedir_uncached(dir); + return rc * 10 - 6; + } +#ifdef HAVE_DIRCACHE + dircache_add_file(pathname, file->fatfile.firstcluster); +#endif + file->size = 0; + file->attr = 0; + } + else { + DEBUGF("Couldn't find %s in %s\n",name,pathnamecopy); + errno = ENOENT; + file->busy = false; + closedir_uncached(dir); + return -7; + } + } else { + if (file->write && (file->attr & FAT_ATTR_DIRECTORY)) { + errno = EISDIR; + file->busy = false; + closedir_uncached(dir); + return -8; + } + } + closedir_uncached(dir); + + file->cacheoffset = -1; + file->fileoffset = 0; + + if (file->write && (flags & O_APPEND)) { + rc = lseek(fd, 0, SEEK_END); + if (rc < 0) + return rc * 10 - 9; + } + +#ifdef HAVE_DIRCACHE + if (file->write) + dircache_bind(fd, pathname); +#endif + + return fd; +} + +int open(const char* pathname, int flags) +{ + /* By default, use the dircache if available. */ + return open_internal(pathname, flags, true); +} + +int close(int fd) +{ + struct filedesc *file = &openfiles[fd]; + int rc = 0; + + LDEBUGF("close(%d)\n", fd); + + if (fd < 0 || fd > CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES - 1) { + errno = EINVAL; + return -1; + } + if (!file->busy) { + errno = EBADF; + return -2; + } + if (file->write) { + rc = fsync(fd); + if (rc < 0) + return rc * 10 - 3; +#ifdef HAVE_DIRCACHE + dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); + dircache_update_filetime(fd); +#endif + } + + file->busy = false; + return 0; +} + +int fsync(int fd) +{ + struct filedesc *file = &openfiles[fd]; + int rc = 0; + + LDEBUGF("fsync(%d)\n", fd); + + if (fd < 0 || fd > CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES - 1) { + errno = EINVAL; + return -1; + } + if (!file->busy) { + errno = EBADF; + return -2; + } + if (file->write) { + /* flush sector cache */ + if (file->dirty) { + rc = fat_flush_cache(fd); + if (rc < 0) + { + /* when failing, try to close the file anyway */ + fat_closewrite(&(file->fatfile), file->size, file->attr); + return rc * 10 - 3; + } + } + + /* truncate? */ + if (file->trunc) { + rc = ftruncate(fd, file->size); + if (rc < 0) + { + /* when failing, try to close the file anyway */ + fat_closewrite(&(file->fatfile), file->size, file->attr); + return rc * 10 - 4; + } + } + + /* tie up all loose ends */ + rc = fat_closewrite(&(file->fatfile), file->size, file->attr); + if (rc < 0) + return rc * 10 - 5; + } + return 0; +} + +int remove(const char* name) +{ + int rc; + struct filedesc *file; + /* Can't use dircache now, because we need to access the fat structures. */ + int fd = open_internal(name, O_WRONLY, false); + if (fd < 0) + return fd * 10 - 1; + + file = &openfiles[fd]; +#ifdef HAVE_DIRCACHE + dircache_remove(name); +#endif + rc = fat_remove(&(file->fatfile)); + if (rc < 0) { + DEBUGF("Failed removing file: %d\n", rc); + errno = EIO; + return rc * 10 - 3; + } + + file->size = 0; + + rc = close(fd); + if (rc < 0) + return rc * 10 - 4; + + return 0; +} + +int rename(const char* path, const char* newpath) +{ + int rc, fd; + DIR_UNCACHED* dir; + char *nameptr; + char *dirptr; + struct filedesc *file; + char newpath2[MAX_PATH]; + + /* verify new path does not already exist */ + /* If it is a directory, errno == EISDIR if the name exists */ + fd = open(newpath, O_RDONLY); + if (fd >= 0 || errno == EISDIR) { + close(fd); + errno = EBUSY; + return -1; + } + close(fd); + + fd = open_internal(path, O_RDONLY, false); + if (fd < 0) { + errno = EIO; + return fd * 10 - 2; + } + + /* extract new file name */ + nameptr = strrchr(newpath, '/'); + if (nameptr) + nameptr++; + else + return -3; + + /* Extract new path */ + strcpy(newpath2, newpath); + + dirptr = strrchr(newpath2, '/'); + if (dirptr) + *dirptr = 0; + else + return -4; + + dirptr = newpath2; + + if (strlen(dirptr) == 0) { + dirptr = "/"; + } + + dir = opendir_uncached(dirptr); + if (!dir) + return -5; + + file = &openfiles[fd]; + + rc = fat_rename(&file->fatfile, &dir->fatdir, (unsigned char*)nameptr, + file->size, file->attr); +#ifdef HAVE_MULTIVOLUME + if ( rc == -1) { + DEBUGF("Failed renaming file across volumnes: %d\n", rc); + errno = EXDEV; + return -6; + } +#endif + if (rc < 0) { + DEBUGF("Failed renaming file: %d\n", rc); + errno = EIO; + return rc * 10 - 7; + } +#ifdef HAVE_DIRCACHE + dircache_rename(path, newpath); +#endif + + rc = close(fd); + if (rc < 0) { + errno = EIO; + return rc * 10 - 8; + } + + rc = closedir_uncached(dir); + if (rc<0) { + errno = EIO; + return rc * 10 - 9; + } + + return 0; +} + +int ftruncate(int fd, off_t size) +{ + int rc, sector; + struct filedesc *file = &openfiles[fd]; + + sector = size / SECTOR_SIZE; + if (size % SECTOR_SIZE) + sector++; + + rc = fat_seek(&(file->fatfile), sector); + if (rc < 0) { + errno = EIO; + return rc * 10 - 1; + } + + rc = fat_truncate(&(file->fatfile)); + if (rc < 0) { + errno = EIO; + return rc * 10 - 2; + } + + file->size = size; +#ifdef HAVE_DIRCACHE + dircache_update_filesize(fd, size, file->fatfile.firstcluster); +#endif + + return 0; +} + +static int fat_flush_cache(int fd) +{ + int rc; + struct filedesc *file = &openfiles[fd]; + long sector = file->fileoffset / SECTOR_SIZE; + + DEBUGF("Flushing dirty sector cache\n"); + + /* make sure we are on correct sector */ + rc = fat_seek(&(file->fatfile), sector); + if (rc < 0) + return rc * 10 - 3; + + rc = fat_readwrite(&(file->fatfile), 1, file->cache, true); + + if (rc < 0) { + if (file->fatfile.eof) + errno = ENOSPC; + + return rc * 10 - 2; + } + + file->dirty = false; + + return 0; +} + +static int readwrite(int fd, void* buf, long count, bool write) +{ + long sectors; + long nread = 0; + struct filedesc *file = &openfiles[fd]; + int rc; + + if (fd < 0 || fd > CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES-1) { + errno = EINVAL; + return -1; + } + if (!file->busy) { + errno = EBADF; + return -1; + } + + LDEBUGF( "readwrite(%d,%lx,%ld,%s)\n", + fd, (long) buf, count, write ? "write" : "read"); + + /* attempt to read past EOF? */ + if (!write && count > file->size - file->fileoffset) + count = file->size - file->fileoffset; + + /* any head bytes? */ + if (file->cacheoffset != -1) { + int offs = file->cacheoffset; + int headbytes = (count < (SECTOR_SIZE - offs)) ? count : (SECTOR_SIZE - offs); + + if (write) { + memcpy(file->cache + offs, buf, headbytes); + file->dirty = true; + } + else { + memcpy( buf, file->cache + offs, headbytes ); + } + + if (offs + headbytes == SECTOR_SIZE) { + if (file->dirty) { + rc = fat_flush_cache(fd); + if (rc < 0) { + errno = EIO; + return rc * 10 - 2; + } + } + file->cacheoffset = -1; + } + else { + file->cacheoffset += headbytes; + } + + nread = headbytes; + count -= headbytes; + } + + /* If the buffer has been modified, either it has been flushed already + * (if (offs+headbytes == SECTOR_SIZE)...) or does not need to be (no + * more data to follow in this call). Do NOT flush here. */ + + /* read/write whole sectors right into/from the supplied buffer */ + sectors = count / SECTOR_SIZE; + if (sectors) { + rc = fat_readwrite(&(file->fatfile), sectors, + (unsigned char *) buf + nread, write); + if (rc < 0) { + DEBUGF("Failed read/writing %ld sectors\n",sectors); + errno = EIO; + if (write && file->fatfile.eof) { + DEBUGF("No space left on device\n"); + errno = ENOSPC; + } else { + file->fileoffset += nread; + } + file->cacheoffset = -1; + /* adjust file size to length written */ + if ( write && file->fileoffset > file->size ) + { + file->size = file->fileoffset; +#ifdef HAVE_DIRCACHE + dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); +#endif + } + return nread ? nread : rc * 10 - 4; + } + else { + if (rc > 0) { + nread += rc * SECTOR_SIZE; + count -= sectors * SECTOR_SIZE; + + /* if eof, skip tail bytes */ + if (rc < sectors) + count = 0; + } + else { + /* eof */ + count = 0; + } + + file->cacheoffset = -1; + } + } + + /* any tail bytes? */ + if (count) { + if (write) { + if (file->fileoffset + nread < file->size) { + /* sector is only partially filled. copy-back from disk */ + LDEBUGF("Copy-back tail cache\n"); + rc = fat_readwrite(&(file->fatfile), 1, file->cache, false ); + if ( rc < 0 ) { + DEBUGF("Failed writing\n"); + errno = EIO; + file->fileoffset += nread; + file->cacheoffset = -1; + /* adjust file size to length written */ + if ( file->fileoffset > file->size ) + { + file->size = file->fileoffset; +#ifdef HAVE_DIRCACHE + dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); +#endif + } + return nread ? nread : rc * 10 - 5; + } + /* seek back one sector to put file position right */ + rc = fat_seek(&(file->fatfile), + (file->fileoffset + nread) / + SECTOR_SIZE); + if (rc < 0) { + DEBUGF("fat_seek() failed\n"); + errno = EIO; + file->fileoffset += nread; + file->cacheoffset = -1; + /* adjust file size to length written */ + if ( file->fileoffset > file->size ) + { + file->size = file->fileoffset; +#ifdef HAVE_DIRCACHE + dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); +#endif + } + return nread ? nread : rc * 10 - 6; + } + } + memcpy( file->cache, (unsigned char*)buf + nread, count ); + file->dirty = true; + } + else { + rc = fat_readwrite(&(file->fatfile), 1, &(file->cache),false); + if (rc < 1) { + DEBUGF("Failed caching sector\n"); + errno = EIO; + file->fileoffset += nread; + file->cacheoffset = -1; + return nread ? nread : rc * 10 - 7; + } + memcpy( (unsigned char*)buf + nread, file->cache, count ); + } + + nread += count; + file->cacheoffset = count; + } + + file->fileoffset += nread; + LDEBUGF("fileoffset: %ld\n", file->fileoffset); + + /* adjust file size to length written */ + if (write && file->fileoffset > file->size) + { + file->size = file->fileoffset; +#ifdef HAVE_DIRCACHE + dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); +#endif + } + + return nread; +} + +ssize_t write(int fd, const void* buf, size_t count) +{ + if (!openfiles[fd].write) { + errno = EACCES; + return -1; + } + return readwrite(fd, (void *) buf, count, true); +} + +ssize_t read(int fd, void* buf, size_t count) +{ + return readwrite(fd, buf, count, false); +} + +off_t lseek(int fd, off_t offset, int whence) +{ + off_t pos; + long newsector; + long oldsector; + int sectoroffset; + int rc; + struct filedesc *file = &openfiles[fd]; + + LDEBUGF("lseek(%d,%ld,%d)\n",fd,offset,whence); + + if (fd < 0 || fd > CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES-1) { + errno = EINVAL; + return -1; + } + if (!file->busy) { + errno = EBADF; + return -1; + } + + switch (whence) { + case SEEK_SET: + pos = offset; + break; + + case SEEK_CUR: + pos = file->fileoffset + offset; + break; + + case SEEK_END: + pos = file->size + offset; + break; + + default: + errno = EINVAL; + return -2; + } + if ((pos < 0) || (pos > file->size)) { + errno = EINVAL; + return -3; + } + + /* new sector? */ + newsector = pos / SECTOR_SIZE; + oldsector = file->fileoffset / SECTOR_SIZE; + sectoroffset = pos % SECTOR_SIZE; + + if ((newsector != oldsector) || + ((file->cacheoffset == -1) && sectoroffset)) { + + if (newsector != oldsector) { + if (file->dirty) { + rc = fat_flush_cache(fd); + if (rc < 0) + return rc * 10 - 5; + } + + rc = fat_seek(&(file->fatfile), newsector); + if (rc < 0) { + errno = EIO; + return rc * 10 - 4; + } + } + if (sectoroffset) { + rc = fat_readwrite(&(file->fatfile), 1, + &(file->cache), false); + if (rc < 0) { + errno = EIO; + return rc * 10 - 6; + } + file->cacheoffset = sectoroffset; + } + else + file->cacheoffset = -1; + } + else + if ( file->cacheoffset != -1 ) + file->cacheoffset = sectoroffset; + + file->fileoffset = pos; + + return pos; +} + +off_t filesize(int fd) +{ + struct filedesc *file = &openfiles[fd]; + + if (fd < 0 || fd > CONFIG_ROCKBOX_FAT_MAX_OPEN_FILES-1) { + errno = EINVAL; + return -1; + } + if (!file->busy) { + errno = EBADF; + return -1; + } + + return file->size; +} +#ifdef HAVE_HOTSWAP +/* release all file handles on a given volume "by force", to avoid leaks */ +int release_files(int volume) +{ + struct filedesc* pfile = openfiles; + int fd; + int closed = 0; + for ( fd=0; fd<MAX_OPEN_FILES; fd++, pfile++) + { + if (pfile->fatfile.volume == volume) + { + pfile->busy = false; /* mark as available, no further action */ + closed++; + } + } + return closed; /* return how many we did */ +} +#endif /* #ifdef HAVE_HOTSWAP */ + |