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