www.pudn.com > efs.rar > fs_ext_sfat.c
/*
* SFAT external filesystem for EFS2.
*
* Copyright (C) 2004, 2005, 2006 Qualcomm, Inc.
*
* Support using the SFAT filesystem as a mountpoint type of EFS2.
*/
/*===========================================================================
EDIT HISTORY FOR MODULE
This section contains comments describing changes made to the module.
Notice that changes are listed in reverse chronological order.
$$Header: //depot/asic/MSMSHARED/services/efs/MSM_EFS.01.02/fs_ext_sfat.c#18 $$ $$DateTime: 2006/11/13 09:30:29 $$ $$Author: davidb $$
when who what, where, why
---------- --- ---------------------------------------------------------
2006-11-10 sh Add failing chmod stub.
2006-11-10 sh Add chmod stub.
2006-09-12 sh Conditionally compile this file only for SFAT
2006-09-08 yg Use index val of drive letter to get hotplug dev offset.
2006-08-27 sh Include shared declarations in fs_ext_sfat.h.
2006-07-19 dlb Move transaction limits down to each FS.
2006-07-07 dbl Implement truncate.
2006-07-06 dlb Remove 'size' field from vnode.
2006-06-27 dlb Pass stat through readdir, and extra information from SFAT.
2006-05-11 sch Removed fs_hotplug_i.h inclusion
2006-05-10 sh Replaced DRV_GEOMETRY_DESC with block size & count
2006-05-09 sch fixed e_sfat_statvfs() to return correct total blocks
2006-05-08 sh Fixed more 0xFFFFFFFF vs -1 warnings
2006-05-07 sh Fixed hardalloc vs -1 compiler warning
2006-04-16 sch Corrected the return values of e_sfat_read and e_sfat_write
2006-03-17 sch Added USB mass storage functionality
2006-01-11 nrs Fixed Copyright header
2005-12-22 dlb Extensions to statvfs results.
2005-11-09 sh Renamed FS_FEATURE_EXTFS to FEATURE_EFS_EXTFS
2005-10-21 dlb Support both MMC and SDCC.
2005-10-21 dlb Eliminate warnings.
2005-10-11 dlb Support multiple devices.
2005-10-07 dlb Add rename.
2005-09-02 dlb Fix up directory iteration, allowing multiple iterators.
2005-08-11 dlb Add umount support.
2005-06-06 dlb Initial checkin.
===========================================================================*/
#include "customer.h"
#ifdef FEATURE_EFS_EXTFS_SFAT
#include "fs_extfs.h"
#include "fs_errno.h"
#include "fs_err.h"
#include "fs_fcntl.h"
#include "fs_hotplug.h"
#include "msg.h"
#include "fs_ext_sfat.h"
#include "fsfat_sdapi.h"
#include "fs_sfat_i.h"
static int sfat_fixup_error (int driveno);
static int sfat_devno (const char *name);
/* Limit of sfat transaction size. The SD device is faster, but we still
* hold the global lock during these operations, so we don't want the value
* to be too large. Also, the SFAT API uses 16-bit counts, so it needs to
* be smaller than that. */
#define FS_MAX_SFAT_XACT_SIZE 32768
/* These routines are all expected to return EFS2 error codes. The return
* result should either be 0 for success, or a negative error value. */
/* Start the filesystem. */
static int
e_sfat_start (const char *name)
{
SDBOOL fat_result;
fat_result = fsfat_pc_system_init (sfat_devno (name));
if (fat_result) {
return 0;
} else {
return -ENODEV;
}
}
static int
e_sfat_stop (const char *name)
{
fsfat_pc_system_close (sfat_devno (name));
return 0;
}
/* base_stat:
* Used to determine the fundamental type of the file. For regular files,
* it should also fill in the size of the file. */
static int
e_sfat_base_stat (const char *name, fs_mode_t *mode)
{
STAT sbuf;
int fat_result;
fat_result = fsfat_pc_stat ((char *) name, &sbuf);
if (fat_result != 0) {
return -(sfat_fixup_error (sfat_devno (name)));
} else {
*mode = sbuf.st_mode;
/* Ugh. The Sandisk code seems to think that the root directory is a
* file, not a directory. */
if (name[0] != 0 && name[1] == ':' && name[2] == '/' && name[3] == 0)
{
*mode = (*mode & ~S_IFMT) | S_IFDIR;
}
return 0;
}
}
static int
e_sfat_open (const char *name, int mode)
{
int result;
switch (mode) {
case O_RDONLY:
result = fsfat_po_open ((char *) name, PO_RDONLY, S_IREAD);
break;
case O_RDWR:
result = fsfat_po_open ((char *) name, PO_RDWR, S_IREAD | S_IWRITE);
break;
case (O_CREAT | O_EXCL | O_RDWR):
result = fsfat_po_open ((char *) name,
PO_RDWR | PO_CREAT | PO_EXCL,
S_IREAD | S_IWRITE);
break;
default:
FS_ERR_FATAL ("Invalid internal mode used", 0, 0, 0);
/* Eliminate compiler warning. */
return -EINVAL;
}
if (result < 0) {
result = -(sfat_fixup_error (sfat_devno (name)));
}
return result;
}
static int
e_sfat_lseek (int fd, fs_off_t pos)
{
int result;
INT16 errcode;
if (pos == FS_OFFSET_APPEND)
result = fsfat_po_lseek (fd, 0, PSEEK_END, &errcode);
else
result = fsfat_po_lseek (fd, pos, PSEEK_SET, &errcode);
if (result == -1) {
return -EUNKNOWN_SFAT;
}
return 0;
}
static int
e_sfat_read (int fd, void *buf, fs_size_t count)
{
int result;
if (count > FS_MAX_SFAT_XACT_SIZE)
count = FS_MAX_SFAT_XACT_SIZE;
result = fsfat_po_read (fd, buf, count);
if ( (fs_size_t) result > count)
return -EUNKNOWN_SFAT;
else if (result < 0)
return -EUNKNOWN_SFAT;
return result;
}
static int
e_sfat_write (int fd, const void *buf, fs_size_t count)
{
int result;
if (count > FS_MAX_SFAT_XACT_SIZE)
count = FS_MAX_SFAT_XACT_SIZE;
result = fsfat_po_write (fd, (void *) buf, count);
if ((fs_size_t) result > count)
return -EUNKNOWN_SFAT;
else if (result < 0)
return -EUNKNOWN_SFAT;
return result;
}
static int
e_sfat_close (int fd)
{
int result;
result = fsfat_po_close (fd);
if (result != 0)
return -EUNKNOWN_SFAT;
return result;
}
static int
e_sfat_mkdir (const char *name)
{
SDBOOL fat_result;
fat_result = fsfat_pc_mkdir ((char *) name);
if (fat_result)
return 0;
else
return -EINVAL;
}
static int
e_sfat_rmdir (const char *name)
{
SDBOOL fat_result;
fat_result = fsfat_pc_rmdir ((char *) name);
if (fat_result)
return 0;
else {
return -sfat_fixup_error (sfat_devno (name));
}
}
static int
e_sfat_unlink (const char *name)
{
SDBOOL fat_result;
fat_result = fsfat_pc_unlink ((char *) name);
if (fat_result)
return 0;
else
return -sfat_fixup_error (sfat_devno (name));
}
static int
e_sfat_rename (const char *oldname, const char *newname)
{
SDBOOL fat_result;
fat_result = fsfat_pc_mv ((char *) oldname, (char *) newname);
if (fat_result)
return 0;
else
return -sfat_fixup_error (sfat_devno (newname));
}
static int
e_sfat_statvfs (const char *name, struct fs_statvfs *buf)
{
int dev = sfat_devno (name);
ULONG space;
struct hotplug_device *hdev;
uint16 bytes_per_block;
uint32 blocks;
hdev = hotplug_hdev (dev);
(void) hotplug_dev_get_size (hdev, &blocks, &bytes_per_block);
space = fsfat_pc_free (dev);
buf->f_bsize = 512;
buf->f_bfree = space / 512;
buf->f_blocks = blocks * (bytes_per_block / 512);
buf->f_bavail = buf->f_bfree;
buf->f_files = FS_INVALID_FSFILCNT;
buf->f_ffree = FS_INVALID_FSFILCNT;
buf->f_favail = FS_INVALID_FSFILCNT;
buf->f_fsid = 1;
buf->f_flag = 0;
buf->f_namemax = 132;
buf->f_balloc = FS_INVALID_FSBLKCNT;
buf->f_hardalloc = FS_INVALID_HARDALLOC;
return 0;
}
/**********************************************************************
* Directory iterators.
**********************************************************************/
/* Although the SFAT code appears that it intends to allow multiple
* iterators to be opened, it just doesn't work. To compensate for this,
* we keep track of the dirnames for each of the public iterators, and when
* the iterator changes between calls to readdir, we rewind, and seek
* forward again. It is important to do the gfirst on the opendir, so that
* we can return any error at opendir time, rather than later.
*/
/* The various states that a given iterator can be in. The 'dot dot'
* aspect is needed because the FAT doesn't have a "." and ".." entry in
* the root directory. */
enum dirstate {
DS_UNUSED,
DS_FAKE_DOT_EMPTY,
DS_FAKE_DOTDOT_EMPTY,
DS_FAKE_DOT,
DS_FAKE_DOTDOT,
DS_NORMAL,
DS_EOF,
};
static DSTAT sfat_dstat;
struct sfat_iter {
int state;
int index;
char dirname[FS_PATH_MAX+1];
};
typedef struct sfat_iter *sfat_iter_t;
#define FS_SFAT_MAX_ITERATORS FS_MAX_ITERATORS
static struct sfat_iter sfat_iters[FS_SFAT_MAX_ITERATORS];
/* State used by sfat_update_iter to position the iterator properly. */
static sfat_iter_t last_iter = NULL;
static int last_iter_index;
/* Update the single, global SFAT iterator to correspond with the desired
* virtual iterator. If iterators are being interleaved, close and reopen
* the proper one, as well as seek to the appropriate position. */
static int
sfat_update_iter (sfat_iter_t iter)
{
int result;
int skip = 0;
if (iter != last_iter || iter->index < last_iter_index) {
if (last_iter != NULL) {
fsfat_pc_gdone (&sfat_dstat);
}
result = fsfat_pc_gfirst (&sfat_dstat, (char *) iter->dirname);
last_iter_index = 0;
if (!result) {
last_iter = NULL;
return -ENOENT;
}
last_iter = iter;
}
/* Although the SFAT iterators will return names for various non-regular
* file things (such as volume labels), these will return an error on
* stat. Set the 'skip' flag to indicate that this entity is not a
* regular entity (file or dir) and should be skipped over, eliminiating
* it from the result of the readdir. There is a similar line below at
* the end of the loop.
*
* This could be changed to only skip volume labels, rather than both
* hidden and system files, but hidden and system files are of limited
* use on an embedded device. */
skip = (sfat_dstat.fattribute & (AHIDDEN | ASYSTEM | AVOLUME)) != 0;
/* Advance to proper iter. */
while (last_iter_index < iter->index || skip) {
result = fsfat_pc_gnext (&sfat_dstat);
if (!skip)
last_iter_index++;
if (result == 0) {
iter->state = DS_EOF;
fsfat_pc_gdone (&sfat_dstat);
last_iter = NULL;
return -EEOF;
}
skip = (sfat_dstat.fattribute & (AHIDDEN | ASYSTEM | AVOLUME)) != 0;
}
return 0;
}
/* Directory operations. */
static void *
e_sfat_opendir (const char *name)
{
int result;
int len = strlen (name);
int i;
sfat_iter_t iter;
if (name[len-1] == '/')
len--;
if (len + 4 > FS_PATH_MAX) {
/* XXX: Fix result code so we can return errors. */
/* return -ENAMETOOLONG; */
return 0;
}
for (i = 0; i < FS_SFAT_MAX_ITERATORS; i++) {
if (sfat_iters[i].state == DS_UNUSED)
break;
}
if (i == FS_SFAT_MAX_ITERATORS)
return 0;
iter = &sfat_iters[i];
memcpy (iter->dirname, name, len);
strcpy (iter->dirname + len, "/" "*.*");
iter->index = 0;
result = sfat_update_iter (iter);
/* FAT doesn't have "." or ".." entries in the root directory. As such,
* an attempt to iterate an empty root directory will fail. Detect all
* of this, and try to clean up the semantics a bit. */
if (name[0] != 0 && name[1] == ':' && name[2] == '/' && name[3] == 0) {
if (result == 0) {
iter->state = DS_FAKE_DOT;
} else {
iter->state = DS_FAKE_DOT_EMPTY;
}
} else {
if (result != 0) {
/* XXX: How do we cleanup from this? */
return 0;
}
iter->state = DS_NORMAL;
}
return iter;
}
static int
to_lower (int c)
{
if (c >= 'A' && c <= 'Z')
return c + ('a' - 'A');
else
return c;
}
/* Attributes we care about in files. */
#define ATTR_MASK (~(ARCHIVE | ARDONLY))
static int
e_sfat_readdir (void *dir, struct fs_dirent *dirent)
{
sfat_iter_t iter = (sfat_iter_t) dir;
char *out_name;
int advance = 0;
int result;
dirent->d_stats_present = 0;
switch (iter->state) {
case DS_FAKE_DOT_EMPTY:
iter->state = DS_FAKE_DOTDOT_EMPTY;
out_name = ".";
break;
case DS_FAKE_DOTDOT_EMPTY:
iter->state = DS_EOF;
out_name = "..";
break;
case DS_FAKE_DOT:
iter->state = DS_FAKE_DOTDOT;
out_name = ".";
break;
case DS_FAKE_DOTDOT:
iter->state = DS_NORMAL;
out_name = "..";
break;
case DS_NORMAL:
result = sfat_update_iter (iter);
if (result != 0)
return result;
/* Fill in a few fields in the stat structure to make things a bit
* more efficient. */
switch (sfat_dstat.fattribute & ATTR_MASK) {
case ANORMAL:
dirent->d_stat.st_mode = S_IFREG;
break;
case ADIRENT:
dirent->d_stat.st_mode = S_IFDIR;
break;
default:
/* This should have been skipped above, so leave as invalid. */
dirent->d_stat.st_mode = 0;
break;
}
dirent->d_stat.st_size = sfat_dstat.fsize;
dirent->d_stats_present =
FS_DIRENT_HAS_TYPE | FS_DIRENT_HAS_ST_SIZE;
/* Parse either the long or short filename. */
if (sfat_dstat.fsfat_longFileName[0] != 0)
out_name = sfat_dstat.fsfat_longFileName;
else {
char *dest;
int i;
/* We need to construct a filename out of the MSDOS short name.
* They are stored as upperase but need to be converted to
* lowercase, since if they were created in uppercase, the
* longfilename would be used. */
out_name = 0; /* Indicate we've already copied it. */
if (8+1+3 > FS_NAME_MAX)
return -ENAMETOOLONG;
/* XXX: Note that technically, shortnames can contains spaces. It
* may be better to change this to search backward for the first
* non-space character. */
dest = dirent->d_name;
for (i = 0; i < 8 && sfat_dstat.fname[i] != ' '; i++) {
*dest++ = to_lower (sfat_dstat.fname[i]);
}
if (sfat_dstat.fext[0] != ' ')
*dest++ = '.';
for (i = 0; i < 3 && sfat_dstat.fext[i] != ' '; i++) {
*dest++ = to_lower (sfat_dstat.fext[i]);
}
*dest = 0;
}
advance = 1;
break;
case DS_EOF:
return -EEOF;
default:
FS_ERR_FATAL ("unhandled internal dirstate: %d", iter->state, 0, 0);
/* Eliminate compiler warning. */
return -EINVAL;
}
if (out_name != NULL) {
if ((int) strlen (out_name) > FS_NAME_MAX)
return -ENAMETOOLONG;
strcpy (dirent->d_name, out_name);
}
if (advance) {
iter->index++;
}
return 0;
}
static int
e_sfat_closedir (void *dir)
{
sfat_iter_t iter = (sfat_iter_t) dir;
/* If we are closing the active iterator, close the real sfat iterator as
* well. Otherwise, there is no sfat iterator open for this EFS
* iterator. */
if (last_iter == iter) {
fsfat_pc_gdone (&sfat_dstat);
last_iter = NULL;
}
iter->state = DS_UNUSED;
return 0;
}
static int
e_sfat_truncate (int fd, fs_off_t pos)
{
int result;
INT16 errcode;
/* SFAT is "bothered" by trying to truncate a file before one of its
* descriptors. Seek the descriptor to the beginning to make sure we're
* OK. */
result = fsfat_po_lseek (fd, 0, PSEEK_SET, &errcode);
/* Ignore the result of this. */
(void) result;
result = fsfat_po_truncate (fd, pos);
if (result != YES)
// XXX: Fixup this error code.
return -EUNKNOWN_SFAT;
else
return 0;
}
static int
e_sfat_getstat (const char *name, struct fs_stat *buf)
{
STAT sbuf;
int result;
result = fsfat_pc_stat ((char *) name, &sbuf);
if (result != 0) {
return -sfat_fixup_error (sfat_devno (name));
} else {
/* Note that 'dev' and 'inum' aren't filled in. They have already been
* set to valid values, and can be left alone if they don't make sense.
* Also, dev shouldn't be set, since we want the EFS2 internal value,
* not the local filesystem's concept. */
buf->st_mode = sbuf.st_mode;
buf->st_nlink = sbuf.st_nlink;
buf->st_size = sbuf.st_size;
buf->st_blksize = sbuf.st_blksize;
buf->st_blocks = sbuf.st_blocks;
/* The Sandisk code seems to think that the root directory is a file,
* and not a directory. */
if (name[0] != 0 && name[1] == ':' && name[2] == '/' && name[3] == 0)
{
buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFDIR;
}
/* These time fields are different format. */
/* XXX: Convert the dos timestamps appropriately. */
buf->st_mtime = 0;
buf->st_atime = 0;
buf->st_ctime = 0;
return 0;
}
}
static int
e_sfat_chmod (const char *name, fs_mode_t mode)
{
(void) name;
(void) mode;
return -EUNKNOWN_SFAT;
}
static int
sfat_fixup_error (int driveno)
{
int code = fsfat_pc_get_error (driveno);
switch (code) {
case PENOENT:
return ENOENT;
case PENOSPC:
return ENOSPC;
case PEACCES:
/* This seems to happen on remove dir of notempty dir as well as
* access violations. */
return ENOTEMPTY;
case PEEXIST:
return EEXIST;
default:
MSG_HIGH ("Unknown error code from sfat: %d", code, 0, 0);
return EINVAL;
}
}
/* Figure out which SFAT device to use. */
static int
sfat_devno (const char *name)
{
if (((name[0] >= 'A' && name[0] <= 'Z') ||
(name[0] >= 'a' && name[0] <= 'z')) &&
name[1] == ':')
{
if (name[0] >= 'A' && name[0] <= 'Z')
return name[0] - 'A';
else
return name[0] - 'a';
} else {
FS_ERR_FATAL ("Invalid drive letter used for sfat mount", 0, 0, 0);
/* Eliminate compier warning. */
return 0;
}
}
void
fs_sfat_init (void)
{
int i;
for (i = 0; i < FS_SFAT_MAX_ITERATORS; i++)
sfat_iters[i].state = DS_UNUSED;
}
struct fs_extfs_ops fs_sfat_ops = {
e_sfat_start,
e_sfat_stop,
e_sfat_base_stat,
e_sfat_getstat,
e_sfat_open,
e_sfat_lseek,
e_sfat_read,
e_sfat_write,
e_sfat_chmod,
e_sfat_close,
e_sfat_mkdir,
e_sfat_rmdir,
e_sfat_unlink,
e_sfat_rename,
e_sfat_statvfs,
e_sfat_opendir,
e_sfat_readdir,
e_sfat_closedir,
e_sfat_truncate,
};
#else /* FEATURE_EFS_EXTFS_SFAT */
extern int __dont_complain_about_empty_file;
#endif