blob: c81d71426f33a2d3db60268b4e4a164b0a3f1fa0 [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 "featured/service.h"
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <base/json/json_reader.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <build/build_config.h>
#include <build/buildflag.h>
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/files/scoped_temp_dir.h"
namespace featured {
namespace {
constexpr char kPlatformFeaturesPath[] = "/etc/init/platform-features.json";
// JSON Helper to retrieve a string value given a string key
bool GetStringFromKey(const base::Value& obj,
const std::string& key,
std::string* value) {
const std::string* val = obj.FindStringKey(key);
if (!val || val->empty()) {
return false;
}
*value = *val;
return true;
}
} // namespace
WriteFileCommand::WriteFileCommand(const std::string& file_name,
const std::string& value)
: FeatureCommand("WriteFile") {
file_name_ = file_name;
value_ = value;
}
bool WriteFileCommand::Execute() {
if (!base::WriteFile(base::FilePath(file_name_), value_)) {
PLOG(ERROR) << "Unable to write to " << file_name_;
return false;
}
return true;
}
FileExistsCommand::FileExistsCommand(const std::string& file_name)
: FeatureCommand("FileExists") {
file_name_ = file_name;
}
bool FileExistsCommand::Execute() {
return base::PathExists(base::FilePath(file_name_));
}
bool PlatformFeature::Execute() const {
for (auto& cmd : exec_cmds_) {
if (!cmd->Execute()) {
LOG(ERROR) << "Failed to execute command: " << cmd->name();
return false;
}
}
return true;
}
bool PlatformFeature::IsSupported() const {
for (auto& cmd : support_check_cmds_) {
if (!cmd->Execute()) {
return false;
}
}
return true;
}
bool JsonFeatureParser::ParseFile(const base::FilePath& path,
std::string* err_str) {
std::string input;
if (features_parsed_)
return true;
if (!ReadFileToString(path, &input)) {
*err_str = "featured: Failed to read conf file: ";
*err_str += kPlatformFeaturesPath;
return false;
}
VLOG(1) << "JSON file contents: " << input;
base::JSONReader::ValueWithError root =
base::JSONReader::ReadAndReturnValueWithError(input);
if (!root.value) {
*err_str = "featured: Failed to parse conf file: ";
*err_str += kPlatformFeaturesPath;
return false;
}
if (!root.value->is_list() || root.value->GetList().size() == 0) {
*err_str = "featured: features list should be non-zero size!";
return false;
}
for (const auto& item : root.value->GetList()) {
if (!item.is_dict()) {
*err_str = "featured: features conf not list of dicts!";
return false;
}
auto feature_obj_optional = MakeFeatureObject(item, err_str);
if (!feature_obj_optional) {
return false;
}
PlatformFeature feature_obj(std::move(*feature_obj_optional));
auto got = feature_map_.find(feature_obj.name());
if (got != feature_map_.end()) {
*err_str =
"featured: Duplicate feature name found! : " + feature_obj.name();
return false;
}
feature_map_.insert(
std::make_pair(feature_obj.name(), std::move(feature_obj)));
}
features_parsed_ = true;
return true;
}
// PlatformFeature implementation (collect and execute commands).
std::optional<PlatformFeature> JsonFeatureParser::MakeFeatureObject(
const base::Value& feature_obj, std::string* err_str) {
std::string feat_name;
if (!GetStringFromKey(feature_obj, "name", &feat_name)) {
*err_str = "featured: features conf contains empty names";
return std::nullopt;
}
// Commands for querying if device is supported
const base::Value* support_cmd_list_obj =
feature_obj.FindListKey("support_check_commands");
std::vector<std::unique_ptr<FeatureCommand>> query_cmds;
if (!support_cmd_list_obj) {
// Feature is assumed to be always supported, such as a kernel parameter
// that is on all device kernels.
query_cmds.push_back(std::make_unique<AlwaysSupportedCommand>());
} else {
// A support check command was provided, add it to the feature object.
if (!support_cmd_list_obj->is_list() ||
support_cmd_list_obj->GetList().size() == 0) {
*err_str = "featured: Invalid format for support_check_commands commands";
return std::nullopt;
}
for (const auto& item : support_cmd_list_obj->GetList()) {
std::string cmd_name;
if (!GetStringFromKey(item, "name", &cmd_name)) {
*err_str = "featured: Invalid/Empty command name in features config.";
return std::nullopt;
}
if (cmd_name == "FileExists") {
std::string file_name;
VLOG(1) << "featured: command is FileExists";
if (!GetStringFromKey(item, "file", &file_name)) {
*err_str = "featured: JSON contains invalid command name";
return std::nullopt;
}
query_cmds.push_back(std::make_unique<FileExistsCommand>(file_name));
} else {
*err_str =
"featured: Invalid support command name in features config: ";
*err_str += cmd_name;
return std::nullopt;
}
}
}
// Commands to execute to enable feature
const base::Value* cmd_list_obj = feature_obj.FindListKey("commands");
if (!cmd_list_obj || !cmd_list_obj->is_list() ||
cmd_list_obj->GetList().size() == 0) {
*err_str = "featured: Failed to get commands list in feature.";
return std::nullopt;
}
std::vector<std::unique_ptr<FeatureCommand>> feature_cmds;
for (const auto& item : cmd_list_obj->GetList()) {
std::string cmd_name;
if (!GetStringFromKey(item, "name", &cmd_name)) {
*err_str = "featured: Invalid command in features config.";
return std::nullopt;
}
if (cmd_name == "WriteFile") {
std::string file_name, value;
VLOG(1) << "featured: command is WriteFile";
if (!GetStringFromKey(item, "file", &file_name)) {
*err_str = "featured: JSON contains invalid command name!";
return std::nullopt;
}
if (!GetStringFromKey(item, "value", &value)) {
*err_str = "featured: JSON contains invalid command value!";
return std::nullopt;
}
feature_cmds.push_back(
std::make_unique<WriteFileCommand>(file_name, value));
} else {
*err_str = "featured: Invalid command name in features config: ";
*err_str += cmd_name;
return std::nullopt;
}
}
return PlatformFeature(feat_name, std::move(query_cmds),
std::move(feature_cmds));
}
bool DbusFeaturedService::ParseFeatureList(std::string* err_str) {
DCHECK(err_str);
return parser_->ParseFile(base::FilePath(kPlatformFeaturesPath), err_str);
}
bool DbusFeaturedService::GetFeatureList(std::string* csv_list,
std::string* err_str) {
DCHECK(csv_list);
DCHECK(err_str);
csv_list->clear();
if (!ParseFeatureList(err_str)) {
return false;
}
bool first = true;
for (const auto& it : *(parser_->GetFeatureMap())) {
if (!first)
csv_list->append(",");
else
first = false;
csv_list->append(it.first);
}
return true;
}
bool DbusFeaturedService::PlatformFeatureEnable(const std::string& name,
std::string* err_str) {
DCHECK(err_str);
if (!ParseFeatureList(err_str)) {
return false;
}
auto feature = parser_->GetFeatureMap()->find(name);
if (feature == parser_->GetFeatureMap()->end()) {
*err_str = "featured: Feature not found in features config!";
return false;
}
const PlatformFeature& feature_obj = feature->second;
if (!feature_obj.IsSupported()) {
*err_str = "featured: device does not support feature " + name;
return false;
}
if (!feature_obj.Execute()) {
*err_str = "featured: Tried but failed to enable feature " + name;
return false;
}
/* On success, return the feature name to featured for context. */
*err_str = name;
VLOG(1) << "featured: PlatformFeatureEnable: Feature " << name << " enabled";
return true;
}
void DbusFeaturedService::PlatformFeatureEnableWrap(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender sender) {
std::string out, err_str;
bool ret;
dbus::MessageReader reader(method_call);
std::string name;
if (!reader.PopString(&name)) {
out.append("error: missing string argument");
ret = false;
} else if (!PlatformFeatureEnable(name, &err_str)) {
// If failure, assign the output string as the error message
out.append("error: ");
out.append(err_str);
ret = false;
} else {
ret = true;
}
std::unique_ptr<dbus::Response> response =
dbus::Response::FromMethodCall(method_call);
dbus::MessageWriter writer(response.get());
writer.AppendBool(ret);
writer.AppendString(out);
std::move(sender).Run(std::move(response));
}
void DbusFeaturedService::PlatformFeatureList(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender sender) {
std::string out, csv, err_str;
bool ret;
// If failure, assign the output string as the error message
if (!GetFeatureList(&csv, &err_str)) {
out.append("error: ");
out.append(err_str);
ret = false;
} else {
VLOG(1) << "featured: PlatformFeatureList: " << csv;
out = csv;
ret = true;
}
std::unique_ptr<dbus::Response> response =
dbus::Response::FromMethodCall(method_call);
dbus::MessageWriter writer(response.get());
writer.AppendBool(ret);
writer.AppendString(out);
std::move(sender).Run(std::move(response));
}
bool DbusFeaturedService::Start(dbus::Bus* bus,
std::shared_ptr<DbusFeaturedService> ptr) {
if (!bus || !bus->Connect()) {
LOG(ERROR) << "Failed to connect to DBus";
return false;
}
dbus::ObjectPath path(featured::kFeaturedServicePath);
dbus::ExportedObject* object = bus->GetExportedObject(path);
if (!object) {
LOG(ERROR) << "Failed to get exported object at " << path.value();
return false;
}
if (!object->ExportMethodAndBlock(
featured::kFeaturedServiceName, featured::kPlatformFeatureList,
base::BindRepeating(&DbusFeaturedService::PlatformFeatureList,
ptr))) {
bus->UnregisterExportedObject(path);
LOG(ERROR) << "Failed to export method " << featured::kPlatformFeatureList;
return false;
}
if (!object->ExportMethodAndBlock(
featured::kFeaturedServiceName, featured::kPlatformFeatureEnable,
base::BindRepeating(&DbusFeaturedService::PlatformFeatureEnableWrap,
ptr))) {
bus->UnregisterExportedObject(path);
LOG(ERROR) << "Failed to export method "
<< featured::kPlatformFeatureEnable;
return false;
}
if (!bus->RequestOwnershipAndBlock(featured::kFeaturedServiceName,
dbus::Bus::REQUIRE_PRIMARY)) {
bus->UnregisterExportedObject(path);
LOG(ERROR) << "Failed to get ownership of "
<< featured::kFeaturedServiceName;
return false;
}
return true;
}
} // namespace featured