incubator-nuttx/fs/fat/fs_fat32dirent.c

3273 lines
90 KiB
C

/****************************************************************************
* fs/fat/fs_fat32dirent.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* 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/param.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"
/****************************************************************************
* 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(FAR const uint8_t *sfname);
#endif
static inline int fat_parsesfname(FAR const char **path,
FAR struct fat_dirinfo_s *dirinfo,
FAR char *terminator);
#ifdef CONFIG_FAT_LFN
static inline int fat_parselfname(FAR const char **path,
FAR struct fat_dirinfo_s *dirinfo,
FAR char *terminator);
static inline int fat_createalias(FAR struct fat_dirinfo_s *dirinfo);
static inline int fat_findalias(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo);
static inline int fat_uniquealias(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo);
#endif
static int fat_path2dirname(FAR const char **path,
FAR struct fat_dirinfo_s *dirinfo,
FAR char *terminator);
static int fat_findsfnentry(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo);
#ifdef CONFIG_FAT_LFN
static bool fat_cmplfnchunk(FAR uint8_t *chunk, FAR const lfnchar *substr,
int nchunk);
static bool fat_cmplfname(FAR const uint8_t *direntry,
FAR const lfnchar *substr);
static inline int fat_findlfnentry(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo);
#endif
static inline int fat_allocatesfnentry(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo);
#ifdef CONFIG_FAT_LFN
static inline int fat_allocatelfnentry(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo);
#endif
static inline int fat_getsfname(FAR uint8_t *direntry, FAR char *buffer,
unsigned int buflen);
#ifdef CONFIG_FAT_LFN
static void fat_getlfnchunk(FAR uint8_t *chunk, FAR lfnchar *dest,
int nchunk);
static inline int fat_getlfname(FAR struct fat_mountpt_s *fs,
FAR struct fs_dirent_s *dir,
FAR struct dirent *entry);
#endif
static int fat_putsfname(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo);
#ifdef CONFIG_FAT_LFN
static void fat_initlfname(FAR uint8_t *chunk, int nchunk);
static void fat_putlfnchunk(FAR uint8_t *chunk, FAR const lfnchar *src,
int nchunk);
static int fat_putlfname(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo);
#endif
static int fat_putsfdirentry(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo,
uint8_t attributes, uint32_t fattime);
#if defined(CONFIG_FAT_LFN) && defined(CONFIG_FAT_LFN_UTF8)
static int fat_utf8toucs(FAR const char **str, FAR lfnchar *ucs);
static int fat_ucstoutf8(FAR uint8_t *dest, uint8_t offset, lfnchar ucs);
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: fat_utf8toucs
*
* Description:
* Convert the next characters from UTF8 to UCS2.
*
****************************************************************************/
#if defined(CONFIG_FAT_LFN) && defined(CONFIG_FAT_LFN_UTF8)
static int fat_utf8toucs(FAR const char **str, FAR lfnchar *ucs)
{
uint8_t chr;
lfnchar tucs;
int ret = ERROR;
*ucs = '\0';
chr = *((*str)++);
if ((chr & 0x80) == 0x00)
{
tucs = (lfnchar)chr;
ret = OK;
}
else if ((chr & 0xe0) == 0xc0)
{
tucs = ((lfnchar)(chr & ~0xe0)) << 6;
chr = *((*str)++);
if ((chr & 0xc0) == 0x80)
{
tucs |= (lfnchar)(chr & ~0xc0);
ret = OK;
}
}
else if ((chr & 0xf0) == 0xe0)
{
tucs = ((lfnchar)(chr & ~0xf0)) << 12;
chr = *((*str)++);
if ((chr & 0xc0) == 0x80)
{
tucs |= (lfnchar)(chr & ~0xc0) << 6;
chr = *((*str)++);
if ((chr & 0xc0) == 0x80)
{
tucs |= (lfnchar)(chr & ~0xc0);
ret = OK;
}
}
}
if (ret == OK)
{
*ucs = tucs;
}
return ret;
}
#endif
/****************************************************************************
* Name: fat_utf8toucs
*
* Description:
* Convert the next character from UCS2 to UTF8, reverse.
*
****************************************************************************/
#if defined(CONFIG_FAT_LFN) && defined(CONFIG_FAT_LFN_UTF8)
static int fat_ucstoutf8(FAR uint8_t *dest, uint8_t offset, lfnchar ucs)
{
if (ucs < 128 && offset >= 1)
{
dest[--offset] = (uint8_t)(ucs & 0xff);
}
else if (ucs < 2048 && offset >= 2)
{
dest[--offset] = (uint8_t)((ucs >> 0) & ~0xc0) | 0x80;
dest[--offset] = (uint8_t)((ucs >> 6) & ~0xe0) | 0xc0;
}
else if (offset >= 3)
{
dest[--offset] = (uint8_t)((ucs >> 0) & ~0xc0) | 0x80;
dest[--offset] = (uint8_t)((ucs >> 6) & ~0xc0) | 0x80;
dest[--offset] = (uint8_t)((ucs >> 12) & ~0xf0) | 0xe0;
}
return offset;
}
#endif
/****************************************************************************
* Name: fat_lfnchecksum
*
* Description:
* Verify that the checksum of the short file name matches the checksum
* that we found in the long file name entries.
*
****************************************************************************/
#ifdef CONFIG_FAT_LFN
static uint8_t fat_lfnchecksum(FAR 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
*
* Description: 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(FAR const char **path,
FAR struct fat_dirinfo_s *dirinfo,
FAR 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
FAR 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
*
* Description: 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(FAR const char **path,
FAR struct fat_dirinfo_s *dirinfo,
FAR char *terminator)
{
FAR const char *node = *path;
lfnchar ch;
int ndx = 0;
/* Loop until the name is successfully parsed or an error occurs */
for (; ; )
{
/* Get the next character from the path */
# ifdef CONFIG_FAT_LFN_UTF8
if (fat_utf8toucs(&node, &ch) != OK)
{
goto errout;
}
# else
ch = *node++;
# endif
/* 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 = (char)ch;
*path = node;
return OK;
}
/* Accept only the printable character set (including space) */
# ifdef CONFIG_FAT_LFN_UTF8
/* We assume all ucs2 characters printable REVISIT? */
else if (ch < ' ')
# else
else if (!isprint(ch))
# endif
{
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
*
* Description: 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 exceeds >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(FAR struct fat_dirinfo_s *dirinfo)
{
uint8_t ch; /* Current character being processed */
FAR lfnchar *ext; /* Pointer to the extension substring */
FAR lfnchar *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 */
for (len = 0, ext = NULL; dirinfo->fd_lfname[len] != '\0'; len++)
{
if (dirinfo->fd_lfname[len] == '.')
{
ext = &dirinfo->fd_lfname[len];
}
}
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 - (FAR lfnchar *)dirinfo->fd_lfname;
namechars = tmp;
/* And the rest, excluding the '.' is the extension. */
extchars = len - namechars - 1;
ext++;
}
else
{
/* No '.' found. It is all name and no extension. */
namechars = len;
extchars = 0;
}
#ifdef CONFIG_FAT_LCNAMES
/* Alias are always all upper case */
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 pad 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 = (FAR lfnchar *)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.
*/
# ifdef CONFIG_FAT_LFN_UTF8
/* Make sure ch is within printable characters */
if (*src > 0x7f)
{
ch = (uint8_t)(*src++ & 0x1f) + 'A';
if (ch >= '[')
{
ch -= ('[' - '0');
}
}
else
{
ch = *src++;
}
# else
ch = *src++;
# endif
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 end 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 == '|' || 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;
}
}
#if defined(CONFIG_FAT_LFN_ALIAS_TRAILCHARS) && CONFIG_FAT_LFN_ALIAS_TRAILCHARS > 0
/* Take first 6-N characters from beginning of filename and last N
* characters from end of the filename. Useful for filenames like
* "datafile123.txt".
*/
if (ndx == 6 - CONFIG_FAT_LFN_ALIAS_TRAILCHARS
&& namechars > CONFIG_FAT_LFN_ALIAS_TRAILCHARS)
{
src += namechars - CONFIG_FAT_LFN_ALIAS_TRAILCHARS;
}
#endif
}
}
#endif
/****************************************************************************
* Name: fat_findalias
*
* Description: 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(FAR struct fat_mountpt_s *fs,
FAR 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
*
* Description: 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(FAR struct fat_mountpt_s *fs,
FAR 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++)
{
/* Empty */
}
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');
#ifdef CONFIG_FAT_LFN_ALIAS_HASH
/* Add a hash of the long filename to the short filename, to reduce
* collisions.
*/
if ((ret = fat_findalias(fs, dirinfo)) == OK)
{
uint16_t hash = dirinfo->fd_seq.ds_offset;
for (i = 0; dirinfo->fd_lfname[i] != '\0'; i++)
{
hash = ((hash << 5) + hash) ^ dirinfo->fd_lfname[i];
}
for (i = 0; i < tilde - 2; i++)
{
uint8_t nibble = (hash >> (i * 4)) & 0x0f;
FAR const char *digits = "0123456789ABCDEF";
dirinfo->fd_name[tilde - 1 - i] = digits[nibble];
}
}
else if (ret == -ENOENT)
{
return OK; /* Alias was unique already */
}
else
{
return ret; /* Other error */
}
#endif
/* 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
*
* Description: 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(FAR const char **path,
FAR struct fat_dirinfo_s *dirinfo,
FAR 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
*
* Description: Find a short file name directory entry. Returns OK if the
* directory exists; -ENOENT if it does not.
*
****************************************************************************/
static int fat_findsfnentry(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo)
{
uint16_t diroffset;
FAR 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
*
* Description: There are 13 characters per LFN entry, broken up into three
* chunks for characters 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(FAR uint8_t *chunk, FAR const lfnchar *substr,
int nchunk)
{
uint16_t wch;
lfnchar 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 = fat_getuint16((FAR uint8_t *)chunk);
# ifdef CONFIG_FAT_LFN_UTF8
if (wch != ch)
# else
if ((wch & 0xff) != (uint16_t)ch)
# endif
{
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(uint16_t);
}
/* All of the characters in the chunk match.. Return success */
return true;
}
#endif
/****************************************************************************
* Name: fat_cmplfname
*
* Description: 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(FAR const uint8_t *direntry,
FAR const lfnchar *substr)
{
FAR uint8_t *chunk;
int len;
bool match;
/* How much of string do we have to compare? (including the NUL
* terminator).
*/
for (len = 1; substr[len - 1] != '\0'; len++)
{
/* Empty */
}
/* 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
*
* Description: 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(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo)
{
FAR uint8_t *direntry;
uint16_t diroffset;
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).
*/
for (namelen = 0; dirinfo->fd_lfname[namelen] != '\0'; namelen++)
{
/* Empty */
}
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 sequence 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 system 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
*
* Description: Find a free directory entry for a short file name entry.
*
****************************************************************************/
static inline int fat_allocatesfnentry(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo)
{
FAR uint8_t *direntry;
uint16_t diroffset;
#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
*
* Description: 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(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo)
{
FAR uint8_t *direntry;
uint16_t diroffset;
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).
*/
for (namelen = 0; dirinfo->fd_lfname[namelen] != '\0'; namelen++)
{
/* Empty */
}
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
*
* Description: 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(FAR uint8_t *direntry, FAR 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 eencounter 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
*
* Description: There are 13 characters per LFN entry, broken up into three
* chunks for characters 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(FAR uint8_t *chunk, FAR lfnchar *dest,
int nchunk)
{
uint16_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 = fat_getuint16(chunk);
# ifdef CONFIG_FAT_LFN_UTF8
*dest++ = wch;
# else
*dest++ = (uint8_t)(wch & 0xff);
# endif
chunk += sizeof(uint16_t);
}
}
#endif
/****************************************************************************
* Name: fat_getlfname
*
* Description: 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(FAR struct fat_mountpt_s *fs,
FAR struct fs_dirent_s *dir,
FAR struct dirent *entry)
{
FAR struct fat_dirent_s *fdir;
FAR uint8_t *direntry;
lfnchar lfname[LDIR_MAXLFNCHARS];
uint16_t diroffset;
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 */
fdir = (FAR struct fat_dirent_s *)dir;
diroffset = (fdir->dir.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 (; ; )
{
# ifdef CONFIG_FAT_LFN_UTF8
/* Get the string offset associated with the "last" entry. */
/* 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 available 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, init chars (0xffff) and '\0'.
*/
for (; nsrc > 0 && (lfname[nsrc - 1] == ' ' ||
lfname[nsrc - 1] == '\0' ||
lfname[nsrc - 1] == 0xffff); nsrc--);
/* 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).
*/
entry->d_name[NAME_MAX] = '\0';
offset = NAME_MAX;
}
/* Then transfer the characters */
for (i = nsrc - 1; i >= 0; i--)
{
offset = fat_ucstoutf8((FAR uint8_t *)entry->d_name,
offset, lfname[i]);
}
# else
/* 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 available 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, init chars (0xff) and '\0'.
*/
for (; nsrc > 0 && (lfname[nsrc - 1] == ' ' ||
lfname[nsrc - 1] == '\0' ||
lfname[nsrc - 1] == 0xff); 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).
*/
entry->d_name[offset + nsrc] = '\0';
}
/* Then transfer the characters */
for (i = 0; i < nsrc && offset + i < NAME_MAX; i++)
{
entry->d_name[offset + i] = lfname[i];
}
}
#endif
/* Read next directory entry */
if (fat_nextdirentry(fs, &fdir->dir) != OK)
{
return -ENOENT;
}
/* Make sure that the directory sector into the sector cache */
ret = fat_fscacheread(fs, fdir->dir.fd_currsector);
if (ret < 0)
{
return ret;
}
/* Get a reference to the current directory entry */
diroffset = (fdir->dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
direntry = &fs->fs_buffer[diroffset];
/* Get the next expected sequence number. */
seqno = --rawseq;
if (seqno < 1)
{
# ifdef CONFIG_FAT_LFN_UTF8
/* We must left align the d_name after utf8 processing */
if (offset > 0)
{
memmove(entry->d_name, &entry->d_name[offset],
(NAME_MAX + 1) - offset);
}
# endif
/* 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
*
* Description: Write the short directory entry name.
*
* Assumption: The directory sector is in the cache.
*
****************************************************************************/
static int fat_putsfname(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo)
{
FAR 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
*
* Description: There are 13 characters per LFN entry, broken up into three
* chunks for characters 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(FAR 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((FAR uint8_t *)chunk, (uint16_t)0xffff);
chunk += sizeof(uint16_t);
}
}
#endif
/****************************************************************************
* Name: fat_putlfnchunk
*
* Description: There are 13 characters per LFN entry, broken up into three
* chunks for characters 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(FAR uint8_t *chunk, FAR const lfnchar *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(uint16_t);
}
}
#endif
/****************************************************************************
* Name: fat_putlfname
*
* Description:
* 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(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo)
{
FAR uint8_t *direntry;
uint16_t diroffset;
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).
*/
for (namelen = 0; dirinfo->fd_lfname[namelen] != '\0'; namelen++)
{
/* Empty */
}
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);
LDIR_PUTFSTCLUSTLO(direntry, 0);
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
*
* Description: 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(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo,
uint8_t attributes, uint32_t fattime)
{
FAR 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 */
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
*
* Description: 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(FAR struct fat_mountpt_s *fs,
FAR struct fat_dirinfo_s *dirinfo,
FAR const char *path)
{
FAR uint8_t *direntry;
off_t cluster;
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 segment
* 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 matching the
* 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;
}
if (*path == '\0')
{
return OK;
}
/* 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
*
* Description:
* Find (or allocate) all needed directory entries to contain the file name
*
****************************************************************************/
int fat_allocatedirentry(FAR struct fat_mountpt_s *fs,
FAR 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
*
* Description: 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(FAR struct fat_mountpt_s *fs, struct fat_dirseq_s *seq)
{
#ifdef CONFIG_FAT_LFN
struct fs_fatdir_s dir;
FAR uint8_t *direntry;
uint16_t diroffset;
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
FAR 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
*
* Description: 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(FAR struct fat_mountpt_s *fs,
FAR struct fs_dirent_s *dir,
FAR struct dirent *entry)
{
FAR struct fat_dirent_s *fdir;
uint16_t diroffset;
FAR uint8_t *direntry;
#ifdef CONFIG_FAT_LFN
uint8_t attribute;
#endif
/* Get a reference to the current directory entry */
fdir = (FAR struct fat_dirent_s *)dir;
diroffset = (fdir->dir.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, entry);
}
else
#endif
{
/* No.. Get the name from a short file name directory entries */
return fat_getsfname(direntry, entry->d_name, NAME_MAX + 1);
}
}
/****************************************************************************
* Name: fat_dirnamewrite
*
* Description:
* 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(FAR struct fat_mountpt_s *fs,
FAR 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 through 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
*
* Description: 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(FAR struct fat_mountpt_s *fs,
FAR 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 through 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
*
* Description: Create a directory entry for a new file
*
****************************************************************************/
int fat_dircreate(FAR struct fat_mountpt_s *fs,
FAR 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
*
* Description: Remove a directory or file from the file system. This
* implements both rmdir() and unlink().
*
****************************************************************************/
int fat_remove(FAR struct fat_mountpt_s *fs, FAR const char *relpath,
bool directory)
{
struct fat_dirinfo_s dirinfo;
uint32_t dircluster;
FAR 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 directory? */
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;
}