946ed96926
For some case, need large files from tmpfs or shmfs, will lead to high pressure on memory fragments, add an optional fs_heap with independent heap will benifit for memory fragments issue. Signed-off-by: buxiasen <buxiasen@xiaomi.com>
535 lines
12 KiB
C
535 lines
12 KiB
C
/****************************************************************************
|
|
* fs/vfs/fs_pseudofile.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 <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <fcntl.h>
|
|
#include <sys/param.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/fs/ioctl.h>
|
|
#include <nuttx/lib/math32.h>
|
|
|
|
#include "inode/inode.h"
|
|
#include "notify/notify.h"
|
|
#include "fs_heap.h"
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct fs_pseudofile_s
|
|
{
|
|
mutex_t lock;
|
|
uint8_t crefs;
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
bool unlinked;
|
|
#endif
|
|
FAR char *content;
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions Prototypes
|
|
****************************************************************************/
|
|
|
|
static int pseudofile_open(FAR struct file *filep);
|
|
static int pseudofile_close(FAR struct file *filep);
|
|
static ssize_t pseudofile_write(FAR struct file *filep,
|
|
FAR const char *buffer, size_t buflen);
|
|
static ssize_t pseudofile_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static off_t pseudofile_seek(FAR struct file *filep, off_t offset,
|
|
int whence);
|
|
static int pseudofile_mmap(FAR struct file *filep,
|
|
FAR struct mm_map_entry_s *map);
|
|
static int pseudofile_munmap(FAR struct task_group_s *group,
|
|
FAR struct mm_map_entry_s *map,
|
|
FAR void *start,
|
|
size_t length);
|
|
static int pseudofile_truncate(FAR struct file *filep, off_t length);
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
static int pseudofile_unlink(FAR struct inode *inode);
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct file_operations g_pseudofile_ops =
|
|
{
|
|
pseudofile_open, /* open */
|
|
pseudofile_close, /* close */
|
|
pseudofile_read, /* read */
|
|
pseudofile_write, /* write */
|
|
pseudofile_seek, /* seek */
|
|
NULL, /* ioctl */
|
|
pseudofile_mmap, /* mmap */
|
|
pseudofile_truncate, /* truncate */
|
|
NULL, /* poll */
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
pseudofile_unlink, /* unlink */
|
|
#endif
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
static int pseudofile_open(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *node = filep->f_inode;
|
|
FAR struct fs_pseudofile_s *pf = node->i_private;
|
|
int ret;
|
|
|
|
ret = nxmutex_lock(&pf->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (pf->crefs >= 255)
|
|
{
|
|
ret = -EMFILE;
|
|
}
|
|
else
|
|
{
|
|
pf->crefs += 1;
|
|
ret = OK;
|
|
}
|
|
|
|
#ifdef CONFIG_PSEUDOFS_ATTRIBUTES
|
|
node->i_atime.tv_sec = time(NULL);
|
|
#endif
|
|
nxmutex_unlock(&pf->lock);
|
|
return ret;
|
|
}
|
|
|
|
static void pseudofile_remove(FAR struct fs_pseudofile_s *pf)
|
|
{
|
|
nxmutex_unlock(&pf->lock);
|
|
nxmutex_destroy(&pf->lock);
|
|
fs_heap_free(pf->content);
|
|
fs_heap_free(pf);
|
|
}
|
|
|
|
static int pseudofile_close(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *node = filep->f_inode;
|
|
FAR struct fs_pseudofile_s *pf = node->i_private;
|
|
int ret;
|
|
|
|
ret = nxmutex_lock(&pf->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
pf->crefs--;
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
if (pf->crefs <= 0 && pf->unlinked)
|
|
#else
|
|
if (pf->crefs <= 0)
|
|
#endif
|
|
{
|
|
pseudofile_remove(pf);
|
|
return OK;
|
|
}
|
|
|
|
nxmutex_unlock(&pf->lock);
|
|
return OK;
|
|
}
|
|
|
|
static int pseudofile_expand(FAR struct inode *node,
|
|
size_t size)
|
|
{
|
|
FAR struct fs_pseudofile_s *pf = node->i_private;
|
|
FAR void *tmp;
|
|
|
|
if (pf->content && fs_heap_malloc_size(pf->content) >= size)
|
|
{
|
|
node->i_size = size;
|
|
return 0;
|
|
}
|
|
|
|
tmp = fs_heap_realloc(pf->content, 1 << LOG2_CEIL(size));
|
|
if (tmp == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pf->content = tmp;
|
|
node->i_size = size;
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t pseudofile_write(FAR struct file *filep,
|
|
FAR const char *buffer, size_t buflen)
|
|
{
|
|
FAR struct inode *node = filep->f_inode;
|
|
FAR struct fs_pseudofile_s *pf = node->i_private;
|
|
int ret;
|
|
|
|
ret = nxmutex_lock(&pf->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (filep->f_oflags & O_APPEND)
|
|
{
|
|
ret = pseudofile_expand(node, node->i_size + buflen);
|
|
if (ret < 0)
|
|
{
|
|
nxmutex_unlock(&pf->lock);
|
|
return ret;
|
|
}
|
|
|
|
filep->f_pos = node->i_size - buflen;
|
|
}
|
|
else
|
|
{
|
|
ret = pseudofile_expand(node, filep->f_pos + buflen);
|
|
if (ret < 0)
|
|
{
|
|
nxmutex_unlock(&pf->lock);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
memcpy(pf->content + filep->f_pos, buffer, buflen);
|
|
filep->f_pos += buflen;
|
|
#ifdef CONFIG_PSEUDOFS_ATTRIBUTES
|
|
node->i_mtime.tv_sec = time(NULL);
|
|
#endif
|
|
|
|
nxmutex_unlock(&pf->lock);
|
|
return buflen;
|
|
}
|
|
|
|
static ssize_t pseudofile_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct inode *node = filep->f_inode;
|
|
FAR struct fs_pseudofile_s *pf = node->i_private;
|
|
int ret;
|
|
|
|
if (buffer == NULL || node->i_size < filep->f_pos)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = nxmutex_lock(&pf->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
buflen = MIN(node->i_size - filep->f_pos, buflen);
|
|
memcpy(buffer, pf->content + filep->f_pos, buflen);
|
|
filep->f_pos += buflen;
|
|
|
|
#ifdef CONFIG_PSEUDOFS_ATTRIBUTES
|
|
node->i_atime.tv_sec = time(NULL);
|
|
#endif
|
|
nxmutex_unlock(&pf->lock);
|
|
|
|
return buflen;
|
|
}
|
|
|
|
static off_t pseudofile_seek(FAR struct file *filep, off_t offset,
|
|
int whence)
|
|
{
|
|
FAR struct inode *node = filep->f_inode;
|
|
FAR struct fs_pseudofile_s *pf = node->i_private;
|
|
int ret;
|
|
|
|
ret = nxmutex_lock(&pf->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Map the offset according to the whence option */
|
|
|
|
switch (whence)
|
|
{
|
|
case SEEK_SET:
|
|
break;
|
|
|
|
case SEEK_CUR:
|
|
offset += filep->f_pos;
|
|
break;
|
|
|
|
case SEEK_END:
|
|
offset += node->i_size;
|
|
break;
|
|
|
|
default:
|
|
nxmutex_unlock(&pf->lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (offset < 0)
|
|
{
|
|
nxmutex_unlock(&pf->lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
filep->f_pos = offset;
|
|
#ifdef CONFIG_PSEUDOFS_ATTRIBUTES
|
|
node->i_atime.tv_sec = time(NULL);
|
|
#endif
|
|
nxmutex_unlock(&pf->lock);
|
|
return offset;
|
|
}
|
|
|
|
static int pseudofile_mmap(FAR struct file *filep,
|
|
FAR struct mm_map_entry_s *map)
|
|
{
|
|
FAR struct inode *node = filep->f_inode;
|
|
FAR struct fs_pseudofile_s *pf = node->i_private;
|
|
|
|
/* Keep the inode when mmapped, increase refcount */
|
|
|
|
int ret = inode_addref(node);
|
|
if (ret >= 0)
|
|
{
|
|
if (map->offset >= 0 && map->offset < node->i_size &&
|
|
map->length != 0 && map->offset + map->length <= node->i_size)
|
|
{
|
|
map->vaddr = pf->content + map->offset;
|
|
map->munmap = pseudofile_munmap;
|
|
map->priv.p = (FAR void *)node;
|
|
ret = mm_map_add(get_current_mm(), map);
|
|
}
|
|
else
|
|
{
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
inode_release(node);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int pseudofile_munmap(FAR struct task_group_s *group,
|
|
FAR struct mm_map_entry_s *map,
|
|
FAR void *start,
|
|
size_t length)
|
|
{
|
|
FAR struct inode *inode = (FAR struct inode *)map->priv.p;
|
|
|
|
/* If the file has been unlinked previously, delete the contents.
|
|
* The inode is released after this call, hence checking if i_crefs <= 1.
|
|
*/
|
|
|
|
int ret = inode_lock();
|
|
if (ret >= 0)
|
|
{
|
|
if (inode->i_parent == NULL &&
|
|
inode->i_crefs <= 1)
|
|
{
|
|
/* Delete the inode metadata */
|
|
|
|
if (inode->i_private)
|
|
{
|
|
fs_heap_free(inode->i_private);
|
|
}
|
|
|
|
inode->i_private = NULL;
|
|
ret = OK;
|
|
}
|
|
|
|
inode_unlock();
|
|
}
|
|
|
|
/* Unkeep the inode when unmapped, decrease refcount */
|
|
|
|
if (ret == OK)
|
|
{
|
|
inode_release(inode);
|
|
|
|
/* Remove the mapping. */
|
|
|
|
ret = mm_map_remove(get_group_mm(group), map);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int pseudofile_truncate(FAR struct file *filep, off_t length)
|
|
{
|
|
FAR struct inode *node = filep->f_inode;
|
|
FAR struct fs_pseudofile_s *pf = node->i_private;
|
|
int ret;
|
|
|
|
ret = nxmutex_lock(&pf->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (length < node->i_size)
|
|
{
|
|
FAR void *tmp;
|
|
|
|
tmp = fs_heap_realloc(pf->content, length);
|
|
if (tmp == NULL)
|
|
{
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
pf->content = tmp;
|
|
node->i_size = length;
|
|
}
|
|
else
|
|
{
|
|
ret = pseudofile_expand(node, length);
|
|
if (ret < 0)
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
memset(pf->content + node->i_size, 0, length - node->i_size);
|
|
}
|
|
|
|
#ifdef CONFIG_PSEUDOFS_ATTRIBUTES
|
|
node->i_mtime.tv_sec = time(NULL);
|
|
#endif
|
|
|
|
out:
|
|
nxmutex_unlock(&pf->lock);
|
|
return ret;
|
|
}
|
|
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
static int pseudofile_unlink(FAR struct inode *node)
|
|
{
|
|
FAR struct fs_pseudofile_s *pf = node->i_private;
|
|
int ret;
|
|
|
|
ret = nxmutex_lock(&pf->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (pf->crefs <= 0)
|
|
{
|
|
pseudofile_remove(pf);
|
|
return OK;
|
|
}
|
|
|
|
pf->unlinked = true;
|
|
nxmutex_unlock(&pf->lock);
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: pseudofile_create
|
|
*
|
|
* Description:
|
|
* Create the pseudo-file with specified path and mode, and alloc inode
|
|
* of this pseudo-file.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int pseudofile_create(FAR struct inode **node, FAR const char *path,
|
|
mode_t mode)
|
|
{
|
|
FAR struct fs_pseudofile_s *pf;
|
|
int ret;
|
|
|
|
if (node == NULL || path == NULL)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
pf = fs_heap_zalloc(sizeof(struct fs_pseudofile_s));
|
|
if (pf == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
nxmutex_init(&pf->lock);
|
|
|
|
ret = inode_lock();
|
|
if (ret < 0)
|
|
{
|
|
goto lock_err;
|
|
}
|
|
|
|
ret = inode_reserve(path, mode, node);
|
|
if (ret < 0)
|
|
{
|
|
goto reserve_err;
|
|
}
|
|
|
|
(*node)->i_crefs = 0;
|
|
(*node)->i_flags = 1;
|
|
(*node)->u.i_ops = &g_pseudofile_ops;
|
|
(*node)->i_private = pf;
|
|
|
|
inode_unlock();
|
|
#ifdef CONFIG_FS_NOTIFY
|
|
notify_create(path);
|
|
#endif
|
|
return 0;
|
|
|
|
reserve_err:
|
|
inode_unlock();
|
|
lock_err:
|
|
nxmutex_destroy(&pf->lock);
|
|
fs_heap_free(pf);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: inode_is_pseudofile
|
|
*
|
|
* Description:
|
|
* Check inode whether is a pseudo file.
|
|
*
|
|
****************************************************************************/
|
|
|
|
bool inode_is_pseudofile(FAR struct inode *inode)
|
|
{
|
|
return inode != NULL && inode->u.i_ops == &g_pseudofile_ops;
|
|
}
|