Add long file name parsing logic

git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@3781 42af7a65-404d-4744-a932-0658087f49c3
This commit is contained in:
patacongo 2011-07-13 17:19:31 +00:00
parent 95e415d274
commit fb32eed2ee
4 changed files with 617 additions and 109 deletions

View File

@ -12,7 +12,7 @@
<h1><big><font color="#3c34ec">
<i>NuttX RTOS Porting Guide</i>
</font></big></h1>
<p>Last Updated: July 12, 2011</p>
<p>Last Updated: July 13, 2011</p>
</td>
</tr>
</table>
@ -3763,6 +3763,19 @@ build
<li>
<code>CONFIG_FAT_SECTORSIZE</code>: Max supported sector size.
</li>
<li>
<code>CONFIG_FAT_LCNAME</code>: Enable use of the NT-style upper/lower case 8.3 file name support.
</li>
<li>
<code>CONFIG_FAT_LFN</code>: Enable FAT long file names.
NOTE: Microsoft claims patents on FAT long file name technology.
Please read the disclaimer in the top-level COPYING file and only enable this feature if you understand these issues.
</li>
<li>
<code>CONFIG_FAT_MAXFNAME</code>: If <code>CONFIG_FAT_LFN</code> is defined, then the default, maximum long file name is 255 bytes.
This can eat up a lot of memory (especially stack space).
If you are willing to live with some non-standard, short long file names, then define this value.
</li>
<li>
<code>CONFIG_FS_NXFFS</code>: Enable NuttX FLASH file system (NXFF) support.
</li>

View File

@ -564,6 +564,17 @@ defconfig -- This is a configuration file similar to the Linux
Filesystem configuration
CONFIG_FS_FAT - Enable FAT filesystem support
CONFIG_FAT_SECTORSIZE - Max supported sector size
CONFIG_FAT_LCNAME - Enable use of the NT-style upper/lower case 8.3
file name support.
CONFIG_FAT_LFN - Enable FAT long file names. NOTE: Microsoft claims
patents on FAT long file name technology. Please read the
disclaimer in the top-level COPYING file and only enable this
feature if you understand these issues.
CONFIG_FAT_MAXFNAME - If CONFIG_FAT_LFN is defined, then the
default, maximum long file name is 255 bytes. This can eat up
a lot of memory (especially stack space). If you are willing
to live with some non-standard, short long file names, then
define this value.
CONFIG_FS_NXFFS: Enable NuttX FLASH file system (NXFF) support.
CONFIG_NXFFS_ERASEDSTATE: The erased state of FLASH.
This must have one of the values of 0xff or 0x00.

View File

@ -732,9 +732,9 @@ struct fat_dirinfo_s
/* The file/directory name */
#ifdef CONFIG_FAT_LFN
uint8_t fd_lfname[LDIR_MAXFNAME]; /* Long filename */
uint8_t fd_lfname[LDIR_MAXFNAME+1]; /* Long filename with terminator */
#endif
uint8_t fd_name[DIR_MAXFNAME]; /* Short 8.3 alias filename */
uint8_t fd_name[DIR_MAXFNAME]; /* Short 8.3 alias filename (no terminator) */
/* NT flags are not used */

View File

@ -94,6 +94,13 @@
* Private Types
****************************************************************************/
enum fat_case_e
{
FATCASE_UNKNOWN = 0,
FATCASE_UPPER,
FATCASE_LOWER
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
@ -111,19 +118,21 @@
****************************************************************************/
/****************************************************************************
* Name: fat_path2dirname
* Name: fat_parsesfname
*
* Desciption: Convert a user filename into a properly formatted FAT
* (short) filname as it would appear in a directory entry. Here are the
* rules for the 11 byte name in the directory:
* (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 ' '
*
* Any bytes
* 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 = '*', '+', ','
@ -132,152 +141,627 @@
* 0x5b-0x5d = '[', '\\', ;]'
* 0x7c = '|'
*
* Upper case characters are not allowed in directory names (without some
* poorly documented operatgions on the NTRes directory byte). Lower case
* '.' 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_path2dirname(const char **path, struct fat_dirinfo_s *dirinfo,
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;
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
const char *node = *path;
int endndx;
uint8_t ch;
int ndx = 0;
#endif
const char *node = *path;
int endndx;
uint8_t ch;
int ndx = 0;
/* Initialized the name with all spaces */
/* Initialized the name with all spaces */
memset(dirinfo->fd_name, ' ', DIR_MAXFNAME);
memset(dirinfo->fd_name, ' ', DIR_MAXFNAME);
/* Loop until the name is successfully parsed or an error occurs */
/* Loop until the name is successfully parsed or an error occurs */
endndx = 8;
for (;;)
{
/* Get the next byte from the path */
endndx = 8;
for (;;)
{
/* Get the next byte from the path */
ch = *node++;
ch = *node++;
/* Check if this the last byte in this node of the name */
/* 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 */
if ((ch == '\0' || ch == '/') && ndx != 0 )
{
/* Return the accumulated NT flags and the terminating character */
#ifdef CONFIG_FAT_LCNAMES
dirinfo->fd_ntflags = ntlcfound & ntlcenable;
dirinfo->fd_ntflags = ntlcfound & ntlcenable;
#endif
*terminator = ch;
*path = node;
return OK;
}
*terminator = ch;
*path = node;
return OK;
}
/* Accept only the printable character set. Note 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.
*/
/* Accept only the printable character set. Note 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;
}
else if (!isgraph(ch))
{
goto errout;
}
/* Check for transition from name to extension */
/* Check for transition from name to extension. Only one '.' is
* permitted and it must be within first 9 characters
*/
else if (ch == '.')
{
/* Starting the extension */
else if (ch == '.' && endndx == 8)
{
/* Starting the extension */
ndx = 8;
endndx = 11;
continue;
}
ndx = 8;
endndx = 11;
continue;
}
/* Reject printable characters forbidden by FAT */
/* Reject printable characters forbidden by FAT */
else if (ch == '"' || (ch >= '*' && ch <= ',') ||
ch == '.' || ch == '/' ||
(ch >= ':' && ch <= '?') ||
(ch >= '[' && ch <= ']') ||
(ch == '|'))
{
goto errout;
}
else if (ch == '"' || (ch >= '*' && ch <= ',') ||
ch == '.' || ch == '/' ||
(ch >= ':' && ch <= '?') ||
(ch >= '[' && ch <= ']') ||
(ch == '|'))
{
goto errout;
}
/* Check for upper case charaters */
/* 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.
*/
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)
if (endndx == 8)
{
/* Is there mixed case in the name? */
#ifdef CONFIG_FAT_LFN
if (namecase == FATCASE_LOWER)
{
/* Clear lower case name bit in mask*/
ntlcenable &= FATNTRES_LCNAME;
/* Mixed case in the name -- use the long file name */
goto errout;
}
else
{
/* Clear lower case extension in mask */
ntlcenable &= FATNTRES_LCNAME;
}
}
/* So far, only upper case in the name*/
namecase = FATCASE_UPPER;
#endif
/* Check for lower case characters */
/* Clear lower case name bit in mask*/
else if (islower(ch))
{
/* Convert the character to upper case */
ntlcenable &= FATNTRES_LCNAME;
}
else
{
/* Is there mixed case in the extension? */
ch = toupper(ch);
#ifdef CONFIG_FAT_LFN
if (extcase == FATCASE_LOWER)
{
/* Mixed case in the extension -- use the long file name */
/* 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.
*/
goto errout;
}
/* So far, only upper case in the extension*/
extcase = FATCASE_UPPER;
#endif
/* Clear lower case extension in mask */
ntlcenable &= FATNTRES_LCNAME;
}
}
#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)
{
/* Set lower case name bit */
ntlcfound |= FATNTRES_LCNAME;
}
else
{
/* Set lower case extension bit */
ntlcfound |= FATNTRES_LCNAME;
}
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
}
/* Check if the file name exceeds the size permitted (without
* long file name support
*/
/* Set lower case name bit */
if (ndx >= endndx)
{
goto errout;
}
ntlcfound |= FATNTRES_LCNAME;
}
else
{
/* Is there mixed case in the extension? */
/* Save next character in the accumulated name */
#ifdef CONFIG_FAT_LFN
if (extcase == FATCASE_UPPER)
{
/* Mixed case in the extension -- use the long file name */
dirinfo->fd_name[ndx++] = ch;
}
goto errout;
}
/* So far, only lower case in the extension*/
extcase = FATCASE_LOWER;
#endif
/* Set lower case extension bit */
ntlcfound |= FATNTRES_LCNAME;
}
#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 */
else if (!isgraph(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.
*
* Returned value:
* OK - The alias was created correctly.
* <0 - Otherwise an negated error is returned.
*
****************************************************************************/
#ifdef CONFIG_FAT_LFN
static inline int fat_createalias(const char **path,
struct fat_dirinfo_s *dirinfo)
{
uint8_t ch; /* Current character being processed */
char *ext; /* Pointer to the extension substring */
char *ptr; /* Working pointer */
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(dirinfo.fd_lfname);
ext = strrchr(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 - 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);
ptr = ext;
ext = NULL;
namechar = extchars;
extchars = 0;
}
else
{
ptr = dirinfo.fd_ldname;
}
/* 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 = *ptr++;
if (ch == '\0')
{
break;
}
/* 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? Will 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;
ptr = 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_uniquealias
*
* Desciption: Make sure that the short alias for the long file name is
* unique. Modify the alias as necessary to assure uniqueness.
*
* Returned value:
* OK - The alias is unique.
* <0 - Otherwise an negated error is returned.
*
****************************************************************************/
#ifdef CONFIG_FAT_LFN
static inline int fat_uniquealias(const char **path,
struct fat_dirinfo_s *dirinfo)
{
#warning "Missing alias alias uniqueness logic"
return OK;
}
#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);
if (ret < 0)
{
/* Not a valid long file name */
return ret;
}
/* It is a valid long file name, create a quick short file name
* alias.
*/
ret = fat_createalias(path, dirinfo);
DEBUGASSERT(ret == OK); /* This should never fail */
/* Make sure that the alias is unique */
ret = fat_uniquealias(path, dirinfo);
}
return ret;
#else
/* Only short, 8+3 filenames supported */
return fat_parsesfname(path, dirinfo, terminator);
#endif
}
/****************************************************************************
* Name: fat_checkname
*
* Desciption: Given a path to something that may or may not be in the file
* system, return the directory entry of the item.
*
****************************************************************************/
/****************************************************************************
* Public Functions