From 443b76c52708d68336018629446b4dd543764e3f Mon Sep 17 00:00:00 2001
From: Sergey Yakubov <sergey.yakubov@desy.de>
Date: Tue, 28 Aug 2018 15:58:56 +0200
Subject: [PATCH] process inotify events

---
 common/cpp/include/common/data_structs.h      |   1 +
 common/cpp/include/io/io.h                    |   3 +-
 common/cpp/include/unittests/MockIO.h         |  20 +-
 common/cpp/src/system_io/system_io.cpp        |  10 +
 common/cpp/src/system_io/system_io.h          |  25 +--
 common/cpp/src/system_io/system_io_linux.cpp  |  27 ++-
 .../cpp/src/system_io/system_io_windows.cpp   |  29 +++
 .../src/event_monitor_error.h                 |   5 +
 .../src/eventmon_main.cpp                     |   8 +-
 .../src/system_folder_watch_linux.cpp         | 175 +++++++++++++++++-
 .../src/system_folder_watch_linux.h           |  14 ++
 .../file_monitor_producer/check_linux.sh      |   1 +
 tests/automatic/system_io/CMakeLists.txt      |   1 +
 .../read_subdirectories/CMakeLists.txt        |  32 ++++
 .../read_subdirectories/cleanup_linux.sh      |   4 +
 .../read_subdirectories/cleanup_windows.bat   |   3 +
 .../read_subdirectories.cpp                   |  36 ++++
 .../read_subdirectories/setup_linux.sh        |  10 +
 .../read_subdirectories/setup_windows.bat     |  14 ++
 19 files changed, 390 insertions(+), 28 deletions(-)
 create mode 100644 tests/automatic/system_io/read_subdirectories/CMakeLists.txt
 create mode 100644 tests/automatic/system_io/read_subdirectories/cleanup_linux.sh
 create mode 100644 tests/automatic/system_io/read_subdirectories/cleanup_windows.bat
 create mode 100644 tests/automatic/system_io/read_subdirectories/read_subdirectories.cpp
 create mode 100644 tests/automatic/system_io/read_subdirectories/setup_linux.sh
 create mode 100644 tests/automatic/system_io/read_subdirectories/setup_windows.bat

diff --git a/common/cpp/include/common/data_structs.h b/common/cpp/include/common/data_structs.h
index 39aa50292..dab7dd358 100644
--- a/common/cpp/include/common/data_structs.h
+++ b/common/cpp/include/common/data_structs.h
@@ -31,6 +31,7 @@ inline bool operator==(const FileInfo& lhs, const FileInfo& rhs) {
 
 using FileData = std::unique_ptr<uint8_t[]>;
 using FileInfos = std::vector<FileInfo>;
+using SubDirList = std::vector<std::string>;
 
 }
 #endif //ASAPO_FILE_INFO_H
diff --git a/common/cpp/include/io/io.h b/common/cpp/include/io/io.h
index f94e977fe..4af8883a7 100644
--- a/common/cpp/include/io/io.h
+++ b/common/cpp/include/io/io.h
@@ -96,8 +96,7 @@ class IO {
 
     virtual void            CreateNewDirectory      (const std::string& directory_name, Error* err) const = 0;
     virtual FileData        GetDataFromFile         (const std::string& fname, uint64_t fsize, Error* err) const = 0;
-    virtual void CollectFileInformationRecursively(const std::string& path, std::vector<FileInfo>* files,
-                                                   Error* err) const = 0;
+    virtual SubDirList      GetSubDirectories(const std::string& path, Error* err) const = 0;
     virtual std::vector<FileInfo>   FilesInFolder   (const std::string& folder, Error* err) const = 0;
     virtual std::string     ReadFileToString        (const std::string& fname, Error* err) const = 0;
 
diff --git a/common/cpp/include/unittests/MockIO.h b/common/cpp/include/unittests/MockIO.h
index 5287004ba..0a7b826e1 100644
--- a/common/cpp/include/unittests/MockIO.h
+++ b/common/cpp/include/unittests/MockIO.h
@@ -185,15 +185,6 @@ class MockIO : public IO {
 
     MOCK_CONST_METHOD3(WriteDataToFile_t, ErrorInterface * (const std::string& fname, const uint8_t* data, size_t fsize));
 
-    void CollectFileInformationRecursively(const std::string& path, std::vector<FileInfo>* files,
-                                           Error* err) const override {
-        ErrorInterface* error = nullptr;
-        CollectFileInformationRecursivly_t(path, files, &error);
-        err->reset(error);
-    }
-    MOCK_CONST_METHOD3(CollectFileInformationRecursivly_t, void(const std::string& path, std::vector<FileInfo>* files,
-                       ErrorInterface** err));
-
     std::vector<FileInfo> FilesInFolder(const std::string& folder, Error* err) const override {
         ErrorInterface* error = nullptr;
         auto data = FilesInFolder_t(folder, &error);
@@ -202,6 +193,17 @@ class MockIO : public IO {
     }
     MOCK_CONST_METHOD2(FilesInFolder_t, std::vector<FileInfo>(const std::string& folder, ErrorInterface** err));
 
+
+    SubDirList GetSubDirectories(const std::string& path, Error* err) const override {
+        ErrorInterface* error = nullptr;
+        auto data = GetSubDirectories_t(path, &error);
+        err->reset(error);
+        return data;
+    };
+
+    MOCK_CONST_METHOD2(GetSubDirectories_t, SubDirList(const std::string& path, ErrorInterface** err));
+
+
     std::string ReadFileToString(const std::string& fname, Error* err) const override {
         ErrorInterface* error = nullptr;
         auto data = ReadFileToString_t(fname, &error);
diff --git a/common/cpp/src/system_io/system_io.cpp b/common/cpp/src/system_io/system_io.cpp
index 630685b38..9cf619807 100644
--- a/common/cpp/src/system_io/system_io.cpp
+++ b/common/cpp/src/system_io/system_io.cpp
@@ -114,6 +114,16 @@ FileInfos SystemIO::FilesInFolder(const std::string& folder, Error* err) const {
     return files;
 }
 
+SubDirList SystemIO::GetSubDirectories(const std::string& path, Error* err) const {
+    SubDirList list;
+    GetSubDirectoriesRecursively(path, &list, err);
+    if (*err != nullptr) {
+        return {};
+    }
+    return list;
+}
+
+
 void asapo::SystemIO::CreateNewDirectory(const std::string& directory_name, Error* err) const {
     if(_mkdir(directory_name.c_str()) == -1) {
         *err = GetLastError();
diff --git a/common/cpp/src/system_io/system_io.h b/common/cpp/src/system_io/system_io.h
index 9b7c1feb7..156a25b48 100644
--- a/common/cpp/src/system_io/system_io.h
+++ b/common/cpp/src/system_io/system_io.h
@@ -61,7 +61,9 @@ class SystemIO final : public IO {
     static ssize_t		_recv(SocketDescriptor socket_fd, void* buffer, size_t length);
     static ssize_t      _read(FileDescriptor fd, void* buffer, size_t length);
     static ssize_t      _write(FileDescriptor fd, const void* buffer, size_t count);
-
+    void            CollectFileInformationRecursively(const std::string& path, std::vector<FileInfo>* files,
+                                                      Error* err) const;
+    void            GetSubDirectoriesRecursively(const std::string& path, SubDirList* subdirs, Error* err) const;
   public:
     /*
      * Special
@@ -95,17 +97,16 @@ class SystemIO final : public IO {
     /*
      * Filesystem
      */
-    FileDescriptor  Open(const std::string& filename, int open_flags, Error* err) const;
-    void            Close(FileDescriptor fd, Error* err) const;
-    size_t          Read(FileDescriptor fd, void* buf, size_t length, Error* err) const;
-    size_t          Write(FileDescriptor fd, const void* buf, size_t length, Error* err) const;
-    void            CreateNewDirectory(const std::string& directory_name, Error* err) const;
-    FileData        GetDataFromFile(const std::string& fname, uint64_t fsize, Error* err) const;
-    Error           WriteDataToFile  (const std::string& fname, const FileData& data, size_t length) const;
-    Error           WriteDataToFile(const std::string& fname, const uint8_t* data, size_t length) const;
-    void            CollectFileInformationRecursively(const std::string& path, std::vector<FileInfo>* files,
-                                                      Error* err) const;
-    std::string     ReadFileToString(const std::string& fname, Error* err) const;
+    FileDescriptor  Open(const std::string& filename, int open_flags, Error* err) const override;
+    void            Close(FileDescriptor fd, Error* err) const override;
+    size_t          Read(FileDescriptor fd, void* buf, size_t length, Error* err) const override;
+    size_t          Write(FileDescriptor fd, const void* buf, size_t length, Error* err) const override;
+    void            CreateNewDirectory(const std::string& directory_name, Error* err) const override;
+    FileData        GetDataFromFile(const std::string& fname, uint64_t fsize, Error* err) const override;
+    Error           WriteDataToFile  (const std::string& fname, const FileData& data, size_t length) const override;
+    Error           WriteDataToFile(const std::string& fname, const uint8_t* data, size_t length) const override;
+    SubDirList      GetSubDirectories(const std::string& path, Error* err) const override;
+    std::string     ReadFileToString(const std::string& fname, Error* err) const override;
 };
 }
 
diff --git a/common/cpp/src/system_io/system_io_linux.cpp b/common/cpp/src/system_io/system_io_linux.cpp
index d5c2eefc6..76342ac4f 100644
--- a/common/cpp/src/system_io/system_io_linux.cpp
+++ b/common/cpp/src/system_io/system_io_linux.cpp
@@ -145,7 +145,32 @@ void ProcessFileEntity(const struct dirent* entity, const std::string& path,
     files->push_back(file_info);
 }
 
-/** @} */
+
+
+void SystemIO::GetSubDirectoriesRecursively(const std::string& path, SubDirList* subdirs, Error* err) const {
+    errno = 0;
+    auto dir = opendir((path).c_str());
+    if (dir == nullptr) {
+        *err = GetLastError();
+        (*err)->Append(path);
+        return;
+    }
+
+    while (struct dirent* current_entity = readdir(dir)) {
+        if (IsDirectory(current_entity)) {
+            std::string subdir = path + "/" + current_entity->d_name;
+            subdirs->push_back(subdir);
+            GetSubDirectoriesRecursively(subdir, subdirs, err);
+        }
+        if (*err != nullptr) {
+            errno = 0;
+            closedir(dir);
+            return;
+        }
+    }
+    *err = GetLastError();
+    closedir(dir);
+}
 
 void SystemIO::CollectFileInformationRecursively(const std::string& path,
                                                  FileInfos* files, Error* err)  const {
diff --git a/common/cpp/src/system_io/system_io_windows.cpp b/common/cpp/src/system_io/system_io_windows.cpp
index 64842edc7..506356067 100644
--- a/common/cpp/src/system_io/system_io_windows.cpp
+++ b/common/cpp/src/system_io/system_io_windows.cpp
@@ -154,6 +154,35 @@ void ProcessFileEntity(const WIN32_FIND_DATA& f, const std::string& path,
     files->push_back(file_info);
 }
 
+void GetSubDirectoriesRecursively(const std::string& path, SubDirList* subdirs, Error* err) const {
+    WIN32_FIND_DATA find_data;
+    HANDLE handle = FindFirstFile((path + "\\*.*").c_str(), &find_data);
+    if (handle == INVALID_HANDLE_VALUE) {
+        *err = IOErrorFromGetLastError();
+        (*err)->Append(path);
+        return;
+    }
+
+    do {
+        if (IsDirectory(find_data)) {
+            std::string subdir = path + "\\" + find_data.cFileName;
+            subdirs->push_back(subdir);
+            GetSubDirectoriesRecursively(subdir, subdirs, err);
+        }
+        if (*err) {
+            FindClose(handle);
+            return;
+        }
+    } while (FindNextFile(handle, &find_data));
+
+    if (FindClose(handle)) {
+        *err = nullptr;
+    } else {
+        *err = IOErrorFromGetLastError();
+    }
+}
+
+
 void SystemIO::CollectFileInformationRecursively(const std::string& path,
                                                  FileInfos* files, Error* err) const {
     WIN32_FIND_DATA find_data;
diff --git a/producer/event_monitor_producer/src/event_monitor_error.h b/producer/event_monitor_producer/src/event_monitor_error.h
index 071616a19..0c4c566d2 100644
--- a/producer/event_monitor_producer/src/event_monitor_error.h
+++ b/producer/event_monitor_producer/src/event_monitor_error.h
@@ -37,6 +37,11 @@ class EventMonitorErrorTemplate : public SimpleErrorTemplate {
         return error_type_;
     }
 
+    inline Error Generate(std::string sub_error) const noexcept {
+        return Error(new EventMonitorError(error_ + ": " + sub_error, error_type_));
+    }
+
+
     inline Error Generate() const noexcept override {
         return Error(new EventMonitorError(error_, error_type_));
     }
diff --git a/producer/event_monitor_producer/src/eventmon_main.cpp b/producer/event_monitor_producer/src/eventmon_main.cpp
index 057d77c7d..f1a1c7855 100644
--- a/producer/event_monitor_producer/src/eventmon_main.cpp
+++ b/producer/event_monitor_producer/src/eventmon_main.cpp
@@ -69,7 +69,7 @@ int main (int argc, char* argv[]) {
     stop_signal = 0;
     std::signal(SIGINT, SignalHandler);
     std::signal(SIGTERM, SignalHandler);
-
+    siginterrupt(SIGINT, 1);
 
     const auto& logger = asapo::GetDefaultEventMonLogger();
     logger->SetLogLevel(GetEventMonConfig()->log_level);
@@ -81,13 +81,17 @@ int main (int argc, char* argv[]) {
 
     err = event_detector->StartMonitoring();
     if (err) {
+        logger->Error(err->Explain());
         return EXIT_FAILURE;
     }
 
     int i = 0;
-    while (!stop_signal) {
+    while (true) {
         asapo::EventHeader event_header;
         auto err = event_detector->GetNextEvent(&event_header);
+        if (stop_signal) {
+            break; // we check it here because signal can interrupt system call (ready by inotify and result n incomplete event data)
+        }
         if (err) {
             if (err != asapo::EventMonitorErrorTemplates::kNoNewEvent) {
                 logger->Error("cannot retrieve next event: " + err->Explain());
diff --git a/producer/event_monitor_producer/src/system_folder_watch_linux.cpp b/producer/event_monitor_producer/src/system_folder_watch_linux.cpp
index 933581e00..05df62c32 100644
--- a/producer/event_monitor_producer/src/system_folder_watch_linux.cpp
+++ b/producer/event_monitor_producer/src/system_folder_watch_linux.cpp
@@ -1,16 +1,187 @@
 #include "system_folder_watch_linux.h"
 
+
+#include "event_monitor_error.h"
+#include "eventmon_logger.h"
+
 namespace asapo {
 
+Error SystemFolderWatch::AddFolderToWatch(std::string folder, bool recursive) {
+    int id = inotify_add_watch(watch_fd_, folder.c_str(),
+                               IN_CLOSE_WRITE
+                               | IN_MOVED_TO
+                               | IN_MOVED_FROM
+                               | IN_CREATE
+                               | IN_DELETE_SELF
+//                               | IN_MOVE_SELF
+                               | IN_EXCL_UNLINK
+                               | IN_DONT_FOLLOW
+                               | IN_ONLYDIR);
+    if (id == -1) {
+        return EventMonitorErrorTemplates::kSystemError.Generate("cannot add watch for " + folder);
+    } else {
+        GetDefaultEventMonLogger()->Debug("added folder to monitor: " + folder);
+    }
+    watched_folders_paths_[id] = folder;
+    if (recursive) {
+        Error err;
+        auto subdirs = io_-> GetSubDirectories(folder, &err);
+        if (err) {
+            return err;
+        }
+        for (auto& subdir : subdirs) {
+            err = AddFolderToWatch(subdir, false);
+            if (err) {
+                return err;
+            }
+        }
+
+    }
+    return nullptr;
+}
+
+
 Error SystemFolderWatch::StartFolderMonitor(const std::vector<std::string>& monitored_folders) {
+    watch_fd_ = inotify_init();
+    if (watch_fd_ == -1) {
+        return EventMonitorErrorTemplates::kSystemError.Generate("cannot initialize inotify");
+    }
+    for (auto& folder : monitored_folders) {
+        auto err = AddFolderToWatch(folder, true);
+        if (err) {
+            return EventMonitorErrorTemplates::kSystemError.Generate("cannot initialize inotify: " + err->Explain());
+        }
+    }
     return nullptr;
 }
 
+Error SystemFolderWatch::ProcessInotifyEvent(struct inotify_event* i, FileEvents* events) {
+
+    printf("    wd =%2d; ", i->wd);
+    if (i->cookie > 0)
+        printf("cookie =%4d; ", i->cookie);
+
+    printf("mask = ");
+    if (i->mask & IN_ACCESS)        printf("IN_ACCESS ");
+    if (i->mask & IN_ATTRIB)        printf("IN_ATTRIB ");
+    if (i->mask & IN_CLOSE_NOWRITE) printf("IN_CLOSE_NOWRITE ");
+    if (i->mask & IN_CLOSE_WRITE)   printf("IN_CLOSE_WRITE ");
+    if (i->mask & IN_CREATE)        printf("IN_CREATE ");
+    if (i->mask & IN_DELETE)        printf("IN_DELETE ");
+    if (i->mask & IN_DELETE_SELF)   printf("IN_DELETE_SELF ");
+    if (i->mask & IN_IGNORED)       printf("IN_IGNORED ");
+    if (i->mask & IN_ISDIR)         printf("IN_ISDIR ");
+    if (i->mask & IN_MODIFY)        printf("IN_MODIFY ");
+    if (i->mask & IN_MOVE_SELF)     printf("IN_MOVE_SELF ");
+    if (i->mask & IN_MOVED_FROM)    printf("IN_MOVED_FROM ");
+    if (i->mask & IN_MOVED_TO)      printf("IN_MOVED_TO ");
+    if (i->mask & IN_OPEN)          printf("IN_OPEN ");
+    if (i->mask & IN_Q_OVERFLOW)    printf("IN_Q_OVERFLOW ");
+    if (i->mask & IN_UNMOUNT)       printf("IN_UNMOUNT ");
+    printf("\n");
+
+    if (i->len > 0)
+        printf("        name = %s\n", i->name);
+
+
+    if ((i->mask & IN_ISDIR) && ((i->mask & IN_CREATE) || (i->mask & IN_MOVED_TO))) {
+        auto it = watched_folders_paths_.find(i->wd);
+        if (it == watched_folders_paths_.end()) {
+            return EventMonitorErrorTemplates::kSystemError.Generate("cannot find monitored folder to create in " + std::to_string(
+                        i->wd));
+        }
+
+        std::string newpath = it->second + "/" + i->name;
+        auto err = AddFolderToWatch(newpath, true);
+        if (err) {
+            return err;
+        }
+    }
+
+    if ((i->mask & IN_DELETE_SELF) || ((i->mask & IN_ISDIR) && ((i->mask & IN_MOVED_FROM)))) {
+        auto it = watched_folders_paths_.find(i->wd);
+        if (it == watched_folders_paths_.end()) {
+            return EventMonitorErrorTemplates::kSystemError.Generate("cannot find monitored folder to delete " + std::to_string(
+                        i->wd));
+        }
+        std::string oldpath = it->second;
+        if (i->mask & IN_MOVED_FROM) {
+            oldpath += std::string("/") + i->name;
+            for (auto val = watched_folders_paths_.begin(); val != watched_folders_paths_.end();) {
+                if ((oldpath.size() <= val->second.size()) && std::equal(oldpath.begin(), oldpath.end(), val->second.begin())) {
+                    inotify_rm_watch(val->first, watch_fd_);
+                    GetDefaultEventMonLogger()->Debug("removed folder from monitor: " + val->second);
+                    val = watched_folders_paths_.erase(val);
+                } else {
+                    ++val;
+                }
+
+            }
+
+        } else {
+            inotify_rm_watch(it->first, watch_fd_);
+            watched_folders_paths_.erase(it);
+            GetDefaultEventMonLogger()->Debug("removed folder from monitor: " + oldpath);
+        }
+    }
+    if (!(i->mask & IN_ISDIR)) {
+        if ((i->mask & IN_CLOSE_WRITE) || (i->mask & IN_MOVED_TO)) {
+            auto it = watched_folders_paths_.find(i->wd);
+            if (it == watched_folders_paths_.end()) {
+                return EventMonitorErrorTemplates::kSystemError.Generate("cannot find monitored folder for file " + std::to_string(
+                            i->wd));
+            }
+            std::string fname = it->second + "/" + i->name;
+            FileEvent event;
+            event.type = (i->mask & IN_CLOSE_WRITE) ? EventType::closed : EventType::renamed_to;
+            event.name = fname;
+            events->emplace_back(std::move(event));
+            GetDefaultEventMonLogger()->Debug((i->mask & IN_CLOSE_WRITE) ? "file closed: " : "file moved: " + fname);
+        }
+    }
+
+
+
+    return nullptr;
+}
+
+
 FileEvents SystemFolderWatch::GetFileEventList(Error* err) {
     FileEvents events;
-    *err = nullptr;
+
+    char buffer[kBufLen]  __attribute__ ((aligned(8)));
+
+    int numRead = read(watch_fd_, buffer, sizeof(buffer));
+    if (numRead == 0) {
+        *err = TextError("readfrom inotify fd returned 0!");
+        printf("mask = ");
+
+        return events;
+    }
+
+    if (numRead == -1) {
+        *err = TextError("read from inotify fd returned -1!");
+        return events;
+    }
+
+    int nerrors = 0;
+    for (char* p = buffer; p < buffer + numRead; ) {
+        struct inotify_event* event = (struct inotify_event*) p;
+        *err = ProcessInotifyEvent(event, &events);
+        if (*err) {
+            GetDefaultEventMonLogger()->Error("error processing inotify event: " + (*err)->Explain());
+            nerrors++;
+        }
+        p += sizeof(struct inotify_event) + event->len;
+    }
+
+    if (nerrors == 0) {
+        *err = nullptr;
+    } else {
+        *err = TextError("There were " + std::to_string(nerrors) + " error(s) while processing event");
+    }
+
     return events;
 }
 
-
 }
\ No newline at end of file
diff --git a/producer/event_monitor_producer/src/system_folder_watch_linux.h b/producer/event_monitor_producer/src/system_folder_watch_linux.h
index 75ddbdbfa..69ffc51b5 100644
--- a/producer/event_monitor_producer/src/system_folder_watch_linux.h
+++ b/producer/event_monitor_producer/src/system_folder_watch_linux.h
@@ -3,11 +3,16 @@
 
 #include <vector>
 #include <string>
+#include <map>
 
 #include "common/error.h"
 #include "preprocessor/definitions.h"
 #include "asapo_producer.h"
 #include "common.h"
+#include "io/io.h"
+#include "io/io_factory.h"
+#include <sys/inotify.h>
+#include <unistd.h>
 
 namespace asapo {
 
@@ -16,8 +21,17 @@ class SystemFolderWatch {
     VIRTUAL Error StartFolderMonitor(const std::vector<std::string>& monitored_folders);
     VIRTUAL FileEvents GetFileEventList(Error* err);
   private:
+    Error AddFolderToWatch(std::string folder, bool recursive);
+    std::unique_ptr<IO> io_{GenerateDefaultIO()};
+    Error ProcessInotifyEvent(struct inotify_event* i, FileEvents* events);
+  private:
+    static const uint64_t kBufLen  = 2000 * (sizeof(struct inotify_event) + FILENAME_MAX + 1);
+    std::map<int, std::string> watched_folders_paths_;
+    int watch_fd_ = -1;
 };
 
 }
 
+
+
 #endif //ASAPO_SYSTEM_FOLDER_WATCH_LINUX_H
diff --git a/tests/automatic/producer/file_monitor_producer/check_linux.sh b/tests/automatic/producer/file_monitor_producer/check_linux.sh
index f1b62cf64..e6ffdd307 100644
--- a/tests/automatic/producer/file_monitor_producer/check_linux.sh
+++ b/tests/automatic/producer/file_monitor_producer/check_linux.sh
@@ -7,6 +7,7 @@ trap Cleanup EXIT
 Cleanup() {
 	echo cleanup
 	rm -rf test_in test_out #output
+	kill -9 $producer_id &>/dev/null
 }
 
 mkdir -p test_in test_out
diff --git a/tests/automatic/system_io/CMakeLists.txt b/tests/automatic/system_io/CMakeLists.txt
index 84f9db59c..95d945db4 100644
--- a/tests/automatic/system_io/CMakeLists.txt
+++ b/tests/automatic/system_io/CMakeLists.txt
@@ -1,6 +1,7 @@
 CMAKE_MINIMUM_REQUIRED(VERSION 3.7) # needed for fixtures
 
 add_subdirectory(read_folder_content)
+add_subdirectory(read_subdirectories)
 add_subdirectory(read_file_content)
 add_subdirectory(read_string_from_file)
 add_subdirectory(ip_tcp_network)
diff --git a/tests/automatic/system_io/read_subdirectories/CMakeLists.txt b/tests/automatic/system_io/read_subdirectories/CMakeLists.txt
new file mode 100644
index 000000000..57de0e923
--- /dev/null
+++ b/tests/automatic/system_io/read_subdirectories/CMakeLists.txt
@@ -0,0 +1,32 @@
+set(TARGET_NAME read_subdirectories)
+set(SOURCE_FILES read_subdirectories.cpp)
+
+
+################################
+# Executable and link
+################################
+add_executable(${TARGET_NAME} ${SOURCE_FILES}  $<TARGET_OBJECTS:system_io>)
+target_link_libraries(${TARGET_NAME} test_common)
+
+#Add all necessary common libraries
+GET_PROPERTY(ASAPO_COMMON_IO_LIBRARIES GLOBAL PROPERTY ASAPO_COMMON_IO_LIBRARIES)
+target_link_libraries(${TARGET_NAME} ${ASAPO_COMMON_IO_LIBRARIES})
+
+target_include_directories(${TARGET_NAME} PUBLIC ${ASAPO_CXX_COMMON_INCLUDE_DIR})
+set_target_properties(${TARGET_NAME} PROPERTIES LINKER_LANGUAGE CXX)
+
+################################
+# Testing
+################################
+
+add_test_setup_cleanup(${TARGET_NAME})
+IF(WIN32)
+    add_integration_test(${TARGET_NAME} list_folders "test test\\subtest3test\\subtest3\\subtest4test\\subtest1test\\subtest1\\subtest2")
+ELSE()
+    add_integration_test(${TARGET_NAME} list_folders "test test/subtest3test/subtest3/subtest4test/subtest1test/subtest1/subtest2")
+ENDIF(WIN32)
+
+
+add_integration_test(${TARGET_NAME} foldernotfound "test_notexist Nosuchfileordirectory:test_notexist")
+add_integration_test(${TARGET_NAME} foldernoaccess "test_noaccess1 Permissiondenied:test_noaccess1")
+
diff --git a/tests/automatic/system_io/read_subdirectories/cleanup_linux.sh b/tests/automatic/system_io/read_subdirectories/cleanup_linux.sh
new file mode 100644
index 000000000..c6183bf94
--- /dev/null
+++ b/tests/automatic/system_io/read_subdirectories/cleanup_linux.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+
+rm -rf test
+rmdir test_noaccess1
diff --git a/tests/automatic/system_io/read_subdirectories/cleanup_windows.bat b/tests/automatic/system_io/read_subdirectories/cleanup_windows.bat
new file mode 100644
index 000000000..f2d97c705
--- /dev/null
+++ b/tests/automatic/system_io/read_subdirectories/cleanup_windows.bat
@@ -0,0 +1,3 @@
+#rmdir /S /Q test
+icacls test_noaccess1 /grant:r users:D
+rmdir /S /Q test_noaccess1
diff --git a/tests/automatic/system_io/read_subdirectories/read_subdirectories.cpp b/tests/automatic/system_io/read_subdirectories/read_subdirectories.cpp
new file mode 100644
index 000000000..ecb33cb30
--- /dev/null
+++ b/tests/automatic/system_io/read_subdirectories/read_subdirectories.cpp
@@ -0,0 +1,36 @@
+#include <iostream>
+
+#include "io/io_factory.h"
+#include "testing.h"
+
+using asapo::IO;
+using asapo::Error;
+
+
+using asapo::M_AssertEq;
+using asapo::M_AssertContains;
+
+int main(int argc, char* argv[]) {
+    if (argc != 3) {
+        std::cout << "Wrong number of arguments" << std::endl;
+        return 1;
+    }
+    std::string expect{argv[2]};
+
+    Error err;
+    auto io = std::unique_ptr<IO> {asapo::GenerateDefaultIO() };
+    auto subdirs = io->GetSubDirectories(argv[1], &err);
+
+    std::string result{};
+    if (err == nullptr) {
+        for(auto folder : subdirs) {
+            result += folder;
+        }
+    } else {
+        result = err->Explain();
+    }
+
+    M_AssertContains(result, expect);
+
+    return 0;
+}
diff --git a/tests/automatic/system_io/read_subdirectories/setup_linux.sh b/tests/automatic/system_io/read_subdirectories/setup_linux.sh
new file mode 100644
index 000000000..bd288c517
--- /dev/null
+++ b/tests/automatic/system_io/read_subdirectories/setup_linux.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+mkdir -p test/subtest1/subtest2
+sleep 0.1
+mkdir -p test/subtest3/subtest4/
+sleep 0.1
+
+mkdir test_noaccess1
+chmod -rx test_noaccess1
+
diff --git a/tests/automatic/system_io/read_subdirectories/setup_windows.bat b/tests/automatic/system_io/read_subdirectories/setup_windows.bat
new file mode 100644
index 000000000..9ecfc1b06
--- /dev/null
+++ b/tests/automatic/system_io/read_subdirectories/setup_windows.bat
@@ -0,0 +1,14 @@
+mkdir test
+mkdir test\subtest1
+mkdir test\subtest1\subtest2
+
+mkdir test\subtest3
+mkdir test\subtest3\subtest4
+
+ping 1.0.0.0 -n 1 -w 100 > nul
+
+mkdir test_noaccess1
+icacls test_noaccess1 /deny users:D
+
+
+
-- 
GitLab