fa1fad50b9
The field of first cluster under LFN directory entry must be 0x0000. If it is set a value other than 0, it causes a problem where the long file name entry cannot be found on a Windows PC and the short file name is always used. In addition, correct the macro error in big endian.
3274 lines
90 KiB
C
3274 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/types.h>
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/fs/fat.h>
|
|
|
|
#include "inode/inode.h"
|
|
#include "fs_fat32.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#ifndef MIN
|
|
# define MIN(a,b) ((a) < (b) ? (a) : (b))
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* 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);
|
|
#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, 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, 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 */
|
|
lfnchar *ext; /* Pointer to the extension substring */
|
|
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 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 */
|
|
|
|
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 (; ; )
|
|
{
|
|
# 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).
|
|
*/
|
|
|
|
dir->fd_dir.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 *)dir->fd_dir.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).
|
|
*/
|
|
|
|
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];
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* 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)
|
|
{
|
|
# ifdef CONFIG_FAT_LFN_UTF8
|
|
/* We must left align the d_name after utf8 processing */
|
|
|
|
if (offset > 0)
|
|
{
|
|
memmove(dir->fd_dir.d_name, &dir->fd_dir.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 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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
uint16_t diroffset;
|
|
FAR 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
|
|
*
|
|
* 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;
|
|
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;
|
|
}
|