1 /*
2  * Copyright (C) 2019 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/fetcher_config.h"
18 
19 #include <fstream>
20 #include <map>
21 #include <string>
22 #include <vector>
23 
24 #include "android-base/logging.h"
25 #include "android-base/strings.h"
26 #include "gflags/gflags.h"
27 #include "json/json.h"
28 
29 #include "common/libs/utils/files.h"
30 #include "common/libs/utils/result.h"
31 
32 namespace cuttlefish {
33 
34 namespace {
35 
36 const char* kFlags = "flags";
37 const char* kCvdFiles = "cvd_files";
38 const char* kCvdFileSource = "source";
39 const char* kCvdFileBuildId = "build_id";
40 const char* kCvdFileBuildTarget = "build_target";
41 
SourceStringToEnum(std::string source)42 FileSource SourceStringToEnum(std::string source) {
43   for (auto& c : source) {
44     c = std::tolower(c);
45   }
46   if (source == "default_build") {
47     return FileSource::DEFAULT_BUILD;
48   } else if (source == "system_build") {
49     return FileSource::SYSTEM_BUILD;
50   } else if (source == "kernel_build") {
51     return FileSource::KERNEL_BUILD;
52   } else if (source == "local_file") {
53     return FileSource::LOCAL_FILE;
54   } else if (source == "generated") {
55     return FileSource::GENERATED;
56   } else if (source == "bootloader_build") {
57     return FileSource::BOOTLOADER_BUILD;
58   } else if (source == "android_efi_loader_build") {
59     return FileSource::ANDROID_EFI_LOADER_BUILD;
60   } else if (source == "boot_build") {
61     return FileSource::BOOT_BUILD;
62   } else if (source == "host_package_build") {
63     return FileSource::HOST_PACKAGE_BUILD;
64   } else {
65     return FileSource::UNKNOWN_PURPOSE;
66   }
67 }
68 
SourceEnumToString(const FileSource & source)69 std::string SourceEnumToString(const FileSource& source) {
70   if (source == FileSource::DEFAULT_BUILD) {
71     return "default_build";
72   } else if (source == FileSource::SYSTEM_BUILD) {
73     return "system_build";
74   } else if (source == FileSource::KERNEL_BUILD) {
75     return "kernel_build";
76   } else if (source == FileSource::LOCAL_FILE) {
77     return "local_file";
78   } else if (source == FileSource::GENERATED) {
79     return "generated";
80   } else if (source == FileSource::BOOTLOADER_BUILD) {
81     return "bootloader_build";
82   } else if (source == FileSource::ANDROID_EFI_LOADER_BUILD) {
83     return "android_efi_loader_build";
84   } else if (source == FileSource::BOOT_BUILD) {
85     return "boot_build";
86   } else if (source == FileSource::HOST_PACKAGE_BUILD) {
87     return "host_package_build";
88   } else {
89     return "unknown";
90   }
91 }
92 
93 } // namespace
94 
CvdFile()95 CvdFile::CvdFile() {
96 }
97 
CvdFile(const FileSource & source,const std::string & build_id,const std::string & build_target,const std::string & file_path)98 CvdFile::CvdFile(const FileSource& source, const std::string& build_id,
99                  const std::string& build_target, const std::string& file_path)
100     : source(source), build_id(build_id), build_target(build_target), file_path(file_path) {
101 }
102 
operator <<(std::ostream & os,const CvdFile & cvd_file)103 std::ostream& operator<<(std::ostream& os, const CvdFile& cvd_file) {
104   os << "CvdFile(";
105   os << "source = " << SourceEnumToString(cvd_file.source) << ", ";
106   os << "build_id = " << cvd_file.build_id << ", ";
107   os << "build_target = " << cvd_file.build_target << ", ";
108   os << "file_path = " << cvd_file.file_path << ")";
109   return os;
110 }
111 
FetcherConfig()112 FetcherConfig::FetcherConfig() : dictionary_(new Json::Value()) {
113 }
114 
115 FetcherConfig::FetcherConfig(FetcherConfig&&) = default;
116 
~FetcherConfig()117 FetcherConfig::~FetcherConfig() {
118 }
119 
SaveToFile(const std::string & file) const120 bool FetcherConfig::SaveToFile(const std::string& file) const {
121   std::ofstream ofs(file);
122   if (!ofs.is_open()) {
123     LOG(ERROR) << "Unable to write to file " << file;
124     return false;
125   }
126   ofs << *dictionary_;
127   return !ofs.fail();
128 }
129 
LoadFromFile(const std::string & file)130 bool FetcherConfig::LoadFromFile(const std::string& file) {
131   auto real_file_path = AbsolutePath(file);
132   if (real_file_path.empty()) {
133     LOG(ERROR) << "Could not get real path for file " << file;
134     return false;
135   }
136   Json::CharReaderBuilder builder;
137   std::ifstream ifs(real_file_path);
138   std::string errorMessage;
139   if (!Json::parseFromStream(builder, ifs, dictionary_.get(), &errorMessage)) {
140     LOG(ERROR) << "Could not read config file " << file << ": " << errorMessage;
141     return false;
142   }
143 
144   auto base_dir = cpp_dirname(file);
145   if (base_dir != "." && dictionary_->isMember(kCvdFiles)) {
146     LOG(INFO) << "Adjusting cvd_file paths to directory: " << base_dir;
147     for (const auto& member_name : (*dictionary_)[kCvdFiles].getMemberNames()) {
148       (*dictionary_)[kCvdFiles][base_dir + "/" + member_name] =
149           (*dictionary_)[kCvdFiles][member_name];
150       (*dictionary_)[kCvdFiles].removeMember(member_name);
151     }
152   }
153 
154   return true;
155 }
156 
RecordFlags()157 void FetcherConfig::RecordFlags() {
158   std::vector<gflags::CommandLineFlagInfo> all_flags;
159   GetAllFlags(&all_flags);
160   Json::Value flags_json(Json::arrayValue);
161   for (const auto& flag : all_flags) {
162     Json::Value flag_json;
163     flag_json["name"] = flag.name;
164     flag_json["type"] = flag.type;
165     flag_json["description"] = flag.description;
166     flag_json["current_value"] = flag.current_value;
167     flag_json["default_value"] = flag.default_value;
168     flag_json["filename"] = flag.filename;
169     flag_json["has_validator_fn"] = flag.has_validator_fn;
170     flag_json["is_default"] = flag.is_default;
171     flags_json.append(flag_json);
172   }
173   (*dictionary_)[kFlags] = flags_json;
174 }
175 
176 namespace {
177 
JsonToCvdFile(const std::string & file_path,const Json::Value & json)178 CvdFile JsonToCvdFile(const std::string& file_path, const Json::Value& json) {
179   CvdFile cvd_file;
180   cvd_file.file_path = file_path;
181   if (json.isMember(kCvdFileSource)) {
182     cvd_file.source = SourceStringToEnum(json[kCvdFileSource].asString());
183   } else {
184     cvd_file.source = FileSource::UNKNOWN_PURPOSE;
185   }
186   if (json.isMember(kCvdFileBuildId)) {
187     cvd_file.build_id = json[kCvdFileBuildId].asString();
188   }
189   if (json.isMember(kCvdFileBuildTarget)) {
190     cvd_file.build_target = json[kCvdFileBuildTarget].asString();
191   }
192   return cvd_file;
193 }
194 
CvdFileToJson(const CvdFile & cvd_file)195 Json::Value CvdFileToJson(const CvdFile& cvd_file) {
196   Json::Value json;
197   json[kCvdFileSource] = SourceEnumToString(cvd_file.source);
198   json[kCvdFileBuildId] = cvd_file.build_id;
199   json[kCvdFileBuildTarget] = cvd_file.build_target;
200   return json;
201 }
202 
203 } // namespace
204 
add_cvd_file(const CvdFile & file,bool override_entry)205 bool FetcherConfig::add_cvd_file(const CvdFile& file, bool override_entry) {
206   if (!dictionary_->isMember(kCvdFiles)) {
207     Json::Value files_json(Json::objectValue);
208     (*dictionary_)[kCvdFiles] = files_json;
209   }
210   if ((*dictionary_)[kCvdFiles].isMember(file.file_path) && !override_entry) {
211     return false;
212   }
213   (*dictionary_)[kCvdFiles][file.file_path] = CvdFileToJson(file);
214   return true;
215 }
216 
get_cvd_files() const217 std::map<std::string, CvdFile> FetcherConfig::get_cvd_files() const {
218   if (!dictionary_->isMember(kCvdFiles)) {
219     return {};
220   }
221   std::map<std::string, CvdFile> files;
222   const auto& json_files = (*dictionary_)[kCvdFiles];
223   for (auto it = json_files.begin(); it != json_files.end(); it++) {
224     files[it.key().asString()] = JsonToCvdFile(it.key().asString(), *it);
225   }
226   return files;
227 }
228 
FindCvdFileWithSuffix(const std::string & suffix) const229 std::string FetcherConfig::FindCvdFileWithSuffix(const std::string& suffix) const {
230   if (!dictionary_->isMember(kCvdFiles)) {
231     return {};
232   }
233   const auto& json_files = (*dictionary_)[kCvdFiles];
234   for (auto it = json_files.begin(); it != json_files.end(); it++) {
235     const auto& file = it.key().asString();
236     if (android::base::EndsWith(file, suffix)) {
237       return file;
238     }
239   }
240   LOG(DEBUG) << "Could not find file ending in " << suffix;
241   return "";
242 }
243 
AddFilesToConfig(FileSource purpose,const std::string & build_id,const std::string & build_target,const std::vector<std::string> & paths,const std::string & directory_prefix,bool override_entry)244 Result<void> FetcherConfig::AddFilesToConfig(
245     FileSource purpose, const std::string& build_id,
246     const std::string& build_target, const std::vector<std::string>& paths,
247     const std::string& directory_prefix, bool override_entry) {
248   for (const std::string& path : paths) {
249     std::string_view local_path(path);
250     if (!android::base::ConsumePrefix(&local_path, directory_prefix)) {
251       LOG(ERROR) << "Failed to remove prefix " << directory_prefix << " from "
252                  << local_path;
253     }
254     while (android::base::StartsWith(local_path, "/")) {
255       android::base::ConsumePrefix(&local_path, "/");
256     }
257     // TODO(schuffelen): Do better for local builds here.
258     CvdFile file(purpose, build_id, build_target, std::string(local_path));
259     CF_EXPECT(add_cvd_file(file, override_entry),
260               "Duplicate file \""
261                   << file << "\", Existing file: \"" << get_cvd_files()[path]
262                   << "\". Failed to add path \"" << path << "\"");
263   }
264   return {};
265 }
266 
267 } // namespace cuttlefish
268