1 /*
2 * Copyright (C) 2021 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
17 #include "host/libs/config/config_flag.h"
18
19 #include <android-base/logging.h>
20 #include <android-base/strings.h>
21 #include <gflags/gflags.h>
22 #include <json/json.h>
23 #include <fstream>
24 #include <set>
25 #include <string>
26
27 #include "common/libs/utils/files.h"
28 #include "common/libs/utils/flag_parser.h"
29 #include "host/libs/config/cuttlefish_config.h"
30
31 // To support other files that use this from gflags.
32 DEFINE_string(system_image_dir, "", "");
33
34 using gflags::FlagSettingMode::SET_FLAGS_DEFAULT;
35
36 namespace cuttlefish {
37
38 namespace {
39
40 class SystemImageDirFlagImpl : public SystemImageDirFlag {
41 public:
INJECT(SystemImageDirFlagImpl ())42 INJECT(SystemImageDirFlagImpl()) {
43 auto help = "Location of the system partition images.";
44 flag_ = GflagsCompatFlag("system_image_dir", path_).Help(help);
45 }
Path()46 const std::string& Path() override { return path_; }
47
Name() const48 std::string Name() const override { return "SystemImageDirFlagImpl"; }
Dependencies() const49 std::unordered_set<FlagFeature*> Dependencies() const override { return {}; }
Process(std::vector<std::string> & args)50 bool Process(std::vector<std::string>& args) override {
51 path_ = DefaultGuestImagePath("");
52 if (!flag_.Parse(args)) {
53 return false;
54 }
55 // To support other files that use this from gflags.
56 FLAGS_system_image_dir = path_;
57 gflags::SetCommandLineOptionWithMode("system_image_dir", path_.c_str(),
58 SET_FLAGS_DEFAULT);
59 return true;
60 }
WriteGflagsCompatHelpXml(std::ostream &) const61 bool WriteGflagsCompatHelpXml(std::ostream&) const override {
62 // TODO(schuffelen): Write something here when this is removed from gflags
63 return true;
64 }
65
66 private:
67 std::string path_;
68 Flag flag_;
69 };
70
71 class ConfigReader : public FlagFeature {
72 public:
73 INJECT(ConfigReader()) = default;
74
HasConfig(const std::string & name) const75 bool HasConfig(const std::string& name) const {
76 return allowed_config_presets_.count(name) > 0;
77 }
AvailableConfigs() const78 const std::set<std::string>& AvailableConfigs() const {
79 return allowed_config_presets_;
80 }
ReadConfig(const std::string & name) const81 std::optional<Json::Value> ReadConfig(const std::string& name) const {
82 auto path =
83 DefaultHostArtifactsPath("etc/cvd_config/cvd_config_" + name + ".json");
84 Json::Value config;
85 Json::CharReaderBuilder builder;
86 std::ifstream ifs(path);
87 std::string errorMessage;
88 if (!Json::parseFromStream(builder, ifs, &config, &errorMessage)) {
89 LOG(ERROR) << "Could not read config file " << path << ": "
90 << errorMessage;
91 return {};
92 }
93 return config;
94 }
95
96 // FlagFeature
Name() const97 std::string Name() const override { return "ConfigReader"; }
Dependencies() const98 std::unordered_set<FlagFeature*> Dependencies() const override { return {}; }
Process(std::vector<std::string> &)99 bool Process(std::vector<std::string>&) override {
100 for (const std::string& file :
101 DirectoryContents(DefaultHostArtifactsPath("etc/cvd_config"))) {
102 std::string_view local_file(file);
103 if (android::base::ConsumePrefix(&local_file, "cvd_config_") &&
104 android::base::ConsumeSuffix(&local_file, ".json")) {
105 allowed_config_presets_.emplace(local_file);
106 }
107 }
108 return true;
109 }
WriteGflagsCompatHelpXml(std::ostream &) const110 bool WriteGflagsCompatHelpXml(std::ostream&) const override { return true; }
111
112 private:
113 std::set<std::string> allowed_config_presets_;
114 };
115
116 class ConfigFlagImpl : public ConfigFlag {
117 public:
INJECT(ConfigFlagImpl (ConfigReader & cr,SystemImageDirFlag & s))118 INJECT(ConfigFlagImpl(ConfigReader& cr, SystemImageDirFlag& s))
119 : config_reader_(cr), system_image_dir_flag_(s) {
120 is_default_ = true;
121 config_ = "phone"; // default value
122 auto help =
123 "Config preset name. Will automatically set flag fields using the "
124 "values from this file of presets. See "
125 "device/google/cuttlefish/shared/config/config_*.json for possible "
126 "values.";
127 auto getter = [this]() { return config_; };
128 auto setter = [this](const FlagMatch& m) { return ChooseConfig(m.value); };
129 flag_ = GflagsCompatFlag("config").Help(help).Getter(getter).Setter(setter);
130 }
131
Name() const132 std::string Name() const override { return "ConfigFlagImpl"; }
Dependencies() const133 std::unordered_set<FlagFeature*> Dependencies() const override {
134 return {
135 static_cast<FlagFeature*>(&config_reader_),
136 static_cast<FlagFeature*>(&system_image_dir_flag_),
137 };
138 }
Process(std::vector<std::string> & args)139 bool Process(std::vector<std::string>& args) override {
140 if (!flag_.Parse(args)) {
141 LOG(ERROR) << "Failed to parse `--config` flag";
142 return false;
143 }
144
145 if (auto info_cfg = FindAndroidInfoConfig(); is_default_ && info_cfg) {
146 config_ = *info_cfg;
147 }
148 LOG(INFO) << "Launching CVD using --config='" << config_ << "'.";
149 auto config_values = config_reader_.ReadConfig(config_);
150 if (!config_values) {
151 LOG(ERROR) << "Failed to read config for " << config_;
152 return false;
153 }
154 for (const std::string& flag : config_values->getMemberNames()) {
155 std::string value;
156 if (flag == "custom_actions") {
157 Json::StreamWriterBuilder factory;
158 value = Json::writeString(factory, (*config_values)[flag]);
159 } else {
160 value = (*config_values)[flag].asString();
161 }
162 args.insert(args.begin(), "--" + flag + "=" + value);
163 // To avoid the flag forwarder from thinking this song is different from a
164 // default. Should fail silently if the flag doesn't exist.
165 gflags::SetCommandLineOptionWithMode(flag.c_str(), value.c_str(),
166 SET_FLAGS_DEFAULT);
167 }
168 return true;
169 }
WriteGflagsCompatHelpXml(std::ostream & out) const170 bool WriteGflagsCompatHelpXml(std::ostream& out) const override {
171 return flag_.WriteGflagsCompatXml(out);
172 }
173
174 private:
ChooseConfig(const std::string & name)175 bool ChooseConfig(const std::string& name) {
176 if (!config_reader_.HasConfig(name)) {
177 LOG(ERROR) << "Invalid --config option '" << name << "'. Valid options: "
178 << android::base::Join(config_reader_.AvailableConfigs(), ",");
179 return false;
180 }
181 config_ = name;
182 is_default_ = false;
183 return true;
184 }
FindAndroidInfoConfig() const185 std::optional<std::string> FindAndroidInfoConfig() const {
186 auto info_path = system_image_dir_flag_.Path() + "/android-info.txt";
187 if (!FileExists(info_path)) {
188 return {};
189 }
190 std::ifstream ifs{info_path};
191 if (!ifs.is_open()) {
192 return {};
193 }
194 std::string android_info;
195 ifs >> android_info;
196 std::string_view local_android_info(android_info);
197 if (!android::base::ConsumePrefix(&local_android_info, "config=")) {
198 return {};
199 }
200 if (!config_reader_.HasConfig(std::string{local_android_info})) {
201 LOG(WARNING) << info_path << " contains invalid config preset: '"
202 << local_android_info << "'.";
203 return {};
204 }
205 return std::string{local_android_info};
206 }
207
208 ConfigReader& config_reader_;
209 SystemImageDirFlag& system_image_dir_flag_;
210 std::string config_;
211 bool is_default_;
212 Flag flag_;
213 };
214
215 class ConfigFlagPlaceholderImpl : public ConfigFlag {
216 public:
INJECT(ConfigFlagPlaceholderImpl ())217 INJECT(ConfigFlagPlaceholderImpl()) {}
218
Name() const219 std::string Name() const override { return "ConfigFlagPlaceholderImpl"; }
Dependencies() const220 std::unordered_set<FlagFeature*> Dependencies() const override { return {}; }
Process(std::vector<std::string> &)221 bool Process(std::vector<std::string>&) override { return true; }
WriteGflagsCompatHelpXml(std::ostream &) const222 bool WriteGflagsCompatHelpXml(std::ostream&) const override { return true; }
223 };
224
225 } // namespace
226
ConfigFlagComponent()227 fruit::Component<SystemImageDirFlag, ConfigFlag> ConfigFlagComponent() {
228 return fruit::createComponent()
229 .addMultibinding<FlagFeature, ConfigReader>()
230 .bind<ConfigFlag, ConfigFlagImpl>()
231 .addMultibinding<FlagFeature, ConfigFlag>()
232 .bind<SystemImageDirFlag, SystemImageDirFlagImpl>()
233 .addMultibinding<FlagFeature, SystemImageDirFlag>();
234 }
235
ConfigFlagPlaceholder()236 fruit::Component<ConfigFlag> ConfigFlagPlaceholder() {
237 return fruit::createComponent()
238 .addMultibinding<FlagFeature, ConfigFlag>()
239 .bind<ConfigFlag, ConfigFlagPlaceholderImpl>();
240 }
241
242 } // namespace cuttlefish
243