aboutsummaryrefslogtreecommitdiff
path: root/nuttx/fs/fat/fs_fat32dirent.c
diff options
context:
space:
mode:
Diffstat (limited to 'nuttx/fs/fat/fs_fat32dirent.c')
-rw-r--r--nuttx/fs/fat/fs_fat32dirent.c2947
1 files changed, 2947 insertions, 0 deletions
diff --git a/nuttx/fs/fat/fs_fat32dirent.c b/nuttx/fs/fat/fs_fat32dirent.c
new file mode 100644
index 000000000..742fa1eeb
--- /dev/null
+++ b/nuttx/fs/fat/fs_fat32dirent.c
@@ -0,0 +1,2947 @@
+/****************************************************************************
+ * 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/fs.h>
+#include <nuttx/fs/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_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;
+ 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;
+
+ /* 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)
+ {
+ return ret;
+ }
+
+ /* If the terminator character in the path was the end of the string
+ * then we have successfully found the directory entry that describes
+ * the path.
+ */
+
+ if (!terminator)
+ {
+ /* Return success meaning that the description the matching
+ * directory entry is in dirinfo.
+ */
+
+ return OK;
+ }
+
+ /* No.. then we have found one of the intermediate directories on
+ * the way to the final path target. In this case, make sure
+ * the thing that we found is, indeed, a directory.
+ */
+
+ direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset];
+ if (!(DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY))
+ {
+ /* Ooops.. we found something else */
+
+ return -ENOTDIR;
+ }
+
+ /* Get the cluster number of this directory */
+
+ cluster =
+ ((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) |
+ DIR_GETFSTCLUSTLO(direntry);
+
+ /* Then restart scanning at the new directory, skipping over both the
+ * '.' and '..' entries that exist in all directories EXCEPT the root
+ * directory.
+ */
+
+ dirinfo->dir.fd_startcluster = cluster;
+ dirinfo->dir.fd_currcluster = cluster;
+ dirinfo->dir.fd_currsector = fat_cluster2sector(fs, cluster);
+ dirinfo->dir.fd_index = 2;
+ }
+}
+
+/****************************************************************************
+ * Name: fat_allocatedirentry
+ *
+ * Desciption: Find a free directory entry
+ *
+ ****************************************************************************/
+
+int fat_allocatedirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
+{
+ int32_t cluster;
+ off_t sector;
+ int ret;
+ int i;
+
+ /* Re-initialize directory object */
+
+ cluster = dirinfo->dir.fd_startcluster;
+
+ /* Loop until we successfully allocate the sequence of directory entries
+ * or until to fail to extend the directory cluster chain.
+ */
+
+ for (;;)
+ {
+ /* Can this cluster chain be extended */
+
+ 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 */
+
+ 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++;
+ }
+ }
+}
+
+/****************************************************************************
+ * 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;
+ 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;
+
+ /* 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)
+ {
+ /* No such path */
+
+ 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;
+}