www.pudn.com > efs.rar > fs_ext_hfat.c
/***********************************************************************
* fs_ext_hfat.c
*
* Interface between EFS External Filesystem and HFAT library
* Copyright (C) 2006 QUALCOMM, Inc.
*
* The HFAT library provides a FAT-compatible set of filesystem operations.
* This code provides the "glue" between the EFS "External" filesystem and
* the HFAT calls.
*
***********************************************************************/
/*===========================================================================
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_hfat.c#10 $ $DateTime: 2006/11/13 09:30:29 $ $Author: davidb $
when who what, where, why
---------- --- ---------------------------------------------------------
2006-10-11 s h Add chmod support for HIDDEN flag (S_ISVTX)
2006-10-11 s h Add chmod support for READONLY flag.
2006-10-30 sch Returned -ENOENT instead of -EINVAL when e_hfat_open fails
2006-10-17 sch Minor cleanup.
2006-10-17 sch Deleted the volume if initialization fails
2006-10-16 sch Allowed opening of multiple files.
2006-09-27 s h Select FAT16/32 based on media size.
2006-09-06 s h Correct one-too-far in fd table index
2006-09-05 s h Conditional compilation for this entire file.
2006-08-27 s h Fix HFAT format to accept certain error conditions.
2006-08-27 s h Use driveno (hdev index) consistently.
2006-08-27 s h Corrected some return values to reflect errno.
2006-08-10 sch Implemented hfat_format
Corrected getstat() to return correct stats for root dir
Implemented error mapping mechanism
2006-07-18 sch Create
===========================================================================*/
#include "customer.h"
#ifdef FEATURE_EFS_EXTFS_HFAT
#include "fs_extfs.h"
#include "fs_errno.h"
#include "fs_public.h"
#include "fs_err.h"
#include "fs_ext_hfat.h"
#include "fs_hotplug.h"
#include "hfat_fat.h"
#include "hfat_interface.h"
#include "jzap.h"
#include "err.h"
/* Technically, the maximum length of a directory path alone,
* (only the drive letter, colon, and leading slash) is 246 bytes.
*
* Long filenames (by themselves) can be up to 256 characters (incl
* NULL).
*
* The maximum length of a whole file path (drive letter, colon,
* leading slash, directory, trailing slash, filename, extension,
* NULL) is 260 characters. We use this value.
*/
#define HFAT_MAX_PATH 260 /* Includes NULL */
#if HFAT_MAX_PATH > FS_PATH_MAX
#error "EFS filename length (FS_PATH_MAX) is too restrictive for FAT."
#endif
/* HFAT directory iterator states */
enum iterator_state
{
ITER_STATE_DOT,
ITER_STATE_DOT_DOT,
ITER_STATE_NORMAL
};
/* HFAT directory iterator object */
struct hfat_iterator
{
unsigned int index; /* to know the exact position in the iterator */
F_FIND find; /* Structure to hold f_find stats */
/* In each iterator, we store the whole directory name with "*.*"
* appended. The "+1" is a lint tax */
char dirname[HFAT_MAX_PATH + 4 + 1];
unsigned int available;
int state;
} hfat_iterators[FS_MAX_ITERATORS];
/* Mark iterator as unused and available */
static void
reset_iterator (struct hfat_iterator *iter)
{
iter->index = -1;
iter->available = 1;
}
/* HFAT deals in file pointers, while EFS uses integer file
* descriptors. This table stores the file pointers for each
* externally-visible active file descriptor. One entry is used for
* each open file, and NULL is stored in the free slots. */
struct fd_translator
{
F_FILE *fp; /* HFAT file pointer */
};
static struct fd_translator fd_table[F_MAXFILES];
/* Map an EFS file descriptor to HFAT file handle */
static F_FILE *
fd_to_file (int fd)
{
/* This suggests a severe internal failure, since fd's are 'trusted'
* and not user-visible. A Fatal stop is appropriate. */
if ((fd < 0) || (fd >= F_MAXFILES))
ERR_FATAL ("fd out of range %d\n", fd, 0, 0);
return fd_table[fd].fp;
}
/* The file handled associated with this fd is finished */
static void
reset_fd (int fd)
{
if ((fd < 0) || (fd >= F_MAXFILES))
ERR_FATAL ("fd out of range %d\n", fd, 0, 0);
fd_table[fd].fp = NULL;
}
/* Figure out which hfat device number to use based on the DOS-style
* drive letter ("A:" = 0). */
static int
hfat_driveno (const char *name)
{
if (name[1] == ':') {
if (name[0] >= 'A' && name[0] <= 'Z')
return name[0] - 'A';
if (name[0] >= 'a' && name[0] <= 'z')
return name[0] - 'a';
}
FS_ERR_FATAL ("Invalid drive letter used for hfat mount", 0, 0, 0);
/* Eliminate compier warning. */
return 0; /* unreached */
}
/* Map a given HFAT error value to an approximate EFS2 error code */
static int
hfat_to_efs_err (unsigned int err)
{
// if (err)
// printf ("hfat_to_efs_err(%d)!\n", err), fflush (stdout);
switch (err)
{
case F_NO_ERROR:
return 0; /* success */
case F_ERR_INVALIDDIR:
case F_ERR_INVALIDNAME:
case F_ERR_NOTFOUND:
return -ENOENT;
case F_ERR_DUPLICATED:
return -EEXIST;
case FS_TOOLONGNAME:
return -ENAMETOOLONG;
case F_ERR_INVALIDDRIVE:
case F_ERR_CARDREMOVED:
case F_ERR_NOTFORMATTED:
return -ENODEV;
case F_ERR_BUSY:
case F_ERR_DRVALREADYMNT:
return -EBUSY;
case F_ERR_LOCKED:
return -ETXTBSY;
case F_ERR_ALLOCATION:
case F_ERR_NOMOREENTRY:
return -ENOMEM;
case F_ERR_ACCESSDENIED:
return -EACCES;
case F_ERR_NOTOPEN:
return -EBADF;
case F_ERR_EOF:
return -EEOF;
case F_ERR_NOTUSEABLE:
return -ESPIPE;
case F_ERR_NOTEMPTY:
return -ENOTEMPTY;
case F_ERR_INVALIDSECTOR:
return -EINVAL;
case F_ERR_RESERVED:
case F_ERR_INITFUNC:
case F_ERR_READ:
case F_ERR_WRITE:
case F_ERR_WRITEPROTECT:
case F_ERR_INVFATTYPE:
case F_ERR_MEDIATOOSMALL:
case F_ERR_MEDIATOOLARGE:
case F_ERR_NOTSUPPSECTORSIZE:
case FW_ERR_UNKNOWN:
case F_ERR_DELFUNC:
case FS_NOTFORREAD:
return -EUNKNOWN_HFAT; /* catch-all ambiguous error */
}
return -EINVAL;
}
/* Start the filesystem (called during mount). */
static int
e_hfat_start (const char *name)
{
int ret;
int driveno = hfat_driveno (name);
/* Attempt to initialize the HFAT volume. Reads the partition table
* and verifies the formatting. */
ret = f_initvolume (driveno, hfat_device_init, driveno);
/* If that failed, we can not mount the volume */
if (ret)
{
/* HFAT leaves the volume allocated, so we must delete it
manually. Discard the return value from f_delvolume() because
the f_initvolume() error cause is more interesting. */
(void) f_delvolume (driveno);
}
return hfat_to_efs_err (ret);
}
/* Stop HFAT operation on this volume during unmounting */
static int
e_hfat_stop (const char *name)
{
int ret;
ret = f_delvolume (hfat_driveno (name));
return hfat_to_efs_err (ret);
}
/* Fill in EFS mode bits based on FAT attribute flags */
static void
mode_from_attr (fs_mode_t *mode, unsigned char attr)
{
*mode = S_IRUSR | S_IRGRP | S_IROTH; /* All files are readable */
/* If the file is not READ-ONLY, then set ALL the write perm flags */
if (!(attr & F_ATTR_READONLY))
*mode |= S_IWUSR | S_IWGRP | S_IWOTH;
if (attr & F_ATTR_DIR)
*mode |= S_IFDIR; /* Directory */
/* Anything other than Directories and Volume labels are regular */
if (!(attr & (F_ATTR_DIR | F_ATTR_VOLUME)))
*mode |= S_IFREG;
if (attr & F_ATTR_HIDDEN)
*mode |= S_ISVTX; /* Hidden file = "Sticky" */
/* These flags are also available, but disregarded here :
F_ATTR_ARC
F_ATTR_SYSTEM
F_ATTR_LFN
*/
}
/* base_stat:
* Used to determine the fundamental type of the file entry.
*/
static int
e_hfat_base_stat (const char *name, fs_mode_t *mode)
{
int ret;
unsigned char attr;
/* Fetch file attributes from HFAT */
ret = f_getattr (name, &attr);
if (ret)
return hfat_to_efs_err (ret);
mode_from_attr (mode, attr);
return 0;
}
/* Open a file by full pathname */
static int
e_hfat_open (const char *name, int mode)
{
FN_FILE *fp = NULL;
int fd;
switch (mode)
{
case O_RDONLY:
fp = f_open (name, "r");
break;
case O_RDWR:
fp = f_open (name, "r+");
break;
case (O_CREAT | O_EXCL | O_RDWR):
fp = f_open (name, "w+");
break;
default:
/* The modes are from a restricted set used by extfs */
ZPRINTF ("Invalid internal mode used", 0, 0, 0);
break;
}
/* We don't get any reason (from HFAT) for why an f_open() failed,
so all errors (null file pointer) are treated identically. */
if (!fp)
return -ENOENT;
/* Store this fp in our table, converting it to an integer fd index */
for (fd = 0; fd < F_MAXFILES; fd++)
{
if (fd_table[fd].fp == NULL) /* free to use? */
{
fd_table[fd].fp = fp; /* map it */
return fd; /* Return fd */
}
}
/* We could not find any open fd slot to store this fp, so this
seems the most appropriate error. Increase F_MAXFILES, or open
fewer files. Regrettably, we have to close the file as well. */
(void) f_close (fp);
return -EMFILE; /* no room at the inn */
}
/* Seek to a specific position in this open file descriptor */
static int
e_hfat_lseek (int fd, fs_off_t pos)
{
int ret;
F_FILE *fp;
fp = fd_to_file (fd);
// printf ("e_hfat_lseek (%d)\n", pos); fflush(stdout);
if (pos == FS_OFFSET_APPEND)
ret = f_seek (fp, 0, SEEK_END);
else
ret = f_seek (fp, pos, SEEK_SET);
// if (ret)
// printf ("Seek failed!\n"), fflush(stdout);
return hfat_to_efs_err (ret);
}
static int
e_hfat_read (int fd, void *buf, fs_size_t count)
{
F_FILE *fp;
fp = fd_to_file (fd);
//printf ("e_hfat_read (%d)\n", count); fflush(stdout);
return f_read (buf, 1, count, fp);
}
static int
e_hfat_write (int fd, const void *buf, fs_size_t count)
{
F_FILE *fp;
fp = fd_to_file (fd);
//printf ("e_hfat_write (%d)\n", count); fflush(stdout);
return f_write (buf, 1, count, fp);
}
static int
e_hfat_close (int fd)
{
int ret;
F_FILE *fp;
fp = fd_to_file (fd);
ret = f_close (fp);
reset_fd (fd);
return hfat_to_efs_err (ret);
}
static int
e_hfat_mkdir (const char *name)
{
int ret;
ret = f_mkdir (name);
return hfat_to_efs_err (ret);
}
static int
e_hfat_chmod (const char *name, fs_mode_t mode)
{
int ret;
unsigned char attr;
/* Get the current DIR/File/Volume attributes */
ret = f_getattr (name, &attr);
if (ret)
return hfat_to_efs_err (ret);
/* Fail if any illegal bits are attempted. Note that even some
* 'legal' bits will be ignored on FAT. */
if (mode & ~07777)
return -EINVAL;
if ((mode & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0)
attr |= F_ATTR_READONLY; /* Not writable */
else
attr &= ~F_ATTR_READONLY; /* Writeable */
if (mode & S_ISVTX)
attr |= F_ATTR_HIDDEN; /* Hidden */
else
attr &= ~F_ATTR_HIDDEN; /* Not hidden */
ret = f_setattr (name, attr);
return hfat_to_efs_err (ret);
}
static int
e_hfat_rmdir (const char *name)
{
int ret;
ret = f_rmdir (name);
return hfat_to_efs_err (ret);
}
static int
e_hfat_unlink (const char *name)
{
int ret;
ret = f_delete (name);
return hfat_to_efs_err (ret);
}
static int
e_hfat_rename (const char *oldname, const char *newname)
{
int ret;
ret = f_rename (oldname, newname);
return hfat_to_efs_err (ret);
}
static int
e_hfat_statvfs (const char *name, struct fs_statvfs *buf)
{
int driveno = hfat_driveno (name);
F_SPACE space;
int ret;
ret = f_getfreespace (driveno, &space);
if (ret)
return hfat_to_efs_err (ret);
else
{
buf->f_bsize = 512;
buf->f_bfree = space.free / 512;
buf->f_blocks = space.total / 512;
buf->f_bavail = space.free / 512;
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;
}
}
static int
hfat_update_iterator (struct hfat_iterator *iter)
{
int ret;
if (iter->index == 0) /* new iterator, get first entry */
ret = f_findfirst (iter->dirname, &iter->find);
else
ret = f_findnext (&iter->find); /* advance to next entry */
return hfat_to_efs_err (ret);
}
static struct hfat_iterator *
get_free_iterator (void)
{
int i;
for (i = 0; i < FS_MAX_ITERATORS; i++)
{
if (hfat_iterators[i].available)
{
hfat_iterators[i].available = 0;
return &hfat_iterators[i];
}
}
/* Failed to find an available iterator */
return NULL;
}
/* Open a directory iterator.
* This function takes a directory name that begins with a drive letter.
* Example: "a:/mystuff/musicdir" */
static void *
e_hfat_opendir (const char *name)
{
unsigned int len;
struct hfat_iterator *iter;
len = strlen (name);
/* We require a drive letter, colon, and leading slash */
if (name[0] == '\0' || name[1] != ':' || name[2] != '/' || len < 3)
return NULL;
/* Don't count any trailing '/' in the length */
while (name[len - 1] == '/')
len--;
/* If the director name is longer than we expect, fail */
if (len > HFAT_MAX_PATH)
return NULL;
/* Grab an iterator from iterator pool */
iter = get_free_iterator ();
if (!iter)
{
ZAP ("No iterators available\n");
return NULL;
}
/* Initialize the iterator index to 0 so that we know that we just
created the iterator */
iter->index = 0;
/* Initialize the dirname field of the iterator. We need this in readdir
to pass to f_findfirst() and f_findnext() */
memcpy (iter->dirname, name, len);
/* HFAT requires this to find all the files in the directory */
strcpy (iter->dirname + len, "/*.*");
/* The root directory in FAT does not contain '.' and '..'. We need
to fake them with these two special iterator states. Look for
any root directory name ("?:/") and start with these fake
entries. */
if (name[0] != 0 && name[1] == ':' && name[2] == '/' && name[3] == 0)
iter->state = ITER_STATE_DOT;
else
iter->state = ITER_STATE_NORMAL;
return iter;
}
static int
e_hfat_readdir (void *iterator, struct fs_dirent *dirent)
{
int ret;
struct hfat_iterator *iter;
unsigned char attr;
fs_mode_t *mode;
/* We already initialized the iterator in opendir, which is passed
to us as first argument. */
iter = (struct hfat_iterator *) iterator;
switch (iter->state)
{
case ITER_STATE_DOT: /* First fake root entry: "." */
iter->state = ITER_STATE_DOT_DOT;
strcpy (dirent->d_name, ".");
break;
case ITER_STATE_DOT_DOT: /* Second fake root entry: ".." */
iter->state = ITER_STATE_NORMAL;
strcpy (dirent->d_name, "..");
break;
case ITER_STATE_NORMAL:
ret = hfat_update_iterator (iter);
if (ret)
return hfat_to_efs_err (ret); /* Enumeration is done */
attr = iter->find.attr;
mode = &dirent->d_stat.st_mode;
mode_from_attr (mode, attr);
/* Get the file size */
dirent->d_stat.st_size = (unsigned long) iter->find.filesize;
dirent->d_stats_present = FS_DIRENT_HAS_TYPE | FS_DIRENT_HAS_ST_SIZE;
strcpy (dirent->d_name, iter->find.filename);
/* Advance the iterator */
iter->index++;
break;
default:
ERR_FATAL ("Invalid iterator state : %d", iter->state, 0, 0);
}
return 0;
}
static int
e_hfat_closedir (void *iterator)
{
struct hfat_iterator *iter;
iter = (struct hfat_iterator *) iterator;
reset_iterator (iter);
return 0;
}
static int
e_hfat_truncate (int fd, fs_off_t pos)
{
int ret;
FN_FILE *fp;
fp = fd_to_file (fd);
ret = f_ftruncate (fp, (unsigned long) pos);
if (ret)
return hfat_to_efs_err (ret);
return 0;
}
static int
e_hfat_getstat (const char *name, struct fs_stat *buf)
{
unsigned char attr;
int ret;
F_FIND find;
uint16 *mode;
/* f_getattr does not work with root directories. */
if (name[0] != 0 && name[1] == ':' && name[2] == '/' && name[3] == 0)
{
buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFDIR;
buf->st_size = 0; /* Root directory always has zero size */
}
else
{
/* Find the attributes of the file */
ret = f_getattr (name, &attr);
if (ret)
return hfat_to_efs_err (ret);
/* Translate the Attributes into mode */
mode = &buf->st_mode;
mode_from_attr (mode, attr);
ret = f_findfirst (name, &find);
if (ret)
return hfat_to_efs_err (ret);
buf->st_size = find.filesize;
}
/* XXX Fill in the time fields in correct format */
buf->st_mtime = 0;
buf->st_atime = 0;
buf->st_ctime = 0;
return 0;
}
static void
reset_fd_table (void)
{
int i;
struct fd_translator *fd_ptr = NULL;
fd_ptr = &fd_table[0];
for (i = 0; i < F_MAXFILES; i++)
{
fd_ptr->fp = NULL;
fd_ptr++;
}
}
void
fs_hfat_init (void)
{
int i;
reset_fd_table ();
for (i = 0; i < FS_MAX_ITERATORS; i++)
reset_iterator (&hfat_iterators[i]);
}
/* Format the media in drive number 'driveno'
Returns 0 for success and error code for failure. */
int
fs_hfat_format (int driveno)
{
int ret;
uint32 blocks;
uint16 bytes_per_block;
uint32 total_mb = 0;
/* We don't yet have an initialized drive. */
ret = f_initvolume (driveno, hfat_device_init, driveno);
/* An error indicating Not Formatted is OK */
if ((ret != F_NO_ERROR) && (ret != F_ERR_NOTFORMATTED))
return hfat_to_efs_err (ret);
/* Now we have an initialized volume.. Get the size. */
ret = hotplug_dev_get_size (hotplug_hdev (driveno),
&blocks,
&bytes_per_block);
/* HFAT only works with block sizes of 512 bytes */
if (bytes_per_block == 512)
total_mb = (blocks / (1024 / bytes_per_block)) / 1024;
else
ret = F_ERR_INVALIDDRIVE;
/* If the media bigger than 2GB, use FAT32 */
if (ret == 0)
ret = f_format (driveno,
(total_mb > 2048) ? F_FAT32_MEDIA : F_FAT16_MEDIA);
/* Discard the volume */
(void) f_delvolume (driveno);
return hfat_to_efs_err (ret);
}
struct fs_extfs_ops fs_hfat_ops = {
e_hfat_start,
e_hfat_stop,
e_hfat_base_stat,
e_hfat_getstat,
e_hfat_open,
e_hfat_lseek,
e_hfat_read,
e_hfat_write,
e_hfat_chmod,
e_hfat_close,
e_hfat_mkdir,
e_hfat_rmdir,
e_hfat_unlink,
e_hfat_rename,
e_hfat_statvfs,
e_hfat_opendir,
e_hfat_readdir,
e_hfat_closedir,
e_hfat_truncate,
};
#else /* FEATURE_EFS_EXTFS_HFAT */
extern int __dont_complain_about_empty_file;
#endif