~aleteoryx/muditaos

2f3e24c7d5f2425f8e7f5647cea3ebfe75a8fb30 — Lucjan Bryndza 4 years ago 6851c04
[EGD-6835] Add inotify mechanism in the VFS

Add file monitoring event mechanism to the
VFS subsystem. Now it will be possible to
monitoring file changes in the reistered
path

Signed-off-by: Lucjan Bryndza <lucjan.bryndza@mudita.com>
29 files changed, 803 insertions(+), 236 deletions(-)

M .gitignore
M Target_RT1051.cmake
M module-services/service-desktop/CMakeLists.txt
M module-services/service-fileindexer/CMakeLists.txt
M module-services/service-fileindexer/ServiceFileIndexer.cpp
M module-services/service-fileindexer/ServiceFileIndexer.hpp
M module-services/service-fileindexer/StartupIndexer.cpp
D module-services/service-fileindexer/messages/FileChangeMessage.cpp
D module-services/service-fileindexer/messages/FileChangeMessage.hpp
M module-vfs/CMakeLists.txt
A module-vfs/include/internal/purefs/fs/notifier.hpp
D module-vfs/include/user/deprecated/vfsNotifier.hpp
R module-vfs/include/{internal => user}/purefs/fs/directory_handle.hpp
R module-vfs/include/{internal => user}/purefs/fs/file_handle.hpp
M module-vfs/include/user/purefs/fs/filesystem.hpp
A module-vfs/include/user/purefs/fs/fsnotify.hpp
R module-vfs/include/{internal => user}/purefs/fs/handle_mapper.hpp
A module-vfs/include/user/purefs/fs/inotify.hpp
A module-vfs/include/user/purefs/fs/inotify_flags.hpp
A module-vfs/include/user/purefs/fs/inotify_message.hpp
R module-vfs/include/{internal => user}/purefs/fs/mount_flags.hpp
R module-vfs/include/{internal => user}/purefs/fs/mount_point.hpp
D module-vfs/src/deprecated/vfsNotifier.cpp
M module-vfs/src/purefs/fs/filesystem.cpp
M module-vfs/src/purefs/fs/filesystem_syscalls.cpp
A module-vfs/src/purefs/fs/fsnotify.cpp
A module-vfs/src/purefs/fs/notifier.cpp
M module-vfs/tests/CMakeLists.txt
A module-vfs/tests/unittest_filesystem_inotify.cpp
M .gitignore => .gitignore +2 -0
@@ 60,3 60,5 @@ update/

# often created by the visual studio code
/null.d

test/test_env
\ No newline at end of file

M Target_RT1051.cmake => Target_RT1051.cmake +1 -0
@@ 95,6 95,7 @@ set(BOARD_DIR_INCLUDES
        ${CMAKE_CURRENT_LIST_DIR}/board/rt1051/newlib/include
        ${CMAKE_SOURCE_DIR}/module-utils/CrashDebug/CrashCatcher/include
        ${CMAKE_SOURCE_DIR}/module-utils/CrashDebug/CrashCatcher/Core/src
        ${CMAKE_SOURCE_DIR}/module-vfs/include/internal
)

set(TARGET_LIBRARIES

M module-services/service-desktop/CMakeLists.txt => module-services/service-desktop/CMakeLists.txt +1 -0
@@ 60,6 60,7 @@ target_link_libraries(${PROJECT_NAME}
        module-cellular
        crc32
        microtar
        utils-bootconfig
)

if (${ENABLE_TESTS})

M module-services/service-fileindexer/CMakeLists.txt => module-services/service-fileindexer/CMakeLists.txt +0 -1
@@ 18,7 18,6 @@ target_link_options(${PROJECT_NAME} PUBLIC ${TARGET_LINK_OPTIONS})
target_sources( ${PROJECT_NAME}
	PRIVATE
	"ServiceFileIndexer.cpp"
	"messages/FileChangeMessage.cpp"
	"StartupIndexer.cpp"
	"notesIndexer.cpp"
)

M module-services/service-fileindexer/ServiceFileIndexer.cpp => module-services/service-fileindexer/ServiceFileIndexer.cpp +40 -14
@@ 1,13 1,23 @@
// 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 <log/log.hpp>
#include <purefs/filesystem_paths.hpp>
#include <purefs/fs/inotify.hpp>
#include "ServiceFileIndexer.hpp"
#include "notesIndexer.hpp"
#include "messages/FileChangeMessage.hpp"
#include "Constants.hpp"
#include <fileref.h>
#include <tag.h>
#include "notesIndexer.hpp"
#include "Constants.hpp"
#include <purefs/fs/inotify_message.hpp>

namespace
{
    inline auto getMusicPath()
    {
        return purefs::createPath(purefs::dir::getUserDiskPath(), "music").string();
    }
} // namespace

namespace service
{


@@ 20,6 30,15 @@ namespace service
    // When receive notification handler
    sys::MessagePointer ServiceFileIndexer::DataReceivedHandler(sys::DataMessage *msg, sys::ResponseMessage *resp)
    {
        auto inotify = dynamic_cast<purefs::fs::message::inotify *>(msg);
        if (inotify) {
            LOG_ERROR("Inotify event %s %08x", inotify->name.c_str(), int(inotify->flags));
        }
        else {
            LOG_ERROR("Not a inotify message");
        }

#if 0
        auto fcm = dynamic_cast<msg::FileChangeMessage *>(msg);
        if (fcm) {
            switch (fcm->event()) {


@@ 47,6 66,7 @@ namespace service
            }
            return std::make_shared<sys::ResponseMessage>();
        }
#endif
        return std::make_shared<sys::ResponseMessage>(sys::ReturnCodes::Unresolved);
    }



@@ 54,23 74,29 @@ namespace service
    sys::ReturnCodes ServiceFileIndexer::InitHandler()
    {
        /*
        vfs.registerNotificationHandler(
            [_this = shared_from_this()](std::string_view new_path, vfs::FsEvent event, std::string_view old_path) {
                namespace fs       = std::filesystem;
                const auto new_ext = fs::path(new_path).extension().string();
                auto msg           = std::make_shared<msg::FileChangeMessage>(new_path, event, old_path);
                _this->bus.sendUnicast(msg, std::string(service::name::file_indexer));
            });
        mStartupIndexer.start(shared_from_this(), service::name::file_indexer);
        */

        mfsNotifier = purefs::fs::inotify_create(shared_from_this());
        if (!mfsNotifier) {
            LOG_ERROR("Unable to create inotify object");
            return sys::ReturnCodes::Failure;
        }
        const int err = mfsNotifier->add_watch(getMusicPath(), purefs::fs::inotify_flags::close_write);
        if (err) {
            LOG_ERROR("Unable to create inotify watch errno: %i", err);
            return sys::ReturnCodes::Failure;
        }
        return sys::ReturnCodes::Success;
    }

    sys::ReturnCodes ServiceFileIndexer::DeinitHandler()
    {
        /*
        vfs.registerNotificationHandler(nullptr);
        */
        const int err = mfsNotifier->rm_watch(getMusicPath());
        if (err) {
            LOG_ERROR("Unable to remove watch errno: %i", err);
            return sys::ReturnCodes::Failure;
        }
        return sys::ReturnCodes::Success;
    }


M module-services/service-fileindexer/ServiceFileIndexer.hpp => module-services/service-fileindexer/ServiceFileIndexer.hpp +8 -0
@@ 8,6 8,11 @@
#include "Constants.hpp"
#include "StartupIndexer.hpp"

namespace purefs::fs
{
    class inotify;
}

namespace service
{



@@ 28,6 33,9 @@ namespace service
        auto onRenameFile(std::string_view oldPath, std::string_view newPath) -> void;
        auto onAudioContentChanged(std::string_view path) -> void;
        auto onTextContentChanged(std::string_view path) -> void;

      private:
        std::shared_ptr<purefs::fs::inotify> mfsNotifier;
        detail::StartupIndexer mStartupIndexer;
    };


M module-services/service-fileindexer/StartupIndexer.cpp => module-services/service-fileindexer/StartupIndexer.cpp +1 -3
@@ 2,10 2,8 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "StartupIndexer.hpp"
#include "messages/FileChangeMessage.hpp"
#include <Timers/TimerFactory.hpp>
#include <filesystem>
//#include <ff_stdio_listdir_recursive.h>
#include <purefs/filesystem_paths.hpp>
#include "Constants.hpp"



@@ 63,7 61,7 @@ namespace service::detail
            mIdxTimer = sys::TimerFactory::createPeriodicTimer(
                svc.get(), "file_indexing", std::chrono::milliseconds{timer_indexing_time}, [this, svc](sys::Timer &) {
                    if (!mMsgs.empty()) {
                        svc->bus.sendUnicast(mMsgs.front(), std::string(service::name::file_indexer));
                        // svc->bus.sendUnicast(mMsgs.front(), std::string(service::name::file_indexer));
                        mMsgs.pop_front();
                    }
                    else {

D module-services/service-fileindexer/messages/FileChangeMessage.cpp => module-services/service-fileindexer/messages/FileChangeMessage.cpp +0 -11
@@ 1,11 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 "FileChangeMessage.hpp"

namespace service::msg
{
    FileChangeMessage::FileChangeMessage(std::string_view new_path, evt_t ev, std::string_view old_path)
        : DataMessage(MessageType::FileContentModified), mNewPath(new_path), mEvent(ev), mOldPath(old_path)
    {}
} // namespace service::msg

D module-services/service-fileindexer/messages/FileChangeMessage.hpp => module-services/service-fileindexer/messages/FileChangeMessage.hpp +0 -36
@@ 1,36 0,0 @@
// 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 <Service/Message.hpp>
#include <vfsNotifier.hpp>
namespace service::msg
{
    class FileChangeMessage final : public sys::DataMessage
    {
      public:
        using evt_t = vfsn::utility::vfsNotifier::FsEvent;
        FileChangeMessage(std::string_view new_path, evt_t ev, std::string_view old_path);
        virtual ~FileChangeMessage()           = default;
        FileChangeMessage(FileChangeMessage &) = delete;
        FileChangeMessage &operator=(FileChangeMessage &) = delete;
        [[nodiscard]] auto newPath() const noexcept
        {
            return mNewPath;
        }
        [[nodiscard]] auto oldPath() const noexcept
        {
            return mOldPath;
        }
        [[nodiscard]] auto event() const noexcept
        {
            return mEvent;
        }

      private:
        const std::string mNewPath;
        const evt_t mEvent;
        const std::string mOldPath;
    };
} // namespace service::msg

M module-vfs/CMakeLists.txt => module-vfs/CMakeLists.txt +22 -20
@@ 16,23 16,28 @@ set(FF_FAT_SOURCES
    ${FF_FAT_SOURCES_THIRDPARTY}
    ${CMAKE_CURRENT_SOURCE_DIR}/drivers/src/thirdparty/fatfs/ffsystem.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/drivers/src/thirdparty/fatfs/ff_glue.cpp
)
set(FF_LFS_SOURCES
    ${CMAKE_CURRENT_SOURCE_DIR}/drivers/src/thirdparty/littlefs/lfs_glue.cpp
)

set(SOURCES
        ${FF_FAT_SOURCES}
        src/purefs/filesystem_paths.cpp
        src/purefs/blkdev/disk_manager.cpp
        src/purefs/blkdev/disk.cpp
        src/purefs/blkdev/partition_parser.cpp
        src/purefs/blkdev/disk_handle.cpp
        src/purefs/fs/filesystem.cpp
        src/purefs/fs/filesystem_operations.cpp
        src/purefs/fs/filesystem_syscalls.cpp
        src/purefs/fs/filesystem_cwd.cpp
        src/purefs/vfs_subsystem.cpp
        drivers/src/purefs/fs/filesystem_vfat.cpp
        drivers/src/purefs/fs/filesystem_littlefs.cpp
    ${FF_FAT_SOURCES}
    ${FF_LFS_SOURCES}
    src/purefs/filesystem_paths.cpp
    src/purefs/blkdev/disk_manager.cpp
    src/purefs/blkdev/disk.cpp
    src/purefs/blkdev/partition_parser.cpp
    src/purefs/blkdev/disk_handle.cpp
    src/purefs/fs/filesystem.cpp
    src/purefs/fs/filesystem_operations.cpp
    src/purefs/fs/filesystem_syscalls.cpp
    src/purefs/fs/filesystem_cwd.cpp
    src/purefs/vfs_subsystem.cpp
    src/purefs/fs/notifier.cpp
    src/purefs/fs/fsnotify.cpp
    drivers/src/purefs/fs/filesystem_vfat.cpp
    drivers/src/purefs/fs/filesystem_littlefs.cpp
)




@@ 64,23 69,20 @@ target_compile_definitions(${PROJECT_NAME}

target_include_directories(${PROJECT_NAME}
        PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include/user/deprecated
        ${CMAKE_CURRENT_SOURCE_DIR}/include/user/
        ${CMAKE_CURRENT_SOURCE_DIR}/include/internal/
        ${CMAKE_CURRENT_SOURCE_DIR}/include/user
        ${CMAKE_CURRENT_SOURCE_DIR}/drivers/include
)

target_include_directories(${PROJECT_NAME}
        PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include/internal
        ${CMAKE_CURRENT_SOURCE_DIR}/drivers/include/thirdparty/fatfs
        ${CMAKE_CURRENT_SOURCE_DIR}/drivers/include/thirdparty
        ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/fatfs/source
)

target_link_libraries(${PROJECT_NAME} PUBLIC ${TARGET_LIBRARIES} module-bsp module-utils)
target_link_libraries(${PROJECT_NAME} PRIVATE littlefs )
# TODO: Temporary only remove when removing old vfs
target_link_libraries(${PROJECT_NAME} PUBLIC utils-bootconfig )
target_link_libraries(${PROJECT_NAME} PUBLIC ${TARGET_LIBRARIES} module-bsp module-utils module-sys module-os)
target_link_libraries(${PROJECT_NAME} PRIVATE littlefs)

if (${ENABLE_TESTS})
    add_subdirectory(tests)

A module-vfs/include/internal/purefs/fs/notifier.hpp => module-vfs/include/internal/purefs/fs/notifier.hpp +130 -0
@@ 0,0 1,130 @@
// 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 <map>
#include <string>
#include <memory>
#include <optional>
#include <purefs/fs/fsnotify.hpp>
#include <purefs/fs/inotify_flags.hpp>

namespace sys
{
    class Service;
}
namespace cpp_freertos
{
    class MutexRecursive;
}
namespace purefs::fs::internal
{
    //! Internal class related to the notify VFS events
    class notifier
    {
        //! Container for path item
        struct path_item
        {
            path_item(std::string_view _path, bool _read_only) : path(_path), read_only(_read_only)
            {}
            const std::string path;
            const bool read_only;
        };
        //! Container for service and subscribed events
        struct service_item
        {
            service_item(std::weak_ptr<sys::Service> _service, inotify_flags _subscribed_events)
                : service(_service), subscribed_events(_subscribed_events)
            {}
            const std::weak_ptr<sys::Service> service;
            const inotify_flags subscribed_events;
        };
        //! Container for the the event
        using container_t = std::multimap<std::string, service_item>;

      public:
        notifier();
        notifier(notifier &) = delete;
        notifier &operator=(notifier &) = delete;
        virtual ~notifier();
        //! Iterator for the registered event
        using item_it = container_t::iterator;
        /**
         * @brief Register selected path for the monitoring
         *
         * @param path Path for monitor
         * @param owner Service which should be notified
         * @param flags Event mask which should be monitored
         * @return std::optional<item_it>  Registered event iterator or nothing if failed
         */
        auto register_path(std::string_view path, std::shared_ptr<sys::Service> owner, inotify_flags flags)
            -> std::optional<item_it>;
        /**
         * @brief Unregister selected path from monitoring
         *
         * @param item Iterator returned by the @see register_path method
         */
        auto unregister_path(item_it item) -> void;
        /**
         * @brief Internal method called on file open
         *
         * @param path Open file path
         * @param fd File descriptor assigned by open method
         * @param ro File is opened in read only mode
         */
        auto notify_open(std::string_view path, int fd, bool ro) const -> void;
        /**
         * @brief Internal method called on closing gile
         *
         * @param fd File descriptor
         */
        auto notify_close(int fd) const -> void;
        /**
         * @brief Notify for event on the file system
         *
         * @param fd File descriptor for file
         * @param mask Filesystem event type
         */
        auto notify(int fd, inotify_flags mask) const -> void;
        /**
         * @brief Notify for event on filesystem on path change
         *
         * @param path Path before event occured
         * @param path_prv Path after event occured
         * @param mask Filesystem event type
         */
        auto notify(std::string_view path, std::string_view path_prv, inotify_flags mask) const -> void;
        /**
         * @brief Notify for event on the filesystem
         *
         * @param path Path related to th event
         * @param mask Filesystem event type
         */
        auto notify(std::string_view path, inotify_flags mask) const -> void
        {
            notify(path, "", mask);
        }

      private:
        /**
         * @brief Private method called for send file monitor event
         *
         * @param svc Target service
         * @param flags Filesystem event type
         * @param name Path related to the event before change
         * @param name_dst Path realated to the event after change
         */
        virtual auto send_notification(std::shared_ptr<sys::Service> svc,
                                       inotify_flags flags,
                                       std::string_view name,
                                       std::string_view name_dst) const -> void;

      private:
        //! Events container
        container_t m_events;
        //! Map file descriptors with path assiociated with it
        mutable std::map<int, path_item> m_fd_map;
        //! Internal mutex for lock the object
        std::unique_ptr<cpp_freertos::MutexRecursive> m_lock;
    };
} // namespace purefs::fs::internal

D module-vfs/include/user/deprecated/vfsNotifier.hpp => module-vfs/include/user/deprecated/vfsNotifier.hpp +0 -98
@@ 1,98 0,0 @@
// 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 <unordered_map>
#include <mutex.hpp>
#include <functional>
#include <string>
#include <thread.hpp>

namespace vfsn::utility
{
    class vfsNotifier
    {
        struct FF_FILE;
        using FILE = FF_FILE;

      public:
        //! Default constructor and destructor
        vfsNotifier()                    = default;
        vfsNotifier(const vfsNotifier &) = delete;
        vfsNotifier &operator=(const vfsNotifier &) = delete;
        //! Event type notified for indexer
        enum class FsEvent
        {
            initialized, //! Filesystem is intialized
            modified,    //! File is modified
            deleted,     //! File is deleted
            renamed
        };
        /** Notification handler type
         * Notify function callback defiinition
         */
        using NotifyHandler = std::function<void(std::string_view, FsEvent, std::string_view)>;
        /** This method is called by the vfs layer when vfs open file
         * @param[in] filename Opened file path
         * @param[in] mode Open mode
         * @param[in] Handle to opened file
         * @return None
         */
        auto onFileOpen(std::string_view filename, const char *mode, const FILE *file) noexcept -> void;
        /** This method is called by the vfs layer when vfs close file
         * @param[in] filee Closed file handle
         * @return None
         */
        auto onFileClose(const FILE *file) noexcept -> void;
        /** This method is called by the vfs layer when file is removed
         * @param[in] filename Removed file path
         * @return None
         */
        auto onFileRemove(std::string_view filename) noexcept -> void;
        /** This method is called by the vfs layer when the vfs rename file
         * @param[in] new_file New path name for the file
         * @param[in] old_file Old path for the file
         * @return None
         */
        auto onFileRename(std::string_view new_file, std::string_view old_file) noexcept -> void;
        /** This method is called by the vfs layer when the disk is intiialized
         * @return None
         */
        auto onFileSystemInitialized() noexcept -> void
        {
            notify("/", FsEvent::initialized);
        }
        /** Method for register notification handler
         * @note This function is called from the thread contest which use vfs call
         * @param[in] hwnd Notification handler funtion
         * @return None
         */
        auto registerNotificationHandler(NotifyHandler hwnd) -> void
        {
            notificationCallback = hwnd;
            threadHandle         = hwnd ? cpp_freertos::Thread::GetCurrentThreadHandle() : nullptr;
        }

      private:
        /** Private notification helper internal method
         * @param[in] file Modified file path
         * @param[in] event Notification event type
         * @param[in] old_file Old file path
         * @return None
         */
        auto notify(std::string_view file, FsEvent event, std::string_view old_file = "") -> void
        {
            if (threadHandle != cpp_freertos::Thread::GetCurrentThreadHandle() && notificationCallback)
                notificationCallback(file, event, old_file);
        }

      private:
        //! Map for opened handles and paths
        std::unordered_map<const FILE *, std::string> mOpenedMap;
        //! Mutex for unordered map
        cpp_freertos::MutexStandard mMutex;
        //! Notification handler callback
        NotifyHandler notificationCallback;
        TaskHandle_t threadHandle;
    };
} // namespace vfsn::utility

R module-vfs/include/internal/purefs/fs/directory_handle.hpp => module-vfs/include/user/purefs/fs/directory_handle.hpp +0 -0
R module-vfs/include/internal/purefs/fs/file_handle.hpp => module-vfs/include/user/purefs/fs/file_handle.hpp +1 -1
@@ 14,7 14,7 @@ namespace purefs::fs::internal
      public:
        file_handle(const file_handle &) = delete;
        auto operator=(const file_handle &) = delete;
        virtual ~file_handle()           = default;
        virtual ~file_handle()              = default;
        file_handle(std::shared_ptr<mount_point> mp, unsigned flags) : m_mount_point(mp), m_flags(flags)
        {}
        [[nodiscard]] auto error() const noexcept

M module-vfs/include/user/purefs/fs/filesystem.hpp => module-vfs/include/user/purefs/fs/filesystem.hpp +11 -1
@@ 15,6 15,7 @@
#include <purefs/fs/directory_handle.hpp>
#include <purefs/fs/mount_point.hpp>
#include <purefs/fs/mount_flags.hpp>
#include <purefs/fs/fsnotify.hpp>
#include <type_traits>

struct statvfs;


@@ 30,6 31,11 @@ namespace cpp_freertos
    class MutexRecursive;
}

namespace sys
{
    class Service;
}

namespace purefs::fs
{
    /** This is the filesystem class layer


@@ 41,6 47,7 @@ namespace purefs::fs
    namespace internal
    {
        class directory_handle;
        class notifier;
    }
    class filesystem
    {


@@ 142,6 149,9 @@ namespace purefs::fs
        auto getcwd() noexcept -> std::string_view;
        auto chdir(std::string_view name) noexcept -> int;

        /** Inotify API */
        [[nodiscard]] auto inotify_create(std::shared_ptr<sys::Service> svc) -> std::shared_ptr<inotify>;

      private:
        /** Unregister filesystem driver
         * @param[in] fsname Unique filesystem name for example fat


@@ 271,7 281,6 @@ namespace purefs::fs
                }
            }
        }

      private:
        std::weak_ptr<blkdev::disk_manager> m_diskmm;
        std::unordered_map<std::string, std::shared_ptr<filesystem_operations>> m_fstypes;


@@ 279,5 288,6 @@ namespace purefs::fs
        std::unordered_set<std::string> m_partitions;
        internal::handle_mapper<fsfile> m_fds;
        std::unique_ptr<cpp_freertos::MutexRecursive> m_lock;
        std::shared_ptr<internal::notifier> m_notifier;
    };
} // namespace purefs::fs

A module-vfs/include/user/purefs/fs/fsnotify.hpp => module-vfs/include/user/purefs/fs/fsnotify.hpp +58 -0
@@ 0,0 1,58 @@
// 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 <string>
#include <memory>
#include <purefs/fs/inotify_flags.hpp>

namespace sys
{
    class Service;
}
namespace cpp_freertos
{
    class MutexRecursive;
}
namespace purefs::fs
{
    namespace internal
    {
        class notifier;
        struct inotify_container;
    } // namespace internal

    //! Inotifier object returned by the monitoring API
    class inotify
    {
      public:
        ~inotify();
        /** Constructor for the inotify user object
         * @param[in] svc Service owner
         * @param[in] notifier Master notifier object
         */
        inotify(std::shared_ptr<sys::Service> svc, std::shared_ptr<purefs::fs::internal::notifier> notifier);
        /**  Add path for monitoring for monitoring
         * @param[in] monitored_path Path or file which should be monitored
         * @param[in] event_mask Event mask for file monitor
         * @return Error code
         */
        int add_watch(std::string_view monitored_path, inotify_flags event_mask);
        /**
         * @param[in] monitored_path Monitored path for removal
         * @return Error code
         */
        int rm_watch(std::string_view monitored_path);

      private:
        //! Owning service
        std::weak_ptr<sys::Service> m_svc;
        //! Owner notifier
        std::weak_ptr<internal::notifier> m_notify;
        //! Map for the devices
        std::unique_ptr<internal::inotify_container> m_evlist;
        //! Lock the object
        std::unique_ptr<cpp_freertos::MutexRecursive> m_lock;
    };

} // namespace purefs::fs

R module-vfs/include/internal/purefs/fs/handle_mapper.hpp => module-vfs/include/user/purefs/fs/handle_mapper.hpp +0 -0
A module-vfs/include/user/purefs/fs/inotify.hpp => module-vfs/include/user/purefs/fs/inotify.hpp +22 -0
@@ 0,0 1,22 @@
// 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/vfs_subsystem.hpp>
#include <purefs/fs/fsnotify.hpp>

namespace purefs::fs
{
    /** Create inotify class which owns the notifier interface
     * @param[in] svc Owner of the service
     * @return Inotify object shared ptr or errno if nullptr
     */
    inline auto inotify_create(std::shared_ptr<sys::Service> svc) -> std::shared_ptr<inotify>
    {
        const auto vfs = subsystem::vfs_core();
        if (!vfs) {
            return nullptr;
        }
        return vfs->inotify_create(svc);
    }
} // namespace purefs::fs

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

#pragma once
namespace purefs::fs
{
    //! Event monitor flag
    enum class inotify_flags : unsigned
    {
        attrib        = 0x01, //! Attribute changed
        close_write   = 0x02, //! File closed after write
        close_nowrite = 0x04, //! File closed without write
        del           = 0x08, //! File was deleted
        move_src      = 0x10, //! File moved
        move_dst      = 0x20, //! File moved
        open          = 0x40, //! File was opended
        dmodify       = 0x80, //! Directory entry modified
    };
    inline auto operator|(inotify_flags fl1, inotify_flags fl2)
    {
        return static_cast<inotify_flags>(static_cast<unsigned>(fl1) | static_cast<unsigned>(fl2));
    }
    inline auto operator&(inotify_flags fl1, inotify_flags fl2)
    {
        return static_cast<inotify_flags>(static_cast<unsigned>(fl1) & static_cast<unsigned>(fl2));
    }
    inline auto operator&&(inotify_flags fl1, inotify_flags fl2) -> bool
    {
        return static_cast<bool>(static_cast<unsigned>(fl1) & static_cast<unsigned>(fl2));
    }
    inline auto operator||(inotify_flags fl1, inotify_flags fl2) -> bool
    {
        return static_cast<bool>(static_cast<unsigned>(fl1) | static_cast<unsigned>(fl2));
    }

} // namespace purefs::fs

A module-vfs/include/user/purefs/fs/inotify_message.hpp => module-vfs/include/user/purefs/fs/inotify_message.hpp +28 -0
@@ 0,0 1,28 @@
// 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 <Service/Message.hpp>
#include <purefs/fs/inotify_flags.hpp>

namespace purefs::fs::message
{
    //! Class message received when on new file event
    struct inotify final : public ::sys::DataMessage
    {
        /**
         * @brief Construct a new inotify object
         *
         * @param _flags Event type
         * @param _name  Filename path
         * @param _name_prev Old path (when move)
         */
        inotify(inotify_flags _flags, std::string_view _name, std::string_view _name_prev)
            : flags(_flags), name(_name), name_prev(_name_prev)
        {}
        virtual ~inotify() = default;
        const inotify_flags flags;
        const std::string name;
        const std::string name_prev;
    };
} // namespace purefs::fs::message

R module-vfs/include/internal/purefs/fs/mount_flags.hpp => module-vfs/include/user/purefs/fs/mount_flags.hpp +0 -0
R module-vfs/include/internal/purefs/fs/mount_point.hpp => module-vfs/include/user/purefs/fs/mount_point.hpp +1 -1
@@ 31,7 31,7 @@ namespace purefs::fs::internal
        {}
        mount_point(const mount_point &) = delete;
        auto operator=(const mount_point &) = delete;
        virtual ~mount_point() = default;
        virtual ~mount_point()              = default;
        auto disk() const noexcept
        {
            return m_diskh.lock();

D module-vfs/src/deprecated/vfsNotifier.cpp => module-vfs/src/deprecated/vfsNotifier.cpp +0 -38
@@ 1,38 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 "vfsNotifier.hpp"
#include <vfs.hpp>
#include <cstring>

namespace vfsn::utility
{

    auto vfsNotifier::onFileOpen(std::string_view filename, const char *mode, const FILE *file) noexcept -> void
    {
        if (file && std::strpbrk(mode, "+wa")) {
            cpp_freertos::LockGuard _lock(mMutex);
            mOpenedMap.emplace(file, vfs.getAbsolutePath(filename));
        }
    }
    auto vfsNotifier::onFileClose(const FILE *file) noexcept -> void
    {
        cpp_freertos::LockGuard _lock(mMutex);
        const auto item = mOpenedMap.find(file);
        if (item != mOpenedMap.end()) {
            const auto path = item->second;
            mOpenedMap.erase(item);
            notify(path, FsEvent::modified);
        }
    }

    auto vfsNotifier::onFileRemove(std::string_view filename) noexcept -> void
    {
        notify(vfs.getAbsolutePath(filename), FsEvent::deleted);
    }

    auto vfsNotifier::onFileRename(std::string_view new_file, std::string_view old_file) noexcept -> void
    {
        notify(vfs.getAbsolutePath(new_file), FsEvent::renamed, vfs.getAbsolutePath(old_file));
    }
} // namespace vfsn::utility

M module-vfs/src/purefs/fs/filesystem.cpp => module-vfs/src/purefs/fs/filesystem.cpp +11 -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
#include <purefs/fs/filesystem.hpp>
#include <purefs/fs/filesystem_operations.hpp>


@@ 6,6 6,8 @@
#include <purefs/blkdev/disk_manager.hpp>
#include <purefs/fs/thread_local_cwd.hpp>
#include <purefs/blkdev/disk_handle.hpp>
#include <purefs/fs/notifier.hpp>
#include <purefs/fs/fsnotify.hpp>
#include <log/log.hpp>
#include <split_sv.hpp>
#include <errno.h>


@@ 21,7 23,8 @@ namespace purefs::fs
        };
    }
    filesystem::filesystem(std::shared_ptr<blkdev::disk_manager> diskmm)
        : m_diskmm(diskmm), m_lock(new cpp_freertos::MutexRecursive)
        : m_diskmm(diskmm), m_lock(std::make_unique<cpp_freertos::MutexRecursive>()),
          m_notifier(std::make_unique<internal::notifier>())
    {}

    filesystem::~filesystem()


@@ 308,4 311,10 @@ namespace purefs::fs
            return {};
        }
    }

    auto filesystem::inotify_create(std::shared_ptr<sys::Service> svc) -> std::shared_ptr<inotify>
    {
        return std::make_shared<inotify>(svc, m_notifier);
    }

} // namespace purefs::fs

M module-vfs/src/purefs/fs/filesystem_syscalls.cpp => module-vfs/src/purefs/fs/filesystem_syscalls.cpp +47 -10
@@ 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.hpp>
#include <errno.h>


@@ 7,6 7,7 @@
#include <purefs/fs/file_handle.hpp>
#include <purefs/fs/directory_handle.hpp>
#include <purefs/fs/thread_local_cwd.hpp>
#include <purefs/fs/notifier.hpp>
#include <fcntl.h>

namespace purefs::fs


@@ 23,12 24,20 @@ namespace purefs::fs

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

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

    auto filesystem::ioctl(std::string_view path, int cmd, void *arg) noexcept -> int


@@ 38,7 47,11 @@ namespace purefs::fs

    auto filesystem::utimens(std::string_view path, std::array<timespec, 2> &tv) noexcept -> int
    {
        return invoke_fops(iaccess::ro, &filesystem_operations::utimens, path, tv);
        const auto err = invoke_fops(iaccess::ro, &filesystem_operations::utimens, path, tv);
        if (!err) {
            m_notifier->notify(path, inotify_flags::attrib);
        }
        return err;
    }

    auto filesystem::flock(int fd, int cmd) noexcept -> int


@@ 53,7 66,11 @@ namespace purefs::fs

    auto filesystem::chmod(std::string_view path, mode_t mode) noexcept -> int
    {
        return invoke_fops(iaccess::rw, &filesystem_operations::chmod, path, mode);
        const auto err = invoke_fops(iaccess::rw, &filesystem_operations::chmod, path, mode);
        if (!err) {
            m_notifier->notify(path, inotify_flags::attrib);
        }
        return err;
    }

    auto filesystem::write(int fd, const char *ptr, size_t len) noexcept -> ssize_t


@@ 88,22 105,39 @@ namespace purefs::fs

    auto filesystem::fchmod(int fd, mode_t mode) noexcept -> int
    {
        return invoke_fops(&filesystem_operations::fchmod, fd, mode);
        const auto err = invoke_fops(&filesystem_operations::fchmod, fd, mode);
        if (!err) {
            m_notifier->notify(fd, inotify_flags::attrib);
        }
        return err;
    }

    auto filesystem::symlink(std::string_view existing, std::string_view newlink) noexcept -> int
    {
        return invoke_fops_same_mp(&filesystem_operations::symlink, existing, newlink);
        const auto err = invoke_fops_same_mp(&filesystem_operations::symlink, existing, newlink);
        if (!err) {
            m_notifier->notify(newlink, inotify_flags::dmodify);
        }
        return err;
    }

    auto filesystem::link(std::string_view existing, std::string_view newlink) noexcept -> int
    {
        return invoke_fops_same_mp(&filesystem_operations::link, existing, newlink);
        const auto err = invoke_fops_same_mp(&filesystem_operations::link, existing, newlink);
        if (!err) {
            m_notifier->notify(newlink, inotify_flags::dmodify);
        }
        return err;
    }

    auto filesystem::rename(std::string_view oldname, std::string_view newname) noexcept -> int
    {
        return invoke_fops_same_mp(&filesystem_operations::rename, oldname, newname);
        const auto err = invoke_fops_same_mp(&filesystem_operations::rename, oldname, newname);
        if (!err) {
            m_notifier->notify(oldname, newname, inotify_flags::move_src);
            m_notifier->notify(newname, oldname, inotify_flags::move_dst);
        }
        return err;
    }

    auto filesystem::open(std::string_view path, int flags, int mode) noexcept -> int


@@ 129,7 163,9 @@ namespace purefs::fs
            if (err) {
                return err;
            }
            return add_filehandle(fh);
            const auto fd = add_filehandle(fh);
            m_notifier->notify_open(path, fd, (flags & O_ACCMODE) == O_RDONLY);
            return fd;
        }
        else {
            LOG_ERROR("VFS: Unable to lock fops");


@@ 142,6 178,7 @@ namespace purefs::fs
        auto ret = invoke_fops(&filesystem_operations::close, fd);
        if (!ret) {
            ret = (remove_filehandle(fd)) ? (0) : (-EBADF);
            m_notifier->notify_close(fd);
        }
        return ret;
    }

A module-vfs/src/purefs/fs/fsnotify.cpp => module-vfs/src/purefs/fs/fsnotify.cpp +83 -0
@@ 0,0 1,83 @@
// 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/fsnotify.hpp>
#include <purefs/fs/notifier.hpp>
#include <log/log.hpp>
#include <mutex.hpp>
#include <cerrno>

namespace purefs::fs
{
    namespace internal
    {
        struct inotify_container
        {
            std::map<std::string, internal::notifier::item_it> items;
        };
    } // namespace internal
    inotify::inotify(std::shared_ptr<sys::Service> svc, std::shared_ptr<internal::notifier> notifier)
        : m_svc(svc), m_notify(notifier), m_evlist(std::make_unique<internal::inotify_container>()),
          m_lock(std::make_unique<cpp_freertos::MutexRecursive>())
    {}

    inotify::~inotify()
    {
        const auto notifier = m_notify.lock();
        if (!notifier) {
            LOG_ERROR("Unable lock notifier");
        }
        const auto svc = m_svc.lock();
        if (!svc) {
            LOG_ERROR("Unable lock service");
        }
        for (const auto &[_, val] : m_evlist->items) {
            notifier->unregister_path(val);
        }
    }

    int inotify::add_watch(std::string_view monitored_path, inotify_flags event_mask)
    {
        const auto notifier = m_notify.lock();
        if (!notifier) {
            LOG_ERROR("Unable lock notifier");
            return -ENXIO;
        }
        const auto svc = m_svc.lock();
        if (!svc) {
            LOG_ERROR("Unable lock service");
            return -ENXIO;
        }
        auto it = notifier->register_path(monitored_path, svc, event_mask);
        if (!it) {
            LOG_ERROR("Unable to register path");
            return -EIO;
        }
        cpp_freertos::LockGuard _lck(*m_lock);
        m_evlist->items.emplace(std::make_pair(std::string(monitored_path), *it));
        return {};
    }

    int inotify::rm_watch(std::string_view monitored_path)
    {
        const auto notifier = m_notify.lock();
        if (!notifier) {
            LOG_ERROR("Unable lock notifier");
            return -ENXIO;
        }
        const auto svc = m_svc.lock();
        if (!svc) {
            LOG_ERROR("Unable lock service");
            return -ENXIO;
        }
        cpp_freertos::LockGuard _lck(*m_lock);
        auto it = m_evlist->items.find(std::string(monitored_path));
        if (it == std::end(m_evlist->items)) {
            LOG_ERROR("Unable to find registered path %s", std::string(monitored_path).c_str());
            return -ENOENT;
        }
        notifier->unregister_path(it->second);
        m_evlist->items.erase(it);
        return {};
    }
} // namespace purefs::fs

A module-vfs/src/purefs/fs/notifier.cpp => module-vfs/src/purefs/fs/notifier.cpp +115 -0
@@ 0,0 1,115 @@
// 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/notifier.hpp>
#include <purefs/fs/inotify_message.hpp>
#include <functional>
#include <Service/Service.hpp>
#include <purefs/fs/inotify_message.hpp>
#include <purefs/fs/thread_local_cwd.hpp>
#include <log/log.hpp>

namespace purefs::fs::internal
{
    namespace
    {
        void for_path(std::string_view path, std::function<void(std::string_view)> fun)
        {
            constexpr auto sep = '/';
            for (auto it = (path.back() == sep) ? (path.size() - 1) : (path.size()); (it && it != std::string::npos);
                 it      = path.rfind(sep, it - 1))
                fun(path.substr(0, it));
        }
        std::string absolute_path(std::string_view path)
        {
            using namespace std::string_literals;
            std::string ret;
            if (!path.empty() && path[0] != '/') {
                ret = std::string(internal::get_thread_local_cwd_path()) + "/"s + std::string(path);
            }
            else {
                ret = std::string(path);
            }
            if (!ret.empty() && ret.back() == '/') {
                ret.pop_back();
            }
            return ret;
        }
    } // namespace
    notifier::notifier() : m_lock(std::make_unique<cpp_freertos::MutexRecursive>())
    {}
    notifier::~notifier()
    {}
    auto notifier::register_path(std::string_view path, std::shared_ptr<sys::Service> owner, inotify_flags flags)
        -> std::optional<item_it>
    {
        cpp_freertos::LockGuard _lck(*m_lock);
        const auto abspath = absolute_path(path);
        // # Check if it is already registered for same path
        const auto range = m_events.equal_range(abspath);
        for (auto i = range.first; i != range.second; ++i) {
            if (i->second.service.lock() == owner) {
                return std::nullopt;
            }
        }
        return m_events.emplace(std::make_pair(abspath, service_item(owner, flags)));
    }
    auto notifier::unregister_path(item_it item) -> void
    {
        cpp_freertos::LockGuard _lck(*m_lock);
        m_events.erase(item);
    }
    auto notifier::notify(int fd, inotify_flags mask) const -> void
    {
        cpp_freertos::LockGuard _lck(*m_lock);
        const auto fname_it = m_fd_map.find(fd);
        if (fname_it != std::end(m_fd_map)) {
            notify(fname_it->second.path, mask);
        }
    }
    void notifier::notify(std::string_view path, std::string_view path_prv, inotify_flags mask) const
    {
        cpp_freertos::LockGuard _lck(*m_lock);
        const auto abs_path     = absolute_path(path);
        const auto abs_path_prv = absolute_path(path_prv);
        for_path(abs_path, [this, abs_path, abs_path_prv, mask](std::string_view path) {
            const auto range = m_events.equal_range(std::string(path));
            for (auto i = range.first; i != range.second; ++i) {
                if (i->second.subscribed_events && mask) {
                    auto svc = i->second.service.lock();
                    if (svc) {
                        send_notification(svc, mask, abs_path, abs_path_prv);
                    }
                }
            }
        });
    }
    auto notifier::notify_open(std::string_view path, int fd, bool ro) const -> void
    {
        cpp_freertos::LockGuard _lck(*m_lock);
        m_fd_map.emplace(std::make_pair(fd, path_item(path, ro)));
        notify(path, inotify_flags::open);
    }
    auto notifier::notify_close(int fd) const -> void
    {
        cpp_freertos::LockGuard _lck(*m_lock);
        const auto fname_it = m_fd_map.find(fd);
        if (fname_it != std::end(m_fd_map)) {
            notify(fname_it->first,
                   fname_it->second.read_only ? inotify_flags::close_nowrite : inotify_flags::close_write);
            m_fd_map.erase(fname_it);
        }
    }
    auto notifier::send_notification(std::shared_ptr<sys::Service> svc,
                                     inotify_flags flags,
                                     std::string_view name,
                                     std::string_view name_dst) const -> void
    {
        if (svc->GetHandle() != cpp_freertos::Thread::GetCurrentThreadHandle()) {
            auto msg = std::make_shared<message::inotify>(flags, name, name_dst);
            svc->bus.sendUnicast(std::move(msg), svc->GetName());
        }
        else {
            LOG_WARN("Sent notification to the same thread is forbidded");
        }
    }
} // namespace purefs::fs::internal

M module-vfs/tests/CMakeLists.txt => module-vfs/tests/CMakeLists.txt +17 -0
@@ 22,6 22,8 @@ add_catch2_executable(
    NAME vfs-core-fs
    SRCS
        ${CMAKE_CURRENT_LIST_DIR}/unittest_filesystem_core.cpp
    INCLUDE 
        ${CMAKE_CURRENT_SOURCE_DIR}/../include/internal
    LIBS
        module-vfs
)


@@ 38,6 40,8 @@ add_catch2_executable(
    NAME vfs-littlefs
    SRCS
        ${CMAKE_CURRENT_LIST_DIR}/unittest_filesystem_littlefs.cpp
    INCLUDE 
        ${CMAKE_CURRENT_SOURCE_DIR}/../include/internal
    LIBS
        module-vfs
    DEPS


@@ 48,12 52,25 @@ add_catch2_executable(
    NAME vfs-dualmount
    SRCS
        ${CMAKE_CURRENT_LIST_DIR}/unittest_filesystem_dualmount.cpp
    INCLUDE 
        ${CMAKE_CURRENT_SOURCE_DIR}/../include/internal

    LIBS
        module-vfs
    DEPS
        ${LITTLEFS_IMAGE}
)

add_catch2_executable(
    NAME vfs-inotify
    SRCS
        ${CMAKE_CURRENT_LIST_DIR}/unittest_filesystem_inotify.cpp
    LIBS
        module-vfs
    INCLUDE 
        ${CMAKE_CURRENT_SOURCE_DIR}/../include/internal
)

# prepare test assets
set(ASSETS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/test_dir")
set(ASSETS_TARGET_DIR "${CMAKE_BINARY_DIR}/module-vfs/test_dir")

A module-vfs/tests/unittest_filesystem_inotify.cpp => module-vfs/tests/unittest_filesystem_inotify.cpp +168 -0
@@ 0,0 1,168 @@
// 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 <purefs/fs/notifier.hpp>
#include <purefs/fs/inotify_message.hpp>

namespace purefs::fs
{

    struct notifier_mock final : public internal::notifier
    {
        virtual ~notifier_mock() = default;
        auto send_notification(std::shared_ptr<sys::Service> svc,
                               inotify_flags flags,
                               std::string_view name,
                               std::string_view name_dst) const -> void override
        {
            messages.emplace_back(flags, name, name_dst);
            xsvc.push_back(svc.get());
        }
        mutable std::vector<message::inotify> messages;
        mutable std::vector<sys::Service *> xsvc;
    };
} // namespace purefs::fs

namespace sys
{
    class Service
    {};
} // namespace sys

TEST_CASE("vfsinotify: Notifier base class test")
{
    using namespace purefs::fs;
    auto svc = std::make_shared<sys::Service>();
    notifier_mock notify;
    notify.register_path("/sys/ala",
                         svc,
                         inotify_flags::open | inotify_flags::close_nowrite | inotify_flags::close_write |
                             inotify_flags::attrib | inotify_flags::move_src | inotify_flags::move_dst);
    SECTION("open and close simple")
    {
        notify.notify_open("/sys/ala", 77, true);
        notify.notify_close(77);
        REQUIRE(notify.messages.size() == 2);
        REQUIRE(notify.messages[0].name == "/sys/ala");
        REQUIRE(notify.messages[0].name_prev.empty());
        REQUIRE((notify.messages[0].flags && inotify_flags::open));
        REQUIRE(notify.messages[1].name == "/sys/ala");
        REQUIRE(notify.messages[1].name_prev.empty());
        REQUIRE((notify.messages[1].flags && inotify_flags::close_nowrite));
        notify.messages.clear();
    }
    SECTION("open and close pairing")
    {
        notify.notify_open("/sys/ala/1", 100, true);
        notify.notify_open("/sys/ala/2", 200, false);
        notify.notify_open("/sys/ala/3", 300, true);
        REQUIRE(notify.messages.size() == 3);
        notify.notify(200, inotify_flags::attrib);
        REQUIRE(notify.messages.size() == 4);
        notify.notify_close(200);
        REQUIRE(notify.messages.size() == 5);
        notify.notify_close(200);
        REQUIRE(notify.messages.size() == 5);
        REQUIRE(notify.messages[0].name == "/sys/ala/1");
        REQUIRE((notify.messages[0].flags && inotify_flags::open));
        REQUIRE(notify.messages[1].name == "/sys/ala/2");
        REQUIRE((notify.messages[1].flags && inotify_flags::open));
        REQUIRE(notify.messages[2].name == "/sys/ala/3");
        REQUIRE((notify.messages[2].flags && inotify_flags::open));
        REQUIRE(notify.messages[3].name == "/sys/ala/2");
        REQUIRE((notify.messages[3].flags && inotify_flags::attrib));
        REQUIRE(notify.messages[4].name == "/sys/ala/2");
        REQUIRE((notify.messages[4].flags && inotify_flags::close_write));
        notify.messages.clear();
        notify.notify_close(100);
        notify.notify_close(300);
        REQUIRE(notify.messages.size() == 2);
        REQUIRE(notify.messages[0].name == "/sys/ala/1");
        REQUIRE((notify.messages[0].flags && inotify_flags::close_nowrite));
        REQUIRE(notify.messages[1].name == "/sys/ala/3");
        REQUIRE((notify.messages[1].flags && inotify_flags::close_nowrite));
        notify.notify_close(300);
        notify.notify_close(300);
        REQUIRE(notify.messages.size() == 2);
        notify.messages.clear();
    }
    SECTION("Other notifies tests")
    {
        notify.notify("/sys/ala/100", "/sys/ala/555", inotify_flags::move_src);
        notify.notify("/sys/ala/555", "/sys/ala/100", inotify_flags::move_dst);
        notify.notify("/sys/ala/999/989", inotify_flags::attrib);
        REQUIRE(notify.messages.size() == 3);
        REQUIRE(notify.messages[0].name == "/sys/ala/100");
        REQUIRE(notify.messages[0].name_prev == "/sys/ala/555");
        REQUIRE((notify.messages[0].flags && inotify_flags::move_src));
        REQUIRE(notify.messages[1].name == "/sys/ala/555");
        REQUIRE(notify.messages[1].name_prev == "/sys/ala/100");
        REQUIRE((notify.messages[1].flags && inotify_flags::move_dst));
    }
}

TEST_CASE("vfsinotify: Filtering test")
{
    using namespace purefs::fs;
    SECTION("Different mask for services")
    {
        notifier_mock notify;
        auto svc1 = std::make_shared<sys::Service>();
        auto svc2 = std::make_shared<sys::Service>();
        notify.register_path(
            "/sys/path1", svc1, inotify_flags::open | inotify_flags::close_nowrite | inotify_flags::close_write);
        notify.register_path("/sys/path1/", svc2, inotify_flags::open | inotify_flags::close_write);
        notify.notify_open("/sys/path1/file.txt", 100, false);
        notify.notify_open("/sys/path1/file1.txt", 200, true);
        REQUIRE(notify.messages.size() == 4);
        notify.notify_close(100);
        REQUIRE(notify.messages.size() == 6);
        notify.notify_close(200);
        REQUIRE(notify.messages.size() == 7);
        // Checking values in the queue
        REQUIRE(notify.messages[0].name == "/sys/path1/file.txt");
        REQUIRE((notify.messages[0].flags && inotify_flags::open));
        REQUIRE(notify.xsvc[0] == svc1.get());
        REQUIRE(notify.messages[1].name == "/sys/path1/file.txt");
        REQUIRE((notify.messages[1].flags && inotify_flags::open));
        REQUIRE(notify.xsvc[1] == svc2.get());
        REQUIRE(notify.messages[2].name == "/sys/path1/file1.txt");
        REQUIRE((notify.messages[2].flags && inotify_flags::open));
        REQUIRE(notify.xsvc[2] == svc1.get());
        REQUIRE(notify.messages[3].name == "/sys/path1/file1.txt");
        REQUIRE((notify.messages[3].flags && inotify_flags::open));
        REQUIRE(notify.xsvc[3] == svc2.get());
        REQUIRE(notify.messages[4].name == "/sys/path1/file.txt");
        REQUIRE((notify.messages[4].flags && inotify_flags::close_write));
        REQUIRE(notify.xsvc[4] == svc1.get());
        REQUIRE(notify.messages[5].name == "/sys/path1/file.txt");
        REQUIRE((notify.messages[5].flags && inotify_flags::close_write));
        REQUIRE(notify.xsvc[5] == svc2.get());
        REQUIRE(notify.messages[6].name == "/sys/path1/file1.txt");
        REQUIRE((notify.messages[6].flags && inotify_flags::close_nowrite));
        REQUIRE(notify.xsvc[6] == svc1.get());
    }
    SECTION("Different paths for services")
    {
        notifier_mock notify;
        auto svc1 = std::make_shared<sys::Service>();
        auto svc2 = std::make_shared<sys::Service>();
        notify.register_path("/sys/path1", svc1, inotify_flags::attrib);
        notify.register_path("/sys/path2", svc2, inotify_flags::attrib);
        notify.notify("/sys/path1/file.txt", inotify_flags::attrib);
        REQUIRE(notify.messages.size() == 1);
        notify.notify("/sys/path1/file2.txt", inotify_flags::attrib);
        REQUIRE(notify.messages.size() == 2);
        notify.notify("/sys/path3", inotify_flags::attrib);
        REQUIRE(notify.messages.size() == 2);
        notify.notify("/sys/path2/", inotify_flags::attrib);
        REQUIRE(notify.messages.size() == 3);
        REQUIRE(notify.messages[0].name == "/sys/path1/file.txt");
        REQUIRE(notify.xsvc[0] == svc1.get());
        REQUIRE(notify.messages[1].name == "/sys/path1/file2.txt");
        REQUIRE(notify.xsvc[1] == svc1.get());
        REQUIRE(notify.messages[2].name == "/sys/path2");
        REQUIRE(notify.xsvc[2] == svc2.get());
    }
}