/**************************************************************************** * fs_fat32.c * * Copyright (C) 2007-2009, 2011 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * References: * Microsoft FAT documentation * Some good ideas were leveraged from the FAT implementation: * 'Copyright (C) 2007, ChaN, all right reserved.' * which has an unrestricted license. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fs_internal.h" #include "fs_fat32.h" /**************************************************************************** * Definitions ****************************************************************************/ /**************************************************************************** * Private Types ****************************************************************************/ /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int fat_open(FAR struct file *filep, const char *relpath, int oflags, mode_t mode); static int fat_close(FAR struct file *filep); static ssize_t fat_read(FAR struct file *filep, char *buffer, size_t buflen); static ssize_t fat_write(FAR struct file *filep, const char *buffer, size_t buflen); static off_t fat_seek(FAR struct file *filep, off_t offset, int whence); static int fat_ioctl(FAR struct file *filep, int cmd, unsigned long arg); static int fat_sync(FAR struct file *filep); static int fat_opendir(struct inode *mountpt, const char *relpath, struct fs_dirent_s *dir); static int fat_readdir(struct inode *mountpt, struct fs_dirent_s *dir); static int fat_rewinddir(struct inode *mountpt, struct fs_dirent_s *dir); static int fat_bind(FAR struct inode *blkdriver, const void *data, void **handle); static int fat_unbind(void *handle, FAR struct inode **blkdriver); static int fat_statfs(struct inode *mountpt, struct statfs *buf); static int fat_unlink(struct inode *mountpt, const char *relpath); static int fat_mkdir(struct inode *mountpt, const char *relpath, mode_t mode); static int fat_rmdir(struct inode *mountpt, const char *relpath); static int fat_rename(struct inode *mountpt, const char *oldrelpath, const char *newrelpath); static int fat_stat(struct inode *mountpt, const char *relpath, struct stat *buf); /**************************************************************************** * Private Variables ****************************************************************************/ /**************************************************************************** * Public Variables ****************************************************************************/ /* See fs_mount.c -- this structure is explicitly externed there. * We use the old-fashioned kind of initializers so that this will compile * with any compiler. */ const struct mountpt_operations fat_operations = { fat_open, fat_close, fat_read, fat_write, fat_seek, fat_ioctl, fat_sync, fat_opendir, NULL, fat_readdir, fat_rewinddir, fat_bind, fat_unbind, fat_statfs, fat_unlink, fat_mkdir, fat_rmdir, fat_rename, fat_stat }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: fat_open ****************************************************************************/ static int fat_open(FAR struct file *filep, const char *relpath, int oflags, mode_t mode) { struct fat_dirinfo_s dirinfo; struct inode *inode; struct fat_mountpt_s *fs; struct fat_file_s *ff; int ret; /* Sanity checks */ DEBUGASSERT(filep->f_priv == NULL && filep->f_inode != NULL); /* Get the mountpoint inode reference from the file structure and the * mountpoint private data from the inode structure */ inode = filep->f_inode; fs = inode->i_private; DEBUGASSERT(fs != NULL); /* Check if the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_semaphore; } /* Initialize the directory info structure */ memset(&dirinfo, 0, sizeof(struct fat_dirinfo_s)); /* Locate the directory entry for this path */ ret = fat_finddirentry(fs, &dirinfo, relpath); /* Three possibililities: (1) a node exists for the relpath and * dirinfo describes the directory entry of the entity, (2) the * node does not exist, or (3) some error occurred. */ if (ret == OK) { bool readonly; /* The name exists -- but is it a file or a directory? */ if (dirinfo.fd_entry == NULL || (DIR_GETATTRIBUTES(dirinfo.fd_entry) & FATATTR_DIRECTORY)) { /* It is a directory */ ret = -EISDIR; goto errout_with_semaphore; } /* It would be an error if we are asked to create it exclusively */ if ((oflags & (O_CREAT|O_EXCL)) == (O_CREAT|O_EXCL)) { /* Already exists -- can't create it exclusively */ ret = -EEXIST; goto errout_with_semaphore; } #ifdef CONFIG_FILE_MODE # warning "Missing check for privileges based on inode->i_mode" #endif /* Check if the caller has sufficient privileges to open the file */ readonly = ((DIR_GETATTRIBUTES(dirinfo.fd_entry) & FATATTR_READONLY) != 0); if (((oflags & O_WRONLY) != 0) && readonly) { ret = -EACCES; goto errout_with_semaphore; } /* If O_TRUNC is specified and the file is opened for writing, * then truncate the file. This operation requires that the file is * writable, but we have already checked that. O_TRUNC without write * access is ignored. */ if ((oflags & (O_TRUNC|O_WRONLY)) == (O_TRUNC|O_WRONLY)) { /* Truncate the file to zero length */ ret = fat_dirtruncate(fs, &dirinfo); if (ret < 0) { goto errout_with_semaphore; } } /* fall through to finish the file open operations */ } else if (ret == -ENOENT) { /* The file does not exist. Were we asked to create it? */ if ((oflags & O_CREAT) == 0) { /* No.. then we fail with -ENOENT */ ret = -ENOENT; goto errout_with_semaphore; } /* Yes.. create the file */ ret = fat_dircreate(fs, &dirinfo); if (ret < 0) { goto errout_with_semaphore; } /* Fall through to finish the file open operation */ } else { /* An error occurred while checking for file existence -- * such as if an invalid path were provided. */ goto errout_with_semaphore; } /* Create an instance of the file private date to describe the opened * file. */ ff = (struct fat_file_s *)zalloc(sizeof(struct fat_file_s)); if (!ff) { ret = -ENOMEM; goto errout_with_semaphore; } /* Create a file buffer to support partial sector accesses */ ff->ff_buffer = (uint8_t*)malloc(fs->fs_hwsectorsize); if (!ff->ff_buffer) { ret = -ENOMEM; goto errout_with_struct; } /* Initialize the file private data (only need to initialize non-zero elements) */ ff->ff_open = true; ff->ff_oflags = oflags; /* Save information that can be used later to recover the directory entry */ ff->ff_dirsector = fs->fs_currentsector; ff->ff_dirindex = dirinfo.dir.fd_index; /* File cluster/size info */ ff->ff_startcluster = ((uint32_t)DIR_GETFSTCLUSTHI(dirinfo.fd_entry) << 16) | DIR_GETFSTCLUSTLO(dirinfo.fd_entry); ff->ff_currentcluster = ff->ff_startcluster; ff->ff_sectorsincluster = fs->fs_fatsecperclus; ff->ff_size = DIR_GETFILESIZE(dirinfo.fd_entry); /* Attach the private date to the struct file instance */ filep->f_priv = ff; /* Then insert the new instance into the mountpoint structure. * It needs to be there (1) to handle error conditions that effect * all files, and (2) to inform the umount logic that we are busy * (but a simple reference count could have done that). */ ff->ff_next = fs->fs_head; fs->fs_head = ff->ff_next; fat_semgive(fs); /* In write/append mode, we need to set the file pointer to the end of the file */ if ((oflags & (O_APPEND|O_WRONLY)) == (O_APPEND|O_WRONLY)) { ssize_t offset = (ssize_t)fat_seek(filep, ff->ff_size, SEEK_SET); if (offset < 0) { free(ff); return (int)offset; } } return OK; /* Error exits -- goto's are nasty things, but they sure can make error * handling a lot simpler. */ errout_with_struct: free(ff); errout_with_semaphore: fat_semgive(fs); return ret; } /**************************************************************************** * Name: fat_close ****************************************************************************/ static int fat_close(FAR struct file *filep) { struct inode *inode; struct fat_mountpt_s *fs; struct fat_file_s *ff; int ret = OK; /* Sanity checks */ DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL); /* Recover our private data from the struct file instance */ ff = filep->f_priv; inode = filep->f_inode; fs = inode->i_private; DEBUGASSERT(fs != NULL); /* Do not check if the mount is healthy. We must support closing of * the file even when there is healthy mount. */ /* Synchronize the file buffers and disk content; update times */ ret = fat_sync(filep); /* Then deallocate the memory structures created when the open method * was called. * * Free the sector buffer that was used to manage partial sector accesses. */ if (ff->ff_buffer) { free(ff->ff_buffer); } /* Then free the file structure itself. */ free(ff); filep->f_priv = NULL; return ret; } /**************************************************************************** * Name: fat_read ****************************************************************************/ static ssize_t fat_read(FAR struct file *filep, char *buffer, size_t buflen) { struct inode *inode; struct fat_mountpt_s *fs; struct fat_file_s *ff; unsigned int bytesread; unsigned int readsize; unsigned int nsectors; size_t bytesleft; int32_t cluster; uint8_t *userbuffer = (uint8_t*)buffer; int sectorindex; int ret; /* Sanity checks */ DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL); /* Recover our private data from the struct file instance */ ff = filep->f_priv; inode = filep->f_inode; fs = inode->i_private; DEBUGASSERT(fs != NULL); /* Make sure that the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_semaphore; } /* Check if the file was opened with read access */ if ((ff->ff_oflags & O_RDOK) == 0) { ret = -EACCES; goto errout_with_semaphore; } /* Get the number of bytes left in the file */ bytesleft = ff->ff_size - filep->f_pos; /* Truncate read count so that it does not exceed the number * of bytes left in the file. */ if (buflen > bytesleft) { buflen = bytesleft; } /* Get the first sector to read from. */ if (!ff->ff_currentsector) { /* The current sector can be determined from the current cluster * and the file offset. */ ret = fat_currentsector(fs, ff, filep->f_pos); if (ret < 0) { return ret; } } /* Loop until either (1) all data has been transferred, or (2) an * error occurs. We assume we start with the current sector * (ff_currentsector) which may be uninitialized. */ readsize = 0; sectorindex = filep->f_pos & SEC_NDXMASK(fs); while (buflen > 0) { bytesread = 0; /* Check if the user has provided a buffer large enough to * hold one or more complete sectors -AND- the read is * aligned to a sector boundary. */ nsectors = buflen / fs->fs_hwsectorsize; if (nsectors > 0 && sectorindex == 0) { /* Read maximum contiguous sectors directly to the user's * buffer without using our tiny read buffer. * * Limit the number of sectors that we read on this time * through the loop to the remaining contiguous sectors * in this cluster */ if (nsectors > ff->ff_sectorsincluster) { nsectors = ff->ff_sectorsincluster; } /* We are not sure of the state of the file buffer so * the safest thing to do is just invalidate it */ (void)fat_ffcacheinvalidate(fs, ff); /* Read all of the sectors directly into user memory */ ret = fat_hwread(fs, userbuffer, ff->ff_currentsector, nsectors); if (ret < 0) { goto errout_with_semaphore; } ff->ff_sectorsincluster -= nsectors; ff->ff_currentsector += nsectors; bytesread = nsectors * fs->fs_hwsectorsize; } else { /* We are reading a partial sector. First, read the whole sector * into the file data buffer. This is a caching buffer so if * it is already there then all is well. */ ret = fat_ffcacheread(fs, ff, ff->ff_currentsector); if (ret < 0) { goto errout_with_semaphore; } /* Copy the partial sector into the user buffer */ bytesread = fs->fs_hwsectorsize - sectorindex; if (bytesread > buflen) { /* We will not read to the end of the buffer */ bytesread = buflen; } else { /* We will read to the end of the buffer (or beyond) */ ff->ff_sectorsincluster--; ff->ff_currentsector++; } memcpy(userbuffer, &ff->ff_buffer[sectorindex], bytesread); } /* Set up for the next sector read */ userbuffer += bytesread; filep->f_pos += bytesread; readsize += bytesread; buflen -= bytesread; sectorindex = filep->f_pos & SEC_NDXMASK(fs); /* Check if the current read stream has incremented to the next * cluster boundary */ if (ff->ff_sectorsincluster < 1) { /* Find the next cluster in the FAT. */ cluster = fat_getcluster(fs, ff->ff_currentcluster); if (cluster < 2 || cluster >= fs->fs_nclusters) { ret = -EINVAL; /* Not the right error */ goto errout_with_semaphore; } /* Setup to read the first sector from the new cluster */ ff->ff_currentcluster = cluster; ff->ff_currentsector = fat_cluster2sector(fs, cluster); ff->ff_sectorsincluster = fs->fs_fatsecperclus; } } fat_semgive(fs); return readsize; errout_with_semaphore: fat_semgive(fs); return ret; } /**************************************************************************** * Name: fat_write ****************************************************************************/ static ssize_t fat_write(FAR struct file *filep, const char *buffer, size_t buflen) { struct inode *inode; struct fat_mountpt_s *fs; struct fat_file_s *ff; int32_t cluster; unsigned int byteswritten; unsigned int writesize; unsigned int nsectors; uint8_t *userbuffer = (uint8_t*)buffer; int sectorindex; int ret; /* Sanity checks */ DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL); /* Recover our private data from the struct file instance */ ff = filep->f_priv; inode = filep->f_inode; fs = inode->i_private; DEBUGASSERT(fs != NULL); /* Make sure that the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_semaphore; } /* Check if the file was opened for write access */ if ((ff->ff_oflags & O_WROK) == 0) { ret = -EACCES; goto errout_with_semaphore; } /* Check if the file size would exceed the range of size_t */ if (ff->ff_size + buflen < ff->ff_size) { ret = -EFBIG; goto errout_with_semaphore; } /* Get the first sector to write to. */ if (!ff->ff_currentsector) { /* Has the starting cluster been defined? */ if (ff->ff_startcluster == 0) { /* No.. we have to create a new cluster chain */ ff->ff_startcluster = fat_createchain(fs); ff->ff_currentcluster = ff->ff_startcluster; ff->ff_sectorsincluster = fs->fs_fatsecperclus; } /* The current sector can then be determined from the currentcluster * and the file offset. */ ret = fat_currentsector(fs, ff, filep->f_pos); if (ret < 0) { return ret; } } /* Loop until either (1) all data has been transferred, or (2) an * error occurs. We assume we start with the current sector in * cache (ff_currentsector) */ byteswritten = 0; sectorindex = filep->f_pos & SEC_NDXMASK(fs); while (buflen > 0) { /* Check if there is unwritten data in the file buffer */ ret = fat_ffcacheflush(fs, ff); if (ret < 0) { goto errout_with_semaphore; } /* Check if the user has provided a buffer large enough to * hold one or more complete sectors. */ nsectors = buflen / fs->fs_hwsectorsize; if (nsectors > 0 && sectorindex == 0) { /* Write maximum contiguous sectors directly from the user's * buffer without using our tiny read buffer. * * Limit the number of sectors that we write on this time * through the loop to the remaining contiguous sectors * in this cluster */ if (nsectors > ff->ff_sectorsincluster) { nsectors = ff->ff_sectorsincluster; } /* We are not sure of the state of the file buffer so * the safest thing to do is just invalidate it */ (void)fat_ffcacheinvalidate(fs, ff); /* Write all of the sectors directly from user memory */ ret = fat_hwwrite(fs, userbuffer, ff->ff_currentsector, nsectors); if (ret < 0) { goto errout_with_semaphore; } ff->ff_sectorsincluster -= nsectors; ff->ff_currentsector += nsectors; writesize = nsectors * fs->fs_hwsectorsize; ff->ff_bflags |= FFBUFF_MODIFIED; } else { /* We are writing a partial sector -OR- the current sector * has not yet been filled. * * We will first have to read the full sector in memory as * part of a read-modify-write operation. NOTE we don't * have to read the data on a rare case: When we are extending * the file (filep->f_pos == ff->ff_size) -AND- the new data * happens to be aligned at the beginning of the sector * (sectorindex == 0). */ if (filep->f_pos < ff->ff_size || sectorindex != 0) { ret = fat_ffcacheread(fs, ff, ff->ff_currentsector); if (ret < 0) { goto errout_with_semaphore; } } /* Copy the partial sector from the user buffer */ writesize = fs->fs_hwsectorsize - sectorindex; if (writesize > buflen) { /* We will not write to the end of the buffer */ writesize = buflen; } else { /* We will write to the end of the buffer (or beyond) */ ff->ff_sectorsincluster--; ff->ff_currentsector++; } memcpy(&ff->ff_buffer[sectorindex], userbuffer, writesize); ff->ff_bflags |= (FFBUFF_DIRTY|FFBUFF_VALID|FFBUFF_MODIFIED); ff->ff_cachesector = ff->ff_currentsector; } /* Set up for the next write */ userbuffer += writesize; filep->f_pos += writesize; byteswritten += writesize; buflen -= writesize; sectorindex = filep->f_pos & SEC_NDXMASK(fs); /* Check if the current read stream has incremented to the next * cluster boundary */ if (ff->ff_sectorsincluster < 1) { /* Extend the current cluster by one (unless lseek was used to * move the file position back from the end of the file) */ cluster = fat_extendchain(fs, ff->ff_currentcluster); /* Verify the cluster number */ if (cluster < 0) { ret = cluster; goto errout_with_semaphore; } else if (cluster < 2 || cluster >= fs->fs_nclusters) { ret = -ENOSPC; goto errout_with_semaphore; } /* Setup to write the first sector from the new cluster */ ff->ff_currentcluster = cluster; ff->ff_sectorsincluster = fs->fs_fatsecperclus; ff->ff_currentsector = fat_cluster2sector(fs, cluster); } } /* The transfer has completed without error. Update the file size */ if (filep->f_pos > ff->ff_size) { ff->ff_size = filep->f_pos; } fat_semgive(fs); return byteswritten; errout_with_semaphore: fat_semgive(fs); return ret; } /**************************************************************************** * Name: fat_seek ****************************************************************************/ static off_t fat_seek(FAR struct file *filep, off_t offset, int whence) { struct inode *inode; struct fat_mountpt_s *fs; struct fat_file_s *ff; int32_t cluster; ssize_t position; unsigned int clustersize; int ret; /* Sanity checks */ DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL); /* Recover our private data from the struct file instance */ ff = filep->f_priv; inode = filep->f_inode; fs = inode->i_private; DEBUGASSERT(fs != NULL); /* Map the offset according to the whence option */ switch (whence) { case SEEK_SET: /* The offset is set to offset bytes. */ position = offset; break; case SEEK_CUR: /* The offset is set to its current location plus * offset bytes. */ position = offset + filep->f_pos; break; case SEEK_END: /* The offset is set to the size of the file plus * offset bytes. */ position = offset + ff->ff_size; break; default: return -EINVAL; } /* Make sure that the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_semaphore; } /* Check if there is unwritten data in the file buffer */ ret = fat_ffcacheflush(fs, ff); if (ret < 0) { goto errout_with_semaphore; } /* Attempts to set the position beyound the end of file will * work if the file is open for write access. */ if (position > ff->ff_size && (ff->ff_oflags & O_WROK) == 0) { /* Otherwise, the position is limited to the file size */ position = ff->ff_size; } /* Set file position to the beginning of the file (first cluster, * first sector in cluster) */ filep->f_pos = 0; ff->ff_sectorsincluster = fs->fs_fatsecperclus; /* Get the start cluster of the file */ cluster = ff->ff_startcluster; /* Create a new cluster chain if the file does not have one (and * if we are seeking beyond zero */ if (!cluster && position > 0) { cluster = fat_createchain(fs); if (cluster < 0) { ret = cluster; goto errout_with_semaphore; } ff->ff_startcluster = cluster; } /* Move file position if necessary */ if (cluster) { /* If the file has a cluster chain, follow it to the * requested position. */ clustersize = fs->fs_fatsecperclus * fs->fs_hwsectorsize; for (;;) { /* Skip over clusters prior to the one containing * the requested position. */ ff->ff_currentcluster = cluster; if (position < clustersize) { break; } /* Extend the cluster chain if write in enabled. NOTE: * this is not consistent with the lseek description: * "The lseek() function allows the file offset to be * set beyond the end of the file (but this does not * change the size of the file). If data is later written * at this point, subsequent reads of the data in the * gap (a "hole") return null bytes ('\0') until data * is actually written into the gap." */ if ((ff->ff_oflags & O_WROK) != 0) { /* Extend the cluster chain (fat_extendchain * will follow the existing chain or add new * clusters as needed. */ cluster = fat_extendchain(fs, cluster); } else { /* Otherwise we can only follong the existing chain */ cluster = fat_getcluster(fs, cluster); } if (cluster < 0) { /* An error occurred getting the cluster */ ret = cluster; goto errout_with_semaphore; } /* Zero means that there is no further clusters available * in the chain. */ if (cluster == 0) { /* At the position to the current locaiton and * break out. */ position = clustersize; break; } if (cluster >= fs->fs_nclusters) { ret = -ENOSPC; goto errout_with_semaphore; } /* Otherwise, update the position and continue looking */ filep->f_pos += clustersize; position -= clustersize; } /* We get here after we have found the sector containing * the requested position. * * Save the new file position */ filep->f_pos += position; /* Then get the current sector from the cluster and the offset * into the cluster from the position */ (void)fat_currentsector(fs, ff, filep->f_pos); /* Load the sector corresponding to the position */ if ((position & SEC_NDXMASK(fs)) != 0) { ret = fat_ffcacheread(fs, ff, ff->ff_currentsector); if (ret < 0) { goto errout_with_semaphore; } } } /* If we extended the size of the file, then mark the file as modified. */ if ((ff->ff_oflags & O_WROK) != 0 && filep->f_pos > ff->ff_size) { ff->ff_size = filep->f_pos; ff->ff_bflags |= FFBUFF_MODIFIED; } fat_semgive(fs); return OK; errout_with_semaphore: fat_semgive(fs); return ret; } /**************************************************************************** * Name: fat_ioctl ****************************************************************************/ static int fat_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { struct inode *inode; struct fat_mountpt_s *fs; struct fat_file_s *ff; int ret; /* Sanity checks */ DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL); /* Recover our private data from the struct file instance */ ff = filep->f_priv; inode = filep->f_inode; fs = inode->i_private; DEBUGASSERT(fs != NULL); /* Make sure that the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret != OK) { fat_semgive(fs); return ret; } /* ioctl calls are just passed through to the contained block driver */ fat_semgive(fs); return -ENOSYS; } /**************************************************************************** * Name: fat_sync * * Description: Synchronize the file state on disk to match internal, in- * memory state. * ****************************************************************************/ static int fat_sync(FAR struct file *filep) { struct inode *inode; struct fat_mountpt_s *fs; struct fat_file_s *ff; uint32_t wrttime; uint8_t *direntry; int ret; /* Sanity checks */ DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL); /* Recover our private data from the struct file instance */ ff = filep->f_priv; inode = filep->f_inode; fs = inode->i_private; DEBUGASSERT(fs != NULL); /* Make sure that the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_semaphore; } /* Check if the has been modified in any way */ if ((ff->ff_bflags & FFBUFF_MODIFIED) != 0) { /* Flush any unwritten data in the file buffer */ ret = fat_ffcacheflush(fs, ff); if (ret < 0) { goto errout_with_semaphore; } /* Update the directory entry. First read the directory * entry into the fs_buffer (preserving the ff_buffer) */ ret = fat_fscacheread(fs, ff->ff_dirsector); if (ret < 0) { goto errout_with_semaphore; } /* Recover a pointer to the specific directory entry * in the sector using the saved directory index. */ direntry = &fs->fs_buffer[ff->ff_dirindex * 32]; /* Set the archive bit, set the write time, and update * anything that may have* changed in the directory * entry: the file size, and the start cluster */ direntry[DIR_ATTRIBUTES] |= FATATTR_ARCHIVE; DIR_PUTFILESIZE(direntry, ff->ff_size); DIR_PUTFSTCLUSTLO(direntry, ff->ff_startcluster); DIR_PUTFSTCLUSTHI(direntry, ff->ff_startcluster >> 16); wrttime = fat_systime2fattime(); DIR_PUTWRTTIME(direntry, wrttime & 0xffff); DIR_PUTWRTDATE(direntry, wrttime >> 16); /* Clear the modified bit in the flags */ ff->ff_bflags &= ~FFBUFF_MODIFIED; /* Flush these change to disk and update FSINFO (if * appropriate. */ fs->fs_dirty = true; ret = fat_updatefsinfo(fs); } errout_with_semaphore: fat_semgive(fs); return ret; } /**************************************************************************** * Name: fat_opendir * * Description: Open a directory for read access * ****************************************************************************/ static int fat_opendir(struct inode *mountpt, const char *relpath, struct fs_dirent_s *dir) { struct fat_mountpt_s *fs; struct fat_dirinfo_s dirinfo; int ret; /* Sanity checks */ DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); /* Recover our private data from the inode instance */ fs = mountpt->i_private; /* Make sure that the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_semaphore; } /* Find the requested directory */ ret = fat_finddirentry(fs, &dirinfo, relpath); if (ret < 0) { goto errout_with_semaphore; } /* Check if this is the root directory */ if (dirinfo.fd_entry == NULL) { /* Handle the FAT12/16/32 root directory using the values setup by * fat_finddirentry() above. */ dir->u.fat.fd_startcluster = dirinfo.dir.fd_startcluster; dir->u.fat.fd_currcluster = dirinfo.dir.fd_currcluster; dir->u.fat.fd_currsector = dirinfo.dir.fd_currsector; dir->u.fat.fd_index = dirinfo.dir.fd_index; } /* This is not the root directory. Verify that it is some kind of directory */ else if ((DIR_GETATTRIBUTES(dirinfo.fd_entry) & FATATTR_DIRECTORY) == 0) { /* The entry is not a directory */ ret = -ENOTDIR; goto errout_with_semaphore; } else { /* The entry is a directory */ dir->u.fat.fd_startcluster = ((uint32_t)DIR_GETFSTCLUSTHI(dirinfo.fd_entry) << 16) | DIR_GETFSTCLUSTLO(dirinfo.fd_entry); dir->u.fat.fd_currcluster = dir->u.fat.fd_startcluster; dir->u.fat.fd_currsector = fat_cluster2sector(fs, dir->u.fat.fd_currcluster); dir->u.fat.fd_index = 2; } fat_semgive(fs); return OK; errout_with_semaphore: fat_semgive(fs); return ERROR; } /**************************************************************************** * Name: fat_readdir * * Description: Read the next directory entry * ****************************************************************************/ static int fat_readdir(struct inode *mountpt, struct fs_dirent_s *dir) { struct fat_mountpt_s *fs; unsigned int dirindex; uint8_t *direntry; uint8_t ch; uint8_t attribute; int ret = OK; /* Sanity checks */ DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); /* Recover our private data from the inode instance */ fs = mountpt->i_private; /* Make sure that the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_semaphore; } /* 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') { ret = fat_fscacheread(fs, dir->u.fat.fd_currsector); if (ret < 0) { goto errout_with_semaphore; } /* Get a reference to the current directory entry */ dirindex = (dir->u.fat.fd_index & DIRSEC_NDXMASK(fs)) * 32; direntry = &fs->fs_buffer[dirindex]; /* Has it reached to end of the directory */ ch = *direntry; if (ch == DIR0_ALLEMPTY) { /* We signal the end of the directory by returning the * special error -ENOENT */ ret = -ENOENT; goto errout_with_semaphore; } /* No, is the current entry a valid entry? */ attribute = DIR_GETATTRIBUTES(direntry); if (ch != DIR0_EMPTY && (attribute & FATATTR_VOLUMEID) == 0) { /* Yes.. get the name from the directory info */ (void)fat_dirname2path(dir->fd_dir.d_name, direntry); /* And the file type */ if ((attribute & FATATTR_DIRECTORY) == 0) { dir->fd_dir.d_type = DTYPE_FILE; } else { dir->fd_dir.d_type = DTYPE_DIRECTORY; } } /* Set up the next directory index */ if (fat_nextdirentry(fs, &dir->u.fat) != OK) { ret = -ENOENT; goto errout_with_semaphore; } } fat_semgive(fs); return OK; errout_with_semaphore: fat_semgive(fs); return ret; } /**************************************************************************** * Name: fat_rewindir * * Description: Reset directory read to the first entry * ****************************************************************************/ static int fat_rewinddir(struct inode *mountpt, struct fs_dirent_s *dir) { struct fat_mountpt_s *fs; int ret; /* Sanity checks */ DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); /* Recover our private data from the inode instance */ fs = mountpt->i_private; /* Make sure that the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_semaphore; } /* Check if this is the root directory */ if (fs->fs_type != FSTYPE_FAT32 && dir->u.fat.fd_startcluster == 0) { /* Handle the FAT12/16 root directory */ dir->u.fat.fd_currcluster = 0; dir->u.fat.fd_currsector = fs->fs_rootbase; dir->u.fat.fd_index = 0; } else if (fs->fs_type == FSTYPE_FAT32 && dir->u.fat.fd_startcluster == fs->fs_rootbase) { /* Handle the FAT32 root directory */ dir->u.fat.fd_currcluster = dir->u.fat.fd_startcluster; dir->u.fat.fd_currsector = fat_cluster2sector(fs, fs->fs_rootbase); dir->u.fat.fd_index = 0; } /* This is not the root directory */ else { dir->u.fat.fd_currcluster = dir->u.fat.fd_startcluster; dir->u.fat.fd_currsector = fat_cluster2sector(fs, dir->u.fat.fd_currcluster); dir->u.fat.fd_index = 2; } fat_semgive(fs); return OK; errout_with_semaphore: fat_semgive(fs); return ERROR; } /**************************************************************************** * Name: fat_bind * * Description: This implements a portion of the mount operation. This * function allocates and initializes the mountpoint private data and * binds the blockdriver inode to the filesystem private data. The final * binding of the private data (containing the blockdriver) to the * mountpoint is performed by mount(). * ****************************************************************************/ static int fat_bind(FAR struct inode *blkdriver, const void *data, void **handle) { struct fat_mountpt_s *fs; int ret; /* Open the block driver */ if (!blkdriver || !blkdriver->u.i_bops) { return -ENODEV; } if (blkdriver->u.i_bops->open && blkdriver->u.i_bops->open(blkdriver) != OK) { return -ENODEV; } /* Create an instance of the mountpt state structure */ fs = (struct fat_mountpt_s *)zalloc(sizeof(struct fat_mountpt_s)); if (!fs) { return -ENOMEM; } /* Initialize the allocated mountpt state structure. The filesystem is * responsible for one reference ont the blkdriver inode and does not * have to addref() here (but does have to release in ubind(). */ fs->fs_blkdriver = blkdriver; /* Save the block driver reference */ sem_init(&fs->fs_sem, 0, 0); /* Initialize the semaphore that controls access */ /* Then get information about the FAT32 filesystem on the devices managed * by this block driver. */ ret = fat_mount(fs, true); if (ret != 0) { sem_destroy(&fs->fs_sem); free(fs); return ret; } *handle = (void*)fs; fat_semgive(fs); return OK; } /**************************************************************************** * Name: fat_unbind * * Description: This implements the filesystem portion of the umount * operation. * ****************************************************************************/ static int fat_unbind(void *handle, FAR struct inode **blkdriver) { struct fat_mountpt_s *fs = (struct fat_mountpt_s*)handle; int ret; if (!fs) { return -EINVAL; } /* Check if there are sill any files opened on the filesystem. */ ret = OK; /* Assume success */ fat_semtake(fs); if (fs->fs_head) { /* We cannot unmount now.. there are open files */ ret = -EBUSY; } else { /* Unmount ... close the block driver */ if (fs->fs_blkdriver) { struct inode *inode = fs->fs_blkdriver; if (inode) { if (inode->u.i_bops && inode->u.i_bops->close) { (void)inode->u.i_bops->close(inode); } /* We hold a reference to the block driver but should * not but mucking with inodes in this context. So, we will just return * our contained reference to the block driver inode and let the umount * logic dispose of it. */ if (blkdriver) { *blkdriver = inode; } } } /* Release the mountpoint private data */ if (fs->fs_buffer) { free(fs->fs_buffer); } free(fs); } fat_semgive(fs); return ret; } /**************************************************************************** * Name: fat_statfs * * Description: Return filesystem statistics * ****************************************************************************/ static int fat_statfs(struct inode *mountpt, struct statfs *buf) { struct fat_mountpt_s *fs; int ret; /* Sanity checks */ DEBUGASSERT(mountpt && mountpt->i_private); /* Get the mountpoint private data from the inode structure */ fs = mountpt->i_private; /* Check if the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret < 0) { goto errout_with_semaphore; } /* Fill in the statfs info */ memset(buf, 0, sizeof(struct statfs)); buf->f_type = MSDOS_SUPER_MAGIC; /* We will claim that the optimal transfer size is the size of a cluster in bytes */ buf->f_bsize = fs->fs_fatsecperclus * fs->fs_hwsectorsize; /* Everything else follows in units of clusters */ buf->f_blocks = fs->fs_nclusters; /* Total data blocks in the file system */ buf->f_bfree = fat_nfreeclusters(fs, &buf->f_bfree); /* Free blocks in the file system */ buf->f_bavail = buf->f_bfree; /* Free blocks avail to non-superuser */ buf->f_namelen = (8+1+3); /* Maximum length of filenames */ fat_semgive(fs); return OK; errout_with_semaphore: fat_semgive(fs); return ret; } /**************************************************************************** * Name: fat_unlink * * Description: Remove a file * ****************************************************************************/ static int fat_unlink(struct inode *mountpt, const char *relpath) { struct fat_mountpt_s *fs; int ret; /* Sanity checks */ DEBUGASSERT(mountpt && mountpt->i_private); /* Get the mountpoint private data from the inode structure */ fs = mountpt->i_private; /* Check if the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret == OK) { /* If the file is open, the correct behavior is to remove the file * name, but to keep the file cluster chain in place until the last * open reference to the file is closed. */ #ifdef CONFIG_CPP_HAVE_WARNING # warning "Need to defer deleting cluster chain if the file is open" #endif /* Remove the file */ ret = fat_remove(fs, relpath, false); } fat_semgive(fs); return ret; } /**************************************************************************** * Name: fat_mkdir * * Description: Create a directory * ****************************************************************************/ static int fat_mkdir(struct inode *mountpt, const char *relpath, mode_t mode) { struct fat_mountpt_s *fs; struct fat_dirinfo_s dirinfo; uint8_t *direntry; uint8_t *direntry2; size_t parentsector; ssize_t dirsector; int32_t dircluster; uint32_t parentcluster; uint32_t crtime; unsigned int i; int ret; /* Sanity checks */ DEBUGASSERT(mountpt && mountpt->i_private); /* Get the mountpoint private data from the inode structure */ fs = mountpt->i_private; /* Check if the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_semaphore; } /* Find the directory where the new directory should be created. */ ret = fat_finddirentry(fs, &dirinfo, relpath); /* If anything exists at this location, then we fail with EEXIST */ if (ret == OK) { ret = -EEXIST; goto errout_with_semaphore; } /* What we want to see is for fat_finddirentry to fail with -ENOENT. * This error means that no failure occurred but that nothing exists * with this name. */ if (ret != -ENOENT) { goto errout_with_semaphore; } /* NOTE: There is no check that dirinfo.fd_name contains the final * directory name. We could be creating an intermediate directory * in the full relpath. */ /* Allocate a directory entry for the new directory in this directory */ ret = fat_allocatedirentry(fs, &dirinfo); if (ret != OK) { goto errout_with_semaphore; } parentsector = fs->fs_currentsector; /* Allocate a cluster for new directory */ dircluster = fat_createchain(fs); if (dircluster < 0) { ret = dircluster; goto errout_with_semaphore; } else if (dircluster < 2) { ret = -ENOSPC; goto errout_with_semaphore; } dirsector = fat_cluster2sector(fs, dircluster); if (dirsector < 0) { ret = dirsector; goto errout_with_semaphore; } /* Flush any existing, dirty data in fs_buffer (because we need * it to create the directory entries. */ ret = fat_fscacheflush(fs); if (ret < 0) { goto errout_with_semaphore; } /* Get a pointer to the first directory entry in the sector */ direntry = fs->fs_buffer; /* Now erase the contents of fs_buffer */ fs->fs_currentsector = dirsector; memset(direntry, 0, fs->fs_hwsectorsize); /* Now clear all sectors in the new directory cluster (except for the first) */ for (i = 1; i < fs->fs_fatsecperclus; i++) { ret = fat_hwwrite(fs, direntry, ++dirsector, 1); if (ret < 0) { goto errout_with_semaphore; } } /* Now create the "." directory entry in the first directory slot */ memset(&direntry[DIR_NAME], ' ', 8+3); direntry[DIR_NAME] = '.'; DIR_PUTATTRIBUTES(direntry, FATATTR_DIRECTORY); crtime = fat_systime2fattime(); DIR_PUTCRTIME(direntry, crtime & 0xffff); DIR_PUTWRTTIME(direntry, crtime & 0xffff); DIR_PUTCRDATE(direntry, crtime >> 16); DIR_PUTWRTDATE(direntry, crtime >> 16); /* Create ".." directory entry in the second directory slot */ direntry2 = direntry + 32; /* So far, the two entries are nearly the same */ memcpy(direntry2, direntry, 32); direntry2[DIR_NAME+1] = '.'; /* Now add the cluster information to both directory entries */ DIR_PUTFSTCLUSTHI(direntry, dircluster >> 16); DIR_PUTFSTCLUSTLO(direntry, dircluster); parentcluster = dirinfo.dir.fd_startcluster; if (fs->fs_type != FSTYPE_FAT32 && parentcluster == fs->fs_rootbase) { parentcluster = 0; } DIR_PUTFSTCLUSTHI(direntry2, parentcluster >> 16); DIR_PUTFSTCLUSTLO(direntry2, parentcluster); /* Save the first sector of the directory cluster and re-read * the parentsector */ fs->fs_dirty = true; ret = fat_fscacheread(fs, parentsector); if (ret < 0) { goto errout_with_semaphore; } /* Initialize the new entry directory entry in the parent directory */ direntry = dirinfo.fd_entry; memset(direntry, 0, 32); memcpy(direntry, dirinfo.fd_name, 8+3); #ifdef CONFIG_FLAT_LCNAMES DIR_PUTNTRES(direntry, dirinfo.fd_ntflags); #endif DIR_PUTATTRIBUTES(dirinfo.fd_entry, FATATTR_DIRECTORY); /* Same creation time as for . and .. */ DIR_PUTCRTIME(dirinfo.fd_entry, crtime & 0xffff); DIR_PUTWRTTIME(dirinfo.fd_entry, crtime & 0xffff); DIR_PUTCRDATE(dirinfo.fd_entry, crtime >> 16); DIR_PUTWRTDATE(dirinfo.fd_entry, crtime >> 16); /* Set subdirectory start cluster */ DIR_PUTFSTCLUSTLO(dirinfo.fd_entry, dircluster); DIR_PUTFSTCLUSTHI(dirinfo.fd_entry, dircluster >> 16); /* Now update the FAT32 FSINFO sector */ fs->fs_dirty = true; ret = fat_updatefsinfo(fs); if (ret < 0) { goto errout_with_semaphore; } fat_semgive(fs); return OK; errout_with_semaphore: fat_semgive(fs); return ret; } /**************************************************************************** * Name: fat_rmdir * * Description: Remove a directory * ****************************************************************************/ int fat_rmdir(struct inode *mountpt, const char *relpath) { struct fat_mountpt_s *fs; int ret; /* Sanity checks */ DEBUGASSERT(mountpt && mountpt->i_private); /* Get the mountpoint private data from the inode structure */ fs = mountpt->i_private; /* Check if the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret == OK) { /* If the directory is open, the correct behavior is to remove the directory * name, but to keep the directory cluster chain in place until the last * open reference to the directory is closed. */ #ifdef CONFIG_CPP_HAVE_WARNING # warning "Need to defer deleting cluster chain if the directory is open" #endif /* Remove the directory */ ret = fat_remove(fs, relpath, true); } fat_semgive(fs); return ret; } /**************************************************************************** * Name: fat_rename * * Description: Rename a file or directory * ****************************************************************************/ int fat_rename(struct inode *mountpt, const char *oldrelpath, const char *newrelpath) { struct fat_mountpt_s *fs; struct fat_dirinfo_s dirinfo; size_t oldsector; uint8_t *olddirentry; uint8_t *newdirentry; uint8_t dirstate[32-11]; int ret; /* Sanity checks */ DEBUGASSERT(mountpt && mountpt->i_private); /* Get the mountpoint private data from the inode structure */ fs = mountpt->i_private; /* Check if the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_semaphore; } /* Find the directory entry for the oldrelpath */ ret = fat_finddirentry(fs, &dirinfo, oldrelpath); if (ret != OK) { /* Some error occurred -- probably -ENOENT */ goto errout_with_semaphore; } /* Save the information that will need to recover the * directory sector and directory entry offset to the * old directory. */ olddirentry = dirinfo.fd_entry; /* One more check: Make sure that the oldrelpath does * not refer to the root directory. We can't rename the * root directory. */ if (!olddirentry) { ret = -EXDEV; goto errout_with_semaphore; } oldsector = fs->fs_currentsector; memcpy(dirstate, &olddirentry[DIR_ATTRIBUTES], 32-11); /* No find the directory where we should create the newpath object */ ret = fat_finddirentry(fs, &dirinfo, newrelpath); if (ret == OK) { /* It is an error if the object at newrelpath already exists */ ret = -EEXIST; goto errout_with_semaphore; } /* What we expect is -ENOENT mean that the full directory path was * followed but that the object does not exists in the terminal directory. */ if (ret != -ENOENT) { goto errout_with_semaphore; } /* Reserve a directory entry */ ret = fat_allocatedirentry(fs, &dirinfo); if (ret != OK) { goto errout_with_semaphore; } /* Create the new directory entry */ newdirentry = dirinfo.fd_entry; memcpy(&newdirentry[DIR_ATTRIBUTES], dirstate, 32-11); memcpy(&newdirentry[DIR_NAME], dirinfo.fd_name, 8+3); #ifdef CONFIG_FLAT_LCNAMES DIR_PUTNTRES(newdirentry, dirinfo.fd_ntflags); #else DIR_PUTNTRES(newdirentry, 0); #endif fs->fs_dirty = true; /* Now flush the new directory entry to disk and read the sector * containing the old directory entry. */ ret = fat_fscacheread(fs, oldsector); if (ret < 0) { goto errout_with_semaphore; } /* Remove the old entry */ olddirentry[DIR_NAME] = DIR0_EMPTY; fs->fs_dirty = true; /* Write the old entry to disk and update FSINFO if necessary */ ret = fat_updatefsinfo(fs); if (ret < 0) { goto errout_with_semaphore; } fat_semgive(fs); return OK; errout_with_semaphore: fat_semgive(fs); return ret; } /**************************************************************************** * Name: fat_stat * * Description: Return information about a file or directory * ****************************************************************************/ static int fat_stat(struct inode *mountpt, const char *relpath, struct stat *buf) { struct fat_mountpt_s *fs; struct fat_dirinfo_s dirinfo; uint16_t date; uint16_t date2; uint16_t time; uint8_t attribute; int ret; /* Sanity checks */ DEBUGASSERT(mountpt && mountpt->i_private); /* Get the mountpoint private data from the inode structure */ fs = mountpt->i_private; /* Check if the mount is still healthy */ fat_semtake(fs); ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_semaphore; } /* Find the directory entry corresponding to relpath. */ ret = fat_finddirentry(fs, &dirinfo, relpath); /* If nothing was found, then we fail with EEXIST */ if (ret < 0) { goto errout_with_semaphore; } memset(buf, 0, sizeof(struct stat)); if (!dirinfo.fd_entry) { /* It's directory name of mount point */ buf->st_mode = S_IFDIR|S_IROTH|S_IRGRP|S_IRUSR|S_IWOTH|S_IWGRP|S_IWUSR; ret = OK; goto errout_with_semaphore; } /* Get the FAT attribute and map it so some meaningful mode_t values */ attribute = DIR_GETATTRIBUTES(dirinfo.fd_entry); if ((attribute & FATATTR_VOLUMEID) != 0) { ret = -ENOENT; goto errout_with_semaphore; } /* Set the access permissions. The file/directory is always readable * by everyone but may be writeable by no-one. */ buf->st_mode = S_IROTH|S_IRGRP|S_IRUSR; if ((attribute & FATATTR_READONLY) == 0) { buf->st_mode |= S_IWOTH|S_IWGRP|S_IWUSR; } /* We will report only types file or directory */ if ((attribute & FATATTR_DIRECTORY) != 0) { buf->st_mode |= S_IFDIR; } else { buf->st_mode |= S_IFREG; } /* File/directory size, access block size */ buf->st_size = DIR_GETFILESIZE(dirinfo.fd_entry); buf->st_blksize = fs->fs_fatsecperclus * fs->fs_hwsectorsize; buf->st_blocks = (buf->st_size + buf->st_blksize - 1) / buf->st_blksize; /* Times */ date = DIR_GETWRTDATE(dirinfo.fd_entry); time = DIR_GETWRTTIME(dirinfo.fd_entry); buf->st_mtime = fat_fattime2systime(time, date); date2 = DIR_GETLASTACCDATE(dirinfo.fd_entry); if (date == date2) { buf->st_atime = buf->st_mtime; } else { buf->st_atime = fat_fattime2systime(0, date2); } date = DIR_GETCRDATE(dirinfo.fd_entry); time = DIR_GETCRTIME(dirinfo.fd_entry); buf->st_ctime = fat_fattime2systime(time, date); ret = OK; errout_with_semaphore: fat_semgive(fs); return ret; } /**************************************************************************** * Public Functions ****************************************************************************/