nuttx/fs/tmpfs/fs_tmpfs.c
dongjiuzhu1 e4739ab575 fs/tmpfs: add munmap interface for tmpfs file map
Signed-off-by: dongjiuzhu1 <dongjiuzhu1@xiaomi.com>
2023-07-28 08:05:39 -07:00

2719 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_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)
{
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_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);
/* 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;
}
/* Decrement the reference count on the file */
DEBUGASSERT(tfo->tfo_refs > 0);
if (tfo->tfo_refs > 0)
{
tfo->tfo_refs--;
}
filep->f_priv = NULL;
/* If the reference count decremented to zero and the file has been
* unlinked, then free the file allocation now.
*/
if (tfo->tfo_refs == 0 && (tfo->tfo_flags & TFO_FLAG_UNLINKED) != 0)
{
/* Free the file object while we hold the lock? Weird but this
* should be safe because the object is unlinked and could not
* have any other references.
*/
nxrmutex_destroy(&tfo->tfo_lock);
kmm_free(tfo->tfo_data);
kmm_free(tfo);
return OK;
}
/* Release the lock on the file */
tmpfs_unlock_file(tfo);
return OK;
}
/****************************************************************************
* 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 file *filep = 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);
}
/* No.. We have been asked to "unmap' only a portion of the memory
* (offset > 0).
*/
else
{
entry->length = length;
ret = tmpfs_mmap(filep, entry);
}
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 = filep;
map->munmap = tmpfs_unmap;
ret = mm_map_add(get_current_mm(), map);
}
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 */