summaryrefslogblamecommitdiff
path: root/nuttx/fs/fs_fat32.c
blob: 9d1b273f2933661e3b7c49971f27efaae116971c (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11





                                                                             




                                                                       


































                                                                              
                     
                       
                   
                   
                   

                      
                  
                  
                  

                     
                      



                        



                                                                              







                                                                              
                                                                   
                                                 

                                                                            

                                                                   

                                                                            
                                               
 

                                                                      

                                                                                
 

                                                                      
                                                                      

                                                                     

                                                                      
                                      
                                                                     

                                                                        
                                                                                      





















                                                                              
           
 
              


                
 
           
             

             

             
            

             






                                                                              
                 

                                                                              
                                                               
                                            
 




                                
 
                     
 
                                                             
 


                                                                       
 

                           
 
                          
 
                                           
 


                           
     
                                 
     
 
                                               
 
                                                    
 
                                                
 
                                                
 
                                                                 

                                                                 

     
                
     
                       
 
                                                               
 

                                                                   
         


                                     

         
                                                                         
 





                                                             
 


                                                               
 
                                                                          
 
                                                                                 
                                                 



                                     
 






                                                                          
         



                                                
             
                                         

             
 
                                                           
 
     
                          
     
                                                                 
 
                                  




                                              
 
                                 
 




                                        
 
                                                          

      
     


                                                               
 
                                 

     

                                                                       

     

                                                              

                    
                                 
     
  








                                                               
                                                                                    
 


                                   
 
                                                                              
 
                                                 
                                                 
 
                              
 


                                                           
 
                                                              
 
                                                                                    
 
                                                            
     
                                        

     
                                                           
 
                    












                                                                    





                                                                         
 

                       
             







                                                                              
                              
                           
                           
                                 
 



                                                             
                                                              





                           
 


                                                                      
 



















                                                                            







                                                                              
                              
                           
                           
                                




                                   
                                                     
                            
 



                                                             
                                                              





                           
 

                                                 
                  


                           
                                 
     






















































                                                                        
                                                                        


                                       

                                                         

































                                                                          

                                                                       


































































                                                                               





                                                                              

                                                                   
 
                              
                           
                           





                                                    
                            
 



                                                             
                                                              





                           
 

                                                 
                  


                           









































































































































































                                                                               
 

























                                                                       
     

                  




                       







                                                                              
                              
                           
                           



                                     
                            
 



                                                             
                                                              





                           
 






















                                                                      

                                                 
                  


                           































































































































































                                                                            
 



                                                                      
     

                  




                       







                                                                              
                              
                           

                            
 



                                                             
                                                              





                           
 

                                                 
                  


                           
                      



                                                                         

                  



                                                                             





























































                                                                              
                                                      











                                                             
                                      

                                                 


















                                                                             










































                                                                                              



                                                   



                                                                                 
                                                                          








                                         
                                   

                                                               






























































































































































                                                                                     










                                                                             









                                                                              

                                                                  



                           












                                                  
                                                         
 





                                                                    



                                                                         
 

                                                                                    

























                                                                              
                                                                 
 
























                                                                   
                    
             




                                                            




                                                                                      
 



                                     













                                               


                                                                             




















































                                                                                                      





                                                                              
                                                                 















                                                                
                
     



                                                                         
 





                                                                   
 










                                                                              
                                                                             

                           









                                

















                                                                




























































































                                                                                 
                                 


















                                                                 
                                              




















































                                                                        












                                                                              
                                                         















                                                                
                
     



                                                                                  
 





                                                                        
 










                                                                              

                                                             

                           




                                        


















                                                                
































































































                                                                            






                                                                             



















































































                                                                                 

                                                                             




























                                                                             


                                                                              
/****************************************************************************
 * fs_fat32.c
 *
 *   Copyright (C) 2007 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <spudmonkey@racsa.co.cr>
 *
 * References:
 *   Microsoft FAT documentation
 *   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 Gregory Nutt 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>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
#include <assert.h>
#include <fcntl.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/fs.h>
#include <nuttx/fat.h>

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

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

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

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

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

static int     fat_opendir(struct inode *mountpt, const char *relpath,
                           struct internal_dir_s *dir);
static int     fat_readdir(struct inode *mountpt, struct internal_dir_s *dir);
static int     fat_rewinddir(struct inode *mountpt, struct internal_dir_s *dir);

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 *filp, const char *relpath,
                    int oflags, mode_t mode)
{
  struct fat_dirinfo_s  dirinfo;
  struct inode         *inode;
  struct fat_mountpt_s *fs;
  struct fat_file_s    *ff;
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(filp->f_priv == NULL && filp->f_inode != NULL);

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

  inode = filp->f_inode;
  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)
    {
      boolean readonly;

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

      if (dirinfo.fd_entry == NULL ||
         (DIR_GETATTRIBUTES(dirinfo.fd_entry) & FATATTR_DIRECTORY))
        {
          /* It is a directory */
          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 */
          ret = -EEXIST;
          goto errout_with_semaphore;
        }

#ifdef CONFIG_FILE_MODE
# warning "Missing check for privileges based on inode->i_mode"
#endif

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

      readonly = ((DIR_GETATTRIBUTES(dirinfo.fd_entry) & FATATTR_READONLY) != 0);
      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 */
          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 */
    }
  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.
   */

  ff = (struct fat_file_s *)zalloc(sizeof(struct fat_file_s));
  if (!ff)
    {
      ret = -ENOMEM;
      goto errout_with_semaphore;
    }
  
  /* Create a file buffer to support partial sector accesses */

  ff->ff_buffer = (ubyte*)malloc(fs->fs_hwsectorsize);
  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;
  ff->ff_oflags           = oflags;
  ff->ff_sectorsincluster = 1;

  /* 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)DIR_GETFSTCLUSTHI(dirinfo.fd_entry) << 16) |
      DIR_GETFSTCLUSTLO(dirinfo.fd_entry);

  ff->ff_size             = DIR_GETFILESIZE(dirinfo.fd_entry);

  /* 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))
    {
        ff->ff_position   = ff->ff_size;
    }

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

  filp->f_priv = ff;

  /* 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);
  return OK;

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

 errout_with_struct:
  free(ff);

 errout_with_semaphore:
  fat_semgive(fs);
  return ret;
}

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

static int fat_close(FAR struct file *filp)
{
  struct inode         *inode;
  struct fat_mountpt_s *fs;
  struct fat_file_s    *ff;
  int                   ret = OK;

  /* Sanity checks */

  DEBUGASSERT(filp->f_priv != NULL && filp->f_inode != NULL);

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

  ff    = filp->f_priv;
  inode = filp->f_inode;
  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(filp);

  /* 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)
  {
      free(ff->ff_buffer);
  }

  /* Then free the file structure itself. */

  free(ff);
  filp->f_priv = NULL;
  return ret;
}

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

static ssize_t fat_read(FAR struct file *filp, char *buffer, size_t buflen)
{
  struct inode         *inode;
  struct fat_mountpt_s *fs;
  struct fat_file_s    *ff;
  uint32                cluster;
  unsigned int          bytesread;
  unsigned int          readsize;
  unsigned int          nsectors;
  size_t                readsector;
  size_t                bytesleft;
  ubyte                 *userbuffer = (ubyte*)buffer;
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(filp->f_priv != NULL && filp->f_inode != NULL);

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

  ff    = filp->f_priv;
  inode = filp->f_inode;
  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 - ff->ff_position;

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

  if (buflen > bytesleft)
  {
      buflen = bytesleft;
  }

  /* Loop until either (1) all data has been transferred, or (2) an
   * error occurs.
   */

  readsize = 0;
  while (buflen > 0)
    {
      /* Get offset into the sector where we begin the read */

      int sectorindex = ff->ff_position & SEC_NDXMASK(fs);

      /* Check if the current read stream happens to lie on a
       * sector boundary.
       */

      if (sectorindex == 0)
        {
          /* Try to read another contiguous sector from the cluster */

          ff->ff_sectorsincluster--;

          /* Are there unread sectors remaining in the cluster? */

          if (ff->ff_sectorsincluster > 0)
            {
              /* Yes.. There are more sectors in this cluster to be read
               * just increment the current sector number and read.
               */

              readsector = ff->ff_currentsector + 1;
            }
          else
            {
              /* No.. Handle the case of the first sector of the file */

              if (ff->ff_position == 0)
                {
                  /* Get the first cluster of the file */

                  cluster = ff->ff_startcluster;
                }

              /* But in the general case, we have to find the next cluster
               * in the FAT.
               */

              else
                {
                  cluster = fat_getcluster(fs, ff->ff_currentcluster);
                }

              /* Verify the cluster number */

              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;
              readsector              = fat_cluster2sector(fs, cluster);
              ff->ff_sectorsincluster = fs->fs_fatsecperclus;
            }

          /* 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)
            {
              /* 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);

              /* Read all of the sectors directory into user memory */

              ret = fat_hwread(fs, userbuffer, readsector, nsectors);
              if (ret < 0)
                {
                  goto errout_with_semaphore;
                }

              ff->ff_sectorsincluster -= nsectors - 1;
              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.
               */

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

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

              bytesread = fs->fs_hwsectorsize - sectorindex;
              if (bytesread > buflen)
                {
                  bytesread = buflen;
                }

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

          /* Set up for the next sector read */

          userbuffer      += bytesread;
          ff->ff_position += bytesread;
          readsize        += bytesread;
          buflen          -= bytesread;
        }
    }

  fat_semgive(fs);
  return readsize;

 errout_with_semaphore:
  fat_semgive(fs);
  return ret;
}

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

static ssize_t fat_write(FAR struct file *filp, const char *buffer,
                         size_t buflen)
{
  struct inode         *inode;
  struct fat_mountpt_s *fs;
  struct fat_file_s    *ff;
  sint32                cluster;
  size_t                writesector;
  unsigned int          byteswritten;
  unsigned int          writesize;
  unsigned int          nsectors;
  ubyte                *userbuffer = (ubyte*)buffer;
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(filp->f_priv != NULL && filp->f_inode != NULL);

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

  ff    = filp->f_priv;
  inode = filp->f_inode;
  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 size_t */

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

  /* Loop until either (1) all data has been transferred, or (2) an
   * error occurs.
   */

  byteswritten = 0;
  while (buflen > 0)
    {
      /* Get offset into the sector where we begin the read */

      int sectorindex = ff->ff_position & SEC_NDXMASK(fs);

      /* Check if the current read stream happens to lie on a
       * sector boundary.
       */

      if (sectorindex == 0)
        {
          /* Decrement the number of sectors left in this cluster */

          ff->ff_sectorsincluster--;

          /* Are there unwritten sectors remaining in this cluster */

          if (ff->ff_sectorsincluster > 0)
            {
              /* Yes.. There are more sectors in this cluster to be written.
               * just increment the current sector number and write.
               */

              writesector = ff->ff_currentsector + 1;
            }
          else
            {
              /* No.. Handle the case of the first sector of the file */

              if (ff->ff_position == 0)
                {
                  /* Check the first cluster of the file.  Zero means that
                   * the file is empty -- perhaps the file was truncated or
                   * created when it was opened
                   */

                  if (ff->ff_startcluster == 0)
                    {
                      /* In this case, we have to create a new cluster chain */

                      ff->ff_startcluster = fat_createchain(fs);
                    }

                  /* Start writing at the first cluster of the file */

                  cluster = ff->ff_startcluster;
                }

              /* But in the general case, we have to extend the current
               * cluster by one (unless lseek was used to move the file
               * position back from the end of the file)
               */

              else
                {
                    /* Extend the chain by adding a new cluster after
                     * the last one
                     */

                    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;
              writesector             = fat_cluster2sector(fs, cluster);
              ff->ff_sectorsincluster = fs->fs_fatsecperclus;
            }
        }

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

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

      /* 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)
        {
          /* 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 file buffer so
           * the safest thing to do is just invalidate it
           */

          (void)fat_ffcacheinvalidate(fs, ff);

          /* Write all of the sectors directory from user memory */

          ret = fat_hwwrite(fs, userbuffer, writesector, nsectors);
          if (ret < 0)
            {
              goto errout_with_semaphore;
            }

          ff->ff_sectorsincluster -= nsectors - 1;
          writesize                = nsectors * fs->fs_hwsectorsize;
          ff->ff_bflags           |= FFBUFF_MODIFIED;
        }
      else
        {
          /* We are write a partial sector.  We will first have to
           * read the full sector in memory as part of a read-modify-write
           * operation.
           */

          if (ff->ff_position < ff->ff_size)
            {
              ff->ff_currentsector = writesector;
              ret = fat_ffcacheread(fs, ff, writesector);
              if (ret < 0)
                {
                  goto errout_with_semaphore;
                }
            }

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

          writesize = fs->fs_hwsectorsize - sectorindex;
          if (writesize > buflen)
            {
              writesize = buflen;
            }

          memcpy(&ff->ff_buffer[sectorindex], userbuffer, writesize);
          ff->ff_currentsector = writesector;
          ff->ff_bflags |= (FFBUFF_DIRTY|FFBUFF_VALID|FFBUFF_MODIFIED);
        }

      /* Set up for the next write */

      userbuffer += writesize;
      ff->ff_position += writesize;
      byteswritten += writesize;
      buflen -= writesize;
    }

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

  if (ff->ff_position > ff->ff_size)
    {
      ff->ff_size = ff->ff_position;
    }

  fat_semgive(fs);
  return byteswritten;

 errout_with_semaphore:
  fat_semgive(fs);
  return ret;
}

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

static off_t fat_seek(FAR struct file *filp, off_t offset, int whence)
{
  struct inode         *inode;
  struct fat_mountpt_s *fs;
  struct fat_file_s    *ff;
  sint32                cluster;
  ssize_t               position;
  unsigned int          clustersize;
  unsigned int          sectoroffset;
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(filp->f_priv != NULL && filp->f_inode != NULL);

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

  ff    = filp->f_priv;
  inode = filp->f_inode;
  fs    = inode->i_private;

  DEBUGASSERT(fs != NULL);

  /* Map the offset according to the whence option */
  switch (whence)
  {
      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 + ff->ff_position;
          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;
  }

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

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

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

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

  /* 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 */
        position = ff->ff_size;
    }

  /* Set file position to the beginning of the file */

  ff->ff_position         = 0;
  ff->ff_sectorsincluster = 1;

  /* Move file position if necessary */

  if (position)
    {
        /* Get the start cluster of the file */

        cluster = ff->ff_startcluster;
        if (!cluster)
        {
            /* Create a new cluster chain if the file does not have one */

            cluster = fat_createchain(fs);
            if (cluster < 0)
            {
                ret = cluster;
                goto errout_with_semaphore;
            }
            ff->ff_startcluster = cluster;
        }

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

            clustersize = fs->fs_fatsecperclus * fs->fs_hwsectorsize;
            for (;;)
            {
                /* 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:
                 * this is not consistent with the lseek description:
                 * "The  lseek() function allows the file offset to be
                 * set beyond the end of the file (but this does not
                 * change the size of the file).  If data is later written
                 * at  this  point, subsequent reads of the data in the
                 * gap (a "hole") return null bytes ('\0') until data
                 * is actually written into the gap."
                 */

                if ((ff->ff_oflags & O_WROK) != 0)
                {
                    /* Extend the cluster chain (fat_extendchain
                     * will follow the existing chain or add new
                     * clusters as needed.
                     */

                    cluster = fat_extendchain(fs, cluster);
                }
                else
                {
                    /* Other we can only follong the existing chain */

                    cluster = fat_getcluster(fs, cluster);
                }

                if (cluster < 0)
                {
                    /* An error occurred getting the cluster */

                    ret = cluster;
                    goto errout_with_semaphore;
                }

                /* Zero means that there is no further clusters available
                 * in the chain.
                 */

                if (cluster == 0)
                {
                    /* At the position to the current locaiton and
                     * break out.
                     */

                    position = clustersize;
                    break;
                }

                if (cluster >= fs->fs_nclusters)
                {
                    ret = -ENOSPC;
                    goto errout_with_semaphore;
                }

                /* Otherwise, update the position and continue looking */

                ff->ff_position += clustersize;
                position        -= clustersize;
            }

            /* We get here after we have found the sector containing
             * the requested position.
             */

            sectoroffset = (position - 1) / fs->fs_hwsectorsize;

            /* And get the current sector from the cluster and
             * the sectoroffset into the cluster.
             */

            ff->ff_currentsector =
                fat_cluster2sector(fs, cluster) + sectoroffset;

            /* Load the sector corresponding to the position */

            if ((position & SEC_NDXMASK(fs)) != 0)
            {
                ret = fat_ffcacheread(fs, ff, ff->ff_currentsector);
                if (ret < 0)
                {
                    goto errout_with_semaphore;
                }
            }

            /* Save the number of sectors left in the cluster */

            ff->ff_sectorsincluster = fs->fs_fatsecperclus - sectoroffset;

            /* And save the new file position */

            ff->ff_position += position;
        }
    }

  /* If we extended the size of the file, then mark the file as modified. */

  if ((ff->ff_oflags & O_WROK) != 0 &&  ff->ff_position > ff->ff_size)
    {
        ff->ff_size    = ff->ff_position;
        ff->ff_bflags |= FFBUFF_MODIFIED;
    }

  fat_semgive(fs);
  return OK;

 errout_with_semaphore:
  fat_semgive(fs);
  return ret;
}

/****************************************************************************
 * Name: fat_ioctl
 ****************************************************************************/

static int fat_ioctl(FAR struct file *filp, int cmd, unsigned long arg)
{
  struct inode         *inode;
  struct fat_mountpt_s *fs;
  struct fat_file_s    *ff;
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(filp->f_priv != NULL && filp->f_inode != NULL);

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

  ff    = filp->f_priv;
  inode = filp->f_inode;
  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)
    {
      fat_semgive(fs);
      return ret;
    }

  /* ioctl calls are just passed through to the contained block driver */

  fat_semgive(fs);
  return -ENOSYS;
}

/****************************************************************************
 * Name: fat_sync
 *
 * Description: Synchronize the file state on disk to match internal, in-
 *   memory state.
 *
 ****************************************************************************/

static int fat_sync(FAR struct file *filp)
{
  struct inode         *inode;
  struct fat_mountpt_s *fs;
  struct fat_file_s    *ff;
  uint32                wrttime;
  ubyte                *direntry;
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(filp->f_priv != NULL && filp->f_inode != NULL);

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

  ff    = filp->f_priv;
  inode = filp->f_inode;
  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 has been modified in any way */
  if ((ff->ff_bflags & FFBUFF_MODIFIED) != 0)
    {
      /* Flush any unwritten data in the file buffer */

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

      /* Update the directory entry.  First read the directory
       * entry into the fs_buffer (preserving the ff_buffer)
       */

      ret = fat_fscacheread(fs, ff->ff_dirsector);
      if (ret < 0)
        {
          goto errout_with_semaphore;
        }

      /* Recover a pointer to the specific directory entry
       * in the sector using the saved directory index.
       */

      direntry = &fs->fs_buffer[ff->ff_dirindex * 32];

      /* Set the archive bit, set the write time, and update
       * anything that may have* changed in the directory
       * entry: the file size, and the start cluster
       */

      direntry[DIR_ATTRIBUTES] |= FATATTR_ARCHIVE;

      DIR_PUTFILESIZE(direntry, ff->ff_size);
      DIR_PUTFSTCLUSTLO(direntry, ff->ff_startcluster);
      DIR_PUTFSTCLUSTHI(direntry, ff->ff_startcluster >> 16);

      wrttime = fat_systime2fattime();
      DIR_PUTWRTTIME(direntry, wrttime & 0xffff);
      DIR_PUTWRTDATE(direntry, wrttime >> 16);

      /* Clear the modified bit in the flags */

      ff->ff_bflags &= ~FFBUFF_MODIFIED;

      /* Flush these change to disk and update FSINFO (if
       * appropriate.
       */

      fs->fs_dirty = TRUE;
      ret          = fat_updatefsinfo(fs);
    }

 errout_with_semaphore:
  fat_semgive(fs);
  return ret;
}

/****************************************************************************
 * Name: fat_opendir
 *
 * Description: Open a directory for read access
 *
 ****************************************************************************/

static int fat_opendir(struct inode *mountpt, const char *relpath, struct internal_dir_s *dir)
{
  struct fat_mountpt_s *fs;
  struct fat_dirinfo_s  dirinfo;
  int                     ret;

  /* Sanity checks */

  DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL);

  /* Recover our private data from the inode instance */

  fs = mountpt->i_private;

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

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

  /* Find the requested directory */

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

  /* Check if this is the root directory */

  if (dirinfo.fd_entry == NULL)
    {
      /* Handler the FAT12/16 root directory */

      dir->u.fat.fd_startcluster = 0;
      dir->u.fat.fd_currcluster  = 0;
      dir->u.fat.fd_currsector   = fs->fs_rootbase;
      dir->u.fat.fd_index        = 2;
    }

  /* This is not the root directory.  Verify that it is some kind of directory */

  else if ((DIR_GETATTRIBUTES(dirinfo.fd_entry) & FATATTR_DIRECTORY) == 0)
    {
       /* The entry is not a directory */
       ret = -ENOTDIR;
       goto errout_with_semaphore;
    }
  else
    {
       /* The entry is a directory */

      dir->u.fat.fd_startcluster = 
          ((uint32)DIR_GETFSTCLUSTHI(dirinfo.fd_entry) << 16) |
                   DIR_GETFSTCLUSTLO(dirinfo.fd_entry);
      dir->u.fat.fd_currcluster  = dir->u.fat.fd_startcluster;
      dir->u.fat.fd_currsector   = fat_cluster2sector(fs, dir->u.fat.fd_currcluster);
      dir->u.fat.fd_index        = 2;
    }

  fat_semgive(fs);
  return OK;

errout_with_semaphore:
  fat_semgive(fs);
  return ERROR;
}

/****************************************************************************
 * Name: fat_readdir
 *
 * Description: Read the next directory entry
 *
 ****************************************************************************/

static int fat_readdir(struct inode *mountpt, struct internal_dir_s *dir)
{
  struct fat_mountpt_s *fs;
  unsigned int            dirindex;
  ubyte                  *direntry;
  ubyte                   ch;
  ubyte                   attribute;
  int                     ret;

  /* Sanity checks */

  DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL);

  /* Recover our private data from the inode instance */

  fs = mountpt->i_private;

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

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

  /* Read the next directory entry */

  dir->fd_dir.d_name[0] = '\0';
  while (dir->u.fat.fd_currsector && dir->fd_dir.d_name[0] == '\0')
    {
      ret = fat_fscacheread(fs, dir->u.fat.fd_currsector);
      if ( ret < 0)
        {
          goto errout_with_semaphore;
        }

      /* Get a reference to the current directory entry */

      dirindex = (dir->u.fat.fd_index & DIRSEC_NDXMASK(fs)) * 32;
      direntry = &fs->fs_buffer[dirindex];

      /* Has it reached to end of the directory */

      ch = *direntry;
      if (ch == DIR0_ALLEMPTY)
        {
          /* We signal the end of the directory by returning the
           * special error -ENOENT
           */

          ret = -ENOENT;
          goto errout_with_semaphore;
        }

      /* No, is the current entry a valid entry? */

      attribute = DIR_GETATTRIBUTES(direntry);
      if (ch != DIR0_EMPTY && (attribute & FATATTR_VOLUMEID) == 0)
        {
          /* Yes.. get the name from the directory info */

          (void)fat_dirname2path(dir->fd_dir.d_name, direntry);

          /* And the file type */

          if ((attribute & FATATTR_DIRECTORY) == 0)
            {
              dir->fd_dir.d_type = DTYPE_FILE;
            }
          else
            {
              dir->fd_dir.d_type = DTYPE_DIRECTORY;
            }
        }

      /* Set up the next directory index */

      if (fat_nextdirentry(fs, &dir->u.fat) != OK)
        {
          dir->u.fat.fd_currsector = 0;
        }
    }

  fat_semgive(fs);
  return OK;

errout_with_semaphore:
  fat_semgive(fs);
  return ERROR;
}

/****************************************************************************
 * Name: fat_rewindir
 *
 * Description: Reset directory read to the first entry
 *
 ****************************************************************************/

static int fat_rewinddir(struct inode *mountpt, struct internal_dir_s *dir)
{
  struct fat_mountpt_s *fs;
  int ret;

  /* Sanity checks */

  DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL);

  /* Recover our private data from the inode instance */

  fs = mountpt->i_private;

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

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

  /* Check if this is the root directory */

  if (dir->u.fat.fd_startcluster == 0)
    {
      /* Handler the FAT12/16 root directory */

      dir->u.fat.fd_currcluster  = 0;
      dir->u.fat.fd_currsector   = fs->fs_rootbase;
      dir->u.fat.fd_index        = 2;
    }

  /* This is not the root directory */

  else
    {
      dir->u.fat.fd_currcluster  = dir->u.fat.fd_startcluster;
      dir->u.fat.fd_currsector   = fat_cluster2sector(fs, dir->u.fat.fd_currcluster);
      dir->u.fat.fd_index        = 2;
    }

  fat_semgive(fs);
  return OK;

errout_with_semaphore:
  fat_semgive(fs);
  return ERROR;
}

/****************************************************************************
 * Name: fat_bind
 *
 * Description: This implements a portion of the mount operation. This
 *  function allocates and initializes the mountpoint private data and
 *  binds the blockdriver inode to the filesystem private data.  The final
 *  binding of the private data (containing the blockdriver) to the
 *  mountpoint is performed by mount().
 *
 ****************************************************************************/

static int fat_bind(FAR struct inode *blkdriver, const void *data,
                    void **handle)
{
  struct fat_mountpt_s *fs;
  int ret;

  /* Open the block driver */

  if (!blkdriver || !blkdriver->u.i_bops)
    {
      return -ENODEV;
    }

  if ( blkdriver->u.i_bops->open &&
       blkdriver->u.i_bops->open(blkdriver) != OK)
    {
      return -ENODEV;
    }

  /* Create an instance of the mountpt state structure */

  fs = (struct fat_mountpt_s *)zalloc(sizeof(struct fat_mountpt_s));
  if ( !fs )
    {
      return -ENOMEM;
    }

  /* Initialize the allocated mountpt state structure.  The filesystem is
   * responsible for one reference ont the blkdriver inode and does not
   * have to addref() here (but does have to release in ubind().
   */

  fs->fs_blkdriver = blkdriver;  /* Save the block driver reference */
  sem_init(&fs->fs_sem, 0, 0);   /* Initialize the semaphore that controls access */

  /* Then get information about the FAT32 filesystem on the devices managed
   * by this block driver.
   */

  ret = fat_mount(fs, TRUE);
  if ( ret != 0 )
    {
      sem_destroy(&fs->fs_sem);
      free(fs);
      return ret;
    }

  *handle = (void*)fs;
  fat_semgive(fs);
  return OK;
}

/****************************************************************************
 * Name: fat_unbind
 *
 * Description: This implements the filesystem portion of the umount
 *   operation.
 *
 ****************************************************************************/

static int fat_unbind(void *handle, FAR struct inode **blkdriver)
{
  struct fat_mountpt_s *fs = (struct fat_mountpt_s*)handle;
  int ret;

  if ( !fs )
    {
      return -EINVAL;
    }

  /* Check if there are sill any files opened on the filesystem. */

  ret = OK; /* Assume success */
  fat_semtake(fs);
  if (fs->fs_head)
    {
      /* We cannot unmount now.. there are open files */

      ret = -EBUSY;
    }
  else
    {
       /* Unmount ... close the block driver */

      if (fs->fs_blkdriver)
        {
          struct inode *inode = fs->fs_blkdriver;
          if (inode)
            {
              if (inode->u.i_bops && inode->u.i_bops->close)
                {
                  (void)inode->u.i_bops->close(inode);
                }

              /* We hold a reference to the block driver but should
               * not but mucking with inodes in this context.  So, we will just return
               * our contained reference to the block driver inode and let the umount
               * logic dispose of it.
               */

              if (blkdriver)
                {
                  *blkdriver = inode;
                }
            }
        }

      /* Release the mountpoint private data */

      if (fs->fs_buffer)
        {
          free(fs->fs_buffer);
        }
      free(fs);
    }

  fat_semgive(fs);
  return ret;
}

/****************************************************************************
 * Name: fat_statfs
 *
 * Description: Return filesystem statistics
 *
 ****************************************************************************/

static int fat_statfs(struct inode *mountpt, struct statfs *buf)
{
  struct fat_mountpt_s *fs;
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(mountpt && mountpt->i_private);

  /* Get the mountpoint private data from the inode structure */

  fs = mountpt->i_private;

  /* Check if the mount is still healthy */

  fat_semtake(fs);
  ret = fat_checkmount(fs);
  if (ret < 0)
    {
       goto errout_with_semaphore;
    }

  /* Fill in the statfs info */

  memset(buf, 0, sizeof(struct statfs));
  buf->f_type    = MSDOS_SUPER_MAGIC;

  /* We will claim that the optimal transfer size is the size of a cluster in bytes */

  buf->f_bsize   = fs->fs_fatsecperclus * fs->fs_hwsectorsize;

  /* Everything else follows in units of clusters */

  buf->f_blocks  = fs->fs_nclusters;                        /* Total data blocks in the file system */
  ret = fat_nfreeclusters(fs, &buf->f_bfree);               /* Free blocks in the file system */
  buf->f_bavail  = buf->f_bfree;                            /* Free blocks avail to non-superuser */
  buf->f_namelen = (8+1+3);                                 /* Maximum length of filenames */

  fat_semgive(fs);
  return OK;

errout_with_semaphore:
  fat_semgive(fs);
  return ret;
}

/****************************************************************************
 * Name: fat_unlink
 *
 * Description: Remove a file
 *
 ****************************************************************************/

static int fat_unlink(struct inode *mountpt, const char *relpath)
{
  struct fat_mountpt_s *fs;
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(mountpt && mountpt->i_private);

  /* Get the mountpoint private data from the inode structure */

  fs = mountpt->i_private;

  /* Check if the mount is still healthy */

  fat_semtake(fs);
  ret = fat_checkmount(fs);
  if (ret == OK)
    {
      /* If the file is open, the correct behavior is to remove the file
       * name, but to keep the file cluster chain in place until the last
       * open reference to the file is closed.
       */

#warning "Need to defer deleting cluster chain if the file is open"

      /* Remove the file */

      ret = fat_remove(fs, relpath, FALSE);
    }

  fat_semgive(fs);
  return ret;
}

/****************************************************************************
 * Name: fat_mkdir
 *
 * Description: Create a directory
 *
 ****************************************************************************/

static int fat_mkdir(struct inode *mountpt, const char *relpath, mode_t mode)
{
  struct fat_mountpt_s *fs;
  struct fat_dirinfo_s  dirinfo;
  ubyte       *direntry;
  ubyte       *direntry2;
  size_t       parentsector;
  ssize_t      dirsector;
  sint32       dircluster;
  uint32       parentcluster;
  uint32       crtime;
  unsigned int i;
  int          ret;

  /* Sanity checks */

  DEBUGASSERT(mountpt && mountpt->i_private);

  /* Get the mountpoint private data from the inode structure */

  fs = mountpt->i_private;

  /* Check if the mount is still healthy */

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

  /* Find the directory where the new directory should be created. */

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

  /* If anything exists at this location, then we fail with EEXIST */

  if (ret == OK)
    {
      ret = -EEXIST;
      goto errout_with_semaphore;
    }

  /* What we want to see is for fat_finddirentry to fail with -ENOENT.
   * This error means that no failure occurred but that nothing exists
   * with this name.
   */

  if (ret != -ENOENT)
    {
      goto errout_with_semaphore;
    }

  /* NOTE: There is no check that dirinfo.fd_name contains the final
   * directory name.  We could be creating an intermediate directory
   * in the full relpath.
   */

  /* Allocate a directory entry for the new directory in this directory */

  ret = fat_allocatedirentry(fs, &dirinfo);
  if (ret != OK)
    {
      goto errout_with_semaphore;
    }
  parentsector = fs->fs_currentsector;

  /* Allocate a cluster for new directory */

  dircluster = fat_createchain(fs);
  if (dircluster < 0)
    {
      ret = dircluster;
      goto errout_with_semaphore;
    }
  else if (dircluster < 2)
    {
      ret = -ENOSPC;
      goto errout_with_semaphore;
    }

  dirsector = fat_cluster2sector(fs, dircluster);
  if (dirsector < 0)
    {
      ret = dirsector;
      goto errout_with_semaphore;
    }

  /* Flush any existing, dirty data in fs_buffer (because we need 
   * it to create the directory entries.
   */

  ret = fat_fscacheflush(fs);
  if (ret < 0)
    {
      goto errout_with_semaphore;
    }

  /* Get a pointer to the first directory entry in the sector */

  direntry = fs->fs_buffer;

  /* Now erase the contents of fs_buffer */

  fs->fs_currentsector = dirsector;
  memset(direntry, 0, fs->fs_hwsectorsize);

  /* Now clear all sectors in the new directory cluster (except for the first) */

  for (i = 1; i < fs->fs_fatsecperclus; i++)
    {
      ret = fat_hwwrite(fs, direntry, ++dirsector, 1);
      if (ret < 0)
        {
          goto errout_with_semaphore;
        }
    }

  /* Now create the "." directory entry in the first directory slot */

  memset(&direntry[DIR_NAME], ' ', 8+3);
  direntry[DIR_NAME] = '.';
  DIR_PUTATTRIBUTES(direntry, FATATTR_DIRECTORY);

  crtime = fat_systime2fattime();
  DIR_PUTCRTIME(direntry, crtime & 0xffff);
  DIR_PUTWRTTIME(direntry, crtime & 0xffff);
  DIR_PUTCRDATE(direntry, crtime >> 16);
  DIR_PUTWRTDATE(direntry, crtime >> 16);

  /* Create ".." directory entry in the second directory slot */

  direntry2 = direntry + 32;

  /* So far, the two entries are nearly the same */

  memcpy(direntry2, direntry, 32);
  direntry2[DIR_NAME+1] = '.';

  /* Now add the cluster information to both directory entries */

  DIR_PUTFSTCLUSTHI(direntry, dircluster >> 16);
  DIR_PUTFSTCLUSTLO(direntry, dircluster);

  parentcluster = dirinfo.dir.fd_startcluster;
  if (fs->fs_type != FSTYPE_FAT32 && parentcluster == fs->fs_rootbase)
    {
      parentcluster = 0;
    }

  DIR_PUTFSTCLUSTHI(direntry2, parentcluster >> 16);
  DIR_PUTFSTCLUSTLO(direntry2, parentcluster);

  /* Save the first sector of the directory cluster and re-read
   *  the parentsector
   */

  fs->fs_dirty = TRUE;
  ret = fat_fscacheread(fs, parentsector);
  if (ret < 0)
    {
      goto errout_with_semaphore;
    }

  /* Initialize the new entry directory entry in the parent directory */

  direntry = dirinfo.fd_entry;
  memset(direntry, 0, 32);

  memcpy(direntry, dirinfo.fd_name, 8+3);
#ifdef CONFIG_FLAT_LCNAMES
  DIR_PUTNTRES(direntry, dirinfo.fd_ntflags);
#endif
  DIR_PUTATTRIBUTES(dirinfo.fd_entry, FATATTR_DIRECTORY);

  /* Same creation time as for . and .. */

  DIR_PUTCRTIME(dirinfo.fd_entry, crtime & 0xffff);
  DIR_PUTWRTTIME(dirinfo.fd_entry, crtime & 0xffff);
  DIR_PUTCRDATE(dirinfo.fd_entry, crtime >> 16);
  DIR_PUTWRTDATE(dirinfo.fd_entry, crtime >> 16);

  /* Set subdirectory start cluster */

  DIR_PUTFSTCLUSTLO(dirinfo.fd_entry, dircluster);
  DIR_PUTFSTCLUSTHI(dirinfo.fd_entry, dircluster >> 16);

  /* Now update the FAT32 FSINFO sector */

  fs->fs_dirty = TRUE;
  ret = fat_updatefsinfo(fs);
  if (ret < 0)
    {
      goto errout_with_semaphore;
    }

  fat_semgive(fs);
  return OK;

 errout_with_semaphore:
  fat_semgive(fs);
  return ret;
}

/****************************************************************************
 * Name: fat_rmdir
 *
 * Description: Remove a directory
 *
 ****************************************************************************/

int fat_rmdir(struct inode *mountpt, const char *relpath)
{
  struct fat_mountpt_s *fs;
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(mountpt && mountpt->i_private);

  /* Get the mountpoint private data from the inode structure */

  fs = mountpt->i_private;

  /* Check if the mount is still healthy */

  fat_semtake(fs);
  ret = fat_checkmount(fs);
  if (ret == OK)
    {
      /* If the directory is open, the correct behavior is to remove the directory
       * name, but to keep the directory cluster chain in place until the last
       * open reference to the directory is closed.
       */

#warning "Need to defer deleting cluster chain if the directory is open"

      /* Remove the directory */

      ret = fat_remove(fs, relpath, TRUE);
    }

  fat_semgive(fs);
  return ret;
}

/****************************************************************************
 * Name: fat_rename
 *
 * Description: Rename a file or directory
 *
 ****************************************************************************/

int fat_rename(struct inode *mountpt, const char *oldrelpath,
               const char *newrelpath)
{
  struct fat_mountpt_s *fs;
  struct fat_dirinfo_s  dirinfo;
  size_t                oldsector;
  ubyte                *olddirentry;
  ubyte                *newdirentry;
  ubyte                 dirstate[32-11];
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(mountpt && mountpt->i_private);

  /* Get the mountpoint private data from the inode structure */

  fs = mountpt->i_private;

  /* Check if the mount is still healthy */

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

  /* Find the directory entry for the oldrelpath */

  ret = fat_finddirentry(fs, &dirinfo, oldrelpath);
  if (ret != OK)
    {
      /* Some error occurred -- probably -ENOENT */

      goto errout_with_semaphore;
    }

  /* Save the information that will need to recover the
   * directory sector and directory entry offset to the
   * old directory.
   */

  olddirentry = dirinfo.fd_entry;

  /* One more check:  Make sure that the oldrelpath does
   * not refer to the root directory.  We can't rename the
   * root directory.
   */

  if (!olddirentry)
    {
      ret = -EXDEV;
      goto errout_with_semaphore;
    }

  oldsector   = fs->fs_currentsector;
  memcpy(dirstate, &olddirentry[DIR_ATTRIBUTES], 32-11);

  /* No find the directory where we should create the newpath object */

  ret = fat_finddirentry(fs, &dirinfo, newrelpath);
  if (ret == OK)
    {
      /* It is an error if the object at newrelpath already exists */

      ret = -EEXIST;
      goto errout_with_semaphore;
    }

  /* What we expect is -ENOENT mean that the full directory path was
   * followed but that the object does not exists in the terminal directory.
   */

  if (ret != -ENOENT)
    {
      goto errout_with_semaphore;
    }

  /* Reserve a directory entry */

  ret = fat_allocatedirentry(fs, &dirinfo);
  if (ret != OK)
    {
      goto errout_with_semaphore;
    }

  /* Create the new directory entry */

  newdirentry = dirinfo.fd_entry;

  memcpy(&newdirentry[DIR_ATTRIBUTES], dirstate, 32-11);
  memcpy(&newdirentry[DIR_NAME], dirinfo.fd_name, 8+3);
#ifdef CONFIG_FLAT_LCNAMES
  DIR_PUTNTRES(newdirentry, dirinfo.fd_ntflags);
#else
  DIR_PUTNTRES(newdirentry, 0);
#endif
  fs->fs_dirty = TRUE;

  /* Now flush the new directory entry to disk and read the sector
   * containing the old directory entry.
   */

  ret = fat_fscacheread(fs, oldsector);
  if (ret < 0)
    {
      goto errout_with_semaphore;
    }

  /* Remove the old entry */

  olddirentry[DIR_NAME] = DIR0_EMPTY;
  fs->fs_dirty = TRUE;

  /* Write the old entry to disk and update FSINFO if necessary */

  ret = fat_updatefsinfo(fs);
  if (ret < 0)
    {
      goto errout_with_semaphore;
    }

  fat_semgive(fs);
  return OK;

 errout_with_semaphore:
  fat_semgive(fs);
  return ret;
}

/****************************************************************************
 * Name: fat_stat
 *
 * Description: Return information about a file or directory
 *
 ****************************************************************************/

static int fat_stat(struct inode *mountpt, const char *relpath, struct stat *buf)
{
  struct fat_mountpt_s *fs;
  struct fat_dirinfo_s  dirinfo;
  uint16                date;
  uint16                date2;
  uint16                time;
  ubyte                 attribute;
  int                   ret;

  /* Sanity checks */

  DEBUGASSERT(mountpt && mountpt->i_private);

  /* Get the mountpoint private data from the inode structure */

  fs = mountpt->i_private;

  /* Check if the mount is still healthy */

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

  /* Find the directory entry corresponding to relpath. */

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

  /* If nothing was found, then we fail with EEXIST */

  if (ret < 0)
    {
      goto errout_with_semaphore;
    }

  if (! dirinfo.fd_entry)
    {
      ret = -ENOENT;
      goto errout_with_semaphore;
    }

  /* Get the FAT attribute and map it so some meaningful mode_t values */

  attribute = DIR_GETATTRIBUTES(dirinfo.fd_entry);
  if ((attribute & FATATTR_VOLUMEID) != 0)
    {
      ret = -ENOENT;
      goto errout_with_semaphore;
    }

  /* Set the access permissions.  The file/directory is always readable
   * by everyone but may be writeable by no-one.
   */

  memset(buf, 0, sizeof(struct stat));
  buf->st_mode = S_IROTH|S_IRGRP|S_IRUSR;
  if ((attribute & FATATTR_READONLY) == 0)
    {
      buf->st_mode |= S_IWOTH|S_IWGRP|S_IWUSR;
    }

  /* We will report only types file or directory */

  if ((attribute & FATATTR_DIRECTORY) != 0)
    {
      buf->st_mode |= S_IFDIR;
    }
  else
    {
      buf->st_mode |= S_IFREG;
    }

  /* File/directory size, access block size */

  buf->st_size      = DIR_GETFILESIZE(dirinfo.fd_entry);
  buf->st_blksize   = fs->fs_fatsecperclus * fs->fs_hwsectorsize;
  buf->st_blocks    = (buf->st_size + buf->st_blksize - 1) / buf->st_blksize;

  /* Times */

  date              = DIR_GETWRTDATE(dirinfo.fd_entry);
  time              = DIR_GETWRTTIME(dirinfo.fd_entry);
  buf->st_mtime     = fat_fattime2systime(time, date);

  date2             = DIR_GETLASTACCDATE(dirinfo.fd_entry);
  if (date == date2)
    {
      buf->st_atime = buf->st_mtime;
    }
  else
    {
      buf->st_atime = fat_fattime2systime(0, date2);
    }

  date              = DIR_GETCRDATE(dirinfo.fd_entry);
  time              = DIR_GETCRTIME(dirinfo.fd_entry);
  buf->st_ctime     = fat_fattime2systime(time, date);

  ret = OK;

 errout_with_semaphore:
  fat_semgive(fs);
  return ret;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/