Skip to content
Snippets Groups Projects
fs_fat32.c 60.6 KiB
Newer Older
patacongo's avatar
patacongo committed
/****************************************************************************
 * fs/fat/fs_fat32.c
patacongo's avatar
patacongo committed
 *
 *   Copyright (C) 2007-2009, 2011 Gregory Nutt. All rights reserved.
patacongo's avatar
patacongo committed
 *   Author: Gregory Nutt <spudmonkey@racsa.co.cr>
 *
 * References:
 *   Microsoft FAT documentation
 *   Some good ideas were leveraged from the FAT implementation:
 *     'Copyright (C) 2007, ChaN, all right reserved.'
 *     which has an unrestricted license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statfs.h>
patacongo's avatar
patacongo committed
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
#include <assert.h>
#include <fcntl.h>
#include <errno.h>
#include <debug.h>

patacongo's avatar
patacongo committed
#include <nuttx/kmalloc.h>
patacongo's avatar
patacongo committed
#include <nuttx/fs.h>
#include <nuttx/fat.h>
patacongo's avatar
patacongo committed

#include "fs_internal.h"
#include "fs_fat32.h"

/****************************************************************************
 * Definitions
 ****************************************************************************/

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int     fat_open(FAR struct file *filep, const char *relpath,
patacongo's avatar
patacongo committed
                        int oflags, mode_t mode);
static int     fat_close(FAR struct file *filep);
static ssize_t fat_read(FAR struct file *filep, char *buffer, size_t buflen);
static ssize_t fat_write(FAR struct file *filep, const char *buffer,
patacongo's avatar
patacongo committed
                         size_t buflen);
static off_t   fat_seek(FAR struct file *filep, off_t offset, int whence);
static int     fat_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
static int     fat_sync(FAR struct file *filep);
patacongo's avatar
patacongo committed

static int     fat_opendir(struct inode *mountpt, const char *relpath,
                           struct fs_dirent_s *dir);
static int     fat_readdir(struct inode *mountpt, struct fs_dirent_s *dir);
static int     fat_rewinddir(struct inode *mountpt, struct fs_dirent_s *dir);
patacongo's avatar
patacongo committed

static int     fat_bind(FAR struct inode *blkdriver, const void *data,
                        void **handle);
static int     fat_unbind(void *handle, FAR struct inode **blkdriver);
static int     fat_statfs(struct inode *mountpt, struct statfs *buf);

static int     fat_unlink(struct inode *mountpt, const char *relpath);
static int     fat_mkdir(struct inode *mountpt, const char *relpath,
                         mode_t mode);
static int     fat_rmdir(struct inode *mountpt, const char *relpath);
static int     fat_rename(struct inode *mountpt, const char *oldrelpath,
                          const char *newrelpath);
static int     fat_stat(struct inode *mountpt, const char *relpath, struct stat *buf);

/****************************************************************************
 * Private Variables
 ****************************************************************************/

/****************************************************************************
 * Public Variables
 ****************************************************************************/

/* See fs_mount.c -- this structure is explicitly externed there.
 * We use the old-fashioned kind of initializers so that this will compile
 * with any compiler.
 */

const struct mountpt_operations fat_operations =
{
  fat_open,
  fat_close,
  fat_read,
  fat_write,
  fat_seek,
  fat_ioctl,
  fat_sync,

  fat_opendir,
  NULL,
  fat_readdir,
  fat_rewinddir,

  fat_bind,
  fat_unbind,
  fat_statfs,

  fat_unlink,
  fat_mkdir,
  fat_rmdir,
  fat_rename,
  fat_stat
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: fat_open
 ****************************************************************************/

static int fat_open(FAR struct file *filep, const char *relpath,
patacongo's avatar
patacongo committed
                    int oflags, mode_t mode)
{
  struct fat_dirinfo_s  dirinfo;
  struct inode         *inode;
  struct fat_mountpt_s *fs;
  struct fat_file_s    *ff;
patacongo's avatar
patacongo committed
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(filep->f_priv == NULL && filep->f_inode != NULL);
patacongo's avatar
patacongo committed

  /* Get the mountpoint inode reference from the file structure and the
   * mountpoint private data from the inode structure
   */

  inode = filep->f_inode;
patacongo's avatar
patacongo committed
  fs    = inode->i_private;

  DEBUGASSERT(fs != NULL);

  /* Check if the mount is still healthy */

  fat_semtake(fs);
  ret = fat_checkmount(fs);
  if (ret != OK)
    {
      goto errout_with_semaphore;
    }

  /* Initialize the directory info structure */

  memset(&dirinfo, 0, sizeof(struct fat_dirinfo_s));

  /* Locate the directory entry for this path */

  ret = fat_finddirentry(fs, &dirinfo, relpath);

  /* Three possibililities: (1) a node exists for the relpath and
   * dirinfo describes the directory entry of the entity, (2) the
   * node does not exist, or (3) some error occurred.
   */

  if (ret == OK)
    {
      bool readonly;
patacongo's avatar
patacongo committed

      /* The name exists -- but is it a file or a directory? */

      direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset];
      if (dirinfo.fd_root ||
         (DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY))
patacongo's avatar
patacongo committed
        {
          /* It is a directory */
patacongo's avatar
patacongo committed
          ret = -EISDIR;
          goto errout_with_semaphore;
        }

      /* It would be an error if we are asked to create it exclusively */

      if ((oflags & (O_CREAT|O_EXCL)) == (O_CREAT|O_EXCL))
        {
          /* Already exists -- can't create it exclusively */
patacongo's avatar
patacongo committed
          ret = -EEXIST;
          goto errout_with_semaphore;
        }

#ifdef CONFIG_FILE_MODE
#  warning "Missing check for privileges based on inode->i_mode"
patacongo's avatar
patacongo committed
#endif

      /* Check if the caller has sufficient privileges to open the file */

      readonly = ((DIR_GETATTRIBUTES(direntry) & FATATTR_READONLY) != 0);
patacongo's avatar
patacongo committed
      if (((oflags & O_WRONLY) != 0) && readonly)
        {
          ret = -EACCES;
          goto errout_with_semaphore;
        }

      /* If O_TRUNC is specified and the file is opened for writing,
       * then truncate the file.  This operation requires that the file is
       * writable, but we have already checked that. O_TRUNC without write
       * access is ignored.
       */

      if ((oflags & (O_TRUNC|O_WRONLY)) == (O_TRUNC|O_WRONLY))
        {
          /* Truncate the file to zero length */

          ret = fat_dirtruncate(fs, &dirinfo);
          if (ret < 0)
            {
              goto errout_with_semaphore;
            }
        }

      /* fall through to finish the file open operations */
    }
  else if (ret == -ENOENT)
    {
      /* The file does not exist.  Were we asked to create it? */

      if ((oflags & O_CREAT) == 0)
        {
          /* No.. then we fail with -ENOENT */
patacongo's avatar
patacongo committed
          ret = -ENOENT;
          goto errout_with_semaphore;
        }

      /* Yes.. create the file */

      ret = fat_dircreate(fs, &dirinfo);
      if (ret < 0)
        {
          goto errout_with_semaphore;
        }

      /* Fall through to finish the file open operation */
      
      direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset];
patacongo's avatar
patacongo committed
    }
  else
    {
      /* An error occurred while checking for file existence --
       * such as if an invalid path were provided.
       */

      goto errout_with_semaphore;
    }

  /* Create an instance of the file private date to describe the opened
   * file.
   */

patacongo's avatar
patacongo committed
  ff = (struct fat_file_s *)kzalloc(sizeof(struct fat_file_s));
patacongo's avatar
patacongo committed
  if (!ff)
    {
      ret = -ENOMEM;
      goto errout_with_semaphore;
    }
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
  /* Create a file buffer to support partial sector accesses */

patacongo's avatar
patacongo committed
  ff->ff_buffer = (uint8_t*)kmalloc(fs->fs_hwsectorsize);
patacongo's avatar
patacongo committed
  if (!ff->ff_buffer)
    {
      ret = -ENOMEM;
      goto errout_with_struct;
    }

  /* Initialize the file private data (only need to initialize non-zero elements) */

  ff->ff_open             = true;
patacongo's avatar
patacongo committed
  ff->ff_oflags           = oflags;

  /* Save information that can be used later to recover the directory entry */

  ff->ff_dirsector        = fs->fs_currentsector;
  ff->ff_dirindex         = dirinfo.dir.fd_index;

  /* File cluster/size info */

  ff->ff_startcluster     =
    ((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) |
      DIR_GETFSTCLUSTLO(direntry);
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
  ff->ff_currentcluster   = ff->ff_startcluster;
  ff->ff_sectorsincluster = fs->fs_fatsecperclus;
  ff->ff_size             = DIR_GETFILESIZE(direntry);
patacongo's avatar
patacongo committed

  /* Attach the private date to the struct file instance */

  filep->f_priv = ff;
patacongo's avatar
patacongo committed

  /* Then insert the new instance into the mountpoint structure.
   * It needs to be there (1) to handle error conditions that effect
   * all files, and (2) to inform the umount logic that we are busy
   * (but a simple reference count could have done that).
   */

  ff->ff_next = fs->fs_head;
  fs->fs_head = ff->ff_next;

  fat_semgive(fs);
 
  /* In write/append mode, we need to set the file pointer to the end of the file */

  if ((oflags & (O_APPEND|O_WRONLY)) == (O_APPEND|O_WRONLY))
    {
      off_t offset = fat_seek(filep, ff->ff_size, SEEK_SET);
patacongo's avatar
patacongo committed
          kfree(ff);
patacongo's avatar
patacongo committed
  return OK;

  /* Error exits -- goto's are nasty things, but they sure can make error
   * handling a lot simpler.
   */

errout_with_struct:
patacongo's avatar
patacongo committed
  kfree(ff);
patacongo's avatar
patacongo committed

errout_with_semaphore:
  fat_semgive(fs);
  return ret;
}

/****************************************************************************
 * Name: fat_close
 ****************************************************************************/

static int fat_close(FAR struct file *filep)
patacongo's avatar
patacongo committed
{
  struct inode         *inode;
  struct fat_mountpt_s *fs;
  struct fat_file_s    *ff;
  int                   ret = OK;

  /* Sanity checks */

  DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
patacongo's avatar
patacongo committed

  /* Recover our private data from the struct file instance */

  ff    = filep->f_priv;
  inode = filep->f_inode;
patacongo's avatar
patacongo committed
  fs    = inode->i_private;

  DEBUGASSERT(fs != NULL);

  /* Do not check if the mount is healthy.  We must support closing of
   * the file even when there is healthy mount.
   */

  /* Synchronize the file buffers and disk content; update times */

  ret = fat_sync(filep);
patacongo's avatar
patacongo committed

  /* Then deallocate the memory structures created when the open method
   * was called.
   *
   * Free the sector buffer that was used to manage partial sector accesses.
   */

  if (ff->ff_buffer)
patacongo's avatar
patacongo committed
    {
patacongo's avatar
patacongo committed
      kfree(ff->ff_buffer);
patacongo's avatar
patacongo committed
    }
patacongo's avatar
patacongo committed

  /* Then free the file structure itself. */

patacongo's avatar
patacongo committed
  kfree(ff);
  filep->f_priv = NULL;
patacongo's avatar
patacongo committed
  return ret;
}

/****************************************************************************
 * Name: fat_read
 ****************************************************************************/

static ssize_t fat_read(FAR struct file *filep, char *buffer, size_t buflen)
patacongo's avatar
patacongo committed
{
  struct inode         *inode;
  struct fat_mountpt_s *fs;
  struct fat_file_s    *ff;
  unsigned int          bytesread;
  unsigned int          readsize;
  unsigned int          nsectors;
  size_t                bytesleft;
  int32_t               cluster;
  uint8_t               *userbuffer = (uint8_t*)buffer;
patacongo's avatar
patacongo committed
  int                   sectorindex;
patacongo's avatar
patacongo committed
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
patacongo's avatar
patacongo committed

  /* Recover our private data from the struct file instance */

  ff    = filep->f_priv;
  inode = filep->f_inode;
patacongo's avatar
patacongo committed
  fs    = inode->i_private;

  DEBUGASSERT(fs != NULL);

  /* Make sure that the mount is still healthy */

  fat_semtake(fs);
  ret = fat_checkmount(fs);
  if (ret != OK)
    {
      goto errout_with_semaphore;
    }

  /* Check if the file was opened with read access */

  if ((ff->ff_oflags & O_RDOK) == 0)
    {
      ret = -EACCES;
      goto errout_with_semaphore;
    }

  /* Get the number of bytes left in the file */

  bytesleft = ff->ff_size - filep->f_pos;
patacongo's avatar
patacongo committed

  /* Truncate read count so that it does not exceed the number
   * of bytes left in the file.
   */

  if (buflen > bytesleft)
patacongo's avatar
patacongo committed
    {
patacongo's avatar
patacongo committed
      buflen = bytesleft;
patacongo's avatar
patacongo committed
    }
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
  /* Get the first sector to read from. */
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
  if (!ff->ff_currentsector)
patacongo's avatar
patacongo committed
    {
patacongo's avatar
patacongo committed
      /* The current sector can be determined from the current cluster
       * and the file offset.
patacongo's avatar
patacongo committed
       */

patacongo's avatar
patacongo committed
      ret = fat_currentsector(fs, ff, filep->f_pos);
      if (ret < 0)
        {
           return ret;
        }
patacongo's avatar
patacongo committed
    }
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
  /* Loop until either (1) all data has been transferred, or (2) an
   * error occurs.  We assume we start with the current sector
  * (ff_currentsector) which may be uninitialized.
   */
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
  readsize    = 0;
  sectorindex = filep->f_pos & SEC_NDXMASK(fs);
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
  while (buflen > 0)
    {
      bytesread  = 0;
patacongo's avatar
patacongo committed

      /* Check if the user has provided a buffer large enough to
patacongo's avatar
patacongo committed
       * hold one or more complete sectors -AND- the read is
       * aligned to a sector boundary.
patacongo's avatar
patacongo committed
       */

      nsectors = buflen / fs->fs_hwsectorsize;
patacongo's avatar
patacongo committed
      if (nsectors > 0 && sectorindex == 0)
patacongo's avatar
patacongo committed
        {
          /* Read maximum contiguous sectors directly to the user's
           * buffer without using our tiny read buffer.
           *
           * Limit the number of sectors that we read on this time
           * through the loop to the remaining contiguous sectors
           * in this cluster
           */

          if (nsectors > ff->ff_sectorsincluster)
            {
              nsectors = ff->ff_sectorsincluster;
            }

          /* We are not sure of the state of the file buffer so
           * the safest thing to do is just invalidate it
           */

          (void)fat_ffcacheinvalidate(fs, ff);

patacongo's avatar
patacongo committed
          /* Read all of the sectors directly into user memory */
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
          ret = fat_hwread(fs, userbuffer, ff->ff_currentsector, nsectors);
patacongo's avatar
patacongo committed
          if (ret < 0)
            {
              goto errout_with_semaphore;
            }

patacongo's avatar
patacongo committed
          ff->ff_sectorsincluster -= nsectors;
          ff->ff_currentsector    += nsectors;
patacongo's avatar
patacongo committed
          bytesread                = nsectors * fs->fs_hwsectorsize;
        }
      else
        {
          /* We are reading a partial sector.  First, read the whole sector
           * into the file data buffer.  This is a caching buffer so if
           * it is already there then all is well.
           */

patacongo's avatar
patacongo committed
          ret = fat_ffcacheread(fs, ff, ff->ff_currentsector);
patacongo's avatar
patacongo committed
          if (ret < 0)
            {
              goto errout_with_semaphore;
            }

          /* Copy the partial sector into the user buffer */

          bytesread = fs->fs_hwsectorsize - sectorindex;
          if (bytesread > buflen)
            {
patacongo's avatar
patacongo committed
              /* We will not read to the end of the buffer */

patacongo's avatar
patacongo committed
              bytesread = buflen;
            }
patacongo's avatar
patacongo committed
          else
            {
patacongo's avatar
patacongo committed
              /* We will read to the end of the buffer (or beyond) */

patacongo's avatar
patacongo committed
              ff->ff_sectorsincluster--;
              ff->ff_currentsector++;
            }
patacongo's avatar
patacongo committed

          memcpy(userbuffer, &ff->ff_buffer[sectorindex], bytesread);
        }

      /* Set up for the next sector read */

      userbuffer   += bytesread;
      filep->f_pos += bytesread;
      readsize     += bytesread;
      buflen       -= bytesread;
patacongo's avatar
patacongo committed
      sectorindex   = filep->f_pos & SEC_NDXMASK(fs);

      /* Check if the current read stream has incremented to the next
       * cluster boundary
       */

      if (ff->ff_sectorsincluster < 1)
        {
          /* Find the next cluster in the FAT. */

patacongo's avatar
patacongo committed
          cluster = fat_getcluster(fs, ff->ff_currentcluster);
patacongo's avatar
patacongo committed
          if (cluster < 2 || cluster >= fs->fs_nclusters)
            {
              ret = -EINVAL; /* Not the right error */
              goto errout_with_semaphore;
            }

          /* Setup to read the first sector from the new cluster */

          ff->ff_currentcluster   = cluster;
          ff->ff_currentsector    = fat_cluster2sector(fs, cluster);
          ff->ff_sectorsincluster = fs->fs_fatsecperclus;
        }
patacongo's avatar
patacongo committed
    }

  fat_semgive(fs);
  return readsize;

errout_with_semaphore:
  fat_semgive(fs);
  return ret;
}

/****************************************************************************
 * Name: fat_write
 ****************************************************************************/

static ssize_t fat_write(FAR struct file *filep, const char *buffer,
patacongo's avatar
patacongo committed
                         size_t buflen)
{
  struct inode         *inode;
  struct fat_mountpt_s *fs;
  struct fat_file_s    *ff;
  int32_t               cluster;
patacongo's avatar
patacongo committed
  unsigned int          byteswritten;
  unsigned int          writesize;
  unsigned int          nsectors;
  uint8_t              *userbuffer = (uint8_t*)buffer;
patacongo's avatar
patacongo committed
  int                   sectorindex;
patacongo's avatar
patacongo committed
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
patacongo's avatar
patacongo committed

  /* Recover our private data from the struct file instance */

  ff    = filep->f_priv;
  inode = filep->f_inode;
patacongo's avatar
patacongo committed
  fs    = inode->i_private;

  DEBUGASSERT(fs != NULL);

  /* Make sure that the mount is still healthy */

  fat_semtake(fs);
  ret = fat_checkmount(fs);
  if (ret != OK)
    {
      goto errout_with_semaphore;
    }

  /* Check if the file was opened for write access */

  if ((ff->ff_oflags & O_WROK) == 0)
    {
      ret = -EACCES;
      goto errout_with_semaphore;
    }

  /* Check if the file size would exceed the range of off_t */
patacongo's avatar
patacongo committed

  if (ff->ff_size + buflen < ff->ff_size)
    {
      ret = -EFBIG;
      goto errout_with_semaphore;
    }

patacongo's avatar
patacongo committed
  /* Get the first sector to write to. */
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
  if (!ff->ff_currentsector)
patacongo's avatar
patacongo committed
    {
patacongo's avatar
patacongo committed
      /* Has the starting cluster been defined? */
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
      if (ff->ff_startcluster == 0)
patacongo's avatar
patacongo committed
        {
patacongo's avatar
patacongo committed
          /* No.. we have to create a new cluster chain */
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
          ff->ff_startcluster     = fat_createchain(fs);
          ff->ff_currentcluster   = ff->ff_startcluster;
          ff->ff_sectorsincluster = fs->fs_fatsecperclus;
        }
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
      /* The current sector can then be determined from the currentcluster
       * and the file offset.
       */
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
      ret = fat_currentsector(fs, ff, filep->f_pos);
      if (ret < 0)
        {
           return ret;
        }
patacongo's avatar
patacongo committed
    }
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
  /* Loop until either (1) all data has been transferred, or (2) an
   * error occurs.  We assume we start with the current sector in
   * cache (ff_currentsector)
   */
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
  byteswritten = 0;
  sectorindex = filep->f_pos & SEC_NDXMASK(fs);
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
  while (buflen > 0)
    {
patacongo's avatar
patacongo committed
      /* Check if the user has provided a buffer large enough to
       * hold one or more complete sectors.
       */

      nsectors = buflen / fs->fs_hwsectorsize;
      if (nsectors > 0 && sectorindex == 0)
patacongo's avatar
patacongo committed
        {
          /* Write maximum contiguous sectors directly from the user's
           * buffer without using our tiny read buffer.
           *
           * Limit the number of sectors that we write on this time
           * through the loop to the remaining contiguous sectors
           * in this cluster
           */

          if (nsectors > ff->ff_sectorsincluster)
            {
              nsectors = ff->ff_sectorsincluster;
            }

          /* We are not sure of the state of the sector cache so the
           * safest thing to do is write back any dirty, cached sector
           * and invalidate the current cache content.
patacongo's avatar
patacongo committed
           */

          (void)fat_ffcacheinvalidate(fs, ff);

patacongo's avatar
patacongo committed
          /* Write all of the sectors directly from user memory */
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
          ret = fat_hwwrite(fs, userbuffer, ff->ff_currentsector, nsectors);
patacongo's avatar
patacongo committed
          if (ret < 0)
            {
              goto errout_with_semaphore;
            }

patacongo's avatar
patacongo committed
          ff->ff_sectorsincluster -= nsectors;
          ff->ff_currentsector    += nsectors;
patacongo's avatar
patacongo committed
          writesize                = nsectors * fs->fs_hwsectorsize;
          ff->ff_bflags           |= FFBUFF_MODIFIED;
        }
      else
        {
          /* We are writing a partial sector -OR- the current sector
           * has not yet been filled.
           *
           * We will first have to read the full sector in memory as
           * part of a read-modify-write operation.  NOTE we don't
           * have to read the data on a rare case: When we are extending
           * the file (filep->f_pos == ff->ff_size) -AND- the new data
           * happens to be aligned at the beginning of the sector
           * (sectorindex == 0).
patacongo's avatar
patacongo committed
           */

          if (filep->f_pos < ff->ff_size || sectorindex != 0)
patacongo's avatar
patacongo committed
            {
              /* Read the current sector into memory (perhaps first flushing
               * the old, dirty sector to disk).
               */

patacongo's avatar
patacongo committed
              ret = fat_ffcacheread(fs, ff, ff->ff_currentsector);
patacongo's avatar
patacongo committed
              if (ret < 0)
                {
                  goto errout_with_semaphore;
                }
            }
               /* Flush unwritten data in the sector cache. */

               ret = fat_ffcacheflush(fs, ff);
               if (ret < 0)
                 {
                   goto errout_with_semaphore;
                 }

              /* Now mark the clean cache buffer as the current sector. */

              ff->ff_cachesector = ff->ff_currentsector;
            }
patacongo's avatar
patacongo committed

          /* Copy the partial sector from the user buffer */

          writesize = fs->fs_hwsectorsize - sectorindex;
          if (writesize > buflen)
patacongo's avatar
patacongo committed
            {
             /* We will not write to the end of the buffer.  Set
              * write size to the size of the user buffer.
              */
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
              writesize = buflen;
            }
patacongo's avatar
patacongo committed
          else
            {
              /* We will write to the end of the buffer (or beyond).  Bump
               * up the current sector number (actually the next sector number).
patacongo's avatar
patacongo committed

              ff->ff_sectorsincluster--;
              ff->ff_currentsector++;
patacongo's avatar
patacongo committed
            }
patacongo's avatar
patacongo committed

          /* Copy the data into the cached sector and make sure that the
           * cached sector is marked "dirty"
           */

patacongo's avatar
patacongo committed
          memcpy(&ff->ff_buffer[sectorindex], userbuffer, writesize);
          ff->ff_bflags |= (FFBUFF_DIRTY|FFBUFF_VALID|FFBUFF_MODIFIED);
patacongo's avatar
patacongo committed
        }

      /* Set up for the next write */

      userbuffer   += writesize;
      filep->f_pos += writesize;
patacongo's avatar
patacongo committed
      byteswritten += writesize;
      buflen       -= writesize;
patacongo's avatar
patacongo committed
      sectorindex   = filep->f_pos & SEC_NDXMASK(fs);

      /* Check if the current read stream has incremented to the next
       * cluster boundary
       */

      if (ff->ff_sectorsincluster < 1)
        {
          /* Extend the current cluster by one (unless lseek was used to
           * move the file position back from the end of the file)
           */

          cluster = fat_extendchain(fs, ff->ff_currentcluster);

          /* Verify the cluster number */

          if (cluster < 0)
            {
              ret = cluster;
              goto errout_with_semaphore;
            }
          else if (cluster < 2 || cluster >= fs->fs_nclusters)
            {
              ret = -ENOSPC;
              goto errout_with_semaphore;
            }

          /* Setup to write the first sector from the new cluster */

          ff->ff_currentcluster   = cluster;
          ff->ff_sectorsincluster = fs->fs_fatsecperclus;
          ff->ff_currentsector    = fat_cluster2sector(fs, cluster);
        }
   }
patacongo's avatar
patacongo committed

  /* The transfer has completed without error.  Update the file size */

  if (filep->f_pos > ff->ff_size)
patacongo's avatar
patacongo committed
    {
      ff->ff_size = filep->f_pos;
patacongo's avatar
patacongo committed
    }

  fat_semgive(fs);
  return byteswritten;

errout_with_semaphore:
  fat_semgive(fs);
  return ret;
}

/****************************************************************************
 * Name: fat_seek
 ****************************************************************************/

static off_t fat_seek(FAR struct file *filep, off_t offset, int whence)
patacongo's avatar
patacongo committed
{
  struct inode         *inode;
  struct fat_mountpt_s *fs;
  struct fat_file_s    *ff;
  int32_t               cluster;
patacongo's avatar
patacongo committed
  unsigned int          clustersize;
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
patacongo's avatar
patacongo committed

  /* Recover our private data from the struct file instance */

  ff    = filep->f_priv;
  inode = filep->f_inode;
patacongo's avatar
patacongo committed
  fs    = inode->i_private;

  DEBUGASSERT(fs != NULL);

  /* Map the offset according to the whence option */
  switch (whence)
patacongo's avatar
patacongo committed
    {
patacongo's avatar
patacongo committed
      case SEEK_SET: /* The offset is set to offset bytes. */
          position = offset;
          break;

      case SEEK_CUR: /* The offset is set to its current location plus
                      * offset bytes. */

          position = offset + filep->f_pos;
patacongo's avatar
patacongo committed
          break;

      case SEEK_END: /* The offset is set to the size of the file plus
                      * offset bytes. */

          position = offset + ff->ff_size;
          break;

      default:
          return -EINVAL;
patacongo's avatar
patacongo committed
    }
patacongo's avatar
patacongo committed

  /* Make sure that the mount is still healthy */

  fat_semtake(fs);
  ret = fat_checkmount(fs);
  if (ret != OK)
    {
patacongo's avatar
patacongo committed
      goto errout_with_semaphore;
patacongo's avatar
patacongo committed
    }

  /* Check if there is unwritten data in the file buffer */

  ret = fat_ffcacheflush(fs, ff);
  if (ret < 0)
patacongo's avatar
patacongo committed
    {
patacongo's avatar
patacongo committed
      goto errout_with_semaphore;
patacongo's avatar
patacongo committed
    }
patacongo's avatar
patacongo committed

  /* Attempts to set the position beyound the end of file will
   * work if the file is open for write access.
   */

  if (position > ff->ff_size && (ff->ff_oflags & O_WROK) == 0)
    {
        /* Otherwise, the position is limited to the file size */
patacongo's avatar
patacongo committed

patacongo's avatar
patacongo committed
        position = ff->ff_size;
    }

  /* Set file position to the beginning of the file (first cluster,
   * first sector in cluster)
   */
patacongo's avatar
patacongo committed

  filep->f_pos            = 0;
  ff->ff_sectorsincluster = fs->fs_fatsecperclus;

  /* Get the start cluster of the file */

  cluster = ff->ff_startcluster;

  /* Create a new cluster chain if the file does not have one (and
   * if we are seeking beyond zero
   */

  if (!cluster && position > 0)
    {
      cluster = fat_createchain(fs);
      if (cluster < 0)
        {
          ret = cluster;
          goto errout_with_semaphore;
        }
      ff->ff_startcluster = cluster;
    }
patacongo's avatar
patacongo committed

  /* Move file position if necessary */

  if (cluster)
patacongo's avatar
patacongo committed
    {
      /* If the file has a cluster chain, follow it to the
       * requested position.
       */
patacongo's avatar
patacongo committed

      clustersize = fs->fs_fatsecperclus * fs->fs_hwsectorsize;
      for (;;)
patacongo's avatar
patacongo committed
        {
          /* Skip over clusters prior to the one containing
           * the requested position.
           */

          ff->ff_currentcluster = cluster;
          if (position < clustersize)
            {
              break;
            }

          /* Extend the cluster chain if write in enabled.  NOTE: