~aleteoryx/muditaos

bf2609524ca23233e1349c7c21cc44b86da9f514 — Lucjan Bryndza 5 years ago 0614797
[EGD-4075] Use FATFS in the Linux builds (#843)

* [EGD-4075] Use FAT fs in the emulator

Add script for genrate base phone image

* [EGD-4075] Use FreeRTOS-FAT in emulator initial commit

* [EGD-4075] Thread local storage fix

Fixing thread local storage when running on the
Linux platform in the test mode

* [EGD-4075] Improve image generation

Improve image generation script on the linux platform

* [EGD-4075] Fix all test with FAT fs image

Fix all tests with fatfs image

* [EGD-4075] Fix calculator utility test

* [EGD-4075] Image dependencies fix

* Remove uneeded comments

* Missing headers in CR

* [EGD-4075] Fixed whitespaces

Co-authored-by: Lucjan Bryndza <lucjan.bryndza@mudita.com>
M .gitmodules => .gitmodules +1 -1
@@ 54,7 54,7 @@
	path = module-utils/magic_enum
	url = git@github.com:Neargye/magic_enum.git
[submodule "module-vfs/board/cross/freeRTOS_FAT"]
	path = module-vfs/board/cross/freeRTOS_FAT
	path = module-vfs/board/freeRTOS_FAT
	url = ../Lab-Project-FreeRTOS-FAT.git
[submodule "module-utils/tinyexpr"]
	path = module-utils/tinyexpr

M CMakeLists.txt => CMakeLists.txt +14 -0
@@ 204,6 204,20 @@ set_source_files_properties(source/main.cpp PROPERTIES COMPILE_DEFINITIONS "${EN
target_link_options(${PROJECT_NAME} PUBLIC ${TARGET_LINK_OPTIONS})

if (${PROJECT_TARGET} STREQUAL "TARGET_Linux")

	set(FAT_IMAGE ${CMAKE_PROJECT_NAME}.img)
	add_custom_command(
		OUTPUT ${FAT_IMAGE}
		DEPENDS assets
		COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/generate_fatfs_image.sh ${FAT_IMAGE} ${CMAKE_BINARY_DIR}
		WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
		COMMENT "Generate ${FAT_IMAGE}"
	)
	add_custom_target(
		${FAT_IMAGE}-target ALL
		DEPENDS ${FAT_IMAGE}
	)
	add_dependencies(check ${FAT_IMAGE}-target)
    install(TARGETS ${CMAKE_PROJECT_NAME} DESTINATION "./")
endif()


A generate_fatfs_image.sh => generate_fatfs_image.sh +60 -0
@@ 0,0 1,60 @@
#!/bin/bash -e
# Copyright (c) 2017-2020, 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_dir] [assets_root_dir]
	image_dir Target disk image
	asset_root_dir Asset root directory
==usage
}

ASSETS_DIR="assets country-codes.db Luts.bin"

TEST_ITEMS="testfiles"

if [ $# -ne 2 ]; then
	echo "Error! Invalid argument count"
	usage
	exit -1
fi
IMAGE_NAME=$(realpath $1)
SRC_DATA=$(realpath $2)

if [ ! -d "$SRC_DATA" ]; then
	echo "Error! asset_root_dir is not a directory"
	usage
	exit -1
fi

truncate -s 16G $IMAGE_NAME
sfdisk $IMAGE_NAME << ==sfdisk
label: dos
label-id: 0x09650eb4
unit: sectors

/dev/sda1 : start=        2048, size=    28522496, type=b, bootable
/dev/sda2 : start=    28524544, size=     2097152, type=b
==sfdisk

PART1="$IMAGE_NAME@@1048576"
PART2="$IMAGE_NAME@@14604566528"
mformat -i "$PART1" -F -T 28522496 -v PUREOS
mformat -i "$PART2" -F -T 2097152 -v RECOVER
mmd -i "$PART1" ::/current
cd "$SRC_DATA"
for i in $ASSETS_DIR; do
	mcopy -s -i "$PART1" $i ::/current/
done
mcopy -s -i "$PART1" user ::
mcopy -s -i "$PART1" .boot.json ::
mcopy -s -i "$PART1" .boot.json.crc32 ::

# Testing parts of files
for i in $TEST_ITEMS; do
	mcopy -s -i "$PART1" $i ::/current
done
mcopy -s -i "$PART1" sys/updates ::

cd -

M module-apps/application-calculator/tests/CalculatorUtility_tests.cpp => module-apps/application-calculator/tests/CalculatorUtility_tests.cpp +8 -0
@@ 9,6 9,14 @@

class vfs vfs;

struct vfs_initializer
{
    vfs_initializer()
    {
        vfs.Init();
    }
} vfs_initializer;

TEST_CASE("Calculator utilities")
{
    auto calculator = Calculator();

M module-audio/Audio/test/unittest_audio.cpp => module-audio/Audio/test/unittest_audio.cpp +21 -10
@@ 19,18 19,29 @@

class vfs vfs;

struct vfs_initializer
{
    vfs_initializer()
    {
        vfs.Init();
    }
} vfs_init;

TEST_CASE("Test audio tags")
{
    std::vector<std::string> testExtensions = {"flac", "wav", "mp3"};
    for (auto ext : testExtensions) {
        auto dec = audio::decoder::Create(("testfiles/audio." + ext).c_str());
        REQUIRE(dec);
        auto tags = dec->fetchTags();
        REQUIRE(tags);
        REQUIRE(tags->title == ext + " Test track title");
        REQUIRE(tags->artist == ext + " Test artist name");
        REQUIRE(tags->album == ext + " Test album title");
        REQUIRE(tags->year == "2020");
    SECTION(" Encoder tests ")
    {
        std::vector<std::string> testExtensions = {"flac", "wav", "mp3"};
        for (auto ext : testExtensions) {
            auto dec = audio::decoder::Create(("testfiles/audio." + ext).c_str());
            REQUIRE(dec);
            auto tags = dec->fetchTags();
            REQUIRE(tags);
            REQUIRE(tags->title == ext + " Test track title");
            REQUIRE(tags->artist == ext + " Test artist name");
            REQUIRE(tags->album == ext + " Test album title");
            REQUIRE(tags->year == "2020");
        }
    }
}


M module-db/tests/unittest.cpp => module-db/tests/unittest.cpp +8 -0
@@ 18,6 18,14 @@

class vfs vfs;

struct vfs_initializer
{
    vfs_initializer()
    {
        vfs.Init();
    }
} vfs_initializer;

TEST_CASE("Create and destroy simple database")
{


M module-gui/test/test-catch-text/main.cpp => module-gui/test/test-catch-text/main.cpp +8 -0
@@ 8,3 8,11 @@

class vfs vfs;       // needed for compilation, our vfs is global
utils::i18 localize; // needed to load any keymap - these are stored in i18

struct vfs_initializer
{
    vfs_initializer()
    {
        vfs.Init();
    }
} vfs_initializer;

M module-gui/test/test-catch/test-gui.cpp => module-gui/test/test-catch/test-gui.cpp +9 -1
@@ 6,6 6,7 @@
#include "mock/InitializedFontManager.hpp"
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file

#include <vfs.hpp>
#include <memory>
#include <functional>
#include <iostream>


@@ 25,7 26,6 @@
#include <module-gui/gui/widgets/Label.hpp>
#include <module-gui/gui/widgets/BoxLayout.hpp>
#include <module-gui/gui/widgets/Image.hpp>
#include <vfs.hpp>

#include <mock/TestWindow.hpp>



@@ 33,6 33,14 @@ using namespace std;

class vfs vfs;

struct vfs_initializer
{
    vfs_initializer()
    {
        vfs.Init();
    }
} vfs_init;

TEST_CASE("Test BoundingBox intersect")
{
    gui::BoundingBox result;

M module-services/service-desktop/tests/unittest.cpp => module-services/service-desktop/tests/unittest.cpp +9 -4
@@ 19,11 19,17 @@

class vfs vfs;

struct vfs_initializer
{
    vfs_initializer()
    {
        vfs.Init();
    }
} vfs_initializer;

TEST_CASE("System Update Tests")
{
    ServiceDesktop serviceDesktop;
    vfs.Init();
    UpdatePureOS updateOS(&serviceDesktop);
    UpdatePureOS updateOS(nullptr);

    updateos::UpdateError err = updateOS.prepareTempDirForUpdate();
    REQUIRE(err == updateos::UpdateError::NoError);


@@ 39,7 45,6 @@ TEST_CASE("System Update Tests")

TEST_CASE("Factory Reset Test")
{
    vfs.Init();

    std::string sysdir = purefs::dir::eMMC_disk;
    sysdir += "/factory-test/sys";

M module-utils/board/linux/log.cpp => module-utils/board/linux/log.cpp +2 -1
@@ 94,7 94,8 @@ static void _log_Log(
    std::cout << loggerBuffer;
}

void log_Log(logger_level level, const char *file, int line, const char *function, const char *fmt, ...)
__attribute__((weak)) void log_Log(
    logger_level level, const char *file, int line, const char *function, const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);

M module-utils/test/test_time_conversion.cpp => module-utils/test/test_time_conversion.cpp +20 -0
@@ 4,6 4,25 @@
#include <iostream>
#include <time/time_conversion.hpp>
#include <algorithm>
#include <thread.hpp>

namespace
{
    thread_local void *tls_pointers[configNUM_THREAD_LOCAL_STORAGE_POINTERS];
}

extern "C"
{

    void *ff_stdio_pvTaskGetThreadLocalStoragePointer(TaskHandle_t, BaseType_t xIndex)
    {
        return tls_pointers[xIndex];
    }
    void ff_stdio_vTaskSetThreadLocalStoragePointer(TaskHandle_t, BaseType_t xIndex, void *pvValue)
    {
        tls_pointers[xIndex] = pvValue;
    }
}

/// crap null stream from internets
/// (https://stackoverflow.com/questions/8243743/is-there-a-null-stdostream-implementation-in-c-or-libraries)


@@ 213,6 232,7 @@ int main(int argc, char *argv[])
{
    time_t time_today = 0;

    vfs.Init();
    // get reference Today time
    if (bsp::rtc_GetCurrentTimestamp(&time_today)) {
        std::cerr << "Error on gettime" << std::endl;

M module-utils/test/unittest_duration.cpp => module-utils/test/unittest_duration.cpp +8 -0
@@ 15,6 15,14 @@
class vfs vfs;
utils::i18 localize;

struct vfs_initializer
{
    vfs_initializer()
    {
        vfs.Init();
    }
} vfs_intializer;

TEST_CASE("Duration - creation")
{
    utils::localize.Switch(utils::Lang::En);

M module-vfs/CMakeLists.txt => module-vfs/CMakeLists.txt +37 -2
@@ 1,9 1,44 @@
cmake_minimum_required(VERSION 3.12)
include(thirdparty)

project(module-vfs VERSION 1.0
        DESCRIPTION "VFS module library")

set(SOURCES "" vfs-utils.cpp)
set(FREERTOS_FAT_SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_crc.c
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_dir.c
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_error.c
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_fat.c
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_file.c
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_format.c
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_ioman.c
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_locking.c
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_memory.c
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_stdio.c
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_string.c
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_sys.c
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/ff_time.c
        )

third_party_source_optimization(${FREERTOS_FAT_SOURCES})

# Disable some warnings due to ugly embedded hacks
# in the FREERTOS FAT implementation
if(${PROJECT_TARGET}  STREQUAL "TARGET_Linux")
set_property(SOURCE ${FREERTOS_FAT_SOURCES} APPEND_STRING PROPERTY COMPILE_FLAGS
" -Wno-overflow -Wno-type-limits -Wno-format -Wno-int-to-pointer-cast -Wno-pointer-to-int-cast")
endif()

set(PROJECT_INCLUDES
        ${CMAKE_CURRENT_SOURCE_DIR}/board/freeRTOS_FAT/include
        ${CMAKE_CURRENT_SOURCE_DIR}/board/free_rtos_custom/include
        )

set(SOURCES ""
        ${FREERTOS_FAT_SOURCES}
        vfs-utils.cpp
        vfs.cpp
)

if(NOT ${PROJECT_TARGET}  STREQUAL "TARGET_Linux")
    include(targets/Target_Cross.cmake)


@@ 45,7 80,7 @@ target_include_directories(${PROJECT_NAME}

)

target_link_libraries(${PROJECT_NAME} ${TARGET_LIBRARIES} module-os module-bsp module-utils)
target_link_libraries(${PROJECT_NAME} ${TARGET_LIBRARIES} module-bsp module-utils)

if(${PROJECT_TARGET} STREQUAL "TARGET_Linux")
    target_link_libraries(${PROJECT_NAME} -lstdc++fs)

D module-vfs/board/cross/freeRTOS_FAT => module-vfs/board/cross/freeRTOS_FAT +0 -1
@@ 1,1 0,0 @@
Subproject commit 49b471939b4b3de570d34fabf97ad6e3541e19e9

A module-vfs/board/cross/free_rtos_custom/portable/vfs.cpp => module-vfs/board/cross/free_rtos_custom/portable/vfs.cpp +50 -0
@@ 0,0 1,50 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "vfs.hpp"
#include "ff_eMMC_user_disk.hpp"

vfs::vfs() : emmc()
{}

vfs::~vfs()
{
    FF_eMMC_user_DiskDelete(emmcFFDisk);
    emmc.DeInit();
}

void vfs::Init()
{
    emmc.Init();

    emmcFFDisk = FF_eMMC_user_DiskInit(purefs::dir::eMMC_disk.c_str(), &emmc);

    /* Print out information on the disk. */
    FF_eMMC_user_DiskShowPartition(emmcFFDisk);

    bootConfig.os_root_path = purefs::dir::eMMC_disk;

    if (loadBootConfig(getCurrentBootJSON())) {
        LOG_INFO("vfs::Init osType %s root:%s", bootConfig.os_type.c_str(), bootConfig.os_root_path.c_str());
        if (ff_chdir(bootConfig.os_root_path.c_str()) != 0) {
            LOG_ERROR("vfs::Init can't chdir to %s", bootConfig.os_root_path.c_str());
        }
    }
    else {
        LOG_ERROR("vfs::Init unable to determine OS type, fallback to %s", bootConfig.os_root_path.c_str());
    }

    LOG_INFO("vfs::Init running on ARM osRootPath: %s", bootConfig.os_root_path.c_str());

    // this should already exist and have user data in it
    // if it does not create an exmpty directory so that sqlite3 does not fault
    if (isDir(purefs::dir::user_disk.c_str()) == false) {
        LOG_ERROR("vfs::Init looks like %s does not exist, try to create it", purefs::dir::user_disk.c_str());
        if (ff_mkdir(purefs::dir::user_disk.c_str()) != 0) {
            LOG_ERROR("vfs::Init can't create %s directory", purefs::dir::user_disk.c_str());
        }
    }
    else {
        LOG_INFO("vfs::Init looks like %s exists", purefs::dir::user_disk.c_str());
    }
}

A module-vfs/board/freeRTOS_FAT => module-vfs/board/freeRTOS_FAT +1 -0
@@ 0,0 1,1 @@
Subproject commit ab9206031204ee0f34b4dad8fd874db4f128d23a

R module-vfs/board/cross/free_rtos_custom/include/FreeRTOSFATConfig.h => module-vfs/board/free_rtos_custom/include/FreeRTOSFATConfig.h +0 -0
A module-vfs/board/linux/free_rtos_custom/include/ff_image_user_disk.hpp => module-vfs/board/linux/free_rtos_custom/include/ff_image_user_disk.hpp +14 -0
@@ 0,0 1,14 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once

#include "vfs.hpp"

namespace freertos_fat::internals
{
    FF_Disk_t *diskInit(const char *pcName, const char img_path[]);
    BaseType_t diskDelete(FF_Disk_t *pxDisk);
    BaseType_t diskShowPartition(FF_Disk_t *pxDisk);
    void diskFlush(FF_Disk_t *pxDisk);
    uint8_t diskIsPresent(void);
} // namespace freertos_fat::internals

A module-vfs/board/linux/free_rtos_custom/portable/common.cpp => module-vfs/board/linux/free_rtos_custom/portable/common.cpp +12 -0
@@ 0,0 1,12 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <ctime>

extern "C"
{
    time_t FreeRTOS_time(time_t *pxTime)
    {
        return std::time(pxTime);
    }
}

A module-vfs/board/linux/free_rtos_custom/portable/ff_image_user_disk.cpp => module-vfs/board/linux/free_rtos_custom/portable/ff_image_user_disk.cpp +426 -0
@@ 0,0 1,426 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
//
#include <vfs.hpp>
#include <ff_image_user_disk.hpp>
#include <filesystem>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

namespace
{
    constexpr auto FSL_SDMMC_DEFAULT_BLOCK_SIZE = 512UL;
    constexpr auto eMMCSIGNATURE                = 0x61606362;
    constexpr auto mainIO_MANAGER_CACHE_SIZE    = 15UL * FSL_SDMMC_DEFAULT_BLOCK_SIZE;
    constexpr auto eMMCPARTITION_NUMBER         = 0;

    constexpr auto eMMCHIDDEN_SECTOR_COUNT = 8;
    constexpr auto eMMCPRIMARY_PARTITIONS  = 1;

    constexpr auto eMMCHUNDRED_64_BIT = 100ULL;

    constexpr auto eMMCBYTES_PER_MB   = 1024ull * 1024ull;
    constexpr auto eMMCSECTORS_PER_MB = eMMCBYTES_PER_MB / 512ull;

    alignas(32) uint8_t emmc_user_CacheBuffer[mainIO_MANAGER_CACHE_SIZE];
} // namespace

namespace
{
    class file_operation
    {
        static constexpr auto SECT_SIZE = 512;

      public:
        file_operation(const file_operation &) = delete;
        file_operation &operator=(const file_operation &) = delete;
        // Constructor
        file_operation(const char file_name[])
        {
            m_fd = ::open(file_name, O_RDWR, O_SYNC);
            if (m_fd < 0) {
                throw std::system_error();
            }
        }
        // Destructor
        ~file_operation()
        {
            if (m_fd) {
                ::close(m_fd);
            }
        }
        // Read sector
        int read(void *buf, unsigned sector_number, unsigned sector_count)
        {
            int ret = ::lseek(m_fd, sector_number * SECT_SIZE, SEEK_SET);
            if (ret < 0) {
                return ret;
            }
            auto to_read = sector_count * SECT_SIZE;
            auto buf_b   = reinterpret_cast<uint8_t *>(buf);
            do {
                ret = ::read(m_fd, buf_b, to_read);
                if (ret < 0) {
                    return ret;
                }
                to_read -= ret;
                buf_b += ret;
            } while (to_read > 0);
            return 0;
        }
        // Write sector
        int write(const void *buf, unsigned sector_number, unsigned sector_count)
        {
            int ret = ::lseek(m_fd, sector_number * SECT_SIZE, SEEK_SET);
            if (ret < 0) {
                return ret;
            }
            auto to_write = sector_count * SECT_SIZE;
            auto buf_b    = reinterpret_cast<const uint8_t *>(buf);
            do {
                ret = ::write(m_fd, buf_b, to_write);
                if (ret < 0) {
                    return ret;
                }
                to_write -= ret;
                buf_b += ret;
            } while (to_write > 0);
            return 0;
        }

      private:
        int m_fd{};
    };

    int32_t writeBlocks(uint8_t *pucSource, uint32_t ulSectorNumber, uint32_t ulSectorCount, FF_Disk_t *pxDisk)
    {
        int32_t lReturn = FF_ERR_NONE;
        auto mmc_card   = static_cast<file_operation *>(pxDisk->pvTag);

        if (pxDisk != NULL) {

            if (pxDisk->ulSignature != eMMCSIGNATURE) {
                /* The disk structure is not valid because it doesn't contain a
                magic number written to the disk when it was created. */
                lReturn = FF_ERR_IOMAN_DRIVER_FATAL_ERROR | FF_ERRFLAG;
            }
            else if (pxDisk->xStatus.bIsInitialised == pdFALSE) {
                /* The disk has not been initialised. */
                lReturn = FF_ERR_IOMAN_OUT_OF_BOUNDS_WRITE | FF_ERRFLAG;
            }
            else if (ulSectorNumber >= pxDisk->ulNumberOfSectors) {
                /* The start sector is not within the bounds of the disk. */
                lReturn = (FF_ERR_IOMAN_OUT_OF_BOUNDS_WRITE | FF_ERRFLAG);
            }
            else if ((pxDisk->ulNumberOfSectors - ulSectorNumber) < ulSectorCount) {
                /* The end sector is not within the bounds of the disk. */
                lReturn = (FF_ERR_IOMAN_OUT_OF_BOUNDS_WRITE | FF_ERRFLAG);
            }
            else {
                if (mmc_card->write(pucSource, ulSectorNumber, ulSectorCount) < 0) {
                    lReturn = FF_ERR_DEVICE_DRIVER_FAILED;
                }
            }
        }
        else {
            lReturn = FF_ERR_NULL_POINTER | FF_ERRFLAG;
        }

        return lReturn;
    }

    int32_t readBlocks(uint8_t *pucDestination, uint32_t ulSectorNumber, uint32_t ulSectorCount, FF_Disk_t *pxDisk)
    {
        int32_t lReturn = FF_ERR_NONE;

        auto mmc_card = static_cast<file_operation *>(pxDisk->pvTag);

        if (pxDisk != NULL) {
            if (pxDisk->ulSignature != eMMCSIGNATURE) {
                /* The disk structure is not valid because it doesn't contain a
                magic number written to the disk when it was created. */
                lReturn = FF_ERR_IOMAN_DRIVER_FATAL_ERROR | FF_ERRFLAG;
            }
            else if (pxDisk->xStatus.bIsInitialised == pdFALSE) {
                /* The disk has not been initialised. */
                lReturn = FF_ERR_IOMAN_OUT_OF_BOUNDS_WRITE | FF_ERRFLAG;
            }
            else if (ulSectorNumber >= pxDisk->ulNumberOfSectors) {
                /* The start sector is not within the bounds of the disk. */
                lReturn = (FF_ERR_IOMAN_OUT_OF_BOUNDS_WRITE | FF_ERRFLAG);
            }
            else if ((pxDisk->ulNumberOfSectors - ulSectorNumber) < ulSectorCount) {
                /* The end sector is not within the bounds of the disk. */
                lReturn = (FF_ERR_IOMAN_OUT_OF_BOUNDS_WRITE | FF_ERRFLAG);
            }
            else {
                if (mmc_card->read(pucDestination, ulSectorNumber, ulSectorCount) < 0) {
                    lReturn = FF_ERR_DEVICE_DRIVER_FAILED;
                }
            }
        }
        else {
            lReturn = FF_ERR_NULL_POINTER | FF_ERRFLAG;
        }

        return lReturn;
    }

    FF_Error_t prvPartitionAndFormatDisk(FF_Disk_t *pxDisk)
    {
        FF_PartitionParameters_t xPartition;
        FF_Error_t xError;

        /* Create a single partition that fills all available space on the disk. */
        memset(&xPartition, '\0', sizeof(xPartition));
        xPartition.ulSectorCount   = pxDisk->ulNumberOfSectors;
        xPartition.ulHiddenSectors = eMMCHIDDEN_SECTOR_COUNT;
        xPartition.xPrimaryCount   = eMMCPRIMARY_PARTITIONS;
        xPartition.eSizeType       = eSizeIsQuota;

        /* Partition the disk */
        xError = FF_Partition(pxDisk, &xPartition);
        LOG_PRINTF("FF_Partition: %s\r\n", (const char *)FF_GetErrMessage(xError));

        if (FF_isERR(xError) == pdFALSE) {
            /* Format the partition. */
            xError = FF_Format(pxDisk, eMMCPARTITION_NUMBER, pdFALSE, pdFALSE);
            LOG_PRINTF("FF_eMMC_user_DiskInit: FF_Format: %s\r\n", (const char *)FF_GetErrMessage(xError));
        }

        return xError;
    }

    FF_Error_t FF_InvalidateCache(FF_IOManager_t *pxIOManager)
    {
        BaseType_t xIndex;
        FF_Error_t xError;

        if (pxIOManager == NULL) {
            xError = FF_ERR_NULL_POINTER | FF_FLUSHCACHE;
        }
        else {
            xError = FF_ERR_NONE;

            FF_PendSemaphore(pxIOManager->pvSemaphore);
            {
                for (xIndex = 0; xIndex < pxIOManager->usCacheSize; xIndex++) {
                    /* If a buffers has no users and if it has been modified... */
                    if ((pxIOManager->pxBuffers[xIndex].usNumHandles != 0) ||
                        (pxIOManager->pxBuffers[xIndex].bModified == pdTRUE)) {
                        /* Can not flush all caches. */
                        xError++;
                    }
                    else {
                        /* Mark the buffer as invalid so that it won't be used again. */
                        pxIOManager->pxBuffers[xIndex].bValid = pdFALSE;
                    }
                }
            }

            FF_ReleaseSemaphore(pxIOManager->pvSemaphore);
        }

        /* Function successful if it returns 0. */
        return xError;
    }
} // namespace

namespace freertos_fat::internals
{
    // *** ***
    FF_Disk_t *diskInit(const char *pcName, const char img_path[])
    {
        FF_Error_t xError;
        FF_Disk_t *pxDisk = NULL;
        FF_CreationParameters_t xParameters;

        /* Attempt to allocated the FF_Disk_t structure. */
        pxDisk = (FF_Disk_t *)malloc(sizeof(FF_Disk_t));

        if (pxDisk != NULL) {
            /* Start with every member of the structure set to zero. */
            memset(pxDisk, '\0', sizeof(FF_Disk_t));

            /* The signature is used by the disk read and disk write functions to
            ensure the disk being accessed is a eMMC disk. */
            pxDisk->ulSignature = eMMCSIGNATURE;

            /* The number of sectors is recorded for bounds checking in the read and
            write functions. */
            pxDisk->ulNumberOfSectors = std::filesystem::file_size(img_path) / FSL_SDMMC_DEFAULT_BLOCK_SIZE;
            /* Store pointer to mmc_card_t structure */
            pxDisk->pvTag = new file_operation(img_path);

            /* Create the IO manager that will be used to control the eMMC disk. */
            memset(&xParameters, '\0', sizeof(xParameters));
            xParameters.pucCacheMemory = emmc_user_CacheBuffer;
            xParameters.ulMemorySize   = mainIO_MANAGER_CACHE_SIZE;
            xParameters.ulSectorSize   = FSL_SDMMC_DEFAULT_BLOCK_SIZE;
            xParameters.fnWriteBlocks  = writeBlocks;
            xParameters.fnReadBlocks   = readBlocks;
            xParameters.pxDisk         = pxDisk;

            /* Driver is reentrant so xBlockDeviceIsReentrant can be set to pdTRUE.
            In this case the semaphore is only used to protect FAT data
            structures. */
            xParameters.pvSemaphore             = (void *)xSemaphoreCreateRecursiveMutex();
            xParameters.xBlockDeviceIsReentrant = pdFALSE;

            /* Check the validity of the xIOManagerCacheSize parameter. */
            configASSERT((mainIO_MANAGER_CACHE_SIZE % xParameters.ulSectorSize) == 0);
            configASSERT((mainIO_MANAGER_CACHE_SIZE >= (size_t)(2 * xParameters.ulSectorSize)));

            pxDisk->pxIOManager = FF_CreateIOManger(&xParameters, &xError);

            if ((pxDisk->pxIOManager != NULL) && (FF_isERR(xError) == pdFALSE)) {
                /* Record that the eMMC disk has been initialised. */
                pxDisk->xStatus.bIsInitialised = pdTRUE;

                /* Record the partition number the FF_Disk_t structure is, then
                mount the partition. */
                pxDisk->xStatus.bPartitionNumber = eMMCPARTITION_NUMBER;

#if 1
                /* Mount the partition. */
                xError = FF_Mount(pxDisk, pxDisk->xStatus.bPartitionNumber);
                LOG_PRINTF("FF_eMMC_user_DiskInit: FF_Mount: %s\r\n", (const char *)FF_GetErrMessage(xError));

                if (FF_isERR(xError) == pdFALSE) {
                    /* The partition mounted successfully, add it to the virtual
                    file system - where it will appear as a directory off the file
                    system's root directory. */
                    FF_FS_Add(pcName, pxDisk);
                }
                else
#endif
                {
                    /* This is completely new eMMC disk so at first it needs proper formatting. */
                    xError = prvPartitionAndFormatDisk(pxDisk);

                    /* Mount the partition again, it should complete without fault. */
                    if (FF_isERR(xError) == pdFALSE) {
                        /* Record the partition number the FF_Disk_t structure is, then
                        mount the partition. */
                        pxDisk->xStatus.bPartitionNumber = eMMCPARTITION_NUMBER;

                        /* Mount the partition. */
                        xError = FF_Mount(pxDisk, eMMCPARTITION_NUMBER);
                        LOG_PRINTF("FF_eMMC_user_DiskInit: FF_Mount: %s\r\n", (const char *)FF_GetErrMessage(xError));
                    }

                    if (FF_isERR(xError) == pdFALSE) {
                        /* The partition mounted successfully, add it to the virtual
                        file system - where it will appear as a directory off the file
                        system's root directory. */
                        FF_FS_Add(pcName, pxDisk);
                    }
                }
            }
            else {
                LOG_PRINTF("FF_eMMC_user_DiskInit: FF_CreateIOManger: %s\r\n", (const char *)FF_GetErrMessage(xError));

                /* The disk structure was allocated, but the disk's IO manager could
                not be allocated, so free the disk again. */
                diskDelete(pxDisk);
                pxDisk = NULL;
            }
        }
        else {
            LOG_PRINTF("FF_eMMC_user_DiskInit: Malloc failed\r\n");
        }

        return pxDisk;
    }
    BaseType_t diskDelete(FF_Disk_t *pxDisk)
    {
        if (pxDisk != NULL) {
            pxDisk->ulSignature            = 0;
            pxDisk->xStatus.bIsInitialised = 0;
            if (pxDisk->pvTag) {
                auto fops = reinterpret_cast<file_operation *>(pxDisk->pvTag);
                delete fops;
            }
            if (pxDisk->pxIOManager != NULL) {
                FF_DeleteIOManager(pxDisk->pxIOManager);
            }
            // TODO: Fixme in the laters stage
            free(pxDisk);
        }
        return pdPASS;
    }

    BaseType_t diskShowPartition(FF_Disk_t *pxDisk)
    {

        FF_Error_t xError;
        uint64_t ullFreeSectors;
        uint32_t ulTotalSizeMB, ulFreeSizeMB;
        int iPercentageFree;
        FF_IOManager_t *pxIOManager;
        const char *pcTypeName = "unknown type";
        BaseType_t xReturn     = pdPASS;

        if (pxDisk == NULL) {
            xReturn = pdFAIL;
        }
        else {
            pxIOManager = pxDisk->pxIOManager;

            LOG_PRINTF("Reading FAT and calculating Free Space\r\n");

            switch (pxIOManager->xPartition.ucType) {
            case FF_T_FAT12:
                pcTypeName = "FAT12";
                break;

            case FF_T_FAT16:
                pcTypeName = "FAT16";
                break;

            case FF_T_FAT32:
                pcTypeName = "FAT32";
                break;

            default:
                pcTypeName = "UNKOWN";
                break;
            }

            FF_GetFreeSize(pxIOManager, &xError);

            ullFreeSectors  = pxIOManager->xPartition.ulFreeClusterCount * pxIOManager->xPartition.ulSectorsPerCluster;
            iPercentageFree = (int)((eMMCHUNDRED_64_BIT * ullFreeSectors + pxIOManager->xPartition.ulDataSectors / 2) /
                                    ((uint64_t)pxIOManager->xPartition.ulDataSectors));

            ulTotalSizeMB = pxIOManager->xPartition.ulDataSectors / eMMCSECTORS_PER_MB;
            ulFreeSizeMB  = (uint32_t)(ullFreeSectors / eMMCSECTORS_PER_MB);

            /* It is better not to use the 64-bit format such as %Lu because it
            might not be implemented. */

            LOG_PRINTF("Type           %8u (%s)\r\n", pxIOManager->xPartition.ucType, pcTypeName);
            LOG_PRINTF("VolLabel       '%8s' \r\n", pxIOManager->xPartition.pcVolumeLabel);
            LOG_PRINTF("TotalSectors   %8u\r\n", pxIOManager->xPartition.ulTotalSectors);
            LOG_PRINTF("SecsPerCluster %8u\r\n", pxIOManager->xPartition.ulSectorsPerCluster);
            LOG_PRINTF("Size           %8u MB\r\n", ulTotalSizeMB);
            LOG_PRINTF("FreeSize       %8u MB ( %d perc free )\r\n", ulFreeSizeMB, iPercentageFree);
        }

        return xReturn;
    }
    void diskFlush(FF_Disk_t *pxDisk)
    {
        FF_FlushCache(pxDisk->pxIOManager);

        if (FF_InvalidateCache(pxDisk->pxIOManager) != 0) {
            /* Not all buffers could be invalidated. */
        }
    }
    uint8_t diskIsPresent(void)
    {
        return 1;
    }
} // namespace freertos_fat::internals

A module-vfs/board/linux/free_rtos_custom/portable/vfs.cpp => module-vfs/board/linux/free_rtos_custom/portable/vfs.cpp +56 -0
@@ 0,0 1,56 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
//
#include "vfs.hpp"
#include "ff_image_user_disk.hpp"

namespace
{
    constexpr auto image_name = "PurePhone.img";
}

vfs::vfs()
{}

vfs::~vfs()
{
    freertos_fat::internals::diskDelete(emmcFFDisk);
}

void vfs::Init()
{
    if (emmcFFDisk) {
        LOG_WARN("Disk manager already initialized");
        return;
    }
    emmcFFDisk = freertos_fat::internals::diskInit(purefs::dir::eMMC_disk.c_str(), image_name);

    /* Print out information on the disk. */
    freertos_fat::internals::diskShowPartition(emmcFFDisk);

    bootConfig.os_root_path = purefs::dir::eMMC_disk;

    if (loadBootConfig(getCurrentBootJSON())) {
        LOG_INFO("vfs::Init osType %s root:%s", bootConfig.os_type.c_str(), bootConfig.os_root_path.c_str());
        if (ff_chdir(bootConfig.os_root_path.c_str()) != 0) {
            LOG_ERROR("vfs::Init can't chdir to %s", bootConfig.os_root_path.c_str());
        }
    }
    else {
        LOG_ERROR("vfs::Init unable to determine OS type, fallback to %s", bootConfig.os_root_path.c_str());
    }

    LOG_INFO("vfs::Init running on ARM osRootPath: %s", bootConfig.os_root_path.c_str());

    // this should already exist and have user data in it
    // if it does not create an exmpty directory so that sqlite3 does not fault
    if (isDir(purefs::dir::user_disk.c_str()) == false) {
        LOG_ERROR("vfs::Init looks like %s does not exist, try to create it", purefs::dir::user_disk.c_str());
        if (ff_mkdir(purefs::dir::user_disk.c_str()) != 0) {
            LOG_ERROR("vfs::Init can't create %s directory", purefs::dir::user_disk.c_str());
        }
    }
    else {
        LOG_INFO("vfs::Init looks like %s exists", purefs::dir::user_disk.c_str());
    }
}

D module-vfs/board/linux/vfs.cpp => module-vfs/board/linux/vfs.cpp +0 -256
@@ 1,256 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "../../vfs.hpp"

#include <fstream>
#include <iostream>
#include <filesystem>
#include <cstdio>
#include <cstddef>

#include <dirent.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <log/log.hpp>

namespace fs = std::filesystem;

vfs::vfs()
{}

vfs::~vfs()
{}

void vfs::Init()
{
    // whatever current path on Linux we treat that as the root for our app
    bootConfig.os_root_path = "./";
    LOG_DEBUG("vfs::Iinit running on Linux osRootPath: %s", bootConfig.os_root_path.c_str());
}

std::string vfs::relativeToRoot(const std::string path)
{
    return (bootConfig.os_root_path / fs::path(path).relative_path()).relative_path();
}

vfs::FILE *vfs::fopen(const char *filename, const char *mode)
{
    return std::fopen(relativeToRoot(filename).c_str(), mode);
}

int vfs::fclose(FILE *stream)
{
    return std::fclose(stream);
}

int vfs::remove(const char *name)
{
    return std::remove(relativeToRoot(name).c_str());
}

size_t vfs::fread(void *ptr, size_t size, size_t count, FILE *stream)
{
    return std::fread(ptr, size, count, stream);
}

size_t vfs::fwrite(const void *ptr, size_t size, size_t count, FILE *stream)
{
    return std::fwrite(ptr, size, count, stream);
}

int vfs::fseek(FILE *stream, long int offset, int origin)
{
    return std::fseek(stream, offset, origin);
}

long int vfs::ftell(FILE *stream)
{
    return std::ftell(stream);
}

void vfs::rewind(FILE *stream)
{
    std::rewind(stream);
}

bool vfs::eof(FILE *stream)
{
    return std::feof(stream);
}

size_t vfs::filelength(FILE *stream)
{

    size_t currPos = std::ftell(stream);

    std::fseek(stream, 0, SEEK_END);
    size_t size = std::ftell(stream);
    std::fseek(stream, currPos, SEEK_SET);

    return size;
}

char *vfs::fgets(char *buffer, size_t count, FILE *stream)
{
    return std::fgets(buffer, count, stream);
}

std::string vfs::getcurrdir()
{
    char cwd[PATH_MAX];
    if (getcwd(cwd, sizeof(cwd)) != NULL) {
        return std::string{cwd};
    }
    else {
        return "";
    }
}

static inline bool hasEnding(std::string const &fullString, std::string const &ending)
{
    if (fullString.length() >= ending.length()) {
        return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(), ending));
    }
    else {
        return false;
    }
}

std::vector<vfs::DirectoryEntry> vfs::listdir(const char *path, const std::string &ext, const bool bypassRootCheck)
{
    std::vector<DirectoryEntry> dir_list;
    FileAttributes attribute = FileAttributes::ReadOnly;
    size_t fileSize          = 0;

    for (auto &p : fs::directory_iterator(bypassRootCheck ? path : relativeToRoot(path))) {
        if (fs::is_directory(p)) {
            attribute = FileAttributes ::Directory;
        }
        else if ((fs::status(p).permissions() & fs::perms::owner_write) != fs::perms::none) {
            attribute = FileAttributes ::Writable;
            fileSize  = std::filesystem::file_size(p);
        }
        else if ((fs::status(p).permissions() & fs::perms::owner_read) != fs::perms::none) {
            attribute = FileAttributes ::ReadOnly;
            fileSize  = std::filesystem::file_size(p);
        }

        auto pathStr = p.path().string();
        auto path    = pathStr.substr(pathStr.find_last_of("/\\") + 1);

        if (ext.empty()) {
            dir_list.push_back(DirectoryEntry{path, attribute, static_cast<uint32_t>(fileSize)});
        }
        else {
            if (hasEnding(path, ext))
                dir_list.push_back(DirectoryEntry{path, attribute, static_cast<uint32_t>(fileSize)});
        }
    }

    return dir_list;
}

std::string vfs::getline(FILE *stream, uint32_t length)
{
    uint32_t currentPosition = ftell(stream);

    // allocate memory to read number of signs defined by length param. Size of buffer is increased by 1 to add string's
    // null terminator.
    std::unique_ptr<char[]> buffer(new char[length + 1]);
    memset(buffer.get(), 0, length + 1);

    uint32_t bytesRead = fread(buffer.get(), 1, length, stream);

    // search buffer for /n sign
    for (uint32_t i = 0; i < bytesRead; ++i) {
        if (buffer[i] == 0x0A) {
            buffer[i] = 0;
            fseek(stream, currentPosition + i + 1, SEEK_SET);
            break;
        }
    }

    std::string ret = std::string(buffer.get());

    return ret;
}

vfs::FilesystemStats vfs::getFilesystemStats()
{
    return vfs::FilesystemStats();
}

bool vfs::isDir(const char *path)
{
    if (path == nullptr)
        return false;

    struct stat fileStatus;

    const int ret = stat(relativeToRoot(path).c_str(), &fileStatus);
    if (ret == 0) {
        return (S_ISDIR(fileStatus.st_mode));
    }
    else {
        return false;
    }
}

bool vfs::fileExists(const char *path)
{
    if (path == nullptr)
        return false;

    struct stat fileStatus;
    const int ret = stat(relativeToRoot(path).c_str(), &fileStatus);
    if (ret == 0) {
        return true;
    }
    return false;
}

int vfs::deltree(const char *path)
{
    if (path != nullptr) {
        std::error_code ec;
        std::filesystem::remove_all(relativeToRoot(path), ec);
        return ec.value();
    }
    else
        return -1;
}

int vfs::mkdir(const char *dir)
{
    if (dir != nullptr) {
        return ::mkdir(relativeToRoot(dir).c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
    }
    else
        return -1;
}

int vfs::rename(const char *oldname, const char *newname)
{
    if (oldname != nullptr && newname != nullptr)
        return std::rename(relativeToRoot(oldname).c_str(), relativeToRoot(newname).c_str());
    else
        return -1;
}

std::string vfs::lastErrnoToStr()
{
    return strerror(errno);
}

size_t vfs::fprintf(FILE *stream, const char *format, ...)
{
    va_list argList;
    size_t ret;
    va_start(argList, format);
    ret = std::vfprintf(stream, format, argList);
    va_end(argList);
    return ret;
}

M module-vfs/targets/Target_Cross.cmake => module-vfs/targets/Target_Cross.cmake +3 -28
@@ 1,34 1,9 @@
include(thirdparty)

set(FREERTOS_FAT_SOURCES
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_crc.c
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_dir.c
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_error.c
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_fat.c
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_file.c
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_format.c
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_ioman.c
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_locking.c
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_memory.c
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_stdio.c
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_string.c
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_sys.c
		${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/ff_time.c
)

set(BOARD_DIR_INCLUDES
        ${CMAKE_CURRENT_SOURCE_DIR}/board/cross/freeRTOS_FAT/include
        ${CMAKE_CURRENT_SOURCE_DIR}/board/cross/free_rtos_custom/include
        CACHE INTERNAL ""
)


third_party_source_optimization(${FREERTOS_FAT_SOURCES})

set( BOARD_SOURCES
	${FREERTOS_FAT_SOURCES} 
	${CMAKE_CURRENT_SOURCE_DIR}/board/cross/vfs.cpp
	${CMAKE_CURRENT_SOURCE_DIR}/board/cross/free_rtos_custom/portable/ff_eMMC_user_disk.cpp
	${CMAKE_CURRENT_SOURCE_DIR}/board/cross/free_rtos_custom/portable/common.cpp
	${CMAKE_CURRENT_SOURCE_DIR}/board/cross/free_rtos_custom/portable/vfs.cpp
	CACHE INTERNAL ""
)

set(BOARD_DIR_INCLUDES  ${CMAKE_CURRENT_SOURCE_DIR}/board/cross/free_rtos_custom/include CACHE INTERNAL "")

M module-vfs/targets/Target_Linux.cmake => module-vfs/targets/Target_Linux.cmake +7 -2
@@ 1,3 1,8 @@
set(BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/board/linux/vfs.cpp CACHE INTERNAL "")
set(BOARD_SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/board/linux/free_rtos_custom/portable/ff_image_user_disk.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/board/linux/free_rtos_custom/portable/common.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/board/linux/free_rtos_custom/portable/vfs.cpp
        CACHE INTERNAL ""
)

set(BOARD_DIR_INCLUDES  ${CMAKE_CURRENT_SOURCE_DIR}/board/linux CACHE INTERNAL "")
set(BOARD_DIR_INCLUDES  ${CMAKE_CURRENT_SOURCE_DIR}/board/linux/free_rtos_custom/include CACHE INTERNAL "")

M module-vfs/tests/unittest_vfs.cpp => module-vfs/tests/unittest_vfs.cpp +38 -14
@@ 13,12 13,21 @@

#include <cstdint>

#include <thread.hpp>

class vfs vfs;

TEST_CASE("Test case 1")
struct vfs_initializer
{
    vfs_initializer()
    {
        vfs.Init();
    }
} vfs_initializer;

TEST_CASE("Test vfs case 1")
{

    vfs.Init();

    const size_t testBufferSize = 1024 * 1024;



@@ 44,8 53,19 @@ TEST_CASE("Test case 1")
    // current directory is the build dir
    // vfs adds sys/ to the path we need to got outside sys (bad!)
    // and look for some files there
    auto dirList = vfs.listdir("module-vfs/test_dir");
    REQUIRE(dirList.size() == 3);
    vfs.mkdir("module-vfs");
    vfs.mkdir("module-vfs/test_dir2");

    fd = vfs.fopen("module-vfs/test1.txt", "a");
    REQUIRE(fd != nullptr);
    REQUIRE(vfs.fclose(fd) == 0);

    fd = vfs.fopen("module-vfs/test2.txt", "a");
    REQUIRE(fd != nullptr);
    REQUIRE(vfs.fclose(fd) == 0);

    auto dirList = vfs.listdir("module-vfs");
    REQUIRE(dirList.size() >= 4);
    for (auto &dir : dirList) {
        if (dir.fileName == "test_dir2") {
            REQUIRE(dir.attributes == vfs::FileAttributes::Directory);


@@ 59,7 79,6 @@ TEST_CASE("Test case 1")
#define RANDDOM_TESTS 4
TEST_CASE("Random strings")
{
    vfs.Init();
    const std::string allowedChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    std::string randomIds8, randomIds16, randomIds32;
    randomIds8  = vfs.generateRandomId(8);


@@ 81,7 100,6 @@ TEST_CASE("Random strings")

TEST_CASE("CRC32 tests")
{
    vfs.Init();
    unsigned long crc32 = 0;
    char crcBuf[purefs::buffer::crc_char_size];
    std::string randomData = vfs.generateRandomId(128);


@@ 99,6 117,7 @@ TEST_CASE("CRC32 tests")
    bytesWritten = vfs.fwrite(&crcBuf, 1, purefs::buffer::crc_char_size, fdCRC);
    REQUIRE(bytesWritten == purefs::buffer::crc_char_size);
    REQUIRE(vfs.fclose(fdCRC) == 0);
    REQUIRE(vfs.fclose(fd) == 0);

    REQUIRE(vfs.verifyCRC("testFile.txt") == true);
    REQUIRE(vfs.remove("testFile.txt") == 0);


@@ 107,12 126,17 @@ TEST_CASE("CRC32 tests")

TEST_CASE("File loading and saving")
{
    vfs.Init();
    std::string fileContents = vfs.loadFileAsString("module-vfs/test_dir/test1.txt");
    REQUIRE(strcmp(fileContents.c_str(), "abcd") == 0);

    vfs.replaceWithString("module-vfs/test_dir/testWrite.txt", "this is a test");
    fileContents = vfs.loadFileAsString("module-vfs/test_dir/testWrite.txt");
    static constexpr auto test_str = "abcd";
    auto fd                        = vfs.fopen("test1.txt", "w");
    REQUIRE(fd != nullptr);
    const auto slen = std::strlen(test_str);
    REQUIRE(vfs.fwrite(test_str, 1, slen, fd) == slen);
    vfs.fclose(fd);
    std::string fileContents = vfs.loadFileAsString("test1.txt");
    REQUIRE(strcmp(fileContents.c_str(), test_str) == 0);
    vfs.mkdir("module-vfs/test_dirx");
    vfs.replaceWithString("module-vfs/test_dirx/testWrite.txt", "this is a test");
    fileContents = vfs.loadFileAsString("module-vfs/test_dirx/testWrite.txt");
    REQUIRE(strcmp(fileContents.c_str(), "this is a test") == 0);
    REQUIRE(vfs.remove("module-vfs/test_dir/testWrite.txt") == 0);
}
\ No newline at end of file
    REQUIRE(vfs.remove("module-vfs/test_dirx/testWrite.txt") == 0);
}

R module-vfs/board/cross/vfs.cpp => module-vfs/vfs.cpp +1 -47
@@ 1,8 1,7 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "../../vfs.hpp"
#include "ff_eMMC_user_disk.hpp"
#include "vfs.hpp"

#include <memory>



@@ 18,51 17,6 @@ disk. */
#define eMMCSIGNATURE             0x61606362
#define mainIO_MANAGER_CACHE_SIZE (15UL * FSL_SDMMC_DEFAULT_BLOCK_SIZE)

vfs::vfs() : emmc()
{}

vfs::~vfs()
{
    FF_eMMC_user_DiskDelete(emmcFFDisk);
    emmc.DeInit();
}

void vfs::Init()
{
    emmc.Init();

    emmcFFDisk = FF_eMMC_user_DiskInit(purefs::dir::eMMC_disk.c_str(), &emmc);

    /* Print out information on the disk. */
    FF_eMMC_user_DiskShowPartition(emmcFFDisk);

    bootConfig.os_root_path = purefs::dir::eMMC_disk;

    if (loadBootConfig(getCurrentBootJSON())) {
        LOG_INFO("vfs::Init osType %s root:%s", bootConfig.os_type.c_str(), bootConfig.os_root_path.c_str());
        if (ff_chdir(bootConfig.os_root_path.c_str()) != 0) {
            LOG_ERROR("vfs::Init can't chdir to %s", bootConfig.os_root_path.c_str());
        }
    }
    else {
        LOG_ERROR("vfs::Init unable to determine OS type, fallback to %s", bootConfig.os_root_path.c_str());
    }

    LOG_INFO("vfs::Init running on ARM osRootPath: %s", bootConfig.os_root_path.c_str());

    // this should already exist and have user data in it
    // if it does not create an exmpty directory so that sqlite3 does not fault
    if (isDir(purefs::dir::user_disk.c_str()) == false) {
        LOG_ERROR("vfs::Init looks like %s does not exist, try to create it", purefs::dir::user_disk.c_str());
        if (ff_mkdir(purefs::dir::user_disk.c_str()) != 0) {
            LOG_ERROR("vfs::Init can't create %s directory", purefs::dir::user_disk.c_str());
        }
    }
    else {
        LOG_INFO("vfs::Init looks like %s exists", purefs::dir::user_disk.c_str());
    }
}

vfs::FILE *vfs::fopen(const char *filename, const char *mode)
{
    return ff_fopen(relativeToRoot(filename).c_str(), mode);

M module-vfs/vfs.hpp => module-vfs/vfs.hpp +5 -9
@@ 12,13 12,12 @@
#include <log/log.hpp>
#include <json/json11.hpp>

#ifdef TARGET_Linux
#include <cstdio>
#else
#include "ff_stdio.h"
#ifndef TARGET_Linux
#include "board/cross/eMMC/eMMC.hpp"
#endif

#include "ff_stdio.h"

#define PATH_SYS      "/sys"
#define PATH_USER     "user"
#define PATH_CURRENT  "current"


@@ 107,11 106,7 @@ namespace purefs
class vfs
{
  public:
#ifdef TARGET_Linux
    using FILE = std::FILE;
#else
    using FILE = FF_FILE;
#endif

    enum class FileAttributes
    {


@@ 177,9 172,10 @@ class vfs

#ifndef TARGET_Linux
    bsp::eMMC emmc;
    FF_Disk_t *emmcFFDisk;
#endif

    FF_Disk_t *emmcFFDisk{};

    static void computeCRC32(FILE *file, unsigned long *outCrc32);
    static bool verifyCRC(const std::string filePath, const unsigned long crc32);
    static bool verifyCRC(const fs::path filePath);

M test/CMakeLists.txt => test/CMakeLists.txt +1 -0
@@ 51,6 51,7 @@ function(add_catch2_executable)
    # disable logs in unit tests
    if (NOT ${ENABLE_TEST_LOGS})
    	target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-logs.cpp)
    	target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-freertos-tls.cpp)
    endif (NOT ${ENABLE_TEST_LOGS})

    target_link_libraries(${_TESTNAME} PRIVATE Catch2::Catch2)

A test/mock-freertos-tls.cpp => test/mock-freertos-tls.cpp +22 -0
@@ 0,0 1,22 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <thread.hpp>

namespace
{
    thread_local void *tls_pointers[configNUM_THREAD_LOCAL_STORAGE_POINTERS];
}

extern "C"
{

    void *ff_stdio_pvTaskGetThreadLocalStoragePointer(TaskHandle_t, BaseType_t xIndex)
    {
        return tls_pointers[xIndex];
    }
    void ff_stdio_vTaskSetThreadLocalStoragePointer(TaskHandle_t, BaseType_t xIndex, void *pvValue)
    {
        tls_pointers[xIndex] = pvValue;
    }
}

M test/mock-logs.cpp => test/mock-logs.cpp +2 -2
@@ 3,8 3,8 @@

#include <log/log.hpp>
#include <cstdarg>

void log_Log(logger_level level, const char *file, int line, const char *function, const char *fmt, ...)
__attribute__((weak)) void log_Log(
    logger_level level, const char *file, int line, const char *function, const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);