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 <utility>
22 
23 #include <android-base/parseint.h>
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 #include "update_engine/update_metadata.pb.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 
Clear()57 void VerityConfig::Clear() {
58   hash_tree_data_extent.Clear();
59   hash_tree_extent.Clear();
60   hash_tree_algorithm.clear();
61   hash_tree_salt.clear();
62   fec_data_extent.Clear();
63   fec_extent.Clear();
64   fec_roots = 0;
65 }
66 
ValidateExists() const67 bool PartitionConfig::ValidateExists() const {
68   TEST_AND_RETURN_FALSE(!path.empty());
69   TEST_AND_RETURN_FALSE(utils::FileExists(path.c_str()));
70   TEST_AND_RETURN_FALSE(size > 0);
71   // The requested size is within the limits of the file.
72   TEST_AND_RETURN_FALSE(static_cast<off_t>(size) <=
73                         utils::FileSize(path.c_str()));
74   return true;
75 }
76 
OpenFilesystem()77 bool PartitionConfig::OpenFilesystem() {
78   if (path.empty())
79     return true;
80   fs_interface.reset();
81   if (diff_utils::IsExtFilesystem(path)) {
82     fs_interface = Ext2Filesystem::CreateFromFile(path);
83     // TODO(deymo): The delta generator algorithm doesn't support a block size
84     // different than 4 KiB. Remove this check once that's fixed. b/26972455
85     if (fs_interface) {
86       TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize);
87       return true;
88     }
89   }
90   fs_interface = ErofsFilesystem::CreateFromFile(path, erofs_compression_param);
91   if (fs_interface) {
92     TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize);
93     return true;
94   }
95 
96   if (!mapfile_path.empty()) {
97     fs_interface = MapfileFilesystem::CreateFromFile(path, mapfile_path);
98     if (fs_interface) {
99       TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize);
100       return true;
101     }
102   }
103 
104   fs_interface = BootImgFilesystem::CreateFromFile(path);
105   if (fs_interface) {
106     TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize);
107     return true;
108   }
109 
110   fs_interface = SquashfsFilesystem::CreateFromFile(path,
111                                                     /*extract_deflates=*/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     std::string cow_version;
216     if (!store.GetString("virtual_ab_cow_version", &cow_version)) {
217       metadata->set_cow_version(2);
218     } else {
219       uint32_t cow_version_num{};
220       android::base::ParseUint(cow_version, &cow_version_num);
221       metadata->set_cow_version(cow_version_num);
222     }
223     std::string compression_factor;
224     if (store.GetString("virtual_ab_compression_factor", &compression_factor)) {
225       LOG(INFO) << "Using VABC compression factor " << compression_factor;
226     } else {
227       LOG(INFO) << "No compression factor specified. Defaulting to 4k";
228       compression_factor = "4096";
229     }
230     size_t compression_factor_value{};
231     if (!android::base::ParseUint(compression_factor,
232                                   &compression_factor_value)) {
233       LOG(ERROR) << "failed to parse compression factor value: "
234                  << compression_factor;
235       return false;
236     }
237     CHECK_EQ(static_cast<int>(compression_factor_value % kBlockSize), 0);
238     CHECK_EQ(static_cast<int>(compression_factor_value &
239                               (compression_factor_value - 1)),
240              0);
241     metadata->set_compression_factor(compression_factor_value);
242   }
243   dynamic_partition_metadata = std::move(metadata);
244   return true;
245 }
246 
ValidateDynamicPartitionMetadata() const247 bool ImageConfig::ValidateDynamicPartitionMetadata() const {
248   if (dynamic_partition_metadata == nullptr) {
249     LOG(ERROR) << "dynamic_partition_metadata is not loaded.";
250     return false;
251   }
252 
253   for (const auto& group : dynamic_partition_metadata->groups()) {
254     uint64_t sum_size = 0;
255     for (const auto& partition_name : group.partition_names()) {
256       auto partition_config = std::find_if(partitions.begin(),
257                                            partitions.end(),
258                                            [&partition_name](const auto& e) {
259                                              return e.name == partition_name;
260                                            });
261 
262       if (partition_config == partitions.end()) {
263         LOG(ERROR) << "Cannot find partition " << partition_name
264                    << " which is in " << group.name() << "_partition_list";
265         return false;
266       }
267       sum_size += partition_config->size;
268     }
269 
270     if (sum_size > group.size()) {
271       LOG(ERROR) << "Sum of sizes in " << group.name() << "_partition_list is "
272                  << sum_size << ", which is greater than " << group.name()
273                  << "_size (" << group.size() << ")";
274       return false;
275     }
276   }
277   return true;
278 }
279 
PayloadVersion(uint64_t major_version,uint32_t minor_version)280 PayloadVersion::PayloadVersion(uint64_t major_version, uint32_t minor_version) {
281   major = major_version;
282   minor = minor_version;
283 }
284 
Validate() const285 bool PayloadVersion::Validate() const {
286   TEST_AND_RETURN_FALSE(major == kBrilloMajorPayloadVersion);
287   TEST_AND_RETURN_FALSE(minor == kFullPayloadMinorVersion ||
288                         minor == kSourceMinorPayloadVersion ||
289                         minor == kOpSrcHashMinorPayloadVersion ||
290                         minor == kBrotliBsdiffMinorPayloadVersion ||
291                         minor == kPuffdiffMinorPayloadVersion ||
292                         minor == kVerityMinorPayloadVersion ||
293                         minor == kPartialUpdateMinorPayloadVersion ||
294                         minor == kZucchiniMinorPayloadVersion ||
295                         minor == kLZ4DIFFMinorPayloadVersion);
296   return true;
297 }
298 
OperationAllowed(InstallOperation::Type operation) const299 bool PayloadVersion::OperationAllowed(InstallOperation::Type operation) const {
300   switch (operation) {
301     // Full operations:
302     case InstallOperation::REPLACE:
303     case InstallOperation::REPLACE_BZ:
304       // These operations were included in the original payload format.
305     case InstallOperation::REPLACE_XZ:
306       // These operations are included minor version 3 or newer and full
307       // payloads.
308       return true;
309 
310     case InstallOperation::ZERO:
311     case InstallOperation::DISCARD:
312       // The implementation of these operations had a bug in earlier versions
313       // that prevents them from being used in any payload. We will enable
314       // them for delta payloads for now.
315       return minor >= kBrotliBsdiffMinorPayloadVersion;
316 
317     case InstallOperation::SOURCE_COPY:
318     case InstallOperation::SOURCE_BSDIFF:
319       return minor >= kSourceMinorPayloadVersion;
320 
321     case InstallOperation::BROTLI_BSDIFF:
322       return minor >= kBrotliBsdiffMinorPayloadVersion;
323 
324     case InstallOperation::PUFFDIFF:
325       return minor >= kPuffdiffMinorPayloadVersion;
326 
327     case InstallOperation::ZUCCHINI:
328       return minor >= kZucchiniMinorPayloadVersion;
329     case InstallOperation::LZ4DIFF_BSDIFF:
330     case InstallOperation::LZ4DIFF_PUFFDIFF:
331       return minor >= kLZ4DIFFMinorPayloadVersion;
332 
333     case InstallOperation::MOVE:
334     case InstallOperation::BSDIFF:
335       NOTREACHED();
336   }
337   return false;
338 }
339 
IsDeltaOrPartial() const340 bool PayloadVersion::IsDeltaOrPartial() const {
341   return minor != kFullPayloadMinorVersion;
342 }
343 
Validate() const344 bool PayloadGenerationConfig::Validate() const {
345   TEST_AND_RETURN_FALSE(version.Validate());
346   TEST_AND_RETURN_FALSE(version.IsDeltaOrPartial() ==
347                         (is_delta || is_partial_update));
348   if (is_delta) {
349     for (const PartitionConfig& part : source.partitions) {
350       if (!part.path.empty()) {
351         TEST_AND_RETURN_FALSE(part.ValidateExists());
352         TEST_AND_RETURN_FALSE(part.size % block_size == 0);
353       }
354       // Source partition should not have postinstall or verity config.
355       TEST_AND_RETURN_FALSE(part.postinstall.IsEmpty());
356       TEST_AND_RETURN_FALSE(part.verity.IsEmpty());
357     }
358 
359   } else {
360     // All the "source" image fields must be empty for full payloads.
361     TEST_AND_RETURN_FALSE(source.ValidateIsEmpty());
362   }
363 
364   // In all cases, the target image must exists.
365   for (const PartitionConfig& part : target.partitions) {
366     TEST_AND_RETURN_FALSE(part.ValidateExists());
367     TEST_AND_RETURN_FALSE(part.size % block_size == 0);
368     if (version.minor < kVerityMinorPayloadVersion)
369       TEST_AND_RETURN_FALSE(part.verity.IsEmpty());
370   }
371 
372   if (version.minor < kPartialUpdateMinorPayloadVersion) {
373     TEST_AND_RETURN_FALSE(!is_partial_update);
374   }
375 
376   TEST_AND_RETURN_FALSE(hard_chunk_size == -1 ||
377                         hard_chunk_size % block_size == 0);
378   TEST_AND_RETURN_FALSE(soft_chunk_size % block_size == 0);
379 
380   TEST_AND_RETURN_FALSE(rootfs_partition_size % block_size == 0);
381 
382   return true;
383 }
384 
ParseCompressorTypes(const std::string & compressor_types)385 void PayloadGenerationConfig::ParseCompressorTypes(
386     const std::string& compressor_types) {
387   auto types = brillo::string_utils::Split(compressor_types, ":");
388   CHECK_LE(types.size(), 2UL)
389       << "Only two compressor types are allowed: bz2 and brotli";
390   CHECK_GT(types.size(), 0UL) << "Please pass in at least 1 valid compressor. "
391                                  "Allowed values are bz2 and brotli.";
392   compressors.clear();
393   for (const auto& type : types) {
394     if (type == "bz2") {
395       compressors.emplace_back(bsdiff::CompressorType::kBZ2);
396     } else if (type == "brotli") {
397       compressors.emplace_back(bsdiff::CompressorType::kBrotli);
398     } else {
399       LOG(FATAL) << "Unknown compressor type: " << type;
400     }
401   }
402 }
403 
OperationEnabled(InstallOperation::Type op) const404 bool PayloadGenerationConfig::OperationEnabled(
405     InstallOperation::Type op) const noexcept {
406   if (!version.OperationAllowed(op)) {
407     return false;
408   }
409   switch (op) {
410     case InstallOperation::ZUCCHINI:
411       return enable_zucchini;
412     case InstallOperation::LZ4DIFF_BSDIFF:
413     case InstallOperation::LZ4DIFF_PUFFDIFF:
414       return enable_lz4diff;
415     case InstallOperation::PUFFDIFF:
416       return enable_puffdiff;
417     default:
418       return true;
419   }
420 }
421 
ParseCompressionParam(std::string_view param)422 CompressionAlgorithm PartitionConfig::ParseCompressionParam(
423     std::string_view param) {
424   CompressionAlgorithm algo;
425   auto algo_name = param;
426   const auto pos = param.find_first_of(',');
427   if (pos != std::string::npos) {
428     algo_name = param.substr(0, pos);
429   }
430   if (algo_name == "lz4") {
431     algo.set_type(CompressionAlgorithm::LZ4);
432     CHECK_EQ(pos, std::string::npos)
433         << "Invalid compression param " << param
434         << ", compression level not supported for lz4";
435   } else if (algo_name == "lz4hc") {
436     algo.set_type(CompressionAlgorithm::LZ4HC);
437     if (pos != std::string::npos) {
438       const auto level = param.substr(pos + 1);
439       int level_num = 0;
440       const auto [ptr, ec] =
441           std::from_chars(level.data(), level.data() + level.size(), level_num);
442       CHECK_EQ(ec, std::errc()) << "Failed to parse compression level " << level
443                                 << ", compression param: " << param;
444       algo.set_level(level_num);
445     } else {
446       LOG(FATAL) << "Unrecognized compression type: " << algo_name
447                  << ", param: " << param;
448     }
449   }
450   return algo;
451 }
452 
453 }  // namespace chromeos_update_engine
454