Spell checking tools do not recognize "iff", replace with "if and only if". See https://en.wikipedia.org/wiki/If_and_only_if Signed-off-by: Pieter De Gendt <pieter.degendt@basalte.be>
662 lines
13 KiB
C
662 lines
13 KiB
C
/*
|
|
* Copyright (c) 2023 Antmicro <www.antmicro.com>
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/fs/fs.h>
|
|
#include <zephyr/fs/fs_sys.h>
|
|
#include <zephyr/fs/ext2.h>
|
|
#include <zephyr/logging/log.h>
|
|
|
|
#include "../fs_impl.h"
|
|
#include "ext2.h"
|
|
#include "ext2_impl.h"
|
|
#include "ext2_struct.h"
|
|
|
|
LOG_MODULE_DECLARE(ext2);
|
|
|
|
K_MEM_SLAB_DEFINE(file_struct_slab, sizeof(struct ext2_file), CONFIG_MAX_FILES, sizeof(void *));
|
|
|
|
/* File operations */
|
|
|
|
static int ext2_open(struct fs_file_t *filp, const char *fs_path, fs_mode_t flags)
|
|
{
|
|
int rc, ret = 0;
|
|
struct ext2_file *file;
|
|
struct ext2_data *fs = filp->mp->fs_data;
|
|
|
|
if (fs->open_files >= CONFIG_MAX_FILES) {
|
|
LOG_DBG("Too many open files");
|
|
return -EMFILE;
|
|
}
|
|
|
|
LOG_DBG("Open mode: Rd:%d Wr:%d App:%d Creat:%d",
|
|
(flags & FS_O_READ) != 0,
|
|
(flags & FS_O_WRITE) != 0,
|
|
(flags & FS_O_APPEND) != 0,
|
|
(flags & FS_O_CREATE) != 0);
|
|
|
|
const char *path = fs_impl_strip_prefix(fs_path, filp->mp);
|
|
struct ext2_lookup_args args = {
|
|
.path = path,
|
|
.inode = NULL,
|
|
.flags = LOOKUP_ARG_OPEN,
|
|
};
|
|
|
|
if (flags & FS_O_CREATE) {
|
|
args.flags |= LOOKUP_ARG_CREATE;
|
|
args.parent = NULL;
|
|
}
|
|
|
|
rc = ext2_lookup_inode(fs, &args);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* Inodes allocated by lookup. Must be freed in manually. */
|
|
struct ext2_inode *found_inode = args.inode;
|
|
|
|
/* Not NULL if FS_O_CREATE and found_inode == NULL */
|
|
struct ext2_inode *parent = args.parent;
|
|
|
|
/* File has to be created */
|
|
if (flags & FS_O_CREATE && found_inode == NULL) {
|
|
LOG_DBG("Returned from lookup & create: '%s':%d creating file: %d",
|
|
path + args.name_pos, args.name_len, found_inode == NULL);
|
|
|
|
struct ext2_inode *new_inode;
|
|
|
|
rc = ext2_inode_get(fs, 0, &new_inode);
|
|
if (rc < 0) {
|
|
ret = rc;
|
|
goto out;
|
|
}
|
|
|
|
rc = ext2_create_file(parent, new_inode, &args);
|
|
if (rc < 0) {
|
|
ext2_inode_drop(new_inode);
|
|
ret = rc;
|
|
goto out;
|
|
}
|
|
|
|
found_inode = new_inode;
|
|
}
|
|
|
|
if ((found_inode->i_mode & EXT2_S_IFMT) != EXT2_S_IFREG) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
rc = k_mem_slab_alloc(&file_struct_slab, (void **)&file, K_FOREVER);
|
|
if (rc < 0) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
file->f_inode = found_inode;
|
|
file->f_off = 0;
|
|
file->f_flags = flags & (FS_O_RDWR | FS_O_APPEND);
|
|
|
|
filp->filep = file;
|
|
|
|
ext2_inode_drop(parent);
|
|
return 0;
|
|
|
|
out:
|
|
ext2_inode_drop(found_inode);
|
|
ext2_inode_drop(parent);
|
|
return ret;
|
|
}
|
|
|
|
static int ext2_close(struct fs_file_t *filp)
|
|
{
|
|
int rc;
|
|
struct ext2_file *f = filp->filep;
|
|
|
|
rc = ext2_inode_sync(f->f_inode);
|
|
if (rc < 0) {
|
|
goto out;
|
|
}
|
|
|
|
rc = ext2_inode_drop(f->f_inode);
|
|
if (rc < 0) {
|
|
goto out;
|
|
}
|
|
|
|
k_mem_slab_free(&file_struct_slab, (void *)f);
|
|
filp->filep = NULL;
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t ext2_read(struct fs_file_t *filp, void *dest, size_t nbytes)
|
|
{
|
|
struct ext2_file *f = filp->filep;
|
|
|
|
if ((f->f_flags & FS_O_READ) == 0) {
|
|
return -EACCES;
|
|
}
|
|
|
|
ssize_t r = ext2_inode_read(f->f_inode, dest, f->f_off, nbytes);
|
|
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
f->f_off += r;
|
|
return r;
|
|
}
|
|
|
|
static ssize_t ext2_write(struct fs_file_t *filp, const void *src, size_t nbytes)
|
|
{
|
|
struct ext2_file *f = filp->filep;
|
|
|
|
if ((f->f_flags & FS_O_WRITE) == 0) {
|
|
return -EACCES;
|
|
}
|
|
|
|
if (f->f_flags & FS_O_APPEND) {
|
|
f->f_off = f->f_inode->i_size;
|
|
}
|
|
|
|
ssize_t r = ext2_inode_write(f->f_inode, src, f->f_off, nbytes);
|
|
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
|
|
f->f_off += r;
|
|
return r;
|
|
}
|
|
|
|
static int ext2_lseek(struct fs_file_t *filp, off_t off, int whence)
|
|
{
|
|
struct ext2_file *f = filp->filep;
|
|
off_t new_off = 0;
|
|
|
|
switch (whence) {
|
|
case FS_SEEK_SET:
|
|
new_off = off;
|
|
break;
|
|
|
|
case FS_SEEK_CUR:
|
|
new_off = f->f_off + off;
|
|
break;
|
|
|
|
case FS_SEEK_END:
|
|
new_off = f->f_inode->i_size + off;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* New offset not inside the file. */
|
|
if (new_off < 0 || new_off > f->f_inode->i_size) {
|
|
return -EINVAL;
|
|
}
|
|
f->f_off = new_off;
|
|
return 0;
|
|
}
|
|
|
|
static off_t ext2_tell(struct fs_file_t *filp)
|
|
{
|
|
struct ext2_file *f = filp->filep;
|
|
|
|
return f->f_off;
|
|
}
|
|
|
|
static int ext2_truncate(struct fs_file_t *filp, off_t length)
|
|
{
|
|
struct ext2_file *f = filp->filep;
|
|
|
|
if ((f->f_flags & FS_O_WRITE) == 0) {
|
|
return -EACCES;
|
|
}
|
|
|
|
int rc = ext2_inode_trunc(f->f_inode, length);
|
|
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ext2_sync(struct fs_file_t *filp)
|
|
{
|
|
struct ext2_file *f = filp->filep;
|
|
|
|
int rc = ext2_inode_sync(f->f_inode);
|
|
|
|
if (rc >= 0) {
|
|
return 0;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/* Directory operations */
|
|
|
|
static int ext2_mkdir(struct fs_mount_t *mountp, const char *name)
|
|
{
|
|
int rc, ret = 0;
|
|
struct ext2_data *fs = mountp->fs_data;
|
|
|
|
const char *path = fs_impl_strip_prefix(name, mountp);
|
|
struct ext2_lookup_args args = {
|
|
.path = path,
|
|
.inode = NULL,
|
|
.parent = NULL,
|
|
};
|
|
|
|
args.flags = LOOKUP_ARG_CREATE;
|
|
|
|
rc = ext2_lookup_inode(fs, &args);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
struct ext2_inode *found_inode = args.inode;
|
|
struct ext2_inode *parent = args.parent;
|
|
|
|
LOG_DBG("Returned from lookup & create: '%s':%d res: %d",
|
|
path + args.name_pos, args.name_len, found_inode == NULL);
|
|
|
|
if (found_inode != NULL) {
|
|
ret = -EEXIST;
|
|
goto out;
|
|
}
|
|
|
|
rc = ext2_inode_get(fs, 0, &found_inode);
|
|
if (rc < 0) {
|
|
ret = rc;
|
|
goto out;
|
|
}
|
|
|
|
rc = ext2_create_dir(parent, found_inode, &args);
|
|
if (rc < 0) {
|
|
ret = rc;
|
|
}
|
|
|
|
out:
|
|
ext2_inode_drop(parent);
|
|
ext2_inode_drop(found_inode);
|
|
return ret;
|
|
}
|
|
|
|
static int ext2_opendir(struct fs_dir_t *dirp, const char *fs_path)
|
|
{
|
|
int rc, ret = 0;
|
|
struct ext2_file *dir;
|
|
const char *path = fs_impl_strip_prefix(fs_path, dirp->mp);
|
|
struct ext2_data *fs = dirp->mp->fs_data;
|
|
struct ext2_lookup_args args = {
|
|
.path = path,
|
|
.inode = NULL,
|
|
.flags = LOOKUP_ARG_OPEN,
|
|
};
|
|
|
|
rc = ext2_lookup_inode(fs, &args);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
struct ext2_inode *found_inode = args.inode;
|
|
|
|
if (!(found_inode->i_mode & EXT2_S_IFDIR)) {
|
|
ret = -ENOTDIR;
|
|
goto out;
|
|
}
|
|
|
|
rc = k_mem_slab_alloc(&file_struct_slab, (void **)&dir, K_FOREVER);
|
|
if (rc < 0) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
|
|
if (!dir) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
dir->f_inode = found_inode;
|
|
dir->f_off = 0;
|
|
|
|
dirp->dirp = dir;
|
|
return 0;
|
|
|
|
out:
|
|
ext2_inode_drop(found_inode);
|
|
return ret;
|
|
}
|
|
|
|
static int ext2_readdir(struct fs_dir_t *dirp, struct fs_dirent *entry)
|
|
{
|
|
struct ext2_file *dir = dirp->dirp;
|
|
int rc = ext2_get_direntry(dir, entry);
|
|
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ext2_closedir(struct fs_dir_t *dirp)
|
|
{
|
|
struct ext2_file *dir = dirp->dirp;
|
|
|
|
ext2_inode_drop(dir->f_inode);
|
|
k_mem_slab_free(&file_struct_slab, (void *)dir);
|
|
return 0;
|
|
}
|
|
|
|
/* File system level operations */
|
|
|
|
#ifdef CONFIG_FILE_SYSTEM_MKFS
|
|
FS_EXT2_DECLARE_DEFAULT_CONFIG(ext2_default_cfg);
|
|
#endif
|
|
|
|
/* Superblock is used only once. Because ext2 may have only one instance at the time we could
|
|
* statically allocate this strusture.
|
|
*/
|
|
static struct ext2_disk_superblock superblock;
|
|
|
|
static int ext2_mount(struct fs_mount_t *mountp)
|
|
{
|
|
int ret = 0;
|
|
struct ext2_data *fs = NULL;
|
|
#ifdef CONFIG_FILE_SYSTEM_MKFS
|
|
bool do_format = false;
|
|
bool possible_format = (mountp->flags & FS_MOUNT_FLAG_NO_FORMAT) == 0 &&
|
|
(mountp->flags & FS_MOUNT_FLAG_READ_ONLY) == 0;
|
|
#endif
|
|
|
|
ret = ext2_init_storage(&fs, mountp->storage_dev, mountp->flags);
|
|
if (ret < 0) {
|
|
goto err;
|
|
}
|
|
|
|
fs->flags = 0;
|
|
if (mountp->flags & FS_MOUNT_FLAG_READ_ONLY) {
|
|
fs->flags |= EXT2_DATA_FLAGS_RO;
|
|
}
|
|
|
|
ret = fs->backend_ops->read_superblock(fs, &superblock);
|
|
if (ret < 0) {
|
|
goto err;
|
|
}
|
|
|
|
ret = ext2_verify_disk_superblock(&superblock);
|
|
if (ret == 0) {
|
|
fs->block_size = 1024 << superblock.s_log_block_size;
|
|
|
|
} else if (ret == -EROFS) {
|
|
fs->block_size = 1024 << superblock.s_log_block_size;
|
|
fs->flags |= EXT2_DATA_FLAGS_RO;
|
|
|
|
#ifdef CONFIG_FILE_SYSTEM_MKFS
|
|
} else if (ret == -EINVAL && possible_format) {
|
|
do_format = true;
|
|
fs->block_size = ext2_default_cfg.block_size;
|
|
#endif
|
|
|
|
} else {
|
|
goto err;
|
|
}
|
|
|
|
if (fs->block_size % fs->write_size != 0) {
|
|
LOG_ERR("Blocks size isn't multiple of sector size. (bsz: %d, ssz: %d)",
|
|
fs->block_size, fs->write_size);
|
|
ret = -ENOTSUP;
|
|
goto err;
|
|
}
|
|
|
|
ext2_init_blocks_slab(fs);
|
|
|
|
#ifdef CONFIG_FILE_SYSTEM_MKFS
|
|
if (do_format) {
|
|
LOG_INF("Formatting the storage device");
|
|
|
|
ret = ext2_format(fs, &ext2_default_cfg);
|
|
if (ret < 0) {
|
|
goto err;
|
|
}
|
|
/* We don't need to verify superblock here again. Format has succeeded hence
|
|
* superblock must be valid.
|
|
*/
|
|
}
|
|
#endif
|
|
|
|
ret = ext2_init_fs(fs);
|
|
if (ret < 0) {
|
|
goto err;
|
|
}
|
|
|
|
mountp->fs_data = fs;
|
|
return 0;
|
|
|
|
err:
|
|
ext2_close_struct(fs);
|
|
return ret;
|
|
}
|
|
|
|
#if defined(CONFIG_FILE_SYSTEM_MKFS)
|
|
|
|
static int ext2_mkfs(uintptr_t dev_id, void *vcfg, int flags)
|
|
{
|
|
int ret = 0;
|
|
struct ext2_data *fs;
|
|
struct ext2_cfg *cfg = vcfg;
|
|
|
|
if (cfg == NULL) {
|
|
cfg = &ext2_default_cfg;
|
|
}
|
|
|
|
ret = ext2_init_storage(&fs, (const void *)dev_id, flags);
|
|
if (ret < 0) {
|
|
LOG_ERR("Initialization of %ld device failed (%d)", dev_id, ret);
|
|
goto out;
|
|
}
|
|
|
|
fs->block_size = cfg->block_size;
|
|
|
|
ext2_init_blocks_slab(fs);
|
|
|
|
LOG_INF("Formatting the storage device");
|
|
ret = ext2_format(fs, cfg);
|
|
if (ret < 0) {
|
|
LOG_ERR("Format of %ld device failed (%d)", dev_id, ret);
|
|
}
|
|
|
|
out:
|
|
ext2_close_struct(fs);
|
|
return ret;
|
|
}
|
|
|
|
#endif /* CONFIG_FILE_SYSTEM_MKFS */
|
|
|
|
static int ext2_unmount(struct fs_mount_t *mountp)
|
|
{
|
|
int ret;
|
|
struct ext2_data *fs = mountp->fs_data;
|
|
|
|
ret = ext2_close_fs(fs);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ext2_close_struct(fs);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
mountp->fs_data = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static int ext2_unlink(struct fs_mount_t *mountp, const char *name)
|
|
{
|
|
int rc, ret = 0;
|
|
struct ext2_data *fs = mountp->fs_data;
|
|
|
|
const char *path = fs_impl_strip_prefix(name, mountp);
|
|
struct ext2_lookup_args args = {
|
|
.path = path,
|
|
.inode = NULL,
|
|
.parent = NULL,
|
|
};
|
|
|
|
args.flags = LOOKUP_ARG_UNLINK;
|
|
|
|
rc = ext2_lookup_inode(fs, &args);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
ret = ext2_inode_unlink(args.parent, args.inode, args.offset);
|
|
|
|
rc = ext2_inode_drop(args.parent);
|
|
if (rc < 0) {
|
|
LOG_WRN("Parent inode not dropped correctly in unlink (%d)", rc);
|
|
}
|
|
rc = ext2_inode_drop(args.inode);
|
|
if (rc < 0) {
|
|
LOG_WRN("Unlinked inode not dropped correctly in unlink (%d)", rc);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int ext2_rename(struct fs_mount_t *mountp, const char *from, const char *to)
|
|
{
|
|
int rc, ret = 0;
|
|
struct ext2_data *fs = mountp->fs_data;
|
|
|
|
LOG_DBG("Rename: %s -> %s", from, to);
|
|
|
|
const char *path_from = fs_impl_strip_prefix(from, mountp);
|
|
const char *path_to = fs_impl_strip_prefix(to, mountp);
|
|
|
|
struct ext2_lookup_args args_from = {
|
|
.path = path_from,
|
|
.inode = NULL,
|
|
.parent = NULL,
|
|
.flags = LOOKUP_ARG_UNLINK,
|
|
};
|
|
|
|
struct ext2_lookup_args args_to = {
|
|
.path = path_to,
|
|
.inode = NULL,
|
|
.parent = NULL,
|
|
.flags = LOOKUP_ARG_CREATE,
|
|
};
|
|
|
|
rc = ext2_lookup_inode(fs, &args_from);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = ext2_lookup_inode(fs, &args_to);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
if (args_to.inode != NULL) {
|
|
/* Replace existing directory entry with new one. */
|
|
ret = ext2_replace_file(&args_from, &args_to);
|
|
} else {
|
|
/* Moving to new location */
|
|
ret = ext2_move_file(&args_from, &args_to);
|
|
}
|
|
|
|
ext2_inode_drop(args_from.inode);
|
|
ext2_inode_drop(args_from.parent);
|
|
ext2_inode_drop(args_to.inode);
|
|
ext2_inode_drop(args_to.parent);
|
|
return ret;
|
|
}
|
|
|
|
static int ext2_stat(struct fs_mount_t *mountp, const char *path, struct fs_dirent *entry)
|
|
{
|
|
int rc;
|
|
struct ext2_data *fs = mountp->fs_data;
|
|
|
|
path = fs_impl_strip_prefix(path, mountp);
|
|
|
|
struct ext2_lookup_args args = {
|
|
.path = path,
|
|
.parent = NULL,
|
|
.flags = LOOKUP_ARG_STAT,
|
|
};
|
|
|
|
rc = ext2_lookup_inode(fs, &args);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
uint32_t offset = args.offset;
|
|
struct ext2_inode *parent = args.parent;
|
|
struct ext2_file dir = {.f_inode = parent, .f_off = offset};
|
|
|
|
rc = ext2_get_direntry(&dir, entry);
|
|
|
|
ext2_inode_drop(parent);
|
|
ext2_inode_drop(args.inode);
|
|
return rc;
|
|
}
|
|
|
|
static int ext2_statvfs(struct fs_mount_t *mountp, const char *path, struct fs_statvfs *stat)
|
|
{
|
|
ARG_UNUSED(path);
|
|
struct ext2_data *fs = mountp->fs_data;
|
|
|
|
stat->f_bsize = fs->block_size;
|
|
stat->f_frsize = fs->block_size;
|
|
stat->f_blocks = fs->sblock.s_blocks_count;
|
|
stat->f_bfree = fs->sblock.s_free_blocks_count;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* File system interface */
|
|
|
|
static const struct fs_file_system_t ext2_fs = {
|
|
.open = ext2_open,
|
|
.close = ext2_close,
|
|
.read = ext2_read,
|
|
.write = ext2_write,
|
|
.lseek = ext2_lseek,
|
|
.tell = ext2_tell,
|
|
.truncate = ext2_truncate,
|
|
.sync = ext2_sync,
|
|
.mkdir = ext2_mkdir,
|
|
.opendir = ext2_opendir,
|
|
.readdir = ext2_readdir,
|
|
.closedir = ext2_closedir,
|
|
.mount = ext2_mount,
|
|
.unmount = ext2_unmount,
|
|
.unlink = ext2_unlink,
|
|
.rename = ext2_rename,
|
|
.stat = ext2_stat,
|
|
.statvfs = ext2_statvfs,
|
|
#if defined(CONFIG_FILE_SYSTEM_MKFS)
|
|
.mkfs = ext2_mkfs,
|
|
#endif
|
|
};
|
|
|
|
static int ext2_init(void)
|
|
{
|
|
int rc = fs_register(FS_EXT2, &ext2_fs);
|
|
|
|
if (rc < 0) {
|
|
LOG_WRN("Ext2 register error (%d)\n", rc);
|
|
} else {
|
|
LOG_DBG("Ext2 fs registered\n");
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
SYS_INIT(ext2_init, POST_KERNEL, 99);
|