/**************************************************************************** * fs/fat/fs_fat32dirent.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * NOTE: If CONFIG_FAT_LFN is defined, then there may be some legal, patent * issues. The following was extracted from the entry "File Allocation Table * from Wikipedia, the free encyclopedia: * * "On December 3, 2003 Microsoft announced it would be offering licenses * for use of its FAT specification and 'associated intellectual property', * at the cost of a US$0.25 royalty per unit sold, with a $250,000 maximum * royalty per license agreement. * * o "U.S. Patent 5,745,902 (http://www.google.com/patents?vid=5745902) - * Method and system for accessing a file using file names having * different file name formats. ... * o "U.S. Patent 5,579,517 (http://www.google.com/patents?vid=5579517) - * Common name space for long and short filenames. ... * o "U.S. Patent 5,758,352 (http://www.google.com/patents?vid=5758352) - * Common name space for long and short filenames. ... * o "U.S. Patent 6,286,013 (http://www.google.com/patents?vid=6286013) - * Method and system for providing a common name space for long and * short file names in an operating system. ... * * "Many technical commentators have concluded that these patents only cover * FAT implementations that include support for long filenames, and that * removable solid state media and consumer devices only using short names * would be unaffected. ..." * * So you have been forewarned: Use the long filename at your own risk! * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #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 *terminator = ch; *path = node; return OK; } /* Accept only the printable character set (excluding space). Note * that the first byte of the name could be 0x05 meaning that is it * 0xe5, but this is not a printable character in this character in * either case. */ else if (!isgraph(ch)) { goto errout; } /* Check for transition from name to extension. Only one '.' is * permitted and it must be within first 9 characters */ else if (ch == '.' && endndx == 8) { /* Starting the extension */ ndx = 8; endndx = 11; continue; } /* Reject printable characters forbidden by FAT */ else if (ch == '"' || (ch >= '*' && ch <= ',') || ch == '.' || ch == '/' || (ch >= ':' && ch <= '?') || (ch >= '[' && ch <= ']') || (ch == '|')) { goto errout; } /* Check for upper case characters */ #ifdef CONFIG_FAT_LCNAMES else if (isupper(ch)) { /* Some or all of the characters in the name or extension * are upper case. Force all of the characters to be interpreted * as upper case. */ if (endndx == 8) { /* Is there mixed case in the name? */ #ifdef CONFIG_FAT_LFN if (namecase == FATCASE_LOWER) { /* Mixed case in the name -- use the long file name */ goto errout; } /* So far, only upper case in the name */ namecase = FATCASE_UPPER; #endif /* Clear lower case name bit in mask */ ntlcenable &= ~FATNTRES_LCNAME; } else { /* Is there mixed case in the extension? */ #ifdef CONFIG_FAT_LFN if (extcase == FATCASE_LOWER) { /* Mixed case in the extension -- use the long file name */ goto errout; } /* So far, only upper case in the extension */ extcase = FATCASE_UPPER; #endif /* Clear lower case extension in mask */ ntlcenable &= ~FATNTRES_LCEXT; } } #endif /* Check for lower case characters */ else if (islower(ch)) { #if defined(CONFIG_FAT_LFN) && !defined(CONFIG_FAT_LCNAMES) /* If lower case characters are present, then a long file * name will be constructed. */ goto errout; #else /* Convert the character to upper case */ ch = toupper(ch); /* Some or all of the characters in the name or extension * are lower case. They can be interpreted as lower case if * only if all of the characters in the name or extension are * lower case. */ #ifdef CONFIG_FAT_LCNAMES if (endndx == 8) { /* Is there mixed case in the name? */ #ifdef CONFIG_FAT_LFN if (namecase == FATCASE_UPPER) { /* Mixed case in the name -- use the long file name */ goto errout; } /* So far, only lower case in the name */ namecase = FATCASE_LOWER; #endif /* Set lower case name bit */ ntlcfound |= FATNTRES_LCNAME; } else { /* Is there mixed case in the extension? */ #ifdef CONFIG_FAT_LFN if (extcase == FATCASE_UPPER) { /* Mixed case in the extension -- use the long file name */ goto errout; } /* So far, only lower case in the extension */ extcase = FATCASE_LOWER; #endif /* Set lower case extension bit */ ntlcfound |= FATNTRES_LCEXT; } #endif #endif /* CONFIG_FAT_LFN && !CONFIG_FAT_LCNAMES */ } /* Check if the file name exceeds the size permitted (without * long file name support). */ if (ndx >= endndx) { goto errout; } /* Save next character in the accumulated name */ dirinfo->fd_name[ndx++] = ch; } errout: return -EINVAL; } /**************************************************************************** * Name: fat_parselfname * * Description: Convert a user filename into a properly formatted FAT * long filename as it would appear in a directory entry. Here are * the rules for the long file name in the directory: * * Valid characters are the same as for short file names EXCEPT: * * 1. '+', ',', ';', '=', '[', and ']' are accepted in the file name * 2. '.' (dot) can occur more than once in a filename. Extension is * the substring after the last dot. * * Returned Value: * OK - The path refers to a valid long file name and has been properly * stored in dirinfo. * <0 - Otherwise an negated error is returned meaning that the string is * not a valid long file name: * * 1. Contains characters not in the printable character set, * 2. Contains forbidden characters * 3. File name is too long. * ****************************************************************************/ #ifdef CONFIG_FAT_LFN static inline int fat_parselfname(FAR const char **path, FAR struct fat_dirinfo_s *dirinfo, FAR char *terminator) { FAR const char *node = *path; lfnchar ch; int ndx = 0; /* Loop until the name is successfully parsed or an error occurs */ for (; ; ) { /* Get the next character from the path */ # ifdef CONFIG_FAT_LFN_UTF8 if (fat_utf8toucs(&node, &ch) != OK) { goto errout; } # else ch = *node++; # endif /* Check if this the last byte in this node of the name */ if ((ch == '\0' || ch == '/') && ndx != 0) { /* Null terminate the string */ dirinfo->fd_lfname[ndx] = '\0'; /* Return the remaining sub-string and the terminating character. */ *terminator = (char)ch; *path = node; return OK; } /* Accept only the printable character set (including space) */ # ifdef CONFIG_FAT_LFN_UTF8 /* We assume all ucs2 characters printable REVISIT? */ else if (ch < ' ') # else else if (!isprint(ch)) # endif { goto errout; } /* Reject printable characters forbidden by FAT */ else if (ch == '"' || ch == '*' || ch == '/' || ch == ':' || ch == '<' || ch == '>' || ch == '?' || ch == '\\' || ch == '|') { goto errout; } /* Check if the file name exceeds the size permitted. */ if (ndx >= LDIR_MAXFNAME) { goto errout; } /* Save next character in the accumulated name */ dirinfo->fd_lfname[ndx++] = ch; } errout: dirinfo->fd_lfname[0] = '\0'; return -EINVAL; } #endif /**************************************************************************** * Name: fat_createalias * * Description: Given a valid long file name, create a short filename alias. * Here are the rules for creation of the alias: * * 1. All uppercase * 2. All dots except the last deleted * 3. First 6 (uppercase) characters used as a base * 4. Then ~1. The number is increased if the file already exists in the * directory. If the number exceeds >10, then character stripped off the * base. * 5. The extension is the first 3 uppercase chars of extension. * * This function is called only from fat_putlfname() * * Returned Value: * OK - The alias was created correctly. * <0 - Otherwise an negated error is returned. * ****************************************************************************/ #ifdef CONFIG_FAT_LFN static inline int fat_createalias(FAR struct fat_dirinfo_s *dirinfo) { uint8_t ch; /* Current character being processed */ 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 filaname), but we could eencounter this in a filesystem * written by some other system */ if (ndx == 0 && ch == DIR0_E5) { ch = 0xe5; } /* Check if we should perform upper to lower case conversion * of the (whole) filename. */ #ifdef CONFIG_FAT_LCNAMES if (ntflags & FATNTRES_LCNAME && isupper(ch)) { ch = tolower(ch); } #endif /* Copy the next character into the filename */ *buffer++ = ch; buflen--; } /* Check if there is an extension */ if (direntry[8] != ' ' && buflen > 0) { /* Yes, output the dot before the extension */ *buffer++ = '.'; buflen--; /* Then output the (up to) 3 character extension */ for (ndx = 8; ndx < 11 && buflen > 0; ndx++) { /* Get the next extensions character from the directory entry */ ch = direntry[DIR_NAME + ndx]; /* Any space (or ndx==11) terminates the extension */ if (ch == ' ') { break; } /* Check if we should perform upper to lower case conversion * of the (whole) filename. */ #ifdef CONFIG_FAT_LCNAMES if (ntflags & FATNTRES_LCEXT && isupper(ch)) { ch = tolower(ch); } #endif /* Copy the next character into the filename */ *buffer++ = ch; buflen--; } } /* Put a null terminator at the end of the filename. We don't have to * check if there is room because we reserved a byte for the NUL * terminator at the beginning of this function. */ *buffer = '\0'; return OK; } /**************************************************************************** * Name: fat_getlfnchunk * * Description: There are 13 characters per LFN entry, broken up into three * chunks for characters 1-5, 6-11, and 12-13. This function will get the * file name characters from one chunk. * ****************************************************************************/ #ifdef CONFIG_FAT_LFN static void fat_getlfnchunk(FAR uint8_t *chunk, FAR lfnchar *dest, int nchunk) { uint16_t wch; int i; /* Copy bytes 1-nchunk */ for (i = 0; i < nchunk; i++) { /* Get the next unicode character from the chunk. We only handle * ASCII. For ASCII, the upper byte should be zero and the lower * should match the ASCII code. */ wch = fat_getuint16(chunk); # ifdef CONFIG_FAT_LFN_UTF8 *dest++ = wch; # else *dest++ = (uint8_t)(wch & 0xff); # endif chunk += sizeof(uint16_t); } } #endif /**************************************************************************** * Name: fat_getlfname * * Description: Get the long filename from a sequence of directory entries. * On entry, the "last" long file name entry is in the cache. Returns with * the short file name entry in the cache. * ****************************************************************************/ #ifdef CONFIG_FAT_LFN static inline int fat_getlfname(FAR struct fat_mountpt_s *fs, FAR struct fs_dirent_s *dir, FAR 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; }