// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace purefs::fs::drivers { namespace { template auto invoke_efs(filesystem_ext4::fsfile zfil, T efs_fun, Args &&...args) { auto vfile = std::dynamic_pointer_cast(zfil); if (!vfile) { LOG_ERROR("Non ext4 filesystem file pointer"); return -EBADF; } auto mntp = std::static_pointer_cast(vfile->mntpoint()); if (!mntp) { LOG_ERROR("Non ext4 mount point"); return -EBADF; } ext4_locker _lck(mntp); auto err = efs_fun(vfile->filp(), std::forward(args)...); return -err; } template auto invoke_efs(filesystem_ext4::fsmount mnt, T efs_fun, std::string_view oldpath, std::string_view newpath) { auto mntp = std::static_pointer_cast(mnt); if (!mntp) { LOG_ERROR("Non ext4 mount point"); return -EBADF; } const auto native_old = mntp->native_path(oldpath); const auto native_new = mntp->native_path(newpath); ext4_locker _lck(mntp); auto err = efs_fun(native_old.c_str(), native_new.c_str()); return -err; } template auto invoke_efs(filesystem_ext4::fsmount fmnt, T efs_fun, std::string_view path, Args &&...args) { auto mntp = std::static_pointer_cast(fmnt); if (!mntp) { LOG_ERROR("Non ext4 mount point"); return -EBADF; } const auto native_path = mntp->native_path(path); ext4_locker _lck(mntp); auto err = efs_fun(native_path.c_str(), std::forward(args)...); return -err; } template auto invoke_efs(filesystem_ext4::fsdir zdir, T lfs_fun, Args &&...args) { auto vdir = std::dynamic_pointer_cast(zdir); if (!vdir) { LOG_ERROR("Non ext4 filesystem directory pointer"); return -EBADF; } auto mntp = std::static_pointer_cast(vdir->mntpoint()); if (!mntp) { LOG_ERROR("Non ext4 mount point"); return -EBADF; } ext4_locker _lck(mntp); auto err = lfs_fun(vdir->dirp(), std::forward(args)...); return err; } inline auto ino_to_st_mode(int dtype) { switch (dtype) { case EXT4_DE_REG_FILE: return S_IFREG; case EXT4_DE_DIR: return S_IFDIR; case EXT4_DE_CHRDEV: return S_IFCHR; case EXT4_DE_BLKDEV: return S_IFBLK; case EXT4_DE_FIFO: return S_IFIFO; case EXT4_DE_SOCK: return S_IFSOCK; default: return 0; } } } // namespace auto filesystem_ext4::mount_prealloc(std::shared_ptr diskh, std::string_view path, unsigned flags) -> fsmount { return std::make_shared(diskh, path, flags, shared_from_this()); } auto filesystem_ext4::mount(fsmount mnt, const void *data) noexcept -> int { auto disk = mnt->disk(); if (!disk) { return -EIO; } auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non ext4 mount point"); return -EIO; } ext4_locker _lck(vmnt); auto [bd, err] = ext4::internal::append_volume(disk_mngr(), disk); if (err) { LOG_ERROR("Unable to append volume err: %i", err); return err; } /** If verbosed lwext4 debug is required please uncomment * this line. It may cause to print a lot of messages * especially when the ext4 journal is recovered * on the log output device so it is disabled by default */ // ext4_dmask_set(DEBUG_ALL); err = ext4_device_register(bd, disk->name().c_str()); if (err) { LOG_ERROR("Unable to register device with err: %i", err); ext4::internal::remove_volume(bd); return -err; } const auto mnt_path = vmnt->mount_path(); // Mount err = ext4_mount(disk->name().c_str(), mnt_path.c_str(), 0); if (err) { LOG_ERROR("Unable to mount ext4 errno %i", err); ext4_device_unregister(disk->name().c_str()); ext4::internal::remove_volume(bd); return -err; } // Start ext4 recover err = ext4_recover(mnt_path.c_str()); if (err) { LOG_ERROR("Ext4 recover failed errno %i", err); ext4_umount(mnt_path.c_str()); ext4_device_unregister(disk->name().c_str()); ext4::internal::remove_volume(bd); return -err; } // Start journaling err = ext4_journal_start(mnt_path.c_str()); if (err) { LOG_WARN("Unable to start journalling errno %i", err); } err = ext4_block_cache_write_back(bd, true); if (err) { LOG_ERROR("Unable to switch to write back mode errno %i", err); ext4_umount(mnt_path.c_str()); ext4_device_unregister(disk->name().c_str()); ext4::internal::remove_volume(bd); return -err; } filesystem_operations::mount(mnt, data); vmnt->block_dev(bd); return err; } // Unmount filesystem auto filesystem_ext4::umount(fsmount mnt) noexcept -> int { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non ext4 mount point"); return -EIO; } ext4_locker _lck(vmnt); auto err = ext4_cache_write_back(mnt->mount_path().c_str(), false); if (err) { LOG_WARN("Unable to disable cache wb errno %i", err); err = 0; } err = ext4_journal_stop(mnt->mount_path().c_str()); if (err) { LOG_WARN("Unable to stop ext4 journal %i", err); err = 0; } err = ext4_umount(mnt->mount_path().c_str()); if (err) { LOG_ERROR("Unable to umount device"); return -err; } //! NOTE: Bug in the lib it always return ENOENT ext4_device_unregister(vmnt->disk()->name().c_str()); err = ext4::internal::remove_volume(vmnt->block_dev()); if (err) { LOG_ERROR("Remove volume error %i", err); return err; } filesystem_operations::umount(mnt); return err; } // Stat the filesystem auto filesystem_ext4::stat_vfs(fsmount mnt, std::string_view path, struct statvfs &stat) const noexcept -> int { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non ext4 mount point"); return -EIO; } ext4_locker _lck(vmnt); ext4_mount_stats estats; auto err = ext4_mount_point_stats(vmnt->mount_path().c_str(), &estats); if (err) { LOG_ERROR("Mount point stats error %i", err); return -err; } std::memset(&stat, 0, sizeof stat); stat.f_bsize = estats.block_size; stat.f_frsize = estats.block_size; stat.f_blocks = estats.blocks_count; stat.f_bfree = estats.free_blocks_count; stat.f_bavail = stat.f_bfree; stat.f_files = estats.inodes_count; stat.f_ffree = estats.free_inodes_count; stat.f_favail = stat.f_ffree; stat.f_flag = vmnt->flags(); stat.f_namemax = PATH_MAX; return err; } auto filesystem_ext4::open(fsmount mnt, std::string_view path, int flags, int mode) noexcept -> fsfile { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non ext4 mount point"); return nullptr; } ext4_locker _lck(vmnt); const auto fspath = vmnt->native_path(path); auto filp = std::make_shared(mnt, fspath, flags); auto err = ext4_fopen2(filp->filp(), fspath.c_str(), flags); filp->error(-err); return filp; } auto filesystem_ext4::close(fsfile zfile) noexcept -> int { return -invoke_efs(zfile, ::ext4_fclose); } auto filesystem_ext4::write(fsfile zfile, const char *ptr, size_t len) noexcept -> ssize_t { size_t n_written; auto err = invoke_efs(zfile, ::ext4_fwrite, ptr, len, &n_written); return (err) ? (-err) : (n_written); } auto filesystem_ext4::read(fsfile zfile, char *ptr, size_t len) noexcept -> ssize_t { size_t n_read; auto err = invoke_efs(zfile, ::ext4_fread, ptr, len, &n_read); return (err) ? (-err) : (n_read); } auto filesystem_ext4::seek(fsfile zfile, off_t pos, int dir) noexcept -> off_t { auto vfile = std::dynamic_pointer_cast(zfile); if (!vfile) { LOG_ERROR("Non ext4 filesystem file pointer"); return -EBADF; } auto mntp = std::static_pointer_cast(vfile->mntpoint()); if (!mntp) { LOG_ERROR("Non ext4 mount point"); return -EBADF; } ext4_locker _lck(mntp); auto err = ext4_fseek(vfile->filp(), pos, dir); if (err) { return -err; } auto rpos = ext4_ftell(vfile->filp()); return rpos; } auto filesystem_ext4::stat(const char *mount_point, const char *path, struct stat *st, bool ro) noexcept -> int { uint32_t inonum; ext4_inode ino; ext4_sblock *sb; auto err = ext4_raw_inode_fill(path, &inonum, &ino); if (err) { return -err; } err = ext4_get_sblock(mount_point, &sb); if (err) { return -err; } std::memset(st, 0, sizeof(*st)); st->st_ino = inonum; const auto btype = ext4_inode_type(sb, &ino); st->st_mode = ext4_inode_get_mode(sb, &ino) | ino_to_st_mode(btype); if (ro) { st->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); } // Update file type st->st_nlink = ext4_inode_get_links_cnt(&ino); st->st_uid = ext4_inode_get_uid(&ino); st->st_gid = ext4_inode_get_gid(&ino); st->st_blocks = ext4_inode_get_blocks_count(sb, &ino); st->st_size = ext4_inode_get_size(sb, &ino); st->st_blksize = ext4_sb_get_block_size(sb); st->st_dev = ext4_inode_get_dev(&ino); return err; } auto filesystem_ext4::fstat(fsfile zfile, struct stat &st) noexcept -> int { auto vfile = std::dynamic_pointer_cast(zfile); if (!vfile) { LOG_ERROR("Non ext4 filesystem file pointer"); return -EBADF; } auto mntp = std::static_pointer_cast(vfile->mntpoint()); if (!mntp) { LOG_ERROR("Non ext4 mount point"); return -EBADF; } const auto path = vfile->open_path(); ext4_locker _lck(mntp); return stat(mntp->mount_path().c_str(), vfile->open_path().c_str(), &st, zfile->mntpoint()->is_ro()); } auto filesystem_ext4::stat(fsmount mnt, std::string_view file, struct stat &st) noexcept -> int { auto mntp = std::dynamic_pointer_cast(mnt); if (!mntp) { LOG_ERROR("Non ext4 mount point"); return -EBADF; } // NOTE: mod path fix for lwext4 open root dir auto npath = mntp->native_path(file); if (npath.empty()) { npath = mntp->mount_path(); } ext4_locker _lck(mntp); return stat(mntp->mount_path().c_str(), npath.c_str(), &st, mntp->is_ro()); } auto filesystem_ext4::link(fsmount mnt, std::string_view existing, std::string_view newlink) noexcept -> int { return invoke_efs(mnt, ::ext4_flink, existing, newlink); } auto filesystem_ext4::symlink(fsmount mnt, std::string_view existing, std::string_view newlink) noexcept -> int { return invoke_efs(mnt, ::ext4_fsymlink, existing, newlink); } auto filesystem_ext4::unlink(fsmount mnt, std::string_view name) noexcept -> int { return invoke_efs( mnt, [](const char *path) { if (ext4_inode_exist(path, EXT4_DE_DIR) == 0) { LOG_WARN("rmdir syscall instead of unlink is recommended for remove directory"); return -ext4_dir_rm(path); } else { return -ext4_fremove(path); } }, name); } auto filesystem_ext4::rmdir(fsmount mnt, std::string_view name) noexcept -> int { return invoke_efs(mnt, ::ext4_dir_rm, name); } auto filesystem_ext4::rename(fsmount mnt, std::string_view oldname, std::string_view newname) noexcept -> int { return invoke_efs(mnt, ::ext4_frename, oldname, newname); } auto filesystem_ext4::mkdir(fsmount mnt, std::string_view path, int mode) noexcept -> int { return invoke_efs(mnt, ::ext4_dir_mk, path); } auto filesystem_ext4::diropen(fsmount mnt, std::string_view path) noexcept -> fsdir { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non ext4 mount point"); return nullptr; } // NOTE: mod path fix for lwext4 open root dir auto fspath = vmnt->native_path(path); if (fspath.empty()) { fspath = vmnt->mount_path(); } const auto dirp = std::make_shared(mnt, 0); ext4_locker _lck(vmnt); const auto lret = ext4_dir_open(dirp->dirp(), fspath.c_str()); dirp->error(-lret); return dirp; } auto filesystem_ext4::dirreset(fsdir dirstate) noexcept -> int { return invoke_efs(dirstate, [](auto arg) { ::ext4_dir_entry_rewind(arg); return 0; }); } auto filesystem_ext4::dirnext(fsdir dirstate, std::string &filename, struct stat &st) -> int { const ext4_direntry *dentry{}; const auto err = invoke_efs(dirstate, [&dentry](auto arg) { dentry = ext4_dir_entry_next(arg); return dentry ? 0 : -ENODATA; }); if (!err) { std::memset(&st, 0, sizeof(st)); st.st_ino = dentry->inode; st.st_mode = ino_to_st_mode(dentry->inode_type); filename = std::string(reinterpret_cast(dentry->name), dentry->name_length); } return err; } auto filesystem_ext4::dirclose(fsdir dirstate) noexcept -> int { return invoke_efs(dirstate, ::ext4_dir_close); } auto filesystem_ext4::isatty(fsfile zfile) noexcept -> int { return 0; } auto filesystem_ext4::chmod(fsmount mnt, std::string_view path, mode_t mode) noexcept -> int { return invoke_efs(mnt, ::ext4_mode_set, path, mode); } auto filesystem_ext4::fchmod(fsfile zfile, mode_t mode) noexcept -> int { auto vfile = std::dynamic_pointer_cast(zfile); if (!vfile) { LOG_ERROR("Non ext4 filesystem file pointer"); return -EBADF; } return invoke_efs(vfile->mntpoint(), ::ext4_mode_set, vfile->open_path().c_str(), mode); } auto filesystem_ext4::ftruncate(fsfile zfile, off_t len) noexcept -> int { return invoke_efs( zfile, [](ext4_file *file, uint64_t length) { int err = ext4_ftruncate(file, length); if (err == ENOTSUP) { // NOTE: Ext4 ftruncate supports only shrinking const size_t zbuf_len = 8192; auto buf = std::make_unique(zbuf_len); err = 0; for (size_t n = 0; n < length / zbuf_len; ++n) { err = ext4_fwrite(file, buf.get(), zbuf_len, nullptr); if (err) { err = -err; break; } } if (!err) { const ssize_t remain = length % zbuf_len; if (remain > 0) { err = -ext4_fwrite(file, buf.get(), remain, nullptr); } } } return err; }, len); } auto filesystem_ext4::fsync(fsfile zfile) noexcept -> int { auto vfile = std::dynamic_pointer_cast(zfile); if (!vfile) { LOG_ERROR("Non ext4 filesystem file pointer"); return -EBADF; } return invoke_efs(vfile->mntpoint(), ::ext4_cache_flush, vfile->mntpoint()->mount_path().c_str()); } } // namespace purefs::fs::drivers