• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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