/**************************************************************************** * fs/fat/fs_fat32.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "inode/inode.h" #include "fs_fat32.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #if defined(CONFIG_FS_LARGEFILE) # define OFF_MAX INT64_MAX #else # define OFF_MAX INT32_MAX #endif #define ROUND_DOWN(a, b) ((a) & ~((b) - 1)) #define ROUND_UP(a, b) (((a) + (b) - 1) & ~((b) - 1)) #define DIV_ROUND_UP(a, b) (ROUND_UP(a, b) / (b)) /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int fat_open(FAR struct file *filep, FAR 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, FAR char *buffer, size_t buflen); static ssize_t fat_write(FAR struct file *filep, FAR 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_dup(FAR const struct file *oldp, FAR struct file *newp); static int fat_fstat(FAR const struct file *filep, FAR struct stat *buf); static int fat_truncate(FAR struct file *filep, off_t length); static int fat_opendir(FAR struct inode *mountpt, FAR const char *relpath, FAR struct fs_dirent_s **dir); static int fat_closedir(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir); static int fat_readdir(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir, FAR struct dirent *entry); static int fat_rewinddir(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir); static int fat_bind(FAR struct inode *blkdriver, FAR const void *data, FAR void **handle); static int fat_unbind(FAR void *handle, FAR struct inode **blkdriver, unsigned int flags); static int fat_statfs(FAR struct inode *mountpt, FAR struct statfs *buf); static int fat_unlink(FAR struct inode *mountpt, FAR const char *relpath); static int fat_mkdir(FAR struct inode *mountpt, FAR const char *relpath, mode_t mode); static int fat_rmdir(FAR struct inode *mountpt, FAR const char *relpath); static int fat_rename(FAR struct inode *mountpt, FAR const char *oldrelpath, FAR const char *newrelpath); static int fat_stat_common(FAR struct fat_mountpt_s *fs, FAR uint8_t *direntry, FAR struct stat *buf); static int fat_stat_file(FAR struct fat_mountpt_s *fs, FAR uint8_t *direntry, FAR struct stat *buf); static int fat_stat_root(FAR struct fat_mountpt_s *fs, FAR struct stat *buf); static int fat_stat(struct inode *mountpt, const char *relpath, FAR struct stat *buf); /**************************************************************************** * Public Data ****************************************************************************/ /* See fs_mount.c -- this structure is explicitly extern'ed there. * We use the old-fashioned kind of initializers so that this will compile * with any compiler. */ const struct mountpt_operations g_fat_operations = { fat_open, /* open */ fat_close, /* close */ fat_read, /* read */ fat_write, /* write */ fat_seek, /* seek */ fat_ioctl, /* ioctl */ NULL, /* mmap */ fat_truncate, /* truncate */ NULL, /* poll */ fat_sync, /* sync */ fat_dup, /* dup */ fat_fstat, /* fstat */ NULL, /* fchstat */ fat_opendir, /* opendir */ fat_closedir, /* closedir */ fat_readdir, /* readdir */ fat_rewinddir, /* rewinddir */ fat_bind, /* bind */ fat_unbind, /* unbind */ fat_statfs, /* statfs */ fat_unlink, /* unlink */ fat_mkdir, /* mkdir */ fat_rmdir, /* rmdir */ fat_rename, /* rename */ fat_stat, /* stat */ NULL /* chstat */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: fat_open ****************************************************************************/ static int fat_open(FAR struct file *filep, FAR const char *relpath, int oflags, mode_t mode) { struct fat_dirinfo_s dirinfo; FAR struct inode *inode; FAR struct fat_mountpt_s *fs; FAR struct fat_file_s *ff; uint8_t *direntry; int ret; /* Sanity checks */ DEBUGASSERT(filep->f_priv == 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 */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* 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 possibilities: (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_root) { /* It is the root directory */ ret = -EISDIR; goto errout_with_lock; } direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset]; if ((DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY) != 0) { /* It is a regular directory */ ret = -EISDIR; goto errout_with_lock; } /* 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_lock; } /* Check if the caller has sufficient privileges to open the file */ readonly = ((DIR_GETATTRIBUTES(direntry) & FATATTR_READONLY) != 0); if (((oflags & O_WRONLY) != 0) && readonly) { ret = -EACCES; goto errout_with_lock; } /* If O_TRUNC is specified and the file is opened for writing, * then truncate the file. This operation requires that the file is * writeable, 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, direntry); if (ret < 0) { goto errout_with_lock; } } /* fall through to finish the file open operations */ } /* ENOENT would be returned by fat_finddirentry() if the full directory * path was found, but the file was not found in the final directory. */ 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_lock; } /* Yes.. create the file */ ret = fat_dircreate(fs, &dirinfo); if (ret < 0) { goto errout_with_lock; } /* Fall through to finish the file open operation */ direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset]; } /* No other error is handled */ else { /* An error occurred while checking for file existence -- * such as if an invalid path were provided. */ goto errout_with_lock; } /* Create an instance of the file private date to describe the opened * file. */ ff = fs_heap_zalloc(sizeof(struct fat_file_s)); if (!ff) { ret = -ENOMEM; goto errout_with_lock; } /* Create a file buffer to support partial sector accesses */ ff->ff_buffer = (FAR uint8_t *)fat_io_alloc(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_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(direntry) << 16) | DIR_GETFSTCLUSTLO(direntry); ff->ff_currentcluster = ff->ff_startcluster; ff->ff_sectorsincluster = fs->fs_fatsecperclus; ff->ff_size = DIR_GETFILESIZE(direntry); /* 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; nxmutex_unlock(&fs->fs_lock); /* 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)) { off_t offset = fat_seek(filep, ff->ff_size, SEEK_SET); if (offset < 0) { fs_heap_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: fs_heap_free(ff); errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_close ****************************************************************************/ static int fat_close(FAR struct file *filep) { FAR struct inode *inode; FAR struct fat_file_s *ff; FAR struct fat_file_s *currff; FAR struct fat_file_s *prevff; FAR struct fat_mountpt_s *fs; int ret = OK; /* Sanity checks */ DEBUGASSERT(filep->f_priv != 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); /* Check for the forced mount condition */ if ((ff->ff_bflags & UMOUNT_FORCED) == 0) { /* 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); /* Remove the file structure from the list of open files in the * mountpoint structure. */ for (prevff = NULL, currff = fs->fs_head; currff && currff != ff; prevff = currff, currff = currff->ff_next); if (currff) { if (prevff) { prevff->ff_next = ff->ff_next; } else { fs->fs_head = ff->ff_next; } } } /* 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) { fat_io_free(ff->ff_buffer, fs->fs_hwsectorsize); } /* Then free the file structure itself. */ fs_heap_free(ff); filep->f_priv = NULL; return ret; } /**************************************************************************** * Name: fat_zero_cluster * Description: * Zero the data in a cluster. Use to zero the data in the gap between EOF * and the write offset. * * Input Parameters: * fs - A reference to the fat volume object instance * cluster - Cluster index to be zeroed * start - The starting position in the cluster * end - The ending position in the cluster * * Returned Value: * Zero (OK) is returned on success; A negated errno value is returned on * any failure. * ****************************************************************************/ static int fat_zero_cluster(FAR struct fat_mountpt_s *fs, int cluster, int start, int end) { FAR uint8_t *buf; int zero_len = fs->fs_hwsectorsize - (start & (fs->fs_hwsectorsize - 1)); off_t i; off_t sector = fat_cluster2sector(fs, cluster); off_t start_sec = sector + start / fs->fs_hwsectorsize; off_t end_sec = sector + DIV_ROUND_UP(end, fs->fs_hwsectorsize); int ret; buf = fs_heap_malloc(fs->fs_hwsectorsize); if (!buf) { return -ENOMEM; } if (zero_len) { ret = fat_hwread(fs, buf, start_sec, 1); if (ret < 0) { goto out; } memset(buf + fs->fs_hwsectorsize - zero_len, 0, zero_len); ret = fat_hwwrite(fs, buf, start_sec, 1); if (ret < 0) { goto out; } start_sec++; } memset(buf, 0, fs->fs_hwsectorsize - zero_len); for (i = start_sec; i < end_sec; i++) { ret = fat_hwwrite(fs, buf, i, 1); if (ret < 0) { goto out; } } ret = OK; out: fs_heap_free(buf); return ret; } /**************************************************************************** * Name: fat_get_sectors * * Description: * Get the sector index where ->f_pos is located. This function will * allocate new clusters if get sectors for writing and ->f_pos is out of * EOF and zero the data in the gap between EOF and the write offset. * * Input Parameters: * fs - A reference to the file * read - True if get sectors for reading * * Output: * ->ff_currentsector - the sector index where ->f_pos is located * ->ff_currentcluster - the cluster index where ->f_pos is located * ->ff_sectorsincluster - sectors remaining in cluster * ->ff_startcluster - the first cluster of the file when writing an * empty file * * Returned Value: * Zero (OK) is returned on success; A negated errno value is returned on * any failure. * ****************************************************************************/ static int fat_get_sectors(FAR struct file *filep, bool read) { FAR struct inode *inode = filep->f_inode; FAR struct fat_mountpt_s *fs = inode->i_private; FAR struct fat_file_s *ff = filep->f_priv; int i; int num_clu; int new_num_clu; int num_traversed; int cluster; int ret; int zero_start; int zero_end; int clu_size = fs->fs_fatsecperclus * fs->fs_hwsectorsize; num_clu = DIV_ROUND_UP(ff->ff_size, clu_size); new_num_clu = DIV_ROUND_UP(filep->f_pos + 1, clu_size); if (ff->ff_startcluster == 0) { /* empty file */ cluster = 0; num_traversed = 0; } else if (ff->ff_currentcluster >= 2 && ff->ff_currentcluster < fs->fs_nclusters + 2 && ff->ff_pos <= filep->f_pos) { /* fatchain is traversed but not reach filep->f_pos */ cluster = ff->ff_currentcluster; num_traversed = ff->ff_pos / clu_size + 1; } else { /* Traverse the FATchain from the first cluster of the file */ cluster = ff->ff_startcluster; num_traversed = 1; } /* Traverse the existing chain */ for (i = num_traversed; i < num_clu && i < new_num_clu; i++) { cluster = fat_getcluster(fs, cluster); /* The chain is broken */ if (cluster < 2 || cluster >= fs->fs_nclusters + 2) { return -EIO; } } if (read) { goto out; } /* The 3 areas should be zeroed. * * cluster cluster+1..N cluster+2..N+1 * +-------------------+------------------+---------------------+ * | | (1) | (2) | (3) | | | * +-------------------+------------------+---------------------+ * ^ ^ * ff_size f_pos */ /* zero area (1) */ if (i == num_clu && filep->f_pos > ff->ff_size && ff->ff_size) { zero_start = ff->ff_size & (clu_size - 1); if (num_clu == new_num_clu) { zero_end = filep->f_pos & (clu_size - 1); } else { zero_end = clu_size; } fat_ffcacheinvalidate(fs, ff); ret = fat_zero_cluster(fs, cluster, zero_start, zero_end); if (ret) { return ret; } } /* Append new clusters for writing */ for (; i < new_num_clu - 1; i++) { cluster = fat_extendchain(fs, cluster); if (cluster < 2 || cluster >= fs->fs_nclusters + 2) { return -EIO; } /* zero area (2) */ ret = fat_zero_cluster(fs, cluster, 0, clu_size); if (ret < 0) { return ret; } if (ff->ff_startcluster == 0) { ff->ff_startcluster = cluster; } } if (i == new_num_clu - 1) { cluster = fat_extendchain(fs, cluster); if (cluster < 2 || cluster >= fs->fs_nclusters + 2) { return -EIO; } /* zero area (3) */ zero_end = filep->f_pos & (clu_size -1); if (zero_end) { ret = fat_zero_cluster(fs, cluster, 0, zero_end); if (ret < 0) { return ret; } } if (ff->ff_startcluster == 0) { ff->ff_startcluster = cluster; } } if (filep->f_pos > ff->ff_size) { ff->ff_size = filep->f_pos; } out: ff->ff_currentcluster = cluster; ret = fat_currentsector(fs, ff, filep->f_pos); if (ret < 0) { return ret; } ff->ff_pos = ROUND_DOWN(filep->f_pos, clu_size); return 0; } /**************************************************************************** * Name: fat_read ****************************************************************************/ static ssize_t fat_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { FAR struct inode *inode; FAR struct fat_mountpt_s *fs; FAR struct fat_file_s *ff; unsigned int bytesread; unsigned int readsize; size_t bytesleft; FAR uint8_t *userbuffer = (FAR uint8_t *)buffer; int sectorindex; int ret; #ifndef CONFIG_FAT_FORCE_INDIRECT unsigned int nsectors; bool force_indirect = false; #endif /* Sanity checks */ DEBUGASSERT(filep->f_priv != NULL); /* Recover our private data from the struct file instance */ ff = filep->f_priv; /* Check for the forced mount condition */ if ((ff->ff_bflags & UMOUNT_FORCED) != 0) { return -EPIPE; } inode = filep->f_inode; fs = inode->i_private; DEBUGASSERT(fs != NULL); /* Make sure that the mount is still healthy */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Check if the file was opened with read access */ if ((ff->ff_oflags & O_RDOK) == 0) { ret = -EACCES; goto errout_with_lock; } /* Check that the file position is not past the end of the file */ if (filep->f_pos > ff->ff_size) { /* Return EOF */ ret = 0; goto errout_with_lock; } else { /* 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; } } /* 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; ret = fat_get_sectors(filep, true); if (ret < 0) { goto errout_with_lock; } #ifdef CONFIG_FAT_DIRECT_RETRY /* Warning avoidance */ fat_read_restart: #endif #ifndef CONFIG_FAT_FORCE_INDIRECT /* 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 && !force_indirect) { /* 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 */ 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) { #ifdef CONFIG_FAT_DIRECT_RETRY /* The low-level driver may return -EFAULT in the case where * the transfer cannot be performed due to buffer memory * constraints. It is probable that the buffer is completely * un-DMA-able or improperly aligned. In this case, force * indirect transfers via the sector buffer and restart the * operation (unless we have already tried that). */ if (ret == -EFAULT && !force_indirect) { ferr("ERROR: DMA read alignment error," " restarting indirect\n"); force_indirect = true; goto fat_read_restart; } #endif /* CONFIG_FAT_DIRECT_RETRY */ goto errout_with_lock; } ff->ff_sectorsincluster -= nsectors; ff->ff_currentsector += nsectors; bytesread = nsectors * fs->fs_hwsectorsize; } else #endif /* CONFIG_FAT_FORCE_INDIRECT */ { /* We are reading a partial sector, or handling a non-DMA-able * whole-sector transfer. 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_lock; } /* Copy the requested part of the 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); } nxmutex_unlock(&fs->fs_lock); return readsize; errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_write ****************************************************************************/ static ssize_t fat_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) { FAR struct inode *inode; FAR struct fat_mountpt_s *fs; FAR struct fat_file_s *ff; unsigned int byteswritten; unsigned int writesize; FAR uint8_t *userbuffer = (FAR uint8_t *)buffer; int sectorindex; int ret; #ifndef CONFIG_FAT_FORCE_INDIRECT unsigned int nsectors; bool force_indirect = false; #endif DEBUGASSERT(filep->f_priv != NULL); /* Recover our private data from the struct file instance */ ff = filep->f_priv; /* Check for the forced mount condition */ if ((ff->ff_bflags & UMOUNT_FORCED) != 0) { return -EPIPE; } inode = filep->f_inode; fs = inode->i_private; DEBUGASSERT(fs != NULL); /* Make sure that the mount is still healthy */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Check if the file was opened for write access */ if ((ff->ff_oflags & O_WROK) == 0) { ret = -EACCES; goto errout_with_lock; } /* Check if the file size would exceed the range of off_t */ if (buflen > OFF_MAX || ff->ff_size > OFF_MAX - (off_t)buflen) { ret = -EFBIG; goto errout_with_lock; } /* 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) { ret = fat_get_sectors(filep, false); if (ret < 0) { goto errout_with_lock; } #ifdef CONFIG_FAT_DIRECT_RETRY /* Warning avoidance */ fat_write_restart: #endif #ifndef CONFIG_FAT_FORCE_INDIRECT /* 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 && !force_indirect) { /* 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 sector cache so the * safest thing to do is write back any dirty, cached sector * and invalidate the current cache content. */ 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) { #ifdef CONFIG_FAT_DIRECT_RETRY /* The low-level driver may return -EFAULT in the case where * the transfer cannot be performed due to buffer memory * constraints. It is probable that the buffer is completely * un-DMA-able or improperly aligned. In this case, force * indirect transfers via the sector buffer and restart the * operation (unless we have already tried that). */ if (ret == -EFAULT && !force_indirect) { ferr("ERROR: DMA write alignment error," " restarting indirect\n"); force_indirect = true; goto fat_write_restart; } #endif /* CONFIG_FAT_DIRECT_RETRY */ goto errout_with_lock; } ff->ff_sectorsincluster -= nsectors; ff->ff_currentsector += nsectors; writesize = nsectors * fs->fs_hwsectorsize; ff->ff_bflags |= FFBUFF_MODIFIED; } else #endif /* CONFIG_FAT_FORCE_INDIRECT */ { /* Decide whether we are performing a read-modify-write * operation, in which case we have to read the existing sector * into the buffer first. * * There are two cases where we can avoid this read: * * - If we are performing a whole-sector write that was rejected * by fat_hwwrite(), i.e. sectorindex == 0 and buflen >= sector * size. * * - If the write is aligned to the beginning of the sector and * extends beyond the end of the file, i.e. sectorindex == 0 and * file pos + buflen >= file size. */ if ((sectorindex == 0) && ((buflen >= fs->fs_hwsectorsize) || ((filep->f_pos + buflen) >= ff->ff_size))) { /* Flush unwritten data in the sector cache. */ ret = fat_ffcacheflush(fs, ff); if (ret < 0) { goto errout_with_lock; } /* Now mark the clean cache buffer as the current sector. */ ff->ff_cachesector = ff->ff_currentsector; } else { /* Read the current sector into memory (perhaps first flushing * the old, dirty sector to disk). */ ret = fat_ffcacheread(fs, ff, ff->ff_currentsector); if (ret < 0) { goto errout_with_lock; } } /* Copy the requested part of the sector from the user buffer */ writesize = fs->fs_hwsectorsize - sectorindex; if (writesize > buflen) { /* We will not write to the end of the buffer. Set * write size to the size of the user buffer. */ writesize = buflen; } else { /* We will write to the end of the buffer (or beyond). Bump * up the current sector number (actually the next sector * number). */ ff->ff_sectorsincluster--; ff->ff_currentsector++; } /* Copy the data into the cached sector and make sure that the * cached sector is marked "dirty" */ memcpy(&ff->ff_buffer[sectorindex], userbuffer, writesize); ff->ff_bflags |= (FFBUFF_DIRTY | FFBUFF_VALID | FFBUFF_MODIFIED); } /* Set up for the next write */ userbuffer += writesize; filep->f_pos += writesize; byteswritten += writesize; buflen -= writesize; sectorindex = filep->f_pos & SEC_NDXMASK(fs); /* Update the file size */ if (filep->f_pos > ff->ff_size) { ff->ff_size = filep->f_pos; } } nxmutex_unlock(&fs->fs_lock); return byteswritten; errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_seek ****************************************************************************/ static off_t fat_seek(FAR struct file *filep, off_t offset, int whence) { FAR struct inode *inode; FAR struct fat_mountpt_s *fs; FAR struct fat_file_s *ff; off_t position; int ret; /* Sanity checks */ DEBUGASSERT(filep->f_priv != NULL); /* Recover our private data from the struct file instance */ ff = filep->f_priv; /* Check for the forced mount condition */ if ((ff->ff_bflags & UMOUNT_FORCED) != 0) { return -EPIPE; } 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; } /* Invalid arguments are entered, returns an error. */ if (position < 0) { return -EINVAL; } /* Special case: We are seeking to the current position. This would * happen normally with ftell() which does lseek(fd, 0, SEEK_CUR) but can * also happen in other situation such as when SEEK_SET is used to assure * assure sequential access in a multi-threaded environment where there * may be are multiple users to the file descriptor. * Effectively handles the situation when a new file position is within * the current sector. */ if (position / fs->fs_hwsectorsize == filep->f_pos / fs->fs_hwsectorsize) { filep->f_pos = position; return OK; } /* Make sure that the mount is still healthy */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Check if there is unwritten data in the file buffer */ ret = fat_ffcacheflush(fs, ff); if (ret < 0) { goto errout_with_lock; } filep->f_pos = position; nxmutex_unlock(&fs->fs_lock); return OK; errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_ioctl ****************************************************************************/ static int fat_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct inode *inode; FAR struct fat_file_s *ff; FAR struct fat_mountpt_s *fs; int ret; /* Sanity checks */ DEBUGASSERT(filep->f_priv != NULL); /* Check for the forced mount condition */ ff = filep->f_priv; if ((ff->ff_bflags & UMOUNT_FORCED) != 0) { return -EPIPE; } /* Recover our private data from the struct file instance */ inode = filep->f_inode; fs = inode->i_private; DEBUGASSERT(fs != NULL); /* Make sure that the mount is still healthy */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { nxmutex_unlock(&fs->fs_lock); return ret; } /* ioctl calls are just passed through to the contained block driver */ nxmutex_unlock(&fs->fs_lock); return -ENOTTY; } /**************************************************************************** * Name: fat_sync * * Description: Synchronize the file state on disk to match internal, in- * memory state. * ****************************************************************************/ static int fat_sync(FAR struct file *filep) { FAR struct inode *inode; FAR struct fat_mountpt_s *fs; FAR struct fat_file_s *ff; uint32_t wrttime; uint8_t *direntry; int ret; /* Sanity checks */ DEBUGASSERT(filep->f_priv != NULL); /* Check for the forced mount condition */ ff = filep->f_priv; if ((ff->ff_bflags & UMOUNT_FORCED) != 0) { return -EPIPE; } /* Recover our private data from the struct file instance */ inode = filep->f_inode; fs = inode->i_private; DEBUGASSERT(fs != NULL); /* Make sure that the mount is still healthy */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Check if the has been modified in any way */ if ((ff->ff_bflags & FFBUFF_MODIFIED) != 0) { uint8_t dircopy[DIR_SIZE]; /* Flush any unwritten data in the file buffer */ ret = fat_ffcacheflush(fs, ff); if (ret < 0) { goto errout_with_lock; } /* 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_lock; } /* Recover a pointer to the specific directory entry * in the sector using the saved directory index. */ direntry = &fs->fs_buffer[(ff->ff_dirindex & DIRSEC_NDXMASK(fs)) * DIR_SIZE]; /* Copy directory entry */ memcpy(dircopy, direntry, DIR_SIZE); /* 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; /* Compare old and new directory entry */ if (memcmp(direntry, dircopy, DIR_SIZE) != 0) { fs->fs_dirty = true; } /* Flush these change to disk and update FSINFO (if * appropriate. */ ret = fat_updatefsinfo(fs); } errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_dup * * Description: Duplicate open file data in the new file structure. * ****************************************************************************/ static int fat_dup(FAR const struct file *oldp, FAR struct file *newp) { FAR struct fat_mountpt_s *fs; FAR struct fat_file_s *oldff; FAR struct fat_file_s *newff; int ret; finfo("Dup %p->%p\n", oldp, newp); /* Sanity checks */ DEBUGASSERT(oldp->f_priv != NULL && newp->f_priv == NULL && newp->f_inode != NULL); /* Recover the old private data from the old struct file instance */ oldff = oldp->f_priv; /* Check for the forced mount condition */ if ((oldff->ff_bflags & UMOUNT_FORCED) != 0) { return -EPIPE; } /* Recover our private data from the struct file instance */ fs = oldp->f_inode->i_private; DEBUGASSERT(fs != NULL); /* Check if the mount is still healthy */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Create a new instance of the file private date to describe the * dup'ed file. */ newff = fs_heap_malloc(sizeof(struct fat_file_s)); if (!newff) { ret = -ENOMEM; goto errout_with_lock; } /* Create a file buffer to support partial sector accesses */ newff->ff_buffer = (FAR uint8_t *)fat_io_alloc(fs->fs_hwsectorsize); if (!newff->ff_buffer) { ret = -ENOMEM; goto errout_with_struct; } /* Copy the rest of the open open file state from the old file structure. * There are some assumptions and potential issues here: * * 1) We assume that the higher level logic has copied the elements of * the file structure, in particular, the file position. * 2) There is a problem with ff_size if there are multiple opened * file structures, each believing they know the size of the file. * If one instance modifies the file length, then the new size of * the opened file will be unknown to the other. That is a lurking * bug! * * One good solution to this might be to add a reference count to the * file structure. Then, instead of dup'ing the whole structure * as is done here, just increment the reference count on the * structure. The would have to be integrated with open logic as * well, however, so that the same file structure is re-used if the * file is re-opened. */ newff->ff_bflags = 0; /* File buffer flags */ newff->ff_oflags = oldff->ff_oflags; /* File open flags */ newff->ff_sectorsincluster = oldff->ff_sectorsincluster; /* Sectors remaining in cluster */ newff->ff_dirindex = oldff->ff_dirindex; /* Index to directory entry */ newff->ff_currentcluster = oldff->ff_currentcluster; /* Current cluster */ newff->ff_dirsector = oldff->ff_dirsector; /* Sector containing directory entry */ newff->ff_size = oldff->ff_size; /* Size of the file */ newff->ff_startcluster = oldff->ff_startcluster; /* Start cluster of file on media */ newff->ff_currentsector = oldff->ff_currentsector; /* Current sector */ newff->ff_cachesector = 0; /* Sector in file buffer */ /* Attach the private date to the struct file instance */ newp->f_priv = newff; /* 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). */ newff->ff_next = fs->fs_head; fs->fs_head = newff; nxmutex_unlock(&fs->fs_lock); return OK; /* Error exits -- goto's are nasty things, but they sure can make error * handling a lot simpler. */ errout_with_struct: fs_heap_free(newff); errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_opendir * * Description: Open a directory for read access * ****************************************************************************/ static int fat_opendir(FAR struct inode *mountpt, FAR const char *relpath, FAR struct fs_dirent_s **dir) { FAR struct fat_dirent_s *fdir; FAR struct fat_mountpt_s *fs; FAR struct fat_dirinfo_s dirinfo; uint8_t *direntry; int ret; /* Sanity checks */ DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); /* Recover our private data from the inode instance */ fs = mountpt->i_private; fdir = fs_heap_zalloc(sizeof(struct fat_dirent_s)); if (fdir == NULL) { return -ENOMEM; } /* Make sure that the mount is still healthy */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { goto errout_with_fdir; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Find the requested directory */ ret = fat_finddirentry(fs, &dirinfo, relpath); if (ret < 0) { goto errout_with_lock; } /* Check if this is the root directory */ if (dirinfo.fd_root) { /* Handle the FAT12/16/32 root directory using the values setup by * fat_finddirentry() above. */ fdir->dir.fd_startcluster = dirinfo.dir.fd_startcluster; fdir->dir.fd_currcluster = dirinfo.dir.fd_currcluster; fdir->dir.fd_currsector = dirinfo.dir.fd_currsector; fdir->dir.fd_index = dirinfo.dir.fd_index; } else { /* This is not the root directory. Verify that it is some kind of * directory. */ direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset]; if ((DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY) == 0) { /* The entry is not a directory */ ret = -ENOTDIR; goto errout_with_lock; } else { /* The entry is a directory (but not the root directory) */ fdir->dir.fd_startcluster = ((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) | DIR_GETFSTCLUSTLO(direntry); fdir->dir.fd_currcluster = fdir->dir.fd_startcluster; fdir->dir.fd_currsector = fat_cluster2sector(fs, fdir->dir.fd_currcluster); fdir->dir.fd_index = 2; } } *dir = (FAR struct fs_dirent_s *)fdir; nxmutex_unlock(&fs->fs_lock); return OK; errout_with_lock: nxmutex_unlock(&fs->fs_lock); errout_with_fdir: fs_heap_free(fdir); return ret; } /**************************************************************************** * Name: fat_closedir * * Description: Close directory * ****************************************************************************/ static int fat_closedir(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir) { DEBUGASSERT(dir); fs_heap_free(dir); return 0; } /**************************************************************************** * Name: fat_fstat * * Description: * Obtain information about an open file associated with the file * structure 'filep', and will write it to the area pointed to by 'buf'. * ****************************************************************************/ static int fat_fstat(FAR const struct file *filep, FAR struct stat *buf) { FAR struct inode *inode; FAR struct fat_mountpt_s *fs; FAR struct fat_file_s *ff; uint8_t *direntry; int ret; /* Sanity checks */ DEBUGASSERT(filep->f_priv != 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 */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Recover our private data from the struct file instance */ ff = filep->f_priv; /* 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_lock; } /* Recover a pointer to the specific directory entry in the sector using * the saved directory index. */ direntry = &fs->fs_buffer[(ff->ff_dirindex & DIRSEC_NDXMASK(fs)) * DIR_SIZE]; /* Call fat_stat_file() to create the buf and to save information to * it. */ ret = fat_stat_file(fs, direntry, buf); errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_truncate * * Description: * Set the length of the open, regular file associated with the file * structure 'filep' to 'length'. * ****************************************************************************/ static int fat_truncate(FAR struct file *filep, off_t length) { FAR struct inode *inode; FAR struct fat_mountpt_s *fs; FAR struct fat_file_s *ff; off_t oldsize; int ret; DEBUGASSERT(filep->f_priv != NULL); /* Recover our private data from the struct file instance */ ff = filep->f_priv; /* Check for the forced mount condition */ if ((ff->ff_bflags & UMOUNT_FORCED) != 0) { return -EPIPE; } inode = filep->f_inode; fs = inode->i_private; DEBUGASSERT(fs != NULL); /* Make sure that the mount is still healthy */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Check if the file was opened for write access */ if ((ff->ff_oflags & O_WROK) == 0) { ret = -EACCES; goto errout_with_lock; } /* Are we shrinking the file? Or extending it? */ oldsize = ff->ff_size; if (oldsize == length) { /* Do nothing but say that we did */ ret = OK; } else if (oldsize > length) { FAR uint8_t *direntry; int ndx; /* We are shrinking the file. * * Read the directory entry into the fs_buffer. */ ret = fat_fscacheread(fs, ff->ff_dirsector); if (ret < 0) { goto errout_with_lock; } /* Recover a pointer to the specific directory entry in the sector * using the saved directory index. */ ndx = (ff->ff_dirindex & DIRSEC_NDXMASK(fs)) * DIR_SIZE; direntry = &fs->fs_buffer[ndx]; /* Handle the simple case where we are shrinking the file to zero * length. */ if (length == 0) { /* Shrink to length == 0 */ ret = fat_dirtruncate(fs, direntry); } else { /* Shrink to 0 < length < oldsize */ ret = fat_dirshrink(fs, direntry, length); } if (ret >= 0) { /* The truncation has completed without error. Update the file * size. */ ff->ff_size = length; ret = OK; } } else { /* Otherwise we are extending the file. This is essentially the same * as a write except that (1) we write zeros and (2) we don't update * the file position. */ ret = fat_dirextend(fs, ff, length); if (ret >= 0) { /* The truncation has completed without error. Update the file * size. */ ff->ff_size = length; ret = OK; } } errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_readdir * * Description: Read the next directory entry * ****************************************************************************/ static int fat_readdir(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir, FAR struct dirent *entry) { FAR struct fat_dirent_s *fdir; FAR struct fat_mountpt_s *fs; unsigned int dirindex; FAR uint8_t *direntry; uint8_t ch; uint8_t attribute; bool found; int ret; /* Sanity checks */ DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); /* Recover our private data from the inode instance */ fs = mountpt->i_private; fdir = (FAR struct fat_dirent_s *)dir; /* Make sure that the mount is still healthy. * REVISIT: What if a forced unmount was done since opendir() was called? */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Read the next directory entry */ entry->d_name[0] = '\0'; found = false; while (fdir->dir.fd_currsector && !found) { ret = fat_fscacheread(fs, fdir->dir.fd_currsector); if (ret < 0) { goto errout_with_lock; } /* Get a reference to the current directory entry */ dirindex = (fdir->dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE; 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_lock; } /* No, is the current entry a valid entry? */ attribute = DIR_GETATTRIBUTES(direntry); #ifdef CONFIG_FAT_LFN if (ch != DIR0_EMPTY && ((attribute & FATATTR_VOLUMEID) == 0 || ((ch & LDIR0_LAST) != 0 && attribute == LDDIR_LFNATTR))) #else if (ch != DIR0_EMPTY && (attribute & FATATTR_VOLUMEID) == 0) #endif { /* Yes.. get the name from the directory entry. NOTE: For the case * of the long file name entry, this will advance the several * several directory entries. */ ret = fat_dirname2path(fs, dir, entry); if (ret == OK) { /* The name was successfully extracted. Re-read the * attributes: If this is long directory entry, then the * attributes that we need will be the final, short file * name entry and not in the directory entry where we started * looking for the file name. We can be assured that, on * success, fat_dirname2path() will leave the short file name * entry in the cache regardless of the kind of directory * entry. We simply have to re-read it to cover the long * file name case. */ #ifdef CONFIG_FAT_LFN /* Get a reference to the current, short file name directory * entry. */ dirindex = (fdir->dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE; direntry = &fs->fs_buffer[dirindex]; /* Then re-read the attributes from the short file name entry */ attribute = DIR_GETATTRIBUTES(direntry); #endif /* Now get the file type from the directory attributes. */ if ((attribute & FATATTR_DIRECTORY) == 0) { entry->d_type = DTYPE_FILE; } else { entry->d_type = DTYPE_DIRECTORY; } /* Mark the entry found. We will set up the next directory * index, and then exit with success. */ found = true; } } /* Set up the next directory index */ if (fat_nextdirentry(fs, &fdir->dir) != OK) { ret = -ENOENT; goto errout_with_lock; } } nxmutex_unlock(&fs->fs_lock); return OK; errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_rewindir * * Description: Reset directory read to the first entry * ****************************************************************************/ static int fat_rewinddir(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir) { FAR struct fat_dirent_s *fdir; FAR 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; fdir = (FAR struct fat_dirent_s *)dir; /* Make sure that the mount is still healthy * REVISIT: What if a forced unmount was done since opendir() was called? */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Check if this is the root directory. If it is the root directory, we * reset the fd_index to 0, starting with the initial, entry. */ if (fs->fs_type != FSTYPE_FAT32 && fdir->dir.fd_startcluster == 0) { /* Handle the FAT12/16 root directory */ fdir->dir.fd_currcluster = 0; fdir->dir.fd_currsector = fs->fs_rootbase; fdir->dir.fd_index = 0; } else if (fs->fs_type == FSTYPE_FAT32 && fdir->dir.fd_startcluster == fs->fs_rootbase) { /* Handle the FAT32 root directory */ fdir->dir.fd_currcluster = fdir->dir.fd_startcluster; fdir->dir.fd_currsector = fat_cluster2sector(fs, fs->fs_rootbase); fdir->dir.fd_index = 0; } /* This is not the root directory. Here the fd_index is set to 2, skipping * over both the "." and ".." entries. */ else { fdir->dir.fd_currcluster = fdir->dir.fd_startcluster; fdir->dir.fd_currsector = fat_cluster2sector(fs, fdir->dir.fd_currcluster); fdir->dir.fd_index = 2; } nxmutex_unlock(&fs->fs_lock); return OK; errout_with_lock: nxmutex_unlock(&fs->fs_lock); 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, FAR const void *data, FAR void **handle) { FAR 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 = fs_heap_zalloc(sizeof(struct fat_mountpt_s)); if (!fs) { return -ENOMEM; } /* Initialize the allocated mountpt state structure. The filesystem is * responsible for one reference on the blkdriver inode and does not * have to addref() here (but does have to release in unbind(). */ fs->fs_blkdriver = blkdriver; /* Save the block driver reference */ nxmutex_init(&fs->fs_lock); /* Initialize the mutex 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) { nxmutex_destroy(&fs->fs_lock); fs_heap_free(fs); return ret; } *handle = (FAR void *)fs; return OK; } /**************************************************************************** * Name: fat_unbind * * Description: This implements the filesystem portion of the umount * operation. * ****************************************************************************/ static int fat_unbind(FAR void *handle, FAR struct inode **blkdriver, unsigned int flags) { FAR struct fat_mountpt_s *fs = (FAR struct fat_mountpt_s *)handle; int ret; if (!fs) { return -EINVAL; } /* Check if there are sill any files opened on the filesystem. */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } if (fs->fs_head) { /* There are open files. We umount now unless we are forced with the * MNT_FORCE flag. Forcing the unmount will cause data loss because * the filesystem buffers are not flushed to the media. MNT_DETACH, * the 'lazy' unmount, could be implemented to fix this. */ if ((flags & MNT_FORCE) != 0) { FAR struct fat_file_s *ff; /* Set a flag in each open file structure. This flag will signal * the file system to fail any subsequent attempts to used the * file handle. */ for (ff = fs->fs_head; ff; ff = ff->ff_next) { ff->ff_bflags |= UMOUNT_FORCED; } } else { /* We cannot unmount now.. there are open files. This * implementation does not support any other umount2() * options. */ nxmutex_unlock(&fs->fs_lock); return (flags != 0) ? -ENOSYS : -EBUSY; } } /* Unmount ... close the block driver */ if (fs->fs_blkdriver) { FAR struct inode *inode = fs->fs_blkdriver; if (inode) { if (inode->u.i_bops && inode->u.i_bops->close) { 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) { fat_io_free(fs->fs_buffer, fs->fs_hwsectorsize); } nxmutex_destroy(&fs->fs_lock); fs_heap_free(fs); return OK; } /**************************************************************************** * Name: fat_statfs * * Description: Return filesystem statistics * ****************************************************************************/ static int fat_statfs(FAR struct inode *mountpt, FAR struct statfs *buf) { FAR 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 */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret < 0) { goto errout_with_lock; } /* Fill in the statfs info */ 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 */ ret = fat_nfreeclusters(fs, &buf->f_bfree); /* Free blocks in the file system */ if (ret >= 0) { buf->f_blocks = fs->fs_nclusters; /* Total data blocks in the * file system */ buf->f_bavail = buf->f_bfree; /* Free blocks avail to non- * superuser */ #ifdef CONFIG_FAT_LFN buf->f_namelen = LDIR_MAXFNAME; /* Maximum length of filenames */ #else buf->f_namelen = (8 + 1 + 3); /* Maximum length of filenames */ #endif } errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_unlink * * Description: Remove a file * ****************************************************************************/ static int fat_unlink(FAR struct inode *mountpt, FAR const char *relpath) { FAR 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 */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } 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. */ /* Remove the file * * TODO: Need to defer deleting cluster chain if the file is open. */ ret = fat_remove(fs, relpath, false); } nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_mkdir * * Description: Create a directory * ****************************************************************************/ static int fat_mkdir(FAR struct inode *mountpt, FAR const char *relpath, mode_t mode) { FAR struct fat_mountpt_s *fs; FAR struct fat_dirinfo_s dirinfo; FAR uint8_t *direntry; FAR uint8_t *direntry2; off_t parentsector; off_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 */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Find the directory where the new directory should be created. */ ret = fat_finddirentry(fs, &dirinfo, relpath); /* Or if any error occurs other -ENOENT, then return the error. For * example, if one of the earlier directory path segments was not found * then ENOTDIR will be returned. */ if (ret != -ENOENT) { /* If anything exists at this location, then we fail with EEXIST */ if (ret == OK) { ret = -EEXIST; } goto errout_with_lock; } /* 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. NOTE: The name has already been set in dirinfo * structure. */ if (ret != -ENOENT) { goto errout_with_lock; } /* 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_lock; } parentsector = fs->fs_currentsector; /* Allocate a cluster for new directory */ dircluster = fat_createchain(fs); if (dircluster < 0) { ret = dircluster; goto errout_with_lock; } else if (dircluster < 2) { ret = -ENOSPC; goto errout_with_lock; } dirsector = fat_cluster2sector(fs, dircluster); if (dirsector < 0) { ret = dirsector; goto errout_with_lock; } /* 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_lock; } /* 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_lock; } } /* Now create the "." directory entry in the first directory slot. These * are special directory entries and are not handled by the normal * directory management routines. */ memset(&direntry[DIR_NAME], ' ', DIR_MAXFNAME); 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 + DIR_SIZE; /* So far, the two entries are nearly the same */ memcpy(direntry2, direntry, DIR_SIZE); 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 (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_lock; } /* Write the new entry directory entry in the parent directory */ ret = fat_dirwrite(fs, &dirinfo, FATATTR_DIRECTORY, crtime); if (ret < 0) { goto errout_with_lock; } /* Set subdirectory start cluster. We assume that fat_dirwrite() did not * change the sector in the cache. */ direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset]; DIR_PUTFSTCLUSTLO(direntry, dircluster); DIR_PUTFSTCLUSTHI(direntry, dircluster >> 16); fs->fs_dirty = true; /* Now update the FAT32 FSINFO sector */ ret = fat_updatefsinfo(fs); if (ret < 0) { goto errout_with_lock; } nxmutex_unlock(&fs->fs_lock); return OK; errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_rmdir * * Description: Remove a directory * ****************************************************************************/ static int fat_rmdir(FAR struct inode *mountpt, FAR const char *relpath) { FAR 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 */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } 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. */ /* Remove the directory. * * TODO: Need to defer deleting cluster chain if the file is open. */ ret = fat_remove(fs, relpath, true); } nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_rename * * Description: Rename a file or directory * ****************************************************************************/ static int fat_rename(FAR struct inode *mountpt, FAR const char *oldrelpath, FAR const char *newrelpath) { FAR struct fat_mountpt_s *fs; FAR struct fat_dirinfo_s dirinfo; FAR struct fat_dirseq_s dirseq; uint8_t *direntry; uint8_t dirstate[DIR_SIZE - DIR_ATTRIBUTES]; 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 */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Find the directory entry for the oldrelpath (there may be multiple * directory entries if long file name support is enabled). */ ret = fat_finddirentry(fs, &dirinfo, oldrelpath); if (ret != OK) { /* Some error occurred -- probably -ENOENT */ goto errout_with_lock; } /* One more check: Make sure that the oldrelpath does not refer to the * root directory. We can't rename the root directory. */ if (dirinfo.fd_root) { ret = -EXDEV; goto errout_with_lock; } /* Save the information that will need to recover the directory sector and * directory entry offset to the old directory. * * Save the positional information of the old directory entry. */ memcpy(&dirseq, &dirinfo.fd_seq, sizeof(struct fat_dirseq_s)); /* Save the non-name-related portion of the directory entry intact */ direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset]; memcpy(dirstate, &direntry[DIR_ATTRIBUTES], DIR_SIZE - DIR_ATTRIBUTES); /* Now find the directory where we should create the newpath object */ ret = fat_finddirentry(fs, &dirinfo, newrelpath); /* 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) { if (ret == OK) { /* It is an error if the directory entry at newrelpath already * exists. The necessary steps to avoid this case should have * been handled by higher level logic in the VFS. */ ret = -EEXIST; } goto errout_with_lock; } /* Reserve a directory entry. If long file name support is enabled, then * this might, in fact, allocate a sequence of directory entries. A side * effect of fat_allocatedirentry() in either case is that it leaves the * short file name entry in the sector cache. */ ret = fat_allocatedirentry(fs, &dirinfo); if (ret != OK) { goto errout_with_lock; } /* Then write the new file name into the directory entry. This, of course, * may involve writing multiple directory entries if long file name * support is enabled. A side effect of fat_allocatedirentry() in either * case is that it leaves the short file name entry in the sector cache. */ ret = fat_dirnamewrite(fs, &dirinfo); if (ret < 0) { goto errout_with_lock; } /* Copy the unchanged information into the new short file name entry. */ direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset]; memcpy(&direntry[DIR_ATTRIBUTES], dirstate, DIR_SIZE - DIR_ATTRIBUTES); fs->fs_dirty = true; /* Remove the old entry, flushing the new directory entry to disk. If * the old file name was a long file name, then multiple directory * entries may be freed. */ ret = fat_freedirentry(fs, &dirseq); if (ret < 0) { goto errout_with_lock; } /* Write the old entry to disk and update FSINFO if necessary */ ret = fat_updatefsinfo(fs); if (ret < 0) { goto errout_with_lock; } nxmutex_unlock(&fs->fs_lock); return OK; errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Name: fat_stat_common * * Description: * Common logic used by fat_stat_file() and fat_fstat_root(). * ****************************************************************************/ static int fat_stat_common(FAR struct fat_mountpt_s *fs, FAR uint8_t *direntry, FAR struct stat *buf) { uint16_t fatdate; uint16_t date2; uint16_t fattime; /* Times */ fatdate = DIR_GETWRTDATE(direntry); fattime = DIR_GETWRTTIME(direntry); buf->st_mtime = fat_fattime2systime(fattime, fatdate); date2 = DIR_GETLASTACCDATE(direntry); if (fatdate == date2) { buf->st_atime = buf->st_mtime; } else { buf->st_atime = fat_fattime2systime(0, date2); } fatdate = DIR_GETCRDATE(direntry); fattime = DIR_GETCRTIME(direntry); buf->st_ctime = fat_fattime2systime(fattime, fatdate); /* File/directory size, access block size */ buf->st_size = DIR_GETFILESIZE(direntry); buf->st_blksize = fs->fs_fatsecperclus * fs->fs_hwsectorsize; buf->st_blocks = (buf->st_size + buf->st_blksize - 1) / buf->st_blksize; return OK; } /**************************************************************************** * Name: fat_stat_file * * Description: * Function to return the status associated with a file in the FAT file * system. Used by fat_stat() and fat_fstat(). * ****************************************************************************/ static int fat_stat_file(FAR struct fat_mountpt_s *fs, FAR uint8_t *direntry, FAR struct stat *buf) { uint8_t attribute; /* Initialize the "struct stat" */ memset(buf, 0, sizeof(struct stat)); /* Get attribute from direntry */ attribute = DIR_GETATTRIBUTES(direntry); if ((attribute & FATATTR_VOLUMEID) != 0) { return -ENOENT; } /* 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; } return fat_stat_common(fs, direntry, buf); } /**************************************************************************** * Name: fat_stat_root * * Description: * Logic to stat the root directory. Used by fat_stat(). * ****************************************************************************/ static int fat_stat_root(FAR struct fat_mountpt_s *fs, FAR struct stat *buf) { /* Clear the "struct stat" */ memset(buf, 0, sizeof(struct stat)); /* It's directory name of the mount point */ buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR | S_IWOTH | S_IWGRP | S_IWUSR; return OK; } /**************************************************************************** * Name: fat_stat * * Description: Return information about a file or directory * ****************************************************************************/ static int fat_stat(FAR struct inode *mountpt, FAR const char *relpath, FAR struct stat *buf) { FAR struct fat_mountpt_s *fs; struct fat_dirinfo_s dirinfo; FAR uint8_t *direntry; 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 */ ret = nxmutex_lock(&fs->fs_lock); if (ret < 0) { return ret; } ret = fat_checkmount(fs); if (ret != OK) { goto errout_with_lock; } /* Find the directory entry corresponding to relpath. */ ret = fat_finddirentry(fs, &dirinfo, relpath); /* If nothing was found, then we fail with the reported error */ if (ret < 0) { goto errout_with_lock; } /* Get the FAT attribute and map it so some meaningful mode_t values */ if (dirinfo.fd_root) { ret = fat_stat_root(fs, buf); } else { /* Get a pointer to the directory entry */ direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset]; /* Call fat_stat_file() to create the buf and to save information to * the stat buffer. */ ret = fat_stat_file(fs, direntry, buf); } errout_with_lock: nxmutex_unlock(&fs->fs_lock); return ret; } /**************************************************************************** * Public Functions ****************************************************************************/