blob: 1e6e40735359d2a75e7868caa0826ba867ed5fc5 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Look up identity information for the current device
// Also provide a way to fake identity for testing.
#include "chromeos-config/libcros_config/cros_config.h"
#include "chromeos-config/libcros_config/identity.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
#include <vector>
#include <base/logging.h>
#include <base/files/file_util.h>
#include <base/strings/stringprintf.h>
namespace brillo {
// Table header
// The specific table struct follows immediately after this
struct SmbiosHeader {
uint8_t type; // enum SmbiosTypes
uint8_t length; // length of this table in bytes (excluding string table)
uint16_t handle;
} __attribute__((packed));
// Table type 1 - system information
struct SmbiosTableSystem {
uint8_t manufacturer;
uint8_t name; // Index of string containing platform name
uint8_t version;
uint8_t serial_number;
uint8_t uuid[16];
uint8_t wakeup_type;
uint8_t sku_number; // Index of string containing SKU name "sku%d"
uint8_t family;
} __attribute__((packed));
// This is our internal format for a decoded table. It includes the header, the
// data specific to this table type, and a table of strings.
struct SmbiosTable {
SmbiosHeader header;
union {
SmbiosTableSystem system;
uint8_t data[1024];
} data;
std::vector<std::string> strings;
};
CrosConfigIdentity::CrosConfigIdentity() {}
CrosConfigIdentity::~CrosConfigIdentity() {}
bool CrosConfigIdentity::WriteFakeTables(base::File* smbios_file,
const std::string& name,
int sku_id) {
// Write a header for a BIOS table
// We don't put anything in this. It is just to test that we can skip tables
// we don't want to parse.
// This goes right at the start of the file, corresponding to the start of
// the /sys/firmware/dmi/tables/DMI file.
SmbiosHeader hdr;
memset(&hdr, '\0', sizeof(hdr));
hdr.type = SMBIOS_TYPE_BIOS;
hdr.length = 100; // Arbitrary size smaller than the maximum.
int pos = hdr.length;
if (smbios_file->Write(0, reinterpret_cast<char*>(&hdr), sizeof(hdr)) < 0) {
CROS_CONFIG_LOG(ERROR) << "Failed to write header";
return false;
}
// Write an empty string table after the header record.
if (smbios_file->Write(pos, "\0\0", 2) < 0) {
CROS_CONFIG_LOG(ERROR) << "Failed to write empty string table";
return false;
}
// Our system table follows immediately after the BIOS table. It has two
// indexed strings, numbered 1 and 2.
SmbiosTableSystem system;
memset(&system, '\0', sizeof(system));
system.name = 1;
system.sku_number = 2;
std::string sku_id_str = base::StringPrintf("sku%d", sku_id);
hdr.type = SMBIOS_TYPE_SYSTEM;
hdr.length = sizeof(hdr) + sizeof(system);
if (smbios_file->Seek(base::File::FROM_BEGIN, pos + 2) == -1) {
CROS_CONFIG_LOG(ERROR) << "Failed to seek";
}
// This is the system table. First write the header and the table data.
if (smbios_file->WriteAtCurrentPos(reinterpret_cast<char*>(&hdr),
sizeof(hdr)) < 0) {
CROS_CONFIG_LOG(ERROR) << "Failed to write system header";
return false;
}
if (smbios_file->WriteAtCurrentPos(reinterpret_cast<char*>(&system),
sizeof(system)) < 0) {
CROS_CONFIG_LOG(ERROR) << "Failed to write system table";
return false;
}
// Next write the string table and terminator.
if (smbios_file->WriteAtCurrentPos(name.c_str(), name.length() + 1) < 0) {
CROS_CONFIG_LOG(ERROR) << "Failed to write name";
return false;
}
if (smbios_file->WriteAtCurrentPos(sku_id_str.c_str(),
sku_id_str.length() + 1) < 0) {
CROS_CONFIG_LOG(ERROR) << "Failed to write SKU string";
return false;
}
char zero = 0;
if (smbios_file->WriteAtCurrentPos(&zero, 1) < 0) {
CROS_CONFIG_LOG(ERROR) << "Failed to write terminator";
return false;
}
return true;
}
bool CrosConfigIdentity::FakeIdentity(const std::string& name, int sku_id,
const std::string& customization_id,
base::FilePath* smbios_file_out,
base::FilePath* vpd_file_out) {
*vpd_file_out = base::FilePath("vpd");
if (base::WriteFile(*vpd_file_out, customization_id.c_str(),
customization_id.length()) != customization_id.length()) {
CROS_CONFIG_LOG(ERROR) << "Failed to write VPD file";
return false;
}
// Write out to a temporary file used just for testing.
*smbios_file_out = base::FilePath("dev_mem");
base::File dev_mem(*smbios_file_out,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!dev_mem.IsValid()) {
CROS_CONFIG_LOG(ERROR) << "Failed to create dev_mem";
return false;
}
if (!WriteFakeTables(&dev_mem, name, sku_id)) {
CROS_CONFIG_LOG(ERROR) << "Failed to write tables";
return false;
}
return true;
}
unsigned int CrosConfigIdentity::StringTableLength(const char* ptr) {
// Use a special case for an empty string table.
if (!*ptr)
return 2;
int len, total_len;
for (total_len = 0; *ptr; ptr += len) {
len = strlen(ptr) + 1;
total_len += len;
}
return total_len + 1;
}
bool CrosConfigIdentity::FindAndCopyTable(enum SmbiosTypes type,
const char* base_ptr,
unsigned int size,
SmbiosTable* table_out) {
// Look through the region for the table type we want.
const SmbiosHeader* hdr = 0;
const char* ptr;
for (ptr = base_ptr; ptr < base_ptr + size;
ptr += hdr->length, ptr += StringTableLength(ptr)) {
hdr = reinterpret_cast<const SmbiosHeader*>(ptr);
if (hdr->type == type) {
break;
}
}
if (!hdr || hdr->type != type) {
CROS_CONFIG_LOG(ERROR) << "Table type " << type
<< " not found in system table";
return false;
}
// Copy out the table header and contents
memcpy(&table_out->header, hdr, sizeof(*hdr));
memcpy(table_out->data.data, hdr + 1, hdr->length - sizeof(*hdr));
// Figure out the size of the string table, then copy that too.
const char* strings_ptr = ptr + hdr->length;
while (*strings_ptr) {
const int len = strlen(strings_ptr);
table_out->strings.emplace_back(strings_ptr, len);
strings_ptr += len + 1;
}
VLOG(1) << "found SMBIOS table";
return true;
}
bool CrosConfigIdentity::GetSystemTable(const base::FilePath& smbios_file,
SmbiosTable* table_out) {
int fd = open(smbios_file.MaybeAsASCII().c_str(), O_RDONLY);
if (fd < 0) {
CROS_CONFIG_LOG(ERROR) << "Could not open " << smbios_file.MaybeAsASCII()
<< ": " << strerror(errno);
return false;
}
struct stat buf;
if (fstat(fd, &buf) == -1) {
CROS_CONFIG_LOG(ERROR) << "Could not get file size of"
<< smbios_file.MaybeAsASCII() << ": "
<< strerror(errno);
}
unsigned int table_size = buf.st_size;
void* ptr = mmap(0, table_size, PROT_READ, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
close(fd);
CROS_CONFIG_LOG(ERROR) << "Could not map file "
<< smbios_file.MaybeAsASCII() << ": "
<< strerror(errno);
return false;
}
// Find the system table and copy it out.
bool ok = FindAndCopyTable(SMBIOS_TYPE_SYSTEM, static_cast<char*>(ptr),
table_size, table_out);
munmap(ptr, table_size);
close(fd);
return ok;
}
std::string CrosConfigIdentity::GetSmbiosString(const SmbiosTable& table,
unsigned int string_id) {
// The first string is numbered 1, so we need to subtract one first.
string_id--;
if (string_id < table.strings.size()) {
return table.strings[string_id];
}
return std::string();
}
bool CrosConfigIdentity::ReadIdentity(const base::FilePath& smbios_file,
const base::FilePath& vpd_file,
std::string* name_out, int* sku_id_out,
std::string* customization_id_out) {
SmbiosTable table;
if (!GetSystemTable(smbios_file, &table)) {
CROS_CONFIG_LOG(ERROR) << "Could not get system table";
return false;
}
// Get the system name and decode the SKU ID from its string.
*name_out = GetSmbiosString(table, table.data.system.name);
std::string sku_str = GetSmbiosString(table, table.data.system.sku_number);
if (std::sscanf(sku_str.c_str(), "sku%d", sku_id_out) != 1) {
CROS_CONFIG_LOG(WARNING) << "Invalid SKU string: " << sku_str;
*sku_id_out = -1;
}
if (!base::ReadFileToString(vpd_file, customization_id_out)) {
CROS_CONFIG_LOG(WARNING) << "No customization_id in VPD";
// This file is only used for whitelabels, so may be missing. Without it
// we rely on just the name and SKU ID.
}
CROS_CONFIG_LOG(INFO) << "name: " << *name_out << ", sku_id: " << *sku_id_out
<< ", customization_id: " << *customization_id_out;
return true;
}
} // namespace brillo