| #include "smbios_file.h" |
| |
| #include <cerrno> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstring> |
| #include <ctime> |
| #include <filesystem> // NOLINT |
| #include <string> |
| #include <system_error> // NOLINT |
| #include <utility> |
| |
| #include "absl/log/log.h" |
| #include "absl/strings/str_format.h" |
| #include "grpcblob_defs.h" |
| |
| /* |
| * File format: |
| * 10 bytes app internal header |
| * var bytes Structure Table |
| * 24 bytes Entry Point |
| * |
| * As the header and Entry Point are of fixed size, simple concatenation |
| * is sufficient to distinguish these three fields. |
| * |
| * Only SMBIOS 3.0 (64-bit) is supported. |
| * 32-bit SMBIOS, aka DMI, is not supported. |
| * |
| * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.6.0.pdf |
| */ |
| |
| namespace blobs { |
| |
| // SMBIOS 3.0 (64-bit) Entry Point, always 24 bytes |
| constexpr size_t entryPointSize = 24; |
| |
| // Arbitrary bounds on acceptable size of Structure Table |
| constexpr size_t minTableSize = 40; |
| constexpr size_t maxTableSize = 32 * 1024; |
| |
| // The internal app header, always 10 bytes |
| constexpr size_t appHeaderSize = 10; |
| |
| // Constants to match the behavior of the IPMI blob handler |
| constexpr uint8_t headerDirVer = 0x01; |
| constexpr uint8_t headerSmbios = 0x02; |
| |
| // File prefix for persistent storage |
| constexpr auto filePrefix = "/var/lib/smbios/smbios-remote-host-"; |
| |
| std::string SmbiosEntry::validate() const { |
| if (instance < 0) { |
| return absl::StrFormat("Instance number below minimum: %d", instance); |
| } |
| |
| // Instances are 0-based counting, must be less than numInstances |
| if (instance >= numInstances) { |
| return absl::StrFormat("Instance number above maximum: %d", instance); |
| } |
| |
| auto sizeHeader = appHeader.size(); |
| auto sizeEntry = entryPoint.size(); |
| auto sizeTable = structureTable.size(); |
| |
| if (sizeHeader != appHeaderSize) { |
| return absl::StrFormat( |
| "Size of internal app header is not correct: %d bytes", sizeHeader); |
| } |
| if (sizeEntry != entryPointSize) { |
| return absl::StrFormat("Size of Entry Point is not correct: %d bytes", |
| sizeEntry); |
| } |
| |
| if (sizeTable < minTableSize) { |
| return absl::StrFormat("Size of Structure Table below minimum: %d bytes", |
| sizeTable); |
| } |
| if (sizeTable > maxTableSize) { |
| return absl::StrFormat("Size of Structure Table above maximum: %d bytes", |
| sizeTable); |
| } |
| |
| auto sizeActual = static_cast<uint32_t>(sizeEntry + sizeTable); |
| |
| // Copy a slice, the last 4 bytes of the header, local byte order |
| auto offset = sizeHeader - sizeof(uint32_t); |
| uint32_t sizeClaimed; |
| memcpy(&sizeClaimed, appHeader.data() + offset, sizeof(uint32_t)); |
| |
| // All other fields in app header are "don't care" for our purposes |
| if (sizeClaimed != sizeActual) { |
| return absl::StrFormat( |
| "Data size disagrees with header: %d claimed, %d actual size", |
| sizeClaimed, sizeActual); |
| } |
| |
| // The app will do additional validation, at app layer, not here |
| LOG(INFO) << "SMBIOS structure successfully validated"; |
| return ""; |
| } |
| |
| void SmbiosEntry::updateAppHeader() { |
| appHeader.clear(); |
| |
| // Constants |
| appHeader += headerDirVer; |
| appHeader += headerSmbios; |
| |
| // Pad these strings to exact size required |
| std::string bufferTime(sizeof(uint32_t), '\0'); |
| std::string bufferSize(sizeof(uint32_t), '\0'); |
| |
| // Timestamp (all fields are in local byte order) |
| auto timeActual = time(nullptr); |
| auto time32 = static_cast<uint32_t>(timeActual); |
| memcpy(bufferTime.data(), &time32, sizeof(uint32_t)); |
| appHeader += bufferTime; |
| |
| // Size of Entry Point and Structure Table together |
| auto sizeActual = entryPoint.size() + structureTable.size(); |
| auto size32 = static_cast<uint32_t>(sizeActual); |
| memcpy(bufferSize.data(), &size32, sizeof(uint32_t)); |
| appHeader += bufferSize; |
| } |
| |
| std::string SmbiosEntry::getFilename() const { |
| // Use 1-based numbering with filename, for consistency with object |
| return absl::StrFormat("%s%s%d", basePath_, filePrefix, instance + 1); |
| } |
| |
| int SmbiosEntry::getInstance() const { return instance; } |
| |
| bool SmbiosEntry::saveToFile(const std::string& fileName) const { |
| std::filesystem::path pathName{fileName}; |
| std::filesystem::path pathDir = pathName.parent_path(); |
| |
| // Make the directory first |
| std::error_code err; |
| std::filesystem::create_directories(pathDir, err); |
| if (err) { |
| LOG(ERROR) << "Unable to make directory " << pathDir << ": " |
| << err.message(); |
| return false; |
| } |
| |
| // Classic stdio to write exact bytes without additional formatting |
| FILE* fp = fopen(fileName.c_str(), "w"); |
| if (!fp) { |
| int e = errno; |
| LOG(ERROR) << "Unable to open file " << fileName |
| << " for writing: " << strerror(e); |
| return false; |
| } |
| |
| // Simple concatenation of blobs: header, Structure Table, Entry Point |
| if (fwrite(appHeader.data(), appHeader.size(), 1, fp) != 1) { |
| int e = errno; |
| LOG(ERROR) << "Unable to write to file " << fileName << ": " << strerror(e); |
| return false; |
| } |
| if (fwrite(structureTable.data(), structureTable.size(), 1, fp) != 1) { |
| int e = errno; |
| LOG(ERROR) << "Unable to write to file " << fileName << ": " << strerror(e); |
| return false; |
| } |
| if (fwrite(entryPoint.data(), entryPoint.size(), 1, fp) != 1) { |
| int e = errno; |
| LOG(ERROR) << "Unable to write to file " << fileName << ": " << strerror(e); |
| return false; |
| } |
| |
| if (fclose(fp) != 0) { |
| int e = errno; |
| LOG(ERROR) << "Unable to close file " << fileName << ": " << strerror(e); |
| return false; |
| } |
| |
| LOG(INFO) << "SMBIOS structure successfully written: " << fileName; |
| return true; |
| } |
| |
| bool SmbiosEntry::loadFromFile(const std::string& fileName) { |
| std::filesystem::path pathName{fileName}; |
| std::error_code err; |
| |
| auto totalFileSize = std::filesystem::file_size(pathName, err); |
| if (err) { |
| LOG(ERROR) << "Unable to read from file " << pathName << ": " |
| << err.message(); |
| return false; |
| } |
| |
| auto dataSize = totalFileSize - appHeaderSize; |
| auto tableSize = dataSize - entryPointSize; |
| |
| // Preflight the size before trying to read the content |
| if (tableSize < minTableSize) { |
| LOG(ERROR) << "Saved file format is too small: " << pathName; |
| return false; |
| } |
| if (tableSize > maxTableSize) { |
| LOG(ERROR) << "Saved file format is too large: " << pathName; |
| return false; |
| } |
| |
| LOG(INFO) << "Reading into instance " << instance << ": " << pathName; |
| |
| LOG(INFO) << "File size " << totalFileSize << " bytes: " << appHeaderSize |
| << " header, " << tableSize << " table, " << entryPointSize |
| << " entry"; |
| |
| // Classic stdio to read exact bytes without additional formatting |
| FILE* fp = fopen(fileName.c_str(), "r"); |
| if (!fp) { |
| int e = errno; |
| LOG(ERROR) << "Unable to open file " << fileName |
| << " for reading: " << strerror(e); |
| return false; |
| } |
| |
| // Pad these strings to exact size required |
| std::string bufferHeader(appHeaderSize, '\0'); |
| std::string bufferTable(tableSize, '\0'); |
| std::string bufferEntry(entryPointSize, '\0'); |
| |
| // Simple concatenation of blobs: header, Structure Table, Entry Point |
| if (fread(bufferHeader.data(), bufferHeader.size(), 1, fp) != 1) { |
| int e = errno; |
| LOG(ERROR) << "Unable to read from file " << fileName << ": " |
| << strerror(e); |
| return false; |
| } |
| if (fread(bufferTable.data(), bufferTable.size(), 1, fp) != 1) { |
| int e = errno; |
| LOG(ERROR) << "Unable to read from file " << fileName << ": " |
| << strerror(e); |
| return false; |
| } |
| if (fread(bufferEntry.data(), bufferEntry.size(), 1, fp) != 1) { |
| int e = errno; |
| LOG(ERROR) << "Unable to read from file " << fileName << ": " |
| << strerror(e); |
| return false; |
| } |
| |
| if (fclose(fp) != 0) { |
| int e = errno; |
| LOG(ERROR) << "Unable to close file " << fileName << ": " << strerror(e); |
| return false; |
| } |
| |
| // The file is good, temporary buffers can now become our content |
| appHeader = std::move(bufferHeader); |
| structureTable = std::move(bufferTable); |
| entryPoint = std::move(bufferEntry); |
| |
| LOG(INFO) << "SMBIOS structure successfully read: " << fileName; |
| return true; |
| } |
| |
| } // namespace blobs |