/* * Copyright (c) 2023 Antmicro * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #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 iff 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);