text: /****************************************************************************
   * fs/fat/fs_fat32dirent.c
   *
   *   Copyright (C) 2011, 2013 Gregory Nutt. All rights reserved.
   *   Author: Gregory Nutt <gnutt@nuttx.org>
   *
   * 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/fs.h>
  #include <nuttx/fs/fat.h>
  
  #include "inode/inode.h"
  #include "fs_fat32.h"
  
  /****************************************************************************
   * Pre-processor 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_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:
   *
   *   The first byte:
   *
   *     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
  #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;
  #endif
  
                /* 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;
              }
          }
  #endif
  
        /* 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;
  #endif
  
                /* 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;
      }
  
   errout:
      dirinfo->fd_lfname[0] = '\0';
      return -EINVAL;
  }
  #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;
      }
    else
      {
         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.
         */
  
        ch = *src++;
        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;
                src    = ext;
              }
          }
  
        /* 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, starting
     * with the first entry.
     */
  
    tmpinfo.dir.fd_startcluster = tmpinfo.dir.fd_currcluster;
    tmpinfo.dir.fd_currsector   = tmpinfo.fd_seq.ds_startsector;
    tmpinfo.dir.fd_index        = 0;
  
    /* 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);
      }
  
    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.
   *
   ****************************************************************************/
  
  static int fat_findsfnentry(struct fat_mountpt_s *fs,
                              struct fat_dirinfo_s *dirinfo)
  {
    uint16_t diroffset;
    uint8_t *direntry;
  #ifdef CONFIG_FAT_LFN
    off_t    startsector;
  #endif
    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
     */
  
    for (;;)
      {
        /* Read the next sector into memory */
  
        ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector);
        if (ret < 0)
          {
            return ret;
          }
  
        /* Get a pointer to the directory entry */
  
        diroffset = DIRSEC_BYTENDX(fs, dirinfo->dir.fd_index);
        direntry  = &fs->fs_buffer[diroffset];
  
        /* Check if we are at the end of the directory */
  
        if (direntry[DIR_NAME] == DIR0_ALLEMPTY)
          {
            return -ENOENT;
          }
  
        /* Check if we have found the directory entry that we are looking for */
  
        if (direntry[DIR_NAME] != DIR0_EMPTY &&
            !(DIR_GETATTRIBUTES(direntry) & FATATTR_VOLUMEID) &&
            !memcmp(&direntry[DIR_NAME], dirinfo->fd_name, DIR_MAXFNAME) )
          {
            /* Yes.. Return success */
  
            dirinfo->fd_seq.ds_sector      = fs->fs_currentsector;
            dirinfo->fd_seq.ds_offset      = diroffset;
  #ifdef CONFIG_FAT_LFN
            dirinfo->fd_seq.ds_cluster     = dirinfo->dir.fd_currcluster;
            dirinfo->fd_seq.ds_startsector = startsector;
  
            /* Position the last long file name directory entry at the same
             * position.
             */
  
            dirinfo->fd_seq.ds_lfnsector   = dirinfo->fd_seq.ds_sector;
            dirinfo->fd_seq.ds_lfnoffset   = dirinfo->fd_seq.ds_offset;
            dirinfo->fd_seq.ds_lfncluster  = dirinfo->fd_seq.ds_cluster;
  #endif
           return OK;
          }
  
        /* No... get the next directory index and try again */
  
        if (fat_nextdirentry(fs, &dirinfo->dir) != OK)
          {
            return -ENOENT;
          }
      }
  }
  
  /****************************************************************************
   * Name: fat_cmplfnchunk
   *
   * 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 perform
   *   the comparison of a single chunk.
   *
   ****************************************************************************/
  
  #ifdef CONFIG_FAT_LFN
  static bool fat_cmplfnchunk(uint8_t *chunk, const uint8_t *substr, int nchunk)
  {
    wchar_t wch;
    uint8_t ch;
    int     i;
  
    /* Check bytes 1-nchunk */
  
    for (i = 0; i < nchunk; i++)
      {
        /* Get the next character from the name string (which might be the NUL
         * terminating character).
         */
  
        if (*substr == '\0')
          {
            ch = '\0';
          }
        else
          {
            ch = *substr++;
          }
  
        /* Get the next unicode character from the chunk.  We only handle
         * ASCII. For ASCII, the upper byte should be zero and the lower
         * should match the ASCII code.
         */
  
        wch = (wchar_t)fat_getuint16((uint8_t*)chunk);
        if ((wch & 0xff) != (wchar_t)ch)
          {
            return false;
          }
  
        /* The characters match.  If we just matched the NUL terminating
         * character, then the strings match and we are finished.
         */
  
        if (ch == '\0')
          {
            return true;
          }
  
        /* Try the next character from the directory entry. */
  
        chunk += sizeof(wchar_t);
      }
  
    /* All of the characters in the chunk match.. Return success */
  
    return true;
  }
  #endif
  
  /****************************************************************************
   * Name: fat_cmplfname
   *
   * Desciption: Given an LFN directory entry, compare a substring of the name
   *   to a portion in the directory entry.
   *
   ****************************************************************************/
  
  #ifdef CONFIG_FAT_LFN
  static bool fat_cmplfname(const uint8_t *direntry, const uint8_t *substr)
  {
    uint8_t *chunk;
    int len;
    bool match;
  
    /* How much of string do we have to compare? (including the NUL
     * terminator).
     */
  
    len = strlen((char*)substr) + 1;
  
    /* Check bytes 1-5 */
  
    chunk = LDIR_PTRWCHAR1_5(direntry);
    match = fat_cmplfnchunk(chunk, substr, 5);
    if (match && len > 5)
      {
        /* Check bytes 6-11 */
  
        chunk = LDIR_PTRWCHAR6_11(direntry);
        match = fat_cmplfnchunk(chunk, &substr[5], 6);
        if (match && len > 11)
          {
            /* Check bytes 12-13 */
  
            chunk = LDIR_PTRWCHAR12_13(direntry);
            match = fat_cmplfnchunk(chunk, &substr[11], 2);
          }
      }
  
    return match;
  }
  #endif
  
  /****************************************************************************
   * Name: fat_findlfnentry
   *
   * Desciption: Find a 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.
   *
   ****************************************************************************/
  
  #ifdef CONFIG_FAT_LFN
  static inline int fat_findlfnentry(struct fat_mountpt_s *fs,
                                     struct fat_dirinfo_s *dirinfo)
  {
    uint16_t diroffset;
    uint8_t *direntry;
    uint8_t  lastseq;
    uint8_t  seqno;
    uint8_t  nfullentries;
    uint8_t  nentries;
    uint8_t  remainder;
    uint8_t  checksum = 0;
    off_t    startsector;
    int      offset;
    int      namelen;
    int      ret;
  
    /* 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).
     */
  
    namelen = strlen((char*)dirinfo->fd_lfname);
    DEBUGASSERT(namelen <= LDIR_MAXFNAME+1);
  
    /* How many LFN directory entries are we expecting? */
  
    nfullentries = namelen / LDIR_MAXLFNCHARS;
    remainder    = namelen - nfullentries * LDIR_MAXLFNCHARS;
    nentries     = nfullentries;
    if (remainder > 0)
      {
        nentries++;
      }
    DEBUGASSERT(nentries > 0 && nentries <= LDIR_MAXLFNS);
  
    /* This is the first sequency number we are looking for, the sequence
     * number of the last LFN entry (remember that they appear in reverse
     * order.. from last to first).
     */
  
    lastseq = LDIR0_LAST | nentries;
    seqno   = lastseq;
  
    /* Save the starting sector of the directory.  This is needed later to
     * re-scan the directory, looking duplicate short alias names.
     */
  
    startsector   = dirinfo->dir.fd_currsector;
  
    /* Search, beginning with the current sector, for a directory entry this
     * the match shore name
     */
  
    for (;;)
      {
        /* Read the next sector into memory */
  
        ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector);
        if (ret < 0)
          {
            return ret;
          }
  
        /* Get a pointer to the directory entry */
  
        diroffset = DIRSEC_BYTENDX(fs, dirinfo->dir.fd_index);
        direntry  = &fs->fs_buffer[diroffset];
  
        /* Check if we are at the end of the directory */
  
        if (direntry[DIR_NAME] == DIR0_ALLEMPTY)
          {
            return -ENOENT;
          }
  
        /* Is this an LFN entry?  Does it have the sequence number we are
         * looking for?
         */
  
        if (LDIR_GETATTRIBUTES(direntry) != LDDIR_LFNATTR ||
            LDIR_GETSEQ(direntry) != seqno)
          {
            /* No, restart the search at the next entry */
  
            seqno = lastseq;
            goto next_entry;
          }
  
        /* Yes.. If this is not the "last" LFN entry, then the checksum must
         * also be the same.
         */
  
        if (seqno == lastseq)
          {
            /* Just save the checksum for subsequent checks */
  
            checksum = LDIR_GETCHECKSUM(direntry);
          }
  
        /* Not the first entry in the sequence.  Does the checksum match the
         * previous sequences?
         */
  
        else if (checksum != LDIR_GETCHECKSUM(direntry))
          {
            /* No, restart the search at the next entry */
  
            seqno = lastseq;
            goto next_entry;
          }
  
        /* Check if the name substring in this LFN matches the corresponding
         * substring of the name we are looking for.
         */
  
        offset = ((seqno & LDIR0_SEQ_MASK) - 1) * LDIR_MAXLFNCHARS;
        if (fat_cmplfname(direntry, &dirinfo->fd_lfname[offset]))
          {
            /* Yes.. it matches.  Check the sequence number.  Is this the
             * "last" LFN entry (i.e., the one that appears first)?
             */
  
            if (seqno == lastseq)
              {
                /* Yes.. Save information about this LFN entry position */
  
                dirinfo->fd_seq.ds_lfnsector   = fs->fs_currentsector;
                dirinfo->fd_seq.ds_lfnoffset   = diroffset;
                dirinfo->fd_seq.ds_lfncluster  = dirinfo->dir.fd_currcluster;
                dirinfo->fd_seq.ds_startsector = startsector;
                seqno &= LDIR0_SEQ_MASK;
              }
  
            /* Is this the first sequence number (i.e., the LFN entry that
             * will appear last)?
             */
  
            if (seqno == 1)
              {
                /* We have found all of the LFN entries.  The next directory
                 * entry should be the one containing the short file name
                 * alias and all of the meat about the file or directory.
                 */
  
                if (fat_nextdirentry(fs, &dirinfo->dir) != OK)
                  {
                    return -ENOENT;
                  }
  
                /* Make sure that the directory entry is in the sector cache */
  
                ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector);
                if (ret < 0)
                  {
                    return ret;
                  }
  
                /* Get a pointer to the directory entry */
  
                diroffset = DIRSEC_BYTENDX(fs, dirinfo->dir.fd_index);
                direntry  = &fs->fs_buffer[diroffset];
  
                /* Verify the checksum */
  
                if (fat_lfnchecksum(&direntry[DIR_NAME]) == checksum)
                  {
                    /* Success! Save the position of the directory entry and
                     * return success.
                     */
  
                    dirinfo->fd_seq.ds_sector  = fs->fs_currentsector;
                    dirinfo->fd_seq.ds_offset  = diroffset;
                    dirinfo->fd_seq.ds_cluster = dirinfo->dir.fd_currcluster;
                    return OK;
                  }
  
                /* Bad news.. reset and continue with this entry (which is
                 * probably not an LFN entry unless the file systen is
                 * seriously corrupted.
                 */
  
                seqno = lastseq;
                continue;
              }
  
            /* No.. there are more LFN entries to go.  Decrement the sequence
             * number and check the next directory entry.
             */
  
            seqno--;
          }
        else
          {
            /* No.. the names do not match.  Restart the search at the next
             * entry.
             */
  
            seqno = lastseq;
          }
  
        /* Continue at the next directory entry */
  
  next_entry:
        if (fat_nextdirentry(fs, &dirinfo->dir) != OK)
          {
            return -ENOENT;
          }
      }
  }
  #endif
  
  /****************************************************************************
   * Name: fat_allocatesfnentry
   *
   * Desciption: Find a free directory entry for a short file name entry.
   *
   ****************************************************************************/
  
  static inline int fat_allocatesfnentry(struct fat_mountpt_s *fs,
                                         struct fat_dirinfo_s *dirinfo)
  {
    uint16_t diroffset;
    uint8_t *direntry;
  #ifdef CONFIG_FAT_LFN
    off_t    startsector;
  #endif
    uint8_t  ch;
    int      ret;
  
    /* Save the sector number of the first sector of the directory.  We don't
     * really need this for short file name entries; this is just done for
     * consistency with the long file name logic.
     */
  
  #ifdef CONFIG_FAT_LFN
    startsector = dirinfo->dir.fd_currsector;
  #endif
  
    /* Then search for a free short file name directory entry */
  
    for (;;)
      {
        /* Read the directory sector into fs_buffer */
  
        ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector);
        if (ret < 0)
          {
            /* Make sure that the return value is NOT -ENOSPC */
  
            return -EIO;
          }
  
        /* Get a pointer to the entry at fd_index */
  
        diroffset = (dirinfo->dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
        direntry  = &fs->fs_buffer[diroffset];
  
        /* Check if this directory entry is empty */
  
        ch = direntry[DIR_NAME];
        if (ch == DIR0_ALLEMPTY || ch == DIR0_EMPTY)
          {
            /* It is empty -- we have found a directory entry */
  
            dirinfo->fd_seq.ds_sector       = fs->fs_currentsector;
            dirinfo->fd_seq.ds_offset       = diroffset;
  #ifdef CONFIG_FAT_LFN
            dirinfo->fd_seq.ds_cluster      = dirinfo->dir.fd_currcluster;
            dirinfo->fd_seq.ds_startsector  = startsector;
  
            /* Set the "last" long file name offset to the same entry */
  
            dirinfo->fd_seq.ds_lfnsector    = dirinfo->fd_seq.ds_sector;
            dirinfo->fd_seq.ds_lfnoffset    = dirinfo->fd_seq.ds_offset;
            dirinfo->fd_seq.ds_lfncluster   = dirinfo->fd_seq.ds_cluster;
  #endif
            return OK;
          }
  
        /* It is not empty try the next one */
  
        ret = fat_nextdirentry(fs, &dirinfo->dir);
        if (ret < 0)
          {
            /* This will return -ENOSPC if we have examined all of the
             * directory entries without finding a free entry.
             */
  
            return ret;
          }
      }
  }
  
  /****************************************************************************
   * Name: fat_allocatelfnentry
   *
   * Desciption: Find a sequence of free directory entries for a several long
   *   and one short file name entry.
   *
   * On entry, dirinfo.dir refers to the first interesting entry the directory.
   *
   ****************************************************************************/
  
  #ifdef CONFIG_FAT_LFN
  static inline int fat_allocatelfnentry(struct fat_mountpt_s *fs,
                                         struct fat_dirinfo_s *dirinfo)
  {
    uint16_t diroffset;
    uint8_t *direntry;
    off_t    startsector;
    uint8_t  nentries;
    uint8_t  remainder;
    uint8_t  needed;
    uint8_t  ch;
    int      namelen;
    int      ret;
  
    /* 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).
     */
  
    namelen = strlen((char *)dirinfo->fd_lfname);
    DEBUGASSERT(namelen <= LDIR_MAXFNAME+1);
  
    /* How many LFN directory entries are we expecting? */
  
    nentries   = namelen / LDIR_MAXLFNCHARS;
    remainder  = namelen - nentries * LDIR_MAXLFNCHARS;
    if (remainder > 0)
      {
        nentries++;
      }
    DEBUGASSERT(nentries > 0 && nentries <= LDIR_MAXLFNS);
  
    /* Plus another for short file name entry that follows the sequence of LFN
     * entries.
     */
  
    nentries++;
  
    /* Save the sector number of the first sector of the directory.  We will
     * need this later for re-scanning the directory to verify that a FAT file
     * name is unique.
     */
  
    startsector = dirinfo->dir.fd_currsector;
  
    /* Now, search the directory looking for a sequence for free entries that
     * long.
     */
  
    needed = nentries;
    for (;;)
      {
        /* Read the directory sector into fs_buffer */
  
        ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector);
        if (ret < 0)
          {
            /* Make sure that the return value is NOT -ENOSPC */
  
            return -EIO;
          }
  
        /* Get a pointer to the entry at fd_index */
  
        diroffset = (dirinfo->dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
        direntry  = &fs->fs_buffer[diroffset];
  
        /* Check if this directory entry is empty */
  
        ch = LDIR_GETSEQ(direntry);
        if (ch == DIR0_ALLEMPTY || ch == DIR0_EMPTY)
          {
            /* It is empty -- we have found a directory entry.  Is this the
             * "last" LFN entry (i.e., the one that occurs first)?
             */
  
            if (needed == nentries)
              {
                /* Yes.. remember the position of this entry */
  
                dirinfo->fd_seq.ds_lfnsector    = fs->fs_currentsector;
                dirinfo->fd_seq.ds_lfnoffset    = diroffset;
                dirinfo->fd_seq.ds_lfncluster   = dirinfo->dir.fd_currcluster;
                dirinfo->fd_seq.ds_startsector  = startsector;
              }
  
            /* Is this last entry we need (i.e., the entry for the short
             * file name entry)?
             */
  
            if (needed <= 1)
              {
                /* Yes.. remember the position of this entry and return
                 * success.
                 */
  
                dirinfo->fd_seq.ds_sector  = fs->fs_currentsector;
                dirinfo->fd_seq.ds_offset  = diroffset;
                dirinfo->fd_seq.ds_cluster = dirinfo->dir.fd_currcluster;
                return OK;
              }
  
            /* Otherwise, just decrement the number of directory entries
             * needed and continue looking.
             */
  
            needed--;
          }
  
        /* The directory entry is not available */
  
        else
          {
            /* Reset the search and continue looking */
  
            needed = nentries;
          }
  
        /* Try the next directory entry */
  
        ret = fat_nextdirentry(fs, &dirinfo->dir);
        if (ret < 0)
          {
            /* This will return -ENOSPC if we have examined all of the
             * directory entries without finding a free entry.
             */
  
            return ret;
          }
      }
  }
  #endif
  
  /****************************************************************************
   * Name: fat_getsfname
   *
   * Desciption:  Get the 8.3 filename from a directory entry.  On entry, the
   *  short file name entry is already in the cache.
   *
   ****************************************************************************/
  
  static inline int fat_getsfname(uint8_t *direntry, char *buffer,
                                  unsigned int buflen)
  {
  #ifdef CONFIG_FAT_LCNAMES
      uint8_t ntflags;
  #endif
      int  ch;
      int  ndx;
  
      /* Check if we will be doing upper to lower case conversions */
  
  #ifdef CONFIG_FAT_LCNAMES
      ntflags = DIR_GETNTRES(direntry);
  #endif
  
      /* Reserve a byte for the NUL terminator */
  
      buflen--;
  
      /* Get the 8-byte filename */
  
      for (ndx = 0; ndx < 8 && buflen > 0; ndx++)
        {
          /* Get the next filename character from the directory entry */
  
          ch = direntry[ndx];
  
          /* Any space (or ndx==8) terminates the filename */
  
          if (ch == ' ')
            {
              break;
            }
  
          /* In this version, we never write 0xe5 in the directory filenames
           * (because we do not handle any character sets where 0xe5 is valid
           * in a filaname), but we could encounted this in a filesystem
           * written by some other system
           */
  
          if (ndx == 0 && ch == DIR0_E5)
            {
              ch = 0xe5;
            }
  
          /* Check if we should perform upper to lower case conversion
           * of the (whole) filename.
           */
  
  #ifdef CONFIG_FAT_LCNAMES
          if (ntflags & FATNTRES_LCNAME && isupper(ch))
            {
              ch = tolower(ch);
            }
  #endif
          /* Copy the next character into the filename */
  
          *buffer++ = ch;
          buflen--;
        }
  
      /* Check if there is an extension */
  
      if (direntry[8] != ' ' && buflen > 0)
        {
          /* Yes, output the dot before the extension */
  
          *buffer++ = '.';
          buflen--;
  
          /* Then output the (up to) 3 character extension */
  
          for (ndx = 8; ndx < 11 && buflen > 0; ndx++)
            {
              /* Get the next extensions character from the directory entry */
  
              ch = direntry[DIR_NAME + ndx];
  
              /* Any space (or ndx==11) terminates the extension */
  
              if (ch == ' ')
                {
                  break;
                }
  
              /* Check if we should perform upper to lower case conversion
               * of the (whole) filename.
               */
  
  #ifdef CONFIG_FAT_LCNAMES
              if (ntflags & FATNTRES_LCEXT && isupper(ch))
                {
                  ch = tolower(ch);
                }
  #endif
          /* Copy the next character into the filename */
  
              *buffer++ = ch;
              buflen--;
            }
        }
  
      /* Put a null terminator at the end of the filename.  We don't have to
       * check if there is room because we reserved a byte for the NUL
       * terminator at the beginning of this function.
       */
  
      *buffer = '\0';
      return OK;
  }
  
  /****************************************************************************
   * Name: fat_getlfnchunk
   *
   * 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 get the
   *   file name characters from one chunk.
   *
   ****************************************************************************/
  
  #ifdef CONFIG_FAT_LFN
  static void fat_getlfnchunk(uint8_t *chunk, uint8_t *dest, int nchunk)
  {
    wchar_t wch;
    int i;
  
    /* Copy bytes 1-nchunk */
  
    for (i = 0; i < nchunk; i++)
      {
        /* Get the next unicode character from the chunk.  We only handle ASCII.
         * For ASCII, the upper byte should be zero and the lower should match
         * the ASCII code.
         */
  
        wch = (wchar_t)fat_getuint16(chunk);
        *dest++ = (uint8_t)(wch & 0xff);
        chunk += sizeof(wchar_t);
      }
  }
  #endif
  
  /****************************************************************************
   * Name: fat_getlfname
   *
   * Desciption:  Get the long filename from 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 inline int fat_getlfname(struct fat_mountpt_s *fs, struct fs_dirent_s *dir)
  {
    uint8_t  lfname[LDIR_MAXLFNCHARS];
    uint16_t diroffset;
    uint8_t *direntry;
    uint8_t  seqno;
    uint8_t  rawseq;
    uint8_t  offset;
    uint8_t  checksum;
    int      nsrc;
    int      ret;
    int      i;
  
    /* Get a reference to the current directory entry */
  
    diroffset = (dir->u.fat.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
    direntry  = &fs->fs_buffer[diroffset];
  
    /* Get the starting sequence number */
  
    seqno = LDIR_GETSEQ(direntry);
    DEBUGASSERT((seqno & LDIR0_LAST) != 0);
  
    /* Sanity check */
  
    rawseq = (seqno & LDIR0_SEQ_MASK);
    if (rawseq < 1 || rawseq > LDIR_MAXLFNS)
      {
        return -EINVAL;
      }
  
    /* Save the checksum value */
  
    checksum = LDIR_GETCHECKSUM(direntry);
  
    /* Loop until the whole file name has been transferred */
  
    for (;;)
      {
        /* Get the string offset associated with the "last" entry. */
  
        offset = (rawseq - 1) * LDIR_MAXLFNCHARS;
  
        /* Will any of this file name fit into the destination buffer? */
  
        if (offset < NAME_MAX)
          {
            /* Yes.. extract and convert the unicode name */
  
            fat_getlfnchunk(LDIR_PTRWCHAR1_5(direntry), lfname, 5);
            fat_getlfnchunk(LDIR_PTRWCHAR6_11(direntry), &lfname[5], 6);
            fat_getlfnchunk(LDIR_PTRWCHAR12_13(direntry), &lfname[11], 2);
  
            /* Ignore trailing spaces on the "last" directory entry.  The
             * number of characters avaiable is LDIR_MAXLFNCHARS or that
             * minus the number of trailing spaces on the "last" directory
             * entry.
             */
  
            nsrc = LDIR_MAXLFNCHARS;
            if ((seqno & LDIR0_LAST) != 0)
              {
                /* Reduce the number of characters by the number of trailing
                 * spaces.
                 */
  
                for (; nsrc > 0 && lfname[nsrc-1] == ' '; nsrc--);
  
                /* Further reduce the length so that it fits in the destination
                 * buffer.
                 */
  
                if (offset + nsrc > NAME_MAX)
                  {
                    nsrc = NAME_MAX - offset;
                  }
  
                /* Add a null terminator to the destination string (the actual
                 * length of the destination buffer is NAME_MAX+1, so the NUL
                 * terminator will fit).
                 */
  
                dir->fd_dir.d_name[offset+nsrc] = '\0';
              }
  
            /* Then transfer the characters */
  
            for (i = 0; i < nsrc && offset+i < NAME_MAX; i++)
              {
                dir->fd_dir.d_name[offset+i] = lfname[i];
              }
          }
  
        /* Read next directory entry */
  
        if (fat_nextdirentry(fs, &dir->u.fat) != OK)
          {
            return -ENOENT;
          }
  
        /* Make sure that the directory sector into the sector cache */
  
        ret = fat_fscacheread(fs, dir->u.fat.fd_currsector);
        if (ret < 0)
          {
            return ret;
          }
  
        /* Get a reference to the current directory entry */
  
        diroffset = (dir->u.fat.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
        direntry  = &fs->fs_buffer[diroffset];
  
        /* Get the next expected sequence number. */
  
        seqno = --rawseq;
        if (seqno < 1)
          {
            /* We just completed processing the "first" long file name entry
             * and we just read the short file name entry.  Verify that the
             * checksum of the short file name matches the checksum that we
             * found in the long file name entries.
             */
  
            if (fat_lfnchecksum(direntry) == checksum)
              {
                /* Yes.. return success! */
  
                return OK;
              }
  
            /* No, the checksum is bad. */
  
            return -EINVAL;
          }
  
        /* Verify the next long file name entry. Is this an LFN entry?  Does it
         * have the sequence number we are looking for?  Does the checksum
         * match the previous entries?
         */
  
        if (LDIR_GETATTRIBUTES(direntry) != LDDIR_LFNATTR ||
            LDIR_GETSEQ(direntry)        != seqno ||
            LDIR_GETCHECKSUM(direntry)   != checksum)
          {
            return -EINVAL;
          }
      }
  }
  #endif
  
  /****************************************************************************
   * Name: fat_putsfname
   *
   * Desciption: Write the short directory entry name.
   *
   * Assumption:  The directory sector is in the cache.
   *
   ****************************************************************************/
  
  static int fat_putsfname(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
  {
    uint8_t *direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset];
  
    /* Write the short directory entry */
  
    memcpy(&direntry[DIR_NAME], dirinfo->fd_name, DIR_MAXFNAME);
  #ifdef CONFIG_FAT_LCNAMES
    DIR_PUTNTRES(direntry, dirinfo->fd_ntflags);
  #else
    DIR_PUTNTRES(direntry, 0);
  #endif
    fs->fs_dirty = true;
    return OK;
  }
  
  /****************************************************************************
   * Name: fat_initlfname
   *
   * 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
   *   0xffff characters into one chunk.
   *
   ****************************************************************************/
  
  #ifdef CONFIG_FAT_LFN
  static void fat_initlfname(uint8_t *chunk, int nchunk)
  {
    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;
    off_t    startsector;
    int      namelen;
    int      ret;
  
    /* 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++;
        remainder++;
      }
    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;
  
    /* ds_lfnoffset is the offset in the sector. However fd_index is used as
     * index for the entire cluster. We need to add that offset
     */
  
    startsector                 = fat_cluster2sector(fs, dirinfo->dir.fd_currcluster);
    dirinfo->dir.fd_index      += (dirinfo->dir.fd_currsector - startsector) * DIRSEC_NDIRS(fs);
  
    /* 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;
  
    /* Make sure that the sector containing the "last" long file name entry
     * is in the sector cache (it probably is not).
     */
  
    ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector);
    if (ret < 0)
      {
        return ret;
      }
  
    /* Now loop, writing each long file name entry */
  
    for (;;)
      {
        /* Get the string offset associated with the directory entry. */
  
        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)
              {
                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;
  
    /* 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 the
     * the first, entry in the root directory.
     */
  
    dirinfo->dir.fd_index = 0;
  
    /* 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;
      }
  
    /* 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)
          {
            /* A return value of -ENOENT would mean that the path segement
             * was not found.  Let's distinguish two cases:  (1) the final
             * file was not found in the directory (-ENOENT), or (2) one
             * of the directory path segments does not exist (-ENOTDIR)
             */
  
            if (ret == -ENOENT && terminator != '\0')
              {
                ret = -ENOTDIR;
              }
  
            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 == '\0')
          {
            /* 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 (or allocate) all needed directory entries to contain the file name
   *
   ****************************************************************************/
  
  int fat_allocatedirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
  {
    int32_t  cluster;
    int32_t  prevcluster;
    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 */
  
        if (cluster)
          {
           /* Cluster chain can be extended */
  
            dirinfo->dir.fd_currcluster = cluster;
            dirinfo->dir.fd_currsector  = fat_cluster2sector(fs, cluster);
          }
        else
          {
            /* Fixed size FAT12/16 root directory is at fixed offset/size */
  
            dirinfo->dir.fd_currsector = fs->fs_rootbase;
          }
  
        /* Start at the first entry in the root directory. */
  
        dirinfo->dir.fd_index = 0;
  
        /* 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.. Allocate for the sequence of long file name directory
             * entries plus a short file name directory entry.
             */
  
            ret = fat_allocatelfnentry(fs, dirinfo);
          }
  
        /* No.. Allocate only a short file name directory entry */
  
        else
  #endif
          {
            ret = fat_allocatesfnentry(fs, dirinfo);
          }
  
        /* 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)
          {
            return ret;
          }
  
        /* 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.
         * Otherwise, we can try to extend the directory cluster chain to
         * make space for the new directory entry.
         */
  
        if (!cluster)
          {
            /* The size is fixed */
  
            return -ENOSPC;
          }
  
        /* Try to extend the cluster chain for this directory */
  
        prevcluster = cluster;
        cluster     = fat_extendchain(fs, dirinfo->dir.fd_currcluster);
  
        if (cluster < 0)
          {
            return cluster;
          }
  
       /* 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;
          }
  
        /* Clear all sectors comprising the new directory cluster */
  
        fs->fs_currentsector = fat_cluster2sector(fs, cluster);
        memset(fs->fs_buffer, 0, fs->fs_hwsectorsize);
  
        sector = fs->fs_currentsector;
        for (i = fs->fs_fatsecperclus; i; i--)
          {
            ret = fat_hwwrite(fs, fs->fs_buffer, sector, 1);
            if (ret < 0)
              {
                return ret;
              }
  
            sector++;
          }
  
        /* Start the search again */
  
        cluster = prevcluster;
      }
  }
  
  /****************************************************************************
   * 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)
  {
  #ifdef CONFIG_FAT_LFN
    struct fs_fatdir_s dir;
    uint16_t diroffset;
    uint8_t *direntry;
    off_t    startsector;
    int      ret;
  
    /* 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;
  
    /* Remember that ds_lfnoffset is the offset in the sector and not the
     * cluster.
     */
  
    startsector        = fat_cluster2sector(fs, dir.fd_currcluster);
    dir.fd_index      += (dir.fd_currsector - startsector) * DIRSEC_NDIRS(fs);
  
    /* 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);
        if (ret < 0)
          {
            return ret;
          }
  
        /* Get a pointer to the directory entry */
  
        diroffset = (dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
        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)
          {
            /* 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);
        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;
  #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);
      }
    else
  #endif
      {
        /* 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.
     */
  #endif
  
    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);
        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.
     */
  #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. */
  
        return ret;
      }
  
    /* 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)
      {
        /* Most likely, some element of the path does not exist. */
  
        return -ENOENT;
      }
  
    /* Check if this is a FAT12/16 root directory */
  
    if (dirinfo.fd_root)
      {
        /* 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. */
  
    dircluster =
        ((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)
      {
        return ret;
      }
  
    return OK;
  }
  