Skip to content
Snippets Groups Projects
fs_fat32dirent.c 84.4 KiB
Newer Older
  int i;

  /* Initialize unicode characters 1-nchunk */

  for (i = 0; i < nchunk; i++)
    {
      /* The write the 16-bit 0xffff character into the directory entry. */
      fat_putuint16((uint8_t *)chunk, (uint16_t)0xffff);
      chunk += sizeof(wchar_t);
    }
}
#endif

/****************************************************************************
 * Name: fat_putlfnchunk
 *
 * Desciption:  There are 13 characters per LFN entry, broken up into three
 *   chunks for characts 1-5, 6-11, and 12-13.  This function will put the
 *   file name characters into one chunk.
 *
 ****************************************************************************/

#ifdef CONFIG_FAT_LFN
static void fat_putlfnchunk(uint8_t *chunk, const uint8_t *src, int nchunk)
{
  uint16_t wch;
  int i;

  /* Write bytes 1-nchunk */

  for (i = 0; i < nchunk; i++)
    {
      /* Get the next ascii character from the name substring and convert it
       * to unicode.  The upper byte should be zero and the lower should be
       * the ASCII code.  The write the unicode character into the directory
       * entry.
       */

      wch = (uint16_t)*src++;
      fat_putuint16(chunk, wch);
      chunk += sizeof(wchar_t);
    }
}
#endif

/****************************************************************************
 * Name: fat_putlfname
 *
 * Desciption:  Write the long filename into a sequence of directory entries.
 *   On entry, the "last" long file name entry is in the cache.  Returns with
 *   the short file name entry in the cache.
 *
 ****************************************************************************/

#ifdef CONFIG_FAT_LFN
static int fat_putlfname(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
{
  uint16_t diroffset;
  uint8_t *direntry;
  uint8_t  nfullentries;
  uint8_t  nentries;
  uint8_t  remainder;
  uint8_t  offset;
  uint8_t  seqno;
  uint8_t  checksum;
  int      namelen;
  int      ret;

  /* Some special handling in case we are writing the first entry of the
   * root directory in a freshly formatted volume.
   */

  (void)fat_dirverify(fs, dirinfo, dirinfo->fd_seq.ds_lfnoffset);

  /* Get the length of the long file name (size of the fd_lfname array is
   * LDIR_MAXFNAME+1 we do not have to check the length of the string).
   * NOTE that remainder is conditionally incremented to include the NUL
   * terminating character that may also need be written to the directory
   * entry. NUL terminating is not required if length is multiple of
   * LDIR_MAXLFNCHARS (13).
  namelen = strlen((char*)dirinfo->fd_lfname);
  DEBUGASSERT(namelen <= LDIR_MAXFNAME+1);

  /* How many LFN directory entries do we need to write? */

  nfullentries = namelen / LDIR_MAXLFNCHARS;
  remainder    = namelen - nfullentries * LDIR_MAXLFNCHARS;
  nentries     = nfullentries;
  if (remainder > 0)
    {
      nentries++;
    }
  DEBUGASSERT(nentries > 0 && nentries <= LDIR_MAXLFNS);

  /* Create the short file name alias */

  ret = fat_createalias(dirinfo);
  if (ret < 0)
    {
      return ret;
    }

  /* Set up the initial positional data */

  dirinfo->dir.fd_currcluster = dirinfo->fd_seq.ds_lfncluster;
  dirinfo->dir.fd_currsector  = dirinfo->fd_seq.ds_lfnsector;
  dirinfo->dir.fd_index       = dirinfo->fd_seq.ds_lfnoffset / DIR_SIZE;

  /* Make sure that the alias is unique in this directory*/

  ret = fat_uniquealias(fs, dirinfo);
  if (ret < 0)
    {
      return ret;
    }

  /* Get the short file name checksum */

  checksum = fat_lfnchecksum(dirinfo->fd_name);

  /* Setup the starting sequence number */

  seqno = LDIR0_LAST | nentries;

  /* Now loop, writing each long file name entry.  We know that the sector
   * is in the sector cache because fat_dirverify() assures us that that is
   * so.
   */

  for (;;)
    {
      /* Get the string offset associated with the directory entry. */

patacongo's avatar
patacongo committed
      offset = (nentries - 1) * LDIR_MAXLFNCHARS;

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

      diroffset = (dirinfo->dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
      direntry  = &fs->fs_buffer[diroffset];

      /* Is this the "last" LFN directory entry? */

      if ((seqno & LDIR0_LAST) != 0 && remainder != 0)
        {
          int nbytes;

          /* Initialize the "last" directory entry name to all 0xffff */
          fat_initlfname(LDIR_PTRWCHAR1_5(direntry), 5);
          fat_initlfname(LDIR_PTRWCHAR6_11(direntry), 6);
          fat_initlfname(LDIR_PTRWCHAR12_13(direntry), 2);

          /* Store the tail portion of the long file name in directory entry */

          nbytes = MIN(5, remainder);
          fat_putlfnchunk(LDIR_PTRWCHAR1_5(direntry),
                          &dirinfo->fd_lfname[offset], nbytes);
          remainder -= nbytes;

          if (remainder > 0)
            {
patacongo's avatar
patacongo committed
              nbytes = MIN(6, remainder);
              fat_putlfnchunk(LDIR_PTRWCHAR6_11(direntry),
                              &dirinfo->fd_lfname[offset+5], nbytes);
              remainder -= nbytes;
            }

          if (remainder > 0)
            {
              nbytes = MIN(2, remainder);
              fat_putlfnchunk(LDIR_PTRWCHAR12_13(direntry),
                              &dirinfo->fd_lfname[offset+11], nbytes);
              remainder -= nbytes;
            }

          /* The remainder should now be zero */
          
          DEBUGASSERT(remainder == 0);
        }
      else
        {
          /* Store a portion long file name in this directory entry  */

          fat_putlfnchunk(LDIR_PTRWCHAR1_5(direntry),
                          &dirinfo->fd_lfname[offset], 5);
          fat_putlfnchunk(LDIR_PTRWCHAR6_11(direntry),
                          &dirinfo->fd_lfname[offset+5], 6);
          fat_putlfnchunk(LDIR_PTRWCHAR12_13(direntry),
                          &dirinfo->fd_lfname[offset+11], 2);
        }

      /* Write the remaining directory entries */

      LDIR_PUTSEQ(direntry, seqno);
      LDIR_PUTATTRIBUTES(direntry, LDDIR_LFNATTR);
      LDIR_PUTNTRES(direntry, 0);
      LDIR_PUTCHECKSUM(direntry, checksum);
      fs->fs_dirty = true;

      /* Read next directory entry */

      if (fat_nextdirentry(fs, &dirinfo->dir) != OK)
        {
          return -ENOENT;
        }

      /* Make sure that the sector containing the directory entry is in the
       * sector cache
       */

      ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector);
      if (ret < 0)
        {
          return ret;
        }

      /* Decrement the number of entries and get the next sequence number. */

      if (--nentries <= 0)
        {
          /* We have written all of the long file name entries to the media
           * and we have the short file name entry in the cache.  We can
           * just return success.
           */

          return OK;
        }

      /* The sequence number is just the number of entries left to be
       * written.
       */

      seqno = nentries;
    }
}
#endif

/****************************************************************************
 * Name: fat_putsfdirentry
 *
 * Desciption: Write a short file name directory entry
 *
 * Assumption:  The directory sector is in the cache.  The caller will write
 *   sector information.
 *
 ****************************************************************************/

static int fat_putsfdirentry(struct fat_mountpt_s *fs,
                             struct fat_dirinfo_s *dirinfo,
                             uint8_t attributes, uint32_t fattime)
{
  uint8_t *direntry;

  /* Some special handling in case we are writing the first entry of the
   * root directory in a freshly formatted volume.
   */

  (void)fat_dirverify(fs, dirinfo, dirinfo->fd_seq.ds_offset);

  /* Initialize the 32-byte directory entry */

  direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset];
  memset(direntry, 0, DIR_SIZE);

  /* Directory name info */

  (void)fat_putsfname(fs, dirinfo);

  /* Set the attribute attribute, write time, creation time */

  DIR_PUTATTRIBUTES(direntry, attributes);

  /* Set the time information */

  DIR_PUTWRTTIME(direntry, fattime & 0xffff);
  DIR_PUTCRTIME(direntry, fattime & 0xffff);
  DIR_PUTWRTDATE(direntry, fattime >> 16);
  DIR_PUTCRDATE(direntry, fattime >> 16);

  fs->fs_dirty = true;
  return OK;
}

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

/****************************************************************************
 * Name: fat_finddirentry
 *
 * Desciption: Given a path to something that may or may not be in the file
 *   system, return the description of the directory entry of the requested
 *   item.
 *
 * NOTE: As a side effect, this function returns with the sector containing
 *   the short file name directory entry in the cache.
 *
 ****************************************************************************/

int fat_finddirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo,
                     const char *path)
{
  off_t    cluster;
  uint8_t *direntry;
  char     terminator;
  int      ret;

  /* Initialize to traverse the chain.  Set it to the cluster of the root
   * directory
   */

  cluster = fs->fs_rootbase;
  if (fs->fs_type == FSTYPE_FAT32)
    {
      /* For FAT32, the root directory is variable sized and is a cluster
       * chain like any other directory.  fs_rootbase holds the first
       * cluster of the root directory.
       */

      dirinfo->dir.fd_startcluster = cluster;
      dirinfo->dir.fd_currcluster  = cluster;
      dirinfo->dir.fd_currsector   = fat_cluster2sector(fs, cluster);
    }
  else
    {
      /* For FAT12/16, the first sector of the root directory is a sector
       * relative to the first sector of the fat volume.
       */

      dirinfo->dir.fd_startcluster = 0;
      dirinfo->dir.fd_currcluster  = 0;
      dirinfo->dir.fd_currsector   = cluster;
    }

  /* fd_index is the index into the current directory table. It is set to one
   * to skip over the first, unused entry in the root directory.

  /* If no path was provided, then the root directory must be exactly what
   * the caller is looking for.
   */

  if (*path == '\0')
    {
      dirinfo->fd_root = true;
      return OK;
    }

patacongo's avatar
patacongo committed
  /* This is not the root directory */

  dirinfo->fd_root = false;

  /* Now loop until the directory entry corresponding to the path is found */

  for (;;)
    {
      /* Convert the next the path segment name into the kind of name that
       * we would see in the directory entry.
       */

      ret = fat_path2dirname(&path, dirinfo, &terminator);
      if (ret < 0)
        {
          /* ERROR:  The filename contains invalid characters or is
           * too long.
           */

          return ret;
        }

      /* Is this a path segment a long or a short file.  Was a long file
       * name parsed?
       */

#ifdef CONFIG_FAT_LFN
      if (dirinfo->fd_lfname[0] != '\0')
        {
          /* Yes.. Search for the sequence of long file name directory
           * entries. NOTE: As a side effect, this function returns with
           * the sector containing the short file name directory entry
           * in the cache.
           */
 
          ret = fat_findlfnentry(fs, dirinfo);
        }
      else
#endif
        {
          /* No.. Search for the single short file name directory entry */

          ret = fat_findsfnentry(fs, dirinfo);
        }

      /* Did we find the directory entries? */

      if (ret < 0)
        {
          return ret;
        }

      /* If the terminator character in the path was the end of the string
       * then we have successfully found the directory entry that describes
       * the path.
       */

      if (!terminator)
        {
          /* Return success meaning that the description the matching
           * directory entry is in dirinfo.
           */

          return OK;
        }

      /* No.. then we have found one of the intermediate directories on
       * the way to the final path target.  In this case, make sure
       * the thing that we found is, indeed, a directory.
       */

      direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset];
      if (!(DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY))
        {
          /* Ooops.. we found something else */

          return -ENOTDIR;
        }

      /* Get the cluster number of this directory */

      cluster =
          ((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) |
          DIR_GETFSTCLUSTLO(direntry);

      /* Then restart scanning at the new directory, skipping over both the
       * '.' and '..' entries that exist in all directories EXCEPT the root
       * directory.
       */
      dirinfo->dir.fd_startcluster = cluster;
      dirinfo->dir.fd_currcluster  = cluster;
      dirinfo->dir.fd_currsector   = fat_cluster2sector(fs, cluster);
      dirinfo->dir.fd_index        = 2;
    }
}

/****************************************************************************
 * Name: fat_allocatedirentry
 *
 * Desciption: Find a free directory entry
 *
 ****************************************************************************/

int fat_allocatedirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
{
  int32_t  cluster;
  off_t    sector;
  int      ret;
  int      i;

  /* Re-initialize directory object */

  cluster = dirinfo->dir.fd_startcluster;

  /* Loop until we successfully allocate the sequence of directory entries
   * or until to fail to extend the directory cluster chain.
   */

  for (;;)
    {
      /* Can this cluster chain be extended */
patacongo's avatar
patacongo committed
      if (cluster)
patacongo's avatar
patacongo committed
         /* Cluster chain can be extended */
patacongo's avatar
patacongo committed
          dirinfo->dir.fd_currcluster = cluster;
          dirinfo->dir.fd_currsector  = fat_cluster2sector(fs, cluster);
        }
      else
        {
patacongo's avatar
patacongo committed
          /* Fixed size FAT12/16 root directory is at fixed offset/size */
patacongo's avatar
patacongo committed
          dirinfo->dir.fd_currsector = fs->fs_rootbase;
        }
      /* Skip over the first, unused entry in the root directory. */
patacongo's avatar
patacongo committed
      /* Is this a path segment a long or a short file.  Was a long file
       * name parsed?
       */
patacongo's avatar
patacongo committed
#ifdef CONFIG_FAT_LFN
      if (dirinfo->fd_lfname[0] != '\0')
patacongo's avatar
patacongo committed
          /* Yes.. Allocate for the sequence of long file name directory
           * entries plus a short file name directory entry.
           */
 
          ret = fat_allocatelfnentry(fs, dirinfo);
        }
patacongo's avatar
patacongo committed
      /* No.. Allocate only a short file name directory entry */

      else
#endif
        {
          ret = fat_allocatesfnentry(fs, dirinfo);
patacongo's avatar
patacongo committed
      /* Did we successfully allocate the directory entries?  If the error
       * value is -ENOSPC, then we can try to extend the directory cluster
       * (we can't handle other return values)
       */

      if (ret == OK || ret != -ENOSPC)
patacongo's avatar
patacongo committed
      /* If we get here, then we have reached the end of the directory table
       * in this sector without finding a free directory entry.
       *
       * It this is a fixed size directory entry, then this is an error.
patacongo's avatar
patacongo committed
       * Otherwise, we can try to extend the directory cluster chain to
       * make space for the new directory entry.
       */
patacongo's avatar
patacongo committed
      if (!cluster)
        {
          /* The size is fixed */
patacongo's avatar
patacongo committed
          return -ENOSPC;
        }
patacongo's avatar
patacongo committed
      /* Try to extend the cluster chain for this directory */
patacongo's avatar
patacongo committed
      cluster = fat_extendchain(fs, dirinfo->dir.fd_currcluster);
      if (cluster < 0)
        {
          return cluster;
        }
patacongo's avatar
patacongo committed
     /* Flush out any cached data in fs_buffer.. we are going to use
      * it to initialize the new directory cluster.
      */

      ret = fat_fscacheflush(fs);
      if (ret < 0)
        {
          return ret;
        }
patacongo's avatar
patacongo committed
      /* Clear all sectors comprising the new directory cluster */
patacongo's avatar
patacongo committed
      fs->fs_currentsector = fat_cluster2sector(fs, cluster);
      memset(fs->fs_buffer, 0, fs->fs_hwsectorsize);
patacongo's avatar
patacongo committed
      for (i = fs->fs_fatsecperclus; i; i--)
patacongo's avatar
patacongo committed
          ret = fat_hwwrite(fs, fs->fs_buffer, sector, 1);
          if (ret < 0)
patacongo's avatar
patacongo committed
            {
              return ret;
            }
          sector++;
        }
    }
}

/****************************************************************************
 * Name: fat_freedirentry
 *
 * Desciption:  Free the directory entry.
 *
 * NOTE: As a side effect, this function returns with the sector containing
 *   the deleted short file name directory entry in the cache.
 *
 ****************************************************************************/

int fat_freedirentry(struct fat_mountpt_s *fs, struct fat_dirseq_s *seq)
patacongo's avatar
patacongo committed
#ifdef CONFIG_FAT_LFN
  struct fs_fatdir_s dir;
patacongo's avatar
patacongo committed
  uint16_t diroffset;
patacongo's avatar
patacongo committed
  /* Set it to the cluster containing the "last" LFN entry (that appears
   * first on the media).
   */

  dir.fd_currcluster = seq->ds_lfncluster;
  dir.fd_currsector  = seq->ds_lfnsector;
  dir.fd_index       = seq->ds_lfnoffset / DIR_SIZE;
patacongo's avatar
patacongo committed

  /* Free all of the directory entries used for the sequence of long file name
   * and for the single short file name entry.
   */

  for (;;)
    {
      /* Read the directory sector into the sector cache */

      ret = fat_fscacheread(fs, dir.fd_currsector);
patacongo's avatar
patacongo committed
      if (ret < 0)
        {
          return ret;
        }

      /* Get a pointer to the directory entry */

      diroffset = (dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
patacongo's avatar
patacongo committed
      direntry  = &fs->fs_buffer[diroffset];

      /* Then mark the entry as deleted */

      direntry[DIR_NAME] = DIR0_EMPTY;
      fs->fs_dirty       = true;

      /* Did we just free the single short file name entry? */

      if (dir.fd_currsector == seq->ds_sector &&
          diroffset == seq->ds_offset)
patacongo's avatar
patacongo committed
        {
          /* Yes.. then we are finished. flush anything remaining in the
           * cache and return, probably successfully.
           */

          return fat_fscacheflush(fs);
        }

      /* There are more entries to go.. Try the next directory entry */
      ret = fat_nextdirentry(fs, &dir);
patacongo's avatar
patacongo committed
      if (ret < 0)
        {
          return ret;
        }
    }

#else
  uint8_t *direntry;
  int      ret;

  /* Free the single short file name entry.
   *
   * Make sure that the sector containing the directory entry is in the
   * cache.
   */

  ret = fat_fscacheread(fs, seq->ds_sector);
  if (ret == OK)
    {
      /* Then mark the entry as deleted */

      direntry           = &fs->fs_buffer[seq->ds_offset];
      direntry[DIR_NAME] = DIR0_EMPTY;
      fs->fs_dirty       = true;
    }

  return ret;
patacongo's avatar
patacongo committed
#endif
}

/****************************************************************************
 * Name: fat_dirname2path
 *
 * Desciption:  Convert a filename in a raw directory entry into a user
 *    filename.  This is essentially the inverse operation of that performed
 *    by fat_path2dirname.  See that function for more details.
 *
 ****************************************************************************/

int fat_dirname2path(struct fat_mountpt_s *fs, struct fs_dirent_s *dir)
  uint16_t diroffset;
  uint8_t *direntry;
#ifdef CONFIG_FAT_LFN
  uint8_t attribute;
#endif

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

  diroffset = (dir->u.fat.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
  direntry = &fs->fs_buffer[diroffset];
  /* Does this entry refer to the last entry of a long file name? */
#ifdef CONFIG_FAT_LFN
  attribute = DIR_GETATTRIBUTES(direntry);
  if (((*direntry & LDIR0_LAST) != 0 && attribute == LDDIR_LFNATTR))
    {
      /* Yes.. Get the name from a sequence of long file name directory
       * entries.
       */
 
      return fat_getlfname(fs, dir);
    {
      /* No.. Get the name from a short file name directory entries */
      return fat_getsfname(direntry, dir->fd_dir.d_name, NAME_MAX+1);
    }
}

/****************************************************************************
 * Name: fat_dirnamewrite
 *
 * Desciption: Write the (possibly long) directory entry name.  This function
 *   is called only from fat_rename to write the new file name.
 * Assumption:  The directory sector containing the short file name entry
 *   is in the cache.  *NOT* the sector containing the last long file name
 *   entry!
 *
 ****************************************************************************/

int fat_dirnamewrite(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
{
#ifdef CONFIG_FAT_LFN
  int ret;

  /* Is this a long file name? */

  if (dirinfo->fd_lfname[0] != '\0')
    {
      /* Write the sequence of long file name directory entries (this function
       * also creates the short file name alias).
       */
      ret = fat_putlfname(fs, dirinfo);
      if (ret != OK)
        {
          return ret;
        }
    }

  /* On return, fat_lfsfname() will leave the short file name entry in the
   * cache.  So we can just fall throught to write that directory entry, perhaps
   * using the short file name alias for the long file name.
   */

  return fat_putsfname(fs, dirinfo);
}

/****************************************************************************
 * Name: fat_dirwrite
 *
 * Desciption: Write a directory entry, possibly with a long file name.
 *   Called from:
 *
 *   fat_mkdir() to write the new FAT directory entry.
 *   fat_dircreate() to create any new directory entry.
 *
 * Assumption:  The directory sector is in the cache.  The caller will write
 *   sector information.
 *
 ****************************************************************************/

int fat_dirwrite(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo,
                 uint8_t attributes, uint32_t fattime)
{
#ifdef CONFIG_FAT_LFN
  int ret;

  /* Does this directory entry have a long file name? */
  if (dirinfo->fd_lfname[0] != '\0')
      /* Write the sequence of long file name directory entries (this function
       * also creates the short file name alias).
       */
      ret = fat_putlfname(fs, dirinfo);
  /* On return, fat_lfsfname() will leave the short file name entry in the
   * cache.  So we can just fall throught to write that directory entry, perhaps
   * using the short file name alias for the long file name.
   */
#endif
  /* Put the short file name entry data */
  return fat_putsfdirentry(fs, dirinfo, attributes, fattime);
}

/****************************************************************************
 * Name: fat_dircreate
 *
 * Desciption: Create a directory entry for a new file
 *
 ****************************************************************************/

int fat_dircreate(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
{
  uint32_t fattime;
  int ret;

  /* Allocate a directory entry.  If long file name support is enabled, then
   * this might, in fact, allocate a sequence of directory entries.
   */

  ret = fat_allocatedirentry(fs, dirinfo);
  if (ret != OK)
    {
      /* Failed to allocate the required directory entry or entries. */
  /* Write the directory entry (or entries) with the current time and the
   * ARCHIVE attribute.
   */

  fattime = fat_systime2fattime();
  return fat_dirwrite(fs, dirinfo, FATATTR_ARCHIVE, fattime);
}

/****************************************************************************
 * Name: fat_remove
 *
 * Desciption: Remove a directory or file from the file system.  This
 *   implements both rmdir() and unlink().
 *
 ****************************************************************************/

int fat_remove(struct fat_mountpt_s *fs, const char *relpath, bool directory)
{
  struct fat_dirinfo_s dirinfo;
  uint32_t             dircluster;
  uint8_t             *direntry;
  int                  ret;

  /* Find the directory entry referring to the entry to be deleted */

  ret = fat_finddirentry(fs, &dirinfo, relpath);
  if (ret != OK)
    {
      /* No such path */

      return -ENOENT;
    }

  /* Check if this is a FAT12/16 root directory */

    {
      /* The root directory cannot be removed */

      return -EPERM;
    }

  /* The object has to have write access to be deleted */

  direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset];
  if ((DIR_GETATTRIBUTES(direntry) & FATATTR_READONLY) != 0)
    {
      /* It is a read-only entry */

      return -EACCES;
    }

  /* Get the directory sector and cluster containing the entry to be deleted. */
      ((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) |
      DIR_GETFSTCLUSTLO(direntry);

  /* Is this entry a directory? */

  if (DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY)
    {
      /* It is a sub-directory. Check if we are be asked to remove
       * a directory or a file.
       */

      if (!directory)
        {
          /* We are asked to delete a file */

          return -EISDIR;
        }

      /* We are asked to delete a directory. Check if this sub-directory is
       * empty (i.e., that there are no valid entries other than the initial
       * '.' and '..' entries).
       */

      dirinfo.dir.fd_currcluster = dircluster;
      dirinfo.dir.fd_currsector  = fat_cluster2sector(fs, dircluster);
      dirinfo.dir.fd_index       = 2;

      /* Loop until either (1) an entry is found in the directory (error),
       * (2) the directory is found to be empty, or (3) some error occurs.
       */

      for (;;)
        {
          unsigned int subdirindex;
          uint8_t     *subdirentry;

          /* Make sure that the sector containing the of the subdirectory
           * sector is in the cache
           */

          ret = fat_fscacheread(fs, dirinfo.dir.fd_currsector);
          if (ret < 0)
            {
              return ret;
            }

          /* Get a reference to the next entry in the directory */

          subdirindex = (dirinfo.dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
          subdirentry = &fs->fs_buffer[subdirindex];

          /* Is this the last entry in the direcory? */

          if (subdirentry[DIR_NAME] == DIR0_ALLEMPTY)
            {
              /* Yes then the directory is empty.  Break out of the
               * loop and delete the directory.
               */

              break;
            }

          /* Check if the next entry refers to a file or directory */

          if (subdirentry[DIR_NAME] != DIR0_EMPTY &&
              !(DIR_GETATTRIBUTES(subdirentry) & FATATTR_VOLUMEID))
            {
              /* The directory is not empty */

              return -ENOTEMPTY;
            }

          /* Get the next directory entry */

          ret = fat_nextdirentry(fs, &dirinfo.dir);
          if (ret < 0)
            {
              return ret;
            }
        }
    }
  else
    {
      /* It is a file. Check if we are be asked to remove a directory
       * or a file.
       */

      if (directory)
        {
          /* We are asked to remove a directory */

          return -ENOTDIR;
        }
    }

  /* Mark the directory entry 'deleted'.  If long file name support is
   * enabled, then multiple directory entries may be freed.
   */
  ret = fat_freedirentry(fs, &dirinfo.fd_seq);
  if (ret < 0)
    {
      return ret;
    }

  /* And remove the cluster chain making up the subdirectory */

  ret = fat_removechain(fs, dircluster);
  if (ret < 0)
    {
      return ret;
    }

  /* Update the FSINFO sector (FAT32) */

  ret = fat_updatefsinfo(fs);
  if (ret < 0)
    {