Add support for file descriptor PDR Tested: Unit tested and also tested on BMC Google-Bug-Id: 423027264 Change-Id: Ib6c53c78533dfb40d1907bd71a5cf168aede6deb Signed-off-by: Kasun Athukorala <kasunath@google.com>
diff --git a/libpldmresponder/meson.build b/libpldmresponder/meson.build index acbb400..25cb1f6 100644 --- a/libpldmresponder/meson.build +++ b/libpldmresponder/meson.build
@@ -19,6 +19,7 @@ 'bios_config.cpp', 'pdr_utils.cpp', 'pdr.cpp', + 'pdr_file.cpp', 'platform.cpp', 'platform_config.cpp', 'fru_parser.cpp',
diff --git a/libpldmresponder/pdr_file.cpp b/libpldmresponder/pdr_file.cpp new file mode 100644 index 0000000..dc22805 --- /dev/null +++ b/libpldmresponder/pdr_file.cpp
@@ -0,0 +1,166 @@ +#include "pdr_file.hpp" + +#include <libpldm/entity.h> + +#include <phosphor-logging/lg2.hpp> + +PHOSPHOR_LOG2_USING; + +namespace pldm +{ +namespace responder +{ +namespace pdr_file +{ + +using Json = nlohmann::json; + +void generateFileDescriptorPDR( + const Json& json, std::map<uint16_t, FileInfo>& fileIdentifierToFileInfo, + std::map<uint32_t, FileInfo>& recordHandleToFileInfo, + pdr_utils::RepoInterface& repo) +{ + static const std::vector<Json> emptyList{}; + auto entries = json.value("entries", emptyList); + for (const auto& e : entries) + { + auto entityTypeIt = e.find("EntityType"); + if (entityTypeIt == e.end() || !entityTypeIt->is_number()) + { + error("EntityType is missing or it is not a number."); + continue; + } + + uint16_t entityType = static_cast<uint16_t>(*entityTypeIt); + if ((entityType != PLDM_ENTITY_DEVICE_FILE) && + (entityType != PLDM_ENTITY_DEVICE_FILE_DIRECTORY)) + { + error( + "Invalid EntityType '{ENTITY_TYPE}' for file descriptor PDR. Expected 9 or 10.", + "ENTITY_TYPE", entityType); + continue; + } + + auto pathIt = e.find("Path"); + if (pathIt == e.end() || !pathIt->is_string()) + { + error("'Path' is missing or it is not a string."); + continue; + } + + uint16_t container_id = e.value("container", 0); + + auto files = e.value("Files", emptyList); + for (const auto& file : files) + { + auto fileIdIt = file.find("FileIdentifier"); + if (fileIdIt == file.end() || !fileIdIt->is_number()) + { + error("FileIdentifier is missing or it is not a number."); + continue; + } + + auto fileNameIt = file.find("FileName"); + if (fileNameIt == file.end() || !fileNameIt->is_string()) + { + error("FileName is missing or it is not a string."); + continue; + } + + auto fileSizeIt = file.find("FileSize"); + if (fileSizeIt == file.end() || !fileSizeIt->is_number()) + { + error("FileSize is missing or it is not a string."); + continue; + } + + const std::string& fileName = fileNameIt.value(); + int fileNameLengthWithNull = fileName.length() + 1; + + // Calculate the total length of the PDR including the common PDR + // header size. For now this skips the + // OemFileClassificationNameLength and OemFileClassificationName + // since they are not included. + size_t pdrSize = PLDM_PDR_FILE_DESCRIPTOR_PDR_MIN_LENGTH + + fileNameLengthWithNull; + pldm_platform_file_descriptor_pdr pdr{}; + + // This will be assigned automatically by PDR repository. + pdr.hdr.record_handle = 0; + pdr.hdr.version = 1; + pdr.hdr.type = PLDM_FILE_DESCRIPTOR_PDR; + pdr.hdr.record_change_num = 0; + pdr.hdr.length = pdrSize - sizeof(pldm_pdr_hdr); + + pdr.terminus_handle = TERMINUS_HANDLE; + pdr.file_identifier = *fileIdIt; + pdr.container.entity_type = entityType; + pdr.container.entity_instance_num = file.value("InstanceNumber", 0); + pdr.container.entity_container_id = container_id; + pdr.superior_directory_file_identifier = + file.value("SuperiorDirectoryFileIdentifier", 0); + pdr.file_classification = file.value("FileClassification", 0); + pdr.oem_file_classification = 0; + pdr.file_capabilities.value = file.value("FileCapabilities", 0); + pdr.file_maximum_size = fileSizeIt.value(); + pdr.file_maximum_file_descriptor_count = + file.value("MaxFileDescriptors", 0); + pdr.file_name.ptr = + reinterpret_cast<const uint8_t*>(fileName.c_str()); + pdr.file_name.length = fileNameLengthWithNull; + pdr.oem_file_classification_name.ptr = nullptr; + pdr.oem_file_classification_name.length = 0; + + std::vector<uint8_t> entry{}; + entry.resize(pdrSize); + + size_t pdrSizeCopy = pdrSize; + int ret = encode_pldm_platform_file_descriptor_pdr( + &pdr, entry.data(), &pdrSizeCopy); + if (ret != 0) + { + error("Failed to encode file descriptor PDR for '{FILE_NAME}'", + "FILE_NAME", fileName); + continue; + } + + FileInfo fileInfo{}; + + // Try opening the file here. If the open fail, we will not save the + // PDR. If we do open the file later on, PDR information and the + // actual file might not be in sync. + fileInfo.filePath = pathIt->get<std::string>() + "/" + fileName; + FILE* fp = fopen(fileInfo.filePath.c_str(), "rb"); + if (fp == nullptr) + { + error("Failed to open file '{FILE_PATH}'", "FILE_PATH", + fileInfo.filePath); + continue; + } + + // This file pointer will be kept open through out the lifetime of + // the program + // TODO: If the file changes during runtime, we need to load it + // again. At that time we will close this and open a file pointer to + // the new file. + fileInfo.fp = fp; + fileInfo.fileIdentifier = *fileIdIt; + fileInfo.fileSize = fileSizeIt.value(); + + pdr_utils::PdrEntry pdrEntry{}; + pdrEntry.data = entry.data(); + pdrEntry.size = pdrSize; + pdr_utils::RecordHandle recordHandle = repo.addRecord(pdrEntry); + + fileInfo.recordHandle = static_cast<uint32_t>(recordHandle); + + fileIdentifierToFileInfo.try_emplace(*fileIdIt, fileInfo); + recordHandleToFileInfo.try_emplace( + static_cast<uint32_t>(recordHandle), fileInfo); + } + } +} + +} // namespace pdr_file +} // namespace responder +} // namespace pldm
diff --git a/libpldmresponder/pdr_file.hpp b/libpldmresponder/pdr_file.hpp new file mode 100644 index 0000000..fd722f6 --- /dev/null +++ b/libpldmresponder/pdr_file.hpp
@@ -0,0 +1,47 @@ +#pragma once + +#include "pdr_utils.hpp" + +#include <libpldm/platform.h> + +#include <nlohmann/json.hpp> + +namespace pldm +{ +namespace responder +{ +namespace pdr_file +{ + +/** + * Data structue to hold the complete file path, C style file pointer, 16 bit + * file indetifier and record handle + */ +struct FileInfo +{ + std::string filePath = ""; + uint32_t fileSize; + FILE* fp = nullptr; + uint16_t fileIdentifier = 0; + uint32_t recordHandle = 0; +}; + +/** + * @brief Parse PDR JSON file and generate file descriptor PDR structure + * + * @param[in] json - the JSON Object with the file descriptor PDR + * @param[out] fileIdentifierToFileInfo - store FileInfo details based on the + * fileIdentifier + * @param[out] recordHandleToFileInfo - store FileInfo details based on the + * recordHandle + * @param[out] repo - pdr::RepoInterface + */ +void generateFileDescriptorPDR( + const nlohmann::json& json, + std::map<uint16_t, FileInfo>& fileIdentifierToFileInfo, + std::map<uint32_t, FileInfo>& recordHandleToFileInfo, + pdr_utils::RepoInterface& repo); + +} // namespace pdr_file +} // namespace responder +} // namespace pldm
diff --git a/libpldmresponder/platform.cpp b/libpldmresponder/platform.cpp index b034acb..66f205d 100644 --- a/libpldmresponder/platform.cpp +++ b/libpldmresponder/platform.cpp
@@ -116,6 +116,14 @@ auto json = readJson(dirEntry.path().string()); if (!json.empty()) { + auto filePdrs = json.value("fileDescriptorPDRs", empty); + for (const auto& filePdrEntries : filePdrs) + { + generateFileDescriptorPDR( + filePdrEntries, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + } + auto effecterPDRs = json.value("effecterPDRs", empty); for (const auto& effecter : effecterPDRs) {
diff --git a/libpldmresponder/platform.hpp b/libpldmresponder/platform.hpp index 7c835d4..0defc8a 100644 --- a/libpldmresponder/platform.hpp +++ b/libpldmresponder/platform.hpp
@@ -9,6 +9,7 @@ #include "libpldmresponder/pdr_utils.hpp" #include "libpldmresponder/platform_config.hpp" #include "oem_handler.hpp" +#include "pdr_file.hpp" #include "pldmd/handler.hpp" #include <libpldm/pdr.h> @@ -546,6 +547,8 @@ bool pdrCreated; std::vector<fs::path> pdrJsonsDir; std::unique_ptr<sdeventplus::source::Defer> deferredGetPDREvent; + std::map<uint16_t, pdr_file::FileInfo> fileIdentifierToFileInfo; + std::map<uint32_t, pdr_file::FileInfo> recordHandleToFileInfo; }; /** @brief Function to check if a sensor falls in OEM range
diff --git a/libpldmresponder/test/libpldmresponder_pdr_file_test.cpp b/libpldmresponder/test/libpldmresponder_pdr_file_test.cpp new file mode 100644 index 0000000..51b4f38 --- /dev/null +++ b/libpldmresponder/test/libpldmresponder_pdr_file_test.cpp
@@ -0,0 +1,336 @@ +#include "libpldmresponder/pdr_file.hpp" +#include "libpldmresponder/pdr_utils.hpp" + +#include <libpldm/platform.h> + +#include <nlohmann/json.hpp> + +#include <filesystem> +#include <fstream> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +using namespace pldm::responder::pdr_file; +using namespace pldm::responder::pdr_utils; +using ::testing::_; +using ::testing::Return; +using Json = nlohmann::json; +namespace fs = std::filesystem; + +class MockRepo : public RepoInterface +{ + public: + MockRepo() : RepoInterface(nullptr) {} + + MOCK_METHOD(pldm_pdr*, getPdr, (), (const, override)); + MOCK_METHOD(RecordHandle, addRecord, (const PdrEntry&), (override)); + MOCK_METHOD(const pldm_pdr_record*, getFirstRecord, (PdrEntry&), + (override)); + MOCK_METHOD(const pldm_pdr_record*, getNextRecord, + (const pldm_pdr_record*, PdrEntry&), (override)); + MOCK_METHOD(uint32_t, getRecordHandle, (const pldm_pdr_record*), + (const, override)); + MOCK_METHOD(uint32_t, getRecordCount, (), (override)); + MOCK_METHOD(bool, empty, (), (override)); + MOCK_METHOD(uint32_t, getRepositorySize, (), (override)); + MOCK_METHOD(uint32_t, getLargestRecordSize, (), (override)); +}; + +class GenerateFileDescriptorPDRTest : public ::testing::Test +{ + protected: + void SetUp() override + { + // Create a temporary directory for test files. + char tmppldm[] = "/tmp/pldm_file_pdr_test.XXXXXX"; + dir = fs::path(mkdtemp(tmppldm)); + + // Create a dummy file for the success case. + testFilePath = dir / "test_dir"; + fs::create_directory(testFilePath); + std::ofstream(testFilePath / "test_file.bin").close(); + } + + void TearDown() override + { + for (const auto& pair : fileIdentifierToFileInfo) + { + if (pair.second.fp) + { + fclose(pair.second.fp); + } + } + fs::remove_all(dir); + } + + fs::path dir; + fs::path testFilePath; + MockRepo repo; + std::map<uint16_t, FileInfo> fileIdentifierToFileInfo; + std::map<uint32_t, FileInfo> recordHandleToFileInfo; +}; + +TEST_F(GenerateFileDescriptorPDRTest, SuccessCase) +{ + const Json json = R"( + { + "entries": [ + { + "EntityType": 9, + "Path": "placeholder", + "Files": [ + { + "FileIdentifier": 1, + "FileName": "test_file.bin", + "FileSize": 1234 + } + ] + } + ] + })"_json; + + auto modifiedJson = json; + modifiedJson["entries"][0]["Path"] = testFilePath.string(); + + EXPECT_CALL(repo, addRecord(_)).WillOnce(Return(1)); + + generateFileDescriptorPDR(modifiedJson, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + + EXPECT_EQ(fileIdentifierToFileInfo.size(), 1); + EXPECT_EQ(recordHandleToFileInfo.size(), 1); + EXPECT_TRUE(fileIdentifierToFileInfo.contains(1)); + EXPECT_TRUE(recordHandleToFileInfo.contains(1)); + + const auto& fileInfo = fileIdentifierToFileInfo.at(1); + EXPECT_EQ(fileInfo.fileIdentifier, 1); + EXPECT_EQ(fileInfo.fileSize, 1234); + EXPECT_EQ(fileInfo.recordHandle, 1); + EXPECT_EQ(fileInfo.filePath, testFilePath / "test_file.bin"); + EXPECT_NE(fileInfo.fp, nullptr); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailureMissingEntityType) +{ + const Json json = R"({ "entries": [{ "Path": "p" }]})"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailureEntityTypeNotNumber) +{ + const Json json = + R"({ "entries": [{ "EntityType": "abc", "Path": "p" }]})"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailureInvalidEntityType) +{ + const Json json = + R"({ "entries": [{ "EntityType": 8, "Path": "p" }]})"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailureMissingPath) +{ + const Json json = R"({ "entries": [{ "EntityType": 9 }]})"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailurePathNotString) +{ + const Json json = + R"({ "entries": [{ "EntityType": 9, "Path": 123 }]})"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailureMissingFileIdentifier) +{ + const Json json = R"( + { + "entries": [ + { + "EntityType": 9, + "Path": "p", + "Files": [ { "FileName": "f", "FileSize": 0 } ] + } + ] + })"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailureFileIdentifierNotNumber) +{ + const Json json = R"( + { + "entries": [ + { + "EntityType": 9, + "Path": "p", + "Files": [ { "FileIdentifier": "abc", "FileName": "f", "FileSize": 0 } ] + } + ] + })"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailureMissingFileName) +{ + const Json json = R"( + { + "entries": [ + { + "EntityType": 9, + "Path": "p", + "Files": [ { "FileIdentifier": 1, "FileSize": 0 } ] + } + ] + })"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailureFileNameNotString) +{ + const Json json = R"( + { + "entries": [ + { + "EntityType": 9, + "Path": "p", + "Files": [ { "FileIdentifier": 1, "FileName": 123, "FileSize": 0 } ] + } + ] + })"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailureMissingFileSize) +{ + const Json json = R"( + { + "entries": [ + { + "EntityType": 9, + "Path": "p", + "Files": [ { "FileIdentifier": 1, "FileName": "f" } ] + } + ] + })"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailureFileSizeNotNumber) +{ + const Json json = R"( + { + "entries": [ + { + "EntityType": 9, + "Path": "p", + "Files": [ { "FileIdentifier": 1, "FileName": "f", "FileSize": "abc" } ] + } + ] + })"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailureFileNameTooLong) +{ + std::string longName(255, 'a'); + Json json = {{"entries", + {{{"EntityType", 9}, + {"Path", "p"}, + {"Files", + {{{"FileIdentifier", 1}, + {"FileName", longName}, + {"FileSize", 0}}}}}}}}; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, FailureCannotOpenFile) +{ + const Json json = R"( + { + "entries": [ + { + "EntityType": 9, + "Path": "nonexistent_dir", + "Files": [ + { + "FileIdentifier": 1, + "FileName": "nonexistent_file.bin", + "FileSize": 0 + } + ] + } + ] + })"_json; + + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, CornerCaseEmptyEntries) +{ + const Json json = R"({ "entries": [] })"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +} + +TEST_F(GenerateFileDescriptorPDRTest, CornerCaseEmptyFiles) +{ + const Json json = R"( + { + "entries": [ + { + "EntityType": 9, + "Path": "p", + "Files": [] + } + ] + })"_json; + generateFileDescriptorPDR(json, fileIdentifierToFileInfo, + recordHandleToFileInfo, repo); + EXPECT_TRUE(fileIdentifierToFileInfo.empty()); + EXPECT_TRUE(recordHandleToFileInfo.empty()); +}
diff --git a/libpldmresponder/test/meson.build b/libpldmresponder/test/meson.build index fa581cd..9cb5ca9 100644 --- a/libpldmresponder/test/meson.build +++ b/libpldmresponder/test/meson.build
@@ -9,6 +9,7 @@ 'libpldmresponder_fru_test', 'libpldmresponder_platform_test', 'libpldmresponder_pdr_effecter_test', + 'libpldmresponder_pdr_file_test', 'libpldmresponder_pdr_sensor_test', ]