| /**************************************************************************** |
| * fs_fat32.c |
| * |
| * Copyright (C) 2007 Gregory Nutt. All rights reserved. |
| * Author: Gregory Nutt <spudmonkey@racsa.co.cr> |
| * |
| * References: |
| * Microsoft FAT documentation |
| * 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 Gregory Nutt 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 <nuttx/config.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/statfs.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <semaphore.h> |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/fs.h> |
| #include <nuttx/fat.h> |
| |
| #include "fs_internal.h" |
| #include "fs_fat32.h" |
| |
| /**************************************************************************** |
| * Definitions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| static int fat_open(FAR struct file *filp, const char *relpath, |
| int oflags, mode_t mode); |
| static int fat_close(FAR struct file *filp); |
| static ssize_t fat_read(FAR struct file *filp, char *buffer, size_t buflen); |
| static ssize_t fat_write(FAR struct file *filp, const char *buffer, |
| size_t buflen); |
| static off_t fat_seek(FAR struct file *filp, off_t offset, int whence); |
| static int fat_ioctl(FAR struct file *filp, int cmd, unsigned long arg); |
| static int fat_sync(FAR struct file *filp); |
| |
| static int fat_opendir(struct inode *mountpt, const char *relpath, |
| struct internal_dir_s *dir); |
| static int fat_readdir(struct inode *mountpt, struct internal_dir_s *dir); |
| static int fat_rewinddir(struct inode *mountpt, struct internal_dir_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 *filp, 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(filp->f_priv == NULL && filp->f_inode != NULL); |
| |
| /* Get the mountpoint inode reference from the file structure and the |
| * mountpoint private data from the inode structure |
| */ |
| |
| inode = filp->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) |
| { |
| boolean 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 = (ubyte*)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; |
| ff->ff_sectorsincluster = 1; |
| |
| /* 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)DIR_GETFSTCLUSTHI(dirinfo.fd_entry) << 16) | |
| DIR_GETFSTCLUSTLO(dirinfo.fd_entry); |
| |
| ff->ff_size = DIR_GETFILESIZE(dirinfo.fd_entry); |
| |
| /* 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)) |
| { |
| ff->ff_position = ff->ff_size; |
| } |
| |
| /* Attach the private date to the struct file instance */ |
| |
| filp->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); |
| 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 *filp) |
| { |
| struct inode *inode; |
| struct fat_mountpt_s *fs; |
| struct fat_file_s *ff; |
| int ret = OK; |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(filp->f_priv != NULL && filp->f_inode != NULL); |
| |
| /* Recover our private data from the struct file instance */ |
| |
| ff = filp->f_priv; |
| inode = filp->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(filp); |
| |
| /* 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); |
| filp->f_priv = NULL; |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: fat_read |
| ****************************************************************************/ |
| |
| static ssize_t fat_read(FAR struct file *filp, char *buffer, size_t buflen) |
| { |
| struct inode *inode; |
| struct fat_mountpt_s *fs; |
| struct fat_file_s *ff; |
| uint32 cluster; |
| unsigned int bytesread; |
| unsigned int readsize; |
| unsigned int nsectors; |
| size_t readsector; |
| size_t bytesleft; |
| ubyte *userbuffer = (ubyte*)buffer; |
| int ret; |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(filp->f_priv != NULL && filp->f_inode != NULL); |
| |
| /* Recover our private data from the struct file instance */ |
| |
| ff = filp->f_priv; |
| inode = filp->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 - ff->ff_position; |
| |
| /* Truncate read count so that it does not exceed the number |
| * of bytes left in the file. |
| */ |
| |
| if (buflen > bytesleft) |
| { |
| buflen = bytesleft; |
| } |
| |
| /* Loop until either (1) all data has been transferred, or (2) an |
| * error occurs. |
| */ |
| |
| readsize = 0; |
| while (buflen > 0) |
| { |
| /* Get offset into the sector where we begin the read */ |
| |
| int sectorindex = ff->ff_position & SEC_NDXMASK(fs); |
| |
| /* Check if the current read stream happens to lie on a |
| * sector boundary. |
| */ |
| |
| if (sectorindex == 0) |
| { |
| /* Try to read another contiguous sector from the cluster */ |
| |
| ff->ff_sectorsincluster--; |
| |
| /* Are there unread sectors remaining in the cluster? */ |
| |
| if (ff->ff_sectorsincluster > 0) |
| { |
| /* Yes.. There are more sectors in this cluster to be read |
| * just increment the current sector number and read. |
| */ |
| |
| readsector = ff->ff_currentsector + 1; |
| } |
| else |
| { |
| /* No.. Handle the case of the first sector of the file */ |
| |
| if (ff->ff_position == 0) |
| { |
| /* Get the first cluster of the file */ |
| |
| cluster = ff->ff_startcluster; |
| } |
| |
| /* But in the general case, we have to find the next cluster |
| * in the FAT. |
| */ |
| |
| else |
| { |
| cluster = fat_getcluster(fs, ff->ff_currentcluster); |
| } |
| |
| /* Verify the cluster number */ |
| |
| 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; |
| readsector = fat_cluster2sector(fs, cluster); |
| ff->ff_sectorsincluster = fs->fs_fatsecperclus; |
| } |
| |
| /* 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) |
| { |
| /* 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 directory into user memory */ |
| |
| ret = fat_hwread(fs, userbuffer, readsector, nsectors); |
| if (ret < 0) |
| { |
| goto errout_with_semaphore; |
| } |
| |
| ff->ff_sectorsincluster -= nsectors - 1; |
| 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, readsector); |
| if (ret < 0) |
| { |
| goto errout_with_semaphore; |
| } |
| |
| /* Copy the partial sector into the user buffer */ |
| |
| bytesread = fs->fs_hwsectorsize - sectorindex; |
| if (bytesread > buflen) |
| { |
| bytesread = buflen; |
| } |
| |
| memcpy(userbuffer, &ff->ff_buffer[sectorindex], bytesread); |
| } |
| |
| /* Set up for the next sector read */ |
| |
| userbuffer += bytesread; |
| ff->ff_position += bytesread; |
| readsize += bytesread; |
| buflen -= bytesread; |
| } |
| } |
| |
| fat_semgive(fs); |
| return readsize; |
| |
| errout_with_semaphore: |
| fat_semgive(fs); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: fat_write |
| ****************************************************************************/ |
| |
| static ssize_t fat_write(FAR struct file *filp, const char *buffer, |
| size_t buflen) |
| { |
| struct inode *inode; |
| struct fat_mountpt_s *fs; |
| struct fat_file_s *ff; |
| sint32 cluster; |
| size_t writesector; |
| unsigned int byteswritten; |
| unsigned int writesize; |
| unsigned int nsectors; |
| ubyte *userbuffer = (ubyte*)buffer; |
| int ret; |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(filp->f_priv != NULL && filp->f_inode != NULL); |
| |
| /* Recover our private data from the struct file instance */ |
| |
| ff = filp->f_priv; |
| inode = filp->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; |
| } |
| |
| /* Loop until either (1) all data has been transferred, or (2) an |
| * error occurs. |
| */ |
| |
| byteswritten = 0; |
| while (buflen > 0) |
| { |
| /* Get offset into the sector where we begin the read */ |
| |
| int sectorindex = ff->ff_position & SEC_NDXMASK(fs); |
| |
| /* Check if the current read stream happens to lie on a |
| * sector boundary. |
| */ |
| |
| if (sectorindex == 0) |
| { |
| /* Decrement the number of sectors left in this cluster */ |
| |
| ff->ff_sectorsincluster--; |
| |
| /* Are there unwritten sectors remaining in this cluster */ |
| |
| if (ff->ff_sectorsincluster > 0) |
| { |
| /* Yes.. There are more sectors in this cluster to be written. |
| * just increment the current sector number and write. |
| */ |
| |
| writesector = ff->ff_currentsector + 1; |
| } |
| else |
| { |
| /* No.. Handle the case of the first sector of the file */ |
| |
| if (ff->ff_position == 0) |
| { |
| /* Check the first cluster of the file. Zero means that |
| * the file is empty -- perhaps the file was truncated or |
| * created when it was opened |
| */ |
| |
| if (ff->ff_startcluster == 0) |
| { |
| /* In this case, we have to create a new cluster chain */ |
| |
| ff->ff_startcluster = fat_createchain(fs); |
| } |
| |
| /* Start writing at the first cluster of the file */ |
| |
| cluster = ff->ff_startcluster; |
| } |
| |
| /* But in the general case, we have to extend the current |
| * cluster by one (unless lseek was used to move the file |
| * position back from the end of the file) |
| */ |
| |
| else |
| { |
| /* Extend the chain by adding a new cluster after |
| * the last one |
| */ |
| |
| 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; |
| writesector = fat_cluster2sector(fs, cluster); |
| ff->ff_sectorsincluster = fs->fs_fatsecperclus; |
| } |
| } |
| |
| /* 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) |
| { |
| /* 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 directory from user memory */ |
| |
| ret = fat_hwwrite(fs, userbuffer, writesector, nsectors); |
| if (ret < 0) |
| { |
| goto errout_with_semaphore; |
| } |
| |
| ff->ff_sectorsincluster -= nsectors - 1; |
| writesize = nsectors * fs->fs_hwsectorsize; |
| ff->ff_bflags |= FFBUFF_MODIFIED; |
| } |
| else |
| { |
| /* We are write a partial sector. We will first have to |
| * read the full sector in memory as part of a read-modify-write |
| * operation. |
| */ |
| |
| if (ff->ff_position < ff->ff_size) |
| { |
| ff->ff_currentsector = writesector; |
| ret = fat_ffcacheread(fs, ff, writesector); |
| if (ret < 0) |
| { |
| goto errout_with_semaphore; |
| } |
| } |
| |
| /* Copy the partial sector from the user buffer */ |
| |
| writesize = fs->fs_hwsectorsize - sectorindex; |
| if (writesize > buflen) |
| { |
| writesize = buflen; |
| } |
| |
| memcpy(&ff->ff_buffer[sectorindex], userbuffer, writesize); |
| ff->ff_currentsector = writesector; |
| ff->ff_bflags |= (FFBUFF_DIRTY|FFBUFF_VALID|FFBUFF_MODIFIED); |
| } |
| |
| /* Set up for the next write */ |
| |
| userbuffer += writesize; |
| ff->ff_position += writesize; |
| byteswritten += writesize; |
| buflen -= writesize; |
| } |
| |
| /* The transfer has completed without error. Update the file size */ |
| |
| if (ff->ff_position > ff->ff_size) |
| { |
| ff->ff_size = ff->ff_position; |
| } |
| |
| fat_semgive(fs); |
| return byteswritten; |
| |
| errout_with_semaphore: |
| fat_semgive(fs); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: fat_seek |
| ****************************************************************************/ |
| |
| static off_t fat_seek(FAR struct file *filp, off_t offset, int whence) |
| { |
| struct inode *inode; |
| struct fat_mountpt_s *fs; |
| struct fat_file_s *ff; |
| sint32 cluster; |
| ssize_t position; |
| unsigned int clustersize; |
| unsigned int sectoroffset; |
| int ret; |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(filp->f_priv != NULL && filp->f_inode != NULL); |
| |
| /* Recover our private data from the struct file instance */ |
| |
| ff = filp->f_priv; |
| inode = filp->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 + ff->ff_position; |
| 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 */ |
| |
| ff->ff_position = 0; |
| ff->ff_sectorsincluster = 1; |
| |
| /* Move file position if necessary */ |
| |
| if (position) |
| { |
| /* Get the start cluster of the file */ |
| |
| cluster = ff->ff_startcluster; |
| if (!cluster) |
| { |
| /* Create a new cluster chain if the file does not have one */ |
| |
| cluster = fat_createchain(fs); |
| if (cluster < 0) |
| { |
| ret = cluster; |
| goto errout_with_semaphore; |
| } |
| ff->ff_startcluster = cluster; |
| } |
| |
| 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 |
| { |
| /* Other 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 */ |
| |
| ff->ff_position += clustersize; |
| position -= clustersize; |
| } |
| |
| /* We get here after we have found the sector containing |
| * the requested position. |
| */ |
| |
| sectoroffset = (position - 1) / fs->fs_hwsectorsize; |
| |
| /* And get the current sector from the cluster and |
| * the sectoroffset into the cluster. |
| */ |
| |
| ff->ff_currentsector = |
| fat_cluster2sector(fs, cluster) + sectoroffset; |
| |
| /* 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; |
| } |
| } |
| |
| /* Save the number of sectors left in the cluster */ |
| |
| ff->ff_sectorsincluster = fs->fs_fatsecperclus - sectoroffset; |
| |
| /* And save the new file position */ |
| |
| ff->ff_position += position; |
| } |
| } |
| |
| /* If we extended the size of the file, then mark the file as modified. */ |
| |
| if ((ff->ff_oflags & O_WROK) != 0 && ff->ff_position > ff->ff_size) |
| { |
| ff->ff_size = ff->ff_position; |
| 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 *filp, int cmd, unsigned long arg) |
| { |
| struct inode *inode; |
| struct fat_mountpt_s *fs; |
| struct fat_file_s *ff; |
| int ret; |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(filp->f_priv != NULL && filp->f_inode != NULL); |
| |
| /* Recover our private data from the struct file instance */ |
| |
| ff = filp->f_priv; |
| inode = filp->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 *filp) |
| { |
| struct inode *inode; |
| struct fat_mountpt_s *fs; |
| struct fat_file_s *ff; |
| uint32 wrttime; |
| ubyte *direntry; |
| int ret; |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(filp->f_priv != NULL && filp->f_inode != NULL); |
| |
| /* Recover our private data from the struct file instance */ |
| |
| ff = filp->f_priv; |
| inode = filp->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 internal_dir_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) |
| { |
| /* Handler the FAT12/16 root directory */ |
| |
| dir->u.fat.fd_startcluster = 0; |
| dir->u.fat.fd_currcluster = 0; |
| dir->u.fat.fd_currsector = fs->fs_rootbase; |
| dir->u.fat.fd_index = 2; |
| } |
| |
| /* 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)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 internal_dir_s *dir) |
| { |
| struct fat_mountpt_s *fs; |
| unsigned int dirindex; |
| ubyte *direntry; |
| ubyte ch; |
| ubyte attribute; |
| 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; |
| } |
| |
| /* 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) |
| { |
| dir->u.fat.fd_currsector = 0; |
| } |
| } |
| |
| fat_semgive(fs); |
| return OK; |
| |
| errout_with_semaphore: |
| fat_semgive(fs); |
| return ERROR; |
| } |
| |
| /**************************************************************************** |
| * Name: fat_rewindir |
| * |
| * Description: Reset directory read to the first entry |
| * |
| ****************************************************************************/ |
| |
| static int fat_rewinddir(struct inode *mountpt, struct internal_dir_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 (dir->u.fat.fd_startcluster == 0) |
| { |
| /* Handler the FAT12/16 root directory */ |
| |
| dir->u.fat.fd_currcluster = 0; |
| dir->u.fat.fd_currsector = fs->fs_rootbase; |
| dir->u.fat.fd_index = 2; |
| } |
| |
| /* 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 */ |
| ret = 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. |
| */ |
| |
| #warning "Need to defer deleting cluster chain if the file is open" |
| |
| /* 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; |
| ubyte *direntry; |
| ubyte *direntry2; |
| size_t parentsector; |
| ssize_t dirsector; |
| sint32 dircluster; |
| uint32 parentcluster; |
| uint32 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. |
| */ |
| |
| #warning "Need to defer deleting cluster chain if the directory is open" |
| |
| /* 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; |
| ubyte *olddirentry; |
| ubyte *newdirentry; |
| ubyte 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 date; |
| uint16 date2; |
| uint16 time; |
| ubyte 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; |
| } |
| |
| if (! dirinfo.fd_entry) |
| { |
| ret = -ENOENT; |
| 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. |
| */ |
| |
| memset(buf, 0, sizeof(struct stat)); |
| 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 |
| ****************************************************************************/ |
| |