946ed96926
For some case, need large files from tmpfs or shmfs, will lead to high pressure on memory fragments, add an optional fs_heap with independent heap will benifit for memory fragments issue. Signed-off-by: buxiasen <buxiasen@xiaomi.com>
2837 lines
72 KiB
C
2837 lines
72 KiB
C
/****************************************************************************
|
|
* fs/tmpfs/fs_tmpfs.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 <nuttx/config.h>
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/statfs.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/fs/ioctl.h>
|
|
|
|
#include "inode/inode.h"
|
|
#include "fs_tmpfs.h"
|
|
#include "fs_heap.h"
|
|
|
|
#ifndef CONFIG_DISABLE_MOUNTPOINT
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#if CONFIG_FS_TMPFS_DIRECTORY_FREEGUARD <= CONFIG_FS_TMPFS_DIRECTORY_ALLOCGUARD
|
|
# warning CONFIG_FS_TMPFS_DIRECTORY_FREEGUARD needs to be > ALLOCGUARD
|
|
#endif
|
|
|
|
#if CONFIG_FS_TMPFS_FILE_FREEGUARD <= CONFIG_FS_TMPFS_FILE_ALLOCGUARD
|
|
# warning CONFIG_FS_TMPFS_FILE_FREEGUARD needs to be > ALLOCGUARD
|
|
#endif
|
|
|
|
#define tmpfs_lock(fs) \
|
|
nxrmutex_lock(&fs->tfs_lock)
|
|
#define tmpfs_lock_object(to) \
|
|
nxrmutex_lock(&to->to_lock)
|
|
#define tmpfs_lock_file(tfo) \
|
|
nxrmutex_lock(&tfo->tfo_lock)
|
|
#define tmpfs_lock_directory(tdo) \
|
|
nxrmutex_lock(&tdo->tdo_lock)
|
|
#define tmpfs_unlock(fs) \
|
|
nxrmutex_unlock(&fs->tfs_lock)
|
|
#define tmpfs_unlock_object(to) \
|
|
nxrmutex_unlock(&to->to_lock)
|
|
#define tmpfs_unlock_file(tfo) \
|
|
nxrmutex_unlock(&tfo->tfo_lock)
|
|
#define tmpfs_unlock_directory(tdo) \
|
|
nxrmutex_unlock(&tdo->tdo_lock)
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct tmpfs_dir_s
|
|
{
|
|
struct fs_dirent_s tf_base; /* Vfs directory structure */
|
|
FAR struct tmpfs_directory_s *tf_tdo; /* Directory being enumerated */
|
|
unsigned int tf_index; /* Directory index */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* TMPFS helpers */
|
|
|
|
static int tmpfs_realloc_directory(FAR struct tmpfs_directory_s *tdo,
|
|
unsigned int nentries);
|
|
static int tmpfs_realloc_file(FAR struct tmpfs_file_s *tfo,
|
|
size_t newsize);
|
|
static void tmpfs_release_lockedobject(FAR struct tmpfs_object_s *to);
|
|
static void tmpfs_release_lockedfile(FAR struct tmpfs_file_s *tfo);
|
|
static int tmpfs_release_file(FAR struct tmpfs_file_s *tfo);
|
|
static int tmpfs_find_dirent(FAR struct tmpfs_directory_s *tdo,
|
|
FAR const char *name, size_t len);
|
|
static int tmpfs_remove_dirent(FAR struct tmpfs_directory_s *tdo,
|
|
FAR const char *name);
|
|
static int tmpfs_add_dirent(FAR struct tmpfs_directory_s *tdo,
|
|
FAR struct tmpfs_object_s *to, FAR const char *name);
|
|
static FAR struct tmpfs_file_s *
|
|
tmpfs_alloc_file(FAR struct tmpfs_directory_s *parent);
|
|
static int tmpfs_create_file(FAR struct tmpfs_s *fs,
|
|
FAR const char *relpath, FAR struct tmpfs_file_s **tfo);
|
|
static FAR struct tmpfs_directory_s *
|
|
tmpfs_alloc_directory(FAR struct tmpfs_directory_s *parent);
|
|
static int tmpfs_create_directory(FAR struct tmpfs_s *fs,
|
|
FAR const char *relpath, FAR struct tmpfs_directory_s **tdo);
|
|
static int tmpfs_find_object(FAR struct tmpfs_s *fs,
|
|
FAR const char *relpath, size_t len,
|
|
FAR struct tmpfs_object_s **object,
|
|
FAR struct tmpfs_directory_s **parent);
|
|
static int tmpfs_find_file(FAR struct tmpfs_s *fs,
|
|
FAR const char *relpath,
|
|
FAR struct tmpfs_file_s **tfo,
|
|
FAR struct tmpfs_directory_s **parent);
|
|
static int tmpfs_find_directory(FAR struct tmpfs_s *fs,
|
|
FAR const char *relpath, size_t len,
|
|
FAR struct tmpfs_directory_s **tdo,
|
|
FAR struct tmpfs_directory_s **parent);
|
|
static int tmpfs_statfs_callout(FAR struct tmpfs_directory_s *tdo,
|
|
unsigned int index, FAR void *arg);
|
|
static int tmpfs_free_callout(FAR struct tmpfs_directory_s *tdo,
|
|
unsigned int index, FAR void *arg);
|
|
static int tmpfs_foreach(FAR struct tmpfs_directory_s *tdo,
|
|
tmpfs_foreach_t callout, FAR void *arg);
|
|
|
|
/* File system operations */
|
|
|
|
static int tmpfs_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode);
|
|
static int tmpfs_close(FAR struct file *filep);
|
|
static ssize_t tmpfs_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static ssize_t tmpfs_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen);
|
|
static off_t tmpfs_seek(FAR struct file *filep, off_t offset, int whence);
|
|
static int tmpfs_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
|
|
static int tmpfs_sync(FAR struct file *filep);
|
|
static int tmpfs_dup(FAR const struct file *oldp, FAR struct file *newp);
|
|
static int tmpfs_fstat(FAR const struct file *filep, FAR struct stat *buf);
|
|
static int tmpfs_truncate(FAR struct file *filep, off_t length);
|
|
static int tmpfs_mmap(FAR struct file *filep,
|
|
FAR struct mm_map_entry_s *map);
|
|
|
|
static int tmpfs_opendir(FAR struct inode *mountpt, FAR const char *relpath,
|
|
FAR struct fs_dirent_s **dir);
|
|
static int tmpfs_closedir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir);
|
|
static int tmpfs_readdir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir,
|
|
FAR struct dirent *entry);
|
|
static int tmpfs_rewinddir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir);
|
|
static int tmpfs_bind(FAR struct inode *blkdriver, FAR const void *data,
|
|
FAR void **handle);
|
|
static int tmpfs_unbind(FAR void *handle, FAR struct inode **blkdriver,
|
|
unsigned int flags);
|
|
static int tmpfs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf);
|
|
static int tmpfs_unlink(FAR struct inode *mountpt, FAR const char *relpath);
|
|
static int tmpfs_mkdir(FAR struct inode *mountpt, FAR const char *relpath,
|
|
mode_t mode);
|
|
static int tmpfs_rmdir(FAR struct inode *mountpt, FAR const char *relpath);
|
|
static int tmpfs_rename(FAR struct inode *mountpt,
|
|
FAR const char *oldrelpath, FAR const char *newrelpath);
|
|
static void tmpfs_stat_common(FAR struct tmpfs_object_s *to,
|
|
FAR struct stat *buf);
|
|
static int tmpfs_stat(FAR struct inode *mountpt, FAR const char *relpath,
|
|
FAR struct stat *buf);
|
|
|
|
/****************************************************************************
|
|
* Public Data
|
|
****************************************************************************/
|
|
|
|
const struct mountpt_operations g_tmpfs_operations =
|
|
{
|
|
tmpfs_open, /* open */
|
|
tmpfs_close, /* close */
|
|
tmpfs_read, /* read */
|
|
tmpfs_write, /* write */
|
|
tmpfs_seek, /* seek */
|
|
tmpfs_ioctl, /* ioctl */
|
|
tmpfs_mmap, /* mmap */
|
|
tmpfs_truncate, /* truncate */
|
|
NULL, /* poll */
|
|
|
|
tmpfs_sync, /* sync */
|
|
tmpfs_dup, /* dup */
|
|
tmpfs_fstat, /* fstat */
|
|
NULL, /* fchstat */
|
|
|
|
tmpfs_opendir, /* opendir */
|
|
tmpfs_closedir, /* closedir */
|
|
tmpfs_readdir, /* readdir */
|
|
tmpfs_rewinddir, /* rewinddir */
|
|
|
|
tmpfs_bind, /* bind */
|
|
tmpfs_unbind, /* unbind */
|
|
tmpfs_statfs, /* statfs */
|
|
|
|
tmpfs_unlink, /* unlink */
|
|
tmpfs_mkdir, /* mkdir */
|
|
tmpfs_rmdir, /* rmdir */
|
|
tmpfs_rename, /* rename */
|
|
tmpfs_stat, /* stat */
|
|
NULL /* chstat */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_realloc_directory
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_realloc_directory(FAR struct tmpfs_directory_s *tdo,
|
|
unsigned int nentries)
|
|
{
|
|
FAR struct tmpfs_dirent_s *newentry;
|
|
size_t objsize;
|
|
int ret = tdo->tdo_nentries;
|
|
|
|
/* Get the new object size */
|
|
|
|
objsize = SIZEOF_TMPFS_DIRECTORY(nentries);
|
|
if (objsize <= tdo->tdo_alloc)
|
|
{
|
|
/* Already big enough.
|
|
* REVISIT: Missing logic to shrink directory objects.
|
|
*/
|
|
|
|
tdo->tdo_nentries = nentries;
|
|
return ret;
|
|
}
|
|
|
|
/* Added some additional amount to the new size to account frequent
|
|
* reallocations.
|
|
*/
|
|
|
|
objsize += CONFIG_FS_TMPFS_DIRECTORY_ALLOCGUARD;
|
|
|
|
/* Realloc the directory object */
|
|
|
|
newentry = fs_heap_realloc(tdo->tdo_entry, objsize);
|
|
if (newentry == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Return the new address of the reallocated directory object */
|
|
|
|
tdo->tdo_alloc = objsize;
|
|
tdo->tdo_nentries = nentries;
|
|
tdo->tdo_entry = newentry;
|
|
|
|
/* Return the index to the first, newly allocated directory entry */
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_realloc_file
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_realloc_file(FAR struct tmpfs_file_s *tfo,
|
|
size_t newsize)
|
|
{
|
|
FAR uint8_t *newdata;
|
|
size_t allocsize;
|
|
size_t delta;
|
|
|
|
/* Are we growing or shrinking the object? */
|
|
|
|
if (newsize <= tfo->tfo_alloc)
|
|
{
|
|
/* Shrinking ... Shrink unconditionally if the size is shrinking to
|
|
* zero.
|
|
*/
|
|
|
|
if (newsize == 0)
|
|
{
|
|
/* Free the file object */
|
|
|
|
fs_heap_free(tfo->tfo_data);
|
|
tfo->tfo_data = NULL;
|
|
tfo->tfo_alloc = 0;
|
|
tfo->tfo_size = 0;
|
|
return OK;
|
|
}
|
|
else if (newsize > 0)
|
|
{
|
|
/* Otherwise, don't realloc unless the object has shrunk by a
|
|
* lot.
|
|
*/
|
|
|
|
delta = tfo->tfo_alloc - newsize;
|
|
|
|
/* We should make sure the shrunked memory be zero */
|
|
|
|
memset(tfo->tfo_data + newsize, 0, delta);
|
|
if (delta <= CONFIG_FS_TMPFS_FILE_FREEGUARD)
|
|
{
|
|
/* Hasn't shrunk enough.. Return doing nothing for now */
|
|
|
|
tfo->tfo_size = newsize;
|
|
return OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Added some additional amount to the new size to account frequent
|
|
* reallocations.
|
|
*/
|
|
|
|
allocsize = newsize + CONFIG_FS_TMPFS_FILE_ALLOCGUARD;
|
|
if (allocsize < newsize)
|
|
{
|
|
/* There must have been an integer overflow */
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Realloc the file object */
|
|
|
|
newdata = fs_heap_realloc(tfo->tfo_data, allocsize);
|
|
if (newdata == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Return the new address of the reallocated file object */
|
|
|
|
tfo->tfo_alloc = allocsize;
|
|
tfo->tfo_size = newsize;
|
|
tfo->tfo_data = newdata;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_release_lockedobject
|
|
****************************************************************************/
|
|
|
|
static void tmpfs_release_lockedobject(FAR struct tmpfs_object_s *to)
|
|
{
|
|
DEBUGASSERT(to && to->to_refs > 0);
|
|
|
|
/* Is this a file object? */
|
|
|
|
if (to->to_type == TMPFS_REGULAR)
|
|
{
|
|
tmpfs_release_lockedfile((FAR struct tmpfs_file_s *)to);
|
|
}
|
|
else
|
|
{
|
|
to->to_refs--;
|
|
tmpfs_unlock_object(to);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_release_lockedfile
|
|
****************************************************************************/
|
|
|
|
static void tmpfs_release_lockedfile(FAR struct tmpfs_file_s *tfo)
|
|
{
|
|
DEBUGASSERT(tfo && tfo->tfo_refs > 0);
|
|
|
|
/* If there are no longer any references to the file and the file has been
|
|
* unlinked from its parent directory, then free the file object now.
|
|
*/
|
|
|
|
if (tfo->tfo_refs == 1 && (tfo->tfo_flags & TFO_FLAG_UNLINKED) != 0)
|
|
{
|
|
tmpfs_unlock_file(tfo);
|
|
nxrmutex_destroy(&tfo->tfo_lock);
|
|
fs_heap_free(tfo->tfo_data);
|
|
fs_heap_free(tfo);
|
|
}
|
|
|
|
/* Otherwise, just decrement the reference count on the file object */
|
|
|
|
else
|
|
{
|
|
tfo->tfo_refs--;
|
|
tmpfs_unlock_file(tfo);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_release_file
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_release_file(FAR struct tmpfs_file_s *tfo)
|
|
{
|
|
int ret;
|
|
|
|
DEBUGASSERT(tfo);
|
|
|
|
/* Get exclusive access to the file */
|
|
|
|
ret = tmpfs_lock_file(tfo);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
tmpfs_release_lockedfile(tfo);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_find_dirent
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_find_dirent(FAR struct tmpfs_directory_s *tdo,
|
|
FAR const char *name, size_t len)
|
|
{
|
|
int i;
|
|
|
|
if (len == 0)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
else if (name[len - 1] == '/')
|
|
{
|
|
/* Ignore the tail '/' */
|
|
|
|
if (--len == 0)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* Search the list of directory entries for a match */
|
|
|
|
for (i = 0;
|
|
i < tdo->tdo_nentries &&
|
|
(strncmp(tdo->tdo_entry[i].tde_name, name, len) != 0 ||
|
|
tdo->tdo_entry[i].tde_name[len] != 0);
|
|
i++);
|
|
|
|
/* Return what we found, if anything */
|
|
|
|
return i < tdo->tdo_nentries ? i : -ENOENT;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_remove_dirent
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_remove_dirent(FAR struct tmpfs_directory_s *tdo,
|
|
FAR const char *name)
|
|
{
|
|
int index;
|
|
int last;
|
|
|
|
/* Search the list of directory entries for a match */
|
|
|
|
index = tmpfs_find_dirent(tdo, name, strlen(name));
|
|
if (index < 0)
|
|
{
|
|
return index;
|
|
}
|
|
|
|
/* Free the object name */
|
|
|
|
if (tdo->tdo_entry[index].tde_name != NULL)
|
|
{
|
|
lib_free(tdo->tdo_entry[index].tde_name);
|
|
}
|
|
|
|
/* Remove by replacing this entry with the final directory entry */
|
|
|
|
last = tdo->tdo_nentries - 1;
|
|
if (index != last)
|
|
{
|
|
tdo->tdo_entry[index] = tdo->tdo_entry[last];
|
|
}
|
|
|
|
/* And decrement the count of directory entries */
|
|
|
|
tdo->tdo_nentries = last;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_add_dirent
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_add_dirent(FAR struct tmpfs_directory_s *tdo,
|
|
FAR struct tmpfs_object_s *to,
|
|
FAR const char *name)
|
|
{
|
|
FAR struct tmpfs_dirent_s *tde;
|
|
FAR char *newname;
|
|
unsigned int nentries;
|
|
size_t namelen;
|
|
int index;
|
|
|
|
/* Copy the name string so that it will persist as long as the
|
|
* directory entry.
|
|
*/
|
|
|
|
namelen = strlen(name);
|
|
if (namelen == 0)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
else if (name[namelen - 1] == '/')
|
|
{
|
|
/* Don't copy the tail '/' */
|
|
|
|
if (--namelen == 0)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
newname = strndup(name, namelen);
|
|
if (newname == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Get the new number of entries */
|
|
|
|
nentries = tdo->tdo_nentries + 1;
|
|
|
|
/* Reallocate the directory object (if necessary) */
|
|
|
|
index = tmpfs_realloc_directory(tdo, nentries);
|
|
if (index < 0)
|
|
{
|
|
fs_heap_free(newname);
|
|
return index;
|
|
}
|
|
|
|
/* Save the new object info in the new directory entry */
|
|
|
|
to->to_parent = tdo;
|
|
tde = &tdo->tdo_entry[index];
|
|
tde->tde_object = to;
|
|
tde->tde_name = newname;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_alloc_file
|
|
****************************************************************************/
|
|
|
|
static FAR struct tmpfs_file_s *
|
|
tmpfs_alloc_file(FAR struct tmpfs_directory_s *parent)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo;
|
|
|
|
/* Create a new zero length file object */
|
|
|
|
tfo = fs_heap_zalloc(sizeof(*tfo));
|
|
if (tfo == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialize the new file object. NOTE that the initial state is
|
|
* locked with one reference count.
|
|
*/
|
|
|
|
tfo->tfo_alloc = 0;
|
|
tfo->tfo_type = TMPFS_REGULAR;
|
|
tfo->tfo_refs = 1;
|
|
tfo->tfo_parent = parent;
|
|
tfo->tfo_flags = 0;
|
|
tfo->tfo_size = 0;
|
|
tfo->tfo_data = NULL;
|
|
|
|
nxrmutex_init(&tfo->tfo_lock);
|
|
tmpfs_lock_file(tfo);
|
|
|
|
return tfo;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_create_file
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_create_file(FAR struct tmpfs_s *fs,
|
|
FAR const char *relpath,
|
|
FAR struct tmpfs_file_s **tfo)
|
|
{
|
|
FAR struct tmpfs_directory_s *parent;
|
|
FAR struct tmpfs_file_s *newtfo;
|
|
FAR const char *name;
|
|
int ret;
|
|
|
|
/* Separate the path into the file name and the path to the parent
|
|
* directory.
|
|
*/
|
|
|
|
name = strrchr(relpath, '/');
|
|
if (name == NULL)
|
|
{
|
|
/* No subdirectories... use the root directory */
|
|
|
|
name = relpath;
|
|
parent = (FAR struct tmpfs_directory_s *)fs->tfs_root.tde_object;
|
|
|
|
/* Lock the root directory to emulate the behavior of
|
|
* tmpfs_find_directory()
|
|
*/
|
|
|
|
ret = tmpfs_lock_directory(parent);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
parent->tdo_refs++;
|
|
}
|
|
else if (name[1] != '\0')
|
|
{
|
|
/* Locate the parent directory that should contain this name.
|
|
* On success, tmpfs_find_directory() will lock the parent
|
|
* directory and increment the reference count.
|
|
*/
|
|
|
|
ret = tmpfs_find_directory(fs, relpath, name - relpath, &parent, NULL);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Skip the '/' path separator */
|
|
|
|
name++;
|
|
}
|
|
else
|
|
{
|
|
return -EISDIR;
|
|
}
|
|
|
|
/* Verify that no object of this name already exists in the directory */
|
|
|
|
ret = tmpfs_find_dirent(parent, name, strlen(name));
|
|
if (ret != -ENOENT)
|
|
{
|
|
/* Something with this name already exists in the directory.
|
|
* OR perhaps some fatal error occurred.
|
|
*/
|
|
|
|
if (ret >= 0)
|
|
{
|
|
ret = -EEXIST;
|
|
}
|
|
|
|
goto errout_with_parent;
|
|
}
|
|
|
|
/* Allocate an empty file. The initial state of the file is locked with
|
|
* one reference count.
|
|
*/
|
|
|
|
newtfo = tmpfs_alloc_file(parent);
|
|
if (newtfo == NULL)
|
|
{
|
|
ret = -ENOMEM;
|
|
goto errout_with_parent;
|
|
}
|
|
|
|
/* Then add the new, empty file to the directory */
|
|
|
|
ret = tmpfs_add_dirent(parent, (FAR struct tmpfs_object_s *)newtfo, name);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_file;
|
|
}
|
|
|
|
/* Release the reference and lock on the parent directory */
|
|
|
|
parent->tdo_refs--;
|
|
tmpfs_unlock_directory(parent);
|
|
|
|
/* Return success */
|
|
|
|
*tfo = newtfo;
|
|
return OK;
|
|
|
|
/* Error exits */
|
|
|
|
errout_with_file:
|
|
nxrmutex_destroy(&newtfo->tfo_lock);
|
|
fs_heap_free(newtfo);
|
|
|
|
errout_with_parent:
|
|
parent->tdo_refs--;
|
|
tmpfs_unlock_directory(parent);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_alloc_directory
|
|
****************************************************************************/
|
|
|
|
static FAR struct tmpfs_directory_s *
|
|
tmpfs_alloc_directory(FAR struct tmpfs_directory_s *parent)
|
|
{
|
|
FAR struct tmpfs_directory_s *tdo;
|
|
|
|
/* Create a new zero length directory object */
|
|
|
|
tdo = fs_heap_zalloc(sizeof(*tdo));
|
|
if (tdo == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialize the new directory object */
|
|
|
|
tdo->tdo_alloc = 0;
|
|
tdo->tdo_type = TMPFS_DIRECTORY;
|
|
tdo->tdo_refs = 0;
|
|
tdo->tdo_parent = parent;
|
|
tdo->tdo_nentries = 0;
|
|
tdo->tdo_entry = NULL;
|
|
|
|
nxrmutex_init(&tdo->tdo_lock);
|
|
|
|
return tdo;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_create_directory
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_create_directory(FAR struct tmpfs_s *fs,
|
|
FAR const char *relpath,
|
|
FAR struct tmpfs_directory_s **tdo)
|
|
{
|
|
FAR struct tmpfs_directory_s *parent;
|
|
FAR struct tmpfs_directory_s *newtdo;
|
|
FAR const char *name;
|
|
int ret;
|
|
|
|
/* Separate the path into the file name and the path to the parent
|
|
* directory.
|
|
*/
|
|
|
|
name = strrchr(relpath, '/');
|
|
if (name && name[1] == '\0')
|
|
{
|
|
/* Ignore the tail '/' */
|
|
|
|
name = memrchr(relpath, '/', name - relpath);
|
|
}
|
|
|
|
if (name == NULL)
|
|
{
|
|
/* No subdirectories... use the root directory */
|
|
|
|
name = relpath;
|
|
parent = (FAR struct tmpfs_directory_s *)fs->tfs_root.tde_object;
|
|
|
|
ret = tmpfs_lock_directory(parent);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
parent->tdo_refs++;
|
|
}
|
|
else
|
|
{
|
|
/* Locate the parent directory that should contain this name.
|
|
* On success, tmpfs_find_directory() will lockthe parent
|
|
* directory and increment the reference count.
|
|
*/
|
|
|
|
ret = tmpfs_find_directory(fs, relpath, name - relpath, &parent, NULL);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Skip the '/' path separator */
|
|
|
|
name++;
|
|
}
|
|
|
|
/* Verify that no object of this name already exists in the directory */
|
|
|
|
ret = tmpfs_find_dirent(parent, name, strlen(name));
|
|
if (ret != -ENOENT)
|
|
{
|
|
/* Something with this name already exists in the directory.
|
|
* OR perhaps some fatal error occurred.
|
|
*/
|
|
|
|
if (ret >= 0)
|
|
{
|
|
ret = -EEXIST;
|
|
}
|
|
|
|
goto errout_with_parent;
|
|
}
|
|
|
|
/* Allocate an empty directory object. NOTE that there is no reference on
|
|
* the new directory and the object is not locked.
|
|
*/
|
|
|
|
newtdo = tmpfs_alloc_directory(parent);
|
|
if (newtdo == NULL)
|
|
{
|
|
ret = -ENOMEM;
|
|
goto errout_with_parent;
|
|
}
|
|
|
|
/* Then add the new, empty file to the directory */
|
|
|
|
ret = tmpfs_add_dirent(parent, (FAR struct tmpfs_object_s *)newtdo, name);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_directory;
|
|
}
|
|
|
|
/* Free the copy of the relpath, release our reference to the parent
|
|
* directory, and return success
|
|
*/
|
|
|
|
parent->tdo_refs--;
|
|
tmpfs_unlock_directory(parent);
|
|
|
|
/* Return the (unlocked, unreferenced) directory object to the caller */
|
|
|
|
if (tdo != NULL)
|
|
{
|
|
*tdo = newtdo;
|
|
}
|
|
|
|
return OK;
|
|
|
|
/* Error exits */
|
|
|
|
errout_with_directory:
|
|
nxrmutex_destroy(&newtdo->tdo_lock);
|
|
fs_heap_free(newtdo);
|
|
|
|
errout_with_parent:
|
|
parent->tdo_refs--;
|
|
tmpfs_unlock_directory(parent);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_find_object
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_find_object(FAR struct tmpfs_s *fs,
|
|
FAR const char *relpath, size_t len,
|
|
FAR struct tmpfs_object_s **object,
|
|
FAR struct tmpfs_directory_s **parent)
|
|
{
|
|
FAR struct tmpfs_object_s *to = NULL;
|
|
FAR struct tmpfs_directory_s *tdo = NULL;
|
|
FAR struct tmpfs_directory_s *next_tdo;
|
|
FAR const char *segment;
|
|
FAR const char *next_segment;
|
|
int index;
|
|
int ret;
|
|
|
|
/* Traverse the file system for any object with the matching name */
|
|
|
|
to = fs->tfs_root.tde_object;
|
|
next_tdo = (FAR struct tmpfs_directory_s *)fs->tfs_root.tde_object;
|
|
|
|
for (segment = relpath; len != 0; segment = next_segment + 1)
|
|
{
|
|
/* Get the next segment after the one we are currently working on.
|
|
* This will be NULL is we are working on the final segment of the
|
|
* relpath.
|
|
*/
|
|
|
|
next_segment = memchr(segment, '/', len);
|
|
if (next_segment)
|
|
{
|
|
len -= next_segment + 1 - segment;
|
|
}
|
|
else
|
|
{
|
|
next_segment = segment + len;
|
|
len = 0;
|
|
}
|
|
|
|
/* Search the next directory. */
|
|
|
|
tdo = next_tdo;
|
|
|
|
/* Find the TMPFS object with the next segment name in the current
|
|
* directory.
|
|
*/
|
|
|
|
index = tmpfs_find_dirent(tdo, segment, next_segment - segment);
|
|
if (index < 0)
|
|
{
|
|
/* No object with this name exists in the directory. */
|
|
|
|
return index;
|
|
}
|
|
|
|
to = tdo->tdo_entry[index].tde_object;
|
|
|
|
/* Is this object another directory? */
|
|
|
|
if (to->to_type != TMPFS_DIRECTORY)
|
|
{
|
|
/* No. Was this the final segment in the path? */
|
|
|
|
if (len == 0 && *next_segment != '/')
|
|
{
|
|
/* Then we can break out of the loop now */
|
|
|
|
break;
|
|
}
|
|
|
|
/* No, this was not the final segment of the relpath.
|
|
* We cannot continue the search if any of the intermediate
|
|
* segments do no correspond to directories.
|
|
*/
|
|
|
|
return -ENOTDIR;
|
|
}
|
|
|
|
/* Search this directory for the next segment. If we
|
|
* exit the loop, tdo will still refer to the parent
|
|
* directory of to.
|
|
*/
|
|
|
|
next_tdo = (FAR struct tmpfs_directory_s *)to;
|
|
}
|
|
|
|
/* When we exit this loop (successfully), to will point to the TMPFS
|
|
* object associated with the terminal segment of the relpath.
|
|
* Increment the reference count on the located object.
|
|
*/
|
|
|
|
/* Return what we found */
|
|
|
|
if (parent)
|
|
{
|
|
if (tdo != NULL)
|
|
{
|
|
/* Get exclusive access to the parent and increment the reference
|
|
* count on the object.
|
|
*/
|
|
|
|
ret = tmpfs_lock_directory(tdo);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
tdo->tdo_refs++;
|
|
}
|
|
|
|
*parent = tdo;
|
|
}
|
|
|
|
if (object)
|
|
{
|
|
if (to != NULL)
|
|
{
|
|
/* Get exclusive access to the object and increment the reference
|
|
* count on the object.
|
|
*/
|
|
|
|
ret = tmpfs_lock_object(to);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
to->to_refs++;
|
|
}
|
|
|
|
*object = to;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_find_file
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_find_file(FAR struct tmpfs_s *fs,
|
|
FAR const char *relpath,
|
|
FAR struct tmpfs_file_s **tfo,
|
|
FAR struct tmpfs_directory_s **parent)
|
|
{
|
|
FAR struct tmpfs_object_s *to;
|
|
size_t len;
|
|
int ret;
|
|
|
|
len = strlen(relpath);
|
|
if (len == 0)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
else if (relpath[len - 1] == '/')
|
|
{
|
|
return -EISDIR;
|
|
}
|
|
|
|
/* Find the object at this path. If successful, tmpfs_find_object() will
|
|
* lock both the object and the parent directory and will increment the
|
|
* reference count on both.
|
|
*/
|
|
|
|
ret = tmpfs_find_object(fs, relpath, len, &to, parent);
|
|
if (ret >= 0)
|
|
{
|
|
/* We found it... but is it a regular file? */
|
|
|
|
if (to->to_type != TMPFS_REGULAR)
|
|
{
|
|
/* No... unlock the object and its parent and return an error */
|
|
|
|
tmpfs_release_lockedobject(to);
|
|
|
|
if (parent)
|
|
{
|
|
FAR struct tmpfs_directory_s *tdo = *parent;
|
|
|
|
tdo->tdo_refs--;
|
|
tmpfs_unlock_directory(tdo);
|
|
}
|
|
|
|
ret = -EISDIR;
|
|
}
|
|
|
|
/* Return the verified file object */
|
|
|
|
*tfo = (FAR struct tmpfs_file_s *)to;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_find_directory
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_find_directory(FAR struct tmpfs_s *fs,
|
|
FAR const char *relpath, size_t len,
|
|
FAR struct tmpfs_directory_s **tdo,
|
|
FAR struct tmpfs_directory_s **parent)
|
|
{
|
|
FAR struct tmpfs_object_s *to;
|
|
int ret;
|
|
|
|
/* Find the object at this path */
|
|
|
|
ret = tmpfs_find_object(fs, relpath, len, &to, parent);
|
|
if (ret >= 0)
|
|
{
|
|
/* We found it... but is it a regular file? */
|
|
|
|
if (to->to_type != TMPFS_DIRECTORY)
|
|
{
|
|
/* No... unlock the object and its parent and return an error */
|
|
|
|
tmpfs_release_lockedobject(to);
|
|
|
|
if (parent)
|
|
{
|
|
FAR struct tmpfs_directory_s *tmptdo = *parent;
|
|
|
|
tmptdo->tdo_refs--;
|
|
tmpfs_unlock_directory(tmptdo);
|
|
}
|
|
|
|
ret = -ENOTDIR;
|
|
}
|
|
|
|
/* Return the verified file object */
|
|
|
|
*tdo = (FAR struct tmpfs_directory_s *)to;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_getpath
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_getpath(FAR struct tmpfs_object_s *to,
|
|
FAR char *path, size_t len)
|
|
{
|
|
FAR struct tmpfs_dirent_s *tde = NULL;
|
|
FAR struct tmpfs_directory_s *tdo;
|
|
uint16_t i;
|
|
|
|
if (to->to_parent != NULL)
|
|
{
|
|
int ret = tmpfs_getpath((FAR struct tmpfs_object_s *)to->to_parent,
|
|
path, len);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
tdo = to->to_parent;
|
|
|
|
for (i = 0; i < tdo->tdo_nentries; i++)
|
|
{
|
|
tde = &tdo->tdo_entry[i];
|
|
if (to == tde->tde_object)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == tdo->tdo_nentries)
|
|
{
|
|
return -ENOENT;
|
|
}
|
|
|
|
strlcat(path, tde->tde_name, len);
|
|
if (to->to_type == TMPFS_DIRECTORY)
|
|
{
|
|
strlcat(path, "/", len);
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_statfs_callout
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_statfs_callout(FAR struct tmpfs_directory_s *tdo,
|
|
unsigned int index, FAR void *arg)
|
|
{
|
|
FAR struct tmpfs_object_s *to;
|
|
FAR struct tmpfs_statfs_s *tmpbuf;
|
|
|
|
DEBUGASSERT(tdo != NULL && arg != NULL && index < tdo->tdo_nentries);
|
|
|
|
to = tdo->tdo_entry[index].tde_object;
|
|
tmpbuf = (FAR struct tmpfs_statfs_s *)arg;
|
|
|
|
DEBUGASSERT(to != NULL);
|
|
|
|
/* Accumulate statistics. Save the total memory allocated
|
|
* for this object.
|
|
*/
|
|
|
|
tmpbuf->tsf_alloc += to->to_alloc +
|
|
strlen(tdo->tdo_entry[index].tde_name) + 1;
|
|
|
|
/* Is this directory entry a file object? */
|
|
|
|
if (to->to_type == TMPFS_REGULAR)
|
|
{
|
|
FAR struct tmpfs_file_s *tmptfo;
|
|
|
|
/* It is a file object. Increment the number of files and update the
|
|
* amount of memory in use.
|
|
*/
|
|
|
|
tmptfo = (FAR struct tmpfs_file_s *)to;
|
|
tmpbuf->tsf_alloc += sizeof(struct tmpfs_file_s);
|
|
tmpbuf->tsf_avail += to->to_alloc - tmptfo->tfo_size;
|
|
tmpbuf->tsf_files++;
|
|
}
|
|
else /* if (to->to_type == TMPFS_DIRECTORY) */
|
|
{
|
|
FAR struct tmpfs_directory_s *tmptdo;
|
|
size_t avail;
|
|
|
|
/* It is a directory object. Update the amount of memory in use
|
|
* for the directory and estimate the number of free directory nodes.
|
|
*/
|
|
|
|
tmptdo = (FAR struct tmpfs_directory_s *)to;
|
|
avail = tmptdo->tdo_alloc -
|
|
SIZEOF_TMPFS_DIRECTORY(tmptdo->tdo_nentries);
|
|
|
|
tmpbuf->tsf_alloc += sizeof(struct tmpfs_directory_s);
|
|
tmpbuf->tsf_avail += avail;
|
|
tmpbuf->tsf_ffree += avail / sizeof(struct tmpfs_dirent_s);
|
|
}
|
|
|
|
return TMPFS_CONTINUE;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_free_callout
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_free_callout(FAR struct tmpfs_directory_s *tdo,
|
|
unsigned int index, FAR void *arg)
|
|
{
|
|
FAR struct tmpfs_dirent_s *tde;
|
|
FAR struct tmpfs_object_s *to;
|
|
FAR struct tmpfs_file_s *tfo;
|
|
unsigned int last;
|
|
|
|
/* Free the object name */
|
|
|
|
if (tdo->tdo_entry[index].tde_name != NULL)
|
|
{
|
|
lib_free(tdo->tdo_entry[index].tde_name);
|
|
}
|
|
|
|
/* Remove by replacing this entry with the final directory entry */
|
|
|
|
tde = &tdo->tdo_entry[index];
|
|
to = tde->tde_object;
|
|
last = tdo->tdo_nentries - 1;
|
|
|
|
if (index != last)
|
|
{
|
|
/* Move the directory entry */
|
|
|
|
*tde = tdo->tdo_entry[last];
|
|
}
|
|
|
|
/* And decrement the count of directory entries */
|
|
|
|
tdo->tdo_nentries = last;
|
|
|
|
/* Is this directory entry a file object? */
|
|
|
|
if (to->to_type == TMPFS_REGULAR)
|
|
{
|
|
tfo = (FAR struct tmpfs_file_s *)to;
|
|
|
|
/* Are there references to the file? */
|
|
|
|
if (tfo->tfo_refs > 0)
|
|
{
|
|
/* Yes.. We cannot delete the file now. Just mark it as unlinked. */
|
|
|
|
tfo->tfo_flags |= TFO_FLAG_UNLINKED;
|
|
return TMPFS_UNLINKED;
|
|
}
|
|
|
|
fs_heap_free(tfo->tfo_data);
|
|
}
|
|
else /* if (to->to_type == TMPFS_DIRECTORY) */
|
|
{
|
|
tdo = (FAR struct tmpfs_directory_s *)to;
|
|
|
|
fs_heap_free(tdo->tdo_entry);
|
|
}
|
|
|
|
/* Free the object now */
|
|
|
|
nxrmutex_destroy(&to->to_lock);
|
|
fs_heap_free(to);
|
|
return TMPFS_DELETED;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_foreach
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_foreach(FAR struct tmpfs_directory_s *tdo,
|
|
tmpfs_foreach_t callout, FAR void *arg)
|
|
{
|
|
FAR struct tmpfs_object_s *to;
|
|
unsigned int index;
|
|
int ret;
|
|
|
|
/* Visit each directory entry */
|
|
|
|
for (index = 0; index < tdo->tdo_nentries; )
|
|
{
|
|
/* Lock the object and take a reference */
|
|
|
|
to = tdo->tdo_entry[index].tde_object;
|
|
ret = tmpfs_lock_object(to);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
to->to_refs++;
|
|
|
|
/* Is the next entry a directory? */
|
|
|
|
if (to->to_type == TMPFS_DIRECTORY)
|
|
{
|
|
FAR struct tmpfs_directory_s *next =
|
|
(FAR struct tmpfs_directory_s *)to;
|
|
|
|
/* Yes.. traverse its children first in the case the final
|
|
* action will be to delete the directory.
|
|
*/
|
|
|
|
ret = tmpfs_foreach(next, callout, arg);
|
|
if (ret < 0)
|
|
{
|
|
return -ECANCELED;
|
|
}
|
|
}
|
|
|
|
/* Perform the callout */
|
|
|
|
ret = callout(tdo, index, arg);
|
|
switch (ret)
|
|
{
|
|
case TMPFS_CONTINUE: /* Continue enumeration */
|
|
|
|
/* Release the object and index to the next entry */
|
|
|
|
tmpfs_release_lockedobject(to);
|
|
index++;
|
|
break;
|
|
|
|
case TMPFS_HALT: /* Stop enumeration */
|
|
|
|
/* Release the object and cancel the traversal */
|
|
|
|
tmpfs_release_lockedobject(to);
|
|
return -ECANCELED;
|
|
|
|
case TMPFS_UNLINKED: /* Only the directory entry was deleted */
|
|
|
|
/* Release the object and continue with the same index */
|
|
|
|
tmpfs_release_lockedobject(to);
|
|
|
|
case TMPFS_DELETED: /* Object and directory entry deleted */
|
|
break; /* Continue with the same index */
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_open
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct tmpfs_s *fs;
|
|
FAR struct tmpfs_file_s *tfo;
|
|
off_t offset;
|
|
int ret;
|
|
|
|
finfo("filep: %p\n", filep);
|
|
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 && fs->tfs_root.tde_object != NULL);
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
ret = tmpfs_lock(fs);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Skip over any leading directory separators (shouldn't be any) */
|
|
|
|
for (; *relpath == '/'; relpath++);
|
|
|
|
/* Find the file object associated with this relative path.
|
|
* If successful, this action will lock both the parent directory and
|
|
* the file object, adding one to the reference count of both.
|
|
* In the event that -ENOENT, there will still be a reference and
|
|
* lock on the returned directory.
|
|
*/
|
|
|
|
ret = tmpfs_find_file(fs, relpath, &tfo, NULL);
|
|
if (ret >= 0)
|
|
{
|
|
/* The file exists. We hold the lock and one reference count
|
|
* on the file object.
|
|
*
|
|
* 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_filelock;
|
|
}
|
|
|
|
/* Check if the caller has sufficient privileges to open the file.
|
|
* REVISIT: No file protection implemented
|
|
*/
|
|
|
|
/* 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 (if it is not already
|
|
* zero length)
|
|
*/
|
|
|
|
if (tfo->tfo_size > 0)
|
|
{
|
|
ret = tmpfs_realloc_file(tfo, 0);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_filelock;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ENOENT would be returned by tmpfs_find_file() 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_fslock;
|
|
}
|
|
|
|
/* Yes.. create the file object. There will be a reference and a lock
|
|
* on the new file object.
|
|
*/
|
|
|
|
ret = tmpfs_create_file(fs, relpath, &tfo);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_fslock;
|
|
}
|
|
}
|
|
|
|
/* Some other error occurred */
|
|
|
|
else
|
|
{
|
|
goto errout_with_fslock;
|
|
}
|
|
|
|
/* Save the struct tmpfs_file_s instance as the file private data */
|
|
|
|
filep->f_priv = tfo;
|
|
|
|
/* In write/append mode, we need to set the file pointer to the end of the
|
|
* file.
|
|
*/
|
|
|
|
offset = 0;
|
|
if ((oflags & (O_APPEND | O_WRONLY)) == (O_APPEND | O_WRONLY))
|
|
{
|
|
offset = tfo->tfo_size;
|
|
}
|
|
|
|
filep->f_pos = offset;
|
|
|
|
/* Unlock the file file object, but retain the reference count */
|
|
|
|
tmpfs_unlock_file(tfo);
|
|
tmpfs_unlock(fs);
|
|
return OK;
|
|
|
|
/* Error exits */
|
|
|
|
errout_with_filelock:
|
|
tmpfs_release_lockedfile(tfo);
|
|
|
|
errout_with_fslock:
|
|
tmpfs_unlock(fs);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_close
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_close(FAR struct file *filep)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo;
|
|
int ret;
|
|
|
|
finfo("filep: %p\n", filep);
|
|
DEBUGASSERT(filep->f_priv != NULL);
|
|
|
|
tfo = filep->f_priv;
|
|
|
|
ret = tmpfs_release_file(tfo);
|
|
if (ret >= 0)
|
|
{
|
|
filep->f_priv = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t tmpfs_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo;
|
|
ssize_t nread;
|
|
off_t startpos;
|
|
off_t endpos;
|
|
int ret;
|
|
|
|
finfo("filep: %p buffer: %p buflen: %lu\n",
|
|
filep, buffer, (unsigned long)buflen);
|
|
DEBUGASSERT(filep->f_priv != NULL);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
tfo = filep->f_priv;
|
|
|
|
/* Directly return when the f_pos bigger then tfo_size */
|
|
|
|
if (filep->f_pos > tfo->tfo_size)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Get exclusive access to the file */
|
|
|
|
ret = tmpfs_lock_file(tfo);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Handle attempts to read beyond the end of the file. */
|
|
|
|
startpos = filep->f_pos;
|
|
nread = buflen;
|
|
endpos = startpos + buflen;
|
|
|
|
if (endpos > tfo->tfo_size)
|
|
{
|
|
endpos = tfo->tfo_size;
|
|
nread = endpos - startpos;
|
|
}
|
|
|
|
/* Copy data from the memory object to the user buffer */
|
|
|
|
if (tfo->tfo_data != NULL)
|
|
{
|
|
memcpy(buffer, &tfo->tfo_data[startpos], nread);
|
|
filep->f_pos += nread;
|
|
}
|
|
else
|
|
{
|
|
DEBUGASSERT(tfo->tfo_size == 0 && nread == 0);
|
|
}
|
|
|
|
/* Release the lock on the file */
|
|
|
|
tmpfs_unlock_file(tfo);
|
|
return nread;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t tmpfs_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo;
|
|
ssize_t nwritten;
|
|
off_t startpos;
|
|
off_t endpos;
|
|
int ret;
|
|
|
|
finfo("filep: %p buffer: %p buflen: %lu\n",
|
|
filep, buffer, (unsigned long)buflen);
|
|
DEBUGASSERT(filep->f_priv != NULL);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
tfo = filep->f_priv;
|
|
|
|
/* Get exclusive access to the file */
|
|
|
|
ret = tmpfs_lock_file(tfo);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Handle attempts to write beyond the end of the file */
|
|
|
|
if ((filep->f_oflags & O_APPEND) != 0)
|
|
{
|
|
startpos = tfo->tfo_size;
|
|
}
|
|
else
|
|
{
|
|
startpos = filep->f_pos;
|
|
}
|
|
|
|
nwritten = buflen;
|
|
endpos = startpos + buflen;
|
|
|
|
if (endpos > tfo->tfo_size)
|
|
{
|
|
/* Reallocate the file to handle the write past the end of the file. */
|
|
|
|
ret = tmpfs_realloc_file(tfo, (size_t)endpos);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
}
|
|
|
|
/* Copy data from the memory object to the user buffer */
|
|
|
|
if (tfo->tfo_data != NULL)
|
|
{
|
|
memcpy(&tfo->tfo_data[startpos], buffer, nwritten);
|
|
}
|
|
else
|
|
{
|
|
DEBUGASSERT(tfo->tfo_size == 0 && nwritten == 0);
|
|
}
|
|
|
|
filep->f_pos = endpos;
|
|
|
|
/* Release the lock on the file */
|
|
|
|
tmpfs_unlock_file(tfo);
|
|
return nwritten;
|
|
|
|
errout_with_lock:
|
|
tmpfs_unlock_file(tfo);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_seek
|
|
****************************************************************************/
|
|
|
|
static off_t tmpfs_seek(FAR struct file *filep, off_t offset, int whence)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo;
|
|
off_t position;
|
|
|
|
finfo("filep: %p\n", filep);
|
|
DEBUGASSERT(filep->f_priv != NULL);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
tfo = filep->f_priv;
|
|
|
|
/* 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 + tfo->tfo_size;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Save the new file position */
|
|
|
|
filep->f_pos = position;
|
|
return position;
|
|
}
|
|
|
|
static int tmpfs_unmap(FAR struct task_group_s *group,
|
|
FAR struct mm_map_entry_s *entry,
|
|
FAR void *start, size_t length)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo = entry->priv.p;
|
|
off_t offset;
|
|
int ret;
|
|
|
|
offset = (uintptr_t)start - (uintptr_t)entry->vaddr;
|
|
if (offset + length < entry->length)
|
|
{
|
|
ferr("ERROR: Cannot umap without unmapping to the end\n");
|
|
return -ENOSYS;
|
|
}
|
|
|
|
/* Okay.. the region is being unmapped to the end. Make sure the length
|
|
* indicates that.
|
|
*/
|
|
|
|
length = entry->length - offset;
|
|
|
|
/* Are we unmapping the entire region (offset == 0)? */
|
|
|
|
if (length >= entry->length)
|
|
{
|
|
/* Then remove the mapping from the list */
|
|
|
|
ret = mm_map_remove(get_group_mm(group), entry);
|
|
if (ret >= 0)
|
|
{
|
|
ret = tmpfs_release_file(tfo);
|
|
}
|
|
}
|
|
|
|
/* No.. We have been asked to "unmap' only a portion of the memory
|
|
* (offset > 0).
|
|
*/
|
|
|
|
else
|
|
{
|
|
entry->length = offset;
|
|
tmpfs_lock_file(tfo);
|
|
ret = tmpfs_realloc_file(tfo, offset);
|
|
tmpfs_unlock_file(tfo);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tmpfs_mmap(FAR struct file *filep, FAR struct mm_map_entry_s *map)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo;
|
|
int ret = -EINVAL;
|
|
|
|
DEBUGASSERT(filep->f_priv != NULL);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
tfo = filep->f_priv;
|
|
|
|
DEBUGASSERT(tfo != NULL);
|
|
|
|
if (map->offset >= 0 && map->offset < tfo->tfo_size &&
|
|
map->length && map->offset + map->length <= tfo->tfo_size)
|
|
{
|
|
map->vaddr = tfo->tfo_data + map->offset;
|
|
map->priv.p = tfo;
|
|
map->munmap = tmpfs_unmap;
|
|
ret = mm_map_add(get_current_mm(), map);
|
|
|
|
if (ret >= 0)
|
|
{
|
|
tmpfs_lock_file(tfo);
|
|
tfo->tfo_refs++;
|
|
tmpfs_unlock_file(tfo);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_ioctl
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo;
|
|
int ret = -ENOTTY;
|
|
|
|
/* Sanity checks */
|
|
|
|
DEBUGASSERT(filep->f_priv != NULL);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
tfo = filep->f_priv;
|
|
|
|
/* Only one ioctl command is supported */
|
|
|
|
if (cmd == FIOC_FILEPATH)
|
|
{
|
|
FAR char *ptr = (FAR char *)((uintptr_t)arg);
|
|
ret = inode_getpath(filep->f_inode, ptr, PATH_MAX);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = tmpfs_getpath((FAR struct tmpfs_object_s *)tfo, ptr, PATH_MAX);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_sync
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_sync(FAR struct file *filep)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_dup
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_dup(FAR const struct file *oldp, FAR struct file *newp)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo;
|
|
int ret;
|
|
|
|
finfo("Dup %p->%p\n", oldp, newp);
|
|
DEBUGASSERT(oldp->f_priv != NULL && oldp->f_inode != NULL &&
|
|
newp->f_priv == NULL && newp->f_inode != NULL);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
tfo = oldp->f_priv;
|
|
DEBUGASSERT(tfo != NULL);
|
|
|
|
/* Increment the reference count (atomically) */
|
|
|
|
ret = tmpfs_lock_file(tfo);
|
|
if (ret >= 0)
|
|
{
|
|
tfo->tfo_refs++;
|
|
tmpfs_unlock_file(tfo);
|
|
|
|
/* Save a copy of the file object as the dup'ed file. This
|
|
* simple implementation does not many any per-open data
|
|
* structures so there is not really much to the dup operation.
|
|
*/
|
|
|
|
newp->f_priv = tfo;
|
|
ret = OK;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_fstat
|
|
*
|
|
* Description:
|
|
* Obtain information about an open file associated with the file
|
|
* descriptor 'fd', and will write it to the area pointed to by 'buf'.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_fstat(FAR const struct file *filep, FAR struct stat *buf)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo;
|
|
int ret;
|
|
|
|
finfo("Fstat %p\n", buf);
|
|
DEBUGASSERT(buf != NULL);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
DEBUGASSERT(filep->f_priv != NULL);
|
|
tfo = filep->f_priv;
|
|
|
|
/* Get exclusive access to the file */
|
|
|
|
ret = tmpfs_lock_file(tfo);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Return information about the file in the stat buffer. */
|
|
|
|
tmpfs_stat_common((FAR struct tmpfs_object_s *)tfo, buf);
|
|
|
|
/* Release the lock on the file and return success. */
|
|
|
|
tmpfs_unlock_file(tfo);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_truncate
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_truncate(FAR struct file *filep, off_t length)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo;
|
|
size_t oldsize;
|
|
int ret;
|
|
|
|
finfo("filep: %p length: %ld\n", filep, (long)length);
|
|
DEBUGASSERT(length >= 0);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
tfo = filep->f_priv;
|
|
|
|
/* Get exclusive access to the file */
|
|
|
|
ret = tmpfs_lock_file(tfo);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Get the old size of the file. Do nothing if the file size is not
|
|
* changing.
|
|
*/
|
|
|
|
oldsize = tfo->tfo_size;
|
|
if (oldsize != length)
|
|
{
|
|
/* The size is changing.. up or down. Reallocate the file memory. */
|
|
|
|
ret = tmpfs_realloc_file(tfo, (size_t)length);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* If the size has increased, then we need to zero the newly added
|
|
* memory.
|
|
*/
|
|
|
|
if (length > oldsize)
|
|
{
|
|
memset(&tfo->tfo_data[oldsize], 0, length - oldsize);
|
|
}
|
|
|
|
ret = OK;
|
|
}
|
|
|
|
/* Release the lock on the file */
|
|
|
|
errout_with_lock:
|
|
tmpfs_unlock_file(tfo);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_opendir
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_opendir(FAR struct inode *mountpt, FAR const char *relpath,
|
|
FAR struct fs_dirent_s **dir)
|
|
{
|
|
FAR struct tmpfs_s *fs;
|
|
FAR struct tmpfs_dir_s *tdir;
|
|
FAR struct tmpfs_directory_s *tdo;
|
|
int ret;
|
|
|
|
finfo("mountpt: %p relpath: %s dir: %p\n",
|
|
mountpt, relpath, dir);
|
|
DEBUGASSERT(mountpt != NULL && relpath != NULL && dir != NULL);
|
|
|
|
/* Get the mountpoint private data from the inode structure */
|
|
|
|
fs = mountpt->i_private;
|
|
DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);
|
|
|
|
tdir = fs_heap_zalloc(sizeof(*tdir));
|
|
if (tdir == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
ret = tmpfs_lock(fs);
|
|
if (ret < 0)
|
|
{
|
|
fs_heap_free(tdir);
|
|
return ret;
|
|
}
|
|
|
|
/* Skip over any leading directory separators (shouldn't be any) */
|
|
|
|
for (; *relpath == '/'; relpath++);
|
|
|
|
/* Find the directory object associated with this relative path.
|
|
* If successful, this action will lock both the parent directory and
|
|
* the file object, adding one to the reference count of both.
|
|
* In the event that -ENOENT, there will still be a reference and
|
|
* lock on the returned directory.
|
|
*/
|
|
|
|
ret = tmpfs_find_directory(fs, relpath, strlen(relpath), &tdo, NULL);
|
|
if (ret >= 0)
|
|
{
|
|
tdir->tf_tdo = tdo;
|
|
tdir->tf_index = tdo->tdo_nentries;
|
|
|
|
tmpfs_unlock_directory(tdo);
|
|
}
|
|
|
|
/* Release the lock on the file system and return the result */
|
|
|
|
tmpfs_unlock(fs);
|
|
*dir = &tdir->tf_base;
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_closedir
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_closedir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir)
|
|
{
|
|
FAR struct tmpfs_directory_s *tdo;
|
|
|
|
finfo("mountpt: %p dir: %p\n", mountpt, dir);
|
|
DEBUGASSERT(mountpt != NULL && dir != NULL);
|
|
|
|
/* Get the directory structure from the dir argument */
|
|
|
|
tdo = ((FAR struct tmpfs_dir_s *)dir)->tf_tdo;
|
|
DEBUGASSERT(tdo != NULL);
|
|
|
|
/* Decrement the reference count on the directory object */
|
|
|
|
tmpfs_lock_directory(tdo);
|
|
tdo->tdo_refs--;
|
|
tmpfs_unlock_directory(tdo);
|
|
fs_heap_free(dir);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_readdir
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_readdir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir,
|
|
FAR struct dirent *entry)
|
|
{
|
|
FAR struct tmpfs_directory_s *tdo;
|
|
FAR struct tmpfs_dir_s *tdir;
|
|
unsigned int index;
|
|
int ret;
|
|
|
|
finfo("mountpt: %p dir: %p\n", mountpt, dir);
|
|
DEBUGASSERT(mountpt != NULL && dir != NULL);
|
|
|
|
/* Get the directory structure from the dir argument and lock it */
|
|
|
|
tdir = (FAR struct tmpfs_dir_s *)dir;
|
|
tdo = tdir->tf_tdo;
|
|
DEBUGASSERT(tdo != NULL);
|
|
|
|
tmpfs_lock_directory(tdo);
|
|
|
|
/* Have we reached the end of the directory? */
|
|
|
|
index = tdir->tf_index;
|
|
if (index-- == 0)
|
|
{
|
|
/* We signal the end of the directory by returning the special error:
|
|
* -ENOENT
|
|
*/
|
|
|
|
finfo("End of directory\n");
|
|
ret = -ENOENT;
|
|
}
|
|
else
|
|
{
|
|
FAR struct tmpfs_dirent_s *tde;
|
|
FAR struct tmpfs_object_s *to;
|
|
|
|
/* Does this entry refer to a file or a directory object? */
|
|
|
|
tde = &tdo->tdo_entry[index];
|
|
to = tde->tde_object;
|
|
DEBUGASSERT(to != NULL);
|
|
|
|
if (to->to_type == TMPFS_DIRECTORY)
|
|
{
|
|
/* A directory */
|
|
|
|
entry->d_type = DTYPE_DIRECTORY;
|
|
}
|
|
else /* to->to_type == TMPFS_REGULAR) */
|
|
{
|
|
/* A regular file */
|
|
|
|
entry->d_type = DTYPE_FILE;
|
|
}
|
|
|
|
/* Copy the entry name */
|
|
|
|
strlcpy(entry->d_name, tde->tde_name, sizeof(entry->d_name));
|
|
|
|
/* Save the index for next time */
|
|
|
|
tdir->tf_index = index;
|
|
ret = OK;
|
|
}
|
|
|
|
tmpfs_unlock_directory(tdo);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_rewinddir
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_rewinddir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir)
|
|
{
|
|
FAR struct tmpfs_directory_s *tdo;
|
|
FAR struct tmpfs_dir_s *tdir;
|
|
|
|
finfo("mountpt: %p dir: %p\n", mountpt, dir);
|
|
DEBUGASSERT(mountpt != NULL && dir != NULL);
|
|
|
|
/* Get the directory structure from the dir argument and lock it */
|
|
|
|
tdir = (FAR struct tmpfs_dir_s *)dir;
|
|
tdo = tdir->tf_tdo;
|
|
DEBUGASSERT(tdo != NULL);
|
|
|
|
/* Set the readdir index pass the end */
|
|
|
|
tdir->tf_index = tdo->tdo_nentries;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_bind
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_bind(FAR struct inode *blkdriver, FAR const void *data,
|
|
FAR void **handle)
|
|
{
|
|
FAR struct tmpfs_directory_s *tdo;
|
|
FAR struct tmpfs_s *fs;
|
|
|
|
finfo("blkdriver: %p data: %p handle: %p\n", blkdriver, data, handle);
|
|
DEBUGASSERT(blkdriver == NULL && handle != NULL);
|
|
|
|
/* Create an instance of the tmpfs file system */
|
|
|
|
fs = fs_heap_zalloc(sizeof(struct tmpfs_s));
|
|
if (fs == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Create a root file system. This is like a single directory entry in
|
|
* the file system structure.
|
|
*/
|
|
|
|
tdo = tmpfs_alloc_directory(NULL);
|
|
if (tdo == NULL)
|
|
{
|
|
fs_heap_free(fs);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
fs->tfs_root.tde_object = (FAR struct tmpfs_object_s *)tdo;
|
|
fs->tfs_root.tde_name = "";
|
|
|
|
/* Initialize the file system state */
|
|
|
|
nxrmutex_init(&fs->tfs_lock);
|
|
|
|
/* Return the new file system handle */
|
|
|
|
*handle = (FAR void *)fs;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_unbind
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_unbind(FAR void *handle, FAR struct inode **blkdriver,
|
|
unsigned int flags)
|
|
{
|
|
FAR struct tmpfs_s *fs = (FAR struct tmpfs_s *)handle;
|
|
FAR struct tmpfs_directory_s *tdo;
|
|
int ret;
|
|
|
|
finfo("handle: %p blkdriver: %p flags: %02x\n",
|
|
handle, blkdriver, flags);
|
|
DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);
|
|
|
|
/* Lock the file system */
|
|
|
|
ret = tmpfs_lock(fs);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Traverse all directory entries (recursively), freeing all resources. */
|
|
|
|
tdo = (FAR struct tmpfs_directory_s *)fs->tfs_root.tde_object;
|
|
ret = tmpfs_foreach(tdo, tmpfs_free_callout, NULL);
|
|
|
|
/* Now we can destroy the root file system and the file system itself. */
|
|
|
|
nxrmutex_destroy(&tdo->tdo_lock);
|
|
fs_heap_free(tdo->tdo_entry);
|
|
fs_heap_free(tdo);
|
|
|
|
nxrmutex_destroy(&fs->tfs_lock);
|
|
fs_heap_free(fs);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_statfs
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf)
|
|
{
|
|
FAR struct tmpfs_s *fs;
|
|
FAR struct tmpfs_directory_s *tdo;
|
|
struct tmpfs_statfs_s tmpbuf;
|
|
size_t avail;
|
|
off_t blkalloc;
|
|
off_t blkavail;
|
|
int ret;
|
|
|
|
finfo("mountpt: %p buf: %p\n", mountpt, buf);
|
|
DEBUGASSERT(mountpt != NULL && buf != NULL);
|
|
|
|
/* Get the file system structure from the inode reference. */
|
|
|
|
fs = mountpt->i_private;
|
|
DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
ret = tmpfs_lock(fs);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Set up the memory use for the file system and root directory object */
|
|
|
|
tdo = (FAR struct tmpfs_directory_s *)fs->tfs_root.tde_object;
|
|
avail = tdo->tdo_alloc -
|
|
SIZEOF_TMPFS_DIRECTORY(tdo->tdo_nentries);
|
|
|
|
tmpbuf.tsf_alloc = sizeof(struct tmpfs_s) +
|
|
sizeof(struct tmpfs_directory_s) +
|
|
tdo->tdo_alloc;
|
|
tmpbuf.tsf_avail = avail;
|
|
tmpbuf.tsf_files = 0;
|
|
tmpbuf.tsf_ffree = avail / sizeof(struct tmpfs_dirent_s);
|
|
|
|
/* Traverse the file system to accurmulate statistics */
|
|
|
|
ret = tmpfs_foreach(tdo, tmpfs_statfs_callout, (FAR void *)&tmpbuf);
|
|
if (ret < 0)
|
|
{
|
|
return -ECANCELED;
|
|
}
|
|
|
|
/* Return something for the file system description */
|
|
|
|
blkalloc = (tmpbuf.tsf_alloc + CONFIG_FS_TMPFS_BLOCKSIZE - 1) /
|
|
CONFIG_FS_TMPFS_BLOCKSIZE;
|
|
blkavail = (tmpbuf.tsf_avail + CONFIG_FS_TMPFS_BLOCKSIZE - 1) /
|
|
CONFIG_FS_TMPFS_BLOCKSIZE;
|
|
|
|
buf->f_type = TMPFS_MAGIC;
|
|
buf->f_namelen = NAME_MAX;
|
|
buf->f_bsize = CONFIG_FS_TMPFS_BLOCKSIZE;
|
|
buf->f_blocks = blkalloc;
|
|
buf->f_bfree = blkavail;
|
|
buf->f_bavail = blkavail;
|
|
buf->f_files = tmpbuf.tsf_files;
|
|
buf->f_ffree = tmpbuf.tsf_ffree;
|
|
|
|
/* Release the lock on the file system */
|
|
|
|
tmpfs_unlock(fs);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_unlink
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_unlink(FAR struct inode *mountpt, FAR const char *relpath)
|
|
{
|
|
FAR struct tmpfs_s *fs;
|
|
FAR struct tmpfs_directory_s *tdo;
|
|
FAR struct tmpfs_file_s *tfo = NULL;
|
|
FAR const char *name;
|
|
int ret;
|
|
|
|
finfo("mountpt: %p relpath: %s\n", mountpt, relpath);
|
|
DEBUGASSERT(mountpt != NULL && relpath != NULL);
|
|
|
|
/* Get the file system structure from the inode reference. */
|
|
|
|
fs = mountpt->i_private;
|
|
DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
ret = tmpfs_lock(fs);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Find the file object and parent directory associated with this relative
|
|
* path. If successful, tmpfs_find_file will lock both the file object
|
|
* and the parent directory and take one reference count on each.
|
|
*/
|
|
|
|
ret = tmpfs_find_file(fs, relpath, &tfo, &tdo);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
DEBUGASSERT(tfo != NULL);
|
|
|
|
/* Get the file name from the relative path */
|
|
|
|
name = strrchr(relpath, '/');
|
|
if (name != NULL)
|
|
{
|
|
/* Skip over the file '/' character */
|
|
|
|
name++;
|
|
}
|
|
else
|
|
{
|
|
/* The name must lie in the root directory */
|
|
|
|
name = relpath;
|
|
}
|
|
|
|
/* Remove the file from parent directory */
|
|
|
|
ret = tmpfs_remove_dirent(tdo, name);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_objects;
|
|
}
|
|
|
|
/* If the reference count is not one, then just mark the file as
|
|
* unlinked
|
|
*/
|
|
|
|
if (tfo->tfo_refs > 1)
|
|
{
|
|
/* Make the file object as unlinked */
|
|
|
|
tfo->tfo_flags |= TFO_FLAG_UNLINKED;
|
|
|
|
/* Release the reference count on the file object */
|
|
|
|
tfo->tfo_refs--;
|
|
tmpfs_unlock_file(tfo);
|
|
}
|
|
|
|
/* Otherwise we can free the object now */
|
|
|
|
else
|
|
{
|
|
nxrmutex_destroy(&tfo->tfo_lock);
|
|
fs_heap_free(tfo->tfo_data);
|
|
fs_heap_free(tfo);
|
|
}
|
|
|
|
/* Release the reference and lock on the parent directory */
|
|
|
|
tdo->tdo_refs--;
|
|
tmpfs_unlock_directory(tdo);
|
|
tmpfs_unlock(fs);
|
|
|
|
return OK;
|
|
|
|
errout_with_objects:
|
|
tmpfs_release_lockedfile(tfo);
|
|
|
|
tdo->tdo_refs--;
|
|
tmpfs_unlock_directory(tdo);
|
|
|
|
errout_with_lock:
|
|
tmpfs_unlock(fs);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_mkdir
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_mkdir(FAR struct inode *mountpt, FAR const char *relpath,
|
|
mode_t mode)
|
|
{
|
|
FAR struct tmpfs_s *fs;
|
|
int ret;
|
|
|
|
finfo("mountpt: %p relpath: %s mode: %04x\n", mountpt, relpath, mode);
|
|
DEBUGASSERT(mountpt != NULL && relpath != NULL);
|
|
|
|
/* Get the file system structure from the inode reference. */
|
|
|
|
fs = mountpt->i_private;
|
|
DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
ret = tmpfs_lock(fs);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Create the directory. */
|
|
|
|
ret = tmpfs_create_directory(fs, relpath, NULL);
|
|
tmpfs_unlock(fs);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_rmdir
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_rmdir(FAR struct inode *mountpt, FAR const char *relpath)
|
|
{
|
|
FAR struct tmpfs_s *fs;
|
|
FAR struct tmpfs_directory_s *parent;
|
|
FAR struct tmpfs_directory_s *tdo;
|
|
FAR const char *name;
|
|
int ret;
|
|
|
|
finfo("mountpt: %p relpath: %s\n", mountpt, relpath);
|
|
DEBUGASSERT(mountpt != NULL && relpath != NULL);
|
|
|
|
/* Get the file system structure from the inode reference. */
|
|
|
|
fs = mountpt->i_private;
|
|
DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
ret = tmpfs_lock(fs);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Find the directory object and parent directory associated with this
|
|
* relative path. If successful, tmpfs_find_file will lock both the
|
|
* directory object and the parent directory and take one reference count
|
|
* on each.
|
|
*/
|
|
|
|
ret = tmpfs_find_directory(fs, relpath, strlen(relpath), &tdo, &parent);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Is the directory empty? We cannot remove directories that still
|
|
* contain references to file system objects. No can we remove the
|
|
* directory if there are outstanding references on it (other than
|
|
* our reference).
|
|
*/
|
|
|
|
if (tdo->tdo_nentries > 0 || tdo->tdo_refs > 1)
|
|
{
|
|
ret = -EBUSY;
|
|
goto errout_with_objects;
|
|
}
|
|
|
|
/* Get the directory name from the relative path */
|
|
|
|
name = strrchr(relpath, '/');
|
|
if (name && name[1] == '\0')
|
|
{
|
|
/* Ignore the tail '/' */
|
|
|
|
name = memrchr(relpath, '/', name - relpath);
|
|
}
|
|
|
|
if (name != NULL)
|
|
{
|
|
/* Skip over the fidirectoryle '/' character */
|
|
|
|
name++;
|
|
}
|
|
else
|
|
{
|
|
/* The name must lie in the root directory */
|
|
|
|
name = relpath;
|
|
}
|
|
|
|
/* Remove the directory from parent directory */
|
|
|
|
ret = tmpfs_remove_dirent(parent, name);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_objects;
|
|
}
|
|
|
|
/* Free the directory object */
|
|
|
|
nxrmutex_destroy(&tdo->tdo_lock);
|
|
fs_heap_free(tdo->tdo_entry);
|
|
fs_heap_free(tdo);
|
|
|
|
/* Release the reference and lock on the parent directory */
|
|
|
|
parent->tdo_refs--;
|
|
tmpfs_unlock_directory(parent);
|
|
tmpfs_unlock(fs);
|
|
|
|
return OK;
|
|
|
|
errout_with_objects:
|
|
tdo->tdo_refs--;
|
|
tmpfs_unlock_directory(tdo);
|
|
|
|
parent->tdo_refs--;
|
|
tmpfs_unlock_directory(parent);
|
|
|
|
errout_with_lock:
|
|
tmpfs_unlock(fs);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_rename
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_rename(FAR struct inode *mountpt,
|
|
FAR const char *oldrelpath,
|
|
FAR const char *newrelpath)
|
|
{
|
|
FAR struct tmpfs_directory_s *oldparent;
|
|
FAR struct tmpfs_directory_s *newparent;
|
|
FAR struct tmpfs_object_s *to;
|
|
FAR struct tmpfs_s *fs;
|
|
FAR const char *oldname;
|
|
FAR const char *newname;
|
|
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 && fs->tfs_root.tde_object != NULL);
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
ret = tmpfs_lock(fs);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Separate the new path into the new file name and the path to the new
|
|
* parent directory.
|
|
*/
|
|
|
|
newname = strrchr(newrelpath, '/');
|
|
if (newname && newname[1] == '\0')
|
|
{
|
|
/* Ignore the tail '/' */
|
|
|
|
newname = memrchr(newrelpath, '/', newname - newrelpath);
|
|
}
|
|
|
|
if (newname == NULL)
|
|
{
|
|
/* No subdirectories... use the root directory */
|
|
|
|
newname = newrelpath;
|
|
newparent = (FAR struct tmpfs_directory_s *)fs->tfs_root.tde_object;
|
|
|
|
tmpfs_lock_directory(newparent);
|
|
newparent->tdo_refs++;
|
|
}
|
|
else
|
|
{
|
|
/* Locate the parent directory that should contain this name.
|
|
* On success, tmpfs_find_directory() will lockthe parent
|
|
* directory and increment the reference count.
|
|
*/
|
|
|
|
ret = tmpfs_find_directory(fs, newrelpath, newname - newrelpath,
|
|
&newparent, NULL);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* Skip the '/' path separator */
|
|
|
|
newname++;
|
|
}
|
|
|
|
/* Verify that no object of this name already exists in the destination
|
|
* directory.
|
|
*/
|
|
|
|
ret = tmpfs_find_dirent(newparent, newname, strlen(newname));
|
|
if (ret != -ENOENT)
|
|
{
|
|
/* Something with this name already exists in the directory.
|
|
* OR perhaps some fatal error occurred.
|
|
*/
|
|
|
|
if (ret >= 0)
|
|
{
|
|
ret = -EEXIST;
|
|
}
|
|
|
|
goto errout_with_newparent;
|
|
}
|
|
|
|
/* Find the old object at oldpath. If successful, tmpfs_find_object()
|
|
* will lock both the object and the parent directory and will increment
|
|
* the reference count on both.
|
|
*/
|
|
|
|
ret = tmpfs_find_object(fs, oldrelpath, strlen(oldrelpath),
|
|
&to, &oldparent);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_newparent;
|
|
}
|
|
|
|
/* Get the old file name from the relative path */
|
|
|
|
oldname = strrchr(oldrelpath, '/');
|
|
if (oldname && oldname[1] == '\0')
|
|
{
|
|
/* Ignore the tail '/' */
|
|
|
|
oldname = memrchr(oldrelpath, '/', oldname - oldrelpath);
|
|
}
|
|
|
|
if (oldname != NULL)
|
|
{
|
|
/* Skip over the file '/' character */
|
|
|
|
oldname++;
|
|
}
|
|
else
|
|
{
|
|
/* The name must lie in the root directory */
|
|
|
|
oldname = oldrelpath;
|
|
}
|
|
|
|
/* Remove the entry from the parent directory */
|
|
|
|
ret = tmpfs_remove_dirent(oldparent, oldname);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_oldparent;
|
|
}
|
|
|
|
/* Add an entry to the new parent directory. */
|
|
|
|
ret = tmpfs_add_dirent(newparent, to, newname);
|
|
|
|
errout_with_oldparent:
|
|
oldparent->tdo_refs--;
|
|
tmpfs_unlock_directory(oldparent);
|
|
|
|
tmpfs_release_lockedobject(to);
|
|
|
|
errout_with_newparent:
|
|
newparent->tdo_refs--;
|
|
tmpfs_unlock_directory(newparent);
|
|
|
|
errout_with_lock:
|
|
tmpfs_unlock(fs);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_stat_common
|
|
****************************************************************************/
|
|
|
|
static void tmpfs_stat_common(FAR struct tmpfs_object_s *to,
|
|
FAR struct stat *buf)
|
|
{
|
|
size_t objsize;
|
|
|
|
/* Is the tmpfs object a regular file? */
|
|
|
|
memset(buf, 0, sizeof(struct stat));
|
|
|
|
if (to->to_type == TMPFS_REGULAR)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo =
|
|
(FAR struct tmpfs_file_s *)to;
|
|
|
|
/* -rwxrwxrwx */
|
|
|
|
buf->st_mode = S_IRWXO | S_IRWXG | S_IRWXU | S_IFREG;
|
|
|
|
/* Get the size of the object */
|
|
|
|
objsize = tfo->tfo_size;
|
|
}
|
|
else /* if (to->to_type == TMPFS_DIRECTORY) */
|
|
{
|
|
FAR struct tmpfs_directory_s *tdo =
|
|
(FAR struct tmpfs_directory_s *)to;
|
|
|
|
/* drwxrwxrwx */
|
|
|
|
buf->st_mode = S_IRWXO | S_IRWXG | S_IRWXU | S_IFDIR;
|
|
|
|
/* Get the size of the object */
|
|
|
|
objsize = SIZEOF_TMPFS_DIRECTORY(tdo->tdo_nentries);
|
|
}
|
|
|
|
/* Fake the rest of the information */
|
|
|
|
buf->st_size = objsize;
|
|
buf->st_blksize = CONFIG_FS_TMPFS_BLOCKSIZE;
|
|
buf->st_blocks = (objsize + CONFIG_FS_TMPFS_BLOCKSIZE - 1) /
|
|
CONFIG_FS_TMPFS_BLOCKSIZE;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tmpfs_stat
|
|
****************************************************************************/
|
|
|
|
static int tmpfs_stat(FAR struct inode *mountpt, FAR const char *relpath,
|
|
FAR struct stat *buf)
|
|
{
|
|
FAR struct tmpfs_s *fs;
|
|
FAR struct tmpfs_object_s *to;
|
|
int ret;
|
|
|
|
finfo("mountpt=%p relpath=%s buf=%p\n", mountpt, relpath, buf);
|
|
DEBUGASSERT(mountpt != NULL && relpath != NULL && buf != NULL);
|
|
|
|
/* Get the file system structure from the inode reference. */
|
|
|
|
fs = mountpt->i_private;
|
|
DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
ret = tmpfs_lock(fs);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Find the tmpfs object at the relpath. If successful,
|
|
* tmpfs_find_object() will lock the object and increment the
|
|
* reference count on the object.
|
|
*/
|
|
|
|
ret = tmpfs_find_object(fs, relpath, strlen(relpath), &to, NULL);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_fslock;
|
|
}
|
|
|
|
/* We found it... Return information about the file object in the stat
|
|
* buffer.
|
|
*/
|
|
|
|
DEBUGASSERT(to != NULL);
|
|
tmpfs_stat_common(to, buf);
|
|
|
|
/* Unlock the object and return success */
|
|
|
|
tmpfs_release_lockedobject(to);
|
|
ret = OK;
|
|
|
|
errout_with_fslock:
|
|
tmpfs_unlock(fs);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
#endif /* CONFIG_DISABLE_MOUNTPOINT */
|