~aleteoryx/muditaos

ca75ebe4499b738d5b0e83fa07840ec013522abd — Lucjan Bryndza 4 years ago 6679e50
[EGD-7587] Change user partition to ext4 fs

Add support for the EXT4 filesystem using LWEXT4
Add minor VFS fixes
Add unittest for the EXT4 partitions
Reformat partition for use EXT4 filesystem

[EGD-7587] Initial cmake of the ext4

[EGD-7587] Block driver for the ext4

Initial version of the block driver for the ext4 device

[EGD-7587] Initial version of the EXT4 fs support

Initial version of the ext4 filesystem support

Signed-off-by: Lucjan Bryndza <lucjan.bryndza@mudita.com>

[EGD-7587] Mount unmount initial unit tests

Mount and unmount initial unit tests

[EGD-7587] Unit tests and fixes for ext4

Unit tests and fixes for the ext4 partition

[EGD-7587[ adjust flags

Adjust ext configuration flags

[EGD-7587] Change to generate ext4 image

Change image generation script to generate ext4 image

[EGD-7584] Change cache memory settings

Change cache memory settings

[EGD-7587] Change create image flags

Change create image flags

Signed-off-by: Lucjan Bryndza <lucjan.bryndza@mudita.com>

[EGD-7587] Git change lwext4 to the mudita version

Change LWEXT4 to mudita version

[EGD-7587] Modify ext4 implementation

Modify ext4 implementation according to the new lwext4
mudita branch. Some issues were fixed in this branch

Signed-off-by: Lucjan Bryndza <lucjan.bryndza@mudita.com>

[EGD-7587] Add to VFS rmdir syscall

Add to VFS missing rmdir for compliance POSIX std

Signed-off-by: Lucjan Bryndza <lucjan.bryndza@mudita.com>
35 files changed, 1527 insertions(+), 39 deletions(-)

M .gitmodules
M board/linux/libiosyscalls/src/syscalls_posix.cpp
M board/rt1051/newlib/io_syscalls.cpp
M module-platform/linux/tests/CMakeLists.txt
A module-platform/linux/tests/genext4diskimg.sh
M module-platform/linux/tests/unittest_filesystem_core.cpp
A module-platform/linux/tests/unittest_filesystem_ext4.cpp
M module-platform/linux/tests/unittest_filesystem_littlefs.cpp
M module-vfs/CMakeLists.txt
M module-vfs/board/rt1051/newlib/vfs_io_syscalls.cpp
A module-vfs/drivers/include/purefs/fs/drivers/directory_handle_ext4.hpp
A module-vfs/drivers/include/purefs/fs/drivers/file_handle_ext4.hpp
M module-vfs/drivers/include/purefs/fs/drivers/file_handle_littlefs.hpp
A module-vfs/drivers/include/purefs/fs/drivers/filesystem_ext4.hpp
M module-vfs/drivers/include/purefs/fs/drivers/filesystem_littlefs.hpp
M module-vfs/drivers/include/purefs/fs/drivers/filesystem_vfat.hpp
A module-vfs/drivers/include/purefs/fs/drivers/mount_point_ext4.hpp
A module-vfs/drivers/include/thirdparty/lwext4/ext4_bdev.hpp
A module-vfs/drivers/src/purefs/fs/filesystem_ext4.cpp
M module-vfs/drivers/src/purefs/fs/filesystem_littlefs.cpp
M module-vfs/drivers/src/purefs/fs/filesystem_vfat.cpp
A module-vfs/drivers/src/purefs/fs/mount_point_ext4.cpp
A module-vfs/drivers/src/thirdparty/lwext4/ext4_bdev.cpp
M module-vfs/include/user/newlib/vfs_io_syscalls.hpp
M module-vfs/include/user/purefs/fs/filesystem.hpp
M module-vfs/include/user/purefs/fs/filesystem_operations.hpp
M module-vfs/src/purefs/fs/filesystem.cpp
M module-vfs/src/purefs/fs/filesystem_operations.cpp
M module-vfs/src/purefs/fs/filesystem_syscalls.cpp
M module-vfs/src/purefs/vfs_subsystem.cpp
M third-party/CMakeLists.txt
A third-party/lwext4/CMakeLists.txt
A third-party/lwext4/lwext4
A third-party/lwext4/lwext4_macros.cmake
M tools/generate_image.sh
M .gitmodules => .gitmodules +4 -0
@@ 95,3 95,7 @@
[submodule "klib"]
	path = third-party/klib/src
	url = https://github.com/attractivechaos/klib.git
[submodule "third-party/lwext4/lwext4"]
	path = third-party/lwext4/lwext4
	url = ../lwext4.git
	branch = mudita

M board/linux/libiosyscalls/src/syscalls_posix.cpp => board/linux/libiosyscalls/src/syscalls_posix.cpp +21 -4
@@ 25,6 25,7 @@ namespace
    {
        __REAL_DECL(link);
        __REAL_DECL(unlink);
        __REAL_DECL(rmdir);
        __REAL_DECL(symlink);

        __REAL_DECL(fcntl);


@@ 82,6 83,7 @@ namespace
    {
        __REAL_DLSYM(link);
        __REAL_DLSYM(unlink);
        __REAL_DLSYM(rmdir);
        __REAL_DLSYM(symlink);

        __REAL_DLSYM(fcntl);


@@ 130,10 132,10 @@ namespace
        __REAL_DLSYM(poll);
        __REAL_DLSYM(statvfs);

        if (!(real::link && real::unlink && real::symlink && real::fcntl && real::chdir && real::fchdir &&
              real::getcwd && real::getwd && real::get_current_dir_name && real::mkdir && real::chmod && real::fchmod &&
              real::fsync && real::fdatasync && real::read && real::write && real::lseek && real::lseek64 &&
              real::mount && real::umount && real::ioctl && real::poll && real::statvfs
        if (!(real::link && real::unlink && real::rmdir && real::symlink && real::fcntl && real::chdir &&
              real::fchdir && real::getcwd && real::getwd && real::get_current_dir_name && real::mkdir && real::chmod &&
              real::fchmod && real::fsync && real::fdatasync && real::read && real::write && real::lseek &&
              real::lseek64 && real::mount && real::umount && real::ioctl && real::poll && real::statvfs
#if __GLIBC__ > 2 || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 28))
              && real::fcntl64
#endif


@@ 286,6 288,21 @@ extern "C"
    }
    __asm__(".symver _iosys_unlink,unlink@GLIBC_2.2.5");

    int _iosys_rmdir(const char *name)
    {
        if (vfs::redirect_to_image(name)) {
            TRACE_SYSCALLN("(%s) -> VFS", name);
            return vfs::invoke_fs(&fs::rmdir, name);
        }
        else {
            TRACE_SYSCALLN("(%s) -> linux fs", name);
            char tmp[PATH_MAX];
            const auto path = vfs::npath_translate(name, tmp);
            return real::rmdir(path);
        }
    }
    __asm__(".symver _iosys_rmdir,rmdir@GLIBC_2.2.5");

    int _iosys_fcntl(int fd, int cmd, ... /* arg */)
    {
        if (vfs::is_image_fd(fd)) {

M board/rt1051/newlib/io_syscalls.cpp => board/rt1051/newlib/io_syscalls.cpp +4 -1
@@ 57,7 57,10 @@ extern "C"
    {
        return syscalls::stat(r->_errno, file, pstat);
    }

    int rmdir(const char *path)
    {
        return syscalls::rmdir(_REENT->_errno, path);
    }
    /** POSIX directory related funcs */
    int chdir(const char *path)
    {

M module-platform/linux/tests/CMakeLists.txt => module-platform/linux/tests/CMakeLists.txt +18 -0
@@ 37,6 37,13 @@ add_custom_target(
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    DEPENDS genlittlefs test-assets
)
set(EXT4_IMAGE "ext4test.img")
add_custom_target(
    ${EXT4_IMAGE}
    COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/genext4diskimg.sh 1G ${EXT4_IMAGE} ${CMAKE_BINARY_DIR}/module-platform/test_dir
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    DEPENDS genlittlefs test-assets
)

add_catch2_executable(
    NAME vfs-littlefs


@@ 51,6 58,17 @@ add_catch2_executable(
)

add_catch2_executable(
    NAME vfs-ext4
    SRCS
        ${CMAKE_CURRENT_LIST_DIR}/unittest_filesystem_ext4.cpp
    LIBS
        platform
    DEPS
        ${EXT4_IMAGE}
    USE_FS
)

add_catch2_executable(
    NAME vfs-dualmount
    SRCS
        ${CMAKE_CURRENT_LIST_DIR}/unittest_filesystem_dualmount.cpp

A module-platform/linux/tests/genext4diskimg.sh => module-platform/linux/tests/genext4diskimg.sh +62 -0
@@ 0,0 1,62 @@
#!/bin/bash -e
#Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
#For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

usage() {
cat << ==usage
Usage: $(basename $0) [image_size] [image_file] [sysroot]
	image_size Target disk image size in bytes
	image_file Target image name
        sysroot    Source directory with files
==usage
}

if [ $# -lt 2 ]; then
	echo "Error! Invalid argument count"
	usage
	exit -1
fi
IMAGE_SIZE="$1"
IMAGE_FILE="$2"
SYSROOT="$3"
shift 3

if [ ! -d "${SYSROOT}" ]; then
	echo "Invalid sysroot: ${SYSROOT}"
	exit -1
fi

_REQ_CMDS="sfdisk truncate mke2fs"
for cmd in $_REQ_CMDS; do
	if [ ! $(command -v $cmd) ]; then
		echo "Error! $cmd is not installed, please use 'sudo apt install' for install required tool"
		exit -1
	fi
done
truncate -s $IMAGE_SIZE $IMAGE_FILE

SECTOR_START=2048
SECTOR_END=$(( $(stat -c "%s" $IMAGE_FILE)/512 - $SECTOR_START))

sfdisk $IMAGE_FILE << ==sfdisk
label: dos
unit: sectors

/dev/sdz1 : start=$SECTOR_START, size=$SECTOR_END, type=83
==sfdisk

#Generate image
mke2fs \
  -F \
  -L 'user' \
  -N 0 \
  -E offset=$(($SECTOR_START*512)) \
  -O ^64bit \
  -O ^flex_bg \
  -O ^metadata_csum \
  -d "${SYSROOT}" \
  -m 0 \
  -r 1 \
  -t ext4 \
  "$IMAGE_FILE" \
;

M module-platform/linux/tests/unittest_filesystem_core.cpp => module-platform/linux/tests/unittest_filesystem_core.cpp +8 -4
@@ 134,7 134,7 @@ TEST_CASE("Corefs: Create new file, write, read from it")
    fscore.write(hwnd, text.c_str(), text.size());

    REQUIRE(fscore.close(hwnd) == 0);
    SECTION("Read from file")

    {
        int hwnd = fscore.open("/sys/test.txt", O_RDONLY, 0);
        REQUIRE(hwnd >= 3);


@@ 142,10 142,8 @@ TEST_CASE("Corefs: Create new file, write, read from it")
        REQUIRE(fscore.read(hwnd, buf, sizeof(buf)) == 4);
        REQUIRE(strcmp(buf, text.c_str()) == 0);
        fscore.close(hwnd);
        REQUIRE(fscore.umount("/sys") == 0);
    }

    SECTION("Test seek file")
    {
        int hwnd = fscore.open("/sys/test.txt", O_RDONLY, 0);
        REQUIRE(hwnd >= 3);


@@ 155,8 153,14 @@ TEST_CASE("Corefs: Create new file, write, read from it")
        REQUIRE(fscore.read(hwnd, buf, sizeof(buf)) == 0);
        REQUIRE(fscore.seek(hwnd, 0, SEEK_SET) == 0);
        fscore.close(hwnd);
        REQUIRE(fscore.umount("/sys") == 0);
    }

    {
        REQUIRE(fscore.rmdir("/sys/test23") == -ENOENT);
        REQUIRE(fscore.mkdir("/sys/testdirxyzk", 0666) == 0);
        REQUIRE(fscore.rmdir("/sys/testdirxyzk") == 0);
    }
    REQUIRE(fscore.umount("/sys") == 0);
}

TEST_CASE("Corefs: Register null filesystem")

A module-platform/linux/tests/unittest_filesystem_ext4.cpp => module-platform/linux/tests/unittest_filesystem_ext4.cpp +271 -0
@@ 0,0 1,271 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>

#include <platform/linux/DiskImage.hpp>

#include <purefs/fs/filesystem.hpp>
#include <purefs/blkdev/disk_manager.hpp>
#include <purefs/fs/drivers/filesystem_ext4.hpp>
#include <sys/statvfs.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>

namespace
{
    constexpr auto disk_image = "ext4test.img";

    auto prepare_filesystem(std::string_view dev_name)
        -> std::pair<std::unique_ptr<purefs::fs::filesystem>, std::shared_ptr<purefs::blkdev::disk_manager>>
    {
        using namespace purefs;

        auto dm   = std::make_shared<blkdev::disk_manager>();
        auto disk = std::make_shared<blkdev::disk_image>(disk_image);

        if (dm->register_device(disk, dev_name) != 0) {
            return {};
        }

        auto fs_core       = std::make_unique<fs::filesystem>(dm);
        const auto vfs_lfs = std::make_shared<fs::drivers::filesystem_ext4>();

        if (fs_core->register_filesystem("ext4", vfs_lfs) != 0) {
            return {};
        }

        return std::make_pair(std::move(fs_core), std::move(dm));
    }
} // namespace

TEST_CASE("ext4: Basic mount and functionality")
{
    using namespace purefs;

    auto dm   = std::make_shared<blkdev::disk_manager>();
    auto disk = std::make_shared<blkdev::disk_image>(disk_image);
    REQUIRE(disk);
    REQUIRE(dm->register_device(disk, "emmc0") == 0);

    fs::filesystem fscore(dm);
    const auto vfs_ext = std::make_shared<fs::drivers::filesystem_ext4>();
    REQUIRE(vfs_ext->mount_count() == 0);
    REQUIRE(fscore.register_filesystem("ext4", vfs_ext) == 0);

    REQUIRE(fscore.mount("emmc0part0", "/sys", "ext4") == 0);
    REQUIRE(vfs_ext->mount_count() == 1);

    REQUIRE(fscore.mount("dummy0part0", "/dummy", "ext4") == -ENXIO);
    REQUIRE(fscore.umount("/ala") == -ENOENT);
    REQUIRE(fscore.mount("emmc0part0", "/sys", "vfat") == -EBUSY);
    REQUIRE(fscore.mount("emmc0part0", "/path", "vfat") == -EBUSY);
    struct statvfs ssv;
    REQUIRE(fscore.stat_vfs("/sys/", ssv) == 0);

    REQUIRE(fscore.umount("/sys") == 0);
    REQUIRE(vfs_ext->mount_count() == 0);

    REQUIRE(fscore.mount("emmc0part0", "/path", "ext4") == 0);
    REQUIRE(vfs_ext->mount_count() == 1);
    REQUIRE(fscore.umount("/path") == 0);
}

TEST_CASE("ext4: Read tests")
{
    auto [fs_core, dm] = prepare_filesystem("emmc0");
    REQUIRE(fs_core);
    REQUIRE(fs_core->mount("emmc0part0", "/sys", "ext4") == 0);

    const auto fd = fs_core->open("/sys/test_read_1.txt", O_RDONLY, 0);
    REQUIRE(fd >= 3);

    static char buf[64];

    REQUIRE(fs_core->read(fd, buf, 8) == 8);
    REQUIRE(memcmp(buf, "01234567", 8) == 0);
    REQUIRE(fs_core->seek(fd, 4, SEEK_SET) == 4);
    REQUIRE(fs_core->read(fd, buf, 8) == 8);
    REQUIRE(memcmp(buf, "456789AB", 8) == 0);
    struct stat st;
    REQUIRE(fs_core->fstat(fd, st) == 0);
    REQUIRE(st.st_mode & S_IFREG);
    REQUIRE((st.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == (S_IRUSR | S_IRGRP | S_IROTH));

    REQUIRE(fs_core->close(fd) == 0);
    REQUIRE(fs_core->umount("/sys") == 0);
}

TEST_CASE("ext4: Write tests")
{
    auto [fs_core, dm] = prepare_filesystem("emmc0");
    REQUIRE(fs_core);
    REQUIRE(fs_core->mount("emmc0part0", "/sys", "ext4") == 0);

    static constexpr auto filename = "/sys/test_write_tmp_1.txt";
    auto fd                        = fs_core->open(filename, O_CREAT | O_RDWR, 0);
    REQUIRE(fd >= 3);

    const std::string str = "Hello, littlefs!";
    REQUIRE(fs_core->write(fd, str.c_str(), str.length()) == static_cast<ssize_t>(str.length()));

    // reopen the file to flush write buffers
    REQUIRE(fs_core->close(fd) == 0);
    fd = fs_core->open(filename, O_RDONLY, 0);
    REQUIRE(fd >= 3);

    static char buf[64];
    REQUIRE(fs_core->read(fd, buf, str.length()) == static_cast<ssize_t>(str.length()));
    REQUIRE(memcmp(buf, str.c_str(), str.length()) == 0);

    REQUIRE(fs_core->close(fd) == 0);
    REQUIRE(fs_core->unlink(filename) == 0);

    // Test the ftruncate
    static constexpr auto trunc_fname = "/sys/test_truncate.bin";
    static auto trunc_fsize           = 1024 * 256 + 4;
    fd                                = fs_core->open(trunc_fname, O_CREAT | O_RDWR, 0);
    REQUIRE(fd >= 3);
    REQUIRE(fs_core->ftruncate(fd, trunc_fsize) == 0);
    REQUIRE(fs_core->close(fd) == 0);
    struct stat st;
    REQUIRE(fs_core->stat(trunc_fname, st) == 0);
    REQUIRE(st.st_mode & S_IFREG);
    REQUIRE(st.st_size == trunc_fsize);
    REQUIRE(fs_core->unlink(trunc_fname) == 0);

    REQUIRE(fs_core->umount("/sys") == 0);
}

TEST_CASE("ext4: Read-only filesystem tests")
{
    auto [fs_core, dm] = prepare_filesystem("emmc0");
    REQUIRE(fs_core);
    REQUIRE(fs_core->mount("emmc0part0", "/sys", "ext4", purefs::fs::mount_flags::read_only) == 0);

    const auto fd = fs_core->open("/sys/test_read_1.txt", O_RDONLY, 0);
    REQUIRE(fd >= 3);

    struct stat st;
    REQUIRE(fs_core->fstat(fd, st) == 0);
    REQUIRE(st.st_mode & S_IFREG);
    REQUIRE((st.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == (S_IRUSR | S_IRGRP | S_IROTH));
    REQUIRE((st.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0);

    REQUIRE(fs_core->close(fd) == 0);
    REQUIRE(fs_core->open("/sys/test_read_1.txt", O_RDWR, 0) == -EACCES);
    REQUIRE(fs_core->mkdir("/sys/tmp_dir", 0) == -EACCES);
    REQUIRE(fs_core->unlink("/sys/dummy_file.txt") == -EACCES);
    REQUIRE(fs_core->umount("/sys") == 0);
}

TEST_CASE("ext4: Directory tests")
{
    auto [fs_core, dm] = prepare_filesystem("emmc0");
    REQUIRE(fs_core);
    REQUIRE(fs_core->mount("emmc0part0", "/sys", "ext4") == 0);

    const std::string path = "/sys/test_mkdir_tmp";
    REQUIRE(fs_core->mkdir(path, 0) == 0);
    REQUIRE(fs_core->mkdir(path, 0) == -EEXIST);
    {
        const std::vector<std::string> filenames = {
            "document.txt", "image.jpg", "ringtone.mp3", "data.csv", "picture.png"};
        std::vector<int> fds;
        std::transform(filenames.begin(), filenames.end(), std::back_inserter(fds), [&](const auto &filename) {
            return fs_core->open(path + "/" + filename, O_CREAT | O_RDWR, 0);
        });
        REQUIRE(std::all_of(fds.begin(), fds.end(), [](auto fd) { return fd >= 3; }));
        for (auto fd : fds) {
            REQUIRE(fs_core->close(fd) == 0);
        }

        {
            const auto dh = fs_core->diropen(path);
            REQUIRE(dh);
            std::vector<std::string> dir_filenames;
            int dir_status = 0;
            for (;;) {
                std::string fn;
                struct stat st;
                dir_status = fs_core->dirnext(dh, fn, st);

                if (dir_status == 0) {
                    dir_filenames.push_back(fn);
                }
                else {
                    break;
                }
            }

            REQUIRE(dir_status == -ENODATA);
            REQUIRE(std::all_of(filenames.begin(), filenames.end(), [&dir_filenames](const auto &fn) {
                return std::find(dir_filenames.begin(), dir_filenames.end(), fn) != dir_filenames.end();
            }));
            REQUIRE(fs_core->dirclose(dh) == 0);
        }
        {
            const auto dh = fs_core->diropen(path);
            REQUIRE(dh);
            struct stat st;
            std::string first_fn;
            REQUIRE(fs_core->dirnext(dh, first_fn, st) == 0);

            for (std::string tmp_fn; fs_core->dirnext(dh, tmp_fn, st) == 0;)
                ;
            REQUIRE(fs_core->dirreset(dh) == 0);

            std::string reset_fn;
            REQUIRE(fs_core->dirnext(dh, reset_fn, st) == 0);
            REQUIRE(reset_fn == first_fn);
            REQUIRE(fs_core->dirclose(dh) == 0);
        }

        for (const auto &filename : filenames) {
            REQUIRE(fs_core->unlink(path + "/" + filename) == 0);
        }
        REQUIRE(fs_core->rmdir(path) == 0);
    }

    REQUIRE(fs_core->umount("/sys") == 0);
}

TEST_CASE("littlefs: Remount RO->RW->RW")
{
    using namespace purefs;
    static constexpr auto filename = "/sys/remount_test.txt";

    auto [fs_core, dm] = prepare_filesystem("emmc0");
    REQUIRE(fs_core);
    REQUIRE(fs_core->mount("emmc0part0", "/sys", "ext4", fs::mount_flags::read_only) == 0);
    REQUIRE(fs_core->open(filename, O_RDWR | O_CREAT, 0) == -EACCES);

    const std::string wr_str = "remount_test";

    {
        REQUIRE(fs_core->mount("", "/sys", "", fs::mount_flags::remount) == 0);
        const auto fd = fs_core->open(filename, O_RDWR | O_CREAT, 0);
        REQUIRE(fd >= 3);
        REQUIRE(fs_core->write(fd, wr_str.c_str(), wr_str.length()) == static_cast<ssize_t>(wr_str.length()));
        REQUIRE(fs_core->close(fd) == 0);
    }

    {
        REQUIRE(fs_core->mount("", "/sys", "", fs::mount_flags::remount | fs::mount_flags::read_only) == 0);
        const auto fd = fs_core->open(filename, O_RDONLY, 0);
        REQUIRE(fd >= 3);
        char buf[64];
        REQUIRE(fs_core->read(fd, buf, wr_str.length()) == static_cast<ssize_t>(wr_str.length()));
        REQUIRE(memcmp(buf, wr_str.c_str(), wr_str.length()) == 0);
        REQUIRE(fs_core->close(fd) == 0);
    }

    {
        REQUIRE(fs_core->mount("", "/sys", "", fs::mount_flags::remount) == 0);
        REQUIRE(fs_core->unlink(filename) == 0);
    }

    REQUIRE(fs_core->umount("/sys") == 0);
}

M module-platform/linux/tests/unittest_filesystem_littlefs.cpp => module-platform/linux/tests/unittest_filesystem_littlefs.cpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>


@@ 260,7 260,7 @@ TEST_CASE("littlefs: Directory tests")
            }
        }

        REQUIRE(fs_core->unlink(path) == 0);
        REQUIRE(fs_core->rmdir(path) == 0);
    }

    REQUIRE(fs_core->umount("/sys") == 0);

M module-vfs/CMakeLists.txt => module-vfs/CMakeLists.txt +7 -0
@@ 19,17 19,23 @@ target_sources(module-vfs
        drivers/include/purefs/fs/drivers/file_handle_littlefs.hpp
        drivers/include/purefs/fs/drivers/file_handle_vfat.hpp
        drivers/include/purefs/fs/drivers/filesystem_littlefs.hpp
        drivers/include/purefs/fs/drivers/filesystem_ext4.hpp
        drivers/include/purefs/fs/drivers/filesystem_vfat.hpp
        drivers/include/purefs/fs/drivers/mount_point_littlefs.hpp
        drivers/include/purefs/fs/drivers/mount_point_vfat.hpp
        drivers/include/purefs/fs/drivers/mount_point_ext4.hpp
        drivers/include/thirdparty/fatfs/ffconf.h
        drivers/include/thirdparty/fatfs/volume_mapper.hpp
        drivers/include/thirdparty/littlefs/volume_mapper.hpp
        drivers/include/thirdparty/lwext4/ext4_bdev.hpp
        drivers/src/purefs/fs/filesystem_littlefs.cpp
        drivers/src/purefs/fs/filesystem_ext4.cpp
        drivers/src/purefs/fs/filesystem_vfat.cpp
        drivers/src/thirdparty/fatfs/ff_glue.cpp
        drivers/src/thirdparty/fatfs/ffsystem.cpp
        drivers/src/thirdparty/littlefs/lfs_glue.cpp
        drivers/src/thirdparty/lwext4/ext4_bdev.cpp
        drivers/src/purefs/fs/mount_point_ext4.cpp

        include/internal/purefs/blkdev/disk_handle.hpp
        include/internal/purefs/blkdev/partition_parser.hpp


@@ 93,6 99,7 @@ target_include_directories(module-vfs
target_link_libraries(module-vfs 
    PRIVATE
        fatfs::fatfs
        lwext4::lwext4
        json::json
        littlefs::littlefs
        module-bsp 

M module-vfs/board/rt1051/newlib/vfs_io_syscalls.cpp => module-vfs/board/rt1051/newlib/vfs_io_syscalls.cpp +31 -2
@@ 35,6 35,30 @@ namespace
        }
        return ret;
    }
    auto stmode_to_type(mode_t mode)
    {
        if (S_ISREG(mode)) {
            return DT_REG;
        }
        else if (S_ISDIR(mode)) {
            return DT_DIR;
        }
        else if (S_ISCHR(mode)) {
            return DT_CHR;
        }
        else if (S_ISBLK(mode)) {
            return DT_BLK;
        }
        else if (S_ISFIFO(mode)) {
            return DT_FIFO;
        }
        else if (S_ISSOCK(mode)) {
            return DT_SOCK;
        }
        else {
            return 0;
        }
    }
} // namespace

namespace vfsn::internal::syscalls


@@ 85,6 109,11 @@ namespace vfsn::internal::syscalls
        return invoke_fs(_errno_, &purefs::fs::filesystem::unlink, name);
    }

    int rmdir(int &_errno_, const char *name)
    {
        return invoke_fs(_errno_, &purefs::fs::filesystem::rmdir, name);
    }

    int fcntl(int &_errno_, int fd, int cmd, int arg)
    {
        _errno_ = ENOTSUP;


@@ 215,7 244,7 @@ namespace vfsn::internal::syscalls
            }
            dirp->position += 1;
            dirp->dir_data.d_ino    = stdata.st_ino;
            dirp->dir_data.d_type   = S_ISREG(stdata.st_mode) ? DT_REG : DT_DIR;
            dirp->dir_data.d_type   = stmode_to_type(stdata.st_mode);
            dirp->dir_data.d_reclen = fname.size();
            std::strncpy(dirp->dir_data.d_name, fname.c_str(), sizeof(dirp->dir_data.d_name));
            return &dirp->dir_data;


@@ 255,7 284,7 @@ namespace vfsn::internal::syscalls
            }
            dirp->position += 1;
            entry->d_ino    = stdata.st_ino;
            entry->d_type   = S_ISREG(stdata.st_mode) ? DT_REG : DT_DIR;
            entry->d_type   = stmode_to_type(stdata.st_mode);
            entry->d_reclen = fname.size();
            std::strncpy(entry->d_name, fname.c_str(), sizeof(entry->d_name));
            *result = entry;

A module-vfs/drivers/include/purefs/fs/drivers/directory_handle_ext4.hpp => module-vfs/drivers/include/purefs/fs/drivers/directory_handle_ext4.hpp +26 -0
@@ 0,0 1,26 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <purefs/fs/directory_handle.hpp>
#include <ext4.h>

namespace purefs::fs::drivers
{
    class directory_handle_ext4 final : public internal::directory_handle
    {
      public:
        directory_handle_ext4(std::shared_ptr<internal::mount_point> mp, int error)
            : internal::directory_handle(mp, error)
        {}
        virtual ~directory_handle_ext4() = default;
        auto dirp() noexcept
        {
            return &m_dir;
        }

      private:
        ext4_dir m_dir{};
    };
} // namespace purefs::fs::drivers
\ No newline at end of file

A module-vfs/drivers/include/purefs/fs/drivers/file_handle_ext4.hpp => module-vfs/drivers/include/purefs/fs/drivers/file_handle_ext4.hpp +32 -0
@@ 0,0 1,32 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once
#include <purefs/fs/file_handle.hpp>
#include <ext4_config.h>
#include <ext4.h>

namespace purefs::fs::drivers
{
    class file_handle_ext4 final : public internal::file_handle
    {
      public:
        file_handle_ext4(std::shared_ptr<internal::mount_point> mp, std::string_view path, unsigned flags)
            : file_handle(mp, flags), m_path(path)
        {}
        virtual ~file_handle_ext4() = default;
        [[nodiscard]] auto filp() noexcept
        {
            return &m_file;
        }
        [[nodiscard]] auto open_path() const noexcept -> const std::string &
        {
            return m_path;
        }

      private:
        ::ext4_file m_file{};
        //! Store full path because some handle based fncs are not in lwext4
        const std::string m_path;
    };
} // namespace purefs::fs::drivers

M module-vfs/drivers/include/purefs/fs/drivers/file_handle_littlefs.hpp => module-vfs/drivers/include/purefs/fs/drivers/file_handle_littlefs.hpp +2 -2
@@ 15,11 15,11 @@ namespace purefs::fs::drivers
            : file_handle(mp, flags), m_path(path)
        {}
        virtual ~file_handle_littlefs() = default;
        auto lfs_filp() noexcept
        [[nodiscard]] auto lfs_filp() noexcept
        {
            return &file;
        }
        auto open_path() const noexcept -> const std::string &
        [[nodiscard]] auto open_path() const noexcept -> const std::string &
        {
            return m_path;
        }

A module-vfs/drivers/include/purefs/fs/drivers/filesystem_ext4.hpp => module-vfs/drivers/include/purefs/fs/drivers/filesystem_ext4.hpp +59 -0
@@ 0,0 1,59 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once
#include <purefs/fs/filesystem_operations.hpp>

namespace purefs::fs::drivers
{

    /** Filesystem specific driver for the ext4 fs */
    class filesystem_ext4 final : public filesystem_operations
    {
      public:
        using fsfile               = std::shared_ptr<internal::file_handle>;
        using fsdir                = std::shared_ptr<internal::directory_handle>;
        using fsmount              = std::shared_ptr<internal::mount_point>;
        filesystem_ext4()          = default;
        virtual ~filesystem_ext4() = default;

        auto mount_prealloc(std::shared_ptr<blkdev::internal::disk_handle> diskh, std::string_view path, unsigned flags)
            -> fsmount override;
        auto mount(fsmount mnt, const void *data) noexcept -> int override;
        auto umount(fsmount mnt) noexcept -> int override;
        auto stat_vfs(fsmount mnt, std::string_view path, struct statvfs &stat) const noexcept -> int override;

        /** Standard file access API */
        auto open(fsmount mnt, std::string_view path, int flags, int mode) noexcept -> fsfile override;
        auto close(fsfile zfile) noexcept -> int override;
        auto write(fsfile zfile, const char *ptr, size_t len) noexcept -> ssize_t override;
        auto read(fsfile zfile, char *ptr, size_t len) noexcept -> ssize_t override;
        auto seek(fsfile zfile, off_t pos, int dir) noexcept -> off_t override;
        auto fstat(fsfile zfile, struct stat &st) noexcept -> int override;
        auto stat(fsmount mnt, std::string_view file, struct stat &st) noexcept -> int override;
        auto link(fsmount mnt, std::string_view existing, std::string_view newlink) noexcept -> int override;
        auto symlink(fsmount mnt, std::string_view existing, std::string_view newlink) noexcept -> int override;
        auto unlink(fsmount mnt, std::string_view name) noexcept -> int override;
        auto rmdir(fsmount mnt, std::string_view name) noexcept -> int override;
        auto rename(fsmount mnt, std::string_view oldname, std::string_view newname) noexcept -> int override;
        auto mkdir(fsmount mnt, std::string_view path, int mode) noexcept -> int override;

        /** Directory support API */
        auto diropen(fsmount mnt, std::string_view path) noexcept -> fsdir override;
        auto dirreset(fsdir dirstate) noexcept -> int override;
        auto dirnext(fsdir dirstate, std::string &filename, struct stat &filestat) -> int override;
        auto dirclose(fsdir dirstate) noexcept -> int override;

        /** Other fops API */
        auto ftruncate(fsfile zfile, off_t len) noexcept -> int override;
        auto fsync(fsfile zfile) noexcept -> int override;
        auto isatty(fsfile zfile) noexcept -> int override;

        auto chmod(fsmount mnt, std::string_view path, mode_t mode) noexcept -> int override;
        auto fchmod(fsfile zfile, mode_t mode) noexcept -> int override;

      private:
        static auto stat(const char *mount_point, const char *path, struct stat *st, bool ro) noexcept -> int;
    };

} // namespace purefs::fs::drivers

M module-vfs/drivers/include/purefs/fs/drivers/filesystem_littlefs.hpp => module-vfs/drivers/include/purefs/fs/drivers/filesystem_littlefs.hpp +2 -1
@@ 8,7 8,7 @@
namespace purefs::fs::drivers
{

    /** Filesystem specific driver base class */
    /** Filesystem specific driver for the littlefs */
    class filesystem_littlefs final : public filesystem_operations
    {
      public:


@@ 35,6 35,7 @@ namespace purefs::fs::drivers
        auto fstat(fsfile zfile, struct stat &st) noexcept -> int override;
        auto stat(fsmount mnt, std::string_view file, struct stat &st) noexcept -> int override;
        auto unlink(fsmount mnt, std::string_view name) noexcept -> int override;
        auto rmdir(fsmount mnt, std::string_view name) noexcept -> int override;
        auto rename(fsmount mnt, std::string_view oldname, std::string_view newname) noexcept -> int override;
        auto mkdir(fsmount mnt, std::string_view path, int mode) noexcept -> int override;
        auto fchmod(fsfile zfile, mode_t mode) noexcept -> int override;

M module-vfs/drivers/include/purefs/fs/drivers/filesystem_vfat.hpp => module-vfs/drivers/include/purefs/fs/drivers/filesystem_vfat.hpp +2 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 30,6 30,7 @@ namespace purefs::fs::drivers
        auto fstat(fsfile zfile, struct stat &st) noexcept -> int override;
        auto stat(fsmount mnt, std::string_view file, struct stat &st) noexcept -> int override;
        auto unlink(fsmount mnt, std::string_view name) noexcept -> int override;
        auto rmdir(fsmount mnt, std::string_view name) noexcept -> int override;
        auto rename(fsmount mnt, std::string_view oldname, std::string_view newname) noexcept -> int override;
        auto mkdir(fsmount mnt, std::string_view path, int mode) noexcept -> int override;


A module-vfs/drivers/include/purefs/fs/drivers/mount_point_ext4.hpp => module-vfs/drivers/include/purefs/fs/drivers/mount_point_ext4.hpp +61 -0
@@ 0,0 1,61 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once
#include <memory>
#include <purefs/fs/mount_point.hpp>

struct ext4_blockdev;
namespace cpp_freertos
{
    class MutexRecursive;
}

namespace purefs::fs::drivers
{
    class mount_point_ext4 final : public purefs::fs::internal::mount_point
    {
      public:
        mount_point_ext4(std::shared_ptr<blkdev::internal::disk_handle> diskh,
                         std::string_view path,
                         unsigned flags,
                         std::shared_ptr<filesystem_operations> fs);
        virtual ~mount_point_ext4();
        [[nodiscard]] auto block_dev() const noexcept
        {
            return m_bdev;
        }
        auto block_dev(ext4_blockdev *bdev) noexcept -> void
        {
            m_bdev = bdev;
        }
        auto lock() noexcept -> void;
        auto unlock() noexcept -> void;

      private:
        auto native_root() const noexcept -> std::string_view override
        {
            return m_root;
        }
        ext4_blockdev *m_bdev{};
        const std::string m_root;
        std::unique_ptr<cpp_freertos::MutexRecursive> m_lock;
    };

    // Fs locker
    class ext4_locker
    {
      public:
        explicit ext4_locker(std::shared_ptr<mount_point_ext4> mnt_ext) : m_mnt_ext(mnt_ext)
        {
            m_mnt_ext->lock();
        }
        ~ext4_locker()
        {
            m_mnt_ext->unlock();
        }

      private:
        const std::shared_ptr<mount_point_ext4> m_mnt_ext;
    };
} // namespace purefs::fs::drivers

A module-vfs/drivers/include/thirdparty/lwext4/ext4_bdev.hpp => module-vfs/drivers/include/thirdparty/lwext4/ext4_bdev.hpp +31 -0
@@ 0,0 1,31 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <purefs/blkdev/defs.hpp>

namespace purefs::blkdev
{
    class disk_manager;
}

struct ext4_blockdev;

namespace purefs::fs::drivers::ext4::internal
{
    /**
     * Append volume and return ext4 block device
     * @param diskmm Disk manager
     * @param diskh Disk handle in the manager
     * @return block device and error code
     */
    std::pair<ext4_blockdev *, int> append_volume(std::shared_ptr<blkdev::disk_manager> diskmm, blkdev::disk_fd diskh);

    /** Remove ext4 block device from the table
     * @param ext4_block Ext4 block device for free
     * @return error code
     */
    int remove_volume(ext4_blockdev *ext4_block);

} // namespace purefs::fs::drivers::ext4::internal

A module-vfs/drivers/src/purefs/fs/filesystem_ext4.cpp => module-vfs/drivers/src/purefs/fs/filesystem_ext4.cpp +503 -0
@@ 0,0 1,503 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <purefs/fs/drivers/filesystem_ext4.hpp>
#include <purefs/fs/drivers/mount_point_ext4.hpp>
#include <purefs/blkdev/disk_manager.hpp>
#include <purefs/blkdev/disk_handle.hpp>
#include <purefs/fs/drivers/file_handle_ext4.hpp>
#include <purefs/fs/drivers/directory_handle_ext4.hpp>
#include <lwext4/ext4_bdev.hpp>
#include <log.hpp>
#include <ext4.h>
#include <ext4_inode.h>
#include <ext4_super.h>

#include <climits>
#include <syslimits.h>
#include <sys/statvfs.h>
#include <fcntl.h>
#include <errno.h>
#include <cstring>
#include <algorithm>
#include <sys/stat.h>

namespace purefs::fs::drivers
{
    namespace
    {
        template <typename T, typename... Args>
        auto invoke_efs(filesystem_ext4::fsfile zfil, T efs_fun, Args &&... args)
        {
            auto vfile = std::dynamic_pointer_cast<file_handle_ext4>(zfil);
            if (!vfile) {
                LOG_ERROR("Non ext4 filesystem file pointer");
                return -EBADF;
            }
            auto mntp = std::static_pointer_cast<mount_point_ext4>(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>(args)...);
            return -err;
        }
        template <typename T>
        auto invoke_efs(filesystem_ext4::fsmount mnt, T efs_fun, std::string_view oldpath, std::string_view newpath)
        {
            auto mntp = std::static_pointer_cast<mount_point_ext4>(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 <typename T, typename... Args>
        auto invoke_efs(filesystem_ext4::fsmount fmnt, T efs_fun, std::string_view path, Args &&... args)
        {
            auto mntp = std::static_pointer_cast<mount_point_ext4>(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>(args)...);
            return -err;
        }
        template <typename T, typename... Args> auto invoke_efs(filesystem_ext4::fsdir zdir, T lfs_fun, Args &&... args)
        {
            auto vdir = std::dynamic_pointer_cast<directory_handle_ext4>(zdir);
            if (!vdir) {
                LOG_ERROR("Non ext4 filesystem directory pointer");
                return -EBADF;
            }
            auto mntp = std::static_pointer_cast<mount_point_ext4>(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>(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<blkdev::internal::disk_handle> diskh,
                                         std::string_view path,
                                         unsigned flags) -> fsmount
    {
        return std::make_shared<mount_point_ext4>(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<mount_point_ext4>(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;
        }
        // Test only
        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<mount_point_ext4>(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<mount_point_ext4>(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<mount_point_ext4>(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<file_handle_ext4>(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<file_handle_ext4>(zfile);
        if (!vfile) {
            LOG_ERROR("Non ext4 filesystem file pointer");
            return -EBADF;
        }
        auto mntp = std::static_pointer_cast<mount_point_ext4>(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<file_handle_ext4>(zfile);
        if (!vfile) {
            LOG_ERROR("Non ext4 filesystem file pointer");
            return -EBADF;
        }
        auto mntp = std::static_pointer_cast<mount_point_ext4>(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::static_pointer_cast<mount_point_ext4>(mnt);
        if (!mntp) {
            LOG_ERROR("Non ext4 mount point");
            return -EBADF;
        }
        const auto npath = mntp->native_path(file);
        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<mount_point_ext4>(mnt);
        if (!vmnt) {
            LOG_ERROR("Non ext4 mount point");
            return nullptr;
        }
        const auto fspath = vmnt->native_path(path);
        const auto dirp   = std::make_shared<directory_handle_ext4>(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<const char *>(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<file_handle_ext4>(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<char[]>(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<file_handle_ext4>(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

M module-vfs/drivers/src/purefs/fs/filesystem_littlefs.cpp => module-vfs/drivers/src/purefs/fs/filesystem_littlefs.cpp +5 -0
@@ 375,6 375,11 @@ namespace purefs::fs::drivers
        return invoke_lfs(mnt, name, ::lfs_remove);
    }

    auto filesystem_littlefs::rmdir(fsmount mnt, std::string_view name) noexcept -> int
    {
        return invoke_lfs(mnt, name, ::lfs_remove);
    }

    auto filesystem_littlefs::rename(fsmount mnt, std::string_view oldname, std::string_view newname) noexcept -> int
    {
        auto mntp = std::static_pointer_cast<mount_point_littlefs>(mnt);

M module-vfs/drivers/src/purefs/fs/filesystem_vfat.cpp => module-vfs/drivers/src/purefs/fs/filesystem_vfat.cpp +13 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <purefs/fs/drivers/filesystem_vfat.hpp>


@@ 379,6 379,18 @@ namespace purefs::fs::drivers
        return translate_error(fret);
    }

    auto filesystem_vfat::rmdir(fsmount mnt, std::string_view name) noexcept -> int
    {
        auto vmnt = std::dynamic_pointer_cast<mount_point_vfat>(mnt);
        if (!vmnt) {
            LOG_ERROR("Non VFAT mount point");
            return -ENXIO;
        }
        const auto fspath = vmnt->native_path(name);
        const auto fret   = f_rmdir(fspath.c_str());
        return translate_error(fret);
    }

    auto filesystem_vfat::rename(fsmount mnt, std::string_view oldname, std::string_view newname) noexcept -> int
    {
        auto vmnt = std::dynamic_pointer_cast<mount_point_vfat>(mnt);

A module-vfs/drivers/src/purefs/fs/mount_point_ext4.cpp => module-vfs/drivers/src/purefs/fs/mount_point_ext4.cpp +40 -0
@@ 0,0 1,40 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <purefs/fs/drivers/mount_point_ext4.hpp>
#include <mutex.hpp>
#include <string>

namespace purefs::fs::drivers
{
    namespace
    {
        inline auto mount_path_mod(std::string_view path)
        {
            std::string ret{path};
            if (ret.back() != '/') {
                ret.push_back('/');
            }
            return ret;
        }
    } // namespace
    mount_point_ext4::mount_point_ext4(std::shared_ptr<blkdev::internal::disk_handle> diskh,
                                       std::string_view path,
                                       unsigned flags,
                                       std::shared_ptr<filesystem_operations> fs)
        : mount_point(diskh, mount_path_mod(path), flags, fs), m_root(mount_path_mod(path)),
          m_lock(std::make_unique<cpp_freertos::MutexRecursive>())
    {}

    mount_point_ext4::~mount_point_ext4()
    {}
    auto mount_point_ext4::lock() noexcept -> void
    {
        m_lock->Lock();
    }
    auto mount_point_ext4::unlock() noexcept -> void
    {
        m_lock->Unlock();
    }

} // namespace purefs::fs::drivers
\ No newline at end of file

A module-vfs/drivers/src/thirdparty/lwext4/ext4_bdev.cpp => module-vfs/drivers/src/thirdparty/lwext4/ext4_bdev.cpp +140 -0
@@ 0,0 1,140 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <lwext4/ext4_bdev.hpp>
#include <ext4_config.h>
#include <ext4_blockdev.h>
#include <ext4_errno.h>
#include <unordered_map>
#include <log.hpp>
#include <mutex.hpp>
#include <cstring>
#include <purefs/blkdev/disk_manager.hpp>

namespace purefs::fs::drivers::ext4::internal
{
    namespace
    {
        //! Global structure for single mount
        struct ext4_config
        {
            ::ext4_blockdev bdev;
            std::unique_ptr<uint8_t[]> buf;
            ::ext4_blockdev_iface ifc;
        };

        // Global volumes list
        std::unordered_map<::ext4_blockdev *, std::unique_ptr<ext4_config>> g_volumes;
        // Global lock for recursive mutex
        cpp_freertos::MutexRecursive g_lock;

        namespace io
        {
            //! Global io context for the partition
            struct context
            {
                context(std::shared_ptr<blkdev::disk_manager> diskmm, blkdev::disk_fd _disk_h)
                    : disk(diskmm), disk_h(_disk_h)
                {}
                const std::weak_ptr<blkdev::disk_manager> disk;
                const blkdev::disk_fd disk_h;
                mutable cpp_freertos::MutexRecursive mutex;
            };

            int write(struct ext4_blockdev *bdev, const void *buf, uint64_t blk_id, uint32_t blk_cnt)
            {
                auto ctx = reinterpret_cast<context *>(bdev->bdif->p_user);
                if (!ctx) {
                    return -EIO;
                }
                auto diskmm = ctx->disk.lock();
                if (!diskmm) {
                    return -EIO;
                }
                const auto err = diskmm->write(ctx->disk_h, buf, blk_id, blk_cnt);
                if (err) {
                    LOG_ERROR("Sector write error errno: %i", err);
                }
                return -err;
            }
            int read(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, uint32_t blk_cnt)
            {
                auto ctx = reinterpret_cast<context *>(bdev->bdif->p_user);
                if (!ctx) {
                    return -EIO;
                }
                auto diskmm = ctx->disk.lock();
                if (!diskmm) {
                    return -EIO;
                }
                const auto err = diskmm->read(ctx->disk_h, buf, blk_id, blk_cnt);
                if (err) {
                    LOG_ERROR("Sector write error errno: %i", err);
                }
                return -err;
            }

            int open(struct ext4_blockdev *bdev)
            {
                return 0;
            }
            int close(struct ext4_blockdev *bdev)
            {
                return 0;
            }

        } // namespace io

    } // namespace

    // Append volume
    std::pair<ext4_blockdev *, int> append_volume(std::shared_ptr<blkdev::disk_manager> diskmm, blkdev::disk_fd diskh)
    {
        const auto sect_size = diskmm->get_info(diskh, blkdev::info_type::sector_size);
        if (sect_size < 0) {
            LOG_ERROR("Unable to get sector size %li", long(sect_size));
            return {nullptr, sect_size};
        }
        const auto sect_count = diskmm->get_info(diskh, blkdev::info_type::sector_count);
        if (sect_count < 0) {
            LOG_ERROR("Unable to get sector count %li", long(sect_size));
            return {nullptr, sect_count};
        }

        // Insert into the container
        auto cfg = std::make_unique<ext4_config>();
        std::memset(cfg.get(), 0, sizeof(ext4_config));
        cfg->ifc.open         = io::open;
        cfg->ifc.bread        = io::read;
        cfg->ifc.bwrite       = io::write;
        cfg->ifc.close        = io::close;
        cfg->buf              = std::make_unique<uint8_t[]>(sect_size);
        cfg->ifc.ph_bbuf      = cfg->buf.get();
        cfg->ifc.ph_bcnt      = sect_count;
        cfg->ifc.ph_bsize     = sect_size;
        cfg->ifc.p_user       = new io::context(diskmm, diskh);
        cfg->bdev.bdif        = &cfg->ifc;
        cfg->bdev.part_offset = 0;
        cfg->bdev.part_size   = uint64_t(sect_size) * sect_count;

        cpp_freertos::LockGuard _lck(g_lock);
        auto bdev = &cfg->bdev;
        g_volumes.insert(std::make_pair(&cfg->bdev, std::move(cfg)));
        return {bdev, {}};
    }

    // Remove volume
    int remove_volume(ext4_blockdev *ext4_block)
    {
        cpp_freertos::LockGuard _lck(g_lock);
        auto vol = g_volumes.find(ext4_block);
        if (vol == std::end(g_volumes)) {
            LOG_ERROR("Unable to find LFS ");
            return -ENXIO;
        }
        delete reinterpret_cast<io::context *>(vol->second->ifc.p_user);
        g_volumes.erase(vol);
        return 0;
    }

} // namespace purefs::fs::drivers::ext4::internal
\ No newline at end of file

M module-vfs/include/user/newlib/vfs_io_syscalls.hpp => module-vfs/include/user/newlib/vfs_io_syscalls.hpp +1 -0
@@ 19,6 19,7 @@ namespace vfsn::internal::syscalls
    int fstat(int &_errno_, int fd, struct stat *st);
    int link(int &_errno_, const char *existing, const char *newLink);
    int unlink(int &_errno_, const char *name);
    int rmdir(int &_errno_, const char *name);
    int fcntl(int &_errno_, int fd, int cmd, int arg);
    int stat(int &_errno_, const char *file, struct stat *pstat);
    int chdir(int &_errno_, const char *path);

M module-vfs/include/user/purefs/fs/filesystem.hpp => module-vfs/include/user/purefs/fs/filesystem.hpp +1 -0
@@ 128,6 128,7 @@ namespace purefs::fs
        auto unlink(std::string_view name) noexcept -> int;
        auto rename(std::string_view oldname, std::string_view newname) noexcept -> int;
        auto mkdir(std::string_view path, int mode) noexcept -> int;
        auto rmdir(std::string_view path) noexcept -> int;

        /** Directory support API */
        auto diropen(std::string_view path) noexcept -> fsdir;

M module-vfs/include/user/purefs/fs/filesystem_operations.hpp => module-vfs/include/user/purefs/fs/filesystem_operations.hpp +3 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 47,7 47,7 @@ namespace purefs::fs
                                    unsigned flags) -> fsmount            = 0;
        virtual auto mount(fsmount mnt, const void *data) noexcept -> int = 0;
        virtual auto umount(fsmount mnt) noexcept -> int                  = 0;
        virtual auto stat_vfs(fsmount mnt, std::string_view path, statvfs &stat) const noexcept -> int;
        virtual auto stat_vfs(fsmount mnt, std::string_view path, struct statvfs &stat) const noexcept -> int;

        /** Standard file access API */
        virtual auto open(fsmount mnt, std::string_view path, int flags, int mode) noexcept -> fsfile = 0;


@@ 60,6 60,7 @@ namespace purefs::fs
        virtual auto link(fsmount mnt, std::string_view existing, std::string_view newlink) noexcept -> int;
        virtual auto symlink(fsmount mnt, std::string_view existing, std::string_view newlink) noexcept -> int;
        virtual auto unlink(fsmount mnt, std::string_view name) noexcept -> int;
        virtual auto rmdir(fsmount mnt, std::string_view name) noexcept -> int;
        virtual auto rename(fsmount mnt, std::string_view oldname, std::string_view newname) noexcept -> int;
        virtual auto mkdir(fsmount mnt, std::string_view path, int mode) noexcept -> int;


M module-vfs/src/purefs/fs/filesystem.cpp => module-vfs/src/purefs/fs/filesystem.cpp +1 -3
@@ 18,9 18,7 @@ namespace purefs::fs
    namespace
    {
        constexpr std::pair<short, std::string_view> part_types_to_vfs[] = {
            {0x0b, "vfat"},
            {0x9e, "littlefs"},
        };
            {0x0b, "vfat"}, {0x9e, "littlefs"}, {0x83, "ext4"}};
    }
    filesystem::filesystem(std::shared_ptr<blkdev::disk_manager> diskmm)
        : m_diskmm(diskmm), m_lock(std::make_unique<cpp_freertos::MutexRecursive>()),

M module-vfs/src/purefs/fs/filesystem_operations.cpp => module-vfs/src/purefs/fs/filesystem_operations.cpp +5 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include <purefs/fs/filesystem_operations.hpp>
#include <purefs/fs/directory_handle.hpp>


@@ 49,6 49,10 @@ namespace purefs::fs
    {
        return -ENOTSUP;
    }
    auto filesystem_operations::rmdir(fsmount mnt, std::string_view name) noexcept -> int
    {
        return -ENOTSUP;
    }
    auto filesystem_operations::rename(fsmount mnt, std::string_view oldname, std::string_view newname) noexcept -> int
    {
        return -ENOTSUP;

M module-vfs/src/purefs/fs/filesystem_syscalls.cpp => module-vfs/src/purefs/fs/filesystem_syscalls.cpp +9 -0
@@ 31,6 31,15 @@ namespace purefs::fs
        return err;
    }

    auto filesystem::rmdir(std::string_view name) noexcept -> int
    {
        const auto err = invoke_fops(iaccess::rw, &filesystem_operations::rmdir, name);
        if (!err) {
            m_notifier->notify(name, inotify_flags::del);
        }
        return err;
    }

    auto filesystem::mkdir(std::string_view path, int mode) noexcept -> int
    {
        const auto err = invoke_fops(iaccess::rw, &filesystem_operations::mkdir, path, mode);

M module-vfs/src/purefs/vfs_subsystem.cpp => module-vfs/src/purefs/vfs_subsystem.cpp +21 -8
@@ 4,6 4,7 @@
#include <purefs/fs/filesystem.hpp>
#include <purefs/fs/drivers/filesystem_vfat.hpp>
#include <purefs/fs/drivers/filesystem_littlefs.hpp>
#include <purefs/fs/drivers/filesystem_ext4.hpp>
#include <purefs/blkdev/disk_manager.hpp>
#include <purefs/vfs_subsystem.hpp>
#include <purefs/vfs_subsystem_internal.hpp>


@@ 22,6 23,7 @@ namespace purefs::subsystem
        constexpr auto default_nvrom_name       = "nvrom0";
        constexpr auto fat_part_code            = 0x0b;
        constexpr auto lfs_part_code            = 0x9e;
        constexpr auto linux_part_code          = 0x83;
        constexpr auto layout_part_count        = 3;
        constexpr auto boot_part_index          = 0;
        constexpr auto user_part_index          = 2;


@@ 148,6 150,11 @@ namespace purefs::subsystem
            LOG_FATAL("Unable to register lfs filesystem with error %i", err);
            return {};
        }
        err = fs_core->register_filesystem("ext4", std::make_shared<fs::drivers::filesystem_ext4>());
        if (err) {
            LOG_FATAL("Unable to register ext4 filesystem with error %i", err);
            return {};
        }

        g_disk_mgr = disk_mgr;
        g_fs_core  = fs_core;


@@ 186,7 193,7 @@ namespace purefs::subsystem
            LOG_FATAL("Invalid boot partition type expected code: %i current code: %i", fat_part_code, boot_part.type);
            return -EIO;
        }
        if (user_part.type != lfs_part_code) {
        if ((user_part.type != lfs_part_code) && (user_part.type != linux_part_code)) {
            LOG_FATAL("Invalid user partition type expected code: %i current code: %i", lfs_part_code, user_part.type);
            return -EIO;
        }


@@ 200,14 207,20 @@ namespace purefs::subsystem
        if (err) {
            return err;
        }
        const int lfs_block_log2     = read_mbr_lfs_erase_size(disk, default_blkdev_name, user_part.physical_number);
        uint32_t lfs_block_size      = 0;
        uint32_t *lfs_block_size_ptr = nullptr;
        if (lfs_block_log2 >= block_size_min_shift && lfs_block_log2 <= block_size_max_shift) {
            lfs_block_size     = 1U << lfs_block_log2;
            lfs_block_size_ptr = &lfs_block_size;
        if (user_part.type == lfs_part_code) {
            const int lfs_block_log2 = read_mbr_lfs_erase_size(disk, default_blkdev_name, user_part.physical_number);
            uint32_t lfs_block_size  = 0;
            uint32_t *lfs_block_size_ptr = nullptr;
            if (lfs_block_log2 >= block_size_min_shift && lfs_block_log2 <= block_size_max_shift) {
                lfs_block_size     = 1U << lfs_block_log2;
                lfs_block_size_ptr = &lfs_block_size;
            }
            err =
                vfs->mount(user_part.name, purefs::dir::getUserDiskPath().string(), "littlefs", 0, lfs_block_size_ptr);
        }
        else {
            err = vfs->mount(user_part.name, purefs::dir::getUserDiskPath().string(), "ext4");
        }
        err = vfs->mount(user_part.name, purefs::dir::getUserDiskPath().string(), "littlefs", 0, lfs_block_size_ptr);
        const std::string json_file = (dir::getRootDiskPath() / file::boot_json).string();
        const auto boot_dir_name    = parse_boot_json_directory(json_file);
        const auto user_dir         = (dir::getRootDiskPath() / boot_dir_name).string();

M third-party/CMakeLists.txt => third-party/CMakeLists.txt +1 -0
@@ 11,6 11,7 @@ add_subdirectory(klib)
add_subdirectory(libical)
add_subdirectory(libphonenumber)
add_subdirectory(littlefs)
add_subdirectory(lwext4)
add_subdirectory(magic_enum)
add_subdirectory(microtar)
add_subdirectory(minimp3)

A third-party/lwext4/CMakeLists.txt => third-party/lwext4/CMakeLists.txt +81 -0
@@ 0,0 1,81 @@
cmake_minimum_required(VERSION 3.13)
add_library(lwext4 STATIC)
add_library(lwext4::lwext4 ALIAS lwext4)

include(lwext4_macros.cmake)

target_include_directories(lwext4
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lwext4/include>
        $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>
)

target_compile_definitions(lwext4
    PRIVATE
    CONFIG_DEBUG_PRINTF=1
    CONFIG_DEBUG_ASSERT=0
    CONFIG_HAVE_OWN_OFLAGS=0
    CONFIG_HAVE_OWN_ERRNO=0
    CONFIG_BLOCK_DEV_CACHE_SIZE=256
)
target_compile_options(lwext4
    PRIVATE
    -Wno-format
)

target_sources(lwext4
    PRIVATE
        lwext4/src/ext4_balloc.c
        lwext4/src/ext4_bcache.c
        lwext4/src/ext4_bitmap.c
        lwext4/src/ext4_blockdev.c
        lwext4/src/ext4_block_group.c
        lwext4/src/ext4.c
        lwext4/src/ext4_crc32.c
        lwext4/src/ext4_debug.c
        lwext4/src/ext4_dir.c
        lwext4/src/ext4_dir_idx.c
        lwext4/src/ext4_extent.c
        lwext4/src/ext4_fs.c
        lwext4/src/ext4_hash.c
        lwext4/src/ext4_ialloc.c
        lwext4/src/ext4_inode.c
        lwext4/src/ext4_journal.c
        lwext4/src/ext4_mbr.c
        lwext4/src/ext4_mkfs.c
        lwext4/src/ext4_super.c
        lwext4/src/ext4_trans.c
        lwext4/src/ext4_xattr.c
    PUBLIC
        lwext4/include/ext4_balloc.h
        lwext4/include/ext4_bcache.h
        lwext4/include/ext4_bitmap.h
        lwext4/include/ext4_blockdev.h
        lwext4/include/ext4_block_group.h
        lwext4/include/ext4_config.h
        lwext4/include/ext4_crc32.h
        lwext4/include/ext4_debug.h
        lwext4/include/ext4_dir.h
        lwext4/include/ext4_dir_idx.h
        lwext4/include/ext4_errno.h
        lwext4/include/ext4_extent.h
        lwext4/include/ext4_fs.h
        lwext4/include/ext4.h
        lwext4/include/ext4_hash.h
        lwext4/include/ext4_ialloc.h
        lwext4/include/ext4_inode.h
        lwext4/include/ext4_journal.h
        lwext4/include/ext4_mbr.h
        lwext4/include/ext4_misc.h
        lwext4/include/ext4_mkfs.h
        lwext4/include/ext4_oflags.h
        lwext4/include/ext4_super.h
        lwext4/include/ext4_trans.h
        lwext4/include/ext4_types.h
        lwext4/include/ext4_xattr.h
        lwext4/include/misc/queue.h
        lwext4/include/misc/tree.h
)

lwext4_output_configure()


A third-party/lwext4/lwext4 => third-party/lwext4/lwext4 +1 -0
@@ 0,0 1,1 @@
Subproject commit 2869807352fb7c9c2ab69e8442efa0b2ce404673

A third-party/lwext4/lwext4_macros.cmake => third-party/lwext4/lwext4_macros.cmake +23 -0
@@ 0,0 1,23 @@
macro(lwext4_output_configure)
    get_property(
        definitions
        TARGET lwext4::lwext4
        PROPERTY COMPILE_DEFINITIONS
    )
    file(WRITE
         ${CMAKE_CURRENT_BINARY_DIR}/include/generated/ext4_config.h
         "")
    foreach(item ${definitions})
        string(REGEX MATCH "^CONFIG_" match_res ${item})
        if(match_res)
            string(REGEX REPLACE "=(.+)$" "" replace_res ${item})
            string(CONFIGURE
                   "#define ${replace_res} ${CMAKE_MATCH_1}"
                   output_str)
            file(APPEND
                 ${CMAKE_CURRENT_BINARY_DIR}/include/generated/ext4_config.h
                 "${output_str}\n")
        endif()
    endforeach()
endmacro()
lwext4_output_configure()
\ No newline at end of file

M tools/generate_image.sh => tools/generate_image.sh +36 -7
@@ 75,7 75,6 @@ PART3_SIZE=$(($DEVICE_BLK_COUNT - $PART1_SIZE - $PART2_SIZE - $PART1_START))

echo "Remove previous image file"
rm -f $IMAGE_NAME

truncate -s $(($DEVICE_BLK_COUNT * $DEVICE_BLK_SIZE)) $IMAGE_NAME
sfdisk $IMAGE_NAME << ==sfdisk
label: dos


@@ 83,8 82,8 @@ label-id: 0x09650eb4
unit: sectors

/dev/sdx1 : start=    $PART1_START,  size=    $PART1_SIZE, type=b, bootable
/dev/sdx2 : start=    $PART2_START,  size=    $PART2_SIZE, type=9e
/dev/sdx3 : start=    $PART3_START,  size=    $PART3_SIZE, type=9e
/dev/sdx2 : start=    $PART2_START,  size=    $PART2_SIZE, type=83
/dev/sdx3 : start=    $PART3_START,  size=    $PART3_SIZE, type=83
==sfdisk




@@ 142,10 141,40 @@ fi
mcopy -s -i "$PART1" .boot.json ::
mcopy -s -i "$PART1" .boot.json.crc32 ::

#Littlefs generate image
echo $(pwd)
$GENLFS --image=$IMAGE_NAME --block_size=4096 --overwrite --partition_num=2
$GENLFS --image=$IMAGE_NAME --block_size=32768 --overwrite --partition_num=3 -- user/*
# ^64bit - 64bit inodes are not supported by the lwext4
# ^metadata_csum - Metatata checksums has buggy implementation in the lwext4 

# Ext4 backup partition used by updater
mke2fs \
  -F \
  -L 'backup' \
  -N 0 \
  -E offset=$(($PART2_START*$DEVICE_BLK_SIZE)) \
  -O ^64bit \
  -O ^flex_bg \
  -O ^metadata_csum \
  -m 0 \
  -r 1 \
  -t ext4 \
  "$IMAGE_NAME" \
  $((($PART2_SIZE*$DEVICE_BLK_SIZE)/1024)) \
;

# EXT4 user partition
mke2fs \
  -F \
  -L 'user' \
  -N 0 \
  -E offset=$(($PART3_START*$DEVICE_BLK_SIZE)) \
  -O ^64bit \
  -O ^flex_bg \
  -O ^metadata_csum \
  -d "user" \
  -m 0 \
  -r 1 \
  -t ext4 \
  "$IMAGE_NAME" \
;

# back to previous dir
cd -