From 4de8adaf2544045c1554c7dcda4a4cf318768e68 Mon Sep 17 00:00:00 2001 From: patacongo Date: Thu, 14 Jul 2011 17:57:59 +0000 Subject: FAT long support is code complete (but untested) git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@3783 42af7a65-404d-4744-a932-0658087f49c3 --- nuttx/fs/fat/fs_fat32.c | 82 ++- nuttx/fs/fat/fs_fat32.h | 2 +- nuttx/fs/fat/fs_fat32dirent.c | 1210 ++++++++++++++++++++++++++++++----------- 3 files changed, 950 insertions(+), 344 deletions(-) (limited to 'nuttx/fs') diff --git a/nuttx/fs/fat/fs_fat32.c b/nuttx/fs/fat/fs_fat32.c index 09db4766b..d9e052ed7 100644 --- a/nuttx/fs/fat/fs_fat32.c +++ b/nuttx/fs/fat/fs_fat32.c @@ -1314,6 +1314,7 @@ static int fat_readdir(struct inode *mountpt, struct fs_dirent_s *dir) uint8_t *direntry; uint8_t ch; uint8_t attribute; + bool found; int ret = OK; /* Sanity checks */ @@ -1336,7 +1337,9 @@ static int fat_readdir(struct inode *mountpt, struct fs_dirent_s *dir) /* Read the next directory entry */ dir->fd_dir.d_name[0] = '\0'; - while (dir->u.fat.fd_currsector && dir->fd_dir.d_name[0] == '\0') + found = false; + + while (dir->u.fat.fd_currsector && !found) { ret = fat_fscacheread(fs, dir->u.fat.fd_currsector); if (ret < 0) @@ -1365,21 +1368,39 @@ static int fat_readdir(struct inode *mountpt, struct fs_dirent_s *dir) /* No, is the current entry a valid entry? */ attribute = DIR_GETATTRIBUTES(direntry); + +#ifdef CONFIG_FAT_LFN + if (ch != DIR0_EMPTY && + ((attribute & FATATTR_VOLUMEID) == 0) || + ((ch & LDIR0_LAST) != 0 && attribute == LDDIR_LFNATTR)) +#else if (ch != DIR0_EMPTY && (attribute & FATATTR_VOLUMEID) == 0) +#endif { - /* Yes.. get the name from the directory info */ + /* Yes.. get the name from the directory entry. NOTE: For the case + * of the long file name entry, this will advance the several + * several directory entries. + */ - (void)fat_dirname2path(dir->fd_dir.d_name, direntry); + ret = fat_dirname2path(dir, direntry); + if (ret == OK) + { + /* The name was successfully extracted. Now save the file type */ + + if ((attribute & FATATTR_DIRECTORY) == 0) + { + dir->fd_dir.d_type = DTYPE_FILE; + } + else + { + dir->fd_dir.d_type = DTYPE_DIRECTORY; + } - /* And the file type */ + /* Mark the entry found. We will set up the next directory index, + * and then exit with success. + */ - if ((attribute & FATATTR_DIRECTORY) == 0) - { - dir->fd_dir.d_type = DTYPE_FILE; - } - else - { - dir->fd_dir.d_type = DTYPE_DIRECTORY; + found = true; } } @@ -1981,7 +2002,9 @@ int fat_rename(struct inode *mountpt, const char *oldrelpath, goto errout_with_semaphore; } - /* Find the directory entry for the oldrelpath */ + /* Find the directory entry for the oldrelpath (there may be multiple + * directory entries if long file name support is enabled). + */ ret = fat_finddirentry(fs, &dirinfo, oldrelpath); if (ret != OK) @@ -2003,14 +2026,18 @@ int fat_rename(struct inode *mountpt, const char *oldrelpath, /* Save the information that will need to recover the directory sector and * directory entry offset to the old directory. + * + * Save the positional information of the old directory entry. */ memcpy(&dirseq, &dirinfo.fd_seq, sizeof(struct fat_dirseq_s)); - + + /* Save the non-name-related portion of the directory entry intact */ + direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset]; memcpy(dirstate, &direntry[DIR_ATTRIBUTES], DIR_SIZE-DIR_ATTRIBUTES); - /* No find the directory where we should create the newpath object */ + /* Now find the directory where we should create the newpath object */ ret = fat_finddirentry(fs, &dirinfo, newrelpath); if (ret == OK) @@ -2030,7 +2057,11 @@ int fat_rename(struct inode *mountpt, const char *oldrelpath, goto errout_with_semaphore; } - /* Reserve a directory entry */ + /* Reserve a directory entry. If long file name support is enabled, then + * this might, in fact, allocate a sequence of directory entries. A side + * effect of fat_allocatedirentry() in either case is that it leaves the + * short file name entry in the sector cache. + */ ret = fat_allocatedirentry(fs, &dirinfo); if (ret != OK) @@ -2038,19 +2069,28 @@ int fat_rename(struct inode *mountpt, const char *oldrelpath, goto errout_with_semaphore; } - /* Write the new directory entry */ + /* Then write the new file name into the directory entry. This, of course, + * may involve writing multiple directory entries if long file name + * support is enabled. A side effect of fat_allocatedirentry() in either + * case is that it leaves the short file name entry in the sector cache. + */ - direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset]; - memcpy(&direntry[DIR_ATTRIBUTES], dirstate, DIR_SIZE-DIR_ATTRIBUTES); - fs->fs_dirty = true; - ret = fat_dirnamewrite(fs, &dirinfo); if (ret < 0) { goto errout_with_semaphore; } - /* Remove the old entry (flushing the new directory entry to disk) */ + /* Copy the unchanged information into the new short file name entry. */ + + direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset]; + memcpy(&direntry[DIR_ATTRIBUTES], dirstate, DIR_SIZE-DIR_ATTRIBUTES); + fs->fs_dirty = true; + + /* Remove the old entry, flushing the new directory entry to disk. If + * the old file name was a long file name, then multiple directory + * entries may be freed. + */ ret = fat_freedirentry(fs, &dirseq); if (ret < 0) diff --git a/nuttx/fs/fat/fs_fat32.h b/nuttx/fs/fat/fs_fat32.h index e45d011d8..11ad26ae6 100644 --- a/nuttx/fs/fat/fs_fat32.h +++ b/nuttx/fs/fat/fs_fat32.h @@ -829,7 +829,7 @@ EXTERN int fat_dirwrite(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirin uint8_t attributes, uint32_t fattime); EXTERN int fat_allocatedirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo); EXTERN int fat_freedirentry(struct fat_mountpt_s *fs, struct fat_dirseq_s *seq); -EXTERN int fat_dirname2path(char *path, uint8_t *direntry); +EXTERN int fat_dirname2path(struct fs_dirent_s *dir, uint8_t *direntry); /* File creation and removal helpers */ diff --git a/nuttx/fs/fat/fs_fat32dirent.c b/nuttx/fs/fat/fs_fat32dirent.c index f0db53e47..454ec1052 100644 --- a/nuttx/fs/fat/fs_fat32dirent.c +++ b/nuttx/fs/fat/fs_fat32dirent.c @@ -117,6 +117,28 @@ enum fat_case_e * Private Functions ****************************************************************************/ +/**************************************************************************** + * Name: fat_lfnchecksum + * + * Desciption: Caculate the checksum of . + * + ****************************************************************************/ + +#ifdef CONFIG_FAT_LFN +static uint8_t fat_lfnchecksum(const uint8_t *sfname) +{ + uint8_t sum = 0; + int i; + + for (i = DIR_MAXFNAME; i; i--) + { + sum = ((sum & 1) << 7) + (sum >> 1) + *sfname++; + } + + return sum; +} +#endif + /**************************************************************************** * Name: fat_parsesfname * @@ -497,6 +519,8 @@ static inline int fat_parselfname(const char **path, * 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. @@ -504,12 +528,11 @@ static inline int fat_parselfname(const char **path, ****************************************************************************/ #ifdef CONFIG_FAT_LFN -static inline int fat_createalias(const char **path, - struct fat_dirinfo_s *dirinfo) +static inline int fat_createalias(struct fat_dirinfo_s *dirinfo) { uint8_t ch; /* Current character being processed */ char *ext; /* Pointer to the extension substring */ - char *ptr; /* Working pointer */ + char *src; /* Pointer to the long file name source */ int len; /* Total length of the long file name */ int namechars; /* Number of characters available in long name */ int extchars; /* Number of characters available in long name extension */ @@ -573,14 +596,14 @@ static inline int fat_createalias(const char **path, /* Use the extension as the name */ DEBUGASSERT(ext && extchars > 0); - ptr = ext; + src = ext; ext = NULL; namechar = extchars; extchars = 0; } else { - ptr = dirinfo.fd_ldname; + src = dirinfo.fd_ldname; } /* Then copy the name and extension, handling upper case conversions and @@ -596,7 +619,7 @@ static inline int fat_createalias(const char **path, * encounter the end of null-terminated the long file name string. */ - ch = *ptr++; + ch = *src++; if (ch == '\0') { break; @@ -654,7 +677,7 @@ static inline int fat_createalias(const char **path, ndx = 8; endndx = 11; - ptr = ext; + src = ext; } } @@ -700,32 +723,6 @@ static inline int fat_uniquealias(const char **path, } #endif -/**************************************************************************** - * Name: fat_lfnchecksum - * - * Desciption: Caculate the checksum of . - * - * Returned value: - * OK - The alias is unique. - * <0 - Otherwise an negated error is returned. - * - ****************************************************************************/ - -#ifdef CONFIG_FAT_LFN -static uint8_t fat_lfnchecksum(const uint8_t *sfname) -{ - uint8_t sum = 0; - int i; - - for (i = DIR_MAXFNAME; i; i--) - { - sum = ((sum & 1) << 7) + (sum >> 1) + *sfname++; - } - - return sum; -} -#endif - /**************************************************************************** * Name: fat_path2dirname * @@ -778,8 +775,8 @@ static inline int fat_findsfnentry(struct fat_mountpt_s *fs, uint8_t *direntry; int ret; - /* Search, beginning with the current sector, for a directory entry this - * the match shore name + /* Search, beginning with the current sector, for a directory entry with + * the matching short name */ for (;;) @@ -849,7 +846,7 @@ static inline int fat_findsfnentry(struct fat_mountpt_s *fs, ****************************************************************************/ #ifdef CONFIG_FAT_LFN -static bool fat_cmplfnchunk(wchar_t *chunk, uint8_t *substr, int nchunk) +static bool fat_cmplfnchunk(wchar_t *chunk, const uint8_t *substr, int nchunk) { uint8_t *ptr; int i; @@ -901,7 +898,7 @@ static bool fat_cmplfnchunk(wchar_t *chunk, uint8_t *substr, int nchunk) ****************************************************************************/ #ifdef CONFIG_FAT_LFN -static bool fat_cmplfname(uint8_t *direntry, uint8_t *substr) +static bool fat_cmplfname(const uint8_t *direntry, const uint8_t *substr) { uint8_t *chunk; int i; @@ -916,9 +913,9 @@ static bool fat_cmplfname(uint8_t *direntry, uint8_t *substr) chunk = LDIR_PTRWCHAR6_11(direntry); if (fat_cmplfnchunk(chunk, &substr[5], 6)) { - /* Check bytes 6-11 */ + /* Check bytes 12-13 */ - chunk = efine LDIR_WCHAR12_13; + chunk = LDIR_PTRWCHAR12_13(direntry); return fat_cmplfnchunk(chunk, &substr[11], 2); } } @@ -932,6 +929,9 @@ static bool fat_cmplfname(uint8_t *direntry, uint8_t *substr) * * Desciption: Find a sequence of long file name directory entries. * + * NOTE: As a side effect, this function returns with the sector containing + * the short file name directory entry in the cache. + * ****************************************************************************/ #ifdef CONFIG_FAT_LFN @@ -945,20 +945,17 @@ static inline int fat_findlfnentry(struct fat_mountpt_s *fs, uint8_t nfullentries; uint8_t nentries; uint8_t remainder; - uint8_t cksum; + uint8_t checksum; int offset; int namelen; int ret; - /* Get the length of the long file name and make sure that it does - * not exceed the maximum. + /* Get the length of the long file name (size of the fd_lfname array is + * LDIR_MAXFNAME+1 we do not have to check the length of the string). */ namelen = strlen(dirinfo.fd_lfname); - if (namelen > LDIR_MAXFNAME) - { - return -EINVAL; - } + DEBUGASSERT(namelen <= LDIR_MAXFNAME); /* How many LFN directory entries are we expecting? */ @@ -1009,7 +1006,7 @@ static inline int fat_findlfnentry(struct fat_mountpt_s *fs, * looking for? */ - if (LDIR_GETATTRIBUTES(direntry) != LDDIR_LFNATTR) || + if (LDIR_GETATTRIBUTES(direntry) != LDDIR_LFNATTR || LDIR_GETSEQ(direntry) != seqno); { /* No, restart the search at the next entry */ @@ -1026,14 +1023,14 @@ static inline int fat_findlfnentry(struct fat_mountpt_s *fs, { /* Just save the checksum for subsequent checks */ - cksum = LDIR_GETCHECKSUM(direntry); + checksum = LDIR_GETCHECKSUM(direntry); } /* Not the first entry in the sequence. Does the checksum match the * previous sequences? */ - else if (cksum != LDIR_GETCHECKSUM(direntry)) + else if (checksum != LDIR_GETCHECKSUM(direntry)) { /* No, restart the search at the next entry */ @@ -1080,7 +1077,7 @@ static inline int fat_findlfnentry(struct fat_mountpt_s *fs, /* Verify the checksum */ - if (fat_lfnchecksum(dirinfo->fd_name) == cksum) + if (fat_lfnchecksum(dirinfo->fd_name) == checksum) { /* Success! Save the position of the directory entry and * return success. @@ -1216,15 +1213,12 @@ static inline int fat_allocatelfnentry(struct fat_mountpt_s *fs, int namelen; int ret; - /* Get the length of the long file name and make sure that it does - * not exceed the maximum. + /* Get the length of the long file name (size of the fd_lfname array is + * LDIR_MAXFNAME+1 we do not have to check the length of the string). */ - namelen = strlen(dirinfo->fd_lfname); - if (namelen > LDIR_MAXFNAME) - { - return -EINVAL; - } + namelen = strlen(dirinfo.fd_lfname); + DEBUGASSERT(namelen <= LDIR_MAXFNAME); /* How many LFN directory entries are we expecting? */ @@ -1330,179 +1324,800 @@ static inline int fat_allocatelfnentry(struct fat_mountpt_s *fs, #endif /**************************************************************************** - * Public Functions - ****************************************************************************/ - -/**************************************************************************** - * Name: fat_finddirentry + * Name: fat_getsfname * - * Desciption: Given a path to something that may or may not be in the file - * system, return the directory entry of the item. + * Desciption: Get the 8.3 filename from a directory entry. On entry, the + * short file name entry is already in the cache. * ****************************************************************************/ -int fat_finddirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo, - const char *path) +static inline int fat_getsfname(uint8_t *direntry, char *buffer, + unsigned int buflen) { - off_t cluster; - uint8_t *direntry; - char terminator; - int ret; - - /* Initialize to traverse the chain. Set it to the cluster of the root - * directory - */ +#ifdef CONFIG_FAT_LCNAMES + uint8_t ntflags; +#endif + int ch; + int ndx; - 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. - */ + /* Check if we will be doing upper to lower case conversions */ - 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. - */ +#ifdef CONFIG_FAT_LCNAMES + ntflags = DIR_GETNTRES(direntry); +#endif - dirinfo->dir.fd_startcluster = 0; - dirinfo->dir.fd_currcluster = 0; - dirinfo->dir.fd_currsector = cluster; - } + /* Reserve a byte for the NUL terminator */ - /* fd_index is the index into the current directory table */ + buflen--; - dirinfo->dir.fd_index = 0; + /* Get the 8-byte filename */ - /* If no path was provided, then the root directory must be exactly what - * the caller is looking for. - */ + for (ndx = 0; ndx < 8 && buflen > 0; ndx++) + { + /* Get the next filename character from the directory entry */ - if (*path == '\0') - { - dirinfo->fd_root = true; - return OK; - } + ch = direntry[ndx]; - /* Otherwise, loop until the path is found */ + /* Any space (or ndx==8) terminates the filename */ - for (;;) - { - /* Convert the next the path segment name into the kind of name that - * we would see in the directory entry. - */ + if (ch == ' ') + { + break; + } - ret = fat_path2dirname(&path, dirinfo, &terminator); - if (ret < 0) - { - /* ERROR: The filename contains invalid characters or is - * too long. - */ + /* In this version, we never write 0xe5 in the directory filenames + * (because we do not handle any character sets where 0xe5 is valid + * in a filaname), but we could encounted this in a filesystem + * written by some other system + */ - return ret; - } + if (ndx == 0 && ch == DIR0_E5) + { + ch = 0xe5; + } - /* Is this a path segment a long or a short file. Was a long file - * name parsed? - */ + /* Check if we should perform upper to lower case conversion + * of the (whole) filename. + */ -#ifdef CONFIG_FAT_LFN - if (dirinfo.fd_lfname[0] != '\0') - { - /* Yes.. Search for the sequence of long file name directory - * entries. - */ - - ret = fat_findlfnentry(fs, dirinfo); - } +#ifdef CONFIG_FAT_LCNAMES + if (ntflags & FATNTRES_LCNAME && isupper(ch)) + { + ch = tolower(ch); + } +#endif + /* Copy the next character into the filename */ - /* No.. Search for the single short file name directory entry */ + *buffer++ = ch; + buflen--; + } - else -#endif - { - ret = fat_findsfnentry(fs, dirinfo); - } + /* Check if there is an extension */ - /* Did we find the directory entries? */ + if (direntry[8] != ' ' && buflen > 0) + { + /* Yes, output the dot before the extension */ - if (ret < 0) - { - return ret; - } + *buffer++ = '.'; + buflen--; - /* 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. - */ + /* Then output the (up to) 3 character extension */ - if (!terminator) - { - /* Return success meaning that the description the matching - * directory entry is in dirinfo. - */ + for (ndx = 8; ndx < 11 && buflen > 0; ndx++) + { + /* Get the next extensions character from the directory entry */ - return OK; - } + ch = direntry[DIR_NAME + ndx]; - /* 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. - */ + /* Any space (or ndx==11) terminates the extension */ - direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset]; - if (!(DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY)) - { - /* Ooops.. we found something else */ + if (ch == ' ') + { + break; + } - return -ENOTDIR; - } + /* Check if we should perform upper to lower case conversion + * of the (whole) filename. + */ - /* Get the cluster number of this directory */ +#ifdef CONFIG_FAT_LCNAMES + if (ntflags & FATNTRES_LCEXT && isupper(ch)) + { + ch = tolower(ch); + } +#endif + /* Copy the next character into the filename */ - cluster = - ((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) | - DIR_GETFSTCLUSTLO(direntry); + *buffer++ = ch; + buflen--; + } + } - /* The restart scanning at the new directory */ + /* 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. + */ - dirinfo->dir.fd_currcluster = dirinfo->dir.fd_startcluster = cluster; - dirinfo->dir.fd_currsector = fat_cluster2sector(fs, cluster); - dirinfo->dir.fd_index = 2; - } + *buffer = '\0'; + return OK; } /**************************************************************************** - * Name: fat_allocatedirentry + * Name: fat_getlfnchunk * - * Desciption: Find a free directory entry + * Desciption: There are 13 characters per LFN entry, broken up into three + * chunks for characts 1-5, 6-11, and 12-13. This function will get the + * file name characters from one chunk. * ****************************************************************************/ -int fat_allocatedirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) +#ifdef CONFIG_FAT_LFN +static void fat_getlfnchunk(wchar_t *chunk, uint8_t *dest, int nchunk) { - int32_t cluster; - off_t sector; - int ret; - int i; - - /* Re-initialize directory object */ - - cluster = dirinfo->dir.fd_startcluster; + wchar_t wch; + int i; - /* Loop until we successfully allocate the sequence of directory entries - * or until to fail to extend the directory cluster chain. - */ + /* Copy bytes 1-nchunk */ - for (;;) + for (i = 0; i < nchunk; i++) { - /* Can this cluster chain be extended */ + /* 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); + *dest++ = (uint8_t)(wch & 0xff); + chunk++; + } +} +#endif + +/**************************************************************************** + * Name: fat_getlfname + * + * Desciption: Get the long filename from a sequence of directory entries. + * On entry, the "last" long file name entry is in the cache. Returns with + * the short file name entry in the cache. + * + ****************************************************************************/ + +#ifdef CONFIG_FAT_LFN +static inline int fat_getlfname(struct fs_dirent_s *dir) +{ + uint8_t lfname[LDIR_MAXLFNCHARS]; + uint16_t diroffset; + uint8_t *direntry; + uint8_t seqno; + uint8_t offset; + uint8_t checksum; + int tmp; + int nsrc; + int i; + + /* Get a reference to the current directory entry */ + + diroffset = (dir->u.fat.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE; + direntry = &fs->fs_buffer[diroffset]; + + /* Get the starting sequence number */ + + seqno = LDIR_GETSEQ(direntry); + DEBUGASSERT((seqno & LDIR0_LAST) != 0); + + /* Sanity check */ + + tmp = (seqno & LDIR0_SEQ_MASK); + if (tmp < 1 || tmp > LDIR_MAXLFNS) + { + return -EINVAL; + } + + /* Get the string offset associated with the "last" entry. */ + + offset = (tmp - 1) * LDIR_MAXLFNCHARS; + + /* Save the checksum value */ + + checksum = LDIR_GETCHECKSUM(direntry); + + /* Loop until the whole file name has been transferred */ + + for (;;) + { + /* Will any of this file name fit into the destination buffer? */ + + if (offset < NAME_MAX) + { + /* Yes.. extract and convert the unicode name */ + + fat_getlfnchunk(LDIR_PTRWCHAR1_5(direntry), lfname, 5); + fat_getlfnchunk(LDIR_PTRWCHAR6_11(direntry), &lfname[5], 6); + fat_getlfnchunk(LDIR_PTRWCHAR12_13(direntry), &lfname[11], 2); + + /* Ignore trailing spaces on the "last" directory entry. The + * number of characters avaiable is LDIR_MAXLFNCHARS or that + * minus the number of trailing spaces on the "last" directory + * entry. + */ + + nsrc = LDIR_MAXLFNCHARS; + if ((seqno & LDIR0_LAST) != 0) + { + int len; + + /* Reduce the number of characters by the number of trailing + * spaces. + */ + + for (; nsrc > 0 && lfname[nsrc-1] == ' '; nsrc--); + + /* Further reduce the length so that it fits in the destination + * buffer. + */ + + if (offset + nsrc > NAME_MAX) + { + nsrc = NAME_MAX - offset; + } + + /* Add a null terminator to the destination string (the actual + * length of the destination buffer is NAME_MAX+1, so the NUL + * terminator will fit). + */ + + dir.d_name[offset+nsrc] = '\0'; + } + + /* Then transfer the characters */ + + for (i = 0; i < nsrc && offset+i < NAME_MAX; i++) + { + dir.d_name[offset+i] = lfname[i]; + } + } + + /* Read next directory entry */ + + if (fat_nextdirentry(fs, dir) != OK) + { + return -ENOENT; + } + + /* Make sure that the directory sector into the sector cache */ + + ret = fat_fscacheread(fs, dir->fd_currsector); + if (ret < 0) + { + return ret; + } + + /* Get a reference to the current directory entry */ + + diroffset = (dir->u.fat.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE; + direntry = &fs->fs_buffer[diroffset]; + + /* Get the next expected sequence number. */ + + seqno = (seqno & LDIR0_SEQ_MASK) - 1; + if (seqno < 1) + { + /* We just completed processing the "first" long file name entry + * and we just read the short file name entry. Verify that the + * checksum of the short file name matches the checksum that we + * found in the long file name entries. + */ + + if (fat_lfnchecksum(dirinfo->fd_name) == checksum) + { + /* Yes.. return success! */ + + return OK; + } + + /* No, the checksum is bad. */ + + return -EINVAL; + } + + /* Verify the next long file name entry. Is this an LFN entry? Does it + * have the sequence number we are looking for? Does the checksum + * match the previous entries? + */ + + if (LDIR_GETATTRIBUTES(direntry) != LDDIR_LFNATTR || + LDIR_GETSEQ(direntry) != seqno || + LDIR_GETCHECKSUM(direntry) != checksum) + { + return -EINVAL; + } + } +} +#endif + +/**************************************************************************** + * Name: fat_putsfname + * + * Desciption: Write the short directory entry name. + * + * Assumption: The directory sector is in the cache. + * + ****************************************************************************/ + +static int fat_putsfname(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) +{ + uint8_t *direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset]; + + memcpy(&direntry[DIR_NAME], dirinfo->fd_name, DIR_MAXFNAME); +#ifdef CONFIG_FLAT_LCNAMES + DIR_PUTNTRES(direntry, dirinfo->fd_ntflags); +#else + DIR_PUTNTRES(direntry, 0); +#endif + fs->fs_dirty = true; + return OK; +} + +/**************************************************************************** + * Name: fat_putlfnspaces + * + * Desciption: There are 13 characters per LFN entry, broken up into three + * chunks for characts 1-5, 6-11, and 12-13. This function will put the + * space characters into one chunk. + * + ****************************************************************************/ + +#ifdef CONFIG_FAT_LFN +static void fat_putlfnspaces(wchar_t *chunk, int nchunk) +{ + uint16_t wch; + int i; + + /* Initialize unicode characters 1-nchunk */ + + for (i = 0; i < nchunk; i++) + { + /* The write the unicode spzed character into the directory entry. */ + + fat_putuint16(chunk, (uint16_t)' '); + chunk++; + } +} +#endif + +/**************************************************************************** + * Name: fat_putlfnchunk + * + * Desciption: There are 13 characters per LFN entry, broken up into three + * chunks for characts 1-5, 6-11, and 12-13. This function will put the + * file name characters into one chunk. + * + ****************************************************************************/ + +#ifdef CONFIG_FAT_LFN +static void fat_putlfnchunk(wchar_t *chunk, const uint8_t *src, int nchunk) +{ + uint16_t wch; + int i; + + /* Write bytes 1-nchunk */ + + for (i = 0; i < nchunk; i++) + { + /* Get the next ascii character from the name substring and convert it + * to unicode. The upper byte should be zero and the lower should be + * the ASCII code. The write the unicode character into the directory + * entry. + */ + + wch = (uint16_t)*src++; + fat_putuint16(chunk, wch); + chunk++; + } +} +#endif + +/**************************************************************************** + * Name: fat_putlfname + * + * Desciption: Write the long filename into a sequence of directory entries. + * On entry, the "last" long file name entry is in the cache. Returns with + * the short file name entry in the cache. + * + ****************************************************************************/ + +#ifdef CONFIG_FAT_LFN +static int fat_putlfname((struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) +{ + uint16_t diroffset; + uint8_t *direntry; + uint8_t nfullentries; + uint8_t nentries; + uint8_t remainder; + uint8_t offset; + uint8_t seqno; + uint8_t checksum; + int namelen; + int ret; + + /* Get the length of the long file name (size of the fd_lfname array is + * LDIR_MAXFNAME+1 we do not have to check the length of the string). + */ + + namelen = strlen(dirinfo.fd_lfname); + DEBUGASSERT(namelen <= LDIR_MAXFNAME); + + /* 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++; + } + DEBUGASSERT(nentries > 0 && nentries <= LDIR_MAXLFNS); + + /* Create the short file name alias */ + + ret = fat_createalias(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; + + /* Set up the initial positional data */ + + dirinfo->dir.fd_currcluster = dirinfo->ds_lfncluster; + dirinfo->dir.fd_currsector = dirinfo->ds_lfnsector; + + /* 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 * LDIR_MAXLFNCHARS; + + /* Get a reference to the current directory entry */ + + diroffset = (dir->u.fat.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 spaces */ + + fat_putlfnspaces(LDIR_PTRWCHAR1_5(direntry), 5); + fat_putlfnspaces(LDIR_PTRWCHAR6_11(direntry), 6); + fat_putlfnspaces(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(8, remainder); + fat_putlfnchunk(LDIR_PTRWCHAR6_11(direntry), + &dirinfo->fd_lfname[offset+5], nbytes); + remainder -= nbytes; + } + + if (remainder > 0) + { + nbytes = MIN(2, remainder); + fat_putlfnchunk(LDIR_PTRWCHAR12_13(direntry), + &dirinfo->fd_lfname[offset+11], nbytes); + remainder -= nbytes; + } + + /* The remainder should now be zero */ + + DEBUGASSERT(remainder == 0); + } + else + { + /* Store a portion long file name in this directory entry */ + + fat_putlfnchunk(LDIR_PTRWCHAR1_5(direntry), + &dirinfo->fd_lfname[offset], 5); + fat_putlfnchunk(LDIR_PTRWCHAR6_11(direntry), + &dirinfo->fd_lfname[offset+5], 6); + fat_putlfnchunk(LDIR_PTRWCHAR12_13(direntry), + &dirinfo->fd_lfname[offset+11], 2); + } + + /* Write the remaining directory entries */ + + LDIR_PUTSEQ(direntry, seqno); + LDIR_PUTATTRIBUTES(direntry, LDDIR_LFNATTR); + LDIR_PUTNTRES(direntry, 0); + LDIR_PUTCHECKSUM(direntry, checksum); + fs->fs_dirty = true; + + /* Read next directory entry */ + + if (fat_nextdirentry(fs, dir) != OK) + { + return -ENOENT; + } + + /* Make sure that the sector containing the directory entry is in the + * sector cache + */ + + ret = fat_fscacheread(fs, dirinfo->dir.fd_currsector); + if (ret < 0) + { + return ret; + } + + /* Decrement the number of entries and get the next sequence number. */ + + if (--nentries <= 0) + { + /* We have written all of the long file name entries to the media + * and we have the short file name entry in the cache. We can + * just return success. + */ + + return OK; + } + + /* The sequence number is just the number of entries left to be + * written. + */ + + seqno = nentries; + } +} +#endif + +/**************************************************************************** + * Name: fat_putsfdirentry + * + * Desciption: Write a short file name directory entry + * + * Assumption: The directory sector is in the cache. The caller will write + * sector information. + * + ****************************************************************************/ + +static int fat_putsfdirentry(struct fat_mountpt_s *fs, + struct fat_dirinfo_s *dirinfo, + uint8_t attributes, uint32_t fattime) +{ + uint8_t *direntry; + + /* Initialize the 32-byte directory entry */ + + direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset]; + memset(direntry, 0, DIR_SIZE); + + /* Directory name info */ + + (void)fat_putsfname(fs, dirinfo); + + /* Set the attribute attribute, write time, creation time */ + + DIR_PUTATTRIBUTES(direntry, attributes); + + /* Set the time information */ + + DIR_PUTWRTTIME(direntry, fattime & 0xffff); + DIR_PUTCRTIME(direntry, fattime & 0xffff); + DIR_PUTWRTDATE(direntry, fattime >> 16); + DIR_PUTCRDATE(direntry, fattime >> 16); + + fs->fs_dirty = true; + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: fat_finddirentry + * + * Desciption: Given a path to something that may or may not be in the file + * system, return the description of the directory entry of the requested + * item. + * + * NOTE: As a side effect, this function returns with the sector containing + * the short file name directory entry in the cache. + * + ****************************************************************************/ + +int fat_finddirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo, + const char *path) +{ + off_t cluster; + uint8_t *direntry; + char terminator; + int ret; + + /* Initialize to traverse the chain. Set it to the cluster of the root + * directory + */ + + cluster = fs->fs_rootbase; + if (fs->fs_type == FSTYPE_FAT32) + { + /* For FAT32, the root directory is variable sized and is a cluster + * chain like any other directory. fs_rootbase holds the first + * cluster of the root directory. + */ + + dirinfo->dir.fd_startcluster = cluster; + dirinfo->dir.fd_currcluster = cluster; + dirinfo->dir.fd_currsector = fat_cluster2sector(fs, cluster); + } + else + { + /* For FAT12/16, the first sector of the root directory is a sector + * relative to the first sector of the fat volume. + */ + + dirinfo->dir.fd_startcluster = 0; + dirinfo->dir.fd_currcluster = 0; + dirinfo->dir.fd_currsector = cluster; + } + + /* fd_index is the index into the current directory table */ + + 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; + } + + /* Otherwise, loop until the path is found */ + + for (;;) + { + /* Convert the next the path segment name into the kind of name that + * we would see in the directory entry. + */ + + ret = fat_path2dirname(&path, dirinfo, &terminator); + if (ret < 0) + { + /* ERROR: The filename contains invalid characters or is + * too long. + */ + + return ret; + } + + /* Is this a path segment a long or a short file. Was a long file + * name parsed? + */ + +#ifdef CONFIG_FAT_LFN + if (dirinfo.fd_lfname[0] != '\0') + { + /* Yes.. Search for the sequence of long file name directory + * entries. NOTE: As a side effect, this function returns with + * the sector containing the short file name directory entry + * in the cache. + */ + + ret = fat_findlfnentry(fs, dirinfo); + } + else +#endif + { + /* No.. Search for the single short file name directory entry */ + + ret = fat_findsfnentry(fs, dirinfo); + } + + /* Did we find the directory entries? */ + + if (ret < 0) + { + return ret; + } + + /* If the terminator character in the path was the end of the string + * then we have successfully found the directory entry that describes + * the path. + */ + + if (!terminator) + { + /* Return success meaning that the description the matching + * directory entry is in dirinfo. + */ + + return OK; + } + + /* No.. then we have found one of the intermediate directories on + * the way to the final path target. In this case, make sure + * the thing that we found is, indeed, a directory. + */ + + direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset]; + if (!(DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY)) + { + /* Ooops.. we found something else */ + + return -ENOTDIR; + } + + /* Get the cluster number of this directory */ + + cluster = + ((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) | + DIR_GETFSTCLUSTLO(direntry); + + /* Then restart scanning at the new directory */ + + dirinfo->dir.fd_currcluster = dirinfo->dir.fd_startcluster = cluster; + dirinfo->dir.fd_currsector = fat_cluster2sector(fs, cluster); + dirinfo->dir.fd_index = 2; + } +} + +/**************************************************************************** + * Name: fat_allocatedirentry + * + * Desciption: Find a free directory entry + * + ****************************************************************************/ + +int fat_allocatedirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) +{ + int32_t cluster; + off_t sector; + int ret; + int i; + + /* Re-initialize directory object */ + + cluster = dirinfo->dir.fd_startcluster; + + /* Loop until we successfully allocate the sequence of directory entries + * or until to fail to extend the directory cluster chain. + */ + + for (;;) + { + /* Can this cluster chain be extended */ if (cluster) { @@ -1607,6 +2222,9 @@ int fat_allocatedirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo * * Desciption: Free the directory entry. * + * NOTE: As a side effect, this function returns with the sector containing + * the deleted short file name directory entry in the cache. + * ****************************************************************************/ int fat_freedirentry(struct fat_mountpt_s *fs, struct fat_dirseq_s *seq) @@ -1702,133 +2320,77 @@ int fat_freedirentry(struct fat_mountpt_s *fs, struct fat_dirseq_s *seq) * ****************************************************************************/ -int fat_dirname2path(char *path, uint8_t *direntry) +int fat_dirname2path(struct fs_dirent_s *dir, uint8_t *direntry) { -#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 - - /* Get the 8-byte filename */ - - for (ndx = 0; ndx < 8; 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 directoryfilenames - * (because we do not handle any character sets where 0xe5 is valid - * in a filaname), but we could encounted this in a filesystem - * written by some other system - */ - - if (ndx == 0 && ch == DIR0_E5) - { - ch = 0xe5; - } - - /* Check if we should perform upper to lower case conversion - * of the (whole) filename. - */ - -#ifdef CONFIG_FAT_LCNAMES - if (ntflags & FATNTRES_LCNAME && isupper(ch)) - { - ch = tolower(ch); - } -#endif - /* Copy the next character into the filename */ - - *path++ = ch; - } - - /* Check if there is an extension */ - - if (direntry[8] != ' ') - { - /* Yes, output the dot before the extension */ - - *path++ = '.'; - - /* Then output the (up to) 3 character extension */ - - for (ndx = 8; ndx < 11; 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; - } +#ifdef CONFIG_FAT_LFN + uint8_t attribute = DIR_GETATTRIBUTES(direntry); - /* Check if we should perform upper to lower case conversion - * of the (whole) filename. - */ + /* Does this entry refer to the last entry of a long file name? */ -#ifdef CONFIG_FAT_LCNAMES - if (ntflags & FATNTRES_LCEXT && isupper(ch)) - { - ch = tolower(ch); - } + if (((*direntry & LDIR0_LAST) != 0 && attribute == LDDIR_LFNATTR)) + { + /* Yes.. Get the name from a sequence of long file name directory + * entries. + */ + + return fat_getlfname(dir); + } + else #endif - /* Copy the next character into the filename */ - - *path++ = ch; - } - } - - /* Put a null terminator at the end of the filename */ + { + /* No.. Get the name from a short file name directory entries */ - *path = '\0'; - return OK; + return fat_getsfname(direntry, dir->fd_dir.d_name, NAME_MAX+1); + } } /**************************************************************************** * Name: fat_dirnamewrite * - * Desciption: Write the (possibly long) directory entry name. + * Desciption: Write the (possibly long) directory entry name. This function + * is called only from fat_rename to write the new file name. * - * Assumption: The directory sector is in the cache. + * Assumption: The directory sector containing the short file name entry + * is in the cache. *NOT* the sector containing the last long file name + * entry! * ****************************************************************************/ int fat_dirnamewrite(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) { - uint8_t *direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset]; + /* Is this a long file name? */ + +#ifdef CONFIG_FAT_LFN + if (dirinfo.fd_lfname[0] != '\0') + { + /* Write the sequence of long file name directory entries (this function + * also creates the short file name alias). + */ - memcpy(&direntry[DIR_NAME], dirinfo->fd_name, DIR_MAXFNAME); -#ifdef CONFIG_FLAT_LCNAMES - DIR_PUTNTRES(direntry, dirinfo->fd_ntflags); -#else - DIR_PUTNTRES(direntry, 0); + ret = fat_putlfname(fs, dirinfo); + if (ret != OK) + { + return ret; + } + } + + /* On return, fat_lfsfname() will leave the short file name entry in the + * cache. So we can just fall throught to write that directory entry, perhaps + * using the short file name alias for the long file name. + */ #endif - fs->fs_dirty = true; - return OK; + + return fat_putsfname(fs, dirinfo); } /**************************************************************************** * Name: fat_dirwrite * - * Desciption: Write a directory entry, possibly with a long file name + * Desciption: Write a directory entry, possibly with a long file name. + * Called from: + * + * fat_mkdir() to write the new FAT directory entry. + * fat_dircreate() to create any new directory entry. * * Assumption: The directory sector is in the cache. The caller will write * sector information. @@ -1838,30 +2400,31 @@ int fat_dirnamewrite(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) int fat_dirwrite(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo, uint8_t attributes, uint32_t fattime) { - uint8_t *direntry; - - /* Initialize the 32-byte directory entry */ - - direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset]; - memset(direntry, 0, DIR_SIZE); - - /* Directory name info */ - - (void)fat_dirnamewrite(fs, dirinfo); + /* Does this directory entry have a long file name? */ - /* Set the attribute attribute, write time, creation time */ +#ifdef CONFIG_FAT_LFN + if (dirinfo.fd_lfname[0] != '\0') + { + /* Yes.. write the long file name directory entries. (This function + * also creates the short file name alias). */ + + ret = fat_putsfdirentry(fs, dirinfo); + if (ret != OK) + { + return ret; + } + } - DIR_PUTATTRIBUTES(direntry, attributes); + /* On return, fat_lfsfname() will leave the short file name entry in the + * cache. So we can just fall throught to write that directory entry, perhaps + * using the short file name alias for the long file name. + */ - /* Set the time information */ +#endif - DIR_PUTWRTTIME(direntry, fattime & 0xffff); - DIR_PUTCRTIME(direntry, fattime & 0xffff); - DIR_PUTWRTDATE(direntry, fattime >> 16); - DIR_PUTCRDATE(direntry, fattime >> 16); + /* Put the short file name entry data */ - fs->fs_dirty = true; - return OK; + return fat_putsfdirentry(fs, dirinfo, attributes, fattime); } /**************************************************************************** @@ -1876,17 +2439,21 @@ int fat_dircreate(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) uint32_t fattime; int ret; - /* Allocate a directory entry entry */ + /* 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 set up directory entry */ + /* Failed to allocate the required directory entry or entries. */ return ret; } - /* Write the directory entry with the current time and the ARCHIVE attribute */ + /* 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); @@ -1936,9 +2503,7 @@ int fat_remove(struct fat_mountpt_s *fs, const char *relpath, bool directory) return -EACCES; } - /* Get the directory sector and cluster containing the - * entry to be deleted - */ + /* Get the directory sector and cluster containing the entry to be deleted. */ dircluster = ((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) | @@ -1967,9 +2532,8 @@ int fat_remove(struct fat_mountpt_s *fs, const char *relpath, bool directory) 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. + /* 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 (;;) @@ -1977,8 +2541,8 @@ int fat_remove(struct fat_mountpt_s *fs, const char *relpath, bool directory) unsigned int subdirindex; uint8_t *subdirentry; - /* Make sure that the sector containing the of the - * subdirectory sector is in the cache + /* Make sure that the sector containing the of the subdirectory + * sector is in the cache */ ret = fat_fscacheread(fs, dirinfo.dir.fd_currsector); @@ -2036,7 +2600,9 @@ int fat_remove(struct fat_mountpt_s *fs, const char *relpath, bool directory) } } - /* Mark the directory entry 'deleted' */ + /* 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) -- cgit v1.2.3