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