| /* Copyright 2021 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. |
| */ |
| |
| #define _POSIX_C_SOURCE 200809L |
| |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <strings.h> |
| |
| #include "crosid.h" |
| |
| /* |
| * This file implements matching the current device identity to an |
| * identity defined in the crosid identity table, normally stored at |
| * /usr/share/chromeos-config/identity.bin. |
| * |
| * The file format of identity.bin is as follows: |
| * - The table header (a struct crosid_table_header). |
| * - N table entries (each a struct crosid_table_entry). |
| * - Strings referenced by the table entries. Each is referenced by |
| * an offset from the beginning of the strings area. |
| * |
| * identity.bin is generated by Python code in cros_config_schema. If |
| * the table format is adjusted here, the same changes must be made in |
| * cros_config_schema. Backwards-incompatible changes must bump the |
| * version number in the header to prevent the wrong identity.bin from |
| * being used with crosid. |
| */ |
| |
| #define NO_TABLE_MATCH_ERROR \ |
| "No defined device identities matched this device.\n" \ |
| "This is likely one of the following problems:\n" \ |
| "- You are running an OS image for the wrong board on this device\n" \ |
| "- Your device is not correctly provisioned (i.e., CBI or VPD unprovisioned)\n" \ |
| "- model.yaml or Boxster has not been set up for your correctly provisioned device\n" \ |
| "\n" \ |
| "Hint: you can always run \"crosid -v\" for a full explanation.\n" |
| |
| static char *get_string(struct crosid_table_header *table, uint32_t string_idx) |
| { |
| char *strings = (char *)&table->entries[table->entry_count]; |
| |
| return strings + string_idx; |
| } |
| |
| static bool |
| check_optional_string_match(struct crosid_optional_string *device_str, |
| const char *identity_str, const char *log_name) |
| { |
| crosid_log(LOG_DBG, " Requires %s == \"%s\" ", log_name, identity_str); |
| |
| if (!device_str->present) { |
| /* |
| * For historical reasons, an empty string will match |
| * a non-present value, as both libcros_config and |
| * mosys did it this way. |
| */ |
| if (strlen(identity_str) > 0) { |
| crosid_log(LOG_DBG, "(mismatch, device has no %s)\n", |
| log_name); |
| return false; |
| } |
| } else if (strcasecmp(device_str->value, identity_str)) { |
| crosid_log(LOG_DBG, "(\"%s\" does not match)\n", |
| device_str->value); |
| return false; |
| } |
| |
| crosid_log(LOG_DBG, "(matches!)\n"); |
| return true; |
| } |
| |
| static bool fdt_compatible(struct crosid_optional_string *procfs_compat, |
| const char *match) |
| { |
| if (!procfs_compat->present) { |
| crosid_log(LOG_DBG, |
| " No compatible strings in procfs (no match)\n"); |
| return false; |
| } |
| |
| for (size_t i = 0; i < procfs_compat->len; |
| i += strlen(procfs_compat->value + i) + 1) { |
| crosid_log(LOG_DBG, " \"%s\" == \"%s\"? ", match, |
| procfs_compat->value + i); |
| if (!strncmp(match, procfs_compat->value + i, strlen(match))) { |
| crosid_log(LOG_DBG, "true\n"); |
| return true; |
| } else { |
| crosid_log(LOG_DBG, "false\n"); |
| } |
| } |
| |
| return false; |
| } |
| |
| static bool table_entry_matches(struct crosid_table_header *table, |
| int entry_idx, |
| struct crosid_probed_device_data *data) |
| { |
| struct crosid_table_entry *entry = &table->entries[entry_idx]; |
| const char *strings = (const char *)&table->entries[table->entry_count]; |
| int mismatches = 0; |
| |
| if (entry->flags & MATCH_SMBIOS_NAME) { |
| mismatches += !check_optional_string_match( |
| &data->smbios_name, strings + entry->smbios_name_match, |
| "SMBIOS name"); |
| } |
| |
| if (entry->flags & MATCH_FDT_COMPATIBLE) { |
| crosid_log( |
| LOG_DBG, |
| " Requires one FDT compatible string matching \"%s\"\n", |
| strings + entry->fdt_compatible_match); |
| if (!fdt_compatible(&data->fdt_compatible, |
| strings + entry->fdt_compatible_match)) { |
| mismatches++; |
| crosid_log(LOG_DBG, " Does not match\n"); |
| } else { |
| crosid_log(LOG_DBG, " Match FDT compatible!\n"); |
| } |
| } |
| |
| if (entry->flags & MATCH_SKU_ID) { |
| crosid_log(LOG_DBG, " Requires SKU ID == %u ", |
| entry->sku_id_match); |
| if (!data->has_sku_id) { |
| crosid_log(LOG_DBG, |
| "(mismatch, as this device has no SKU)\n"); |
| mismatches++; |
| } else if (data->sku_id != entry->sku_id_match) { |
| crosid_log(LOG_DBG, "(%u does not match)\n", |
| data->sku_id); |
| mismatches++; |
| } else { |
| crosid_log(LOG_DBG, "(matches!)\n"); |
| } |
| } |
| |
| if (entry->flags & MATCH_CUSTOMIZATION_ID) { |
| mismatches += !check_optional_string_match( |
| &data->customization_id, |
| strings + entry->customization_id_match, |
| "customization_id"); |
| } |
| |
| if (data->custom_label_tag.present && |
| strlen(data->custom_label_tag.value) > 0 && |
| !(entry->flags & MATCH_CUSTOM_LABEL_TAG)) { |
| /* |
| * If the device has a non-zero length custom label |
| * tag, but this config does not have a custom label |
| * tag, it should not match. |
| */ |
| crosid_log(LOG_DBG, |
| "mismatch, as the config has no custom-label-tag" |
| ", but this device does\n"); |
| mismatches++; |
| } else if (entry->flags & MATCH_CUSTOM_LABEL_TAG) { |
| mismatches += !check_optional_string_match( |
| &data->custom_label_tag, |
| strings + entry->custom_label_tag_match, |
| "custom_label_tag"); |
| } |
| |
| return mismatches == 0; |
| } |
| |
| int crosid_match(struct crosid_probed_device_data *data) |
| { |
| int rv = -1; |
| char *table_raw; |
| size_t table_sz; |
| struct crosid_table_header *table; |
| |
| if (crosid_read_file(UNIBUILD_CONFIG_PATH, "identity.bin", &table_raw, |
| &table_sz) < 0) { |
| crosid_log(LOG_ERR, |
| "Failed to read unibuild device identity table\n"); |
| return -1; |
| } |
| |
| table = (struct crosid_table_header *)table_raw; |
| if (table_sz < sizeof(*table)) { |
| crosid_log(LOG_ERR, "Identity table corrupted (too small?)\n"); |
| goto exit; |
| } |
| |
| if (table->version != CROSID_TABLE_VERSION) { |
| crosid_log(LOG_ERR, |
| "Incorrect table version: expected %u, got %u\n", |
| CROSID_TABLE_VERSION, table->version); |
| goto exit; |
| } |
| |
| if (table_raw + table_sz < |
| (char *)&table->entries[table->entry_count]) { |
| crosid_log(LOG_ERR, |
| "Table file is too small to contain all entries\n"); |
| goto exit; |
| } |
| |
| crosid_log(LOG_DBG, "There are %u possible identities\n", |
| table->entry_count); |
| |
| for (size_t i = 0; i < table->entry_count; i++) { |
| crosid_log(LOG_DBG, "\nIdentity %zu:\n", i); |
| if (table_entry_matches(table, i, data)) { |
| crosid_log(LOG_DBG, "Identity %zu is a match!\n", i); |
| rv = i; |
| data->firmware_manifest_key = strdup(get_string( |
| table, |
| table->entries[i].firmware_manifest_key)); |
| goto exit; |
| } else { |
| crosid_log(LOG_DBG, "Identity %zu is incompatible.\n", |
| i); |
| } |
| } |
| |
| if (rv < 0) |
| crosid_log(LOG_ERR, NO_TABLE_MATCH_ERROR); |
| |
| exit: |
| free(table_raw); |
| return rv; |
| } |