• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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