sync_manager: Add callback to do rsync

Call rsync when the subscribed file or directory is modified or
deleted. Don't create error logs as syncing is used for backup
and does not affect the system's operation. Any errors will be
logged into the journal.

Closes openbmc/openbmc#2918

Change-Id: I2671f0afd2924c15ea883d4d037c641c6e9680b4
Signed-off-by: Adriana Kobylak <anoo@us.ibm.com>
diff --git a/configure.ac b/configure.ac
index 1155cfe..0165e08 100755
--- a/configure.ac
+++ b/configure.ac
@@ -96,6 +96,8 @@
     [The base dir where all RO partitions are mounted])
 AC_DEFINE(BMC_ROFS_PREFIX, "/media/rofs-",
     [The prefix path for the versioned read-only bmc partitions])
+AC_DEFINE(ALT_RWFS, "/media/alt/var/persist",
+    [The path of the alt rwfs overlay])
 AC_DEFINE(PERSIST_DIR, "/var/lib/obmc/phosphor-bmc-code-mgmt/",
     [The dir where activation data is stored in files])
 AC_DEFINE(SYSTEMD_BUSNAME, "org.freedesktop.systemd1",
diff --git a/sync_manager.cpp b/sync_manager.cpp
index 6a50711..f83aebd 100644
--- a/sync_manager.cpp
+++ b/sync_manager.cpp
@@ -1,3 +1,9 @@
+#include <experimental/filesystem>
+#include <phosphor-logging/log.hpp>
+#include <sys/inotify.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include "config.h"
 #include "sync_manager.hpp"
 
 namespace phosphor
@@ -7,8 +13,66 @@
 namespace manager
 {
 
-int Sync::processEntry()
+using namespace phosphor::logging;
+namespace fs = std::experimental::filesystem;
+
+int Sync::processEntry(int mask, const fs::path& entryPath)
 {
+    int status{};
+    pid_t pid = fork();
+
+    if (pid == 0)
+    {
+        fs::path dst(ALT_RWFS / entryPath);
+
+        // rsync needs an additional --delete argument to handle file deletions
+        // so need to differentiate between the different file events.
+        if (mask & IN_CLOSE_WRITE)
+        {
+            if (!(fs::exists(dst)))
+            {
+                if (fs::is_directory(entryPath))
+                {
+                    // Source is a directory, create it at the destination.
+                    fs::create_directories(dst);
+                }
+                else
+                {
+                    // Source is a file, create the directory where this file
+                    // resides at the destination.
+                    fs::create_directories(dst.parent_path());
+                }
+            }
+
+            execl("/usr/bin/rsync", "rsync", "-a", entryPath.c_str(),
+                  dst.c_str(), nullptr);
+            // execl only returns on fail
+            log<level::ERR>("Error occurred during the rsync call",
+                            entry("ERRNO=%d", errno),
+                            entry("PATH=%s", entryPath.c_str()));
+            return -1;
+        }
+        else if (mask & IN_DELETE)
+        {
+            execl("/usr/bin/rsync", "rsync", "-a", "--delete",
+                  entryPath.c_str(), dst.c_str(), nullptr);
+            // execl only returns on fail
+            log<level::ERR>("Error occurred during the rsync delete call",
+                            entry("ERRNO=%d", errno),
+                            entry("PATH=%s", entryPath.c_str()));
+            return -1;
+        }
+    }
+    else if (pid > 0)
+    {
+        waitpid(pid, &status, 0);
+    }
+    else
+    {
+        log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", errno));
+        return -1;
+    }
+
     return 0;
 }
 
diff --git a/sync_manager.hpp b/sync_manager.hpp
index a940461..45bfca4 100644
--- a/sync_manager.hpp
+++ b/sync_manager.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include <experimental/filesystem>
+
 namespace phosphor
 {
 namespace software
@@ -7,6 +9,8 @@
 namespace manager
 {
 
+namespace fs = std::experimental::filesystem;
+
 /** @class Sync
  *  @brief Contains filesystem sync functions.
  *  @details The software manager class that contains functions to perform
@@ -24,9 +28,11 @@
 
     /**
      * @brief Process requested file or directory.
+     * @param[in] mask - The inotify mask.
+     * @param[in] entryPath - The file or directory to process.
      * @param[out] result - 0 if successful.
      */
-    int processEntry();
+    int processEntry(int mask, const fs::path& entryPath);
 };
 
 } // namespace manager
diff --git a/sync_manager_main.cpp b/sync_manager_main.cpp
index 46a2d95..e1834b5 100644
--- a/sync_manager_main.cpp
+++ b/sync_manager_main.cpp
@@ -23,7 +23,8 @@
 
         using namespace phosphor::software::manager;
         phosphor::software::manager::SyncWatch watch(
-            *loop, std::bind(std::mem_fn(&Sync::processEntry), &syncManager));
+            *loop, std::bind(std::mem_fn(&Sync::processEntry), &syncManager,
+                             std::placeholders::_1, std::placeholders::_2));
         bus.attach_event(loop, SD_EVENT_PRIORITY_NORMAL);
         sd_event_loop(loop);
     }
diff --git a/sync_watch.cpp b/sync_watch.cpp
index 0fc61d8..a8b4eeb 100644
--- a/sync_watch.cpp
+++ b/sync_watch.cpp
@@ -17,7 +17,7 @@
 namespace fs = std::experimental::filesystem;
 
 SyncWatch::SyncWatch(sd_event& loop,
-                     std::function<int(fs::path&)> syncCallback) :
+                     std::function<int(int, fs::path&)> syncCallback) :
     syncCallback(syncCallback)
 {
     auto syncfile = fs::path(SYNC_LIST_DIR_PATH) / SYNC_LIST_FILE_NAME;
@@ -37,7 +37,8 @@
                 continue;
             }
 
-            auto wd = inotify_add_watch(fd, line.c_str(), IN_CLOSE_WRITE);
+            auto wd =
+                inotify_add_watch(fd, line.c_str(), IN_CLOSE_WRITE | IN_DELETE);
             if (-1 == wd)
             {
                 log<level::ERR>("inotify_add_watch failed",
@@ -81,6 +82,40 @@
 int SyncWatch::callback(sd_event_source* s, int fd, uint32_t revents,
                         void* userdata)
 {
+    if (!(revents & EPOLLIN))
+    {
+        return 0;
+    }
+
+    constexpr auto maxBytes = 1024;
+    uint8_t buffer[maxBytes];
+    auto bytes = read(fd, buffer, maxBytes);
+    if (0 > bytes)
+    {
+        return 0;
+    }
+
+    auto syncWatch = static_cast<SyncWatch*>(userdata);
+    auto offset = 0;
+    while (offset < bytes)
+    {
+        auto event = reinterpret_cast<inotify_event*>(&buffer[offset]);
+
+        // fileMap<fd, std::map<wd, path>>
+        auto it1 = syncWatch->fileMap.find(fd);
+        if (it1 != syncWatch->fileMap.end())
+        {
+            auto it2 = it1->second.begin();
+            auto rc = syncWatch->syncCallback(event->mask, it2->second);
+            if (rc)
+            {
+                return rc;
+            }
+        }
+
+        offset += offsetof(inotify_event, name) + event->len;
+    }
+
     return 0;
 }
 
diff --git a/sync_watch.hpp b/sync_watch.hpp
index a7f8557..4303671 100644
--- a/sync_watch.hpp
+++ b/sync_watch.hpp
@@ -29,7 +29,7 @@
      *  @param[in] syncCallback - The callback function for processing
      *                            files
      */
-    SyncWatch(sd_event& loop, std::function<int(fs::path&)> syncCallback);
+    SyncWatch(sd_event& loop, std::function<int(int, fs::path&)> syncCallback);
 
     SyncWatch(const SyncWatch&) = delete;
     SyncWatch& operator=(const SyncWatch&) = delete;
@@ -58,7 +58,7 @@
     std::map<fd, std::map<wd, fs::path>> fileMap;
 
     /** @brief The callback function for processing the inotify event */
-    std::function<int(fs::path&)> syncCallback;
+    std::function<int(int, fs::path&)> syncCallback;
 };
 
 } // namespace manager