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