diff --git a/README.md b/README.md index c48e8ce..83feba7 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,45 @@ BMC applications. meson setup builddir meson compile -C builddir ``` + +### Signal-Triggered Watched Paths Logging (DataSync Debug Utility) + +Inspect watched paths using the `SIGUSR1` signal. This feature is helpful for +verifying which paths are currently being monitored by the phosphor-data-sync. + +#### To check whether certain files or directories are currently being monitored + +If you want to check if some particular paths are being monitored or not, create +a file `/tmp/data_sync_paths_info.lsv` and enter the paths line by line. Then +send the signal (Refer below), you can see the output in the same file. + +```bash +$ cat data_sync_paths_info.lsv +rtest +/var/lib +$ kill -SIGUSR1 +$ cat data_sync_paths_info.lsv +Individual user entered paths results: +Not Watching path: rtest +Watching path: /var/lib +``` + +If the user input file is not present, then on receiving the signal, it will +dump all the currently monitored paths to `/tmp/data_sync_paths_info.json`. +Refer below for example output + +```bash +$ cat data_sync_paths_info.json +[ + "/var/lib", + "/var/lib/phosphor-state-manager" +] +``` + +To trigger the utility, send a `SIGUSR1` signal to the +`phosphor-rbmc-data-sync-mgr` process: + +```bash +pgrep -f phosphor-rbmc-data-sync-mgr +kill -SIGUSR1 +``` diff --git a/src/data_watcher.cpp b/src/data_watcher.cpp index fe7969d..64c071d 100644 --- a/src/data_watcher.cpp +++ b/src/data_watcher.cpp @@ -20,6 +20,8 @@ DataWatcher::DataWatcher(sdbusplus::async::context& ctx, const int inotifyFlags, std::make_unique(ctx, _inotifyFileDescriptor())) { createWatchers(_dataPathToWatch); + std::lock_guard lock(getMutex()); + getAllWatchers().push_back(this); } DataWatcher::~DataWatcher() @@ -33,6 +35,9 @@ DataWatcher::~DataWatcher() } }); } + auto& instances = getAllWatchers(); + instances.erase(std::remove(instances.begin(), instances.end(), this), + instances.end()); } int DataWatcher::inotifyInit() const diff --git a/src/data_watcher.hpp b/src/data_watcher.hpp index 3452739..c823fd3 100644 --- a/src/data_watcher.hpp +++ b/src/data_watcher.hpp @@ -9,6 +9,7 @@ #include #include +#include namespace data_sync::watch::inotify { @@ -131,6 +132,42 @@ class DataWatcher */ sdbusplus::async::task onDataChange(); + /** + * @brief API to get all the instances of the DataWatcher + * + * @returns std::vector - A reference to the vector containing + * all instances of this class + */ + static std::vector& getAllWatchers() + { + static std::vector instances; + return instances; + } + + /** + * @brief API to get the reference to the static mutex to guard + * concurrent access to shared data (i.e., the vector of DataWatcher + * instances) + * + * @returns std::mutex& - A reference to the static mutex + */ + static std::mutex& getMutex() + { + static std::mutex mtx; + return mtx; + } + + /** + * @brief API to get the file descriptor of the inotify instance + * + * @returns std::map& - A reference to the map of WD and their + * associated file paths. + */ + const std::map& getWatchDescriptors() const + { + return _watchDescriptors; + } + private: /** * @brief inotify flags diff --git a/src/manager.cpp b/src/manager.cpp index 7c83054..1f3e503 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -46,9 +47,85 @@ sdbusplus::async::task<> Manager::init() co_await startFullSync(); } + std::signal(SIGUSR1, logSignalHandler); co_return co_await startSyncEvents(); } +std::unordered_set Manager::collectAllWatchedPaths() +{ + std::unordered_set watchedPaths; + for (const auto* watcher : watch::inotify::DataWatcher::getAllWatchers()) + { + std::ranges::transform( + watcher->getWatchDescriptors(), + std::inserter(watchedPaths, watchedPaths.end()), + [](const auto& wdPathPair) { return wdPathPair.second.string(); }); + } + return watchedPaths; +} + +void Manager::saveWatchedPathsToFile() +{ + std::string storeWatchedPath = "/tmp/data_sync_paths_info.json"; + try + { + data_sync::persist::util::writeFile( + nlohmann::json(collectAllWatchedPaths()), storeWatchedPath); + lg2::info("Wrote all watched paths to file: {FILE}", "FILE", + storeWatchedPath); + } + catch (const std::exception& e) + { + lg2::error("Error writing all watched paths to file: {ERROR}", "ERROR", + e); + } +} + +bool Manager::watchedPathCheck(std::string& filePath) +{ + std::ifstream infile{filePath}; + if (!infile) + { + return false; + } + std::vector lines(std::istream_iterator{infile}, + {}); + std::unordered_set watchedPaths = collectAllWatchedPaths(); + + if (std::ofstream outfile{std::string(filePath), std::ios::trunc}; outfile) + { + outfile << "Individual user entered paths results:\n"; + for (const auto& line : lines) + { + outfile << (watchedPaths.contains(line) ? "Watching" + : "Not Watching") + << " path: " << line << '\n'; + } + return true; + } + else + { + return false; + } +} + +void Manager::logSignalHandler(int signal) +{ + lg2::info("Received signal: {SIGNAL}", "SIGNAL", signal); + std::string checkForWatchedPaths = "/tmp/data_sync_paths_info.lsv"; + if (watchedPathCheck(checkForWatchedPaths)) + { + lg2::info("Checked and wrote each path in file {FILE}", "FILE", + checkForWatchedPaths); + } + else + { + lg2::info("Unable to open user input {FILE}, logging all watched paths", + "FILE", checkForWatchedPaths); + saveWatchedPathsToFile(); + } +} + // NOLINTNEXTLINE sdbusplus::async::task<> Manager::parseConfiguration() { diff --git a/src/manager.hpp b/src/manager.hpp index 14a81b2..6fd4e08 100644 --- a/src/manager.hpp +++ b/src/manager.hpp @@ -9,6 +9,7 @@ #include #include +#include #include namespace data_sync @@ -202,6 +203,42 @@ class Manager */ bool isSyncEligible(const config::DataSyncConfig& dataSyncCfg); + /** + * @brief A helper function which saves all currently watched paths to a + * JSON file. + */ + static void saveWatchedPathsToFile(); + + /** + * @brief A helper function which reads paths from the given input file + * stream and compares each path against currently watched paths, then + * writes the result to same path `/tmp/data_sync_paths_info.lsv`. + * + * @param[in] filePath - Input file path containing one path per line to + * check. + * + * @return True if filePath present and succesfully wrote to it; otherwise + * False. + */ + static bool watchedPathCheck(std::string& filePath); + + /** + * @brief A helper function to handle logging when a SIGUSR1 signal is + * received. + * + * @param[in] signal - The signal value that triggered the handler. + */ + static void logSignalHandler(int signal); + + /** + * @brief A helper API to collect all the paths currently being watched for + * immediate sync. + * + * @returns std::unordered_set - A set containing all the paths + * being watched. + */ + static std::unordered_set collectAllWatchedPaths(); + /** * @brief The async context object used to perform operations asynchronously * as required. diff --git a/test/manager_test.cpp b/test/manager_test.cpp index a545dfd..ac9e4cc 100644 --- a/test/manager_test.cpp +++ b/test/manager_test.cpp @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 #include "manager_test.hpp" +#include + std::filesystem::path ManagerTest::dataSyncCfgDir; std::filesystem::path ManagerTest::tmpDataSyncDataDir; nlohmann::json ManagerTest::commonJsonData; @@ -208,3 +210,118 @@ TEST_F(ManagerTest, testDBusDataPersistency) ctx.run(); } + +TEST_F(ManagerTest, testSignalReceiverLogic) +{ + using namespace std::literals; + namespace ed = data_sync::ext_data; + + std::unique_ptr extDataIface = + std::make_unique(); + + ed::MockExternalDataIFaces* mockExtDataIfaces = + dynamic_cast(extDataIface.get()); + + ON_CALL(*mockExtDataIfaces, fetchBMCRedundancyMgrProps()) + // NOLINTNEXTLINE + .WillByDefault([&mockExtDataIfaces]() -> sdbusplus::async::task<> { + mockExtDataIfaces->setBMCRole(ed::BMCRole::Active); + mockExtDataIfaces->setBMCRedundancy(true); + co_return; + }); + + EXPECT_CALL(*mockExtDataIfaces, fetchSiblingBmcIP()) + // NOLINTNEXTLINE + .WillRepeatedly([]() -> sdbusplus::async::task<> { co_return; }); + + EXPECT_CALL(*mockExtDataIfaces, fetchRbmcCredentials()) + // NOLINTNEXTLINE + .WillRepeatedly([]() -> sdbusplus::async::task<> { co_return; }); + + nlohmann::json jsonData = { + {"Files", + { + {{"Path", ManagerTest::tmpDataSyncDataDir.string() + "/srcFile1"}, + {"DestinationPath", + ManagerTest::tmpDataSyncDataDir.string() + "/destDir/"}, + {"Description", "FullSync from Active to Passive bmc"}, + {"SyncDirection", "Active2Passive"}, + {"SyncType", "Immediate"}}, + {{"Path", ManagerTest::tmpDataSyncDataDir.string() + "/srcFile2"}, + {"DestinationPath", + ManagerTest::tmpDataSyncDataDir.string() + "/destDir/"}, + {"Description", "FullSync from Active to Passive bmc"}, + {"SyncDirection", "Active2Passive"}, + {"SyncType", "Immediate"}}, + }}}; + + fs::path srcFile1{jsonData["Files"][0]["Path"]}; + fs::path srcFile2{jsonData["Files"][1]["Path"]}; + + fs::path destDir1{jsonData["Files"][0]["DestinationPath"]}; + fs::path destDir2{jsonData["Files"][1]["DestinationPath"]}; + + writeConfig(jsonData); + std::filesystem::remove("/tmp/data_sync_paths_info.lsv"); + sdbusplus::async::context ctx; + + std::string data1{"Data written on the file1\n"}; + std::string data2{"Data written on the file2\n"}; + + ManagerTest::writeData(srcFile1, data1); + ManagerTest::writeData(srcFile2, data2); + + ASSERT_EQ(ManagerTest::readData(srcFile1), data1); + ASSERT_EQ(ManagerTest::readData(srcFile2), data2); + + data_sync::Manager manager{ctx, std::move(extDataIface), + ManagerTest::dataSyncCfgDir}; + + auto waitingForFullSyncToFinish = + // NOLINTNEXTLINE + [&](sdbusplus::async::context& ctx) -> sdbusplus::async::task { + auto status = manager.getFullSyncStatus(); + + while (status != FullSyncStatus::FullSyncCompleted && + status != FullSyncStatus::FullSyncFailed) + { + co_await sdbusplus::async::sleep_for(ctx, + std::chrono::milliseconds(50)); + status = manager.getFullSyncStatus(); + } + + EXPECT_EQ(status, FullSyncStatus::FullSyncCompleted) + << "FullSync status is not Completed!"; + + // Raising signal after full sync completes, so that sync events starts + // and the watchers are being created. + sdbusplus::async::sleep_for(ctx, 0.1s); + // Raising a signal to trigger the signal handler logic. + std::raise(SIGUSR1); + // waiting to write all the watched paths to a file as per + // signal handler logic. + sdbusplus::async::sleep_for(ctx, 0.5s); + + ctx.request_stop(); + + // Forcing to trigger inotify events so that all running immediate + // sync tasks will resume and stop since the context is requested to + // stop in the above. + ManagerTest::writeData(srcFile1, data1); + ManagerTest::writeData(srcFile2, data2); + + // Reading the file where all the watched paths are written by signal + // handler logic. + auto watchersF = + ManagerTest::readData("/tmp/data_sync_paths_info.json"); + auto parsed = nlohmann::json::parse(watchersF); + EXPECT_TRUE(parsed.is_array()); + // Checking if the srcFile1 and srcFile2 are present in the parsed json + EXPECT_NE(watchersF.find(srcFile1), std::string::npos); + EXPECT_NE(watchersF.find(srcFile2), std::string::npos); + co_return; + }; + + ctx.spawn(waitingForFullSyncToFinish(ctx)); + ctx.run(); +}