• 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_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