mmap establish a mapping address space that refer to a tmpfs file, there are two issues: 1. if the tmpfs file is removed and a subsequent close(), tmpfs_close will release the backend memory object, use after free errors occur when operating the mapping memory. We add an extra reference to memory object, memory will be released when there are no more mappings. 2. if unmap only a portion of the memory, fix the bug that adds another map. Use realloc and shrink the mapping memory instead. The fix pass LTP posix case mmap/10-1.c and mmap/12-1.c Signed-off-by: fangxinyong <fangxinyong@xiaomi.com>
2721 lines
69 KiB
C
2721 lines
69 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 "fs_tmpfs.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 Type
|
|
****************************************************************************/
|
|
|
|
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(void);
|
|
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(void);
|
|
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_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 */
|
|
NULL, /* ioctl */
|
|
tmpfs_mmap, /* mmap */
|
|
tmpfs_truncate, /* truncate */
|
|
|
|
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 = kmm_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)
|
|
{
|
|
/* Otherwise, don't realloc unless the object has shrunk by a
|
|
* lot.
|
|
*/
|
|
|
|
delta = tfo->tfo_alloc - newsize;
|
|
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;
|
|
|
|
/* Realloc the file object */
|
|
|
|
newdata = kmm_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);
|
|
kmm_free(tfo->tfo_data);
|
|
kmm_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)
|
|
{
|
|
kmm_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)
|
|
{
|
|
kmm_free(newname);
|
|
return index;
|
|
}
|
|
|
|
/* Save the new object info in the new directory entry */
|
|
|
|
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(void)
|
|
{
|
|
FAR struct tmpfs_file_s *tfo;
|
|
|
|
/* Create a new zero length file object */
|
|
|
|
tfo = (FAR struct tmpfs_file_s *)kmm_malloc(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_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();
|
|
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);
|
|
kmm_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(void)
|
|
{
|
|
FAR struct tmpfs_directory_s *tdo;
|
|
|
|
/* Create a new zero length directory object */
|
|
|
|
tdo = (FAR struct tmpfs_directory_s *)kmm_malloc(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_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();
|
|
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);
|
|
kmm_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_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)
|
|
{
|
|
kmm_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;
|
|
}
|
|
|
|
kmm_free(tfo->tfo_data);
|
|
}
|
|
else /* if (to->to_type == TMPFS_DIRECTORY) */
|
|
{
|
|
tdo = (FAR struct tmpfs_directory_s *)to;
|
|
|
|
kmm_free(tdo->tdo_entry);
|
|
}
|
|
|
|
/* Free the object now */
|
|
|
|
nxrmutex_destroy(&to->to_lock);
|
|
kmm_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 && 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 && 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 && filep->f_inode != 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 && filep->f_inode != 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 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 && filep->f_inode != 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 */
|
|
|
|
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);
|
|
filep->f_pos += nwritten;
|
|
}
|
|
else
|
|
{
|
|
DEBUGASSERT(tfo->tfo_size == 0 && nwritten == 0);
|
|
}
|
|
|
|
/* 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 && filep->f_inode != 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;
|
|
}
|
|
|
|
/* Attempts to set the position beyond the end of file will
|
|
* 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 0
|
|
if (position > tfo->tfo_size && (tfo->tfo_oflags & O_WROK) == 0)
|
|
{
|
|
/* Otherwise, the position is limited to the file size */
|
|
|
|
position = tfo->tfo_size;
|
|
}
|
|
#endif
|
|
|
|
/* 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 && filep->f_inode != 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_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(filep != NULL && buf != NULL);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != 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(filep != NULL && 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 = kmm_zalloc(sizeof(*tdir));
|
|
if (tdir == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Get exclusive access to the file system */
|
|
|
|
ret = tmpfs_lock(fs);
|
|
if (ret < 0)
|
|
{
|
|
kmm_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);
|
|
kmm_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 = (FAR struct tmpfs_s *)kmm_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();
|
|
if (tdo == NULL)
|
|
{
|
|
kmm_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);
|
|
kmm_free(tdo->tdo_entry);
|
|
kmm_free(tdo);
|
|
|
|
nxrmutex_destroy(&fs->tfs_lock);
|
|
kmm_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);
|
|
kmm_free(tfo->tfo_data);
|
|
kmm_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);
|
|
kmm_free(tdo->tdo_entry);
|
|
kmm_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 */
|