1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "host/commands/cvd/server_command/load_configs.h"
17
18 #include <chrono>
19 #include <mutex>
20 #include <sstream>
21 #include <string>
22
23 #include <fruit/fruit.h>
24 #include <android-base/parseint.h>
25
26 #include "common/libs/fs/shared_buf.h"
27 #include "common/libs/utils/files.h"
28 #include "common/libs/utils/flag_parser.h"
29 #include "common/libs/utils/result.h"
30 #include "host/commands/cvd/command_sequence.h"
31 #include "host/commands/cvd/parser/cf_configs_common.h"
32 #include "host/commands/cvd/parser/load_configs_parser.h"
33 #include "host/commands/cvd/selector/selector_constants.h"
34 #include "host/commands/cvd/server.h"
35 #include "host/commands/cvd/server_client.h"
36 #include "host/commands/cvd/server_command/utils.h"
37 #include "host/commands/cvd/types.h"
38
39 namespace cuttlefish {
40
41 namespace {
42
43 using DemoCommandSequence = std::vector<RequestWithStdio>;
44
45 } // namespace
46
47 class LoadConfigsCommand : public CvdServerHandler {
48 public:
INJECT(LoadConfigsCommand (CommandSequenceExecutor & executor))49 INJECT(LoadConfigsCommand(CommandSequenceExecutor& executor))
50 : executor_(executor) {}
51 ~LoadConfigsCommand() = default;
52
CanHandle(const RequestWithStdio & request) const53 Result<bool> CanHandle(const RequestWithStdio& request) const override {
54 auto invocation = ParseInvocation(request.Message());
55 return invocation.command == kLoadSubCmd;
56 }
57
Handle(const RequestWithStdio & request)58 Result<cvd::Response> Handle(const RequestWithStdio& request) override {
59 std::unique_lock interrupt_lock(interrupt_mutex_);
60 CF_EXPECT(!interrupted_, "Interrupted");
61 CF_EXPECT(CF_EXPECT(CanHandle(request)));
62
63 auto commands = CF_EXPECT(CreateCommandSequence(request));
64 interrupt_lock.unlock();
65 CF_EXPECT(executor_.Execute(commands, request.Err()));
66
67 cvd::Response response;
68 response.mutable_command_response();
69 return response;
70 }
Interrupt()71 Result<void> Interrupt() override {
72 std::scoped_lock interrupt_lock(interrupt_mutex_);
73 interrupted_ = true;
74 CF_EXPECT(executor_.Interrupt());
75 return {};
76 }
77
CmdList() const78 cvd_common::Args CmdList() const override { return {kLoadSubCmd}; }
79
80 // TODO: expand this enum in the future to support more types ( double , float
81 // , etc) if neeeded
82 enum ArgValueType { UINTEGER, BOOLEAN, TEXT };
83
IsUnsignedInteger(const std::string & str)84 bool IsUnsignedInteger(const std::string& str) {
85 return !str.empty() && std::all_of(str.begin(), str.end(),
86 [](char c) { return std::isdigit(c); });
87 }
88
GetArgValueType(const std::string & str)89 ArgValueType GetArgValueType(const std::string& str) {
90 if (IsUnsignedInteger(str)) {
91 return UINTEGER;
92 }
93
94 if (str == "true" || str == "false") {
95 return BOOLEAN;
96 }
97
98 // Otherwise, treat the string as text
99 return TEXT;
100 }
101
ConvertArgToJson(const std::string & key,const std::string & leafValue)102 Json::Value ConvertArgToJson(const std::string& key,
103 const std::string& leafValue) {
104 std::stack<std::string> levels;
105 std::stringstream ks(key);
106 std::string token;
107 while (std::getline(ks, token, '.')) {
108 levels.push(token);
109 }
110
111 // assign the leaf value based on the type of input value
112 Json::Value leaf;
113 if (GetArgValueType(leafValue) == UINTEGER) {
114 std::uint32_t leaf_val;
115 if (!android::base::ParseUint(leafValue ,&leaf_val)){
116 LOG(ERROR) << "Failed to parse unsigned integer " << leafValue;
117 return Json::Value::null;
118 };
119 leaf = leaf_val;
120 } else if (GetArgValueType(leafValue) == BOOLEAN) {
121 leaf = (leafValue == "true");
122 } else {
123 leaf = leafValue;
124 }
125
126 while (!levels.empty()) {
127 Json::Value curr;
128 std::string index = levels.top();
129
130 if (GetArgValueType(index) == UINTEGER) {
131 std::uint32_t index_val;
132 if (!android::base::ParseUint(index, &index_val)){
133 LOG(ERROR) << "Failed to parse unsigned integer " << index;
134 return Json::Value::null;
135 }
136 curr[index_val] = leaf;
137 } else {
138 curr[index] = leaf;
139 }
140
141 leaf = curr;
142 levels.pop();
143 }
144
145 return leaf;
146 }
147
ParseArgsToJson(const std::vector<std::string> & strings)148 Json::Value ParseArgsToJson(const std::vector<std::string>& strings) {
149 Json::Value jsonValue;
150 for (const auto& str : strings) {
151 std::string key;
152 std::string value;
153 size_t equals_pos = str.find('=');
154 if (equals_pos != std::string::npos) {
155 key = str.substr(0, equals_pos);
156 value = str.substr(equals_pos + 1);
157 } else {
158 key = str;
159 value.clear();
160 LOG(WARNING) << "No value provided for key " << key;
161 return Json::Value::null;
162 }
163 MergeTwoJsonObjs(jsonValue, ConvertArgToJson(key, value));
164 }
165
166 return jsonValue;
167 }
168
HasValidDotSeparatedPrefix(const std::string & str)169 bool HasValidDotSeparatedPrefix(const std::string& str) {
170 auto equalsPos = str.find('=');
171 if (equalsPos == std::string::npos) {
172 return false;
173 }
174 std::string prefix = str.substr(0, equalsPos);
175 // return false if prefix is empty, has no dots, start with dots, end with dot
176 // or has cosequence of dots
177 if (prefix.empty() || prefix.find('.') == std::string::npos ||
178 prefix.find('.') == 0 || prefix.find("..") != std::string::npos ||
179 prefix.back() == '.') {
180 return false;
181 }
182 return true;
183 }
184
hasEqualsWithValidDotSeparatedPrefix(const std::string & str)185 bool hasEqualsWithValidDotSeparatedPrefix(const std::string& str) {
186 auto equalsPos = str.find('=');
187 return equalsPos != std::string::npos && equalsPos < str.length() - 1 &&
188 HasValidDotSeparatedPrefix(str);
189 }
190
ValidateArgsFormat(const std::vector<std::string> & strings)191 bool ValidateArgsFormat(const std::vector<std::string>& strings) {
192 for (const auto& str : strings) {
193 if (!hasEqualsWithValidDotSeparatedPrefix(str)) {
194 LOG(ERROR) << "Invalid argument format. " << str
195 << " Please use arg=value";
196 return false;
197 }
198 }
199 return true;
200 }
201
CreateCommandSequence(const RequestWithStdio & request)202 Result<DemoCommandSequence> CreateCommandSequence(
203 const RequestWithStdio& request) {
204 bool help = false;
205
206 std::vector<Flag> flags;
207 flags.emplace_back(GflagsCompatFlag("help", help));
208 std::string config_path;
209 flags.emplace_back(GflagsCompatFlag("config_path", config_path));
210
211 auto args = ParseInvocation(request.Message()).arguments;
212 CF_EXPECT(ParseFlags(flags, args));
213
214 Json::Value json_configs;
215 if (help) {
216 std::stringstream help_msg_stream;
217 help_msg_stream << "Usage: cvd " << kLoadSubCmd << std::endl;
218 const auto help_msg = help_msg_stream.str();
219 CF_EXPECT(WriteAll(request.Out(), help_msg) == help_msg.size());
220 return {};
221 } else {
222 json_configs =
223 CF_EXPECT(ParseJsonFile(config_path), "parsing input file failed");
224
225 if (args.size() > 0) {
226 for (auto& single_arg : args) {
227 LOG(INFO) << "Filtered args " << single_arg;
228 }
229 // Validate all arguments follow specific pattern
230 if (!ValidateArgsFormat(args)) {
231 return {};
232 }
233 // Convert all arguments to json tree
234 auto args_tree = ParseArgsToJson(args);
235 MergeTwoJsonObjs(json_configs, args_tree);
236 }
237 }
238
239 auto cvd_flags =
240 CF_EXPECT(ParseCvdConfigs(json_configs), "parsing json configs failed");
241
242 std::vector<cvd::Request> req_protos;
243
244 auto& launch_cmd = *req_protos.emplace_back().mutable_command_request();
245 launch_cmd.set_working_directory(
246 request.Message().command_request().working_directory());
247 *launch_cmd.mutable_env() = request.Message().command_request().env();
248
249 /* cvd load will always create instances in deamon mode (to be independent
250 of terminal) and will enable reporting automatically (to run automatically
251 without question during launch)
252 */
253 launch_cmd.add_args("cvd");
254 launch_cmd.add_args("start");
255 launch_cmd.add_args("--daemon");
256 for (auto& parsed_flag : cvd_flags.launch_cvd_flags) {
257 launch_cmd.add_args(parsed_flag);
258 }
259
260 launch_cmd.mutable_selector_opts()->add_args(
261 std::string("--") + selector::SelectorFlags::kDisableDefaultGroup);
262
263 /*Verbose is disabled by default*/
264 auto dev_null = SharedFD::Open("/dev/null", O_RDWR);
265 CF_EXPECT(dev_null->IsOpen(), dev_null->StrError());
266 std::vector<SharedFD> fds = {dev_null, dev_null, dev_null};
267 DemoCommandSequence ret;
268
269 for (auto& request_proto : req_protos) {
270 ret.emplace_back(RequestWithStdio(request.Client(), request_proto, fds,
271 request.Credentials()));
272 }
273
274 return ret;
275 }
276
277 private:
278 static constexpr char kLoadSubCmd[] = "load";
279
280 CommandSequenceExecutor& executor_;
281
282 std::mutex interrupt_mutex_;
283 bool interrupted_ = false;
284 };
285
286 fruit::Component<fruit::Required<CommandSequenceExecutor>>
LoadConfigsComponent()287 LoadConfigsComponent() {
288 return fruit::createComponent()
289 .addMultibinding<CvdServerHandler, LoadConfigsCommand>();
290 }
291
292 } // namespace cuttlefish
293