1 //
2 // Copyright (C) 2015 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 "update_engine/payload_generator/payload_generation_config.h"
18
19 #include <algorithm>
20 #include <charconv>
21 #include <map>
22 #include <utility>
23
24 #include <base/logging.h>
25 #include <base/strings/string_number_conversions.h>
26 #include <brillo/strings/string_utils.h>
27 #include <libsnapshot/cow_format.h>
28
29 #include "bsdiff/constants.h"
30 #include "payload_consumer/payload_constants.h"
31 #include "update_engine/common/utils.h"
32 #include "update_engine/payload_generator/boot_img_filesystem.h"
33 #include "update_engine/payload_generator/delta_diff_generator.h"
34 #include "update_engine/payload_generator/delta_diff_utils.h"
35 #include "update_engine/payload_generator/erofs_filesystem.h"
36 #include "update_engine/payload_generator/ext2_filesystem.h"
37 #include "update_engine/payload_generator/mapfile_filesystem.h"
38 #include "update_engine/payload_generator/raw_filesystem.h"
39 #include "update_engine/payload_generator/squashfs_filesystem.h"
40
41 using std::string;
42
43 namespace chromeos_update_engine {
44
IsEmpty() const45 bool PostInstallConfig::IsEmpty() const {
46 return !run && path.empty() && filesystem_type.empty() && !optional;
47 }
48
IsEmpty() const49 bool VerityConfig::IsEmpty() const {
50 return hash_tree_data_extent.num_blocks() == 0 &&
51 hash_tree_extent.num_blocks() == 0 && hash_tree_algorithm.empty() &&
52 hash_tree_salt.empty() && fec_data_extent.num_blocks() == 0 &&
53 fec_extent.num_blocks() == 0 && fec_roots == 0;
54 }
55
Clear()56 void VerityConfig::Clear() {
57 hash_tree_data_extent.Clear();
58 hash_tree_extent.Clear();
59 hash_tree_algorithm.clear();
60 hash_tree_salt.clear();
61 fec_data_extent.Clear();
62 fec_extent.Clear();
63 fec_roots = 0;
64 }
65
ValidateExists() const66 bool PartitionConfig::ValidateExists() const {
67 TEST_AND_RETURN_FALSE(!path.empty());
68 TEST_AND_RETURN_FALSE(utils::FileExists(path.c_str()));
69 TEST_AND_RETURN_FALSE(size > 0);
70 // The requested size is within the limits of the file.
71 TEST_AND_RETURN_FALSE(static_cast<off_t>(size) <=
72 utils::FileSize(path.c_str()));
73 return true;
74 }
75
OpenFilesystem()76 bool PartitionConfig::OpenFilesystem() {
77 if (path.empty())
78 return true;
79 fs_interface.reset();
80 if (diff_utils::IsExtFilesystem(path)) {
81 fs_interface = Ext2Filesystem::CreateFromFile(path);
82 // TODO(deymo): The delta generator algorithm doesn't support a block size
83 // different than 4 KiB. Remove this check once that's fixed. b/26972455
84 if (fs_interface) {
85 TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize);
86 return true;
87 }
88 }
89 fs_interface = ErofsFilesystem::CreateFromFile(path, erofs_compression_param);
90 if (fs_interface) {
91 TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize);
92 return true;
93 }
94
95 if (!mapfile_path.empty()) {
96 fs_interface = MapfileFilesystem::CreateFromFile(path, mapfile_path);
97 if (fs_interface) {
98 TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize);
99 return true;
100 }
101 }
102
103 fs_interface = BootImgFilesystem::CreateFromFile(path);
104 if (fs_interface) {
105 TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize);
106 return true;
107 }
108
109 fs_interface = SquashfsFilesystem::CreateFromFile(path,
110 /*extract_deflates=*/true,
111 /*load_settings=*/true);
112 if (fs_interface) {
113 TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize);
114 return true;
115 }
116
117 // Fall back to a RAW filesystem.
118 TEST_AND_RETURN_FALSE(size % kBlockSize == 0);
119 fs_interface = RawFilesystem::Create(
120 "<" + name + "-partition>", kBlockSize, size / kBlockSize);
121 return true;
122 }
123
ValidateIsEmpty() const124 bool ImageConfig::ValidateIsEmpty() const {
125 return partitions.empty();
126 }
127
LoadImageSize()128 bool ImageConfig::LoadImageSize() {
129 for (PartitionConfig& part : partitions) {
130 if (part.path.empty())
131 continue;
132 part.size = utils::FileSize(part.path);
133 }
134 return true;
135 }
136
LoadPostInstallConfig(const brillo::KeyValueStore & store)137 bool ImageConfig::LoadPostInstallConfig(const brillo::KeyValueStore& store) {
138 bool found_postinstall = false;
139 for (PartitionConfig& part : partitions) {
140 bool run_postinstall;
141 if (!store.GetBoolean("RUN_POSTINSTALL_" + part.name, &run_postinstall) ||
142 !run_postinstall)
143 continue;
144 found_postinstall = true;
145 part.postinstall.run = true;
146 store.GetString("POSTINSTALL_PATH_" + part.name, &part.postinstall.path);
147 store.GetString("FILESYSTEM_TYPE_" + part.name,
148 &part.postinstall.filesystem_type);
149 store.GetBoolean("POSTINSTALL_OPTIONAL_" + part.name,
150 &part.postinstall.optional);
151 }
152 if (!found_postinstall) {
153 LOG(ERROR) << "No valid postinstall config found.";
154 return false;
155 }
156 return true;
157 }
158
LoadDynamicPartitionMetadata(const brillo::KeyValueStore & store)159 bool ImageConfig::LoadDynamicPartitionMetadata(
160 const brillo::KeyValueStore& store) {
161 auto metadata = std::make_unique<DynamicPartitionMetadata>();
162 string buf;
163 if (!store.GetString("super_partition_groups", &buf)) {
164 LOG(ERROR) << "Dynamic partition info missing super_partition_groups.";
165 return false;
166 }
167 auto group_names = brillo::string_utils::Split(buf, " ");
168 for (const auto& group_name : group_names) {
169 DynamicPartitionGroup* group = metadata->add_groups();
170 group->set_name(group_name);
171 if (!store.GetString("super_" + group_name + "_group_size", &buf) &&
172 !store.GetString(group_name + "_size", &buf)) {
173 LOG(ERROR) << "Missing super_" << group_name + "_group_size or "
174 << group_name << "_size.";
175 return false;
176 }
177
178 uint64_t max_size;
179 if (!base::StringToUint64(buf, &max_size)) {
180 LOG(ERROR) << "Group size for " << group_name << " = " << buf
181 << " is not an integer.";
182 return false;
183 }
184 group->set_size(max_size);
185
186 if (store.GetString("super_" + group_name + "_partition_list", &buf) ||
187 store.GetString(group_name + "_partition_list", &buf)) {
188 auto partition_names = brillo::string_utils::Split(buf, " ");
189 for (const auto& partition_name : partition_names) {
190 group->add_partition_names()->assign(partition_name);
191 }
192 }
193 }
194
195 bool snapshot_enabled = false;
196 store.GetBoolean("virtual_ab", &snapshot_enabled);
197 metadata->set_snapshot_enabled(snapshot_enabled);
198 bool vabc_enabled = false;
199 if (store.GetBoolean("virtual_ab_compression", &vabc_enabled) &&
200 vabc_enabled) {
201 LOG(INFO) << "Target build supports VABC";
202 metadata->set_vabc_enabled(vabc_enabled);
203 }
204 // We use "gz" compression by default for VABC.
205 if (metadata->vabc_enabled()) {
206 std::string compression_method;
207 if (store.GetString("virtual_ab_compression_method", &compression_method)) {
208 LOG(INFO) << "Using VABC compression method '" << compression_method
209 << "'";
210 } else {
211 LOG(INFO) << "No VABC compression method specified. Defaulting to 'gz'";
212 compression_method = "gz";
213 }
214 metadata->set_vabc_compression_param(compression_method);
215 metadata->set_cow_version(android::snapshot::kCowVersionManifest);
216 }
217 dynamic_partition_metadata = std::move(metadata);
218 return true;
219 }
220
ValidateDynamicPartitionMetadata() const221 bool ImageConfig::ValidateDynamicPartitionMetadata() const {
222 if (dynamic_partition_metadata == nullptr) {
223 LOG(ERROR) << "dynamic_partition_metadata is not loaded.";
224 return false;
225 }
226
227 for (const auto& group : dynamic_partition_metadata->groups()) {
228 uint64_t sum_size = 0;
229 for (const auto& partition_name : group.partition_names()) {
230 auto partition_config = std::find_if(partitions.begin(),
231 partitions.end(),
232 [&partition_name](const auto& e) {
233 return e.name == partition_name;
234 });
235
236 if (partition_config == partitions.end()) {
237 LOG(ERROR) << "Cannot find partition " << partition_name
238 << " which is in " << group.name() << "_partition_list";
239 return false;
240 }
241 sum_size += partition_config->size;
242 }
243
244 if (sum_size > group.size()) {
245 LOG(ERROR) << "Sum of sizes in " << group.name() << "_partition_list is "
246 << sum_size << ", which is greater than " << group.name()
247 << "_size (" << group.size() << ")";
248 return false;
249 }
250 }
251 return true;
252 }
253
PayloadVersion(uint64_t major_version,uint32_t minor_version)254 PayloadVersion::PayloadVersion(uint64_t major_version, uint32_t minor_version) {
255 major = major_version;
256 minor = minor_version;
257 }
258
Validate() const259 bool PayloadVersion::Validate() const {
260 TEST_AND_RETURN_FALSE(major == kBrilloMajorPayloadVersion);
261 TEST_AND_RETURN_FALSE(minor == kFullPayloadMinorVersion ||
262 minor == kSourceMinorPayloadVersion ||
263 minor == kOpSrcHashMinorPayloadVersion ||
264 minor == kBrotliBsdiffMinorPayloadVersion ||
265 minor == kPuffdiffMinorPayloadVersion ||
266 minor == kVerityMinorPayloadVersion ||
267 minor == kPartialUpdateMinorPayloadVersion ||
268 minor == kZucchiniMinorPayloadVersion ||
269 minor == kLZ4DIFFMinorPayloadVersion);
270 return true;
271 }
272
OperationAllowed(InstallOperation::Type operation) const273 bool PayloadVersion::OperationAllowed(InstallOperation::Type operation) const {
274 switch (operation) {
275 // Full operations:
276 case InstallOperation::REPLACE:
277 case InstallOperation::REPLACE_BZ:
278 // These operations were included in the original payload format.
279 case InstallOperation::REPLACE_XZ:
280 // These operations are included minor version 3 or newer and full
281 // payloads.
282 return true;
283
284 case InstallOperation::ZERO:
285 case InstallOperation::DISCARD:
286 // The implementation of these operations had a bug in earlier versions
287 // that prevents them from being used in any payload. We will enable
288 // them for delta payloads for now.
289 return minor >= kBrotliBsdiffMinorPayloadVersion;
290
291 case InstallOperation::SOURCE_COPY:
292 case InstallOperation::SOURCE_BSDIFF:
293 return minor >= kSourceMinorPayloadVersion;
294
295 case InstallOperation::BROTLI_BSDIFF:
296 return minor >= kBrotliBsdiffMinorPayloadVersion;
297
298 case InstallOperation::PUFFDIFF:
299 return minor >= kPuffdiffMinorPayloadVersion;
300
301 case InstallOperation::ZUCCHINI:
302 return minor >= kZucchiniMinorPayloadVersion;
303 case InstallOperation::LZ4DIFF_BSDIFF:
304 case InstallOperation::LZ4DIFF_PUFFDIFF:
305 return minor >= kLZ4DIFFMinorPayloadVersion;
306
307 case InstallOperation::MOVE:
308 case InstallOperation::BSDIFF:
309 NOTREACHED();
310 }
311 return false;
312 }
313
IsDeltaOrPartial() const314 bool PayloadVersion::IsDeltaOrPartial() const {
315 return minor != kFullPayloadMinorVersion;
316 }
317
Validate() const318 bool PayloadGenerationConfig::Validate() const {
319 TEST_AND_RETURN_FALSE(version.Validate());
320 TEST_AND_RETURN_FALSE(version.IsDeltaOrPartial() ==
321 (is_delta || is_partial_update));
322 if (is_delta) {
323 for (const PartitionConfig& part : source.partitions) {
324 if (!part.path.empty()) {
325 TEST_AND_RETURN_FALSE(part.ValidateExists());
326 TEST_AND_RETURN_FALSE(part.size % block_size == 0);
327 }
328 // Source partition should not have postinstall or verity config.
329 TEST_AND_RETURN_FALSE(part.postinstall.IsEmpty());
330 TEST_AND_RETURN_FALSE(part.verity.IsEmpty());
331 }
332
333 } else {
334 // All the "source" image fields must be empty for full payloads.
335 TEST_AND_RETURN_FALSE(source.ValidateIsEmpty());
336 }
337
338 // In all cases, the target image must exists.
339 for (const PartitionConfig& part : target.partitions) {
340 TEST_AND_RETURN_FALSE(part.ValidateExists());
341 TEST_AND_RETURN_FALSE(part.size % block_size == 0);
342 if (version.minor < kVerityMinorPayloadVersion)
343 TEST_AND_RETURN_FALSE(part.verity.IsEmpty());
344 }
345
346 if (version.minor < kPartialUpdateMinorPayloadVersion) {
347 TEST_AND_RETURN_FALSE(!is_partial_update);
348 }
349
350 TEST_AND_RETURN_FALSE(hard_chunk_size == -1 ||
351 hard_chunk_size % block_size == 0);
352 TEST_AND_RETURN_FALSE(soft_chunk_size % block_size == 0);
353
354 TEST_AND_RETURN_FALSE(rootfs_partition_size % block_size == 0);
355
356 return true;
357 }
358
ParseCompressorTypes(const std::string & compressor_types)359 void PayloadGenerationConfig::ParseCompressorTypes(
360 const std::string& compressor_types) {
361 auto types = brillo::string_utils::Split(compressor_types, ":");
362 CHECK_LE(types.size(), 2UL)
363 << "Only two compressor types are allowed: bz2 and brotli";
364 CHECK_GT(types.size(), 0UL) << "Please pass in at least 1 valid compressor. "
365 "Allowed values are bz2 and brotli.";
366 compressors.clear();
367 for (const auto& type : types) {
368 if (type == "bz2") {
369 compressors.emplace_back(bsdiff::CompressorType::kBZ2);
370 } else if (type == "brotli") {
371 compressors.emplace_back(bsdiff::CompressorType::kBrotli);
372 } else {
373 LOG(FATAL) << "Unknown compressor type: " << type;
374 }
375 }
376 }
377
OperationEnabled(InstallOperation::Type op) const378 bool PayloadGenerationConfig::OperationEnabled(
379 InstallOperation::Type op) const noexcept {
380 if (!version.OperationAllowed(op)) {
381 return false;
382 }
383 switch (op) {
384 case InstallOperation::ZUCCHINI:
385 return enable_zucchini;
386 case InstallOperation::LZ4DIFF_BSDIFF:
387 case InstallOperation::LZ4DIFF_PUFFDIFF:
388 return enable_lz4diff;
389 default:
390 return true;
391 }
392 }
393
ParseCompressionParam(std::string_view param)394 CompressionAlgorithm PartitionConfig::ParseCompressionParam(
395 std::string_view param) {
396 CompressionAlgorithm algo;
397 auto algo_name = param;
398 const auto pos = param.find_first_of(',');
399 if (pos != std::string::npos) {
400 algo_name = param.substr(0, pos);
401 }
402 if (algo_name == "lz4") {
403 algo.set_type(CompressionAlgorithm::LZ4);
404 CHECK_EQ(pos, std::string::npos)
405 << "Invalid compression param " << param
406 << ", compression level not supported for lz4";
407 } else if (algo_name == "lz4hc") {
408 algo.set_type(CompressionAlgorithm::LZ4HC);
409 if (pos != std::string::npos) {
410 const auto level = param.substr(pos + 1);
411 int level_num = 0;
412 const auto [ptr, ec] =
413 std::from_chars(level.data(), level.data() + level.size(), level_num);
414 CHECK_EQ(ec, std::errc()) << "Failed to parse compression level " << level
415 << ", compression param: " << param;
416 algo.set_level(level_num);
417 } else {
418 LOG(FATAL) << "Unrecognized compression type: " << algo_name
419 << ", param: " << param;
420 }
421 }
422 return algo;
423 }
424
425 } // namespace chromeos_update_engine
426