You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2947 lines
83 KiB
2947 lines
83 KiB
/**************************************************************************** |
|
* fs/fat/fs_fat32dirent.c |
|
* |
|
* Copyright (C) 2011 Gregory Nutt. All rights reserved. |
|
* Author: Gregory Nutt <gnutt@nuttx.org> |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions |
|
* are met: |
|
* |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice, this list of conditions and the following disclaimer in |
|
* the documentation and/or other materials provided with the |
|
* distribution. |
|
* 3. Neither the name NuttX nor the names of its contributors may be |
|
* used to endorse or promote products derived from this software |
|
* without specific prior written permission. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
* POSSIBILITY OF SUCH DAMAGE. |
|
* |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* NOTE: If CONFIG_FAT_LFN is defined, then there may be some legal, patent |
|
* issues. The following was extracted from the entry "File Allocation Table |
|
* from Wikipedia, the free encyclopedia: |
|
* |
|
* "On December 3, 2003 Microsoft announced it would be offering licenses |
|
* for use of its FAT specification and 'associated intellectual property', |
|
* at the cost of a US$0.25 royalty per unit sold, with a $250,000 maximum |
|
* royalty per license agreement. |
|
* |
|
* o "U.S. Patent 5,745,902 (http://www.google.com/patents?vid=5745902) - |
|
* Method and system for accessing a file using file names having |
|
* different file name formats. ... |
|
* o "U.S. Patent 5,579,517 (http://www.google.com/patents?vid=5579517) - |
|
* Common name space for long and short filenames. ... |
|
* o "U.S. Patent 5,758,352 (http://www.google.com/patents?vid=5758352) - |
|
* Common name space for long and short filenames. ... |
|
* o "U.S. Patent 6,286,013 (http://www.google.com/patents?vid=6286013) - |
|
* Method and system for providing a common name space for long and |
|
* short file names in an operating system. ... |
|
* |
|
* "Many technical commentators have concluded that these patents only cover |
|
* FAT implementations that include support for long filenames, and that |
|
* removable solid state media and consumer devices only using short names |
|
* would be unaffected. ..." |
|
* |
|
* So you have been forewarned: Use the long filename at your own risk! |
|
* |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Included Files |
|
****************************************************************************/ |
|
|
|
#include <nuttx/config.h> |
|
|
|
#include <sys/types.h> |
|
|
|
#include <stdint.h> |
|
#include <stdbool.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <ctype.h> |
|
#include <assert.h> |
|
#include <errno.h> |
|
#include <debug.h> |
|
|
|
#include <nuttx/fs/fs.h> |
|
#include <nuttx/fs/fat.h> |
|
|
|
#include "fs_internal.h" |
|
#include "fs_fat32.h" |
|
|
|
/**************************************************************************** |
|
* Definitions |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Private Types |
|
****************************************************************************/ |
|
|
|
enum fat_case_e |
|
{ |
|
FATCASE_UNKNOWN = 0, |
|
FATCASE_UPPER, |
|
FATCASE_LOWER |
|
}; |
|
|
|
/**************************************************************************** |
|
* Private Function Prototypes |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static uint8_t fat_lfnchecksum(const uint8_t *sfname); |
|
#endif |
|
static inline int fat_parsesfname(const char **path, |
|
struct fat_dirinfo_s *dirinfo, |
|
char *terminator); |
|
#ifdef CONFIG_FAT_LFN |
|
static inline int fat_parselfname(const char **path, |
|
struct fat_dirinfo_s *dirinfo, |
|
char *terminator); |
|
static inline int fat_createalias(struct fat_dirinfo_s *dirinfo); |
|
static inline int fat_findalias(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo); |
|
static inline int fat_uniquealias(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo); |
|
#endif |
|
static int fat_path2dirname(const char **path, struct fat_dirinfo_s *dirinfo, |
|
char *terminator); |
|
static int fat_findsfnentry(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo); |
|
#ifdef CONFIG_FAT_LFN |
|
static bool fat_cmplfnchunk(uint8_t *chunk, const uint8_t *substr, int nchunk); |
|
static bool fat_cmplfname(const uint8_t *direntry, const uint8_t *substr); |
|
static inline int fat_findlfnentry(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo); |
|
|
|
#endif |
|
static inline int fat_allocatesfnentry(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo); |
|
#ifdef CONFIG_FAT_LFN |
|
static inline int fat_allocatelfnentry(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo); |
|
#endif |
|
static inline int fat_getsfname(uint8_t *direntry, char *buffer, |
|
unsigned int buflen); |
|
#ifdef CONFIG_FAT_LFN |
|
static void fat_getlfnchunk(uint8_t *chunk, uint8_t *dest, int nchunk); |
|
static inline int fat_getlfname(struct fat_mountpt_s *fs, struct fs_dirent_s *dir); |
|
#endif |
|
static int fat_putsfname(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo); |
|
#ifdef CONFIG_FAT_LFN |
|
static void fat_initlfname(uint8_t *chunk, int nchunk); |
|
static void fat_putlfnchunk(uint8_t *chunk, const uint8_t *src, int nchunk); |
|
static int fat_putlfname(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo); |
|
#endif |
|
static int fat_putsfdirentry(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo, |
|
uint8_t attributes, uint32_t fattime); |
|
|
|
/**************************************************************************** |
|
* Private Variables |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Public Variables |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Private Functions |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Name: fat_lfnchecksum |
|
* |
|
* Desciption: Caculate the checksum of . |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static uint8_t fat_lfnchecksum(const uint8_t *sfname) |
|
{ |
|
uint8_t sum = 0; |
|
int i; |
|
|
|
for (i = DIR_MAXFNAME; i; i--) |
|
{ |
|
sum = ((sum & 1) << 7) + (sum >> 1) + *sfname++; |
|
} |
|
|
|
return sum; |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_parsesfname |
|
* |
|
* Desciption: Convert a user filename into a properly formatted FAT |
|
* (short 8.3) filename as it would appear in a directory entry. Here are |
|
* the rules for the 8+3 short file name in the directory: |
|
* |
|
* The first byte: |
|
* |
|
* 0xe5 = The directory is free |
|
* 0x00 = This directory and all following directories are free |
|
* 0x05 = Really 0xe5 |
|
* 0x20 = May NOT be ' ' |
|
* |
|
* Other characters may be any characters except for the following: |
|
* |
|
* 0x00-0x1f = (except for 0x00 and 0x05 in the first byte) |
|
* 0x22 = '"' |
|
* 0x2a-0x2c = '*', '+', ',' |
|
* 0x2e-0x2f = '.', '/' |
|
* 0x3a-0x3f = ':', ';', '<', '=', '>', '?' |
|
* 0x5b-0x5d = '[', '\\', ;]' |
|
* 0x7c = '|' |
|
* |
|
* '.' May only occur once within string and only within the first 9 |
|
* bytes. The '.' is not save in the directory, but is implicit in |
|
* 8+3 format. |
|
* |
|
* Lower case characters are not allowed in directory names (without some |
|
* poorly documented operations on the NTRes directory byte). Lower case |
|
* codes may represent different characters in other character sets ("DOS |
|
* code pages". The logic below does not, at present, support any other |
|
* character sets. |
|
* |
|
* Returned value: |
|
* OK - The path refers to a valid 8.3 FAT file name and has been properly |
|
* converted and stored in dirinfo. |
|
* <0 - Otherwise an negated error is returned meaning that the string is |
|
* not a valid 8+3 because: |
|
* |
|
* 1. Contains characters not in the printable character set, |
|
* 2. Contains forbidden characters or multiple '.' characters |
|
* 3. File name or extension is too long. |
|
* |
|
* If CONFIG_FAT_LFN is defined and CONFIG_FAT_LCNAMES is NOT |
|
* defined, then: |
|
* |
|
* 4a. File name or extension contains lower case characters. |
|
* |
|
* If CONFIG_FAT_LFN is defined and CONFIG_FAT_LCNAMES is defined, |
|
* then: |
|
* |
|
* 4b. File name or extension is not all the same case. |
|
* |
|
****************************************************************************/ |
|
|
|
static inline int fat_parsesfname(const char **path, |
|
struct fat_dirinfo_s *dirinfo, |
|
char *terminator) |
|
{ |
|
#ifdef CONFIG_FAT_LCNAMES |
|
unsigned int ntlcenable = FATNTRES_LCNAME | FATNTRES_LCEXT; |
|
unsigned int ntlcfound = 0; |
|
#ifdef CONFIG_FAT_LFN |
|
enum fat_case_e namecase = FATCASE_UNKNOWN; |
|
enum fat_case_e extcase = FATCASE_UNKNOWN; |
|
#endif |
|
#endif |
|
const char *node = *path; |
|
int endndx; |
|
uint8_t ch; |
|
int ndx = 0; |
|
|
|
/* Initialized the name with all spaces */ |
|
|
|
memset(dirinfo->fd_name, ' ', DIR_MAXFNAME); |
|
|
|
/* Loop until the name is successfully parsed or an error occurs */ |
|
|
|
endndx = 8; |
|
for (;;) |
|
{ |
|
/* Get the next byte from the path */ |
|
|
|
ch = *node++; |
|
|
|
/* Check if this the last byte in this node of the name */ |
|
|
|
if ((ch == '\0' || ch == '/') && ndx != 0 ) |
|
{ |
|
/* Return the accumulated NT flags and the terminating character */ |
|
|
|
#ifdef CONFIG_FAT_LCNAMES |
|
dirinfo->fd_ntflags = ntlcfound & ntlcenable; |
|
#endif |
|
*terminator = ch; |
|
*path = node; |
|
return OK; |
|
} |
|
|
|
/* Accept only the printable character set (excluding space). Note |
|
* that the first byte of the name could be 0x05 meaning that is it |
|
* 0xe5, but this is not a printable character in this character in |
|
* either case. |
|
*/ |
|
|
|
else if (!isgraph(ch)) |
|
{ |
|
goto errout; |
|
} |
|
|
|
/* Check for transition from name to extension. Only one '.' is |
|
* permitted and it must be within first 9 characters |
|
*/ |
|
|
|
else if (ch == '.' && endndx == 8) |
|
{ |
|
/* Starting the extension */ |
|
|
|
ndx = 8; |
|
endndx = 11; |
|
continue; |
|
} |
|
|
|
/* Reject printable characters forbidden by FAT */ |
|
|
|
else if (ch == '"' || (ch >= '*' && ch <= ',') || |
|
ch == '.' || ch == '/' || |
|
(ch >= ':' && ch <= '?') || |
|
(ch >= '[' && ch <= ']') || |
|
(ch == '|')) |
|
{ |
|
goto errout; |
|
} |
|
|
|
/* Check for upper case characters */ |
|
|
|
#ifdef CONFIG_FAT_LCNAMES |
|
else if (isupper(ch)) |
|
{ |
|
/* Some or all of the characters in the name or extension |
|
* are upper case. Force all of the characters to be interpreted |
|
* as upper case. |
|
*/ |
|
|
|
if (endndx == 8) |
|
{ |
|
/* Is there mixed case in the name? */ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
if (namecase == FATCASE_LOWER) |
|
{ |
|
/* Mixed case in the name -- use the long file name */ |
|
|
|
goto errout; |
|
} |
|
|
|
/* So far, only upper case in the name*/ |
|
|
|
namecase = FATCASE_UPPER; |
|
#endif |
|
|
|
/* Clear lower case name bit in mask*/ |
|
|
|
ntlcenable &= ~FATNTRES_LCNAME; |
|
} |
|
else |
|
{ |
|
/* Is there mixed case in the extension? */ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
if (extcase == FATCASE_LOWER) |
|
{ |
|
/* Mixed case in the extension -- use the long file name */ |
|
|
|
goto errout; |
|
} |
|
|
|
/* So far, only upper case in the extension*/ |
|
|
|
extcase = FATCASE_UPPER; |
|
#endif |
|
|
|
/* Clear lower case extension in mask */ |
|
|
|
ntlcenable &= ~FATNTRES_LCEXT; |
|
} |
|
} |
|
#endif |
|
|
|
/* Check for lower case characters */ |
|
|
|
else if (islower(ch)) |
|
{ |
|
#if defined(CONFIG_FAT_LFN) && !defined(CONFIG_FAT_LCNAMES) |
|
/* If lower case characters are present, then a long file |
|
* name will be constructed. |
|
*/ |
|
|
|
goto errout; |
|
#else |
|
/* Convert the character to upper case */ |
|
|
|
ch = toupper(ch); |
|
|
|
/* Some or all of the characters in the name or extension |
|
* are lower case. They can be interpreted as lower case if |
|
* only if all of the characters in the name or extension are |
|
* lower case. |
|
*/ |
|
|
|
#ifdef CONFIG_FAT_LCNAMES |
|
if (endndx == 8) |
|
{ |
|
/* Is there mixed case in the name? */ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
if (namecase == FATCASE_UPPER) |
|
{ |
|
/* Mixed case in the name -- use the long file name */ |
|
|
|
goto errout; |
|
} |
|
|
|
/* So far, only lower case in the name*/ |
|
|
|
namecase = FATCASE_LOWER; |
|
#endif |
|
|
|
/* Set lower case name bit */ |
|
|
|
ntlcfound |= FATNTRES_LCNAME; |
|
} |
|
else |
|
{ |
|
/* Is there mixed case in the extension? */ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
if (extcase == FATCASE_UPPER) |
|
{ |
|
/* Mixed case in the extension -- use the long file name */ |
|
|
|
goto errout; |
|
} |
|
|
|
/* So far, only lower case in the extension*/ |
|
|
|
extcase = FATCASE_LOWER; |
|
#endif |
|
|
|
/* Set lower case extension bit */ |
|
|
|
ntlcfound |= FATNTRES_LCEXT; |
|
} |
|
#endif |
|
#endif /* CONFIG_FAT_LFN && !CONFIG_FAT_LCNAMES */ |
|
} |
|
|
|
/* Check if the file name exceeds the size permitted (without |
|
* long file name support). |
|
*/ |
|
|
|
if (ndx >= endndx) |
|
{ |
|
goto errout; |
|
} |
|
|
|
/* Save next character in the accumulated name */ |
|
|
|
dirinfo->fd_name[ndx++] = ch; |
|
} |
|
|
|
errout: |
|
return -EINVAL; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_parselfname |
|
* |
|
* Desciption: Convert a user filename into a properly formatted FAT |
|
* long filename as it would appear in a directory entry. Here are |
|
* the rules for the long file name in the directory: |
|
* |
|
* Valid characters are the same as for short file names EXCEPT: |
|
* |
|
* 1. '+', ',', ';', '=', '[', and ']' are accepted in the file name |
|
* 2. '.' (dot) can occur more than once in a filename. Extension is |
|
* the substring after the last dot. |
|
* |
|
* Returned value: |
|
* OK - The path refers to a valid long file name and has been properly |
|
* stored in dirinfo. |
|
* <0 - Otherwise an negated error is returned meaning that the string is |
|
* not a valid long file name: |
|
* |
|
* 1. Contains characters not in the printable character set, |
|
* 2. Contains forbidden characters |
|
* 3. File name is too long. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static inline int fat_parselfname(const char **path, |
|
struct fat_dirinfo_s *dirinfo, |
|
char *terminator) |
|
{ |
|
const char *node = *path; |
|
uint8_t ch; |
|
int ndx = 0; |
|
|
|
/* Loop until the name is successfully parsed or an error occurs */ |
|
|
|
for (;;) |
|
{ |
|
/* Get the next byte from the path */ |
|
|
|
ch = *node++; |
|
|
|
/* Check if this the last byte in this node of the name */ |
|
|
|
if ((ch == '\0' || ch == '/') && ndx != 0 ) |
|
{ |
|
/* Null terminate the string */ |
|
|
|
dirinfo->fd_lfname[ndx] = '\0'; |
|
|
|
/* Return the remaining sub-string and the terminating character. */ |
|
|
|
*terminator = ch; |
|
*path = node; |
|
return OK; |
|
} |
|
|
|
/* Accept only the printable character set (including space) */ |
|
|
|
else if (!isprint(ch)) |
|
{ |
|
goto errout; |
|
} |
|
|
|
/* Reject printable characters forbidden by FAT */ |
|
|
|
else if (ch == '"' || ch == '*' || ch == '/' || ch == ':' || |
|
ch == '<' || ch == '>' || ch == '?' || ch == '\\' || |
|
ch == '|') |
|
{ |
|
goto errout; |
|
} |
|
|
|
/* Check if the file name exceeds the size permitted. */ |
|
|
|
if (ndx >= LDIR_MAXFNAME) |
|
{ |
|
goto errout; |
|
} |
|
|
|
/* Save next character in the accumulated name */ |
|
|
|
dirinfo->fd_lfname[ndx++] = ch; |
|
} |
|
|
|
errout: |
|
dirinfo->fd_lfname[0] = '\0'; |
|
return -EINVAL; |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_createalias |
|
* |
|
* Desciption: Given a valid long file name, create a short filename alias. |
|
* Here are the rules for creation of the alias: |
|
* |
|
* 1. All uppercase |
|
* 2. All dots except the last deleted |
|
* 3. First 6 (uppercase) characters used as a base |
|
* 4. Then ~1. The number is increased if the file already exists in the |
|
* directory. If the number exeeds >10, then character stripped off the |
|
* base. |
|
* 5. The extension is the first 3 uppercase chars of extension. |
|
* |
|
* This function is called only from fat_putlfname() |
|
* |
|
* Returned value: |
|
* OK - The alias was created correctly. |
|
* <0 - Otherwise an negated error is returned. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static inline int fat_createalias(struct fat_dirinfo_s *dirinfo) |
|
{ |
|
uint8_t ch; /* Current character being processed */ |
|
char *ext; /* Pointer to the extension substring */ |
|
char *src; /* Pointer to the long file name source */ |
|
int len; /* Total length of the long file name */ |
|
int namechars; /* Number of characters available in long name */ |
|
int extchars; /* Number of characters available in long name extension */ |
|
int endndx; /* Maximum index into the short name array */ |
|
int ndx; /* Index to store next character */ |
|
|
|
/* First, let's decide what is name and what is extension */ |
|
|
|
len = strlen((char*)dirinfo->fd_lfname); |
|
ext = strrchr((char*)dirinfo->fd_lfname, '.'); |
|
if (ext) |
|
{ |
|
ptrdiff_t tmp; |
|
|
|
/* ext points to the final '.'. The difference in bytes from the |
|
* beginning of the string is then the name length. |
|
*/ |
|
|
|
tmp = ext - (char*)dirinfo->fd_lfname; |
|
namechars = tmp; |
|
|
|
/* And the rest, exluding the '.' is the extension. */ |
|
|
|
extchars = len - namechars - 1; |
|
ext++; |
|
} |
|
else |
|
{ |
|
/* No '.' found. It is all name and no extension. */ |
|
|
|
namechars = len; |
|
extchars = 0; |
|
} |
|
|
|
/* Alias are always all upper case */ |
|
|
|
#ifdef CONFIG_FAT_LCNAMES |
|
dirinfo->fd_ntflags = 0; |
|
#endif |
|
|
|
/* Initialized the short name with all spaces */ |
|
|
|
memset(dirinfo->fd_name, ' ', DIR_MAXFNAME); |
|
|
|
/* Handle a special case where there is no name. Windows seems to use |
|
* the extension plus random stuff then ~1 to pat to 8 bytes. Some |
|
* examples: |
|
* |
|
* a.b -> a.b No long name |
|
* a., -> A26BE~1._ Padded name to make unique, _ replaces , |
|
* .b -> B1DD2~1 Extension used as name |
|
* .bbbbbbb -> BBBBBB~1 Extension used as name |
|
* a.bbbbbbb -> AAD39~1.BBB Padded name to make unique. |
|
* aaa.bbbbbbb -> AAA~1.BBBB Not padded, already unique? |
|
* ,.bbbbbbb -> _82AF~1.BBB _ replaces , |
|
* +[],.bbbbbbb -> ____~1.BBB _ replaces +[], |
|
*/ |
|
|
|
if (namechars < 1) |
|
{ |
|
/* Use the extension as the name */ |
|
|
|
DEBUGASSERT(ext && extchars > 0); |
|
src = ext; |
|
ext = NULL; |
|
namechars = extchars; |
|
extchars = 0; |
|
} |
|
else |
|
{ |
|
src = (char*)dirinfo->fd_lfname; |
|
} |
|
|
|
/* Then copy the name and extension, handling upper case conversions and |
|
* excluding forbidden characters. |
|
*/ |
|
|
|
ndx = 0; /* Position to write the next name character */ |
|
endndx = 6; /* Maximum index before we write ~! and switch to the extension */ |
|
|
|
for (;;) |
|
{ |
|
/* Get the next byte from the path. Break out of the loop if we |
|
* encounter the end of null-terminated the long file name string. |
|
*/ |
|
|
|
ch = *src++; |
|
if (ch == '\0') |
|
{ |
|
/* This is the end of the source string. Do we need to add ~1. We |
|
* will do that if we were parsing the name part when the endo of |
|
* string was encountered. |
|
*/ |
|
|
|
if (endndx == 6) |
|
{ |
|
/* Write the ~1 at the end of the name */ |
|
|
|
dirinfo->fd_name[ndx++] = '~'; |
|
dirinfo->fd_name[ndx] = '1'; |
|
} |
|
|
|
/* In any event, we are done */ |
|
|
|
return OK; |
|
} |
|
|
|
/* Exclude those few characters included in long file names, but |
|
* excluded in short file name: '+', ',', ';', '=', '[', ']', and '.' |
|
*/ |
|
|
|
if (ch == '+' || ch == ',' || ch == '.' || ch == ';' || |
|
ch == '=' || ch == '[' || ch == ']' || ch == '|') |
|
{ |
|
/* Use the underbar character instead */ |
|
|
|
ch = '_'; |
|
} |
|
|
|
/* Handle lower case characters */ |
|
|
|
ch = toupper(ch); |
|
|
|
/* We now have a valid character to add to the name or extension. */ |
|
|
|
dirinfo->fd_name[ndx++] = ch; |
|
|
|
/* Did we just add a character to the name? */ |
|
|
|
if (endndx == 6) |
|
{ |
|
/* Decrement the number of characters available in the name |
|
* portion of the long name. |
|
*/ |
|
|
|
namechars--; |
|
|
|
/* Is it time to add ~1 to the string? We will do that if |
|
* either (1) we have already added the maximum number of |
|
* characters to the short name, or (2) if there are no further |
|
* characters available in the name portion of the long name. |
|
*/ |
|
|
|
if (namechars < 1 || ndx == 6) |
|
{ |
|
/* Write the ~1 at the end of the name */ |
|
|
|
dirinfo->fd_name[ndx++] = '~'; |
|
dirinfo->fd_name[ndx] = '1'; |
|
|
|
/* Then switch to the extension (if there is one) */ |
|
|
|
if (!ext || extchars < 1) |
|
{ |
|
return OK; |
|
} |
|
|
|
ndx = 8; |
|
endndx = 11; |
|
src = ext; |
|
} |
|
} |
|
|
|
/* No.. we just added a character to the extension */ |
|
|
|
else |
|
{ |
|
/* Decrement the number of characters available in the name |
|
* portion of the long name |
|
*/ |
|
|
|
extchars--; |
|
|
|
/* Is the extension complete? */ |
|
|
|
if (extchars < 1 || ndx == 11) |
|
{ |
|
return OK; |
|
} |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_findalias |
|
* |
|
* Desciption: Make sure that the short alias for the long file name is |
|
* unique, ie., that there is no other |
|
* |
|
* NOTE: This function does not restore the directory entry that was in the |
|
* sector cache |
|
* |
|
* Returned value: |
|
* OK - The alias is unique. |
|
* <0 - Otherwise an negated error is returned. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static inline int fat_findalias(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo) |
|
{ |
|
struct fat_dirinfo_s tmpinfo; |
|
|
|
/* Save the current directory info. */ |
|
|
|
memcpy(&tmpinfo, dirinfo, sizeof(struct fat_dirinfo_s)); |
|
|
|
/* Then re-initialize to the beginning of the current directory, starting |
|
* with the first entry. |
|
*/ |
|
|
|
tmpinfo.dir.fd_startcluster = tmpinfo.dir.fd_currcluster; |
|
tmpinfo.dir.fd_currsector = tmpinfo.fd_seq.ds_startsector; |
|
tmpinfo.dir.fd_index = 0; |
|
|
|
/* Search for the single short file name directory entry in this directory */ |
|
|
|
return fat_findsfnentry(fs, &tmpinfo); |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_uniquealias |
|
* |
|
* Desciption: Make sure that the short alias for the long file name is |
|
* unique, modifying the alias as necessary to assure uniqueness. |
|
* |
|
* NOTE: This function does not restore the directory entry that was in the |
|
* sector cache |
|
* |
|
* information upon return. |
|
* Returned value: |
|
* OK - The alias is unique. |
|
* <0 - Otherwise an negated error is returned. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static inline int fat_uniquealias(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo) |
|
{ |
|
int tilde; |
|
int lsdigit; |
|
int ret; |
|
int i; |
|
|
|
/* Find the position of the tilde character in the short name. The tilde |
|
* can not occur in positions 0 or 7: |
|
*/ |
|
|
|
for (tilde = 1; tilde < 7 && dirinfo->fd_name[tilde] != '~'; tilde++); |
|
if (tilde >= 7) |
|
{ |
|
return -EINVAL; |
|
} |
|
|
|
/* The least significant number follows the digit (and must be '1') */ |
|
|
|
lsdigit = tilde + 1; |
|
DEBUGASSERT(dirinfo->fd_name[lsdigit] == '1'); |
|
|
|
/* Search for the single short file name directory entry in this directory */ |
|
|
|
while ((ret = fat_findalias(fs, dirinfo)) == OK) |
|
{ |
|
/* Adjust the numeric value after the '~' to make the file name unique */ |
|
|
|
for (i = lsdigit; i > 0; i--) |
|
{ |
|
/* If we have backed up to the tilde position, then we have to move |
|
* the tilde back one position. |
|
*/ |
|
|
|
if (i == tilde) |
|
{ |
|
/* Is there space to back up the tilde? */ |
|
|
|
if (tilde <= 1) |
|
{ |
|
/* No.. then we cannot add the name to the directory. |
|
* What is the likelihood of that happening? |
|
*/ |
|
|
|
return -ENOSPC; |
|
} |
|
|
|
/* Back up the tilde and break out of the inner loop */ |
|
|
|
tilde--; |
|
dirinfo->fd_name[tilde] = '~'; |
|
dirinfo->fd_name[tilde+1] = '1'; |
|
break; |
|
} |
|
|
|
/* We are not yet at the tilde,. Check if this digit has already |
|
* reached its maximum value. |
|
*/ |
|
|
|
else if (dirinfo->fd_name[i] < '9') |
|
{ |
|
/* No, it has not.. just increment the LS digit and break out of |
|
* the inner loop. |
|
*/ |
|
|
|
dirinfo->fd_name[i]++; |
|
break; |
|
} |
|
|
|
/* Yes.. Reset the digit to '0' and loop to adjust the digit before |
|
* this one. |
|
*/ |
|
|
|
else |
|
{ |
|
dirinfo->fd_name[i] = '0'; |
|
} |
|
} |
|
} |
|
|
|
/* The while loop terminated because of an error; fat_findalias() |
|
* returned something other than OK. The only acceptable error is |
|
* -ENOENT, meaning that the short file name directory does not |
|
* exist in this directory. |
|
*/ |
|
|
|
if (ret == -ENOENT) |
|
{ |
|
ret = OK; |
|
} |
|
|
|
return ret; |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_path2dirname |
|
* |
|
* Desciption: Convert a user filename into a properly formatted FAT |
|
* (short 8.3) filename as it would appear in a directory entry. |
|
* |
|
****************************************************************************/ |
|
|
|
static int fat_path2dirname(const char **path, struct fat_dirinfo_s *dirinfo, |
|
char *terminator) |
|
{ |
|
#ifdef CONFIG_FAT_LFN |
|
int ret; |
|
|
|
/* Assume no long file name */ |
|
|
|
dirinfo->fd_lfname[0] = '\0'; |
|
|
|
/* Then parse the (assumed) 8+3 short file name */ |
|
|
|
ret = fat_parsesfname(path, dirinfo, terminator); |
|
if (ret < 0) |
|
{ |
|
/* No, the name is not a valid short 8+3 file name. Try parsing |
|
* the long file name. |
|
*/ |
|
|
|
ret = fat_parselfname(path, dirinfo, terminator); |
|
} |
|
|
|
return ret; |
|
#else |
|
/* Only short, 8+3 filenames supported */ |
|
|
|
return fat_parsesfname(path, dirinfo, terminator); |
|
#endif |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_findsfnentry |
|
* |
|
* Desciption: Find a short file name directory entry. Returns OK if the |
|
* directory exists; -ENOENT if it does not. |
|
* |
|
****************************************************************************/ |
|
|
|
static int fat_findsfnentry(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo) |
|
{ |
|
uint16_t diroffset; |
|
uint8_t *direntry; |
|
#ifdef CONFIG_FAT_LFN |
|
off_t startsector; |
|
#endif |
|
int ret; |
|
|
|
/* Save the starting sector of the directory. This is not really needed |
|
* for short name entries, but this keeps things consistent with long |
|
* file name entries.. |
|
*/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
startsector = dirinfo->dir.fd_currsector; |
|
#endif |
|
|
|
/* Search, beginning with the current sector, for a directory entry with |
|
* the matching short name |
|
*/ |
|
|
|
for (;;) |
|
{ |
|
/* Read the next sector into memory */ |
|
|
|
ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* Get a pointer to the directory entry */ |
|
|
|
diroffset = DIRSEC_BYTENDX(fs, dirinfo->dir.fd_index); |
|
direntry = &fs->fs_buffer[diroffset]; |
|
|
|
/* Check if we are at the end of the directory */ |
|
|
|
if (direntry[DIR_NAME] == DIR0_ALLEMPTY) |
|
{ |
|
return -ENOENT; |
|
} |
|
|
|
/* Check if we have found the directory entry that we are looking for */ |
|
|
|
if (direntry[DIR_NAME] != DIR0_EMPTY && |
|
!(DIR_GETATTRIBUTES(direntry) & FATATTR_VOLUMEID) && |
|
!memcmp(&direntry[DIR_NAME], dirinfo->fd_name, DIR_MAXFNAME) ) |
|
{ |
|
/* Yes.. Return success */ |
|
|
|
dirinfo->fd_seq.ds_sector = fs->fs_currentsector; |
|
dirinfo->fd_seq.ds_offset = diroffset; |
|
#ifdef CONFIG_FAT_LFN |
|
dirinfo->fd_seq.ds_cluster = dirinfo->dir.fd_currcluster; |
|
dirinfo->fd_seq.ds_startsector = startsector; |
|
|
|
/* Position the last long file name directory entry at the same |
|
* position. |
|
*/ |
|
|
|
dirinfo->fd_seq.ds_lfnsector = dirinfo->fd_seq.ds_sector; |
|
dirinfo->fd_seq.ds_lfnoffset = dirinfo->fd_seq.ds_offset; |
|
dirinfo->fd_seq.ds_lfncluster = dirinfo->fd_seq.ds_cluster; |
|
#endif |
|
return OK; |
|
} |
|
|
|
/* No... get the next directory index and try again */ |
|
|
|
if (fat_nextdirentry(fs, &dirinfo->dir) != OK) |
|
{ |
|
return -ENOENT; |
|
} |
|
} |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_cmplfnchunk |
|
* |
|
* Desciption: There are 13 characters per LFN entry, broken up into three |
|
* chunks for characts 1-5, 6-11, and 12-13. This function will perform |
|
* the comparison of a single chunk. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static bool fat_cmplfnchunk(uint8_t *chunk, const uint8_t *substr, int nchunk) |
|
{ |
|
wchar_t wch; |
|
uint8_t ch; |
|
int i; |
|
|
|
/* Check bytes 1-nchunk */ |
|
|
|
for (i = 0; i < nchunk; i++) |
|
{ |
|
/* Get the next character from the name string (which might be the NUL |
|
* terminating character). |
|
*/ |
|
|
|
if (*substr == '\0') |
|
{ |
|
ch = '\0'; |
|
} |
|
else |
|
{ |
|
ch = *substr++; |
|
} |
|
|
|
/* Get the next unicode character from the chunk. We only handle |
|
* ASCII. For ASCII, the upper byte should be zero and the lower |
|
* should match the ASCII code. |
|
*/ |
|
|
|
wch = (wchar_t)fat_getuint16((uint8_t*)chunk); |
|
if ((wch & 0xff) != (wchar_t)ch) |
|
{ |
|
return false; |
|
} |
|
|
|
/* The characters match. If we just matched the NUL terminating |
|
* character, then the strings match and we are finished. |
|
*/ |
|
|
|
if (ch == '\0') |
|
{ |
|
return true; |
|
} |
|
|
|
/* Try the next character from the directory entry. */ |
|
|
|
chunk += sizeof(wchar_t); |
|
} |
|
|
|
/* All of the characters in the chunk match.. Return success */ |
|
|
|
return true; |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_cmplfname |
|
* |
|
* Desciption: Given an LFN directory entry, compare a substring of the name |
|
* to a portion in the directory entry. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static bool fat_cmplfname(const uint8_t *direntry, const uint8_t *substr) |
|
{ |
|
uint8_t *chunk; |
|
int len; |
|
bool match; |
|
|
|
/* How much of string do we have to compare? (including the NUL |
|
* terminator). |
|
*/ |
|
|
|
len = strlen((char*)substr) + 1; |
|
|
|
/* Check bytes 1-5 */ |
|
|
|
chunk = LDIR_PTRWCHAR1_5(direntry); |
|
match = fat_cmplfnchunk(chunk, substr, 5); |
|
if (match && len > 5) |
|
{ |
|
/* Check bytes 6-11 */ |
|
|
|
chunk = LDIR_PTRWCHAR6_11(direntry); |
|
match = fat_cmplfnchunk(chunk, &substr[5], 6); |
|
if (match && len > 11) |
|
{ |
|
/* Check bytes 12-13 */ |
|
|
|
chunk = LDIR_PTRWCHAR12_13(direntry); |
|
match = fat_cmplfnchunk(chunk, &substr[11], 2); |
|
} |
|
} |
|
|
|
return match; |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_findlfnentry |
|
* |
|
* Desciption: Find a sequence of long file name directory entries. |
|
* |
|
* NOTE: As a side effect, this function returns with the sector containing |
|
* the short file name directory entry in the cache. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static inline int fat_findlfnentry(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo) |
|
{ |
|
uint16_t diroffset; |
|
uint8_t *direntry; |
|
uint8_t lastseq; |
|
uint8_t seqno; |
|
uint8_t nfullentries; |
|
uint8_t nentries; |
|
uint8_t remainder; |
|
uint8_t checksum = 0; |
|
off_t startsector; |
|
int offset; |
|
int namelen; |
|
int ret; |
|
|
|
/* Get the length of the long file name (size of the fd_lfname array is |
|
* LDIR_MAXFNAME+1 we do not have to check the length of the string). |
|
*/ |
|
|
|
namelen = strlen((char*)dirinfo->fd_lfname); |
|
DEBUGASSERT(namelen <= LDIR_MAXFNAME+1); |
|
|
|
/* How many LFN directory entries are we expecting? */ |
|
|
|
nfullentries = namelen / LDIR_MAXLFNCHARS; |
|
remainder = namelen - nfullentries * LDIR_MAXLFNCHARS; |
|
nentries = nfullentries; |
|
if (remainder > 0) |
|
{ |
|
nentries++; |
|
} |
|
DEBUGASSERT(nentries > 0 && nentries <= LDIR_MAXLFNS); |
|
|
|
/* This is the first sequency number we are looking for, the sequence |
|
* number of the last LFN entry (remember that they appear in reverse |
|
* order.. from last to first). |
|
*/ |
|
|
|
lastseq = LDIR0_LAST | nentries; |
|
seqno = lastseq; |
|
|
|
/* Save the starting sector of the directory. This is needed later to |
|
* re-scan the directory, looking duplicate short alias names. |
|
*/ |
|
|
|
startsector = dirinfo->dir.fd_currsector; |
|
|
|
/* Search, beginning with the current sector, for a directory entry this |
|
* the match shore name |
|
*/ |
|
|
|
for (;;) |
|
{ |
|
/* Read the next sector into memory */ |
|
|
|
ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* Get a pointer to the directory entry */ |
|
|
|
diroffset = DIRSEC_BYTENDX(fs, dirinfo->dir.fd_index); |
|
direntry = &fs->fs_buffer[diroffset]; |
|
|
|
/* Check if we are at the end of the directory */ |
|
|
|
if (direntry[DIR_NAME] == DIR0_ALLEMPTY) |
|
{ |
|
return -ENOENT; |
|
} |
|
|
|
/* Is this an LFN entry? Does it have the sequence number we are |
|
* looking for? |
|
*/ |
|
|
|
if (LDIR_GETATTRIBUTES(direntry) != LDDIR_LFNATTR || |
|
LDIR_GETSEQ(direntry) != seqno) |
|
{ |
|
/* No, restart the search at the next entry */ |
|
|
|
seqno = lastseq; |
|
goto next_entry; |
|
} |
|
|
|
/* Yes.. If this is not the "last" LFN entry, then the checksum must |
|
* also be the same. |
|
*/ |
|
|
|
if (seqno == lastseq) |
|
{ |
|
/* Just save the checksum for subsequent checks */ |
|
|
|
checksum = LDIR_GETCHECKSUM(direntry); |
|
} |
|
|
|
/* Not the first entry in the sequence. Does the checksum match the |
|
* previous sequences? |
|
*/ |
|
|
|
else if (checksum != LDIR_GETCHECKSUM(direntry)) |
|
{ |
|
/* No, restart the search at the next entry */ |
|
|
|
seqno = lastseq; |
|
goto next_entry; |
|
} |
|
|
|
/* Check if the name substring in this LFN matches the corresponding |
|
* substring of the name we are looking for. |
|
*/ |
|
|
|
offset = ((seqno & LDIR0_SEQ_MASK) - 1) * LDIR_MAXLFNCHARS; |
|
if (fat_cmplfname(direntry, &dirinfo->fd_lfname[offset])) |
|
{ |
|
/* Yes.. it matches. Check the sequence number. Is this the |
|
* "last" LFN entry (i.e., the one that appears first)? |
|
*/ |
|
|
|
if (seqno == lastseq) |
|
{ |
|
/* Yes.. Save information about this LFN entry position */ |
|
|
|
dirinfo->fd_seq.ds_lfnsector = fs->fs_currentsector; |
|
dirinfo->fd_seq.ds_lfnoffset = diroffset; |
|
dirinfo->fd_seq.ds_lfncluster = dirinfo->dir.fd_currcluster; |
|
dirinfo->fd_seq.ds_startsector = startsector; |
|
seqno &= LDIR0_SEQ_MASK; |
|
} |
|
|
|
/* Is this the first sequence number (i.e., the LFN entry that |
|
* will appear last)? |
|
*/ |
|
|
|
if (seqno == 1) |
|
{ |
|
/* We have found all of the LFN entries. The next directory |
|
* entry should be the one containing the short file name |
|
* alias and all of the meat about the file or directory. |
|
*/ |
|
|
|
if (fat_nextdirentry(fs, &dirinfo->dir) != OK) |
|
{ |
|
return -ENOENT; |
|
} |
|
|
|
/* Make sure that the directory entry is in the sector cache */ |
|
|
|
ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* Get a pointer to the directory entry */ |
|
|
|
diroffset = DIRSEC_BYTENDX(fs, dirinfo->dir.fd_index); |
|
direntry = &fs->fs_buffer[diroffset]; |
|
|
|
/* Verify the checksum */ |
|
|
|
if (fat_lfnchecksum(&direntry[DIR_NAME]) == checksum) |
|
{ |
|
/* Success! Save the position of the directory entry and |
|
* return success. |
|
*/ |
|
|
|
dirinfo->fd_seq.ds_sector = fs->fs_currentsector; |
|
dirinfo->fd_seq.ds_offset = diroffset; |
|
dirinfo->fd_seq.ds_cluster = dirinfo->dir.fd_currcluster; |
|
return OK; |
|
} |
|
|
|
/* Bad news.. reset and continue with this entry (which is |
|
* probably not an LFN entry unless the file systen is |
|
* seriously corrupted. |
|
*/ |
|
|
|
seqno = lastseq; |
|
continue; |
|
} |
|
|
|
/* No.. there are more LFN entries to go. Decrement the sequence |
|
* number and check the next directory entry. |
|
*/ |
|
|
|
seqno--; |
|
} |
|
else |
|
{ |
|
/* No.. the names do not match. Restart the search at the next |
|
* entry. |
|
*/ |
|
|
|
seqno = lastseq; |
|
} |
|
|
|
/* Continue at the next directory entry */ |
|
|
|
next_entry: |
|
if (fat_nextdirentry(fs, &dirinfo->dir) != OK) |
|
{ |
|
return -ENOENT; |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_allocatesfnentry |
|
* |
|
* Desciption: Find a free directory entry for a short file name entry. |
|
* |
|
****************************************************************************/ |
|
|
|
static inline int fat_allocatesfnentry(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo) |
|
{ |
|
uint16_t diroffset; |
|
uint8_t *direntry; |
|
#ifdef CONFIG_FAT_LFN |
|
off_t startsector; |
|
#endif |
|
uint8_t ch; |
|
int ret; |
|
|
|
/* Save the sector number of the first sector of the directory. We don't |
|
* really need this for short file name entries; this is just done for |
|
* consistency with the long file name logic. |
|
*/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
startsector = dirinfo->dir.fd_currsector; |
|
#endif |
|
|
|
/* Then search for a free short file name directory entry */ |
|
|
|
for (;;) |
|
{ |
|
/* Read the directory sector into fs_buffer */ |
|
|
|
ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector); |
|
if (ret < 0) |
|
{ |
|
/* Make sure that the return value is NOT -ENOSPC */ |
|
|
|
return -EIO; |
|
} |
|
|
|
/* Get a pointer to the entry at fd_index */ |
|
|
|
diroffset = (dirinfo->dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE; |
|
direntry = &fs->fs_buffer[diroffset]; |
|
|
|
/* Check if this directory entry is empty */ |
|
|
|
ch = direntry[DIR_NAME]; |
|
if (ch == DIR0_ALLEMPTY || ch == DIR0_EMPTY) |
|
{ |
|
/* It is empty -- we have found a directory entry */ |
|
|
|
dirinfo->fd_seq.ds_sector = fs->fs_currentsector; |
|
dirinfo->fd_seq.ds_offset = diroffset; |
|
#ifdef CONFIG_FAT_LFN |
|
dirinfo->fd_seq.ds_cluster = dirinfo->dir.fd_currcluster; |
|
dirinfo->fd_seq.ds_startsector = startsector; |
|
|
|
/* Set the "last" long file name offset to the same entry */ |
|
|
|
dirinfo->fd_seq.ds_lfnsector = dirinfo->fd_seq.ds_sector; |
|
dirinfo->fd_seq.ds_lfnoffset = dirinfo->fd_seq.ds_offset; |
|
dirinfo->fd_seq.ds_lfncluster = dirinfo->fd_seq.ds_cluster; |
|
#endif |
|
return OK; |
|
} |
|
|
|
/* It is not empty try the next one */ |
|
|
|
ret = fat_nextdirentry(fs, &dirinfo->dir); |
|
if (ret < 0) |
|
{ |
|
/* This will return -ENOSPC if we have examined all of the |
|
* directory entries without finding a free entry. |
|
*/ |
|
|
|
return ret; |
|
} |
|
} |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_allocatelfnentry |
|
* |
|
* Desciption: Find a sequence of free directory entries for a several long |
|
* and one short file name entry. |
|
* |
|
* On entry, dirinfo.dir refers to the first interesting entry the directory. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static inline int fat_allocatelfnentry(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo) |
|
{ |
|
uint16_t diroffset; |
|
uint8_t *direntry; |
|
off_t startsector; |
|
uint8_t nentries; |
|
uint8_t remainder; |
|
uint8_t needed; |
|
uint8_t ch; |
|
int namelen; |
|
int ret; |
|
|
|
/* Get the length of the long file name (size of the fd_lfname array is |
|
* LDIR_MAXFNAME+1 we do not have to check the length of the string). |
|
*/ |
|
|
|
namelen = strlen((char *)dirinfo->fd_lfname); |
|
DEBUGASSERT(namelen <= LDIR_MAXFNAME+1); |
|
|
|
/* How many LFN directory entries are we expecting? */ |
|
|
|
nentries = namelen / LDIR_MAXLFNCHARS; |
|
remainder = namelen - nentries * LDIR_MAXLFNCHARS; |
|
if (remainder > 0) |
|
{ |
|
nentries++; |
|
} |
|
DEBUGASSERT(nentries > 0 && nentries <= LDIR_MAXLFNS); |
|
|
|
/* Plus another for short file name entry that follows the sequence of LFN |
|
* entries. |
|
*/ |
|
|
|
nentries++; |
|
|
|
/* Save the sector number of the first sector of the directory. We will |
|
* need this later for re-scanning the directory to verify that a FAT file |
|
* name is unique. |
|
*/ |
|
|
|
startsector = dirinfo->dir.fd_currsector; |
|
|
|
/* Now, search the directory looking for a sequence for free entries that |
|
* long. |
|
*/ |
|
|
|
needed = nentries; |
|
for (;;) |
|
{ |
|
/* Read the directory sector into fs_buffer */ |
|
|
|
ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector); |
|
if (ret < 0) |
|
{ |
|
/* Make sure that the return value is NOT -ENOSPC */ |
|
|
|
return -EIO; |
|
} |
|
|
|
/* Get a pointer to the entry at fd_index */ |
|
|
|
diroffset = (dirinfo->dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE; |
|
direntry = &fs->fs_buffer[diroffset]; |
|
|
|
/* Check if this directory entry is empty */ |
|
|
|
ch = LDIR_GETSEQ(direntry); |
|
if (ch == DIR0_ALLEMPTY || ch == DIR0_EMPTY) |
|
{ |
|
/* It is empty -- we have found a directory entry. Is this the |
|
* "last" LFN entry (i.e., the one that occurs first)? |
|
*/ |
|
|
|
if (needed == nentries) |
|
{ |
|
/* Yes.. remember the position of this entry */ |
|
|
|
dirinfo->fd_seq.ds_lfnsector = fs->fs_currentsector; |
|
dirinfo->fd_seq.ds_lfnoffset = diroffset; |
|
dirinfo->fd_seq.ds_lfncluster = dirinfo->dir.fd_currcluster; |
|
dirinfo->fd_seq.ds_startsector = startsector; |
|
} |
|
|
|
/* Is this last entry we need (i.e., the entry for the short |
|
* file name entry)? |
|
*/ |
|
|
|
if (needed <= 1) |
|
{ |
|
/* Yes.. remember the position of this entry and return |
|
* success. |
|
*/ |
|
|
|
dirinfo->fd_seq.ds_sector = fs->fs_currentsector; |
|
dirinfo->fd_seq.ds_offset = diroffset; |
|
dirinfo->fd_seq.ds_cluster = dirinfo->dir.fd_currcluster; |
|
return OK; |
|
} |
|
|
|
/* Otherwise, just decrement the number of directory entries |
|
* needed and continue looking. |
|
*/ |
|
|
|
needed--; |
|
} |
|
|
|
/* The directory entry is not available */ |
|
|
|
else |
|
{ |
|
/* Reset the search and continue looking */ |
|
|
|
needed = nentries; |
|
} |
|
|
|
/* Try the next directory entry */ |
|
|
|
ret = fat_nextdirentry(fs, &dirinfo->dir); |
|
if (ret < 0) |
|
{ |
|
/* This will return -ENOSPC if we have examined all of the |
|
* directory entries without finding a free entry. |
|
*/ |
|
|
|
return ret; |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_getsfname |
|
* |
|
* Desciption: Get the 8.3 filename from a directory entry. On entry, the |
|
* short file name entry is already in the cache. |
|
* |
|
****************************************************************************/ |
|
|
|
static inline int fat_getsfname(uint8_t *direntry, char *buffer, |
|
unsigned int buflen) |
|
{ |
|
#ifdef CONFIG_FAT_LCNAMES |
|
uint8_t ntflags; |
|
#endif |
|
int ch; |
|
int ndx; |
|
|
|
/* Check if we will be doing upper to lower case conversions */ |
|
|
|
#ifdef CONFIG_FAT_LCNAMES |
|
ntflags = DIR_GETNTRES(direntry); |
|
#endif |
|
|
|
/* Reserve a byte for the NUL terminator */ |
|
|
|
buflen--; |
|
|
|
/* Get the 8-byte filename */ |
|
|
|
for (ndx = 0; ndx < 8 && buflen > 0; ndx++) |
|
{ |
|
/* Get the next filename character from the directory entry */ |
|
|
|
ch = direntry[ndx]; |
|
|
|
/* Any space (or ndx==8) terminates the filename */ |
|
|
|
if (ch == ' ') |
|
{ |
|
break; |
|
} |
|
|
|
/* In this version, we never write 0xe5 in the directory filenames |
|
* (because we do not handle any character sets where 0xe5 is valid |
|
* in a filaname), but we could encounted this in a filesystem |
|
* written by some other system |
|
*/ |
|
|
|
if (ndx == 0 && ch == DIR0_E5) |
|
{ |
|
ch = 0xe5; |
|
} |
|
|
|
/* Check if we should perform upper to lower case conversion |
|
* of the (whole) filename. |
|
*/ |
|
|
|
#ifdef CONFIG_FAT_LCNAMES |
|
if (ntflags & FATNTRES_LCNAME && isupper(ch)) |
|
{ |
|
ch = tolower(ch); |
|
} |
|
#endif |
|
/* Copy the next character into the filename */ |
|
|
|
*buffer++ = ch; |
|
buflen--; |
|
} |
|
|
|
/* Check if there is an extension */ |
|
|
|
if (direntry[8] != ' ' && buflen > 0) |
|
{ |
|
/* Yes, output the dot before the extension */ |
|
|
|
*buffer++ = '.'; |
|
buflen--; |
|
|
|
/* Then output the (up to) 3 character extension */ |
|
|
|
for (ndx = 8; ndx < 11 && buflen > 0; ndx++) |
|
{ |
|
/* Get the next extensions character from the directory entry */ |
|
|
|
ch = direntry[DIR_NAME + ndx]; |
|
|
|
/* Any space (or ndx==11) terminates the extension */ |
|
|
|
if (ch == ' ') |
|
{ |
|
break; |
|
} |
|
|
|
/* Check if we should perform upper to lower case conversion |
|
* of the (whole) filename. |
|
*/ |
|
|
|
#ifdef CONFIG_FAT_LCNAMES |
|
if (ntflags & FATNTRES_LCEXT && isupper(ch)) |
|
{ |
|
ch = tolower(ch); |
|
} |
|
#endif |
|
/* Copy the next character into the filename */ |
|
|
|
*buffer++ = ch; |
|
buflen--; |
|
} |
|
} |
|
|
|
/* Put a null terminator at the end of the filename. We don't have to |
|
* check if there is room because we reserved a byte for the NUL |
|
* terminator at the beginning of this function. |
|
*/ |
|
|
|
*buffer = '\0'; |
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_getlfnchunk |
|
* |
|
* Desciption: There are 13 characters per LFN entry, broken up into three |
|
* chunks for characts 1-5, 6-11, and 12-13. This function will get the |
|
* file name characters from one chunk. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static void fat_getlfnchunk(uint8_t *chunk, uint8_t *dest, int nchunk) |
|
{ |
|
wchar_t wch; |
|
int i; |
|
|
|
/* Copy bytes 1-nchunk */ |
|
|
|
for (i = 0; i < nchunk; i++) |
|
{ |
|
/* Get the next unicode character from the chunk. We only handle ASCII. |
|
* For ASCII, the upper byte should be zero and the lower should match |
|
* the ASCII code. |
|
*/ |
|
|
|
wch = (wchar_t)fat_getuint16(chunk); |
|
*dest++ = (uint8_t)(wch & 0xff); |
|
chunk += sizeof(wchar_t); |
|
} |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_getlfname |
|
* |
|
* Desciption: Get the long filename from a sequence of directory entries. |
|
* On entry, the "last" long file name entry is in the cache. Returns with |
|
* the short file name entry in the cache. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static inline int fat_getlfname(struct fat_mountpt_s *fs, struct fs_dirent_s *dir) |
|
{ |
|
uint8_t lfname[LDIR_MAXLFNCHARS]; |
|
uint16_t diroffset; |
|
uint8_t *direntry; |
|
uint8_t seqno; |
|
uint8_t rawseq; |
|
uint8_t offset; |
|
uint8_t checksum; |
|
int nsrc; |
|
int ret; |
|
int i; |
|
|
|
/* Get a reference to the current directory entry */ |
|
|
|
diroffset = (dir->u.fat.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE; |
|
direntry = &fs->fs_buffer[diroffset]; |
|
|
|
/* Get the starting sequence number */ |
|
|
|
seqno = LDIR_GETSEQ(direntry); |
|
DEBUGASSERT((seqno & LDIR0_LAST) != 0); |
|
|
|
/* Sanity check */ |
|
|
|
rawseq = (seqno & LDIR0_SEQ_MASK); |
|
if (rawseq < 1 || rawseq > LDIR_MAXLFNS) |
|
{ |
|
return -EINVAL; |
|
} |
|
|
|
/* Save the checksum value */ |
|
|
|
checksum = LDIR_GETCHECKSUM(direntry); |
|
|
|
/* Loop until the whole file name has been transferred */ |
|
|
|
for (;;) |
|
{ |
|
/* Get the string offset associated with the "last" entry. */ |
|
|
|
offset = (rawseq - 1) * LDIR_MAXLFNCHARS; |
|
|
|
/* Will any of this file name fit into the destination buffer? */ |
|
|
|
if (offset < NAME_MAX) |
|
{ |
|
/* Yes.. extract and convert the unicode name */ |
|
|
|
fat_getlfnchunk(LDIR_PTRWCHAR1_5(direntry), lfname, 5); |
|
fat_getlfnchunk(LDIR_PTRWCHAR6_11(direntry), &lfname[5], 6); |
|
fat_getlfnchunk(LDIR_PTRWCHAR12_13(direntry), &lfname[11], 2); |
|
|
|
/* Ignore trailing spaces on the "last" directory entry. The |
|
* number of characters avaiable is LDIR_MAXLFNCHARS or that |
|
* minus the number of trailing spaces on the "last" directory |
|
* entry. |
|
*/ |
|
|
|
nsrc = LDIR_MAXLFNCHARS; |
|
if ((seqno & LDIR0_LAST) != 0) |
|
{ |
|
/* Reduce the number of characters by the number of trailing |
|
* spaces. |
|
*/ |
|
|
|
for (; nsrc > 0 && lfname[nsrc-1] == ' '; nsrc--); |
|
|
|
/* Further reduce the length so that it fits in the destination |
|
* buffer. |
|
*/ |
|
|
|
if (offset + nsrc > NAME_MAX) |
|
{ |
|
nsrc = NAME_MAX - offset; |
|
} |
|
|
|
/* Add a null terminator to the destination string (the actual |
|
* length of the destination buffer is NAME_MAX+1, so the NUL |
|
* terminator will fit). |
|
*/ |
|
|
|
dir->fd_dir.d_name[offset+nsrc] = '\0'; |
|
} |
|
|
|
/* Then transfer the characters */ |
|
|
|
for (i = 0; i < nsrc && offset+i < NAME_MAX; i++) |
|
{ |
|
dir->fd_dir.d_name[offset+i] = lfname[i]; |
|
} |
|
} |
|
|
|
/* Read next directory entry */ |
|
|
|
if (fat_nextdirentry(fs, &dir->u.fat) != OK) |
|
{ |
|
return -ENOENT; |
|
} |
|
|
|
/* Make sure that the directory sector into the sector cache */ |
|
|
|
ret = fat_fscacheread(fs, dir->u.fat.fd_currsector); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* Get a reference to the current directory entry */ |
|
|
|
diroffset = (dir->u.fat.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE; |
|
direntry = &fs->fs_buffer[diroffset]; |
|
|
|
/* Get the next expected sequence number. */ |
|
|
|
seqno = --rawseq; |
|
if (seqno < 1) |
|
{ |
|
/* We just completed processing the "first" long file name entry |
|
* and we just read the short file name entry. Verify that the |
|
* checksum of the short file name matches the checksum that we |
|
* found in the long file name entries. |
|
*/ |
|
|
|
if (fat_lfnchecksum(direntry) == checksum) |
|
{ |
|
/* Yes.. return success! */ |
|
|
|
return OK; |
|
} |
|
|
|
/* No, the checksum is bad. */ |
|
|
|
return -EINVAL; |
|
} |
|
|
|
/* Verify the next long file name entry. Is this an LFN entry? Does it |
|
* have the sequence number we are looking for? Does the checksum |
|
* match the previous entries? |
|
*/ |
|
|
|
if (LDIR_GETATTRIBUTES(direntry) != LDDIR_LFNATTR || |
|
LDIR_GETSEQ(direntry) != seqno || |
|
LDIR_GETCHECKSUM(direntry) != checksum) |
|
{ |
|
return -EINVAL; |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_putsfname |
|
* |
|
* Desciption: Write the short directory entry name. |
|
* |
|
* Assumption: The directory sector is in the cache. |
|
* |
|
****************************************************************************/ |
|
|
|
static int fat_putsfname(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) |
|
{ |
|
uint8_t *direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset]; |
|
|
|
/* Write the short directory entry */ |
|
|
|
memcpy(&direntry[DIR_NAME], dirinfo->fd_name, DIR_MAXFNAME); |
|
#ifdef CONFIG_FAT_LCNAMES |
|
DIR_PUTNTRES(direntry, dirinfo->fd_ntflags); |
|
#else |
|
DIR_PUTNTRES(direntry, 0); |
|
#endif |
|
fs->fs_dirty = true; |
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_initlfname |
|
* |
|
* Desciption: There are 13 characters per LFN entry, broken up into three |
|
* chunks for characts 1-5, 6-11, and 12-13. This function will put the |
|
* 0xffff characters into one chunk. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static void fat_initlfname(uint8_t *chunk, int nchunk) |
|
{ |
|
int i; |
|
|
|
/* Initialize unicode characters 1-nchunk */ |
|
|
|
for (i = 0; i < nchunk; i++) |
|
{ |
|
/* The write the 16-bit 0xffff character into the directory entry. */ |
|
|
|
fat_putuint16((uint8_t *)chunk, (uint16_t)0xffff); |
|
chunk += sizeof(wchar_t); |
|
} |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_putlfnchunk |
|
* |
|
* Desciption: There are 13 characters per LFN entry, broken up into three |
|
* chunks for characts 1-5, 6-11, and 12-13. This function will put the |
|
* file name characters into one chunk. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static void fat_putlfnchunk(uint8_t *chunk, const uint8_t *src, int nchunk) |
|
{ |
|
uint16_t wch; |
|
int i; |
|
|
|
/* Write bytes 1-nchunk */ |
|
|
|
for (i = 0; i < nchunk; i++) |
|
{ |
|
/* Get the next ascii character from the name substring and convert it |
|
* to unicode. The upper byte should be zero and the lower should be |
|
* the ASCII code. The write the unicode character into the directory |
|
* entry. |
|
*/ |
|
|
|
wch = (uint16_t)*src++; |
|
fat_putuint16(chunk, wch); |
|
chunk += sizeof(wchar_t); |
|
} |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_putlfname |
|
* |
|
* Desciption: Write the long filename into a sequence of directory entries. |
|
* On entry, the "last" long file name entry is in the cache. Returns with |
|
* the short file name entry in the cache. |
|
* |
|
****************************************************************************/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
static int fat_putlfname(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) |
|
{ |
|
uint16_t diroffset; |
|
uint8_t *direntry; |
|
uint8_t nfullentries; |
|
uint8_t nentries; |
|
uint8_t remainder; |
|
uint8_t offset; |
|
uint8_t seqno; |
|
uint8_t checksum; |
|
int namelen; |
|
int ret; |
|
|
|
/* Get the length of the long file name (size of the fd_lfname array is |
|
* LDIR_MAXFNAME+1 we do not have to check the length of the string). |
|
* NOTE that remainder is conditionally incremented to include the NUL |
|
* terminating character that may also need be written to the directory |
|
* entry. NUL terminating is not required if length is multiple of |
|
* LDIR_MAXLFNCHARS (13). |
|
*/ |
|
|
|
namelen = strlen((char*)dirinfo->fd_lfname); |
|
DEBUGASSERT(namelen <= LDIR_MAXFNAME+1); |
|
|
|
/* How many LFN directory entries do we need to write? */ |
|
|
|
nfullentries = namelen / LDIR_MAXLFNCHARS; |
|
remainder = namelen - nfullentries * LDIR_MAXLFNCHARS; |
|
nentries = nfullentries; |
|
if (remainder > 0) |
|
{ |
|
nentries++; |
|
remainder++; |
|
} |
|
DEBUGASSERT(nentries > 0 && nentries <= LDIR_MAXLFNS); |
|
|
|
/* Create the short file name alias */ |
|
|
|
ret = fat_createalias(dirinfo); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* Set up the initial positional data */ |
|
|
|
dirinfo->dir.fd_currcluster = dirinfo->fd_seq.ds_lfncluster; |
|
dirinfo->dir.fd_currsector = dirinfo->fd_seq.ds_lfnsector; |
|
dirinfo->dir.fd_index = dirinfo->fd_seq.ds_lfnoffset / DIR_SIZE; |
|
|
|
/* Make sure that the alias is unique in this directory*/ |
|
|
|
ret = fat_uniquealias(fs, dirinfo); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* Get the short file name checksum */ |
|
|
|
checksum = fat_lfnchecksum(dirinfo->fd_name); |
|
|
|
/* Setup the starting sequence number */ |
|
|
|
seqno = LDIR0_LAST | nentries; |
|
|
|
/* Make sure that the sector containing the "last" long file name entry |
|
* is in the sector cache (it probably is not). |
|
*/ |
|
|
|
ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* Now loop, writing each long file name entry */ |
|
|
|
for (;;) |
|
{ |
|
/* Get the string offset associated with the directory entry. */ |
|
|
|
offset = (nentries - 1) * LDIR_MAXLFNCHARS; |
|
|
|
/* Get a reference to the current directory entry */ |
|
|
|
diroffset = (dirinfo->dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE; |
|
direntry = &fs->fs_buffer[diroffset]; |
|
|
|
/* Is this the "last" LFN directory entry? */ |
|
|
|
if ((seqno & LDIR0_LAST) != 0 && remainder != 0) |
|
{ |
|
int nbytes; |
|
|
|
/* Initialize the "last" directory entry name to all 0xffff */ |
|
|
|
fat_initlfname(LDIR_PTRWCHAR1_5(direntry), 5); |
|
fat_initlfname(LDIR_PTRWCHAR6_11(direntry), 6); |
|
fat_initlfname(LDIR_PTRWCHAR12_13(direntry), 2); |
|
|
|
/* Store the tail portion of the long file name in directory entry */ |
|
|
|
nbytes = MIN(5, remainder); |
|
fat_putlfnchunk(LDIR_PTRWCHAR1_5(direntry), |
|
&dirinfo->fd_lfname[offset], nbytes); |
|
remainder -= nbytes; |
|
|
|
if (remainder > 0) |
|
{ |
|
nbytes = MIN(6, remainder); |
|
fat_putlfnchunk(LDIR_PTRWCHAR6_11(direntry), |
|
&dirinfo->fd_lfname[offset+5], nbytes); |
|
remainder -= nbytes; |
|
} |
|
|
|
if (remainder > 0) |
|
{ |
|
nbytes = MIN(2, remainder); |
|
fat_putlfnchunk(LDIR_PTRWCHAR12_13(direntry), |
|
&dirinfo->fd_lfname[offset+11], nbytes); |
|
remainder -= nbytes; |
|
} |
|
|
|
/* The remainder should now be zero */ |
|
|
|
DEBUGASSERT(remainder == 0); |
|
} |
|
else |
|
{ |
|
/* Store a portion long file name in this directory entry */ |
|
|
|
fat_putlfnchunk(LDIR_PTRWCHAR1_5(direntry), |
|
&dirinfo->fd_lfname[offset], 5); |
|
fat_putlfnchunk(LDIR_PTRWCHAR6_11(direntry), |
|
&dirinfo->fd_lfname[offset+5], 6); |
|
fat_putlfnchunk(LDIR_PTRWCHAR12_13(direntry), |
|
&dirinfo->fd_lfname[offset+11], 2); |
|
} |
|
|
|
/* Write the remaining directory entries */ |
|
|
|
LDIR_PUTSEQ(direntry, seqno); |
|
LDIR_PUTATTRIBUTES(direntry, LDDIR_LFNATTR); |
|
LDIR_PUTNTRES(direntry, 0); |
|
LDIR_PUTCHECKSUM(direntry, checksum); |
|
fs->fs_dirty = true; |
|
|
|
/* Read next directory entry */ |
|
|
|
if (fat_nextdirentry(fs, &dirinfo->dir) != OK) |
|
{ |
|
return -ENOENT; |
|
} |
|
|
|
/* Make sure that the sector containing the directory entry is in the |
|
* sector cache |
|
*/ |
|
|
|
ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* Decrement the number of entries and get the next sequence number. */ |
|
|
|
if (--nentries <= 0) |
|
{ |
|
/* We have written all of the long file name entries to the media |
|
* and we have the short file name entry in the cache. We can |
|
* just return success. |
|
*/ |
|
|
|
return OK; |
|
} |
|
|
|
/* The sequence number is just the number of entries left to be |
|
* written. |
|
*/ |
|
|
|
seqno = nentries; |
|
} |
|
} |
|
#endif |
|
|
|
/**************************************************************************** |
|
* Name: fat_putsfdirentry |
|
* |
|
* Desciption: Write a short file name directory entry |
|
* |
|
* Assumption: The directory sector is in the cache. The caller will write |
|
* sector information. |
|
* |
|
****************************************************************************/ |
|
|
|
static int fat_putsfdirentry(struct fat_mountpt_s *fs, |
|
struct fat_dirinfo_s *dirinfo, |
|
uint8_t attributes, uint32_t fattime) |
|
{ |
|
uint8_t *direntry; |
|
|
|
/* Initialize the 32-byte directory entry */ |
|
|
|
direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset]; |
|
memset(direntry, 0, DIR_SIZE); |
|
|
|
/* Directory name info */ |
|
|
|
(void)fat_putsfname(fs, dirinfo); |
|
|
|
/* Set the attribute attribute, write time, creation time */ |
|
|
|
DIR_PUTATTRIBUTES(direntry, attributes); |
|
|
|
/* Set the time information */ |
|
|
|
DIR_PUTWRTTIME(direntry, fattime & 0xffff); |
|
DIR_PUTCRTIME(direntry, fattime & 0xffff); |
|
DIR_PUTWRTDATE(direntry, fattime >> 16); |
|
DIR_PUTCRDATE(direntry, fattime >> 16); |
|
|
|
fs->fs_dirty = true; |
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* Public Functions |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Name: fat_finddirentry |
|
* |
|
* Desciption: Given a path to something that may or may not be in the file |
|
* system, return the description of the directory entry of the requested |
|
* item. |
|
* |
|
* NOTE: As a side effect, this function returns with the sector containing |
|
* the short file name directory entry in the cache. |
|
* |
|
****************************************************************************/ |
|
|
|
int fat_finddirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo, |
|
const char *path) |
|
{ |
|
off_t cluster; |
|
uint8_t *direntry; |
|
char terminator; |
|
int ret; |
|
|
|
/* Initialize to traverse the chain. Set it to the cluster of the root |
|
* directory |
|
*/ |
|
|
|
cluster = fs->fs_rootbase; |
|
if (fs->fs_type == FSTYPE_FAT32) |
|
{ |
|
/* For FAT32, the root directory is variable sized and is a cluster |
|
* chain like any other directory. fs_rootbase holds the first |
|
* cluster of the root directory. |
|
*/ |
|
|
|
dirinfo->dir.fd_startcluster = cluster; |
|
dirinfo->dir.fd_currcluster = cluster; |
|
dirinfo->dir.fd_currsector = fat_cluster2sector(fs, cluster); |
|
} |
|
else |
|
{ |
|
/* For FAT12/16, the first sector of the root directory is a sector |
|
* relative to the first sector of the fat volume. |
|
*/ |
|
|
|
dirinfo->dir.fd_startcluster = 0; |
|
dirinfo->dir.fd_currcluster = 0; |
|
dirinfo->dir.fd_currsector = cluster; |
|
} |
|
|
|
/* fd_index is the index into the current directory table. It is set to the |
|
* the first, entry in the root directory. |
|
*/ |
|
|
|
dirinfo->dir.fd_index = 0; |
|
|
|
/* If no path was provided, then the root directory must be exactly what |
|
* the caller is looking for. |
|
*/ |
|
|
|
if (*path == '\0') |
|
{ |
|
dirinfo->fd_root = true; |
|
return OK; |
|
} |
|
|
|
/* This is not the root directory */ |
|
|
|
dirinfo->fd_root = false; |
|
|
|
/* Now loop until the directory entry corresponding to the path is found */ |
|
|
|
for (;;) |
|
{ |
|
/* Convert the next the path segment name into the kind of name that |
|
* we would see in the directory entry. |
|
*/ |
|
|
|
ret = fat_path2dirname(&path, dirinfo, &terminator); |
|
if (ret < 0) |
|
{ |
|
/* ERROR: The filename contains invalid characters or is |
|
* too long. |
|
*/ |
|
|
|
return ret; |
|
} |
|
|
|
/* Is this a path segment a long or a short file. Was a long file |
|
* name parsed? |
|
*/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
if (dirinfo->fd_lfname[0] != '\0') |
|
{ |
|
/* Yes.. Search for the sequence of long file name directory |
|
* entries. NOTE: As a side effect, this function returns with |
|
* the sector containing the short file name directory entry |
|
* in the cache. |
|
*/ |
|
|
|
ret = fat_findlfnentry(fs, dirinfo); |
|
} |
|
else |
|
#endif |
|
{ |
|
/* No.. Search for the single short file name directory entry */ |
|
|
|
ret = fat_findsfnentry(fs, dirinfo); |
|
} |
|
|
|
/* Did we find the directory entries? */ |
|
|
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* If the terminator character in the path was the end of the string |
|
* then we have successfully found the directory entry that describes |
|
* the path. |
|
*/ |
|
|
|
if (!terminator) |
|
{ |
|
/* Return success meaning that the description the matching |
|
* directory entry is in dirinfo. |
|
*/ |
|
|
|
return OK; |
|
} |
|
|
|
/* No.. then we have found one of the intermediate directories on |
|
* the way to the final path target. In this case, make sure |
|
* the thing that we found is, indeed, a directory. |
|
*/ |
|
|
|
direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset]; |
|
if (!(DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY)) |
|
{ |
|
/* Ooops.. we found something else */ |
|
|
|
return -ENOTDIR; |
|
} |
|
|
|
/* Get the cluster number of this directory */ |
|
|
|
cluster = |
|
((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) | |
|
DIR_GETFSTCLUSTLO(direntry); |
|
|
|
/* Then restart scanning at the new directory, skipping over both the |
|
* '.' and '..' entries that exist in all directories EXCEPT the root |
|
* directory. |
|
*/ |
|
|
|
dirinfo->dir.fd_startcluster = cluster; |
|
dirinfo->dir.fd_currcluster = cluster; |
|
dirinfo->dir.fd_currsector = fat_cluster2sector(fs, cluster); |
|
dirinfo->dir.fd_index = 2; |
|
} |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_allocatedirentry |
|
* |
|
* Desciption: Find a free directory entry |
|
* |
|
****************************************************************************/ |
|
|
|
int fat_allocatedirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) |
|
{ |
|
int32_t cluster; |
|
off_t sector; |
|
int ret; |
|
int i; |
|
|
|
/* Re-initialize directory object */ |
|
|
|
cluster = dirinfo->dir.fd_startcluster; |
|
|
|
/* Loop until we successfully allocate the sequence of directory entries |
|
* or until to fail to extend the directory cluster chain. |
|
*/ |
|
|
|
for (;;) |
|
{ |
|
/* Can this cluster chain be extended */ |
|
|
|
if (cluster) |
|
{ |
|
/* Cluster chain can be extended */ |
|
|
|
dirinfo->dir.fd_currcluster = cluster; |
|
dirinfo->dir.fd_currsector = fat_cluster2sector(fs, cluster); |
|
} |
|
else |
|
{ |
|
/* Fixed size FAT12/16 root directory is at fixed offset/size */ |
|
|
|
dirinfo->dir.fd_currsector = fs->fs_rootbase; |
|
} |
|
|
|
/* Start at the first entry in the root directory. */ |
|
|
|
dirinfo->dir.fd_index = 0; |
|
|
|
/* Is this a path segment a long or a short file. Was a long file |
|
* name parsed? |
|
*/ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
if (dirinfo->fd_lfname[0] != '\0') |
|
{ |
|
/* Yes.. Allocate for the sequence of long file name directory |
|
* entries plus a short file name directory entry. |
|
*/ |
|
|
|
ret = fat_allocatelfnentry(fs, dirinfo); |
|
} |
|
|
|
/* No.. Allocate only a short file name directory entry */ |
|
|
|
else |
|
#endif |
|
{ |
|
ret = fat_allocatesfnentry(fs, dirinfo); |
|
} |
|
|
|
/* Did we successfully allocate the directory entries? If the error |
|
* value is -ENOSPC, then we can try to extend the directory cluster |
|
* (we can't handle other return values) |
|
*/ |
|
|
|
if (ret == OK || ret != -ENOSPC) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* If we get here, then we have reached the end of the directory table |
|
* in this sector without finding a free directory entry. |
|
* |
|
* It this is a fixed size directory entry, then this is an error. |
|
* Otherwise, we can try to extend the directory cluster chain to |
|
* make space for the new directory entry. |
|
*/ |
|
|
|
if (!cluster) |
|
{ |
|
/* The size is fixed */ |
|
|
|
return -ENOSPC; |
|
} |
|
|
|
/* Try to extend the cluster chain for this directory */ |
|
|
|
cluster = fat_extendchain(fs, dirinfo->dir.fd_currcluster); |
|
if (cluster < 0) |
|
{ |
|
return cluster; |
|
} |
|
|
|
/* Flush out any cached data in fs_buffer.. we are going to use |
|
* it to initialize the new directory cluster. |
|
*/ |
|
|
|
ret = fat_fscacheflush(fs); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* Clear all sectors comprising the new directory cluster */ |
|
|
|
fs->fs_currentsector = fat_cluster2sector(fs, cluster); |
|
memset(fs->fs_buffer, 0, fs->fs_hwsectorsize); |
|
|
|
sector = fs->fs_currentsector; |
|
for (i = fs->fs_fatsecperclus; i; i--) |
|
{ |
|
ret = fat_hwwrite(fs, fs->fs_buffer, sector, 1); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
sector++; |
|
} |
|
} |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_freedirentry |
|
* |
|
* Desciption: Free the directory entry. |
|
* |
|
* NOTE: As a side effect, this function returns with the sector containing |
|
* the deleted short file name directory entry in the cache. |
|
* |
|
****************************************************************************/ |
|
|
|
int fat_freedirentry(struct fat_mountpt_s *fs, struct fat_dirseq_s *seq) |
|
{ |
|
#ifdef CONFIG_FAT_LFN |
|
struct fs_fatdir_s dir; |
|
uint16_t diroffset; |
|
uint8_t *direntry; |
|
int ret; |
|
|
|
/* Set it to the cluster containing the "last" LFN entry (that appears |
|
* first on the media). |
|
*/ |
|
|
|
dir.fd_currcluster = seq->ds_lfncluster; |
|
dir.fd_currsector = seq->ds_lfnsector; |
|
dir.fd_index = seq->ds_lfnoffset / DIR_SIZE; |
|
|
|
/* Free all of the directory entries used for the sequence of long file name |
|
* and for the single short file name entry. |
|
*/ |
|
|
|
for (;;) |
|
{ |
|
/* Read the directory sector into the sector cache */ |
|
|
|
ret = fat_fscacheread(fs, dir.fd_currsector); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* Get a pointer to the directory entry */ |
|
|
|
diroffset = (dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE; |
|
direntry = &fs->fs_buffer[diroffset]; |
|
|
|
/* Then mark the entry as deleted */ |
|
|
|
direntry[DIR_NAME] = DIR0_EMPTY; |
|
fs->fs_dirty = true; |
|
|
|
/* Did we just free the single short file name entry? */ |
|
|
|
if (dir.fd_currsector == seq->ds_sector && |
|
diroffset == seq->ds_offset) |
|
{ |
|
/* Yes.. then we are finished. flush anything remaining in the |
|
* cache and return, probably successfully. |
|
*/ |
|
|
|
return fat_fscacheflush(fs); |
|
} |
|
|
|
/* There are more entries to go.. Try the next directory entry */ |
|
|
|
ret = fat_nextdirentry(fs, &dir); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
} |
|
|
|
#else |
|
uint8_t *direntry; |
|
int ret; |
|
|
|
/* Free the single short file name entry. |
|
* |
|
* Make sure that the sector containing the directory entry is in the |
|
* cache. |
|
*/ |
|
|
|
ret = fat_fscacheread(fs, seq->ds_sector); |
|
if (ret == OK) |
|
{ |
|
/* Then mark the entry as deleted */ |
|
|
|
direntry = &fs->fs_buffer[seq->ds_offset]; |
|
direntry[DIR_NAME] = DIR0_EMPTY; |
|
fs->fs_dirty = true; |
|
} |
|
|
|
return ret; |
|
#endif |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_dirname2path |
|
* |
|
* Desciption: Convert a filename in a raw directory entry into a user |
|
* filename. This is essentially the inverse operation of that performed |
|
* by fat_path2dirname. See that function for more details. |
|
* |
|
****************************************************************************/ |
|
|
|
int fat_dirname2path(struct fat_mountpt_s *fs, struct fs_dirent_s *dir) |
|
{ |
|
uint16_t diroffset; |
|
uint8_t *direntry; |
|
#ifdef CONFIG_FAT_LFN |
|
uint8_t attribute; |
|
#endif |
|
|
|
/* Get a reference to the current directory entry */ |
|
|
|
diroffset = (dir->u.fat.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE; |
|
direntry = &fs->fs_buffer[diroffset]; |
|
|
|
/* Does this entry refer to the last entry of a long file name? */ |
|
|
|
#ifdef CONFIG_FAT_LFN |
|
attribute = DIR_GETATTRIBUTES(direntry); |
|
if (((*direntry & LDIR0_LAST) != 0 && attribute == LDDIR_LFNATTR)) |
|
{ |
|
/* Yes.. Get the name from a sequence of long file name directory |
|
* entries. |
|
*/ |
|
|
|
return fat_getlfname(fs, dir); |
|
} |
|
else |
|
#endif |
|
{ |
|
/* No.. Get the name from a short file name directory entries */ |
|
|
|
return fat_getsfname(direntry, dir->fd_dir.d_name, NAME_MAX+1); |
|
} |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_dirnamewrite |
|
* |
|
* Desciption: Write the (possibly long) directory entry name. This function |
|
* is called only from fat_rename to write the new file name. |
|
* |
|
* Assumption: The directory sector containing the short file name entry |
|
* is in the cache. *NOT* the sector containing the last long file name |
|
* entry! |
|
* |
|
****************************************************************************/ |
|
|
|
int fat_dirnamewrite(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) |
|
{ |
|
#ifdef CONFIG_FAT_LFN |
|
int ret; |
|
|
|
/* Is this a long file name? */ |
|
|
|
if (dirinfo->fd_lfname[0] != '\0') |
|
{ |
|
/* Write the sequence of long file name directory entries (this function |
|
* also creates the short file name alias). |
|
*/ |
|
|
|
ret = fat_putlfname(fs, dirinfo); |
|
if (ret != OK) |
|
{ |
|
return ret; |
|
} |
|
} |
|
|
|
/* On return, fat_lfsfname() will leave the short file name entry in the |
|
* cache. So we can just fall throught to write that directory entry, perhaps |
|
* using the short file name alias for the long file name. |
|
*/ |
|
#endif |
|
|
|
return fat_putsfname(fs, dirinfo); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_dirwrite |
|
* |
|
* Desciption: Write a directory entry, possibly with a long file name. |
|
* Called from: |
|
* |
|
* fat_mkdir() to write the new FAT directory entry. |
|
* fat_dircreate() to create any new directory entry. |
|
* |
|
* Assumption: The directory sector is in the cache. The caller will write |
|
* sector information. |
|
* |
|
****************************************************************************/ |
|
|
|
int fat_dirwrite(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo, |
|
uint8_t attributes, uint32_t fattime) |
|
{ |
|
#ifdef CONFIG_FAT_LFN |
|
int ret; |
|
|
|
/* Does this directory entry have a long file name? */ |
|
|
|
if (dirinfo->fd_lfname[0] != '\0') |
|
{ |
|
/* Write the sequence of long file name directory entries (this function |
|
* also creates the short file name alias). |
|
*/ |
|
|
|
ret = fat_putlfname(fs, dirinfo); |
|
if (ret != OK) |
|
{ |
|
return ret; |
|
} |
|
} |
|
|
|
/* On return, fat_lfsfname() will leave the short file name entry in the |
|
* cache. So we can just fall throught to write that directory entry, perhaps |
|
* using the short file name alias for the long file name. |
|
*/ |
|
#endif |
|
|
|
/* Put the short file name entry data */ |
|
|
|
return fat_putsfdirentry(fs, dirinfo, attributes, fattime); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_dircreate |
|
* |
|
* Desciption: Create a directory entry for a new file |
|
* |
|
****************************************************************************/ |
|
|
|
int fat_dircreate(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) |
|
{ |
|
uint32_t fattime; |
|
int ret; |
|
|
|
/* Allocate a directory entry. If long file name support is enabled, then |
|
* this might, in fact, allocate a sequence of directory entries. |
|
*/ |
|
|
|
ret = fat_allocatedirentry(fs, dirinfo); |
|
if (ret != OK) |
|
{ |
|
/* Failed to allocate the required directory entry or entries. */ |
|
|
|
return ret; |
|
} |
|
|
|
/* Write the directory entry (or entries) with the current time and the |
|
* ARCHIVE attribute. |
|
*/ |
|
|
|
fattime = fat_systime2fattime(); |
|
return fat_dirwrite(fs, dirinfo, FATATTR_ARCHIVE, fattime); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fat_remove |
|
* |
|
* Desciption: Remove a directory or file from the file system. This |
|
* implements both rmdir() and unlink(). |
|
* |
|
****************************************************************************/ |
|
|
|
int fat_remove(struct fat_mountpt_s *fs, const char *relpath, bool directory) |
|
{ |
|
struct fat_dirinfo_s dirinfo; |
|
uint32_t dircluster; |
|
uint8_t *direntry; |
|
int ret; |
|
|
|
/* Find the directory entry referring to the entry to be deleted */ |
|
|
|
ret = fat_finddirentry(fs, &dirinfo, relpath); |
|
if (ret != OK) |
|
{ |
|
/* No such path */ |
|
|
|
return -ENOENT; |
|
} |
|
|
|
/* Check if this is a FAT12/16 root directory */ |
|
|
|
if (dirinfo.fd_root) |
|
{ |
|
/* The root directory cannot be removed */ |
|
|
|
return -EPERM; |
|
} |
|
|
|
/* The object has to have write access to be deleted */ |
|
|
|
direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset]; |
|
if ((DIR_GETATTRIBUTES(direntry) & FATATTR_READONLY) != 0) |
|
{ |
|
/* It is a read-only entry */ |
|
|
|
return -EACCES; |
|
} |
|
|
|
/* Get the directory sector and cluster containing the entry to be deleted. */ |
|
|
|
dircluster = |
|
((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) | |
|
DIR_GETFSTCLUSTLO(direntry); |
|
|
|
/* Is this entry a directory? */ |
|
|
|
if (DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY) |
|
{ |
|
/* It is a sub-directory. Check if we are be asked to remove |
|
* a directory or a file. |
|
*/ |
|
|
|
if (!directory) |
|
{ |
|
/* We are asked to delete a file */ |
|
|
|
return -EISDIR; |
|
} |
|
|
|
/* We are asked to delete a directory. Check if this sub-directory is |
|
* empty (i.e., that there are no valid entries other than the initial |
|
* '.' and '..' entries). |
|
*/ |
|
|
|
dirinfo.dir.fd_currcluster = dircluster; |
|
dirinfo.dir.fd_currsector = fat_cluster2sector(fs, dircluster); |
|
dirinfo.dir.fd_index = 2; |
|
|
|
/* Loop until either (1) an entry is found in the directory (error), |
|
* (2) the directory is found to be empty, or (3) some error occurs. |
|
*/ |
|
|
|
for (;;) |
|
{ |
|
unsigned int subdirindex; |
|
uint8_t *subdirentry; |
|
|
|
/* Make sure that the sector containing the of the subdirectory |
|
* sector is in the cache |
|
*/ |
|
|
|
ret = fat_fscacheread(fs, dirinfo.dir.fd_currsector); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* Get a reference to the next entry in the directory */ |
|
|
|
subdirindex = (dirinfo.dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE; |
|
subdirentry = &fs->fs_buffer[subdirindex]; |
|
|
|
/* Is this the last entry in the direcory? */ |
|
|
|
if (subdirentry[DIR_NAME] == DIR0_ALLEMPTY) |
|
{ |
|
/* Yes then the directory is empty. Break out of the |
|
* loop and delete the directory. |
|
*/ |
|
|
|
break; |
|
} |
|
|
|
/* Check if the next entry refers to a file or directory */ |
|
|
|
if (subdirentry[DIR_NAME] != DIR0_EMPTY && |
|
!(DIR_GETATTRIBUTES(subdirentry) & FATATTR_VOLUMEID)) |
|
{ |
|
/* The directory is not empty */ |
|
|
|
return -ENOTEMPTY; |
|
} |
|
|
|
/* Get the next directory entry */ |
|
|
|
ret = fat_nextdirentry(fs, &dirinfo.dir); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
/* It is a file. Check if we are be asked to remove a directory |
|
* or a file. |
|
*/ |
|
|
|
if (directory) |
|
{ |
|
/* We are asked to remove a directory */ |
|
|
|
return -ENOTDIR; |
|
} |
|
} |
|
|
|
/* Mark the directory entry 'deleted'. If long file name support is |
|
* enabled, then multiple directory entries may be freed. |
|
*/ |
|
|
|
ret = fat_freedirentry(fs, &dirinfo.fd_seq); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* And remove the cluster chain making up the subdirectory */ |
|
|
|
ret = fat_removechain(fs, dircluster); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
/* Update the FSINFO sector (FAT32) */ |
|
|
|
ret = fat_updatefsinfo(fs); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
return OK; |
|
}
|
|
|