3f47fd767a
Summary: 1.Add configuration to allocate memory from the specified section 2.Replace all memory operations (kmm_) in the vfs with fs_heap_. When FS_HEAPSIZE > 0, memory is requested for the file system by specifying a configured heap location. By default (i.e. FS_HEAPSIZE=0) fs_heap_ is equivalent to kmm_ Signed-off-by: chenrun1 <chenrun1@xiaomi.com>
1490 lines
38 KiB
C
1490 lines
38 KiB
C
/****************************************************************************
|
|
* fs/littlefs/lfs_vfs.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 <errno.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/fs/ioctl.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/mtd/mtd.h>
|
|
#include <nuttx/mutex.h>
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/statfs.h>
|
|
|
|
#include "inode/inode.h"
|
|
#include "littlefs/lfs.h"
|
|
#include "littlefs/lfs_util.h"
|
|
#include "fs_heap.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#ifndef CONFIG_C99_BOOL
|
|
# error littlefs requires CONFIG_C99_BOOL to be selected
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct littlefs_dir_s
|
|
{
|
|
struct fs_dirent_s base;
|
|
struct lfs_dir dir;
|
|
};
|
|
|
|
struct littlefs_file_s
|
|
{
|
|
struct lfs_file file;
|
|
int refs;
|
|
};
|
|
|
|
/* This structure represents the overall mountpoint state. An instance of
|
|
* this structure is retained as inode private data on each mountpoint that
|
|
* is mounted with a littlefs filesystem.
|
|
*/
|
|
|
|
struct littlefs_mountpt_s
|
|
{
|
|
mutex_t lock;
|
|
FAR struct inode *drv;
|
|
struct mtd_geometry_s geo;
|
|
struct lfs_config cfg;
|
|
struct lfs lfs;
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static int littlefs_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode);
|
|
static int littlefs_close(FAR struct file *filep);
|
|
static ssize_t littlefs_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static ssize_t littlefs_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen);
|
|
static off_t littlefs_seek(FAR struct file *filep, off_t offset,
|
|
int whence);
|
|
static int littlefs_ioctl(FAR struct file *filep, int cmd,
|
|
unsigned long arg);
|
|
|
|
static int littlefs_sync(FAR struct file *filep);
|
|
static int littlefs_dup(FAR const struct file *oldp,
|
|
FAR struct file *newp);
|
|
static int littlefs_fstat(FAR const struct file *filep,
|
|
FAR struct stat *buf);
|
|
static int littlefs_truncate(FAR struct file *filep,
|
|
off_t length);
|
|
|
|
static int littlefs_opendir(FAR struct inode *mountpt,
|
|
FAR const char *relpath,
|
|
FAR struct fs_dirent_s **dir);
|
|
static int littlefs_closedir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir);
|
|
static int littlefs_readdir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir,
|
|
FAR struct dirent *entry);
|
|
static int littlefs_rewinddir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir);
|
|
|
|
static int littlefs_bind(FAR struct inode *driver,
|
|
FAR const void *data, FAR void **handle);
|
|
static int littlefs_unbind(FAR void *handle, FAR struct inode **driver,
|
|
unsigned int flags);
|
|
static int littlefs_statfs(FAR struct inode *mountpt,
|
|
FAR struct statfs *buf);
|
|
|
|
static int littlefs_unlink(FAR struct inode *mountpt,
|
|
FAR const char *relpath);
|
|
static int littlefs_mkdir(FAR struct inode *mountpt,
|
|
FAR const char *relpath, mode_t mode);
|
|
static int littlefs_rmdir(FAR struct inode *mountpt,
|
|
FAR const char *relpath);
|
|
static int littlefs_rename(FAR struct inode *mountpt,
|
|
FAR const char *oldrelpath,
|
|
FAR const char *newrelpath);
|
|
static int littlefs_stat(FAR struct inode *mountpt,
|
|
FAR const char *relpath, FAR struct stat *buf);
|
|
|
|
/****************************************************************************
|
|
* Public Data
|
|
****************************************************************************/
|
|
|
|
/* See fs_mount.c -- this structure is explicitly extern'ed there.
|
|
* We use the old-fashioned kind of initializers so that this will compile
|
|
* with any compiler.
|
|
*/
|
|
|
|
const struct mountpt_operations g_littlefs_operations =
|
|
{
|
|
littlefs_open, /* open */
|
|
littlefs_close, /* close */
|
|
littlefs_read, /* read */
|
|
littlefs_write, /* write */
|
|
littlefs_seek, /* seek */
|
|
littlefs_ioctl, /* ioctl */
|
|
NULL, /* mmap */
|
|
littlefs_truncate, /* truncate */
|
|
NULL, /* poll */
|
|
|
|
littlefs_sync, /* sync */
|
|
littlefs_dup, /* dup */
|
|
littlefs_fstat, /* fstat */
|
|
NULL, /* fchstat */
|
|
|
|
littlefs_opendir, /* opendir */
|
|
littlefs_closedir, /* closedir */
|
|
littlefs_readdir, /* readdir */
|
|
littlefs_rewinddir, /* rewinddir */
|
|
|
|
littlefs_bind, /* bind */
|
|
littlefs_unbind, /* unbind */
|
|
littlefs_statfs, /* statfs */
|
|
|
|
littlefs_unlink, /* unlink */
|
|
littlefs_mkdir, /* mkdir */
|
|
littlefs_rmdir, /* rmdir */
|
|
littlefs_rename, /* rename */
|
|
littlefs_stat, /* stat */
|
|
NULL /* chstat */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_convert_result
|
|
****************************************************************************/
|
|
|
|
static int littlefs_convert_result(int ret)
|
|
{
|
|
switch (ret)
|
|
{
|
|
case LFS_ERR_IO:
|
|
return -EIO;
|
|
|
|
case LFS_ERR_CORRUPT:
|
|
return -EFAULT;
|
|
|
|
case LFS_ERR_NOENT:
|
|
return -ENOENT;
|
|
|
|
case LFS_ERR_EXIST:
|
|
return -EEXIST;
|
|
|
|
case LFS_ERR_NOTDIR:
|
|
return -ENOTDIR;
|
|
|
|
case LFS_ERR_ISDIR:
|
|
return -EISDIR;
|
|
|
|
case LFS_ERR_NOTEMPTY:
|
|
return -ENOTEMPTY;
|
|
|
|
case LFS_ERR_BADF:
|
|
return -EBADF;
|
|
|
|
case LFS_ERR_FBIG:
|
|
return -EFBIG;
|
|
|
|
case LFS_ERR_INVAL:
|
|
return -EINVAL;
|
|
|
|
case LFS_ERR_NOSPC:
|
|
return -ENOSPC;
|
|
|
|
case LFS_ERR_NOMEM:
|
|
return -ENOMEM;
|
|
|
|
case LFS_ERR_NOATTR:
|
|
return -ENODATA;
|
|
|
|
case LFS_ERR_NAMETOOLONG:
|
|
return -ENAMETOOLONG;
|
|
|
|
case LFS_ERR_OK:
|
|
default:
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_convert_oflags
|
|
****************************************************************************/
|
|
|
|
static int littlefs_convert_oflags(int oflags)
|
|
{
|
|
int ret = 0;
|
|
|
|
if ((oflags & O_RDONLY) != 0)
|
|
{
|
|
ret |= LFS_O_RDONLY;
|
|
}
|
|
|
|
if ((oflags & O_WRONLY) != 0)
|
|
{
|
|
ret |= LFS_O_WRONLY;
|
|
}
|
|
|
|
if ((oflags & O_CREAT) != 0)
|
|
{
|
|
ret |= LFS_O_CREAT;
|
|
}
|
|
|
|
if ((oflags & O_EXCL) != 0)
|
|
{
|
|
ret |= LFS_O_EXCL;
|
|
}
|
|
|
|
if ((oflags & O_APPEND) != 0)
|
|
{
|
|
ret |= LFS_O_APPEND;
|
|
}
|
|
|
|
if ((oflags & O_TRUNC) != 0)
|
|
{
|
|
ret |= LFS_O_TRUNC;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_open
|
|
****************************************************************************/
|
|
|
|
static int littlefs_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_file_s *priv;
|
|
FAR struct inode *inode;
|
|
int ret;
|
|
|
|
/* 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;
|
|
|
|
/* Allocate memory for the open file */
|
|
|
|
priv = fs_heap_malloc(sizeof(*priv));
|
|
if (priv == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->refs = 1;
|
|
|
|
/* Lock */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
goto errlock;
|
|
}
|
|
|
|
/* Try to open the file */
|
|
|
|
oflags = littlefs_convert_oflags(oflags);
|
|
ret = littlefs_convert_result(lfs_file_open(&fs->lfs, &priv->file,
|
|
relpath, oflags));
|
|
if (ret < 0)
|
|
{
|
|
/* Error opening file */
|
|
|
|
goto errout;
|
|
}
|
|
|
|
/* In append mode, we need to set the file pointer to the end of the
|
|
* file.
|
|
*/
|
|
|
|
if (oflags & LFS_O_APPEND)
|
|
{
|
|
ret = littlefs_convert_result(lfs_file_seek(&fs->lfs, &priv->file,
|
|
0, LFS_SEEK_END));
|
|
if (ret >= 0)
|
|
{
|
|
filep->f_pos = ret;
|
|
}
|
|
else
|
|
{
|
|
goto errout_with_file;
|
|
}
|
|
}
|
|
|
|
/* Sync here in case of O_TRUNC haven't actually done immediately,
|
|
* e.g. total 8M, fileA 6M, O_TRUNC re-wrting fileA 6M, meet error.
|
|
*/
|
|
|
|
lfs_file_sync(&fs->lfs, &priv->file);
|
|
nxmutex_unlock(&fs->lock);
|
|
|
|
/* Attach the private date to the struct file instance */
|
|
|
|
filep->f_priv = priv;
|
|
return OK;
|
|
|
|
errout_with_file:
|
|
lfs_file_close(&fs->lfs, &priv->file);
|
|
errout:
|
|
nxmutex_unlock(&fs->lock);
|
|
errlock:
|
|
fs_heap_free(priv);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_close
|
|
****************************************************************************/
|
|
|
|
static int littlefs_close(FAR struct file *filep)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_file_s *priv;
|
|
FAR struct inode *inode;
|
|
int ret;
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
priv = filep->f_priv;
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
|
|
/* Close the file */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (--priv->refs <= 0)
|
|
{
|
|
ret = littlefs_convert_result(lfs_file_close(&fs->lfs, &priv->file));
|
|
}
|
|
|
|
nxmutex_unlock(&fs->lock);
|
|
if (priv->refs <= 0)
|
|
{
|
|
fs_heap_free(priv);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t littlefs_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_file_s *priv;
|
|
FAR struct inode *inode;
|
|
ssize_t ret;
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
priv = filep->f_priv;
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
|
|
/* Call LFS to perform the read */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (filep->f_pos != priv->file.pos)
|
|
{
|
|
ret = littlefs_convert_result(lfs_file_seek(&fs->lfs, &priv->file,
|
|
filep->f_pos,
|
|
LFS_SEEK_SET));
|
|
if (ret < 0)
|
|
{
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_file_read(&fs->lfs, &priv->file,
|
|
buffer, buflen));
|
|
if (ret > 0)
|
|
{
|
|
filep->f_pos += ret;
|
|
}
|
|
|
|
out:
|
|
nxmutex_unlock(&fs->lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t littlefs_write(FAR struct file *filep, const char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_file_s *priv;
|
|
FAR struct inode *inode;
|
|
ssize_t ret;
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
priv = filep->f_priv;
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
|
|
/* Call LFS to perform the write */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (filep->f_pos != priv->file.pos)
|
|
{
|
|
ret = littlefs_convert_result(lfs_file_seek(&fs->lfs, &priv->file,
|
|
filep->f_pos,
|
|
LFS_SEEK_SET));
|
|
if (ret < 0)
|
|
{
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_file_write(&fs->lfs, &priv->file,
|
|
buffer, buflen));
|
|
if (ret > 0)
|
|
{
|
|
filep->f_pos += ret;
|
|
}
|
|
|
|
out:
|
|
nxmutex_unlock(&fs->lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_seek
|
|
****************************************************************************/
|
|
|
|
static off_t littlefs_seek(FAR struct file *filep, off_t offset, int whence)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_file_s *priv;
|
|
FAR struct inode *inode;
|
|
off_t ret;
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
priv = filep->f_priv;
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
|
|
/* Call LFS to perform the seek */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_file_seek(&fs->lfs, &priv->file,
|
|
offset, whence));
|
|
if (ret >= 0)
|
|
{
|
|
filep->f_pos = ret;
|
|
}
|
|
|
|
nxmutex_unlock(&fs->lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_ioctl
|
|
****************************************************************************/
|
|
|
|
static int littlefs_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_file_s *priv;
|
|
FAR struct inode *inode;
|
|
FAR struct inode *drv;
|
|
int ret;
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
priv = filep->f_priv;
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
drv = fs->drv;
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
switch (cmd)
|
|
{
|
|
case FIOC_FILEPATH:
|
|
{
|
|
FAR char *path = (FAR char *)(uintptr_t)arg;
|
|
ret = inode_getpath(inode, path, PATH_MAX);
|
|
if (ret >= 0)
|
|
{
|
|
size_t len = strlen(path);
|
|
if (path[len - 1] != '/')
|
|
{
|
|
path[len++] = '/';
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_file_path(&fs->lfs,
|
|
&priv->file,
|
|
path + len,
|
|
PATH_MAX - len));
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
if (INODE_IS_MTD(drv))
|
|
{
|
|
ret = MTD_IOCTL(drv->u.i_mtd, cmd, arg);
|
|
}
|
|
else
|
|
{
|
|
if (drv->u.i_bops->ioctl != NULL)
|
|
{
|
|
ret = drv->u.i_bops->ioctl(drv, cmd, arg);
|
|
}
|
|
else
|
|
{
|
|
ret = -ENOTTY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nxmutex_unlock(&fs->lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_sync
|
|
*
|
|
* Description: Synchronize the file state on disk to match internal, in-
|
|
* memory state.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_sync(FAR struct file *filep)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_file_s *priv;
|
|
FAR struct inode *inode;
|
|
int ret;
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
priv = filep->f_priv;
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_file_sync(&fs->lfs, &priv->file));
|
|
nxmutex_unlock(&fs->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_dup
|
|
*
|
|
* Description: Duplicate open file data in the new file structure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_dup(FAR const struct file *oldp, FAR struct file *newp)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_file_s *priv;
|
|
FAR struct inode *inode;
|
|
int ret;
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
priv = oldp->f_priv;
|
|
inode = oldp->f_inode;
|
|
fs = inode->i_private;
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
priv->refs++;
|
|
newp->f_priv = priv;
|
|
nxmutex_unlock(&fs->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_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 littlefs_fstat(FAR const struct file *filep, FAR struct stat *buf)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_file_s *priv;
|
|
FAR struct inode *inode;
|
|
int ret;
|
|
|
|
memset(buf, 0, sizeof(*buf));
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
priv = filep->f_priv;
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
|
|
/* Call LFS to get file size */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
buf->st_size = lfs_file_size(&fs->lfs, &priv->file);
|
|
nxmutex_unlock(&fs->lock);
|
|
|
|
if (buf->st_size < 0)
|
|
{
|
|
return littlefs_convert_result(buf->st_size);
|
|
}
|
|
|
|
buf->st_mode = S_IRWXO | S_IRWXG | S_IRWXU | S_IFREG;
|
|
buf->st_blksize = fs->cfg.block_size;
|
|
buf->st_blocks = (buf->st_size + buf->st_blksize - 1) / buf->st_blksize;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_truncate
|
|
*
|
|
* Description:
|
|
* Set the length of the open, regular file associated with the file
|
|
* structure 'filep' to 'length'.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_truncate(FAR struct file *filep, off_t length)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_file_s *priv;
|
|
FAR struct inode *inode;
|
|
int ret;
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
priv = filep->f_priv;
|
|
inode = filep->f_inode;
|
|
fs = inode->i_private;
|
|
|
|
/* Call LFS to perform the truncate */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_file_truncate(&fs->lfs, &priv->file,
|
|
length));
|
|
nxmutex_unlock(&fs->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_opendir
|
|
*
|
|
* Description: Open a directory for read access
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_opendir(FAR struct inode *mountpt,
|
|
FAR const char *relpath,
|
|
FAR struct fs_dirent_s **dir)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_dir_s *ldir;
|
|
int ret;
|
|
|
|
/* Recover our private data from the inode instance */
|
|
|
|
fs = mountpt->i_private;
|
|
|
|
/* Allocate memory for the open directory */
|
|
|
|
ldir = fs_heap_malloc(sizeof(*ldir));
|
|
if (ldir == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Take the lock */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
goto errlock;
|
|
}
|
|
|
|
/* Call the LFS's opendir function */
|
|
|
|
ret = littlefs_convert_result(lfs_dir_open(&fs->lfs, &ldir->dir, relpath));
|
|
if (ret < 0)
|
|
{
|
|
goto errout;
|
|
}
|
|
|
|
nxmutex_unlock(&fs->lock);
|
|
*dir = &ldir->base;
|
|
return OK;
|
|
|
|
errout:
|
|
nxmutex_unlock(&fs->lock);
|
|
errlock:
|
|
fs_heap_free(ldir);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_closedir
|
|
*
|
|
* Description: Close a directory
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_closedir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_dir_s *ldir;
|
|
int ret;
|
|
|
|
/* Recover our private data from the inode instance */
|
|
|
|
ldir = (FAR struct littlefs_dir_s *)dir;
|
|
fs = mountpt->i_private;
|
|
|
|
/* Call the LFS's closedir function */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
lfs_dir_close(&fs->lfs, &ldir->dir);
|
|
nxmutex_unlock(&fs->lock);
|
|
|
|
fs_heap_free(ldir);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_readdir
|
|
*
|
|
* Description: Read the next directory entry
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_readdir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir,
|
|
FAR struct dirent *entry)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_dir_s *ldir;
|
|
struct lfs_info info;
|
|
int ret;
|
|
|
|
/* Recover our private data from the inode instance */
|
|
|
|
ldir = (FAR struct littlefs_dir_s *)dir;
|
|
fs = mountpt->i_private;
|
|
|
|
/* Call the LFS's readdir function */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_dir_read(&fs->lfs, &ldir->dir, &info));
|
|
if (ret > 0)
|
|
{
|
|
if (info.type == LFS_TYPE_REG)
|
|
{
|
|
entry->d_type = DTYPE_FILE;
|
|
}
|
|
else
|
|
{
|
|
entry->d_type = DTYPE_DIRECTORY;
|
|
}
|
|
|
|
strlcpy(entry->d_name, info.name, sizeof(entry->d_name));
|
|
}
|
|
else if (ret == 0)
|
|
{
|
|
ret = -ENOENT;
|
|
}
|
|
|
|
nxmutex_unlock(&fs->lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_rewindir
|
|
*
|
|
* Description: Reset directory read to the first entry
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_rewinddir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
FAR struct littlefs_dir_s *ldir;
|
|
int ret;
|
|
|
|
/* Recover our private data from the inode instance */
|
|
|
|
ldir = (FAR struct littlefs_dir_s *)dir;
|
|
fs = mountpt->i_private;
|
|
|
|
/* Call the LFS's rewinddir function */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_dir_rewind(&fs->lfs, &ldir->dir));
|
|
|
|
nxmutex_unlock(&fs->lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_bind
|
|
*
|
|
* Description: This implements a portion of the mount operation. This
|
|
* function allocates and initializes the mountpoint private data and
|
|
* binds the driver inode to the filesystem private data. The final
|
|
* binding of the private data (containing the driver) to the
|
|
* mountpoint is performed by mount().
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_read_block(FAR const struct lfs_config *c,
|
|
lfs_block_t block, lfs_off_t off,
|
|
FAR void *buffer, lfs_size_t size)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs = c->context;
|
|
FAR struct mtd_geometry_s *geo = &fs->geo;
|
|
FAR struct inode *drv = fs->drv;
|
|
int ret;
|
|
|
|
block = (block * c->block_size + off) / geo->blocksize;
|
|
size = size / geo->blocksize;
|
|
|
|
if (INODE_IS_MTD(drv))
|
|
{
|
|
ret = MTD_BREAD(drv->u.i_mtd, block, size, buffer);
|
|
}
|
|
else
|
|
{
|
|
ret = drv->u.i_bops->read(drv, buffer, block, size);
|
|
}
|
|
|
|
return ret >= 0 ? OK : ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_write_block
|
|
****************************************************************************/
|
|
|
|
static int littlefs_write_block(FAR const struct lfs_config *c,
|
|
lfs_block_t block, lfs_off_t off,
|
|
FAR const void *buffer, lfs_size_t size)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs = c->context;
|
|
FAR struct mtd_geometry_s *geo = &fs->geo;
|
|
FAR struct inode *drv = fs->drv;
|
|
int ret;
|
|
|
|
block = (block * c->block_size + off) / geo->blocksize;
|
|
size = size / geo->blocksize;
|
|
|
|
if (INODE_IS_MTD(drv))
|
|
{
|
|
ret = MTD_BWRITE(drv->u.i_mtd, block, size, buffer);
|
|
}
|
|
else
|
|
{
|
|
ret = drv->u.i_bops->write(drv, buffer, block, size);
|
|
}
|
|
|
|
return ret >= 0 ? OK : ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_erase_block
|
|
****************************************************************************/
|
|
|
|
static int littlefs_erase_block(FAR const struct lfs_config *c,
|
|
lfs_block_t block)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs = c->context;
|
|
FAR struct inode *drv = fs->drv;
|
|
int ret = OK;
|
|
|
|
if (INODE_IS_MTD(drv))
|
|
{
|
|
FAR struct mtd_geometry_s *geo = &fs->geo;
|
|
size_t size = c->block_size / geo->erasesize;
|
|
|
|
block = block * c->block_size / geo->erasesize;
|
|
ret = MTD_ERASE(drv->u.i_mtd, block, size);
|
|
}
|
|
|
|
return ret >= 0 ? OK : ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_sync_block
|
|
****************************************************************************/
|
|
|
|
static int littlefs_sync_block(FAR const struct lfs_config *c)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs = c->context;
|
|
FAR struct inode *drv = fs->drv;
|
|
int ret;
|
|
|
|
if (INODE_IS_MTD(drv))
|
|
{
|
|
ret = MTD_IOCTL(drv->u.i_mtd, BIOC_FLUSH, 0);
|
|
}
|
|
else
|
|
{
|
|
if (drv->u.i_bops->ioctl != NULL)
|
|
{
|
|
ret = drv->u.i_bops->ioctl(drv, BIOC_FLUSH, 0);
|
|
}
|
|
else
|
|
{
|
|
ret = -ENOTTY;
|
|
}
|
|
}
|
|
|
|
return ret == -ENOTTY ? OK : ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_bind
|
|
****************************************************************************/
|
|
|
|
static int littlefs_bind(FAR struct inode *driver, FAR const void *data,
|
|
FAR void **handle)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
int ret;
|
|
|
|
/* Open the block driver */
|
|
|
|
if (INODE_IS_BLOCK(driver) && driver->u.i_bops->open)
|
|
{
|
|
ret = driver->u.i_bops->open(driver);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Create an instance of the mountpt state structure */
|
|
|
|
fs = fs_heap_zalloc(sizeof(*fs));
|
|
if (!fs)
|
|
{
|
|
ret = -ENOMEM;
|
|
goto errout_with_block;
|
|
}
|
|
|
|
/* Initialize the allocated mountpt state structure. The filesystem is
|
|
* responsible for one reference on the driver inode and does not
|
|
* have to addref() here (but does have to release in unbind().
|
|
*/
|
|
|
|
fs->drv = driver; /* Save the driver reference */
|
|
nxmutex_init(&fs->lock); /* Initialize the access control mutex */
|
|
|
|
if (INODE_IS_MTD(driver))
|
|
{
|
|
/* Get MTD geometry directly */
|
|
|
|
ret = MTD_IOCTL(driver->u.i_mtd, MTDIOC_GEOMETRY,
|
|
(unsigned long)&fs->geo);
|
|
}
|
|
else
|
|
{
|
|
/* Try to get FLT MTD geometry first */
|
|
|
|
if (driver->u.i_bops->ioctl != NULL)
|
|
{
|
|
ret = driver->u.i_bops->ioctl(driver, MTDIOC_GEOMETRY,
|
|
(unsigned long)&fs->geo);
|
|
}
|
|
else
|
|
{
|
|
ret = -ENOTTY;
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
struct geometry geometry;
|
|
|
|
/* Not FLT MTD device, get normal block geometry */
|
|
|
|
ret = driver->u.i_bops->geometry(driver, &geometry);
|
|
if (ret >= 0)
|
|
{
|
|
/* And convert to MTD geometry */
|
|
|
|
fs->geo.blocksize = geometry.geo_sectorsize;
|
|
fs->geo.erasesize = geometry.geo_sectorsize;
|
|
fs->geo.neraseblocks = geometry.geo_nsectors;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_fs;
|
|
}
|
|
|
|
/* Initialize lfs_config structure */
|
|
|
|
fs->cfg.context = fs;
|
|
fs->cfg.read = littlefs_read_block;
|
|
fs->cfg.prog = littlefs_write_block;
|
|
fs->cfg.erase = littlefs_erase_block;
|
|
fs->cfg.sync = littlefs_sync_block;
|
|
fs->cfg.read_size = fs->geo.blocksize *
|
|
CONFIG_FS_LITTLEFS_READ_SIZE_FACTOR;
|
|
fs->cfg.prog_size = fs->geo.blocksize *
|
|
CONFIG_FS_LITTLEFS_PROGRAM_SIZE_FACTOR;
|
|
fs->cfg.block_size = fs->geo.erasesize *
|
|
CONFIG_FS_LITTLEFS_BLOCK_SIZE_FACTOR;
|
|
fs->cfg.block_count = fs->geo.neraseblocks /
|
|
CONFIG_FS_LITTLEFS_BLOCK_SIZE_FACTOR;
|
|
fs->cfg.block_cycles = CONFIG_FS_LITTLEFS_BLOCK_CYCLE;
|
|
fs->cfg.cache_size = fs->geo.blocksize *
|
|
CONFIG_FS_LITTLEFS_CACHE_SIZE_FACTOR;
|
|
|
|
#if CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE == 0
|
|
fs->cfg.lookahead_size = lfs_min(lfs_alignup(fs->cfg.block_count, 64) / 8,
|
|
fs->cfg.read_size);
|
|
#else
|
|
fs->cfg.lookahead_size = CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE;
|
|
#endif
|
|
|
|
/* Then get information about the littlefs filesystem on the devices
|
|
* managed by this driver.
|
|
*/
|
|
|
|
/* Force format the device if -o forceformat */
|
|
|
|
if (data && strcmp(data, "forceformat") == 0)
|
|
{
|
|
ret = littlefs_convert_result(lfs_format(&fs->lfs, &fs->cfg));
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_fs;
|
|
}
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_mount(&fs->lfs, &fs->cfg));
|
|
if (ret < 0)
|
|
{
|
|
/* Auto format the device if -o autoformat */
|
|
|
|
if (ret != -EFAULT || !data || strcmp(data, "autoformat"))
|
|
{
|
|
goto errout_with_fs;
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_format(&fs->lfs, &fs->cfg));
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_fs;
|
|
}
|
|
|
|
/* Try to mount the device again */
|
|
|
|
ret = littlefs_convert_result(lfs_mount(&fs->lfs, &fs->cfg));
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_fs;
|
|
}
|
|
}
|
|
|
|
*handle = fs;
|
|
return OK;
|
|
|
|
errout_with_fs:
|
|
nxmutex_destroy(&fs->lock);
|
|
fs_heap_free(fs);
|
|
errout_with_block:
|
|
if (INODE_IS_BLOCK(driver) && driver->u.i_bops->close)
|
|
{
|
|
driver->u.i_bops->close(driver);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_unbind
|
|
*
|
|
* Description: This implements the filesystem portion of the umount
|
|
* operation.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_unbind(FAR void *handle, FAR struct inode **driver,
|
|
unsigned int flags)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs = handle;
|
|
FAR struct inode *drv = fs->drv;
|
|
int ret;
|
|
|
|
/* Unmount */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_unmount(&fs->lfs));
|
|
nxmutex_unlock(&fs->lock);
|
|
|
|
if (ret >= 0)
|
|
{
|
|
/* Close the block driver */
|
|
|
|
if (INODE_IS_BLOCK(drv) && drv->u.i_bops->close)
|
|
{
|
|
drv->u.i_bops->close(drv);
|
|
}
|
|
|
|
/* We hold a reference to the driver but should not but
|
|
* mucking with inodes in this context. So, we will just return
|
|
* our contained reference to the driver inode and let the
|
|
* umount logic dispose of it.
|
|
*/
|
|
|
|
if (driver)
|
|
{
|
|
*driver = drv;
|
|
}
|
|
|
|
/* Release the mountpoint private data */
|
|
|
|
nxmutex_destroy(&fs->lock);
|
|
fs_heap_free(fs);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_statfs
|
|
*
|
|
* Description: Return filesystem statistics
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
int ret;
|
|
|
|
/* Get the mountpoint private data from the inode structure */
|
|
|
|
fs = mountpt->i_private;
|
|
|
|
/* Return something for the file system description */
|
|
|
|
buf->f_type = LITTLEFS_SUPER_MAGIC;
|
|
buf->f_namelen = LFS_NAME_MAX;
|
|
buf->f_bsize = fs->cfg.block_size;
|
|
buf->f_blocks = fs->cfg.block_count;
|
|
buf->f_bfree = fs->cfg.block_count;
|
|
buf->f_bavail = fs->cfg.block_count;
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_fs_size(&fs->lfs));
|
|
if (ret > 0)
|
|
{
|
|
buf->f_bfree -= ret;
|
|
buf->f_bavail -= ret;
|
|
|
|
ret = 0;
|
|
}
|
|
|
|
nxmutex_unlock(&fs->lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_unlink
|
|
*
|
|
* Description: Remove a file
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_unlink(FAR struct inode *mountpt,
|
|
FAR const char *relpath)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
int ret;
|
|
|
|
/* Get the mountpoint private data from the inode structure */
|
|
|
|
fs = mountpt->i_private;
|
|
|
|
/* Call the LFS to perform the unlink */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_remove(&fs->lfs, relpath));
|
|
nxmutex_unlock(&fs->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_mkdir
|
|
*
|
|
* Description: Create a directory
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_mkdir(FAR struct inode *mountpt, FAR const char *relpath,
|
|
mode_t mode)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
int ret;
|
|
|
|
/* Get the mountpoint private data from the inode structure */
|
|
|
|
fs = mountpt->i_private;
|
|
|
|
/* Call LFS to do the mkdir */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = lfs_mkdir(&fs->lfs, relpath);
|
|
nxmutex_unlock(&fs->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_rmdir
|
|
*
|
|
* Description: Remove a directory
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_rmdir(FAR struct inode *mountpt, FAR const char *relpath)
|
|
{
|
|
struct stat buf;
|
|
int ret;
|
|
|
|
ret = littlefs_stat(mountpt, relpath, &buf);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (S_ISDIR(buf.st_mode))
|
|
{
|
|
return littlefs_unlink(mountpt, relpath);
|
|
}
|
|
else
|
|
{
|
|
return -ENOTDIR;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_rename
|
|
*
|
|
* Description: Rename a file or directory
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_rename(FAR struct inode *mountpt,
|
|
FAR const char *oldrelpath,
|
|
FAR const char *newrelpath)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
int ret;
|
|
|
|
/* Get the mountpoint private data from the inode structure */
|
|
|
|
fs = mountpt->i_private;
|
|
|
|
/* Call LFS to do the rename */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = littlefs_convert_result(lfs_rename(&fs->lfs, oldrelpath,
|
|
newrelpath));
|
|
nxmutex_unlock(&fs->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: littlefs_stat
|
|
*
|
|
* Description: Return information about a file or directory
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int littlefs_stat(FAR struct inode *mountpt, FAR const char *relpath,
|
|
FAR struct stat *buf)
|
|
{
|
|
FAR struct littlefs_mountpt_s *fs;
|
|
struct lfs_info info;
|
|
int ret;
|
|
|
|
memset(buf, 0, sizeof(*buf));
|
|
|
|
/* Get the mountpoint private data from the inode structure */
|
|
|
|
fs = mountpt->i_private;
|
|
|
|
/* Call the LFS to do the stat operation */
|
|
|
|
ret = nxmutex_lock(&fs->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = lfs_stat(&fs->lfs, relpath, &info);
|
|
nxmutex_unlock(&fs->lock);
|
|
|
|
if (ret >= 0)
|
|
{
|
|
/* Convert info to stat */
|
|
|
|
buf->st_mode = S_IRWXO | S_IRWXG | S_IRWXU;
|
|
if (info.type == LFS_TYPE_REG)
|
|
{
|
|
buf->st_mode |= S_IFREG;
|
|
buf->st_size = info.size;
|
|
}
|
|
else
|
|
{
|
|
buf->st_mode |= S_IFDIR;
|
|
buf->st_size = 0;
|
|
}
|
|
|
|
buf->st_blksize = fs->cfg.block_size;
|
|
buf->st_blocks = (buf->st_size + buf->st_blksize - 1) /
|
|
buf->st_blksize;
|
|
}
|
|
|
|
return ret;
|
|
}
|