blob: 2751f90360e9307c9ddc9441dd9a3a694d671793 [file] [log] [blame] [edit]
#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