Add Serialization Support for Dump Entry Attributes
Implemented serialization of dump entry attributes using the
nlohmann::json library. Added serialization support in the Dump Entry
class, serializing attributes including originatorId, originatorType,
and startTime. These attributes are not part of the dump filename and
thus require serialization to ensure their state is preserved.
Deserialization will occur only if the serialization version matches
and the dump ID in the dump object matches the ID in the
serialized file.
Tests:
- Created BMC dumps and restarted service
- Created BMC dumps and restarted BMC
- Created 200 BMC dumps and restarted service and BMC multiple times
File:
```
{"dumpId":2,"originatorId":"","originatorType":1,"startTime":1718199238942411,"version":1}
```
Change-Id: I16ecb058bddd464c8771bd8d08a50ea1877747ed
Signed-off-by: Dhruvaraj Subhashchandran <dhruvaraj@in.ibm.com>
diff --git a/bmc_dump_entry.cpp b/bmc_dump_entry.cpp
index a75606d..05d1f10 100644
--- a/bmc_dump_entry.cpp
+++ b/bmc_dump_entry.cpp
@@ -2,6 +2,7 @@
#include "dump_manager.hpp"
#include "dump_offload.hpp"
+#include "dump_utils.hpp"
#include <phosphor-logging/lg2.hpp>
@@ -35,6 +36,35 @@
offloaded(true);
}
+void Entry::updateFromFile(const std::filesystem::path& dumpPath)
+{
+ // Extract dump details from the file name
+ auto dumpDetails = phosphor::dump::extractDumpDetails(dumpPath);
+ if (!dumpDetails)
+ {
+ lg2::error("Failed to extract dump details from file name: {PATH}",
+ "PATH", dumpPath);
+ throw std::logic_error("Invalid dump file name format");
+ }
+
+ auto [extractedId, extractedTimestamp, extractedSize] = *dumpDetails;
+
+ if (id != extractedId)
+ {
+ lg2::error("Id({ID}) is not matching with id on filename"
+ "({ID_FILENAME})",
+ "ID", id, "ID_FILENAME", extractedId);
+ throw std::logic_error("Invalid dump id in filename");
+ }
+
+ // Update the entry with extracted details
+ startTime(extractedTimestamp);
+ elapsed(extractedTimestamp);
+ completedTime(extractedTimestamp);
+ size(extractedSize);
+ status(OperationStatus::Completed);
+}
+
} // namespace bmc
} // namespace dump
} // namespace phosphor
diff --git a/bmc_dump_entry.hpp b/bmc_dump_entry.hpp
index b9a2eda..58d9702 100644
--- a/bmc_dump_entry.hpp
+++ b/bmc_dump_entry.hpp
@@ -97,6 +97,70 @@
// TODO: serialization of this property will be handled with
// #ibm-openbmc/2597
completedTime(timeStamp);
+ serialize();
+ }
+
+ /**
+ * @brief Update dump entry attributes from the file name.
+ *
+ * @param[in] dumpPath - The path to the dump directory.
+ */
+ void updateFromFile(const std::filesystem::path& dumpPath);
+
+ /**
+ * @brief Deserialize and create an entry
+ * @param[in] bus - Bus to attach to.
+ * @param[in] id - Dump id.
+ * @param[in] objPath - Object path to attach to.
+ * @param[in] filePath - Path to the dump file.
+ * @param[in] parent - The dump entry's parent.
+ * @return A unique pointer to the created entry.
+ */
+ static std::unique_ptr<Entry> deserializeEntry(
+ sdbusplus::bus_t& bus, uint32_t id, const std::string& objPath,
+ const std::filesystem::path& filePath, phosphor::dump::Manager& parent)
+ {
+ try
+ {
+ auto entry = std::unique_ptr<Entry>(
+ new Entry(bus, objPath, id, filePath, parent));
+ entry->updateFromFile(filePath);
+ entry->deserialize(filePath.parent_path());
+ entry->emitSignal();
+ return entry;
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error(
+ "Dump deserialization failed for path: {PATH}, error: {ERROR}",
+ "PATH", filePath, "ERROR", e.what());
+ return nullptr;
+ }
+ }
+
+ private:
+ /**
+ * @brief A minimal private constructor for the Dump Entry Object
+ * @param[in] bus - Bus to attach to.
+ * @param[in] objPath - Object path to attach to
+ * @param[in] dumpId - Dump id.
+ * @param[in] file - Absolute path to the dump file.
+ * @param[in] parent - The dump entry's parent.
+ */
+ Entry(sdbusplus::bus_t& bus, const std::string& objPath, uint32_t dumpId,
+ const std::filesystem::path& file, phosphor::dump::Manager& parent) :
+ phosphor::dump::Entry(bus, objPath.c_str(), dumpId, 0, 0, file,
+ OperationStatus::InProgress, "",
+ originatorTypes::Internal, parent),
+ EntryIfaces(bus, objPath.c_str(), EntryIfaces::action::defer_emit)
+ {}
+
+ /**
+ * @brief Emit object added signal
+ */
+ void emitSignal()
+ {
+ this->phosphor::dump::bmc::EntryIfaces::emit_object_added();
}
};
diff --git a/dump_entry.cpp b/dump_entry.cpp
index b37a914..b7fa3a0 100644
--- a/dump_entry.cpp
+++ b/dump_entry.cpp
@@ -4,6 +4,7 @@
#include <fcntl.h>
+#include <nlohmann/json.hpp>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/elog.hpp>
#include <phosphor-logging/lg2.hpp>
@@ -63,5 +64,112 @@
return fd;
}
+void Entry::serialize()
+{
+ // Folder for serialized entry
+ std::filesystem::path dir = file.parent_path() / PRESERVE;
+
+ // Serialized entry file
+ std::filesystem::path serializePath = dir / SERIAL_FILE;
+ try
+ {
+ if (!std::filesystem::exists(dir))
+ {
+ std::filesystem::create_directories(dir);
+ }
+
+ std::ofstream os(serializePath, std::ios::binary);
+ if (!os.is_open())
+ {
+ lg2::error("Failed to open file for serialization: {PATH} ", "PATH",
+ serializePath);
+ return;
+ }
+ nlohmann::json j;
+ j["version"] = CLASS_SERIALIZATION_VERSION;
+ j["dumpId"] = id;
+ j["originatorId"] = originatorId();
+ j["originatorType"] = originatorType();
+ j["startTime"] = startTime();
+
+ os << j.dump();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Serialization error: {PATH} {ERROR} ", "PATH",
+ serializePath, "ERROR", e);
+
+ // Remove the serialization folder if that got created
+ // Ignore the error since folder may not be created
+ std::error_code ec;
+ std::filesystem::remove_all(dir, ec);
+ }
+}
+
+void Entry::deserialize(const std::filesystem::path& dumpPath)
+{
+ try
+ {
+ // .preserve folder
+ std::filesystem::path dir = dumpPath / PRESERVE;
+ if (!std::filesystem::exists(dir))
+ {
+ lg2::info("Serialization directory: {SERIAL_DIR} doesnt exist, "
+ "skip deserialization",
+ "SERIAL_DIR", dir);
+ return;
+ }
+
+ // Serialized entry
+ std::filesystem::path serializePath = dir / SERIAL_FILE;
+ std::ifstream is(serializePath, std::ios::binary);
+ if (!is.is_open())
+ {
+ lg2::error("Failed to open file for deserialization: {PATH}",
+ "PATH", serializePath);
+ return;
+ }
+ nlohmann::json j;
+ is >> j;
+
+ uint32_t version;
+ j.at("version").get_to(version);
+ if (version == CLASS_SERIALIZATION_VERSION)
+ {
+ uint32_t storedId;
+ j.at("dumpId").get_to(storedId);
+ if (storedId == id)
+ {
+ originatorId(j["originatorId"].get<std::string>());
+ originatorType(j["originatorType"].get<originatorTypes>());
+ startTime(j["startTime"].get<uint64_t>());
+ }
+ else
+ {
+ lg2::error("The id ({ID_IN_FILE}) is not matching the dump id "
+ "({DUMPID}); skipping deserialization.",
+ "ID_IN_FILE", storedId, "DUMPID", id);
+
+ // Id is not matching, this could be due to file corruption
+ // deleting the .preserve folder.
+ // Attempt to delete the folder and ignore any error.
+ std::error_code ec;
+ std::filesystem::remove_all(dir, ec);
+ }
+ }
+ else
+ {
+ lg2::error("The serialized file version and current class version"
+ "doesnt match, skip deserialization {VERSION}",
+ "VERSION", version);
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Deserialization error: {PATH}, {ERROR}", "PATH", dumpPath,
+ "ERROR", e);
+ }
+}
+
} // namespace dump
} // namespace phosphor
diff --git a/dump_entry.hpp b/dump_entry.hpp
index 8f6cf2f..b30489b 100644
--- a/dump_entry.hpp
+++ b/dump_entry.hpp
@@ -6,18 +6,30 @@
#include "xyz/openbmc_project/Object/Delete/server.hpp"
#include "xyz/openbmc_project/Time/EpochTime/server.hpp"
+#include <phosphor-logging/lg2.hpp>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/server/object.hpp>
#include <sdeventplus/event.hpp>
#include <sdeventplus/source/event.hpp>
#include <filesystem>
+#include <fstream>
namespace phosphor
{
namespace dump
{
+// Current serialiation version of the class increment if there any change
+// in the serialized data members
+constexpr size_t CLASS_SERIALIZATION_VERSION = 1;
+
+// Folder to store serialized dump contents
+constexpr auto PRESERVE = ".preserve";
+
+// Binary file store the contents
+constexpr auto SERIAL_FILE = "serialized_entry.json";
+
template <typename T>
using ServerObject = typename sdbusplus::server::object_t<T>;
@@ -127,6 +139,19 @@
*/
sdbusplus::message::unix_fd getFileHandle() override;
+ /**
+ * @brief Serialize the dump entry attributes to a file.
+ *
+ */
+ virtual void serialize();
+
+ /**
+ * @brief Deserialize the dump entry attributes from a file.
+ *
+ * @param[in] dumpPath - The path where the .preserve folder is located.
+ */
+ virtual void deserialize(const std::filesystem::path& dumpPath);
+
protected:
/** @brief This entry's parent */
Manager& parent;
diff --git a/dump_manager_bmc.cpp b/dump_manager_bmc.cpp
index 171a26c..1fa0acd 100644
--- a/dump_manager_bmc.cpp
+++ b/dump_manager_bmc.cpp
@@ -17,8 +17,6 @@
#include <sdeventplus/source/base.hpp>
#include <cmath>
-#include <ctime>
-#include <regex>
namespace phosphor
{
@@ -171,25 +169,15 @@
void Manager::createEntry(const std::filesystem::path& file)
{
- // Dump File Name format obmcdump_ID_EPOCHTIME.EXT
- static constexpr auto ID_POS = 1;
- static constexpr auto EPOCHTIME_POS = 2;
- std::regex file_regex("obmcdump_([0-9]+)_([0-9]+).([a-zA-Z0-9]+)");
-
- std::smatch match;
- std::string name = file.filename();
-
- if (!((std::regex_search(name, match, file_regex)) && (match.size() > 0)))
+ auto dumpDetails = extractDumpDetails(file);
+ if (!dumpDetails)
{
- lg2::error("Invalid Dump file name, FILENAME: {FILENAME}", "FILENAME",
- file);
+ lg2::error("Failed to extract dump details from file name: {PATH}",
+ "PATH", file);
return;
}
- auto idString = match[ID_POS];
- uint64_t timestamp = stoull(match[EPOCHTIME_POS]) * 1000 * 1000;
-
- auto id = stoul(idString);
+ auto [id, timestamp, size] = *dumpDetails;
// If there is an existing entry update it and return.
auto dumpEntry = entries.find(id);
@@ -223,7 +211,6 @@
"ERROR", e, "OBJECT_PATH", objPath, "ID", id, "TIMESTAMP",
timestamp, "SIZE", std::filesystem::file_size(file), "FILENAME",
file);
- return;
}
}
@@ -283,18 +270,33 @@
{
auto idStr = p.path().filename().string();
- // Consider only directory's with dump id as name.
+ // Consider only directories with dump id as name.
// Note: As per design one file per directory.
if ((std::filesystem::is_directory(p.path())) &&
std::all_of(idStr.begin(), idStr.end(), ::isdigit))
{
lastEntryId = std::max(lastEntryId,
static_cast<uint32_t>(std::stoul(idStr)));
- auto fileIt = std::filesystem::directory_iterator(p.path());
- // Create dump entry d-bus object.
- if (fileIt != std::filesystem::end(fileIt))
+ for (const auto& file :
+ std::filesystem::directory_iterator(p.path()))
{
- createEntry(fileIt->path());
+ // Skip .preserve directory
+ if (file.path().filename() == PRESERVE)
+ {
+ continue;
+ }
+
+ // Entry Object path.
+ auto objPath = std::filesystem::path(baseEntryPath) / idStr;
+ auto entry = Entry::deserializeEntry(bus, std::stoul(idStr),
+ objPath.string(),
+ file.path(), *this);
+
+ if (entry != nullptr)
+ {
+ entries.insert(
+ std::make_pair(entry->getDumpId(), std::move(entry)));
+ }
}
}
}
diff --git a/dump_manager_bmc.hpp b/dump_manager_bmc.hpp
index 1e4e586..f658073 100644
--- a/dump_manager_bmc.hpp
+++ b/dump_manager_bmc.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "dump_entry.hpp"
#include "dump_manager.hpp"
#include "dump_utils.hpp"
#include "watch.hpp"
diff --git a/dump_utils.cpp b/dump_utils.cpp
index da703ec..c362ee6 100644
--- a/dump_utils.cpp
+++ b/dump_utils.cpp
@@ -1,9 +1,17 @@
+#include "config.h"
+
#include "dump_utils.hpp"
#include "dump_types.hpp"
#include <phosphor-logging/lg2.hpp>
+#include <ctime>
+#include <filesystem>
+#include <optional>
+#include <regex>
+#include <tuple>
+
namespace phosphor
{
namespace dump
@@ -46,5 +54,29 @@
return response[0].first;
}
+std::optional<std::tuple<uint32_t, uint64_t, uint64_t>>
+ extractDumpDetails(const std::filesystem::path& file)
+{
+ static constexpr auto ID_POS = 1;
+ static constexpr auto EPOCHTIME_POS = 2;
+ std::regex file_regex("obmcdump_([0-9]+)_([0-9]+).([a-zA-Z0-9]+)");
+
+ std::smatch match;
+ std::string name = file.filename().string();
+
+ if (!((std::regex_search(name, match, file_regex)) && (match.size() > 0)))
+ {
+ lg2::error("Invalid Dump file name, FILENAME: {FILENAME}", "FILENAME",
+ file);
+ return std::nullopt;
+ }
+
+ auto idString = match[ID_POS];
+ uint64_t timestamp = stoull(match[EPOCHTIME_POS]) * 1000 * 1000;
+
+ return std::make_tuple(stoul(idString), timestamp,
+ std::filesystem::file_size(file));
+}
+
} // namespace dump
} // namespace phosphor
diff --git a/dump_utils.hpp b/dump_utils.hpp
index 1b86575..ce66a00 100644
--- a/dump_utils.hpp
+++ b/dump_utils.hpp
@@ -377,5 +377,17 @@
throw std::invalid_argument{"Dump type not found"};
}
+/**
+ * @brief Extracts the dump ID and timestamp from a BMC dump file name.
+ *
+ * @param[in] file The path to the dump file.
+ *
+ * @return A std::optional containing a tuple with the dump ID, timestamp
+ * and size of the file if the extraction is successful, or std::nullopt
+ * if the file name does not match the expected format.
+ */
+std::optional<std::tuple<uint32_t, uint64_t, uint64_t>>
+ extractDumpDetails(const std::filesystem::path& file);
+
} // namespace dump
} // namespace phosphor