blob: 3cb317bf388ccbfc4d2a3c5165786f5a9abaf4ba [file] [log] [blame]
// 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.
#include "arc/setup/arc_property_util.h"
#include <algorithm>
#include <tuple>
#include <vector>
#include <base/command_line.h>
#include <base/files/file_util.h>
#include <base/json/json_reader.h>
#include <base/logging.h>
#include <base/process/launch.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <chromeos-config/libcros_config/cros_config.h>
namespace arc {
namespace {
// The path in the chromeos-config database where Android properties will be
// looked up.
constexpr char kCrosConfigPropertiesPath[] = "/arc/build-properties";
// Android property name used to store the board name.
constexpr char kBoardPropertyPrefix[] = "ro.product.board=";
// Android property name for custom key used for Play Auto Install selection.
constexpr char kOEMKey1PropertyPrefix[] = "ro.oem.key1=";
// Configuration property name of an optional string that contains a comma-
// separated list of regions to include in the OEM key property.
constexpr char kPAIRegionsPropertyName[] = "pai-regions";
// Properties related to dynamically adding native bridge 64 bit support.
// Note that "%s" is lated replaced with a partition name which is either an
// empty string or a string that ends with '.' e.g. "system_ext.".
constexpr char kAbilistPropertyPrefixTemplate[] = "ro.%sproduct.cpu.abilist=";
constexpr char kAbilistPropertyExpected[] = "x86_64,x86,armeabi-v7a,armeabi";
constexpr char kAbilistPropertyReplacement[] =
"x86_64,x86,arm64-v8a,armeabi-v7a,armeabi";
constexpr char kAbilist64PropertyPrefixTemplate[] =
"ro.%sproduct.cpu.abilist64=";
constexpr char kAbilist64PropertyExpected[] = "x86_64";
constexpr char kAbilist64PropertyReplacement[] = "x86_64,arm64-v8a";
constexpr char kDalvikVmIsaArm64[] = "ro.dalvik.vm.isa.arm64=x86_64";
constexpr char kPrimaryDisplayOrientationPropertyTemplate[] =
"ro.surface_flinger.primary_display_orientation=ORIENTATION_%d";
// Prefix of Android property to enable debugging features.
constexpr char kDebuggablePropertyPrefix[] = "ro.debuggable=";
// Maximum length of an Android property value.
constexpr int kAndroidMaxPropertyLength = 91;
bool FindProperty(const std::string& line_prefix_to_find,
std::string* out_prop,
const std::string& line) {
if (base::StartsWith(line, line_prefix_to_find,
base::CompareCase::SENSITIVE)) {
*out_prop = line.substr(line_prefix_to_find.length());
return true;
}
return false;
}
bool TruncateAndroidProperty(const std::string& line, std::string* truncated) {
// If line looks like key=value, cut value down to the max length of an
// Android property. Build fingerprint needs special handling to preserve the
// trailing dev-keys indicator, but other properties can just be truncated.
size_t eq_pos = line.find('=');
if (eq_pos == std::string::npos) {
*truncated = line;
return true;
}
std::string val = line.substr(eq_pos + 1);
base::TrimWhitespaceASCII(val, base::TRIM_ALL, &val);
if (val.length() <= kAndroidMaxPropertyLength) {
*truncated = line;
return true;
}
const std::string key = line.substr(0, eq_pos);
LOG(WARNING) << "Truncating property " << key << " value: " << val;
if (key == "ro.bootimage.build.fingerprint" &&
base::EndsWith(val, "/dev-keys", base::CompareCase::SENSITIVE)) {
// Typical format is brand/product/device/.../dev-keys. We want to remove
// characters from product and device to get below the length limit.
// Assume device has the format {product}_cheets.
std::vector<std::string> fields =
base::SplitString(val, "/", base::WhitespaceHandling::KEEP_WHITESPACE,
base::SplitResult::SPLIT_WANT_ALL);
if (fields.size() < 5) {
LOG(ERROR) << "Invalid build fingerprint: " << val;
return false;
}
size_t remove_chars = (val.length() - kAndroidMaxPropertyLength + 1) / 2;
if (fields[1].length() <= remove_chars) {
LOG(ERROR) << "Unable to remove " << remove_chars << " characters from "
<< fields[1];
return false;
}
fields[1] = fields[1].substr(0, fields[1].length() - remove_chars);
fields[2] = fields[1] + "_cheets";
val = base::JoinString(fields, "/");
} else {
val = val.substr(0, kAndroidMaxPropertyLength);
}
*truncated = key + "=" + val;
return true;
}
// Computes the value of ro.oem.key1 based on the build-time ro.product.board
// value and the device's region of origin.
std::string ComputeOEMKey(brillo::CrosConfigInterface* config,
const std::string& board) {
std::string regions;
if (!config->GetString(kCrosConfigPropertiesPath, kPAIRegionsPropertyName,
&regions)) {
// No region list found, just use the board name as before.
return board;
}
std::string region_code;
if (!base::GetAppOutput({"cros_region_data", "region_code"}, &region_code)) {
LOG(WARNING) << "Failed to get region code";
return board;
}
// Remove trailing newline.
region_code.erase(std::remove(region_code.begin(), region_code.end(), '\n'),
region_code.end());
// Allow wildcard configuration to indicate that all regions should be
// included.
if (regions.compare("*") == 0 && region_code.length() >= 2)
return board + "_" + region_code;
// Check to see if region code is in the list of regions that should be
// included in the property.
const std::vector<std::string> region_vector =
base::SplitString(regions, ",", base::WhitespaceHandling::TRIM_WHITESPACE,
base::SplitResult::SPLIT_WANT_NONEMPTY);
for (const auto& region : region_vector) {
if (region_code.compare(region) == 0)
return board + "_" + region_code;
}
return board;
}
bool IsComment(const std::string& line) {
return base::StartsWith(
base::TrimWhitespaceASCII(line, base::TrimPositions::TRIM_LEADING), "#",
base::CompareCase::SENSITIVE);
}
bool ExpandPropertyContents(const std::string& content,
brillo::CrosConfigInterface* config,
std::string* expanded_content,
bool filter_non_ro_props,
bool add_native_bridge_64bit_support,
bool append_dalvik_isa,
bool debuggable,
const std::string& partition_name) {
const std::vector<std::string> lines = base::SplitString(
content, "\n", base::WhitespaceHandling::KEEP_WHITESPACE,
base::SplitResult::SPLIT_WANT_ALL);
std::string new_properties;
for (std::string line : lines) {
// Since Chrome only expands ro. properties at runtime, skip processing
// non-ro lines here for R+. For P, we cannot do that because the
// expanded property files will directly replace the original ones via
// bind mounts.
if (filter_non_ro_props &&
!base::StartsWith(line, "ro.", base::CompareCase::SENSITIVE)) {
if (!IsComment(line) && line.find('{') != std::string::npos) {
// The non-ro property has substitution(s).
LOG(ERROR) << "Found substitution(s) in a non-ro property: " << line;
return false;
}
continue;
}
// First expand {property} substitutions in the string. The insertions
// may contain substitutions of their own, so we need to repeat until
// nothing more is found.
bool inserted;
do {
inserted = false;
size_t match_start = line.find('{');
size_t prev_match = 0; // 1 char past the end of the previous {} match.
std::string expanded;
// Find all of the {} matches on the line.
while (match_start != std::string::npos) {
expanded += line.substr(prev_match, match_start - prev_match);
size_t match_end = line.find('}', match_start);
if (match_end == std::string::npos) {
LOG(ERROR) << "Unmatched { found in line: " << line;
return false;
}
const std::string keyword =
line.substr(match_start + 1, match_end - match_start - 1);
std::string replacement;
if (config->GetString(kCrosConfigPropertiesPath, keyword,
&replacement)) {
expanded += replacement;
inserted = true;
} else {
LOG(ERROR) << "Did not find a value for " << keyword
<< " while expanding " << line;
return false;
}
prev_match = match_end + 1;
match_start = line.find('{', match_end);
}
if (prev_match != std::string::npos)
expanded += line.substr(prev_match);
line = expanded;
} while (inserted);
if (add_native_bridge_64bit_support) {
// Special-case ro.<partition>product.cpu.abilist and
// ro.<partition>product.cpu.abilist64 to add ARM64.
// Note that <partition> is either an empty string or a string that ends
// with '.' e.g. "system_ext.".
std::string prefix = base::StringPrintf(kAbilistPropertyPrefixTemplate,
partition_name.c_str());
std::string value;
if (FindProperty(prefix, &value, line)) {
if (value == kAbilistPropertyExpected) {
line = prefix + std::string(kAbilistPropertyReplacement);
} else {
LOG(ERROR) << "Found unexpected value for " << prefix << ", value "
<< value;
return false;
}
}
prefix = base::StringPrintf(kAbilist64PropertyPrefixTemplate,
partition_name.c_str());
if (FindProperty(prefix, &value, line)) {
if (value == kAbilist64PropertyExpected) {
line = prefix + std::string(kAbilist64PropertyReplacement);
} else {
LOG(ERROR) << "Found unexpected value for " << prefix << ", value "
<< value;
return false;
}
}
}
{
// Replace ro.debuggable value with |debuggable| flag.
const std::string prefix(kDebuggablePropertyPrefix);
std::string value;
if (FindProperty(prefix, &value, line)) {
line = prefix + (debuggable ? "1" : "0");
}
}
std::string truncated;
if (!TruncateAndroidProperty(line, &truncated)) {
LOG(ERROR) << "Unable to truncate property: " << line;
return false;
}
new_properties += truncated + "\n";
// Special-case ro.product.board to compute ro.oem.key1 at runtime, as it
// can depend upon the device region.
std::string property;
if (FindProperty(kBoardPropertyPrefix, &property, line)) {
std::string oem_key_property = ComputeOEMKey(config, property);
new_properties +=
std::string(kOEMKey1PropertyPrefix) + oem_key_property + "\n";
// TODO(b/211563458): Remove this super dirty hack ASAP.
//
// The only customization method we currently have for this property
// is to add it to PRODUCT_PROPERTY_OVERRIDES in board_specific_setup.
// It is a build time, *per-board* configuration.
// However, a new model in board="dedede" family turned out to require
// a different values from other models in the same family (b/210802730).
//
// To work around the lack of per-model configuration, the below hack
// directly embeds the mapping from model name here as a short-term
// solution. The right approach should be to dynamically pass the
// panel orientation from Chrome down to arc-setup.
if (property == "bugzzy") {
new_properties +=
base::StringPrintf(kPrimaryDisplayOrientationPropertyTemplate, 90) +
"\n";
}
}
}
if (append_dalvik_isa) {
// Special-case to add ro.dalvik.vm.isa.arm64.
new_properties += std::string(kDalvikVmIsaArm64) + "\n";
}
*expanded_content = new_properties;
return true;
}
bool ExpandPropertyFile(const base::FilePath& input,
const base::FilePath& output,
brillo::CrosConfigInterface* config,
bool append,
bool add_native_bridge_64bit_support,
bool append_dalvik_isa,
bool debuggable,
const std::string& partition_name) {
std::string content;
std::string expanded;
if (!base::ReadFileToString(input, &content)) {
PLOG(ERROR) << "Failed to read " << input;
return false;
}
if (!ExpandPropertyContents(content, config, &expanded,
/*filter_non_ro_props=*/append,
add_native_bridge_64bit_support,
append_dalvik_isa, debuggable, partition_name)) {
return false;
}
if (append && base::PathExists(output)) {
if (!base::AppendToFile(output, expanded)) {
PLOG(ERROR) << "Failed to append to " << output;
return false;
}
} else {
if (!base::WriteFile(output, expanded)) {
PLOG(ERROR) << "Failed to write to " << output;
return false;
}
}
return true;
}
} // namespace
bool ExpandPropertyContentsForTesting(const std::string& content,
brillo::CrosConfigInterface* config,
bool debuggable,
std::string* expanded_content) {
return ExpandPropertyContents(content, config, expanded_content,
/*filter_non_ro_props=*/true,
/*add_native_bridge_64bit_support=*/false,
false, debuggable, std::string());
}
bool TruncateAndroidPropertyForTesting(const std::string& line,
std::string* truncated) {
return TruncateAndroidProperty(line, truncated);
}
bool ExpandPropertyFileForTesting(const base::FilePath& input,
const base::FilePath& output,
brillo::CrosConfigInterface* config) {
return ExpandPropertyFile(input, output, config, /*append=*/false,
/*add_native_bridge_64bit_support=*/false, false,
/*debuggable=*/false, std::string());
}
bool ExpandPropertyFiles(const base::FilePath& source_path,
const base::FilePath& dest_path,
bool single_file,
bool add_native_bridge_64bit_support,
bool debuggable) {
brillo::CrosConfig config;
if (single_file)
base::DeleteFile(dest_path);
// default.prop may not exist. Silently skip it if not found.
for (const auto& tuple :
// The order has to match the one in PropertyLoadBootDefaults() in
// system/core/init/property_service.cpp.
// Note: Our vendor image doesn't have /vendor/default.prop although
// PropertyLoadBootDefaults() tries to open it.
{std::tuple<const char*, bool, bool, const char*>{"default.prop", true,
false, ""},
{"build.prop", false, true, ""},
{"system_ext_build.prop", true, false, "system_ext."},
{"vendor_build.prop", false, false, "vendor."},
{"odm_build.prop", true, false, "odm."},
{"product_build.prop", true, false, "product."}}) {
const char* file = std::get<0>(tuple);
const bool is_optional = std::get<1>(tuple);
// When true, unconditionally add |kDalvikVmIsaArm64| property.
const bool append_dalvik_isa =
std::get<2>(tuple) && add_native_bridge_64bit_support;
// Search for ro.<partition_name>product.cpu.abilist* properties.
const char* partition_name = std::get<3>(tuple);
const base::FilePath source_file = source_path.Append(file);
if (is_optional && !base::PathExists(source_file))
continue;
if (!ExpandPropertyFile(
source_file, single_file ? dest_path : dest_path.Append(file),
&config,
/*append=*/single_file, add_native_bridge_64bit_support,
append_dalvik_isa, debuggable, partition_name)) {
LOG(ERROR) << "Failed to expand " << source_file;
return false;
}
}
return true;
}
} // namespace arc