1888 lines
52 KiB
C
1888 lines
52 KiB
C
/****************************************************************************
|
|
* fs/spiffs/spiffs.c
|
|
* Interface between SPIFFS and the NuttX VFS
|
|
*
|
|
* Copyright (C) 2018 Gregory Nutt. All rights reserved.
|
|
* Author: Gregory Nutt <gnutt@nuttx.org>
|
|
*
|
|
* Includes logic taken from 0.3.7 of SPIFFS by Peter Andersion. That
|
|
* version was originally released under the MIT license.
|
|
*
|
|
* Copyright (c) 2013-2017 Peter Andersson (pelleplutt1976@gmail.com)
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/statfs.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
#include <semaphore.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <queue.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/fs/dirent.h>
|
|
#include <nuttx/fs/ioctl.h>
|
|
|
|
#include "spiffs.h"
|
|
#include "spiffs_core.h"
|
|
#include "spiffs_cache.h"
|
|
#include "spiffs_gc.h"
|
|
#include "spiffs_check.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define spiffs_lock_volume(fs) (spiffs_lock_reentrant(&fs->exclsem))
|
|
#define spiffs_unlock_volume(fs) (spiffs_unlock_reentrant(&fs->exclsem))
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* SPIFFS helpers */
|
|
|
|
static void spiffs_lock_reentrant(FAR struct spiffs_sem_s *sem);
|
|
static void spiffs_unlock_reentrant(FAR struct spiffs_sem_s *sem);
|
|
|
|
/* File system operations */
|
|
|
|
static int spiffs_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode);
|
|
static int spiffs_close(FAR struct file *filep);
|
|
static ssize_t spiffs_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static ssize_t spiffs_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen);
|
|
static off_t spiffs_seek(FAR struct file *filep, off_t offset, int whence);
|
|
static int spiffs_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
|
|
|
|
static int spiffs_sync(FAR struct file *filep);
|
|
static int spiffs_dup(FAR const struct file *oldp, FAR struct file *newp);
|
|
static int spiffs_fstat(FAR const struct file *filep, FAR struct stat *buf);
|
|
static int spiffs_truncate(FAR struct file *filep, off_t length);
|
|
|
|
static int spiffs_opendir(FAR struct inode *mountpt, FAR const char *relpath,
|
|
FAR struct fs_dirent_s *dir);
|
|
static int spiffs_closedir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir);
|
|
static int spiffs_readdir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir);
|
|
static int spiffs_rewinddir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir);
|
|
static int spiffs_bind(FAR struct inode *mtdinode, FAR const void *data,
|
|
FAR void **handle);
|
|
static int spiffs_unbind(FAR void *handle, FAR struct inode **mtdinode,
|
|
unsigned int flags);
|
|
static int spiffs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf);
|
|
static int spiffs_unlink(FAR struct inode *mountpt, FAR const char *relpath);
|
|
static int spiffs_mkdir(FAR struct inode *mountpt, FAR const char *relpath,
|
|
mode_t mode);
|
|
static int spiffs_rmdir(FAR struct inode *mountpt, FAR const char *relpath);
|
|
static int spiffs_rename(FAR struct inode *mountpt, FAR const char *oldrelpath,
|
|
FAR const char *newrelpath);
|
|
static int spiffs_stat(FAR struct inode *mountpt, FAR const char *relpath,
|
|
FAR struct stat *buf);
|
|
|
|
/****************************************************************************
|
|
* Public Data
|
|
****************************************************************************/
|
|
|
|
const struct mountpt_operations spiffs_operations =
|
|
{
|
|
spiffs_open, /* open */
|
|
spiffs_close, /* close */
|
|
spiffs_read, /* read */
|
|
spiffs_write, /* write */
|
|
spiffs_seek, /* seek */
|
|
spiffs_ioctl, /* ioctl */
|
|
|
|
spiffs_sync, /* sync */
|
|
spiffs_dup, /* dup */
|
|
spiffs_fstat, /* fstat */
|
|
spiffs_truncate, /* truncate */
|
|
|
|
spiffs_opendir, /* opendir */
|
|
spiffs_closedir, /* closedir */
|
|
spiffs_readdir, /* readdir */
|
|
spiffs_rewinddir, /* rewinddir */
|
|
|
|
spiffs_bind, /* bind */
|
|
spiffs_unbind, /* unbind */
|
|
spiffs_statfs, /* statfs */
|
|
|
|
spiffs_unlink, /* unlink */
|
|
spiffs_mkdir, /* mkdir */
|
|
spiffs_rmdir, /* rmdir */
|
|
spiffs_rename, /* rename */
|
|
spiffs_stat, /* stat */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_map_errno
|
|
****************************************************************************/
|
|
|
|
static inline int spiffs_map_errno(int errcode)
|
|
{
|
|
/* Don't return any or our internal error codes to the application */
|
|
|
|
return errcode < SPIFFS_ERR_INTERNAL ? -EFTYPE : errcode;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_lock_reentrant
|
|
****************************************************************************/
|
|
|
|
static void spiffs_lock_reentrant(FAR struct spiffs_sem_s *rsem)
|
|
{
|
|
pid_t me;
|
|
|
|
/* Do we already hold the semaphore? */
|
|
|
|
me = getpid();
|
|
if (me == rsem->holder)
|
|
{
|
|
/* Yes... just increment the count */
|
|
|
|
rsem->count++;
|
|
DEBUGASSERT(rsem->count > 0);
|
|
}
|
|
|
|
/* Take the semaphore (perhaps waiting) */
|
|
|
|
else
|
|
{
|
|
int ret;
|
|
|
|
do
|
|
{
|
|
ret = nxsem_wait(&rsem->sem);
|
|
|
|
/* The only case that an error should occur here is if the wait
|
|
* was awakened by a signal.
|
|
*/
|
|
|
|
DEBUGASSERT(ret >= 0 || ret == -EINTR);
|
|
}
|
|
while (ret == -EINTR);
|
|
|
|
/* No we hold the semaphore */
|
|
|
|
rsem->holder = me;
|
|
rsem->count = 1;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_unlock_reentrant
|
|
****************************************************************************/
|
|
|
|
static void spiffs_unlock_reentrant(FAR struct spiffs_sem_s *rsem)
|
|
{
|
|
DEBUGASSERT(rsem->holder == getpid());
|
|
|
|
/* Is this our last count on the semaphore? */
|
|
|
|
if (rsem->count > 1)
|
|
{
|
|
/* No.. just decrement the count */
|
|
|
|
rsem->count--;
|
|
}
|
|
|
|
/* Yes.. then we can really release the semaphore */
|
|
|
|
else
|
|
{
|
|
rsem->holder = SPIFFS_NO_HOLDER;
|
|
rsem->count = 0;
|
|
nxsem_post(&rsem->sem);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_readdir_callback
|
|
****************************************************************************/
|
|
|
|
static int spiffs_consistency_check(FAR struct spiffs_s *fs)
|
|
{
|
|
int status;
|
|
int ret = OK;
|
|
|
|
status = spiffs_check_luconsistency(fs);
|
|
if (status < 0)
|
|
{
|
|
fwarn("WARNING spiffs_check_luconsistency failed: %d\n", status);
|
|
if (ret >= 0)
|
|
{
|
|
ret = status;
|
|
}
|
|
}
|
|
|
|
status = spiffs_check_objidconsistency(fs);
|
|
if (status < 0)
|
|
{
|
|
fwarn("WARNING spiffs_check_objidconsistency failed: %d\n", status);
|
|
if (ret >= 0)
|
|
{
|
|
ret = status;
|
|
}
|
|
}
|
|
|
|
status = spiffs_check_pgconsistency(fs);
|
|
if (status < 0)
|
|
{
|
|
fwarn("WARNING spiffs_check_pgconsistency failed: %d\n", status);
|
|
if (ret >= 0)
|
|
{
|
|
ret = status;
|
|
}
|
|
}
|
|
|
|
status = spiffs_objlu_scan(fs);
|
|
if (status < 0)
|
|
{
|
|
fwarn("WARNING spiffs_objlu_scan failed: %d\n", status);
|
|
if (ret >= 0)
|
|
{
|
|
ret = status;
|
|
}
|
|
}
|
|
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_readdir_callback
|
|
****************************************************************************/
|
|
|
|
static int spiffs_readdir_callback(FAR struct spiffs_s *fs,
|
|
int16_t objid, int16_t blkndx, int entry,
|
|
FAR const void *user_const,
|
|
FAR void *user_var)
|
|
{
|
|
struct spiffs_pgobj_ndxheader_s objhdr;
|
|
int16_t pgndx;
|
|
int ret;
|
|
|
|
if (objid == SPIFFS_OBJID_FREE || objid == SPIFFS_OBJID_DELETED ||
|
|
(objid & SPIFFS_OBJID_NDXFLAG) == 0)
|
|
{
|
|
return SPIFFS_VIS_COUNTINUE;
|
|
}
|
|
|
|
pgndx = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, entry);
|
|
ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
|
|
0, SPIFFS_PAGE_TO_PADDR(fs, pgndx),
|
|
sizeof(struct spiffs_pgobj_ndxheader_s),
|
|
(FAR uint8_t *) & objhdr);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_cache_read failed: %d\n", ret);
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
if ((objid & SPIFFS_OBJID_NDXFLAG) &&
|
|
objhdr.phdr.spndx == 0 &&
|
|
(objhdr.phdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL |
|
|
SPIFFS_PH_FLAG_NDXDELE)) ==
|
|
(SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_NDXDELE))
|
|
{
|
|
FAR struct fs_dirent_s *dir = (FAR struct fs_dirent_s *)user_var;
|
|
FAR struct dirent *entryp;
|
|
|
|
DEBUGASSERT(dir != NULL);
|
|
entryp = &dir->fd_dir;
|
|
|
|
strncpy(entryp->d_name, (FAR char *)objhdr.name, NAME_MAX + 1);
|
|
entryp->d_type = objhdr.type;
|
|
return OK;
|
|
}
|
|
|
|
return SPIFFS_VIS_COUNTINUE;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_open
|
|
****************************************************************************/
|
|
|
|
static int spiffs_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct spiffs_file_s *fobj;
|
|
off_t offset;
|
|
int16_t pgndx;
|
|
int ret;
|
|
|
|
finfo("relpath=%s oflags; %04x\n", relpath, oflags);
|
|
DEBUGASSERT(filep->f_priv == NULL && filep->f_inode != NULL);
|
|
|
|
/* Get the mountpoint inode reference from the file structure and the
|
|
* mountpoint private data from the inode structure
|
|
*/
|
|
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Skip over any leading directory separators (shouldn't be any) */
|
|
|
|
for (; *relpath == '/'; relpath++)
|
|
{
|
|
}
|
|
|
|
/* Check the length of the relative path */
|
|
|
|
if (strlen(relpath) > CONFIG_SPIFFS_NAME_MAX - 1)
|
|
{
|
|
return -ENAMETOOLONG;
|
|
}
|
|
|
|
/* Allocate a new file object with a reference count of one. */
|
|
|
|
fobj = (FAR struct spiffs_file_s *)kmm_zalloc(sizeof(struct spiffs_file_s));
|
|
if (fobj == NULL)
|
|
{
|
|
ferr("ERROR: Failed to allocate fail object\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
fobj->crefs = 1;
|
|
fobj->oflags = oflags;
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Check of the file object already exists */
|
|
|
|
ret = spiffs_find_objhdr_pgndx(fs, (FAR const uint8_t *)relpath, &pgndx);
|
|
if (ret < 0 && (oflags & O_CREAT) == 0)
|
|
{
|
|
/* It does not exist and we were not asked to create it */
|
|
|
|
fwarn("WARNING: File does not exist and O_CREAT not set\n");
|
|
goto errout_with_fileobject;
|
|
}
|
|
else if (ret >= 0 && (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
|
|
{
|
|
/* O_CREAT and O_EXCL and file exists - fail */
|
|
|
|
fwarn("WARNING: File exists and O_CREAT|O_EXCL is selected\n");
|
|
ret = -EEXIST;
|
|
goto errout_with_fileobject;
|
|
}
|
|
else if ((oflags & O_CREAT) != 0 && ret == -ENOENT)
|
|
{
|
|
int16_t objid;
|
|
|
|
/* The file does not exist. We need to create the it. */
|
|
|
|
ret = spiffs_objlu_find_free_objid(fs, &objid, 0);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_objlu_find_free_objid() failed: %d\n", ret);
|
|
goto errout_with_fileobject;
|
|
}
|
|
|
|
ret = spiffs_fobj_create(fs, objid, (FAR const uint8_t *)relpath,
|
|
DTYPE_FILE, &pgndx);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_fobj_create() failed: %d\n", ret);
|
|
goto errout_with_fileobject;
|
|
}
|
|
|
|
/* Since we created the file, we don't need to truncate it */
|
|
|
|
oflags &= ~O_TRUNC;
|
|
}
|
|
else if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_find_objhdr_pgndx() failed: %d\n", ret);
|
|
goto errout_with_fileobject;
|
|
}
|
|
|
|
/* Open the file */
|
|
|
|
ret = spiffs_fobj_open_bypage(fs, pgndx, fobj);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_fobj_open_bypage() failed: %d\n", ret);
|
|
goto errout_with_fileobject;
|
|
}
|
|
|
|
/* Truncate the file to zero length */
|
|
|
|
if ((oflags & O_TRUNC) != 0)
|
|
{
|
|
ret = spiffs_fobj_truncate(fs, fobj, 0, false);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_fobj_truncate() failed: %d\n", ret);
|
|
goto errout_with_fileobject;
|
|
}
|
|
}
|
|
|
|
/* Save the struct spiffs_file_s instance as the file private data */
|
|
|
|
filep->f_priv = fobj;
|
|
|
|
/* In write/append mode, we need to set the file pointer to the end of the
|
|
* file.
|
|
*/
|
|
|
|
offset = 0;
|
|
if ((oflags & (O_APPEND | O_WROK)) == (O_APPEND | O_WROK))
|
|
{
|
|
offset = fobj->size == SPIFFS_UNDEFINED_LEN ? 0 : fobj->size;
|
|
}
|
|
|
|
/* Save the file position */
|
|
|
|
filep->f_pos = offset;
|
|
|
|
/* Add the new file object to the tail of the open file list */
|
|
|
|
finfo("Adding fobj for objid=%04x\n", fobj->objid);
|
|
dq_addlast((FAR dq_entry_t *)fobj, &fs->objq);
|
|
|
|
spiffs_unlock_volume(fs);
|
|
return OK;
|
|
|
|
errout_with_fileobject:
|
|
kmm_free(fobj);
|
|
spiffs_unlock_volume(fs);
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_close
|
|
****************************************************************************/
|
|
|
|
static int spiffs_close(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct spiffs_file_s *fobj;
|
|
|
|
finfo("filep=%p\n", filep);
|
|
DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
|
|
|
|
/* Get the mountpoint inode reference from the file structure and the
|
|
* volume state data from the inode structure
|
|
*/
|
|
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
fobj = filep->f_priv;
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Decrement the reference count on the file */
|
|
|
|
DEBUGASSERT(fobj->crefs > 0);
|
|
if (fobj->crefs > 0)
|
|
{
|
|
fobj->crefs--;
|
|
}
|
|
|
|
filep->f_priv = NULL;
|
|
|
|
/* If the reference count decremented to zero then free resources related
|
|
* to the open file.
|
|
*/
|
|
|
|
if (fobj->crefs == 0)
|
|
{
|
|
/* Free the file object while we hold the lock? Weird but this
|
|
* should be safe because the object is unlinked and could not
|
|
* have any other references.
|
|
*
|
|
* If the file was unlinked while it was opened, then now would be
|
|
* the time to perform the unlink operation.
|
|
*/
|
|
|
|
spiffs_fobj_free(fs, fobj, (fobj->flags & SFO_FLAG_UNLINKED) != 0);
|
|
}
|
|
|
|
/* Release the lock on the file system */
|
|
|
|
spiffs_unlock_volume(fs);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t spiffs_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct spiffs_file_s *fobj;
|
|
ssize_t nread;
|
|
|
|
finfo("filep=%p buffer=%p buflen=%lu\n",
|
|
filep, buffer, (unsigned long)buflen);
|
|
DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
|
|
|
|
/* Get the mountpoint inode reference from the file structure and the
|
|
* volume state data from the inode structure
|
|
*/
|
|
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Recover the file object state from the struct file instance */
|
|
|
|
fobj = filep->f_priv;
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Read from FLASH */
|
|
|
|
nread = spiffs_fobj_read(fs, fobj, buffer, buflen, filep->f_pos);
|
|
if (nread > 0)
|
|
{
|
|
filep->f_pos += nread;
|
|
}
|
|
|
|
/* Release the lock on the file system */
|
|
|
|
spiffs_unlock_volume(fs);
|
|
return nread;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t spiffs_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct spiffs_file_s *fobj;
|
|
ssize_t nwritten;
|
|
off_t offset;
|
|
int ret;
|
|
|
|
finfo("filep=%p buffer=%p buflen=%lu\n",
|
|
filep, buffer, (unsigned long)buflen);
|
|
DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
|
|
|
|
/* Get the mountpoint inode reference from the file structure and the
|
|
* volume state data from the inode structure
|
|
*/
|
|
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Recover the file object state from the struct file instance */
|
|
|
|
fobj = filep->f_priv;
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Verify that the file was opened with write access */
|
|
|
|
if ((fobj->oflags & O_WROK) == 0)
|
|
{
|
|
ret = -EACCES;
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Write to FLASH (or cache) */
|
|
|
|
offset = filep->f_pos;
|
|
|
|
if (fobj->cache_page == 0)
|
|
{
|
|
/* See if object ID is associated with cache already */
|
|
|
|
fobj->cache_page = spiffs_cache_page_get_byobjid(fs, fobj);
|
|
}
|
|
|
|
if ((fobj->oflags & O_DIRECT) == 0)
|
|
{
|
|
if (buflen < (size_t)SPIFFS_GEO_PAGE_SIZE(fs))
|
|
{
|
|
/* Small write, try to cache it */
|
|
|
|
bool alloc_cpage = true;
|
|
if (fobj->cache_page != NULL)
|
|
{
|
|
/* We have a cached page for this object already, check cache
|
|
* page boundaries
|
|
*/
|
|
|
|
if (offset < fobj->cache_page->offset ||
|
|
offset > fobj->cache_page->offset + fobj->cache_page->size ||
|
|
offset + buflen > fobj->cache_page->offset + SPIFFS_GEO_PAGE_SIZE(fs))
|
|
{
|
|
/* Boundary violation, write back cache first and allocate
|
|
* new
|
|
*/
|
|
|
|
spiffs_cacheinfo("Cache page=%d for fobj ID=%d "
|
|
"Boundary violation, offset=%d size=%d\n",
|
|
fobj->cache_page->cpndx, fobj->objid,
|
|
fobj->cache_page->offset, fobj->cache_page->size);
|
|
|
|
nwritten = spiffs_fobj_write(fs, fobj,
|
|
spiffs_get_cache_page(fs, spiffs_get_cache(fs),
|
|
fobj->cache_page->cpndx),
|
|
fobj->cache_page->offset,
|
|
fobj->cache_page->size);
|
|
spiffs_cache_page_release(fs, fobj->cache_page);
|
|
if (nwritten < 0)
|
|
{
|
|
ret = (int)nwritten;
|
|
goto errout_with_lock;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Writing within cache */
|
|
|
|
alloc_cpage = false;
|
|
}
|
|
}
|
|
|
|
if (alloc_cpage)
|
|
{
|
|
fobj->cache_page = spiffs_cache_page_allocate_byobjid(fs, fobj);
|
|
if (fobj->cache_page)
|
|
{
|
|
fobj->cache_page->offset = offset;
|
|
fobj->cache_page->size = 0;
|
|
|
|
spiffs_cacheinfo("Allocated cache page %d for fobj %d\n",
|
|
fobj->cache_page->cpndx, fobj->objid);
|
|
}
|
|
}
|
|
|
|
if (fobj->cache_page)
|
|
{
|
|
FAR struct spiffs_cache_s *cache;
|
|
FAR uint8_t *cpage_data;
|
|
off_t offset_in_cpage;
|
|
|
|
offset_in_cpage = offset - fobj->cache_page->offset;
|
|
|
|
spiffs_cacheinfo("Storing to cache page %d for fobj %d offset=%d:%d buflen=%d\n",
|
|
fobj->cache_page->cpndx, fobj->objid, offset,
|
|
offset_in_cpage, buflen);
|
|
|
|
cache = spiffs_get_cache(fs);
|
|
cpage_data = spiffs_get_cache_page(fs, cache, fobj->cache_page->cpndx);
|
|
|
|
memcpy(&cpage_data[offset_in_cpage], buffer, buflen);
|
|
fobj->cache_page->size = MAX(fobj->cache_page->size, offset_in_cpage + buflen);
|
|
|
|
nwritten = buflen;
|
|
goto success_with_lock;
|
|
}
|
|
else
|
|
{
|
|
nwritten = spiffs_fobj_write(fs, fobj, buffer, offset, buflen);
|
|
if (nwritten < 0)
|
|
{
|
|
ret = (int)nwritten;
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
goto success_with_lock;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Big write, no need to cache it - but first check if there is a
|
|
* cached write already
|
|
*/
|
|
|
|
if (fobj->cache_page)
|
|
{
|
|
/* Write back cache first */
|
|
|
|
spiffs_cacheinfo("Cache page=%d for fobj ID=%d "
|
|
"Boundary violation, offset=%d size=%d\n",
|
|
fobj->cache_page->cpndx, fobj->objid,
|
|
fobj->cache_page->offset, fobj->cache_page->size);
|
|
|
|
nwritten = spiffs_fobj_write(fs, fobj,
|
|
spiffs_get_cache_page(fs,
|
|
spiffs_get_cache(fs),
|
|
fobj->cache_page->cpndx),
|
|
fobj->cache_page->offset,
|
|
fobj->cache_page->size);
|
|
spiffs_cache_page_release(fs, fobj->cache_page);
|
|
|
|
if (nwritten < 0)
|
|
{
|
|
ret = (int)nwritten;
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Data written below */
|
|
}
|
|
}
|
|
}
|
|
|
|
nwritten = spiffs_fobj_write(fs, fobj, buffer, offset, buflen);
|
|
if (nwritten < 0)
|
|
{
|
|
ret = (int)nwritten;
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
success_with_lock:
|
|
/* Update the file position */
|
|
|
|
filep->f_pos += nwritten;
|
|
|
|
/* Release our access to the volume */
|
|
|
|
spiffs_unlock_volume(fs);
|
|
return nwritten;
|
|
|
|
errout_with_lock:
|
|
spiffs_unlock_volume(fs);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_seek
|
|
****************************************************************************/
|
|
|
|
static off_t spiffs_seek(FAR struct file *filep, off_t offset, int whence)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct spiffs_file_s *fobj;
|
|
int16_t data_spndx;
|
|
int16_t objndx_spndx;
|
|
off_t fsize;
|
|
off_t pos;
|
|
int ret;
|
|
|
|
finfo("filep=%p offset=%ld whence=%d\n", filep, (long)offset, whence);
|
|
DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
|
|
|
|
/* Get the mountpoint inode reference from the file structure and the
|
|
* volume state data from the inode structure
|
|
*/
|
|
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Recover the file object state from the struct file instance */
|
|
|
|
fobj = filep->f_priv;
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Get the new file offset */
|
|
|
|
spiffs_fflush_cache(fs, fobj);
|
|
|
|
fsize = fobj->size == SPIFFS_UNDEFINED_LEN ? 0 : fobj->size;
|
|
|
|
/* Map the offset according to the whence option */
|
|
|
|
switch (whence)
|
|
{
|
|
case SEEK_SET: /* The offset is set to offset bytes. */
|
|
pos = offset;
|
|
break;
|
|
|
|
case SEEK_CUR: /* The offset is set to its current location plus
|
|
* offset bytes. */
|
|
pos = offset + filep->f_pos;
|
|
break;
|
|
|
|
case SEEK_END: /* The offset is set to the size of the file plus
|
|
* offset bytes. */
|
|
pos = fsize + offset;
|
|
break;
|
|
|
|
default:
|
|
ret = -EINVAL;
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Verify the resulting file position */
|
|
|
|
if (pos < 0)
|
|
{
|
|
ret = -EINVAL;
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Attempts to set the position beyond the end of file should
|
|
* work if the file is open for write access.
|
|
*
|
|
* REVISIT: This simple implementation has no per-open storage that
|
|
* would be needed to retain the open flags.
|
|
*/
|
|
|
|
if (pos > fsize)
|
|
{
|
|
filep->f_pos = fsize;
|
|
ret = -ENOSYS;
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Set up for the new file position */
|
|
|
|
|
|
data_spndx = (pos > 0 ? (pos - 1) : 0) / SPIFFS_DATA_PAGE_SIZE(fs);
|
|
objndx_spndx = SPIFFS_OBJNDX_ENTRY_SPNDX(fs, data_spndx);
|
|
|
|
if (fobj->objndx_spndx != objndx_spndx)
|
|
{
|
|
int16_t pgndx;
|
|
|
|
ret = spiffs_objlu_find_id_and_span(fs, fobj->objid | SPIFFS_OBJID_NDXFLAG,
|
|
objndx_spndx, 0, &pgndx);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
fobj->objndx_spndx = objndx_spndx;
|
|
fobj->objndx_pgndx = pgndx;
|
|
}
|
|
|
|
filep->f_pos = pos;
|
|
spiffs_unlock_volume(fs);
|
|
return pos;
|
|
|
|
errout_with_lock:
|
|
spiffs_unlock_volume(fs);
|
|
return (off_t)ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_ioctl
|
|
****************************************************************************/
|
|
|
|
static int spiffs_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct spiffs_s *fs;
|
|
int ret;
|
|
|
|
finfo("filep=%p cmd=%d arg=%ld\n", filep, cmd, (long)arg);
|
|
DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
|
|
|
|
/* Get the mountpoint inode reference from the file structure and the
|
|
* volume state data from the inode structure
|
|
*/
|
|
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Handle the IOCTL according tot he command */
|
|
|
|
switch (cmd)
|
|
{
|
|
/* Run a consistency check on the file system media.
|
|
* IN: None
|
|
* OUT: None
|
|
*/
|
|
|
|
case FIOC_INTEGRITY:
|
|
{
|
|
ret = spiffs_consistency_check(fs);
|
|
}
|
|
break;
|
|
|
|
/* Force reformatting of media. All data will be lost.
|
|
* IN: None
|
|
* OUT: None
|
|
*/
|
|
|
|
case FIOC_REFORMAT:
|
|
{
|
|
/* Check if the MTD driver supports the MTDIOC_BULKERASE command */
|
|
|
|
ret = MTD_IOCTL(fs->mtd, MTDIOC_BULKERASE, 0);
|
|
if (ret < 0)
|
|
{
|
|
/* No.. we will have to erase a block at a time */
|
|
|
|
int16_t blkndx = 0;
|
|
while (blkndx < SPIFFS_GEO_BLOCK_COUNT(fs))
|
|
{
|
|
fs->max_erase_count = 0;
|
|
ret = spiffs_erase_block(fs, blkndx);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_erase_block() failed: %d\n", ret);
|
|
break;
|
|
}
|
|
|
|
blkndx++;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* Run garbage collection.
|
|
* IN: On entry holds the number of bytes to be recovered.
|
|
* OUT: None
|
|
*/
|
|
|
|
case FIOC_OPTIMIZE:
|
|
{
|
|
ret = spiffs_gc_check(fs, (size_t)arg);
|
|
}
|
|
break;
|
|
|
|
/* Dump logical content of FLASH.
|
|
* IN: None
|
|
* OUT: None
|
|
*/
|
|
|
|
#ifdef CONFIG_SPIFFS_DUMP
|
|
case FIOC_DUMP:
|
|
{
|
|
ret = spiffs_dump(fs);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
/* Pass through to the contained MTD driver */
|
|
|
|
ret = MTD_IOCTL(fs->mtd, cmd, arg);
|
|
break;
|
|
}
|
|
|
|
spiffs_unlock_volume(fs);
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_sync
|
|
*
|
|
* Description: Synchronize the file state on disk to match internal, in-
|
|
* memory state.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int spiffs_sync(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct spiffs_file_s *fobj;
|
|
int ret;
|
|
|
|
finfo("filep=%p\n", filep);
|
|
DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
|
|
|
|
/* Get the mountpoint inode reference from the file structure and the
|
|
* volume state data from the inode structure
|
|
*/
|
|
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Recover the file object state from the struct file instance */
|
|
|
|
fobj = filep->f_priv;
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Flush all cached write data */
|
|
|
|
ret = spiffs_fflush_cache(fs, fobj);
|
|
|
|
spiffs_unlock_volume(fs);
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_dup
|
|
****************************************************************************/
|
|
|
|
static int spiffs_dup(FAR const struct file *oldp, FAR struct file *newp)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct spiffs_file_s *fobj;
|
|
|
|
finfo("Dup %p->%p\n", oldp, newp);
|
|
DEBUGASSERT(oldp->f_priv != NULL && oldp->f_inode != NULL &&
|
|
newp->f_priv == NULL && newp->f_inode != NULL);
|
|
|
|
/* Get the mountpoint inode reference from the file structure and the
|
|
* volume state data from the inode structure
|
|
*/
|
|
|
|
inode = oldp->f_inode;
|
|
fs = inode->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
fobj = oldp->f_priv;
|
|
|
|
/* Increment the reference count (atomically)*/
|
|
|
|
spiffs_lock_volume(fs);
|
|
fobj->crefs++;
|
|
spiffs_unlock_volume(fs);
|
|
|
|
/* Save a copy of the file object as the dup'ed file. */
|
|
|
|
newp->f_priv = fobj;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_fstat
|
|
*
|
|
* Description:
|
|
* Obtain information about an open file associated with the file
|
|
* descriptor 'fobj', and will write it to the area pointed to by 'buf'.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int spiffs_fstat(FAR const struct file *filep, FAR struct stat *buf)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct spiffs_file_s *fobj;
|
|
int ret;
|
|
|
|
finfo("filep=%p buf=%p\n", filep, buf);
|
|
DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL && buf != NULL);
|
|
|
|
/* Get the mountpoint inode reference from the file structure and the
|
|
* volume state data from the inode structure
|
|
*/
|
|
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Recover the file object state from the struct file instance */
|
|
|
|
fobj = filep->f_priv;
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Flush the cache and perform the common stat() operation */
|
|
|
|
spiffs_fflush_cache(fs, fobj);
|
|
|
|
ret = spiffs_stat_pgndx(fs, fobj->objhdr_pgndx, fobj->objid, buf);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_stat_pgndx failed: %d\n", ret);
|
|
}
|
|
|
|
spiffs_unlock_volume(fs);
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_truncate
|
|
****************************************************************************/
|
|
|
|
static int spiffs_truncate(FAR struct file *filep, off_t length)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct spiffs_file_s *fobj;
|
|
off_t fsize;
|
|
int ret;
|
|
|
|
finfo("filep=%p length=%ld\n", filep, (long)length);
|
|
DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL && length >= 0);
|
|
|
|
/* Get the mountpoint inode reference from the file structure and the
|
|
* volume state data from the inode structure
|
|
*/
|
|
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Recover the file object state from the struct file instance */
|
|
|
|
fobj = filep->f_priv;
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* REVISIT: spiffs_fobj_truncate() can only truncate to smaller sizes. */
|
|
|
|
ret = spiffs_fobj_truncate(fs, fobj, length, false);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_fobj_truncate failed: %d/n", ret);
|
|
}
|
|
|
|
/* Check if we need to reset the file pointer. Probably could use
|
|
* 'length', but let's use the authoritative file file size for the
|
|
* comparison.
|
|
*/
|
|
|
|
fsize = fobj->size == SPIFFS_UNDEFINED_LEN ? 0 : fobj->size;
|
|
|
|
if (ret >= 0 && fsize < filep->f_pos)
|
|
{
|
|
/* Reset the file pointer to the new end-of-file position */
|
|
|
|
filep->f_pos = fsize;
|
|
}
|
|
|
|
spiffs_unlock_volume(fs);
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_opendir
|
|
****************************************************************************/
|
|
|
|
static int spiffs_opendir(FAR struct inode *mountpt, FAR const char *relpath,
|
|
FAR struct fs_dirent_s *dir)
|
|
{
|
|
finfo("mountpt=%p relpath=%s dir=%p\n",
|
|
mountpt, relpath, dir);
|
|
|
|
DEBUGASSERT(mountpt != NULL && relpath != NULL && dir != NULL);
|
|
|
|
/* Initialize for traversal of the 'directory' */
|
|
|
|
dir->u.spiffs.block = 0;
|
|
dir->u.spiffs.entry = 0;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_closedir
|
|
****************************************************************************/
|
|
|
|
static int spiffs_closedir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir)
|
|
{
|
|
finfo("mountpt=%p dir=%p\n", mountpt, dir);
|
|
DEBUGASSERT(mountpt != NULL && dir != NULL);
|
|
|
|
/* There is nothing to be done */
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_readdir
|
|
****************************************************************************/
|
|
|
|
static int spiffs_readdir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir)
|
|
{
|
|
FAR struct spiffs_s *fs;
|
|
int16_t blkndx;
|
|
int entry;
|
|
int ret;
|
|
|
|
finfo("mountpt=%p dir=%p\n", mountpt, dir);
|
|
DEBUGASSERT(mountpt != NULL && dir != NULL);
|
|
|
|
/* Get the mountpoint private data from the inode structure */
|
|
|
|
fs = mountpt->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Lock the SPIFFS volume */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* And visit the next file object */
|
|
|
|
ret = spiffs_foreach_objlu(fs, dir->u.spiffs.block, dir->u.spiffs.entry,
|
|
SPIFFS_VIS_NO_WRAP, 0, spiffs_readdir_callback,
|
|
NULL, dir, &blkndx, &entry);
|
|
if (ret >= 0)
|
|
{
|
|
dir->u.spiffs.block = blkndx;
|
|
dir->u.spiffs.entry = entry + 1;
|
|
}
|
|
|
|
/* Release the lock on the file system */
|
|
|
|
spiffs_unlock_volume(fs);
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_rewinddir
|
|
****************************************************************************/
|
|
|
|
static int spiffs_rewinddir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir)
|
|
{
|
|
finfo("mountpt=%p dir=%p\n", mountpt, dir);
|
|
DEBUGASSERT(mountpt != NULL && dir != NULL);
|
|
|
|
/* Reset as when opendir() was called. */
|
|
|
|
dir->u.spiffs.block = 0;
|
|
dir->u.spiffs.entry = 0;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_bind
|
|
****************************************************************************/
|
|
|
|
static int spiffs_bind(FAR struct inode *mtdinode, FAR const void *data,
|
|
FAR void **handle)
|
|
{
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct mtd_dev_s *mtd;
|
|
FAR uint8_t *work;
|
|
size_t cache_size;
|
|
size_t cache_max;
|
|
size_t work_size;
|
|
size_t addrmask;
|
|
int ret;
|
|
|
|
finfo("mtdinode=%p data=%p handle=%p\n", mtdinode, data, handle);
|
|
DEBUGASSERT(mtdinode != NULL && handle != NULL);
|
|
|
|
/* Extract the MTD interface reference */
|
|
|
|
DEBUGASSERT(INODE_IS_MTD(mtdinode) && mtdinode->u.i_mtd != NULL);
|
|
mtd = mtdinode->u.i_mtd;
|
|
|
|
/* Create an instance of the SPIFFS file system */
|
|
|
|
fs = (FAR struct spiffs_s *)kmm_zalloc(sizeof(struct spiffs_s));
|
|
if (fs == NULL)
|
|
{
|
|
ferr("ERROR: Failed to allocate volume structure\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
fs->mtd = mtd;
|
|
|
|
/* Get the MTD geometry */
|
|
|
|
ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&fs->geo));
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: MTD_IOCTL(MTDIOC_GEOMETRY) failed: %d\n", ret);
|
|
goto errout_with_volume;
|
|
}
|
|
|
|
fs->media_size = SPIFFS_GEO_EBLOCK_COUNT(fs) * SPIFFS_GEO_EBLOCK_SIZE(fs);
|
|
fs->total_pages = fs->media_size / SPIFFS_GEO_PAGE_SIZE(fs);
|
|
fs->pages_per_block = SPIFFS_GEO_EBLOCK_SIZE(fs) / SPIFFS_GEO_PAGE_SIZE(fs);
|
|
|
|
/* Get the aligned cache size */
|
|
|
|
addrmask = (sizeof(FAR void *) - 1);
|
|
cache_size = (CONFIG_SPIFFS_CACHE_SIZE + addrmask) & ~addrmask;
|
|
|
|
/* Don't let the cache size exceed the maximum that is needed */
|
|
|
|
cache_max = SPIFFS_GEO_PAGE_SIZE(fs) << 5;
|
|
if (cache_size > cache_max)
|
|
{
|
|
cache_size = cache_max;
|
|
}
|
|
|
|
/* Allocate the cache */
|
|
|
|
fs->cache_size = cache_size;
|
|
fs->cache = (FAR void *)kmm_malloc(cache_size);
|
|
|
|
if (fs->cache == NULL)
|
|
{
|
|
ferr("ERROR: Failed to allocate volume structure\n");
|
|
ret = -ENOMEM;
|
|
goto errout_with_volume;
|
|
}
|
|
|
|
spiffs_cache_initialize(fs);
|
|
|
|
/* Allocate the memory work buffer comprising 3*config->page_size bytes
|
|
* used throughout all file system operations.
|
|
*
|
|
* NOTE: Currently page size is equivalent to block size.
|
|
*
|
|
* REVISIT: The MTD work buffer was added. With some careful analysis,
|
|
* it should, however, be possible to get by with fewer page buffers.
|
|
*/
|
|
|
|
work_size = 3 * SPIFFS_GEO_PAGE_SIZE(fs);
|
|
work = (FAR uint8_t *)kmm_malloc(work_size);
|
|
|
|
if (work == NULL)
|
|
{
|
|
ferr("ERROR: Failed to allocate work buffer\n");
|
|
ret = -ENOMEM;
|
|
goto errout_with_cache;
|
|
}
|
|
|
|
fs->work = &work[0];
|
|
fs->lu_work = &work[SPIFFS_GEO_PAGE_SIZE(fs)];
|
|
fs->mtd_work = &work[2 * SPIFFS_GEO_PAGE_SIZE(fs)];
|
|
|
|
(void)nxsem_init(&fs->exclsem.sem, 0, 1);
|
|
|
|
/* Check the file system */
|
|
|
|
ret = spiffs_objlu_scan(fs);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_objlu_scan() failed: %d\n", ret);
|
|
goto errout_with_work;
|
|
}
|
|
|
|
finfo("page index byte len: %u\n",
|
|
(unsigned int)SPIFFS_GEO_PAGE_SIZE(fs));
|
|
finfo("object lookup pages: %u\n",
|
|
(unsigned int)SPIFFS_OBJ_LOOKUP_PAGES(fs));
|
|
finfo("page pages per block: %u\n",
|
|
(unsigned int)SPIFFS_GEO_PAGES_PER_BLOCK(fs));
|
|
finfo("page header length: %u\n",
|
|
(unsigned int)sizeof(struct spiffs_page_header_s));
|
|
finfo("object header index entries: %u\n",
|
|
(unsigned int)SPIFFS_OBJHDR_NDXLEN(fs));
|
|
finfo("object index entries: %u\n",
|
|
(unsigned int)SPIFFS_OBJNDX_LEN(fs));
|
|
finfo("free blocks: %u\n",
|
|
(unsigned int)fs->free_blocks);
|
|
|
|
#ifdef CONFIG_SPIFFS_CHECK_ONMOUNT
|
|
/* Perform the full consistency check */
|
|
|
|
ret = spiffs_consistency_check(fs);
|
|
if (ret < 0)
|
|
{
|
|
fwarn("WARNING: File system is damaged: %d\n", ret);
|
|
}
|
|
#endif
|
|
|
|
/* Return the new file system handle */
|
|
|
|
*handle = (FAR void *)fs;
|
|
return OK;
|
|
|
|
errout_with_work:
|
|
kmm_free(fs->work);
|
|
|
|
errout_with_cache:
|
|
kmm_free(fs->cache);
|
|
|
|
errout_with_volume:
|
|
kmm_free(fs);
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_unbind
|
|
****************************************************************************/
|
|
|
|
static int spiffs_unbind(FAR void *handle, FAR struct inode **mtdinode,
|
|
unsigned int flags)
|
|
{
|
|
FAR struct spiffs_s *fs = (FAR struct spiffs_s *)handle;
|
|
FAR struct spiffs_file_s *fobj;
|
|
int ret;
|
|
|
|
finfo("handle=%p mtdinode=%p flags=%02x\n",
|
|
handle, mtdinode, flags);
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Lock the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Are there open file system? If so, are we being forced to unmount? */
|
|
|
|
if (!dq_empty(&fs->objq) && (flags & MNT_FORCE) == 0)
|
|
{
|
|
fwarn("WARNING: Open files and umount not forced\n");
|
|
ret = -EBUSY;
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Release all of the open file objects... Very scary stuff. */
|
|
|
|
while ((fobj = (FAR struct spiffs_file_s *)dq_peek(&fs->objq)) != NULL)
|
|
{
|
|
/* Free the file object */
|
|
|
|
spiffs_fobj_free(fs, fobj, false);
|
|
}
|
|
|
|
/* Free allocated working buffers */
|
|
|
|
if (fs->work != NULL)
|
|
{
|
|
kmm_free(fs->work);
|
|
}
|
|
|
|
if (fs->cache != NULL)
|
|
{
|
|
kmm_free(fs->cache);
|
|
}
|
|
|
|
/* Free the volume memory (note that the semaphore is now stale!) */
|
|
|
|
nxsem_destroy(&fs->exclsem.sem);
|
|
kmm_free(fs);
|
|
ret = OK;
|
|
|
|
errout_with_lock:
|
|
spiffs_unlock_volume(fs);
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_statfs
|
|
****************************************************************************/
|
|
|
|
static int spiffs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf)
|
|
{
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct spiffs_file_s *fobj;
|
|
uint32_t pages_per_block;
|
|
uint32_t blocks;
|
|
uint32_t obj_lupages;
|
|
uint32_t data_pgsize;
|
|
uint32_t ndata_pages;
|
|
uint32_t nfile_objs;
|
|
|
|
finfo("mountpt=%p buf=%p\n", mountpt, buf);
|
|
DEBUGASSERT(mountpt != NULL && buf != NULL);
|
|
|
|
/* Get the mountpoint private data from the inode structure */
|
|
|
|
fs = mountpt->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Lock the SPIFFS volume */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Collect some statistics */
|
|
|
|
pages_per_block = SPIFFS_GEO_PAGES_PER_BLOCK(fs);
|
|
blocks = SPIFFS_GEO_BLOCK_COUNT(fs);
|
|
obj_lupages = SPIFFS_OBJ_LOOKUP_PAGES(fs);
|
|
data_pgsize = SPIFFS_DATA_PAGE_SIZE(fs);
|
|
|
|
/* -2 for spare blocks, +1 for emergency page */
|
|
|
|
ndata_pages = (blocks - 2) * (pages_per_block - obj_lupages) + 1;
|
|
|
|
/* Count the number of file objects */
|
|
|
|
nfile_objs = 0;
|
|
for (fobj = (FAR struct spiffs_file_s *)dq_peek(&fs->objq);
|
|
fobj != NULL;
|
|
fobj = (FAR struct spiffs_file_s *)dq_next((FAR dq_entry_t *)fobj))
|
|
{
|
|
nfile_objs++;
|
|
}
|
|
|
|
/* Fill in the statfs structure */
|
|
|
|
buf->f_type = SPIFFS_SUPER_MAGIC;
|
|
buf->f_namelen = CONFIG_SPIFFS_NAME_MAX - 1;
|
|
buf->f_bsize = data_pgsize;
|
|
buf->f_blocks = ndata_pages;
|
|
buf->f_bfree = ndata_pages - fs->alloc_pages;
|
|
buf->f_bavail = buf->f_bfree;
|
|
buf->f_files = nfile_objs;
|
|
buf->f_ffree = buf->f_bfree; /* SWAG */
|
|
|
|
/* Release the lock on the file system */
|
|
|
|
spiffs_unlock_volume(fs);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_unlink
|
|
****************************************************************************/
|
|
|
|
static int spiffs_unlink(FAR struct inode *mountpt, FAR const char *relpath)
|
|
{
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct spiffs_file_s *fobj;
|
|
int16_t pgndx;
|
|
int ret;
|
|
|
|
finfo("mountpt=%p relpath=%s\n", mountpt, relpath);
|
|
DEBUGASSERT(mountpt != NULL && relpath != NULL);
|
|
|
|
if (strlen(relpath) > CONFIG_SPIFFS_NAME_MAX - 1)
|
|
{
|
|
return -ENAMETOOLONG;
|
|
}
|
|
|
|
/* Get the file system structure from the inode reference. */
|
|
|
|
fs = mountpt->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Find the page index to the object header associated with this path */
|
|
|
|
ret = spiffs_find_objhdr_pgndx(fs, (FAR const uint8_t *)relpath, &pgndx);
|
|
if (ret == -ENOENT)
|
|
{
|
|
fwarn("WARNING: No objhdr found for relpath '%s': %d\n", relpath, ret);
|
|
goto errout_with_lock;
|
|
}
|
|
else if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_find_objhdr_pgndx failed: %d\n", ret);
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Check to see if there is an open file reference for the object at this
|
|
* page index.
|
|
*/
|
|
|
|
ret = spiffs_find_fobj_bypgndx(fs, pgndx, &fobj);
|
|
if (ret >= 0)
|
|
{
|
|
/* If so, then we cannot unlink the file now. Just mark the file as
|
|
* 'unlinked' so that it can be removed when the file object is
|
|
* released.
|
|
*/
|
|
|
|
fobj->flags |= SFO_FLAG_UNLINKED;
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, we will need to re-open the file */
|
|
/* Allocate new file object */
|
|
|
|
fobj = (FAR struct spiffs_file_s *)kmm_zalloc(sizeof(struct spiffs_file_s));
|
|
if (fobj == NULL)
|
|
{
|
|
fwarn("WARNING: Failed to allocate fobjs\n");
|
|
ret = -ENOMEM;
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Use the page index to open the file */
|
|
|
|
ret = spiffs_fobj_open_bypage(fs, pgndx, fobj);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_fobj_open_bypage failed: %d\n", ret);
|
|
kmm_free(fobj);
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Now we can remove the file by truncating it to zero length */
|
|
|
|
ret = spiffs_fobj_truncate(fs, fobj, 0, true);
|
|
kmm_free(fobj);
|
|
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_fobj_truncate failed: %d\n", ret);
|
|
goto errout_with_lock;
|
|
}
|
|
}
|
|
|
|
/* Release the lock on the volume */
|
|
|
|
spiffs_unlock_volume(fs);
|
|
return OK;
|
|
|
|
errout_with_lock:
|
|
spiffs_unlock_volume(fs);
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_mkdir
|
|
****************************************************************************/
|
|
|
|
static int spiffs_mkdir(FAR struct inode *mountpt, FAR const char *relpath,
|
|
mode_t mode)
|
|
{
|
|
finfo("mountpt=%p relpath=%s mode=%04x\n", mountpt, relpath, mode);
|
|
DEBUGASSERT(mountpt != NULL && relpath != NULL);
|
|
|
|
/* Directories are not supported */
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_rmdir
|
|
****************************************************************************/
|
|
|
|
static int spiffs_rmdir(FAR struct inode *mountpt, FAR const char *relpath)
|
|
{
|
|
finfo("mountpt=%p relpath=%s\n", mountpt, relpath);
|
|
DEBUGASSERT(mountpt != NULL && relpath != NULL);
|
|
|
|
/* Directories are not supported */
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_rename
|
|
****************************************************************************/
|
|
|
|
static int spiffs_rename(FAR struct inode *mountpt, FAR const char *oldrelpath,
|
|
FAR const char *newrelpath)
|
|
{
|
|
FAR struct spiffs_s *fs;
|
|
FAR struct spiffs_file_s *fobj;
|
|
int16_t oldpgndx;
|
|
int16_t newpgndx;
|
|
int ret;
|
|
|
|
finfo("mountpt=%p oldrelpath=%s newrelpath=%s\n",
|
|
mountpt, oldrelpath, newrelpath);
|
|
DEBUGASSERT(mountpt != NULL && oldrelpath != NULL && newrelpath != NULL);
|
|
|
|
/* Get the file system structure from the inode reference. */
|
|
|
|
fs = mountpt->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
if (strlen(newrelpath) > CONFIG_SPIFFS_NAME_MAX - 1 ||
|
|
strlen(oldrelpath) > CONFIG_SPIFFS_NAME_MAX - 1)
|
|
{
|
|
return -ENAMETOOLONG;
|
|
}
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Get the page index of the object header for the oldrelpath */
|
|
|
|
ret = spiffs_find_objhdr_pgndx(fs, (FAR const uint8_t *)oldrelpath,
|
|
&oldpgndx);
|
|
if (ret < 0)
|
|
{
|
|
fwarn("WARNING: spiffs_find_objhdr_pgndx failed: %d\n");
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Check if there is any file object corresponding to the newrelpath */
|
|
|
|
ret = spiffs_find_objhdr_pgndx(fs, (FAR const uint8_t *)newrelpath,
|
|
&newpgndx);
|
|
if (ret == -ENOENT)
|
|
{
|
|
ret = OK;
|
|
}
|
|
else if (ret >= 0)
|
|
{
|
|
ret = -EEXIST;
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Allocate new file object. NOTE: The file could already be open. */
|
|
|
|
fobj = (FAR struct spiffs_file_s *)kmm_zalloc(sizeof(struct spiffs_file_s));
|
|
if (fobj == NULL)
|
|
{
|
|
ret = -ENOMEM;
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Use the page index to open the file */
|
|
|
|
ret = spiffs_fobj_open_bypage(fs, oldpgndx, fobj);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_fobj;
|
|
}
|
|
|
|
/* Then update the file name */
|
|
|
|
ret = spiffs_fobj_update_ndxhdr(fs, fobj, fobj->objid,
|
|
fobj->objhdr_pgndx, 0,
|
|
(FAR const uint8_t *)newrelpath, 0,
|
|
&newpgndx);
|
|
|
|
errout_with_fobj:
|
|
kmm_free(fobj);
|
|
|
|
errout_with_lock:
|
|
spiffs_unlock_volume(fs);
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_stat
|
|
****************************************************************************/
|
|
|
|
static int spiffs_stat(FAR struct inode *mountpt, FAR const char *relpath,
|
|
FAR struct stat *buf)
|
|
{
|
|
FAR struct spiffs_s *fs;
|
|
int16_t pgndx;
|
|
int len;
|
|
int ret;
|
|
|
|
finfo("mountpt=%p relpath=%s buf=%p\n", mountpt, relpath, buf);
|
|
DEBUGASSERT(mountpt != NULL && relpath != NULL && buf != NULL);
|
|
|
|
/* Skip over any leading directory separators (shouldn't be any) */
|
|
|
|
for (; *relpath == '/'; relpath++)
|
|
{
|
|
}
|
|
|
|
/* Handle long file names */
|
|
|
|
len = strlen(relpath);
|
|
if (len > CONFIG_SPIFFS_NAME_MAX - 1)
|
|
{
|
|
return -ENAMETOOLONG;
|
|
}
|
|
|
|
/* Get the file system structure from the inode reference. */
|
|
|
|
fs = mountpt->i_private;
|
|
DEBUGASSERT(fs != NULL);
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
spiffs_lock_volume(fs);
|
|
|
|
/* Handle stat of the SPIFFS root directory */
|
|
|
|
if (len == 0)
|
|
{
|
|
memset(buf, 0, sizeof(struct stat));
|
|
|
|
buf->st_mode = S_IFDIR | S_IRWXO | S_IRWXG | S_IRWXU;
|
|
buf->st_blksize = fs->geo.blocksize;
|
|
buf->st_blocks = fs->media_size / fs->geo.blocksize;
|
|
ret = OK;
|
|
}
|
|
else
|
|
{
|
|
/* Find the object associated with this relative path */
|
|
|
|
ret = spiffs_find_objhdr_pgndx(fs, (FAR const uint8_t *)relpath,
|
|
&pgndx);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* And get information about the object */
|
|
|
|
ret = spiffs_stat_pgndx(fs, pgndx, 0, buf);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: spiffs_stat_pgndx failed: %d\n", ret);
|
|
}
|
|
}
|
|
|
|
errout_with_lock:
|
|
spiffs_unlock_volume(fs);
|
|
return spiffs_map_errno(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|