blob: 8b36d4c5c1047df8fb94aa536d02bbebe294d814 [file] [log] [blame]
Joel Fernandes8632adc2021-02-16 19:42:58 -05001// Copyright 2021 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <string>
6
7#include "base/files/file.h"
8#include "base/files/file_enumerator.h"
9#include "base/files/file_path.h"
10#include "base/files/file_util.h"
11#include "base/files/scoped_file.h"
12#include "base/files/scoped_temp_dir.h"
13#include <base/json/json_reader.h>
14#include <base/logging.h>
15#include <base/strings/string_number_conversions.h>
16#include <base/strings/stringprintf.h>
17#include <brillo/errors/error_codes.h>
18#include <build/build_config.h>
19#include <build/buildflag.h>
20#include <unordered_map>
21#include <utility>
22#include "debugd/src/error_utils.h"
23#include "debugd/src/kernel_feature_tool.h"
24
25namespace debugd {
26
27namespace {
28constexpr char kErrorPath[] = "org.chromium.debugd.KernelFeatureError";
Joel Fernandesbb6a8d22021-05-19 11:51:50 -040029constexpr char kKernelFeaturesPath[] = "/etc/init/kernel-features.json";
Joel Fernandes8632adc2021-02-16 19:42:58 -050030
31// JSON Helper to retrieve a string value given a string key
32bool GetStringFromKey(base::Value* obj,
33 const std::string& key,
34 std::string* value) {
35 const std::string* val = obj->FindStringKey(key);
36 if (!val || val->empty()) {
37 return false;
38 }
39
40 *value = *val;
41 return true;
42}
43
44} // namespace
45
46WriteFileCommand::WriteFileCommand(const std::string& file_name,
47 const std::string& value)
48 : FeatureCommand("WriteFile") {
49 file_name_ = file_name;
50 value_ = value;
51}
52
53bool WriteFileCommand::Execute() {
54 if (!base::WriteFile(base::FilePath(file_name_), value_)) {
55 PLOG(ERROR) << "Unable to write to " << file_name_;
56 return false;
57 }
58 return true;
59}
60
61FileExistsCommand::FileExistsCommand(const std::string& file_name)
62 : FeatureCommand("FileExists") {
63 file_name_ = file_name;
64}
65
66bool FileExistsCommand::Execute() {
67 return base::PathExists(base::FilePath(file_name_));
68}
69
70void KernelFeature::AddCmd(std::unique_ptr<FeatureCommand> cmd) {
71 exec_cmds_.push_back(std::move(cmd));
72}
73
74void KernelFeature::AddQueryCmd(std::unique_ptr<FeatureCommand> cmd) {
75 support_check_cmds_.push_back(std::move(cmd));
76}
77
78bool KernelFeature::Execute() const {
79 for (auto& cmd : exec_cmds_) {
80 if (!cmd->Execute()) {
81 LOG(ERROR) << "Failed to execute command: " << cmd->name();
82 return false;
83 }
84 }
85 return true;
86}
87
88bool KernelFeature::IsSupported() const {
89 for (auto& cmd : support_check_cmds_) {
90 if (!cmd->Execute()) {
91 return false;
92 }
93 }
94 return true;
95}
96
97bool JsonFeatureParser::ParseFile(const base::FilePath& path,
98 std::string* err_str) {
99 std::string input;
100
101 if (features_parsed_)
102 return true;
103
104 if (!ReadFileToString(path, &input)) {
105 *err_str = "debugd: Failed to read kernel-features config!";
106 return false;
107 }
108
109 VLOG(1) << "JSON feature parsed result: " << input;
110
111 base::JSONReader::ValueWithError root =
112 base::JSONReader::ReadAndReturnValueWithError(input);
113 if (!root.value) {
114 *err_str = "debugd: Failed to parse features conf file!";
115 return false;
116 }
117
118 if (!root.value->is_list() || root.value->GetList().size() == 0) {
119 *err_str = "debugd: features list should be non-zero size!";
120 return false;
121 }
122
123 for (unsigned i = 0; i < root.value->GetList().size(); i++) {
124 base::Value item = std::move(root.value->GetList()[i]);
125 base::Value* feature_json_obj = &item;
126
127 if (!feature_json_obj->is_dict()) {
128 *err_str = "debugd: features conf not list of dicts!";
129 return false;
130 }
131
132 KernelFeature feature_obj;
133 if (!MakeFeatureObject(feature_json_obj, err_str, feature_obj)) {
134 return false;
135 }
136
137 auto got = feature_map_.find(feature_obj.name());
138 if (got != feature_map_.end()) {
139 *err_str =
140 "debugd: Duplicate feature name found! : " + feature_obj.name();
141 return false;
142 }
143
144 feature_map_.insert(
145 std::make_pair(feature_obj.name(), std::move(feature_obj)));
146 }
147
148 features_parsed_ = true;
149 return true;
150}
151
152// KernelFeature implementation (collect and execute commands).
153bool JsonFeatureParser::MakeFeatureObject(base::Value* feature_obj,
154 std::string* err_str,
155 KernelFeature& kern_feat) {
156 std::string feat_name;
157 if (!GetStringFromKey(feature_obj, "name", &feat_name)) {
158 kern_feat.SetName(feat_name);
159 *err_str = "debugd: features conf contains empty names";
160 return false;
161 }
162
163 // Commands for querying if device is supported
164 base::Value* support_cmd_list_obj =
165 feature_obj->FindListKey("support_check_commands");
166
167 if (!support_cmd_list_obj) {
168 // Feature is assumed to be always supported, such as a kernel parameter
169 // that is on all device kernels.
170 kern_feat.AddQueryCmd(std::make_unique<AlwaysSupportedCommand>());
171 } else {
172 // A support check command was provided, add it to the feature object.
173 if (!support_cmd_list_obj->is_list() ||
174 support_cmd_list_obj->GetList().size() == 0) {
175 *err_str = "debugd: Invalid format for support_check_commands commands";
176 return false;
177 }
178
179 for (unsigned i = 0; i < support_cmd_list_obj->GetList().size(); i++) {
180 base::Value item = std::move(support_cmd_list_obj->GetList()[i]);
181 base::Value* cmd_obj = &item;
182 std::string cmd_name;
183
184 if (!GetStringFromKey(cmd_obj, "name", &cmd_name)) {
185 *err_str = "debugd: Invalid/Empty command name in features config.";
186 return false;
187 }
188
189 if (cmd_name == "FileExists") {
190 std::string file_name;
191
192 VLOG(1) << "debugd: command is FileExists";
193 if (!GetStringFromKey(cmd_obj, "file", &file_name)) {
194 *err_str = "debugd: JSON contains invalid command name";
195 return false;
196 }
197
198 kern_feat.AddQueryCmd(std::make_unique<FileExistsCommand>(file_name));
199 }
200 }
201 }
202
203 // Commands to execute to enable feature
204 base::Value* cmd_list_obj = feature_obj->FindListKey("commands");
205 if (!cmd_list_obj || !cmd_list_obj->is_list() ||
206 cmd_list_obj->GetList().size() == 0) {
207 *err_str = "debugd: Failed to get commands list in feature.";
208 return false;
209 }
210
211 for (unsigned i = 0; i < cmd_list_obj->GetList().size(); i++) {
212 base::Value item = std::move(cmd_list_obj->GetList()[i]);
213 base::Value* cmd_obj = &item;
214 std::string cmd_name;
215
216 if (!GetStringFromKey(cmd_obj, "name", &cmd_name)) {
217 *err_str = "debugd: Invalid command in features config.";
218 return false;
219 }
220
221 if (cmd_name == "WriteFile") {
222 std::string file_name, value;
223
224 VLOG(1) << "debugd: command is WriteFile";
225 if (!GetStringFromKey(cmd_obj, "file", &file_name)) {
226 *err_str = "debugd: JSON contains invalid command name!";
227 return false;
228 }
229
230 if (!GetStringFromKey(cmd_obj, "value", &value)) {
231 *err_str = "debugd: JSON contains invalid command value!";
232 return false;
233 }
234 kern_feat.AddCmd(std::make_unique<WriteFileCommand>(file_name, value));
235 }
236 }
237
238 return true;
239}
240
241KernelFeatureTool::KernelFeatureTool()
242 : parser_(std::make_unique<JsonFeatureParser>()) {}
243
244KernelFeatureTool::~KernelFeatureTool() = default;
245
246bool KernelFeatureTool::ParseFeatureList(std::string* err_str) {
247 DCHECK(err_str);
248
249 if (!parser_->ParseFile(base::FilePath(kKernelFeaturesPath), err_str)) {
250 return false;
251 }
252
253 return true;
254}
255
256bool KernelFeatureTool::GetFeatureList(std::string* csv_list,
257 std::string* err_str) {
258 DCHECK(csv_list);
259 DCHECK(err_str);
260
261 csv_list->clear();
262
263 if (!ParseFeatureList(err_str)) {
264 return false;
265 }
266
267 bool first = true;
268 for (auto& it : *(parser_->GetFeatureMap())) {
269 if (!first)
270 csv_list->append(",");
271 else
272 first = false;
273
274 csv_list->append(it.first);
275 }
276
277 return true;
278}
279
280bool KernelFeatureTool::KernelFeatureEnable(brillo::ErrorPtr* error,
281 const std::string& name,
282 bool* result,
283 std::string* err_str) {
284 DCHECK(error);
285 DCHECK(result);
286 DCHECK(err_str);
287
288 if (!ParseFeatureList(err_str)) {
289 *result = false;
290 DEBUGD_ADD_ERROR(error, kErrorPath, *err_str);
291 return false;
292 }
293
294 auto feature = parser_->GetFeatureMap()->find(name);
295 if (feature == parser_->GetFeatureMap()->end()) {
296 *err_str = "debugd: Feature not found in features config!";
297 *result = false;
298 DEBUGD_ADD_ERROR(error, kErrorPath, *err_str);
299 return false;
300 }
301
302 auto& feature_obj = feature->second;
303 if (!feature_obj.IsSupported()) {
304 *err_str = "debugd: device does not support feature " + name;
305 *result = false;
306 DEBUGD_ADD_ERROR(error, kErrorPath, *err_str);
307 return false;
308 }
309
310 if (!feature_obj.Execute()) {
311 *err_str = "debugd: Tried but failed to enable feature " + name;
312 *result = false;
313 DEBUGD_ADD_ERROR(error, kErrorPath, *err_str);
314 return false;
315 }
316
317 /* On success, return the feature name to debugd for context. */
318 *err_str = name;
319 *result = true;
320 LOG(INFO) << "debugd: KernelFeatureEnable: Feature " << name << " enabled";
321 return true;
322}
323
324bool KernelFeatureTool::KernelFeatureList(brillo::ErrorPtr* error,
325 bool* result,
326 std::string* out) {
327 DCHECK(error);
328 DCHECK(result);
329 DCHECK(out);
330 out->clear();
331
332 std::string csv, err_str;
333 *result = GetFeatureList(&csv, &err_str);
334
335 // If failure, assign the output string as the error message
336 if (!*result) {
337 out->append("error:");
338 out->append(err_str);
339 DEBUGD_ADD_ERROR(error, kErrorPath, err_str);
340 } else {
341 LOG(INFO) << "debugd: KernelFeatureList: " << csv;
342 out->append("csv:");
343 out->append(csv);
344 }
345 return *result;
346}
347} // namespace debugd