• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <sys/stat.h>
18 #include <sys/types.h>
19 #include <unistd.h>
20 
21 #include <fstream>
22 #include <iostream>
23 #include <optional>
24 #include <string>
25 #include <vector>
26 
27 #include <android-base/file.h>
28 #include <android-base/result.h>
29 #include <image_aggregator.h>
30 #include <json/json.h>
31 
32 #include "microdroid/metadata.h"
33 
34 using android::base::Dirname;
35 using android::base::ErrnoError;
36 using android::base::Error;
37 using android::base::Result;
38 using android::base::unique_fd;
39 using android::microdroid::ApexPayload;
40 using android::microdroid::ApkPayload;
41 using android::microdroid::Metadata;
42 using android::microdroid::WriteMetadata;
43 
44 using cuttlefish::AlignToPartitionSize;
45 using cuttlefish::CreateCompositeDisk;
46 using cuttlefish::kLinuxFilesystem;
47 using cuttlefish::MultipleImagePartition;
48 
GetFileSize(const std::string & path)49 Result<uint32_t> GetFileSize(const std::string& path) {
50     struct stat st;
51     if (lstat(path.c_str(), &st) == -1) {
52         return ErrnoError() << "Can't lstat " << path;
53     }
54     return static_cast<uint32_t>(st.st_size);
55 }
56 
RelativeTo(const std::string & path,const std::string & dirname)57 std::string RelativeTo(const std::string& path, const std::string& dirname) {
58     bool is_absolute = !path.empty() && path[0] == '/';
59     if (is_absolute || dirname == ".") {
60         return path;
61     } else {
62         return dirname + "/" + path;
63     }
64 }
65 
66 // Returns `append` is appended to the end of filename preserving the extension.
AppendFileName(const std::string & filename,const std::string & append)67 std::string AppendFileName(const std::string& filename, const std::string& append) {
68     size_t pos = filename.find_last_of('.');
69     if (pos == std::string::npos) {
70         return filename + append;
71     } else {
72         return filename.substr(0, pos) + append + filename.substr(pos);
73     }
74 }
75 
76 struct ApexConfig {
77     std::string name; // the apex name
78     std::string path; // the path to the apex file
79                       // absolute or relative to the config file
80 };
81 
82 struct ApkConfig {
83     std::string name;
84     std::string path;
85     std::string idsig_path;
86 };
87 
88 struct Config {
89     std::string dirname; // config file's direname to resolve relative paths in the config
90 
91     std::vector<ApexConfig> apexes;
92     std::optional<ApkConfig> apk;
93     // This is a path in the guest side
94     std::optional<std::string> payload_config_path;
95 };
96 
97 #define DO(expr) \
98     if (auto res = (expr); !res.ok()) return res.error()
99 
ParseJson(const Json::Value & value,std::string & s)100 Result<void> ParseJson(const Json::Value& value, std::string& s) {
101     if (!value.isString()) {
102         return Error() << "should be a string: " << value;
103     }
104     s = value.asString();
105     return {};
106 }
107 
108 template <typename T>
ParseJson(const Json::Value & value,std::optional<T> & s)109 Result<void> ParseJson(const Json::Value& value, std::optional<T>& s) {
110     if (value.isNull()) {
111         s.reset();
112         return {};
113     }
114     s.emplace();
115     return ParseJson(value, *s);
116 }
117 
118 template <typename T>
ParseJson(const Json::Value & values,std::vector<T> & parsed)119 Result<void> ParseJson(const Json::Value& values, std::vector<T>& parsed) {
120     for (const Json::Value& value : values) {
121         T t;
122         DO(ParseJson(value, t));
123         parsed.push_back(std::move(t));
124     }
125     return {};
126 }
127 
ParseJson(const Json::Value & value,ApexConfig & apex_config)128 Result<void> ParseJson(const Json::Value& value, ApexConfig& apex_config) {
129     DO(ParseJson(value["name"], apex_config.name));
130     DO(ParseJson(value["path"], apex_config.path));
131     return {};
132 }
133 
ParseJson(const Json::Value & value,ApkConfig & apk_config)134 Result<void> ParseJson(const Json::Value& value, ApkConfig& apk_config) {
135     DO(ParseJson(value["name"], apk_config.name));
136     DO(ParseJson(value["path"], apk_config.path));
137     DO(ParseJson(value["idsig_path"], apk_config.idsig_path));
138     return {};
139 }
140 
ParseJson(const Json::Value & value,Config & config)141 Result<void> ParseJson(const Json::Value& value, Config& config) {
142     DO(ParseJson(value["apexes"], config.apexes));
143     DO(ParseJson(value["apk"], config.apk));
144     DO(ParseJson(value["payload_config_path"], config.payload_config_path));
145     return {};
146 }
147 
LoadConfig(const std::string & config_file)148 Result<Config> LoadConfig(const std::string& config_file) {
149     std::ifstream in(config_file);
150     Json::CharReaderBuilder builder;
151     Json::Value root;
152     Json::String errs;
153     if (!parseFromStream(builder, in, &root, &errs)) {
154         return Error() << errs;
155     }
156 
157     Config config;
158     config.dirname = Dirname(config_file);
159     DO(ParseJson(root, config));
160     return config;
161 }
162 
163 #undef DO
164 
MakeMetadata(const Config & config,const std::string & filename)165 Result<void> MakeMetadata(const Config& config, const std::string& filename) {
166     Metadata metadata;
167     metadata.set_version(1);
168 
169     int apex_index = 0;
170     for (const auto& apex_config : config.apexes) {
171         auto* apex = metadata.add_apexes();
172         apex->set_name(apex_config.name);
173         apex->set_partition_name("microdroid-apex-" + std::to_string(apex_index++));
174         apex->set_is_factory(true);
175     }
176 
177     if (config.apk.has_value()) {
178         auto* apk = metadata.mutable_apk();
179         apk->set_name(config.apk->name);
180         apk->set_payload_partition_name("microdroid-apk");
181         apk->set_idsig_partition_name("microdroid-apk-idsig");
182     }
183 
184     if (config.payload_config_path.has_value()) {
185         *metadata.mutable_payload_config_path() = config.payload_config_path.value();
186     }
187 
188     std::ofstream out(filename);
189     return WriteMetadata(metadata, out);
190 }
191 
192 // fill zeros to align |file_path|'s size to BLOCK_SIZE(4096) boundary.
193 // return true when the filler is needed.
ZeroFiller(const std::string & file_path,const std::string & filler_path)194 Result<bool> ZeroFiller(const std::string& file_path, const std::string& filler_path) {
195     auto file_size = GetFileSize(file_path);
196     if (!file_size.ok()) {
197         return file_size.error();
198     }
199     auto disk_size = AlignToPartitionSize(*file_size);
200     if (disk_size <= *file_size) {
201         return false;
202     }
203     unique_fd fd(TEMP_FAILURE_RETRY(open(filler_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0600)));
204     if (fd.get() == -1) {
205         return ErrnoError() << "open(" << filler_path << ") failed.";
206     }
207     if (ftruncate(fd.get(), disk_size - *file_size) == -1) {
208         return ErrnoError() << "ftruncate(" << filler_path << ") failed.";
209     }
210     return true;
211 }
212 
MakePayload(const Config & config,const std::string & metadata_file,const std::string & output_file)213 Result<void> MakePayload(const Config& config, const std::string& metadata_file,
214                          const std::string& output_file) {
215     std::vector<MultipleImagePartition> partitions;
216 
217     int filler_count = 0;
218     auto add_partition = [&](auto partition_name, auto file_path) -> Result<void> {
219         std::vector<std::string> image_files{file_path};
220 
221         std::string filler_path =
222                 AppendFileName(output_file, "-filler-" + std::to_string(filler_count++));
223         if (auto ret = ZeroFiller(file_path, filler_path); !ret.ok()) {
224             return ret.error();
225         } else if (*ret) {
226             image_files.push_back(filler_path);
227         }
228         partitions.push_back(MultipleImagePartition{
229                 .label = partition_name,
230                 .image_file_paths = image_files,
231                 .type = kLinuxFilesystem,
232                 .read_only = true,
233         });
234         return {};
235     };
236 
237     // put metadata at the first partition
238     partitions.push_back(MultipleImagePartition{
239             .label = "payload-metadata",
240             .image_file_paths = {metadata_file},
241             .type = kLinuxFilesystem,
242             .read_only = true,
243     });
244     // put apexes at the subsequent partitions
245     for (size_t i = 0; i < config.apexes.size(); i++) {
246         const auto& apex_config = config.apexes[i];
247         std::string apex_path = RelativeTo(apex_config.path, config.dirname);
248         if (auto ret = add_partition("microdroid-apex-" + std::to_string(i), apex_path);
249             !ret.ok()) {
250             return ret.error();
251         }
252     }
253     // put apk and its idsig
254     if (config.apk.has_value()) {
255         std::string apk_path = RelativeTo(config.apk->path, config.dirname);
256         if (auto ret = add_partition("microdroid-apk", apk_path); !ret.ok()) {
257             return ret.error();
258         }
259         std::string idsig_path = RelativeTo(config.apk->idsig_path, config.dirname);
260         if (auto ret = add_partition("microdroid-apk-idsig", idsig_path); !ret.ok()) {
261             return ret.error();
262         }
263     }
264 
265     const std::string gpt_header = AppendFileName(output_file, "-header");
266     const std::string gpt_footer = AppendFileName(output_file, "-footer");
267     CreateCompositeDisk(partitions, gpt_header, gpt_footer, output_file);
268     return {};
269 }
270 
main(int argc,char ** argv)271 int main(int argc, char** argv) {
272     if (argc < 3 || argc > 4) {
273         std::cerr << "Usage: " << argv[0] << " [--metadata-only] <config> <output>\n";
274         return 1;
275     }
276     int arg_index = 1;
277     bool metadata_only = false;
278     if (strcmp(argv[arg_index], "--metadata-only") == 0) {
279         metadata_only = true;
280         arg_index++;
281     }
282 
283     auto config = LoadConfig(argv[arg_index++]);
284     if (!config.ok()) {
285         std::cerr << "bad config: " << config.error() << '\n';
286         return 1;
287     }
288 
289     const std::string output_file(argv[arg_index++]);
290     const std::string metadata_file =
291             metadata_only ? output_file : AppendFileName(output_file, "-metadata");
292 
293     if (const auto res = MakeMetadata(*config, metadata_file); !res.ok()) {
294         std::cerr << res.error() << '\n';
295         return 1;
296     }
297     if (metadata_only) {
298         return 0;
299     }
300     if (const auto res = MakePayload(*config, metadata_file, output_file); !res.ok()) {
301         std::cerr << res.error() << '\n';
302         return 1;
303     }
304 
305     return 0;
306 }