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

/****************************************************************************
 * NOTE:  If CONFIG_FAT_LFN is defined, then there may be some legal, patent
 * issues. The following was extracted from the entry "File Allocation Table
 * from Wikipedia, the free encyclopedia:
 *
 * "On December 3, 2003 Microsoft announced it would be offering licenses
 *  for use of its FAT specification and 'associated intellectual property',
 *  at the cost of a US$0.25 royalty per unit sold, with a $250,000 maximum
 *  royalty per license agreement.
 *
 *  o "U.S. Patent 5,745,902 (http://www.google.com/patents?vid=5745902) -
 *     Method and system for accessing a file using file names having
 *     different file name formats. ...
 *  o "U.S. Patent 5,579,517 (http://www.google.com/patents?vid=5579517) -
 *     Common name space for long and short filenames. ...
 *  o "U.S. Patent 5,758,352 (http://www.google.com/patents?vid=5758352) -
 *     Common name space for long and short filenames. ...
 *  o "U.S. Patent 6,286,013 (http://www.google.com/patents?vid=6286013) -
 *     Method and system for providing a common name space for long and
 *     short file names in an operating system. ...
 *
 * "Many technical commentators have concluded that these patents only cover
 *  FAT implementations that include support for long filenames, and that
 *  removable solid state media and consumer devices only using short names
 *  would be unaffected. ..."
 *
 * So you have been forewarned:  Use the long filename at your own risk!
 *
 ****************************************************************************/

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

#include <nuttx/config.h>

#include <sys/types.h>

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.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
 ****************************************************************************/

enum fat_case_e
{
  FATCASE_UNKNOWN = 0,
  FATCASE_UPPER,
  FATCASE_LOWER
};

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

#ifdef CONFIG_FAT_LFN
static uint8_t fat_lfnchecksum(const uint8_t *sfname);
#endif
static inline int fat_parsesfname(const char **path,
                                  struct fat_dirinfo_s *dirinfo,
                                  char *terminator);
#ifdef CONFIG_FAT_LFN
static inline int fat_parselfname(const char **path,
                                  struct fat_dirinfo_s *dirinfo,
                                  char *terminator);
static inline int fat_createalias(struct fat_dirinfo_s *dirinfo);
static inline int fat_findalias(struct fat_mountpt_s *fs,
                                struct fat_dirinfo_s *dirinfo);
static inline int fat_uniquealias(struct fat_mountpt_s *fs,
                                  struct fat_dirinfo_s *dirinfo);
#endif
static int fat_path2dirname(const char **path, struct fat_dirinfo_s *dirinfo,
                            char *terminator);
static int fat_findsfnentry(struct fat_mountpt_s *fs,
                            struct fat_dirinfo_s *dirinfo);
#ifdef CONFIG_FAT_LFN
static bool fat_cmplfnchunk(uint8_t *chunk, const uint8_t *substr, int nchunk);
static bool fat_cmplfname(const uint8_t *direntry, const uint8_t *substr);
static inline int fat_findlfnentry(struct fat_mountpt_s *fs,
                                   struct fat_dirinfo_s *dirinfo);

#endif
static inline int fat_allocatesfnentry(struct fat_mountpt_s *fs,
                                       struct fat_dirinfo_s *dirinfo);
#ifdef CONFIG_FAT_LFN
static inline int fat_allocatelfnentry(struct fat_mountpt_s *fs,
                                       struct fat_dirinfo_s *dirinfo);
#endif
static inline int fat_getsfname(uint8_t *direntry, char *buffer,
                                unsigned int buflen);
#ifdef CONFIG_FAT_LFN
static void fat_getlfnchunk(uint8_t *chunk, uint8_t *dest, int nchunk);
static inline int fat_getlfname(struct fat_mountpt_s *fs, struct fs_dirent_s *dir);
#endif
static int fat_dirverify(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo,
                         uint16_t offset);
static int fat_putsfname(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo);
#ifdef CONFIG_FAT_LFN
static void fat_initlfname(uint8_t *chunk, int nchunk);
static void fat_putlfnchunk(uint8_t *chunk, const uint8_t *src, int nchunk);
static int fat_putlfname(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo);
#endif
static int fat_putsfdirentry(struct fat_mountpt_s *fs,
                             struct fat_dirinfo_s *dirinfo,
                             uint8_t attributes, uint32_t fattime);

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

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

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

/****************************************************************************
 * Name: fat_lfnchecksum
 *
 * Desciption:  Caculate the checksum of .
 *
 ****************************************************************************/

#ifdef CONFIG_FAT_LFN
static uint8_t fat_lfnchecksum(const uint8_t *sfname)
{
  uint8_t sum = 0;
  int i;

  for (i = DIR_MAXFNAME; i; i--)
    {
      sum = ((sum & 1) << 7) + (sum >> 1) + *sfname++;
	}

  return sum;
}
#endif

/****************************************************************************
 * Name: fat_parsesfname
 *
 * Desciption:  Convert a user filename into a properly formatted FAT
 *   (short 8.3) filename as it would appear in a directory entry.  Here are
 *    the rules for the 8+3 short file name in the directory:
 *     0xe5 = The directory is free
 *     0x00 = This directory and all following directories are free
 *     0x05 = Really 0xe5
 *     0x20 = May NOT be ' '
 *
 *   Other characters may be any characters except for the following:
 *
 *     0x00-0x1f = (except for 0x00 and 0x05 in the first byte)
 *     0x22      = '"'
 *     0x2a-0x2c = '*', '+', ','
 *     0x2e-0x2f = '.', '/'
 *     0x3a-0x3f = ':', ';', '<', '=', '>', '?'
 *     0x5b-0x5d = '[', '\\', ;]'
 *     0x7c      = '|'
 *
 *   '.' May only occur once within string and only within the first 9
 *   bytes.  The '.' is not save in the directory, but is implicit in
 *   8+3 format.
 *
 *   Lower case characters are not allowed in directory names (without some
 *   poorly documented operations on the NTRes directory byte).  Lower case
 *   codes may represent different characters in other character sets ("DOS
 *   code pages".  The logic below does not, at present, support any other
 *   character sets.
 *
 * Returned value:
 *   OK - The path refers to a valid 8.3 FAT file name and has been properly
 *        converted and stored in dirinfo.
 *   <0 - Otherwise an negated error is returned meaning that the string is
 *        not a valid 8+3 because:
 *
 *        1. Contains characters not in the printable character set,
 *        2. Contains forbidden characters or multiple '.' characters
 *        3. File name or extension is too long.
 *
 *        If CONFIG_FAT_LFN is defined and CONFIG_FAT_LCNAMES is NOT
 *        defined, then:
 *
 *        4a. File name or extension contains lower case characters.
 *
 *        If CONFIG_FAT_LFN is defined and CONFIG_FAT_LCNAMES is defined,
 *        then:
 *
 *        4b. File name or extension is not all the same case.
 *
 ****************************************************************************/

static inline int fat_parsesfname(const char **path,
                                  struct fat_dirinfo_s *dirinfo,
                                  char *terminator)
{
#ifdef CONFIG_FAT_LCNAMES
  unsigned int ntlcenable = FATNTRES_LCNAME | FATNTRES_LCEXT;
  unsigned int ntlcfound  = 0;
#ifdef CONFIG_FAT_LFN
  enum fat_case_e namecase = FATCASE_UNKNOWN;
  enum fat_case_e extcase  = FATCASE_UNKNOWN;
#endif
  const char *node = *path;
  int endndx;
  uint8_t ch;
  int ndx = 0;
  /* Initialized the name with all spaces */
  memset(dirinfo->fd_name, ' ', DIR_MAXFNAME);
  /* Loop until the name is successfully parsed or an error occurs */
  endndx  = 8;
  for (;;)
    {
      /* Get the next byte from the path */
      ch = *node++;
      /* Check if this the last byte in this node of the name */

      if ((ch == '\0' || ch == '/') && ndx != 0 )
        {
          /* Return the accumulated NT flags and the terminating character */

#ifdef CONFIG_FAT_LCNAMES
          dirinfo->fd_ntflags = ntlcfound & ntlcenable;
#endif
          *terminator = ch;
          *path       = node;
          return OK;
        }

      /* Accept only the printable character set (excluding space).  Note
       * that the first byte of the name could be 0x05 meaning that is it
       * 0xe5, but this is not a printable character in this character in
       * either case.
       */

      else if (!isgraph(ch))
        {
          goto errout;
        }

      /* Check for transition from name to extension.  Only one '.' is
       * permitted and it must be within first 9 characters
       */

      else if (ch == '.' && endndx == 8)
        {
          /* Starting the extension */

          ndx    = 8;
          endndx = 11;
          continue;
        }

      /* Reject printable characters forbidden by FAT */

      else if (ch == '"'  ||  (ch >= '*' && ch <= ',') ||
               ch == '.'  ||   ch == '/'               ||
              (ch >= ':'  &&   ch <= '?')              ||
              (ch >= '['  &&   ch <= ']')              ||
              (ch == '|'))
        {
          goto errout;
        }

      /* Check for upper case characters */

#ifdef CONFIG_FAT_LCNAMES
      else if (isupper(ch))
        {
          /* Some or all of the characters in the name or extension
           * are upper case. Force all of the characters to be interpreted
           * as upper case.
           */

          if (endndx == 8)
            {
              /* Is there mixed case in the name? */

#ifdef CONFIG_FAT_LFN
              if (namecase == FATCASE_LOWER)
                {
                  /* Mixed case in the name -- use the long file name */

                  goto errout;
                }

              /* So far, only upper case in the name*/

              namecase = FATCASE_UPPER;
              /* Clear lower case name bit in mask*/

              ntlcenable &= ~FATNTRES_LCNAME;
            }
          else
            {
              /* Is there mixed case in the extension? */

#ifdef CONFIG_FAT_LFN
              if (extcase == FATCASE_LOWER)
                {
                  /* Mixed case in the extension -- use the long file name */
                  goto errout;
                }
              /* So far, only upper case in the extension*/
              extcase = FATCASE_UPPER;
#endif
              /* Clear lower case extension in mask */

              ntlcenable &= ~FATNTRES_LCEXT;
      /* Check for lower case characters */
      else if (islower(ch))
        {
#if defined(CONFIG_FAT_LFN) && !defined(CONFIG_FAT_LCNAMES)
          /* If lower case characters are present, then a long file
           * name will be constructed.
           */
          goto errout;
#else
          /* Convert the character to upper case */

          ch = toupper(ch);

          /* Some or all of the characters in the name or extension
           * are lower case.  They can be interpreted as lower case if
           * only if all of the characters in the name or extension are
           * lower case.
           */

#ifdef CONFIG_FAT_LCNAMES
          if (endndx == 8)
            {
                /* Is there mixed case in the name? */

#ifdef CONFIG_FAT_LFN
                if (namecase == FATCASE_UPPER)
                  {
                    /* Mixed case in the name -- use the long file name */

                    goto errout;
                  }

                /* So far, only lower case in the name*/

                namecase = FATCASE_LOWER;
#endif

              /* Set lower case name bit */

              ntlcfound |= FATNTRES_LCNAME;
            }
          else
            {
              /* Is there mixed case in the extension? */

#ifdef CONFIG_FAT_LFN
              if (extcase == FATCASE_UPPER)
                  /* Mixed case in the extension -- use the long file name */

                  goto errout;

              /* So far, only lower case in the extension*/

              extcase = FATCASE_LOWER;
              /* Set lower case extension bit */
              ntlcfound |= FATNTRES_LCEXT;
            }
#endif
#endif /* CONFIG_FAT_LFN && !CONFIG_FAT_LCNAMES */
        }
      /* Check if the file name exceeds the size permitted (without
       * long file name support).
       */
      if (ndx >= endndx)
        {
          goto errout;
        }
      /* Save next character in the accumulated name */

      dirinfo->fd_name[ndx++] = ch;
    }

 errout:
  return -EINVAL;
}

/****************************************************************************
 * Name: fat_parselfname
 *
 * Desciption:  Convert a user filename into a properly formatted FAT
 *   long filename as it would appear in a directory entry.  Here are
 *   the rules for the long file name in the directory:
 *
 *   Valid characters are the same as for short file names EXCEPT:
 *
 *     1. '+', ',', ';', '=', '[', and ']' are accepted in the file name
 *     2. '.' (dot) can occur more than once in a filename. Extension is
 *        the substring after the last dot.
 *
 * Returned value:
 *   OK - The path refers to a valid long file name and has been properly
 *        stored in dirinfo.
 *   <0 - Otherwise an negated error is returned meaning that the string is
 *        not a valid long file name:
 *
 *        1. Contains characters not in the printable character set,
 *        2. Contains forbidden characters
 *        3. File name is too long.
 *
 ****************************************************************************/

#ifdef CONFIG_FAT_LFN
static inline int fat_parselfname(const char **path,
                                  struct fat_dirinfo_s *dirinfo,
                                  char *terminator)
{
  const char *node = *path;
  uint8_t ch;
  int ndx = 0;

  /* Loop until the name is successfully parsed or an error occurs */

  for (;;)
    {
      /* Get the next byte from the path */

      ch = *node++;

      /* Check if this the last byte in this node of the name */

      if ((ch == '\0' || ch == '/') && ndx != 0 )
        {
          /* Null terminate the string */

          dirinfo->fd_lfname[ndx] = '\0';

          /* Return the remaining sub-string and the terminating character. */

          *terminator = ch;
          *path       = node;
          return OK;
        }

      /* Accept only the printable character set (including space) */
      else if (!isprint(ch))
        {
          goto errout;
        }

      /* Reject printable characters forbidden by FAT */
      else if (ch == '"' || ch == '*' || ch == '/' || ch == ':'  ||
               ch == '<' || ch == '>' || ch == '?' || ch == '\\' ||
               ch == '|')
        {
          goto errout;
        }
      /* Check if the file name exceeds the size permitted. */
      if (ndx >= LDIR_MAXFNAME)
        {
          goto errout;
        }
      /* Save next character in the accumulated name */

      dirinfo->fd_lfname[ndx++] = ch;
    }
    dirinfo->fd_lfname[0] = '\0';
#endif

/****************************************************************************
 * Name: fat_createalias
 *
 * Desciption:  Given a valid long file name, create a short filename alias.
 *   Here are the rules for creation of the alias:
 *
 *   1. All uppercase
 *   2. All dots except the last deleted
 *   3. First 6 (uppercase) characters used as a base
 *   4. Then ~1.  The number is increased if the file already exists in the
 *      directory. If the number exeeds >10, then character stripped off the
 *       base.
 *   5. The extension is the first 3 uppercase chars of extension.
 *
 * This function is called only from fat_putlfname()
 *
 * Returned value:
 *   OK - The alias was created correctly.
 *   <0 - Otherwise an negated error is returned.
 *
 ****************************************************************************/

#ifdef CONFIG_FAT_LFN
static inline int fat_createalias(struct fat_dirinfo_s *dirinfo)
{
  uint8_t ch;        /* Current character being processed */
  char   *ext;       /* Pointer to the extension substring */
  char   *src;       /* Pointer to the long file name source */
  int     len;       /* Total length of the long file name */
  int     namechars; /* Number of characters available in long name */
  int     extchars;  /* Number of characters available in long name extension */
  int     endndx;    /* Maximum index into the short name array */
  int     ndx;       /* Index to store next character */

  /* First, let's decide what is name and what is extension */

  len = strlen((char*)dirinfo->fd_lfname);
  ext = strrchr((char*)dirinfo->fd_lfname, '.');
  if (ext)
    {
      ptrdiff_t tmp;

      /* ext points to the final '.'.  The difference in bytes from the
       * beginning of the string is then the name length.
       */

      tmp       = ext - (char*)dirinfo->fd_lfname;
      namechars = tmp;

      /* And the rest, exluding the '.' is the extension. */

      extchars  = len - namechars - 1;
      ext++;
    }
  else
    {
      /* No '.' found.  It is all name and no extension. */

      namechars = len;
      extchars  = 0;
    }

  /* Alias are always all upper case */

#ifdef CONFIG_FAT_LCNAMES
  dirinfo->fd_ntflags = 0;
#endif

  /* Initialized the short name with all spaces */

  memset(dirinfo->fd_name, ' ', DIR_MAXFNAME);
 
  /* Handle a special case where there is no name.  Windows seems to use
   * the extension plus random stuff then ~1 to pat to 8 bytes.  Some
   * examples:
   *
   *   a.b          -> a.b          No long name
   *   a.,          -> A26BE~1._    Padded name to make unique, _ replaces ,
   *   .b           -> B1DD2~1      Extension used as name
   *   .bbbbbbb     -> BBBBBB~1     Extension used as name
   *   a.bbbbbbb    -> AAD39~1.BBB  Padded name to make unique.
   *   aaa.bbbbbbb  -> AAA~1.BBBB   Not padded, already unique?
   *   ,.bbbbbbb    -> _82AF~1.BBB  _ replaces ,
   *   +[],.bbbbbbb -> ____~1.BBB   _ replaces +[],
   */

  if (namechars < 1)
    {
       /* Use the extension as the name */

       DEBUGASSERT(ext && extchars > 0);
       src       = ext;
       ext       = NULL;
       namechars = extchars;
       extchars  = 0;
       src       = (char*)dirinfo->fd_lfname;
    }

  /* Then copy the name and extension, handling upper case conversions and
   * excluding forbidden characters.
   */

  ndx    = 0;  /* Position to write the next name character */
  endndx = 6;  /* Maximum index before we write ~! and switch to the extension */

  for (;;)
    {
      /* Get the next byte from the path.  Break out of the loop if we
       * encounter the end of null-terminated the long file name string.
       */

      if (ch == '\0')
        {
          /* This is the end of the source string. Do we need to add ~1.  We
           * will do that if we were parsing the name part when the endo of
           * string was encountered.
           */

          if (endndx == 6)
            {
              /* Write the ~1 at the end of the name */

              dirinfo->fd_name[ndx++] = '~';
              dirinfo->fd_name[ndx]   = '1';
            }

          /* In any event, we are done */

          return OK;
        }

      /* Exclude those few characters included in long file names, but
       * excluded in short file name: '+', ',', ';', '=', '[', ']', and '.'
       */

      if (ch == '+' || ch == ',' || ch == '.' || ch == ';' ||
          ch == '=' || ch == '[' || ch == ']' || ch == '|')
        {
          /* Use the underbar character instead */

          ch = '_';
        }

      /* Handle lower case characters */

      ch = toupper(ch);
      
      /* We now have a valid character to add to the name or extension. */

      dirinfo->fd_name[ndx++] = ch;

      /* Did we just add a character to the name? */

      if (endndx == 6)
        {
          /* Decrement the number of characters available in the name
           * portion of the long name.
           */

          namechars--;

          /* Is it time to add ~1 to the string?  We will do that if
           * either (1) we have already added the maximum number of
           * characters to the short name, or (2) if there are no further
           * characters available in the name portion of the long name.
           */

          if (namechars < 1 || ndx == 6)
            {
              /* Write the ~1 at the end of the name */

              dirinfo->fd_name[ndx++] = '~';
              dirinfo->fd_name[ndx]   = '1';

              /* Then switch to the extension (if there is one) */

              if (!ext || extchars < 1)
                {
                  return OK;
                }

              ndx    = 8;
              endndx = 11;
            }
        }

      /* No.. we just added a character to the extension */

      else
        {
          /* Decrement the number of characters available in the name
           * portion of the long name
           */

          extchars--;

          /* Is the extension complete? */

          if (extchars < 1 || ndx == 11)
            {
              return OK;
            }
        }
    }
}
#endif

/****************************************************************************
 * Name: fat_findalias
 *
 * Desciption:  Make sure that the short alias for the long file name is
 *   unique, ie., that there is no other
 *
 * NOTE: This function does not restore the directory entry that was in the
 * sector cache
 *
 * Returned value:
 *   OK - The alias is unique.
 *   <0 - Otherwise an negated error is returned.
 *
 ****************************************************************************/

#ifdef CONFIG_FAT_LFN
static inline int fat_findalias(struct fat_mountpt_s *fs,
                                struct fat_dirinfo_s *dirinfo)
{
  struct fat_dirinfo_s tmpinfo;

  /* Save the current directory info. */

  memcpy(&tmpinfo, dirinfo, sizeof(struct fat_dirinfo_s));

  /* Then re-initialize to the beginning of the current directory, skipping
   * over the first entry (unused in the root directory and '.' entry in other
   * directories).
   */

  tmpinfo.dir.fd_startcluster = tmpinfo.dir.fd_currcluster;
  tmpinfo.dir.fd_currsector   = tmpinfo.fd_seq.ds_startsector;

  /* Search for the single short file name directory entry in this directory */

  return fat_findsfnentry(fs, &tmpinfo);
}
#endif

/****************************************************************************
 * Name: fat_uniquealias
 *
 * Desciption:  Make sure that the short alias for the long file name is
 *   unique, modifying the alias as necessary to assure uniqueness.
 *
 * NOTE: This function does not restore the directory entry that was in the
 * sector cache
 *   information upon return.
 * Returned value:
 *   OK - The alias is unique.
 *   <0 - Otherwise an negated error is returned.
 *
 ****************************************************************************/

#ifdef CONFIG_FAT_LFN
static inline int fat_uniquealias(struct fat_mountpt_s *fs,
                                  struct fat_dirinfo_s *dirinfo)
{
  int tilde;
  int lsdigit;
  int ret;
  int i;

  /* Find the position of the tilde character in the short name.  The tilde
   * can not occur in positions 0 or 7:
   */

  for (tilde = 1; tilde < 7 && dirinfo->fd_name[tilde] != '~'; tilde++);
  if (tilde >= 7)
    {
      return -EINVAL;
    }

  /* The least significant number follows the digit (and must be '1') */

  lsdigit = tilde + 1;
  DEBUGASSERT(dirinfo->fd_name[lsdigit] == '1');

  /* Search for the single short file name directory entry in this directory */

  while ((ret = fat_findalias(fs, dirinfo)) == OK)
    {
      /* Adjust the numeric value after the '~' to make the file name unique */

      for (i = lsdigit; i > 0; i--)
        {
          /* If we have backed up to the tilde position, then we have to move
           * the tilde back one position.
           */

          if (i == tilde)
            {
              /* Is there space to back up the tilde? */

              if (tilde <= 1)
                {
                  /* No.. then we cannot add the name to the directory.
                   * What is the likelihood of that happening?
                   */

                  return -ENOSPC;
                }

              /* Back up the tilde and break out of the inner loop */

              tilde--;
              dirinfo->fd_name[tilde]   = '~';
              dirinfo->fd_name[tilde+1] = '1';
              break;
            }

          /* We are not yet at the tilde,.  Check if this digit has already
           * reached its maximum value.
           */

          else if (dirinfo->fd_name[i] < '9')
            {
              /* No, it has not.. just increment the LS digit and break out of
               * the inner loop.
               */

              dirinfo->fd_name[i]++;
              break;
            }

          /* Yes.. Reset the digit to '0' and loop to adjust the digit before
           * this one.
           */

          else
            {
              dirinfo->fd_name[i] = '0';
            }
        }
    }

  /* The while loop terminated because of an error; fat_findalias()
   * returned something other than OK.  The only acceptable error is
   * -ENOENT, meaning that the short file name directory does not
   * exist in this directory.
   */

  if (ret == -ENOENT)
    {
      ret = OK;
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: fat_path2dirname
 *
 * Desciption:  Convert a user filename into a properly formatted FAT
 *   (short 8.3) filename as it would appear in a directory entry.
 *
 ****************************************************************************/

static int fat_path2dirname(const char **path, struct fat_dirinfo_s *dirinfo,
                            char *terminator)
{
#ifdef CONFIG_FAT_LFN
  int ret;

  /* Assume no long file name */

  dirinfo->fd_lfname[0] = '\0';

  /* Then parse the (assumed) 8+3 short file name */

  ret = fat_parsesfname(path, dirinfo, terminator);
  if (ret < 0)
    {
      /* No, the name is not a valid short 8+3 file name. Try parsing
       * the long file name.
       */

      ret = fat_parselfname(path, dirinfo, terminator);
patacongo's avatar
patacongo committed
    }

  return ret;
#else
  /* Only short, 8+3 filenames supported */

  return fat_parsesfname(path, dirinfo, terminator);
#endif
}

/****************************************************************************
 * Name: fat_findsfnentry
 *
 * Desciption: Find a short file name directory entry.  Returns OK if the
 *  directory exists; -ENOENT if it does not.
patacongo's avatar
patacongo committed
 *
 ****************************************************************************/

static int fat_findsfnentry(struct fat_mountpt_s *fs,
                            struct fat_dirinfo_s *dirinfo)
patacongo's avatar
patacongo committed
{
  uint16_t diroffset;
  uint8_t *direntry;
#ifdef CONFIG_FAT_LFN
  off_t    startsector;
#endif
patacongo's avatar
patacongo committed
  int      ret;

  /* Save the starting sector of the directory.  This is not really needed
   * for short name entries, but this keeps things consistent with long
   * file name entries..
   */

#ifdef CONFIG_FAT_LFN
  startsector = dirinfo->dir.fd_currsector;
#endif

  /* Search, beginning with the current sector, for a directory entry with
   * the matching short name
patacongo's avatar
patacongo committed
   */

  for (;;)
    {
      /* Read the next sector into memory */

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