update_engine: Add "verify" support to brillo_update_payload
This change adds a new command "verify" to brillo_update_payload to verify the
process of update by applying a delta or full payload to temporary target
partitions and comparing them with the original target partitions. This is
specially usefull when manually debuggin/testing delta performer operations.
BUG=none
TEST=brillo_update_payload verify --payload=payload.delta --source_image=link_8872.49.bin --target_image=link_9000.82.bin
Change-Id: I4b30bc8a1088f4f72b681c6095cca6863a715078
Reviewed-on: https://chromium-review.googlesource.com/585565
Commit-Ready: Amin Hassani <ahassani@chromium.org>
Tested-by: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Ben Chan <benchan@chromium.org>
Reviewed-by: Sen Jiang <senj@chromium.org>
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index d7d4f35..d93313f 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -29,6 +29,8 @@
#include <brillo/flag_helper.h>
#include <brillo/key_value_store.h>
+#include "update_engine/common/fake_boot_control.h"
+#include "update_engine/common/fake_hardware.h"
#include "update_engine/common/prefs.h"
#include "update_engine/common/terminator.h"
#include "update_engine/common/utils.h"
@@ -179,43 +181,58 @@
// TODO(deymo): This function is likely broken for deltas minor version 2 or
// newer. Move this function to a new file and make the delta_performer
// integration tests use this instead.
-void ApplyDelta(const string& in_file,
- const string& old_kernel,
- const string& old_rootfs,
- const string& prefs_dir) {
+bool ApplyPayload(const string& payload_file,
+ // Simply reuses the payload config used for payload
+ // generation.
+ const PayloadGenerationConfig& config) {
LOG(INFO) << "Applying delta.";
- LOG_IF(FATAL, old_rootfs.empty())
- << "Must pass --old_image to apply delta.";
- Prefs prefs;
+ FakeBootControl fake_boot_control;
+ FakeHardware fake_hardware;
+ MemoryPrefs prefs;
InstallPlan install_plan;
- LOG(INFO) << "Setting up preferences under: " << prefs_dir;
- LOG_IF(ERROR, !prefs.Init(base::FilePath(prefs_dir)))
- << "Failed to initialize preferences.";
- // Get original checksums
- LOG(INFO) << "Calculating original checksums";
- ImageConfig old_image;
- old_image.partitions.emplace_back(kLegacyPartitionNameRoot);
- old_image.partitions.back().path = old_rootfs;
- old_image.partitions.emplace_back(kLegacyPartitionNameKernel);
- old_image.partitions.back().path = old_kernel;
- CHECK(old_image.LoadImageSize());
- for (const auto& old_part : old_image.partitions) {
- PartitionInfo part_info;
- CHECK(diff_utils::InitializePartitionInfo(old_part, &part_info));
+ install_plan.source_slot =
+ config.is_delta ? 0 : BootControlInterface::kInvalidSlot;
+ install_plan.target_slot = 1;
+ install_plan.payload_type =
+ config.is_delta ? InstallPayloadType::kDelta : InstallPayloadType::kFull;
+
+ for (size_t i = 0; i < config.target.partitions.size(); i++) {
InstallPlan::Partition part;
- part.name = old_part.name;
- part.source_hash.assign(part_info.hash().begin(),
- part_info.hash().end());
- part.source_path = old_part.path;
- // Apply the delta in-place to the old_part.
- part.target_path = old_part.path;
+ part.name = config.target.partitions[i].name;
+ part.target_path = config.target.partitions[i].path;
+ fake_boot_control.SetPartitionDevice(
+ part.name, install_plan.target_slot, part.target_path);
+
+ if (config.is_delta) {
+ TEST_AND_RETURN_FALSE(config.target.partitions.size() ==
+ config.source.partitions.size());
+ PartitionInfo part_info;
+ TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(
+ config.source.partitions[i], &part_info));
+ part.source_hash.assign(part_info.hash().begin(), part_info.hash().end());
+ part.source_path = config.source.partitions[i].path;
+
+ fake_boot_control.SetPartitionDevice(
+ part.name, install_plan.source_slot, part.source_path);
+ }
+
install_plan.partitions.push_back(part);
+
+ LOG(INFO) << "Install partition:"
+ << " source: " << part.source_path
+ << " target: " << part.target_path;
+
}
- DeltaPerformer performer(
- &prefs, nullptr, nullptr, nullptr, &install_plan, true);
+ DeltaPerformer performer(&prefs,
+ &fake_boot_control,
+ &fake_hardware,
+ nullptr,
+ &install_plan,
+ true); // is_interactive
+
brillo::Blob buf(1024 * 1024);
- int fd = open(in_file.c_str(), O_RDONLY, 0);
+ int fd = open(payload_file.c_str(), O_RDONLY, 0);
CHECK_GE(fd, 0);
ScopedFdCloser fd_closer(&fd);
for (off_t offset = 0;; offset += buf.size()) {
@@ -223,11 +240,13 @@
CHECK(utils::PReadAll(fd, buf.data(), buf.size(), offset, &bytes_read));
if (bytes_read == 0)
break;
- CHECK_EQ(performer.Write(buf.data(), bytes_read), bytes_read);
+ TEST_AND_RETURN_FALSE(performer.Write(buf.data(), bytes_read));
}
CHECK_EQ(performer.Close(), 0);
DeltaPerformer::ResetUpdateProgress(&prefs, false);
- LOG(INFO) << "Done applying delta.";
+ LOG(INFO) << "Completed applying " << (config.is_delta ? "delta" : "full")
+ << " payload.";
+ return true;
}
int ExtractProperties(const string& payload_path, const string& props_file) {
@@ -289,8 +308,6 @@
DEFINE_string(public_key, "", "Path to public key in .pem format");
DEFINE_int32(public_key_version, -1,
"DEPRECATED. Key-check version # of client");
- DEFINE_string(prefs_dir, "/tmp/update_engine_prefs",
- "Preferences directory, used with apply_delta");
DEFINE_string(signature_size, "",
"Raw signature size used for hash calculation. "
"You may pass in multiple sizes by colon separating them. E.g. "
@@ -401,11 +418,6 @@
if (!FLAGS_properties_file.empty()) {
return ExtractProperties(FLAGS_in_file, FLAGS_properties_file) ? 0 : 1;
}
- if (!FLAGS_in_file.empty()) {
- ApplyDelta(FLAGS_in_file, FLAGS_old_kernel, FLAGS_old_image,
- FLAGS_prefs_dir);
- return 0;
- }
// A payload generation was requested. Convert the flags to a
// PayloadGenerationConfig.
@@ -488,6 +500,11 @@
}
}
+ if (!FLAGS_in_file.empty()) {
+ ApplyPayload(FLAGS_in_file, payload_config);
+ return 0;
+ }
+
if (!FLAGS_new_postinstall_config_file.empty()) {
LOG_IF(FATAL, FLAGS_major_version == kChromeOSMajorPayloadVersion)
<< "Postinstall config is only allowed in major version 2 or newer.";
@@ -563,11 +580,8 @@
LOG(INFO) << "Using provided minor_version=" << FLAGS_minor_version;
}
- if (payload_config.is_delta) {
- LOG(INFO) << "Generating delta update";
- } else {
- LOG(INFO) << "Generating full update";
- }
+ LOG(INFO) << "Generating " << (payload_config.is_delta ? "delta" : "full")
+ << " update";
// From this point, all the options have been parsed.
if (!payload_config.Validate()) {
diff --git a/scripts/brillo_update_payload b/scripts/brillo_update_payload
index b139491..911d622 100755
--- a/scripts/brillo_update_payload
+++ b/scripts/brillo_update_payload
@@ -1,8 +1,20 @@
#!/bin/bash
-# Copyright 2015 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
# Script to generate a Brillo update for use by the update engine.
#
@@ -12,14 +24,15 @@
# hash generate a payload or metadata hash
# sign generate a signed payload
# properties generate a properties file from a payload
+# verify verify a payload by recreating a target image.
#
# Generate command arguments:
# --payload generated unsigned payload output file
# --source_image if defined, generate a delta payload from the specified
# image to the target_image
# --target_image the target image that should be sent to clients
-# --metadata_size_file if defined, generate a file containing the size of the payload
-# metadata in bytes to the specified file
+# --metadata_size_file if defined, generate a file containing the size of the
+# payload metadata in bytes to the specified file
#
# Hash command arguments:
# --unsigned_payload the input unsigned payload to generate the hash from
@@ -50,6 +63,10 @@
# --payload the input signed or unsigned payload
# --properties_file the output path where to write the properties, or
# '-' for stdout.
+# Verify command arguments:
+# --payload payload input file
+# --source_image verify payload to the specified source image.
+# --target_image the target image to verify upon.
# Exit codes:
@@ -85,6 +102,7 @@
for signing."
HELP_SIGN="sign: Insert the signatures into the unsigned payload."
HELP_PROPERTIES="properties: Extract payload properties to a file."
+HELP_VERIFY="verify: Verify a (signed) update payload."
usage() {
echo "Supported commands:"
@@ -93,6 +111,7 @@
echo "${HELP_HASH}"
echo "${HELP_SIGN}"
echo "${HELP_PROPERTIES}"
+ echo "${HELP_VERIFY}"
echo
echo "Use: \"$0 <command> --help\" for more options."
}
@@ -123,6 +142,11 @@
properties)
FLAGS_HELP="${HELP_PROPERTIES}"
;;
+
+ verify)
+ FLAGS_HELP="${HELP_VERIFY}"
+ ;;
+
*)
echo "Unrecognized command: \"${COMMAND}\"" >&2
usage >&2
@@ -174,6 +198,15 @@
"Path to output the extracted property files. If '-' is passed stdout will \
be used."
fi
+if [[ "${COMMAND}" == "verify" ]]; then
+ DEFINE_string payload "" \
+ "Path to the input payload file."
+ DEFINE_string target_image "" \
+ "Path to the target image to verify upon."
+ DEFINE_string source_image "" \
+ "Optional: Path to a source image. If specified, the delta update is \
+applied to this."
+fi
DEFINE_string work_dir "${TMPDIR:-/tmp}" "Where to dump temporary files."
@@ -621,6 +654,87 @@
-properties_file="${FLAGS_properties_file}"
}
+validate_verify() {
+ [[ -n "${FLAGS_payload}" ]] ||
+ die "Error: you must specify an input filename with --payload FILENAME"
+
+ [[ -n "${FLAGS_target_image}" ]] ||
+ die "Error: you must specify a target image with --target_image FILENAME"
+}
+
+cmd_verify() {
+ local payload_type="delta"
+ if [[ -z "${FLAGS_source_image}" ]]; then
+ payload_type="full"
+ fi
+
+ echo "Extracting images for ${payload_type} update."
+
+ if [[ "${payload_type}" == "delta" ]]; then
+ extract_image "${FLAGS_source_image}" SRC_PARTITIONS
+ fi
+ extract_image "${FLAGS_target_image}" DST_PARTITIONS PARTITIONS_ORDER
+
+ declare -A TMP_PARTITIONS
+ for part in "${PARTITIONS_ORDER[@]}"; do
+ local tmp_part=$(create_tempfile "tmp_part.bin.XXXXXX")
+ echo "Creating temporary target partition ${tmp_part} for ${part}"
+ CLEANUP_FILES+=("${tmp_part}")
+ TMP_PARTITIONS[${part}]=${tmp_part}
+ local FILESIZE=$(stat -c%s "${DST_PARTITIONS[${part}]}")
+ echo "Truncating ${TMP_PARTITIONS[${part}]} to ${FILESIZE}"
+ truncate_file "${TMP_PARTITIONS[${part}]}" "${FILESIZE}"
+ done
+
+ echo "Verifying ${payload_type} update."
+ # Common payload args:
+ GENERATOR_ARGS=( -in_file="${FLAGS_payload}" )
+
+ local part old_partitions="" new_partitions="" partition_names=""
+ for part in "${PARTITIONS_ORDER[@]}"; do
+ if [[ -n "${partition_names}" ]]; then
+ partition_names+=":"
+ new_partitions+=":"
+ old_partitions+=":"
+ fi
+ partition_names+="${part}"
+ new_partitions+="${TMP_PARTITIONS[${part}]}"
+ old_partitions+="${SRC_PARTITIONS[${part}]:-}"
+ done
+
+ # Target image args:
+ GENERATOR_ARGS+=(
+ -partition_names="${partition_names}"
+ -new_partitions="${new_partitions}"
+ )
+
+ if [[ "${payload_type}" == "delta" ]]; then
+ # Source image args:
+ GENERATOR_ARGS+=(
+ -old_partitions="${old_partitions}"
+ )
+ fi
+
+ echo "Running delta_generator to verify ${payload_type} payload with args: \
+${GENERATOR_ARGS[@]}"
+ "${GENERATOR}" "${GENERATOR_ARGS[@]}"
+
+ if [[ $? -eq 0 ]]; then
+ echo "Done applying ${payload_type} update."
+ echo "Checking the newly generated partitions against the target partitions"
+ for part in "${PARTITIONS_ORDER[@]}"; do
+ cmp "${TMP_PARTITIONS[${part}]}" "${DST_PARTITIONS[${part}]}"
+ local not_str=""
+ if [[ $? -ne 0 ]]; then
+ not_str="in"
+ fi
+ echo "The new partition (${part}) is ${not_str}valid."
+ done
+ else
+ echo "Failed to apply ${payload_type} update."
+ fi
+}
+
# Sanity check that the real generator exists:
GENERATOR="$(which delta_generator || true)"
[[ -x "${GENERATOR}" ]] || die "can't find delta_generator"
@@ -638,4 +752,7 @@
properties) validate_properties
cmd_properties
;;
+ verify) validate_verify
+ cmd_verify
+ ;;
esac