From 7aabcf55a461014d7a81136c51e96c2c62faffd3 Mon Sep 17 00:00:00 2001 From: patacongo Date: Wed, 13 Jul 2011 17:19:31 +0000 Subject: Add long file name parsing logic git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@3781 42af7a65-404d-4744-a932-0658087f49c3 --- nuttx/Documentation/NuttxPortingGuide.html | 15 +- nuttx/configs/README.txt | 11 + nuttx/fs/fat/fs_fat32.h | 4 +- nuttx/fs/fat/fs_fat32dirent.c | 692 ++++++++++++++++++++++++----- 4 files changed, 615 insertions(+), 107 deletions(-) diff --git a/nuttx/Documentation/NuttxPortingGuide.html b/nuttx/Documentation/NuttxPortingGuide.html index 4e5991001..8d6a3544c 100644 --- a/nuttx/Documentation/NuttxPortingGuide.html +++ b/nuttx/Documentation/NuttxPortingGuide.html @@ -12,7 +12,7 @@

NuttX RTOS Porting Guide

-

Last Updated: July 12, 2011

+

Last Updated: July 13, 2011

@@ -3763,6 +3763,19 @@ build
  • 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.
  • diff --git a/nuttx/configs/README.txt b/nuttx/configs/README.txt index a2c012931..f505685f2 100644 --- a/nuttx/configs/README.txt +++ b/nuttx/configs/README.txt @@ -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. diff --git a/nuttx/fs/fat/fs_fat32.h b/nuttx/fs/fat/fs_fat32.h index 3d3db644c..5c10de000 100644 --- a/nuttx/fs/fat/fs_fat32.h +++ b/nuttx/fs/fat/fs_fat32.h @@ -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 */ diff --git a/nuttx/fs/fat/fs_fat32dirent.c b/nuttx/fs/fat/fs_fat32dirent.c index f98d75a9d..8703ae47a 100644 --- a/nuttx/fs/fat/fs_fat32dirent.c +++ b/nuttx/fs/fat/fs_fat32dirent.c @@ -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 + + /* 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) { - /* Clear lower case extension in mask */ - ntlcenable &= FATNTRES_LCNAME; + /* Mixed case in the extension -- use the long file name */ + + goto errout; } - } + + /* So far, only upper case in the extension*/ + + extcase = FATCASE_UPPER; #endif - /* Check for lower case characters */ + /* Clear lower case extension in mask */ - else if (islower(ch)) - { - /* Convert the character to upper case */ + ntlcenable &= FATNTRES_LCNAME; + } + } +#endif - ch = toupper(ch); + /* Check for lower case characters */ - /* 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. - */ + 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 -- cgit v1.2.3