summaryrefslogtreecommitdiffstats
path: root/updater
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--updater/Android.bp3
-rw-r--r--updater/Android.mk2
-rw-r--r--updater/blockimg.cpp100
-rw-r--r--updater/dynamic_partitions.cpp435
-rw-r--r--updater/include/private/utils.h21
-rw-r--r--updater/include/updater/dynamic_partitions.h19
-rw-r--r--updater/install.cpp97
-rw-r--r--updater/updater.cpp2
-rw-r--r--updater_sample/AndroidManifest.xml2
-rw-r--r--updater_sample/README.md10
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java79
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java (renamed from updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java)106
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java4
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java2
-rw-r--r--updater_sample/tests/Android.bp2
-rw-r--r--updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java75
16 files changed, 758 insertions, 201 deletions
diff --git a/updater/Android.bp b/updater/Android.bp
index c95ec5e90..b80cdb3a0 100644
--- a/updater/Android.bp
+++ b/updater/Android.bp
@@ -26,12 +26,14 @@ cc_defaults {
"libedify",
"libotautil",
"libext4_utils",
+ "libdm",
"libfec",
"libfec_rs",
"libverity_tree",
"libfs_mgr",
"libgtest_prod",
"liblog",
+ "liblp",
"libselinux",
"libsparse",
"libsquashfs_utils",
@@ -66,6 +68,7 @@ cc_library_static {
srcs: [
"blockimg.cpp",
"commands.cpp",
+ "dynamic_partitions.cpp",
"install.cpp",
],
diff --git a/updater/Android.mk b/updater/Android.mk
index 78e32ba39..c7a6ba989 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -29,12 +29,14 @@ updater_common_static_libraries := \
libedify \
libotautil \
libext4_utils \
+ libdm \
libfec \
libfec_rs \
libverity_tree \
libfs_mgr \
libgtest_prod \
liblog \
+ liblp \
libselinux \
libsparse \
libsquashfs_utils \
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 838845673..07c3c7b52 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -53,6 +53,7 @@
#include <ziparchive/zip_archive.h>
#include "edify/expr.h"
+#include "otautil/dirutil.h"
#include "otautil/error_code.h"
#include "otautil/paths.h"
#include "otautil/print_sha1.h"
@@ -69,6 +70,7 @@
static constexpr size_t BLOCKSIZE = 4096;
static constexpr mode_t STASH_DIRECTORY_MODE = 0700;
static constexpr mode_t STASH_FILE_MODE = 0600;
+static constexpr mode_t MARKER_DIRECTORY_MODE = 0700;
static CauseCode failure_type = kNoCause;
static bool is_retry = false;
@@ -109,7 +111,7 @@ static bool ParseLastCommandFile(size_t* last_command_index) {
return false;
}
- if (!android::base::ParseInt(lines[0], last_command_index)) {
+ if (!android::base::ParseUint(lines[0], last_command_index)) {
LOG(ERROR) << "Failed to parse integer in: " << lines[0];
return false;
}
@@ -166,26 +168,37 @@ static bool UpdateLastCommandIndex(size_t command_index, const std::string& comm
return true;
}
-static bool SetPartitionUpdatedMarker(const std::string& marker) {
+bool SetUpdatedMarker(const std::string& marker) {
+ auto dirname = android::base::Dirname(marker);
+ auto res = mkdir(dirname.c_str(), MARKER_DIRECTORY_MODE);
+ if (res == -1 && errno != EEXIST) {
+ PLOG(ERROR) << "Failed to create directory for marker: " << dirname;
+ return false;
+ }
+
if (!android::base::WriteStringToFile("", marker)) {
PLOG(ERROR) << "Failed to write to marker file " << marker;
return false;
}
- if (!FsyncDir(android::base::Dirname(marker))) {
+ if (!FsyncDir(dirname)) {
return false;
}
- LOG(INFO) << "Wrote partition updated marker to " << marker;
+ LOG(INFO) << "Wrote updated marker to " << marker;
return true;
}
-static bool discard_blocks(int fd, off64_t offset, uint64_t size) {
- // Don't discard blocks unless the update is a retry run.
- if (!is_retry) {
+static bool discard_blocks(int fd, off64_t offset, uint64_t size, bool force = false) {
+ // Don't discard blocks unless the update is a retry run or force == true
+ if (!is_retry && !force) {
return true;
}
uint64_t args[2] = { static_cast<uint64_t>(offset), size };
if (ioctl(fd, BLKDISCARD, &args) == -1) {
+ // On devices that does not support BLKDISCARD, ignore the error.
+ if (errno == EOPNOTSUPP) {
+ return true;
+ }
PLOG(ERROR) << "BLKDISCARD ioctl failed";
return false;
}
@@ -874,7 +887,7 @@ static int CreateStash(State* state, size_t maxblocks, const std::string& base)
size_t max_stash_size = maxblocks * BLOCKSIZE;
if (res == -1) {
LOG(INFO) << "creating stash " << dirname;
- res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE);
+ res = mkdir_recursively(dirname, STASH_DIRECTORY_MODE, false, nullptr);
if (res != 0) {
ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s", dirname.c_str(),
@@ -1399,7 +1412,10 @@ static int PerformCommandDiff(CommandParameters& params) {
// We expect the output of the patcher to fill the tgt ranges exactly.
if (!writer.Finished()) {
- LOG(ERROR) << "range sink underrun?";
+ LOG(ERROR) << "Failed to fully write target blocks (range sink underrun): Missing "
+ << writer.AvailableSpace() << " bytes";
+ failure_type = kPatchApplicationFailure;
+ return -1;
}
} else {
LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.blocks() << " ["
@@ -1445,14 +1461,9 @@ static int PerformCommandErase(CommandParameters& params) {
LOG(INFO) << " erasing " << tgt.blocks() << " blocks";
for (const auto& [begin, end] : tgt) {
- uint64_t blocks[2];
- // offset in bytes
- blocks[0] = begin * static_cast<uint64_t>(BLOCKSIZE);
- // length in bytes
- blocks[1] = (end - begin) * static_cast<uint64_t>(BLOCKSIZE);
-
- if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) {
- PLOG(ERROR) << "BLKDISCARD ioctl failed";
+ off64_t offset = static_cast<off64_t>(begin) * BLOCKSIZE;
+ size_t size = (end - begin) * BLOCKSIZE;
+ if (!discard_blocks(params.fd, offset, size, true /* force */)) {
return -1;
}
}
@@ -1514,7 +1525,7 @@ static int PerformCommandComputeHashTree(CommandParameters& params) {
// Starts the hash_tree computation.
HashTreeBuilder builder(BLOCKSIZE, hash_function);
- if (!builder.Initialize(source_ranges.blocks() * BLOCKSIZE, salt)) {
+ if (!builder.Initialize(static_cast<int64_t>(source_ranges.blocks()) * BLOCKSIZE, salt)) {
LOG(ERROR) << "Failed to initialize hash tree computation, source " << source_ranges.ToString()
<< ", salt " << salt_hex;
return -1;
@@ -1570,6 +1581,43 @@ using CommandFunction = std::function<int(CommandParameters&)>;
using CommandMap = std::unordered_map<Command::Type, CommandFunction>;
+static bool Sha1DevicePath(const std::string& path, uint8_t digest[SHA_DIGEST_LENGTH]) {
+ auto device_name = android::base::Basename(path);
+ auto dm_target_name_path = "/sys/block/" + device_name + "/dm/name";
+
+ struct stat sb;
+ if (stat(dm_target_name_path.c_str(), &sb) == 0) {
+ // This is a device mapper target. Use partition name as part of the hash instead. Do not
+ // include extents as part of the hash, because the size of a partition may be shrunk after
+ // the patches are applied.
+ std::string dm_target_name;
+ if (!android::base::ReadFileToString(dm_target_name_path, &dm_target_name)) {
+ PLOG(ERROR) << "Cannot read " << dm_target_name_path;
+ return false;
+ }
+ SHA1(reinterpret_cast<const uint8_t*>(dm_target_name.data()), dm_target_name.size(), digest);
+ return true;
+ }
+
+ if (errno != ENOENT) {
+ // This is a device mapper target, but its name cannot be retrieved.
+ PLOG(ERROR) << "Cannot get dm target name for " << path;
+ return false;
+ }
+
+ // This doesn't appear to be a device mapper target, but if its name starts with dm-, something
+ // else might have gone wrong.
+ if (android::base::StartsWith(device_name, "dm-")) {
+ LOG(WARNING) << "Device " << path << " starts with dm- but is not mapped by device-mapper.";
+ }
+
+ // Stash directory should be different for each partition to avoid conflicts when updating
+ // multiple partitions at the same time, so we use the hash of the block device name as the base
+ // directory.
+ SHA1(reinterpret_cast<const uint8_t*>(path.data()), path.size(), digest);
+ return true;
+}
+
static Value* PerformBlockImageUpdate(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv,
const CommandMap& command_map, bool dryrun) {
@@ -1654,12 +1702,10 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
return StringValue("");
}
- // Stash directory should be different for each partition to avoid conflicts when updating
- // multiple partitions at the same time, so we use the hash of the block device name as the base
- // directory.
uint8_t digest[SHA_DIGEST_LENGTH];
- SHA1(reinterpret_cast<const uint8_t*>(blockdev_filename->data.data()),
- blockdev_filename->data.size(), digest);
+ if (!Sha1DevicePath(blockdev_filename->data, digest)) {
+ return StringValue("");
+ }
params.stashbase = print_sha1(digest);
// Possibly do return early on retry, by checking the marker. If the update on this partition has
@@ -1869,8 +1915,10 @@ pbiudone:
const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
if (partition != nullptr && *(partition + 1) != 0) {
- fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, params.written * BLOCKSIZE);
- fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, params.stashed * BLOCKSIZE);
+ fprintf(cmd_pipe, "log bytes_written_%s: %" PRIu64 "\n", partition + 1,
+ static_cast<uint64_t>(params.written) * BLOCKSIZE);
+ fprintf(cmd_pipe, "log bytes_stashed_%s: %" PRIu64 "\n", partition + 1,
+ static_cast<uint64_t>(params.stashed) * BLOCKSIZE);
fflush(cmd_pipe);
}
// Delete stash only after successfully completing the update, as it may contain blocks needed
@@ -1881,7 +1929,7 @@ pbiudone:
// Create a marker on /cache partition, which allows skipping the update on this partition on
// retry. The marker will be removed once booting into normal boot, or before starting next
// fresh install.
- if (!SetPartitionUpdatedMarker(updated_marker)) {
+ if (!SetUpdatedMarker(updated_marker)) {
LOG(WARNING) << "Failed to set updated marker; continuing";
}
}
diff --git a/updater/dynamic_partitions.cpp b/updater/dynamic_partitions.cpp
new file mode 100644
index 000000000..b50dd75f9
--- /dev/null
+++ b/updater/dynamic_partitions.cpp
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include "updater/dynamic_partitions.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <chrono>
+#include <iterator>
+#include <memory>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <fs_mgr.h>
+#include <fs_mgr_dm_linear.h>
+#include <libdm/dm.h>
+#include <liblp/builder.h>
+
+#include "edify/expr.h"
+#include "otautil/error_code.h"
+#include "otautil/paths.h"
+#include "private/utils.h"
+
+using android::base::ParseUint;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::fs_mgr::CreateLogicalPartition;
+using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::LpMetadata;
+using android::fs_mgr::MetadataBuilder;
+using android::fs_mgr::Partition;
+using android::fs_mgr::PartitionOpener;
+
+static constexpr std::chrono::milliseconds kMapTimeout{ 1000 };
+static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED";
+
+static std::string GetSuperDevice() {
+ return "/dev/block/by-name/" + fs_mgr_get_super_partition_name();
+}
+
+static std::vector<std::string> ReadStringArgs(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv,
+ const std::vector<std::string>& arg_names) {
+ if (argv.size() != arg_names.size()) {
+ ErrorAbort(state, kArgsParsingFailure, "%s expects %zu arguments, got %zu", name,
+ arg_names.size(), argv.size());
+ return {};
+ }
+
+ std::vector<std::unique_ptr<Value>> args;
+ if (!ReadValueArgs(state, argv, &args)) {
+ return {};
+ }
+
+ CHECK_EQ(args.size(), arg_names.size());
+
+ for (size_t i = 0; i < arg_names.size(); ++i) {
+ if (args[i]->type != Value::Type::STRING) {
+ ErrorAbort(state, kArgsParsingFailure, "%s argument to %s must be string",
+ arg_names[i].c_str(), name);
+ return {};
+ }
+ }
+
+ std::vector<std::string> ret;
+ std::transform(args.begin(), args.end(), std::back_inserter(ret),
+ [](const auto& arg) { return arg->data; });
+ return ret;
+}
+
+static bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+ auto state = DeviceMapper::Instance().GetState(partition_name);
+ if (state == DmDeviceState::INVALID) {
+ return true;
+ }
+ if (state == DmDeviceState::ACTIVE) {
+ return DestroyLogicalPartition(partition_name, kMapTimeout);
+ }
+ LOG(ERROR) << "Unknown device mapper state: "
+ << static_cast<std::underlying_type_t<DmDeviceState>>(state);
+ return false;
+}
+
+static bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) {
+ auto state = DeviceMapper::Instance().GetState(partition_name);
+ if (state == DmDeviceState::INVALID) {
+ return CreateLogicalPartition(GetSuperDevice(), 0 /* metadata slot */, partition_name,
+ true /* force writable */, kMapTimeout, path);
+ }
+
+ if (state == DmDeviceState::ACTIVE) {
+ return DeviceMapper::Instance().GetDmDevicePathByName(partition_name, path);
+ }
+ LOG(ERROR) << "Unknown device mapper state: "
+ << static_cast<std::underlying_type_t<DmDeviceState>>(state);
+ return false;
+}
+
+Value* UnmapPartitionFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ auto args = ReadStringArgs(name, state, argv, { "name" });
+ if (args.empty()) return StringValue("");
+
+ return UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t") : StringValue("");
+}
+
+Value* MapPartitionFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ auto args = ReadStringArgs(name, state, argv, { "name" });
+ if (args.empty()) return StringValue("");
+
+ std::string path;
+ bool result = MapPartitionOnDeviceMapper(args[0], &path);
+ return result ? StringValue(path) : StringValue("");
+}
+
+namespace { // Ops
+
+struct OpParameters {
+ std::vector<std::string> tokens;
+ MetadataBuilder* builder;
+
+ bool ExpectArgSize(size_t size) const {
+ CHECK(!tokens.empty());
+ auto actual = tokens.size() - 1;
+ if (actual != size) {
+ LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual;
+ return false;
+ }
+ return true;
+ }
+ const std::string& op() const {
+ CHECK(!tokens.empty());
+ return tokens[0];
+ }
+ const std::string& arg(size_t pos) const {
+ CHECK_LE(pos + 1, tokens.size());
+ return tokens[pos + 1];
+ }
+ std::optional<uint64_t> uint_arg(size_t pos, const std::string& name) const {
+ auto str = arg(pos);
+ uint64_t ret;
+ if (!ParseUint(str, &ret)) {
+ LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str;
+ return std::nullopt;
+ }
+ return ret;
+ }
+};
+
+using OpFunction = std::function<bool(const OpParameters&)>;
+using OpMap = std::map<std::string, OpFunction>;
+
+bool PerformOpResize(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& partition_name = params.arg(0);
+ auto size = params.uint_arg(1, "size");
+ if (!size.has_value()) return false;
+
+ auto partition = params.builder->FindPartition(partition_name);
+ if (partition == nullptr) {
+ LOG(ERROR) << "Failed to find partition " << partition_name
+ << " in dynamic partition metadata.";
+ return false;
+ }
+ if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+ LOG(ERROR) << "Cannot unmap " << partition_name << " before resizing.";
+ return false;
+ }
+ if (!params.builder->ResizePartition(partition, size.value())) {
+ LOG(ERROR) << "Failed to resize partition " << partition_name << " to size " << *size << ".";
+ return false;
+ }
+ return true;
+}
+
+bool PerformOpRemove(const OpParameters& params) {
+ if (!params.ExpectArgSize(1)) return false;
+ const auto& partition_name = params.arg(0);
+
+ if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+ LOG(ERROR) << "Cannot unmap " << partition_name << " before removing.";
+ return false;
+ }
+ params.builder->RemovePartition(partition_name);
+ return true;
+}
+
+bool PerformOpAdd(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& partition_name = params.arg(0);
+ const auto& group_name = params.arg(1);
+
+ if (params.builder->AddPartition(partition_name, group_name, LP_PARTITION_ATTR_READONLY) ==
+ nullptr) {
+ LOG(ERROR) << "Failed to add partition " << partition_name << " to group " << group_name << ".";
+ return false;
+ }
+ return true;
+}
+
+bool PerformOpMove(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& partition_name = params.arg(0);
+ const auto& new_group = params.arg(1);
+
+ auto partition = params.builder->FindPartition(partition_name);
+ if (partition == nullptr) {
+ LOG(ERROR) << "Cannot move partition " << partition_name << " to group " << new_group
+ << " because it is not found.";
+ return false;
+ }
+
+ auto old_group = partition->group_name();
+ if (old_group != new_group) {
+ if (!params.builder->ChangePartitionGroup(partition, new_group)) {
+ LOG(ERROR) << "Cannot move partition " << partition_name << " from group " << old_group
+ << " to group " << new_group << ".";
+ return false;
+ }
+ }
+ return true;
+}
+
+bool PerformOpAddGroup(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& group_name = params.arg(0);
+ auto maximum_size = params.uint_arg(1, "maximum_size");
+ if (!maximum_size.has_value()) return false;
+
+ auto group = params.builder->FindGroup(group_name);
+ if (group != nullptr) {
+ LOG(ERROR) << "Cannot add group " << group_name << " because it already exists.";
+ return false;
+ }
+
+ if (maximum_size.value() == 0) {
+ LOG(WARNING) << "Adding group " << group_name << " with no size limits.";
+ }
+
+ if (!params.builder->AddGroup(group_name, maximum_size.value())) {
+ LOG(ERROR) << "Failed to add group " << group_name << " with maximum size "
+ << maximum_size.value() << ".";
+ return false;
+ }
+ return true;
+}
+
+bool PerformOpResizeGroup(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& group_name = params.arg(0);
+ auto new_size = params.uint_arg(1, "maximum_size");
+ if (!new_size.has_value()) return false;
+
+ auto group = params.builder->FindGroup(group_name);
+ if (group == nullptr) {
+ LOG(ERROR) << "Cannot resize group " << group_name << " because it is not found.";
+ return false;
+ }
+
+ auto old_size = group->maximum_size();
+ if (old_size != new_size.value()) {
+ if (!params.builder->ChangeGroupSize(group_name, new_size.value())) {
+ LOG(ERROR) << "Cannot resize group " << group_name << " from " << old_size << " to "
+ << new_size.value() << ".";
+ return false;
+ }
+ }
+ return true;
+}
+
+std::vector<std::string> ListPartitionNamesInGroup(MetadataBuilder* builder,
+ const std::string& group_name) {
+ auto partitions = builder->ListPartitionsInGroup(group_name);
+ std::vector<std::string> partition_names;
+ std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names),
+ [](Partition* partition) { return partition->name(); });
+ return partition_names;
+}
+
+bool PerformOpRemoveGroup(const OpParameters& params) {
+ if (!params.ExpectArgSize(1)) return false;
+ const auto& group_name = params.arg(0);
+
+ auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
+ if (!partition_names.empty()) {
+ LOG(ERROR) << "Cannot remove group " << group_name << " because it still contains partitions ["
+ << android::base::Join(partition_names, ", ") << "]";
+ return false;
+ }
+ params.builder->RemoveGroupAndPartitions(group_name);
+ return true;
+}
+
+bool PerformOpRemoveAllGroups(const OpParameters& params) {
+ if (!params.ExpectArgSize(0)) return false;
+
+ auto group_names = params.builder->ListGroups();
+ for (const auto& group_name : group_names) {
+ auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
+ for (const auto& partition_name : partition_names) {
+ if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+ LOG(ERROR) << "Cannot unmap " << partition_name << " before removing group " << group_name
+ << ".";
+ return false;
+ }
+ }
+ params.builder->RemoveGroupAndPartitions(group_name);
+ }
+ return true;
+}
+
+} // namespace
+
+Value* UpdateDynamicPartitionsFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() != 1) {
+ ErrorAbort(state, kArgsParsingFailure, "%s expects 1 arguments, got %zu", name, argv.size());
+ return StringValue("");
+ }
+ std::vector<std::unique_ptr<Value>> args;
+ if (!ReadValueArgs(state, argv, &args)) {
+ return nullptr;
+ }
+ const std::unique_ptr<Value>& op_list_value = args[0];
+ if (op_list_value->type != Value::Type::BLOB) {
+ ErrorAbort(state, kArgsParsingFailure, "op_list argument to %s must be blob", name);
+ return StringValue("");
+ }
+
+ std::string updated_marker = Paths::Get().stash_directory_base() + kMetadataUpdatedMarker;
+ if (state->is_retry) {
+ struct stat sb;
+ int result = stat(updated_marker.c_str(), &sb);
+ if (result == 0) {
+ LOG(INFO) << "Skipping already updated dynamic partition metadata based on marker";
+ return StringValue("t");
+ }
+ } else {
+ // Delete the obsolete marker if any.
+ std::string err;
+ if (!android::base::RemoveFileIfExists(updated_marker, &err)) {
+ LOG(ERROR) << "Failed to remove dynamic partition metadata updated marker " << updated_marker
+ << ": " << err;
+ return StringValue("");
+ }
+ }
+
+ auto super_device = GetSuperDevice();
+ auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0);
+ if (builder == nullptr) {
+ LOG(ERROR) << "Failed to load dynamic partition metadata.";
+ return StringValue("");
+ }
+
+ static const OpMap op_map{
+ // clang-format off
+ {"resize", PerformOpResize},
+ {"remove", PerformOpRemove},
+ {"add", PerformOpAdd},
+ {"move", PerformOpMove},
+ {"add_group", PerformOpAddGroup},
+ {"resize_group", PerformOpResizeGroup},
+ {"remove_group", PerformOpRemoveGroup},
+ {"remove_all_groups", PerformOpRemoveAllGroups},
+ // clang-format on
+ };
+
+ std::vector<std::string> lines = android::base::Split(op_list_value->data, "\n");
+ for (const auto& line : lines) {
+ auto comment_idx = line.find('#');
+ auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx);
+ op_and_args = android::base::Trim(op_and_args);
+ if (op_and_args.empty()) continue;
+
+ auto tokens = android::base::Split(op_and_args, " ");
+ const auto& op = tokens[0];
+ auto it = op_map.find(op);
+ if (it == op_map.end()) {
+ LOG(ERROR) << "Unknown operation in op_list: " << op;
+ return StringValue("");
+ }
+ OpParameters params;
+ params.tokens = tokens;
+ params.builder = builder.get();
+ if (!it->second(params)) {
+ return StringValue("");
+ }
+ }
+
+ auto metadata = builder->Export();
+ if (metadata == nullptr) {
+ LOG(ERROR) << "Failed to export metadata.";
+ return StringValue("");
+ }
+
+ if (!UpdatePartitionTable(super_device, *metadata, 0)) {
+ LOG(ERROR) << "Failed to write metadata.";
+ return StringValue("");
+ }
+
+ if (!SetUpdatedMarker(updated_marker)) {
+ LOG(ERROR) << "Failed to set metadata updated marker.";
+ return StringValue("");
+ }
+
+ return StringValue("t");
+}
+
+void RegisterDynamicPartitionsFunctions() {
+ RegisterFunction("unmap_partition", UnmapPartitionFn);
+ RegisterFunction("map_partition", MapPartitionFn);
+ RegisterFunction("update_dynamic_partitions", UpdateDynamicPartitionsFn);
+}
diff --git a/updater/include/private/utils.h b/updater/include/private/utils.h
new file mode 100644
index 000000000..33cf6155d
--- /dev/null
+++ b/updater/include/private/utils.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <string>
+
+bool SetUpdatedMarker(const std::string& marker);
diff --git a/updater/include/updater/dynamic_partitions.h b/updater/include/updater/dynamic_partitions.h
new file mode 100644
index 000000000..31cf859c6
--- /dev/null
+++ b/updater/include/updater/dynamic_partitions.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+void RegisterDynamicPartitionsFunctions();
diff --git a/updater/install.cpp b/updater/install.cpp
index deb7a2b02..20a204a83 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -393,17 +393,20 @@ Value* UnmountFn(const char* name, State* state, const std::vector<std::unique_p
return StringValue(mount_point);
}
-static int exec_cmd(const char* path, char* const argv[]) {
+static int exec_cmd(const std::vector<std::string>& args) {
+ CHECK(!args.empty());
+ auto argv = StringVectorToNullTerminatedArray(args);
+
pid_t child;
if ((child = vfork()) == 0) {
- execv(path, argv);
+ execv(argv[0], argv.data());
_exit(EXIT_FAILURE);
}
int status;
waitpid(child, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
- LOG(ERROR) << path << " failed with status " << WEXITSTATUS(status);
+ LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status);
}
return WEXITSTATUS(status);
}
@@ -453,66 +456,53 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt
}
if (fs_type == "ext4") {
- const char* mke2fs_argv[] = { "/system/bin/mke2fs", "-t", "ext4", "-b", "4096",
- location.c_str(), nullptr, nullptr };
- std::string size_str;
+ std::vector<std::string> mke2fs_args = {
+ "/system/bin/mke2fs", "-t", "ext4", "-b", "4096", location
+ };
if (size != 0) {
- size_str = std::to_string(size / 4096LL);
- mke2fs_argv[6] = size_str.c_str();
+ mke2fs_args.push_back(std::to_string(size / 4096LL));
}
- int status = exec_cmd(mke2fs_argv[0], const_cast<char**>(mke2fs_argv));
- if (status != 0) {
+ if (auto status = exec_cmd(mke2fs_args); status != 0) {
LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location;
return StringValue("");
}
- const char* e2fsdroid_argv[] = { "/system/bin/e2fsdroid", "-e", "-a", mount_point.c_str(),
- location.c_str(), nullptr };
- status = exec_cmd(e2fsdroid_argv[0], const_cast<char**>(e2fsdroid_argv));
- if (status != 0) {
+ if (auto status = exec_cmd({ "/system/bin/e2fsdroid", "-e", "-a", mount_point, location });
+ status != 0) {
LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location;
return StringValue("");
}
return StringValue(location);
- } else if (fs_type == "f2fs") {
+ }
+
+ if (fs_type == "f2fs") {
if (size < 0) {
LOG(ERROR) << name << ": fs_size can't be negative for f2fs: " << fs_size;
return StringValue("");
}
- std::string num_sectors = std::to_string(size / 512);
-
- const char* f2fs_path = "/sbin/mkfs.f2fs";
- const char* f2fs_argv[] = { "mkfs.f2fs",
- "-d1",
- "-f",
- "-O", "encrypt",
- "-O", "quota",
- "-O", "verity",
- "-w", "512",
- location.c_str(),
- (size < 512) ? nullptr : num_sectors.c_str(),
- nullptr };
- int status = exec_cmd(f2fs_path, const_cast<char**>(f2fs_argv));
- if (status != 0) {
- LOG(ERROR) << name << ": mkfs.f2fs failed (" << status << ") on " << location;
+ std::vector<std::string> f2fs_args = {
+ "/system/bin/make_f2fs", "-g", "android", "-w", "512", location
+ };
+ if (size >= 512) {
+ f2fs_args.push_back(std::to_string(size / 512));
+ }
+ if (auto status = exec_cmd(f2fs_args); status != 0) {
+ LOG(ERROR) << name << ": make_f2fs failed (" << status << ") on " << location;
return StringValue("");
}
- const char* sload_argv[] = { "/sbin/sload.f2fs", "-t", mount_point.c_str(), location.c_str(),
- nullptr };
- status = exec_cmd(sload_argv[0], const_cast<char**>(sload_argv));
- if (status != 0) {
- LOG(ERROR) << name << ": sload.f2fs failed (" << status << ") on " << location;
+ if (auto status = exec_cmd({ "/system/bin/sload_f2fs", "-t", mount_point, location });
+ status != 0) {
+ LOG(ERROR) << name << ": sload_f2fs failed (" << status << ") on " << location;
return StringValue("");
}
return StringValue(location);
- } else {
- LOG(ERROR) << name << ": unsupported fs_type \"" << fs_type << "\" partition_type \""
- << partition_type << "\"";
}
+ LOG(ERROR) << name << ": unsupported fs_type \"" << fs_type << "\" partition_type \""
+ << partition_type << "\"";
return nullptr;
}
@@ -679,17 +669,12 @@ Value* RunProgramFn(const char* name, State* state, const std::vector<std::uniqu
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
}
- char* args2[argv.size() + 1];
- for (size_t i = 0; i < argv.size(); i++) {
- args2[i] = &args[i][0];
- }
- args2[argv.size()] = nullptr;
-
- LOG(INFO) << "about to run program [" << args2[0] << "] with " << argv.size() << " args";
+ auto exec_args = StringVectorToNullTerminatedArray(args);
+ LOG(INFO) << "about to run program [" << exec_args[0] << "] with " << argv.size() << " args";
pid_t child = fork();
if (child == 0) {
- execv(args2[0], args2);
+ execv(exec_args[0], exec_args.data());
PLOG(ERROR) << "run_program: execv failed";
_exit(EXIT_FAILURE);
}
@@ -913,20 +898,12 @@ Value* Tune2FsFn(const char* name, State* state, const std::vector<std::unique_p
return ErrorAbort(state, kArgsParsingFailure, "%s() could not read args", name);
}
- char* args2[argv.size() + 1];
- // Tune2fs expects the program name as its args[0]
- args2[0] = const_cast<char*>(name);
- if (args2[0] == nullptr) {
- return nullptr;
- }
- for (size_t i = 0; i < argv.size(); ++i) {
- args2[i + 1] = &args[i][0];
- }
+ // tune2fs expects the program name as its first arg.
+ args.insert(args.begin(), "tune2fs");
+ auto tune2fs_args = StringVectorToNullTerminatedArray(args);
- // tune2fs changes the file system parameters on an ext2 file system; it
- // returns 0 on success.
- int result = tune2fs_main(argv.size() + 1, args2);
- if (result != 0) {
+ // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success.
+ if (auto result = tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data()); result != 0) {
return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result);
}
return StringValue("t");
diff --git a/updater/updater.cpp b/updater/updater.cpp
index e87c57a5c..7b5a3f938 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -35,6 +35,7 @@
#include "otautil/error_code.h"
#include "otautil/sysutil.h"
#include "updater/blockimg.h"
+#include "updater/dynamic_partitions.h"
#include "updater/install.h"
// Generated by the makefile, this function defines the
@@ -125,6 +126,7 @@ int main(int argc, char** argv) {
RegisterBuiltins();
RegisterInstallFunctions();
RegisterBlockImageFunctions();
+ RegisterDynamicPartitionsFunctions();
RegisterDeviceExtensions();
// Parse the script.
diff --git a/updater_sample/AndroidManifest.xml b/updater_sample/AndroidManifest.xml
index 18d8425e1..0a2511617 100644
--- a/updater_sample/AndroidManifest.xml
+++ b/updater_sample/AndroidManifest.xml
@@ -33,7 +33,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <service android:name=".services.PrepareStreamingService"/>
+ <service android:name=".services.PrepareUpdateService"/>
</application>
</manifest>
diff --git a/updater_sample/README.md b/updater_sample/README.md
index f9c3fb8ec..2070ebc21 100644
--- a/updater_sample/README.md
+++ b/updater_sample/README.md
@@ -191,7 +191,7 @@ privileged system app, so it's granted the required permissions to access
</privapp-permissions>
```
to `frameworks/base/data/etc/privapp-permissions-platform.xml`
-5. Build sample app `mmma -j bootable/recovery/updater_sample`.
+5. Build sample app `make -j SystemUpdaterSample`.
6. Build Android `make -j`
7. [Flash the device](https://source.android.com/setup/build/running)
8. Add update config files; look above at `## Update Config file`;
@@ -225,7 +225,9 @@ privileged system app, so it's granted the required permissions to access
## Running tests
-1. Build `mmma bootable/recovery/updater_sample/`
+The commands are expected to be run from `$ANDROID_BUILD_TOP`.
+
+1. Build `make -j SystemUpdaterSample` and `make -j SystemUpdaterSampleTests`.
2. Install app
`adb install $OUT/system/priv-app/SystemUpdaterSample/SystemUpdaterSample.apk`
3. Install tests
@@ -235,8 +237,8 @@ privileged system app, so it's granted the required permissions to access
5. Run a test file
```
adb shell am instrument \
- -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner \
- -c com.example.android.systemupdatersample.util.PayloadSpecsTest
+ -w -e class com.example.android.systemupdatersample.UpdateManagerTest#applyUpdate_appliesPayloadToUpdateEngine \
+ com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner
```
diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
index 12a8f3f5f..c02e60846 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
@@ -17,19 +17,18 @@
package com.example.android.systemupdatersample;
import android.content.Context;
+import android.os.Handler;
import android.os.UpdateEngine;
import android.os.UpdateEngineCallback;
import android.util.Log;
-import com.example.android.systemupdatersample.services.PrepareStreamingService;
-import com.example.android.systemupdatersample.util.PayloadSpecs;
+import com.example.android.systemupdatersample.services.PrepareUpdateService;
import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
import com.example.android.systemupdatersample.util.UpdateEngineProperties;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.AtomicDouble;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -50,11 +49,10 @@ public class UpdateManager {
private static final String TAG = "UpdateManager";
/** HTTP Header: User-Agent; it will be sent to the server when streaming the payload. */
- private static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
+ static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
+ "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36";
private final UpdateEngine mUpdateEngine;
- private final PayloadSpecs mPayloadSpecs;
private AtomicInteger mUpdateEngineStatus =
new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE);
@@ -84,9 +82,15 @@ public class UpdateManager {
private final UpdateManager.UpdateEngineCallbackImpl
mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl();
- public UpdateManager(UpdateEngine updateEngine, PayloadSpecs payloadSpecs) {
+ private final Handler mHandler;
+
+ /**
+ * @param updateEngine UpdateEngine instance.
+ * @param handler Handler for {@link PrepareUpdateService} intent service.
+ */
+ public UpdateManager(UpdateEngine updateEngine, Handler handler) {
this.mUpdateEngine = updateEngine;
- this.mPayloadSpecs = payloadSpecs;
+ this.mHandler = handler;
}
/**
@@ -293,45 +297,17 @@ public class UpdateManager {
mManualSwitchSlotRequired.set(false);
}
- if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
- applyAbNonStreamingUpdate(config);
- } else {
- applyAbStreamingUpdate(context, config);
- }
- }
-
- private void applyAbNonStreamingUpdate(UpdateConfig config)
- throws UpdaterState.InvalidTransitionException {
- UpdateData.Builder builder = UpdateData.builder()
- .setExtraProperties(prepareExtraProperties(config));
-
- try {
- builder.setPayload(mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile()));
- } catch (IOException e) {
- Log.e(TAG, "Error creating payload spec", e);
- setUpdaterState(UpdaterState.ERROR);
- return;
- }
- updateEngineApplyPayload(builder.build());
- }
-
- private void applyAbStreamingUpdate(Context context, UpdateConfig config) {
- UpdateData.Builder builder = UpdateData.builder()
- .setExtraProperties(prepareExtraProperties(config));
-
- Log.d(TAG, "Starting PrepareStreamingService");
- PrepareStreamingService.startService(context, config, (code, payloadSpec) -> {
- if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) {
- builder.setPayload(payloadSpec);
- builder.addExtraProperty("USER_AGENT=" + HTTP_USER_AGENT);
- config.getAbConfig()
- .getAuthorization()
- .ifPresent(s -> builder.addExtraProperty("AUTHORIZATION=" + s));
- updateEngineApplyPayload(builder.build());
- } else {
- Log.e(TAG, "PrepareStreamingService failed, result code is " + code);
+ Log.d(TAG, "Starting PrepareUpdateService");
+ PrepareUpdateService.startService(context, config, mHandler, (code, payloadSpec) -> {
+ if (code != PrepareUpdateService.RESULT_CODE_SUCCESS) {
+ Log.e(TAG, "PrepareUpdateService failed, result code is " + code);
setUpdaterStateSilent(UpdaterState.ERROR);
+ return;
}
+ updateEngineApplyPayload(UpdateData.builder()
+ .setExtraProperties(prepareExtraProperties(config))
+ .setPayload(payloadSpec)
+ .build());
});
}
@@ -343,6 +319,12 @@ public class UpdateManager {
// User will enable it manually by clicking "Switch Slot" button on the screen.
extraProperties.add(UpdateEngineProperties.PROPERTY_DISABLE_SWITCH_SLOT_ON_REBOOT);
}
+ if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_STREAMING) {
+ extraProperties.add("USER_AGENT=" + HTTP_USER_AGENT);
+ config.getAbConfig()
+ .getAuthorization()
+ .ifPresent(s -> extraProperties.add("AUTHORIZATION=" + s));
+ }
return extraProperties;
}
@@ -497,14 +479,14 @@ public class UpdateManager {
* system/update_engine/binder_service_android.cc in
* function BinderUpdateEngineAndroidService::bind).
*
- * @param status one of {@link UpdateEngine.UpdateStatusConstants}.
+ * @param status one of {@link UpdateEngine.UpdateStatusConstants}.
* @param progress a number from 0.0 to 1.0.
*/
private void onStatusUpdate(int status, float progress) {
Log.d(TAG, String.format(
- "onStatusUpdate invoked, status=%s, progress=%.2f",
- status,
- progress));
+ "onStatusUpdate invoked, status=%s, progress=%.2f",
+ status,
+ progress));
int previousStatus = mUpdateEngineStatus.get();
mUpdateEngineStatus.set(status);
@@ -555,7 +537,6 @@ public class UpdateManager {
}
/**
- *
* Contains update data - PayloadSpec and extra properties list.
*
* <p>{@code mPayload} contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}.
diff --git a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java
index 931404857..29eb13da7 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java
@@ -28,6 +28,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.RecoverySystem;
import android.os.ResultReceiver;
+import android.os.UpdateEngine;
import android.util.Log;
import com.example.android.systemupdatersample.PayloadSpec;
@@ -41,7 +42,9 @@ import com.google.common.collect.ImmutableSet;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
import java.util.Optional;
/**
@@ -49,10 +52,10 @@ import java.util.Optional;
* without downloading the whole package. And it constructs {@link PayloadSpec}.
* All this work required to install streaming A/B updates.
*
- * PrepareStreamingService runs on it's own thread. It will notify activity
+ * PrepareUpdateService runs on it's own thread. It will notify activity
* using interface {@link UpdateResultCallback} when update is ready to install.
*/
-public class PrepareStreamingService extends IntentService {
+public class PrepareUpdateService extends IntentService {
/**
* UpdateResultCallback result codes.
@@ -61,62 +64,63 @@ public class PrepareStreamingService extends IntentService {
public static final int RESULT_CODE_ERROR = 1;
/**
- * This interface is used to send results from {@link PrepareStreamingService} to
+ * Extra params that will be sent to IntentService.
+ */
+ public static final String EXTRA_PARAM_CONFIG = "config";
+ public static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver";
+
+ /**
+ * This interface is used to send results from {@link PrepareUpdateService} to
* {@code MainActivity}.
*/
public interface UpdateResultCallback {
-
/**
* Invoked when files are downloaded and payload spec is constructed.
*
- * @param resultCode result code, values are defined in {@link PrepareStreamingService}
+ * @param resultCode result code, values are defined in {@link PrepareUpdateService}
* @param payloadSpec prepared payload spec for streaming update
*/
void onReceiveResult(int resultCode, PayloadSpec payloadSpec);
}
/**
- * Starts PrepareStreamingService.
+ * Starts PrepareUpdateService.
*
- * @param context application context
- * @param config update config
+ * @param context application context
+ * @param config update config
* @param resultCallback callback that will be called when the update is ready to be installed
*/
public static void startService(Context context,
UpdateConfig config,
+ Handler handler,
UpdateResultCallback resultCallback) {
- Log.d(TAG, "Starting PrepareStreamingService");
- ResultReceiver receiver = new CallbackResultReceiver(new Handler(), resultCallback);
- Intent intent = new Intent(context, PrepareStreamingService.class);
+ Log.d(TAG, "Starting PrepareUpdateService");
+ ResultReceiver receiver = new CallbackResultReceiver(handler, resultCallback);
+ Intent intent = new Intent(context, PrepareUpdateService.class);
intent.putExtra(EXTRA_PARAM_CONFIG, config);
intent.putExtra(EXTRA_PARAM_RESULT_RECEIVER, receiver);
context.startService(intent);
}
- public PrepareStreamingService() {
+ public PrepareUpdateService() {
super(TAG);
}
- private static final String TAG = "PrepareStreamingService";
-
- /**
- * Extra params that will be sent from Activity to IntentService.
- */
- private static final String EXTRA_PARAM_CONFIG = "config";
- private static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver";
+ private static final String TAG = "PrepareUpdateService";
/**
* The files that should be downloaded before streaming.
*/
private static final ImmutableSet<String> PRE_STREAMING_FILES_SET =
ImmutableSet.of(
- PackageFiles.CARE_MAP_FILE_NAME,
- PackageFiles.COMPATIBILITY_ZIP_FILE_NAME,
- PackageFiles.METADATA_FILE_NAME,
- PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME
+ PackageFiles.CARE_MAP_FILE_NAME,
+ PackageFiles.COMPATIBILITY_ZIP_FILE_NAME,
+ PackageFiles.METADATA_FILE_NAME,
+ PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME
);
private final PayloadSpecs mPayloadSpecs = new PayloadSpecs();
+ private final UpdateEngine mUpdateEngine = new UpdateEngine();
@Override
protected void onHandleIntent(Intent intent) {
@@ -142,6 +146,17 @@ public class PrepareStreamingService extends IntentService {
private PayloadSpec execute(UpdateConfig config)
throws IOException, PreparationFailedException {
+ if (config.getAbConfig().getVerifyPayloadMetadata()) {
+ Log.i(TAG, "Verifying payload metadata with UpdateEngine.");
+ if (!verifyPayloadMetadata(config)) {
+ throw new PreparationFailedException("Payload metadata is not compatible");
+ }
+ }
+
+ if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
+ return mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile());
+ }
+
downloadPreStreamingFiles(config, OTA_PACKAGE_DIR);
Optional<UpdateConfig.PackageFile> payloadBinary =
@@ -173,9 +188,52 @@ public class PrepareStreamingService extends IntentService {
}
/**
+ * Downloads only payload_metadata.bin and verifies with
+ * {@link UpdateEngine#verifyPayloadMetadata}.
+ * Returns {@code true} if the payload is verified or the result is unknown because of
+ * exception from UpdateEngine.
+ * By downloading only small portion of the package, it allows to verify if UpdateEngine
+ * will install the update.
+ */
+ private boolean verifyPayloadMetadata(UpdateConfig config) {
+ Optional<UpdateConfig.PackageFile> metadataPackageFile =
+ Arrays.stream(config.getAbConfig().getPropertyFiles())
+ .filter(p -> p.getFilename().equals(
+ PackageFiles.PAYLOAD_METADATA_FILE_NAME))
+ .findFirst();
+ if (!metadataPackageFile.isPresent()) {
+ Log.w(TAG, String.format("ab_config.property_files doesn't contain %s",
+ PackageFiles.PAYLOAD_METADATA_FILE_NAME));
+ return true;
+ }
+ Path metadataPath = Paths.get(OTA_PACKAGE_DIR, PackageFiles.PAYLOAD_METADATA_FILE_NAME);
+ try {
+ Files.deleteIfExists(metadataPath);
+ FileDownloader d = new FileDownloader(
+ config.getUrl(),
+ metadataPackageFile.get().getOffset(),
+ metadataPackageFile.get().getSize(),
+ metadataPath.toFile());
+ d.download();
+ } catch (IOException e) {
+ Log.w(TAG, String.format("Downloading %s from %s failed",
+ PackageFiles.PAYLOAD_METADATA_FILE_NAME,
+ config.getUrl()), e);
+ return true;
+ }
+ try {
+ return mUpdateEngine.verifyPayloadMetadata(metadataPath.toAbsolutePath().toString());
+ } catch (Exception e) {
+ Log.w(TAG, "UpdateEngine#verifyPayloadMetadata failed", e);
+ return true;
+ }
+ }
+
+ /**
* Downloads files defined in {@link UpdateConfig#getAbConfig()}
* and exists in {@code PRE_STREAMING_FILES_SET}, and put them
* in directory {@code dir}.
+ *
* @throws IOException when can't download a file
*/
private void downloadPreStreamingFiles(UpdateConfig config, String dir)
@@ -212,7 +270,7 @@ public class PrepareStreamingService extends IntentService {
}
/**
- * Used by {@link PrepareStreamingService} to pass {@link PayloadSpec}
+ * Used by {@link PrepareUpdateService} to pass {@link PayloadSpec}
* to {@link UpdateResultCallback#onReceiveResult}.
*/
private static class CallbackResultReceiver extends ResultReceiver {
diff --git a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java
index fc9fddd70..6d1e4c35a 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java
@@ -21,6 +21,7 @@ import android.app.AlertDialog;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.os.UpdateEngine;
import android.util.Log;
import android.view.View;
@@ -34,7 +35,6 @@ import com.example.android.systemupdatersample.R;
import com.example.android.systemupdatersample.UpdateConfig;
import com.example.android.systemupdatersample.UpdateManager;
import com.example.android.systemupdatersample.UpdaterState;
-import com.example.android.systemupdatersample.util.PayloadSpecs;
import com.example.android.systemupdatersample.util.UpdateConfigs;
import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
import com.example.android.systemupdatersample.util.UpdateEngineStatuses;
@@ -67,7 +67,7 @@ public class MainActivity extends Activity {
private List<UpdateConfig> mConfigs;
private final UpdateManager mUpdateManager =
- new UpdateManager(new UpdateEngine(), new PayloadSpecs());
+ new UpdateManager(new UpdateEngine(), new Handler());
@Override
protected void onCreate(Bundle savedInstanceState) {
diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java
index ddd0919b8..0f9083d27 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java
@@ -30,7 +30,7 @@ import java.net.URLConnection;
* Downloads chunk of a file from given url using {@code offset} and {@code size},
* and saves to a given location.
*
- * In real-life application this helper class should download from HTTP Server,
+ * In a real-life application this helper class should download from HTTP Server,
* but in this sample app it will only download from a local file.
*/
public final class FileDownloader {
diff --git a/updater_sample/tests/Android.bp b/updater_sample/tests/Android.bp
index c2783ef88..7867770a0 100644
--- a/updater_sample/tests/Android.bp
+++ b/updater_sample/tests/Android.bp
@@ -28,7 +28,7 @@ android_test {
"guava",
],
- instrumentation_for: "com.example.android.systemupdatersample",
+ instrumentation_for: "SystemUpdaterSample",
optimize: {
enabled: false,
diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java
index e05ad290c..5ad16d477 100644
--- a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java
+++ b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java
@@ -18,20 +18,25 @@ package com.example.android.systemupdatersample;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
import android.os.UpdateEngine;
import android.os.UpdateEngineCallback;
import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import com.example.android.systemupdatersample.services.PrepareUpdateService;
import com.example.android.systemupdatersample.tests.R;
-import com.example.android.systemupdatersample.util.PayloadSpecs;
import com.google.common.collect.ImmutableList;
import com.google.common.io.CharStreams;
@@ -43,7 +48,6 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -60,49 +64,39 @@ public class UpdateManagerTest {
@Mock
private UpdateEngine mUpdateEngine;
@Mock
- private PayloadSpecs mPayloadSpecs;
+ private Context mMockContext;
private UpdateManager mSubject;
- private Context mContext;
- private UpdateConfig mNonStreamingUpdate003;
+ private Context mTestContext;
+ private UpdateConfig mStreamingUpdate002;
@Before
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getContext();
- mSubject = new UpdateManager(mUpdateEngine, mPayloadSpecs);
- mNonStreamingUpdate003 =
- UpdateConfig.fromJson(readResource(R.raw.update_config_003_nonstream));
+ mTestContext = InstrumentationRegistry.getContext();
+ mSubject = new UpdateManager(mUpdateEngine, null);
+ mStreamingUpdate002 =
+ UpdateConfig.fromJson(readResource(R.raw.update_config_002_stream));
}
@Test
public void applyUpdate_appliesPayloadToUpdateEngine() throws Exception {
- PayloadSpec payload = buildMockPayloadSpec();
- when(mPayloadSpecs.forNonStreaming(any(File.class))).thenReturn(payload);
- when(mUpdateEngine.bind(any(UpdateEngineCallback.class))).thenAnswer(answer -> {
- // When UpdateManager is bound to update_engine, it passes
- // UpdateEngineCallback as a callback to update_engine.
- UpdateEngineCallback callback = answer.getArgument(0);
- callback.onStatusUpdate(
- UpdateEngine.UpdateStatusConstants.IDLE,
- /*engineProgress*/ 0.0f);
- return null;
- });
-
- mSubject.bind();
- mSubject.applyUpdate(null, mNonStreamingUpdate003);
+ mockContextStartServiceAnswer(buildMockPayloadSpec());
+ mSubject.applyUpdate(mMockContext, mStreamingUpdate002);
verify(mUpdateEngine).applyPayload(
"file://blah",
120,
340,
- new String[] {
- "SWITCH_SLOT_ON_REBOOT=0" // ab_config.force_switch_slot = false
+ new String[]{
+ "SWITCH_SLOT_ON_REBOOT=0", // ab_config.force_switch_slot = false
+ "USER_AGENT=" + UpdateManager.HTTP_USER_AGENT
});
}
@Test
- public void stateIsRunningAndEngineStatusIsIdle_reApplyLastUpdate() throws Exception {
- PayloadSpec payload = buildMockPayloadSpec();
- when(mPayloadSpecs.forNonStreaming(any(File.class))).thenReturn(payload);
+ @UiThreadTest
+ public void stateIsRunningAndEngineStatusIsIdle_reApplyLastUpdate() throws Throwable {
+ mockContextStartServiceAnswer(buildMockPayloadSpec());
+ // UpdateEngine always returns IDLE status.
when(mUpdateEngine.bind(any(UpdateEngineCallback.class))).thenAnswer(answer -> {
// When UpdateManager is bound to update_engine, it passes
// UpdateEngineCallback as a callback to update_engine.
@@ -114,21 +108,36 @@ public class UpdateManagerTest {
});
mSubject.bind();
- mSubject.applyUpdate(null, mNonStreamingUpdate003);
+ mSubject.applyUpdate(mMockContext, mStreamingUpdate002);
mSubject.unbind();
mSubject.bind(); // re-bind - now it should re-apply last update
assertEquals(mSubject.getUpdaterState(), UpdaterState.RUNNING);
- // it should be called 2 times
verify(mUpdateEngine, times(2)).applyPayload(
"file://blah",
120,
340,
- new String[] {
- "SWITCH_SLOT_ON_REBOOT=0" // ab_config.force_switch_slot = false
+ new String[]{
+ "SWITCH_SLOT_ON_REBOOT=0", // ab_config.force_switch_slot = false
+ "USER_AGENT=" + UpdateManager.HTTP_USER_AGENT
});
}
+ private void mockContextStartServiceAnswer(PayloadSpec payloadSpec) {
+ doAnswer(args -> {
+ Intent intent = args.getArgument(0);
+ ResultReceiver resultReceiver = intent.getParcelableExtra(
+ PrepareUpdateService.EXTRA_PARAM_RESULT_RECEIVER);
+ Bundle b = new Bundle();
+ b.putSerializable(
+ /* PrepareUpdateService.CallbackResultReceiver.BUNDLE_PARAM_PAYLOAD_SPEC */
+ "payload-spec",
+ payloadSpec);
+ resultReceiver.send(PrepareUpdateService.RESULT_CODE_SUCCESS, b);
+ return null;
+ }).when(mMockContext).startService(any(Intent.class));
+ }
+
private PayloadSpec buildMockPayloadSpec() {
PayloadSpec payload = mock(PayloadSpec.class);
when(payload.getUrl()).thenReturn("file://blah");
@@ -140,7 +149,7 @@ public class UpdateManagerTest {
private String readResource(int id) throws IOException {
return CharStreams.toString(new InputStreamReader(
- mContext.getResources().openRawResource(id)));
+ mTestContext.getResources().openRawResource(id)));
}
}