summaryrefslogtreecommitdiffstats
path: root/updater
diff options
context:
space:
mode:
authorTao Bao <tbao@google.com>2018-06-15 06:57:43 +0200
committerTao Bao <tbao@google.com>2018-06-25 22:39:29 +0200
commit6a7e4af7c640cd8b056cdc703e84b9d122ce3703 (patch)
treed5c156ace4253fe1cf4292701a7e7e783873ed5c /updater
parentMerge "applypatch: {Load,Save}FileContents and ParseSha1 take std::string." (diff)
downloadandroid_bootable_recovery-6a7e4af7c640cd8b056cdc703e84b9d122ce3703.tar
android_bootable_recovery-6a7e4af7c640cd8b056cdc703e84b9d122ce3703.tar.gz
android_bootable_recovery-6a7e4af7c640cd8b056cdc703e84b9d122ce3703.tar.bz2
android_bootable_recovery-6a7e4af7c640cd8b056cdc703e84b9d122ce3703.tar.lz
android_bootable_recovery-6a7e4af7c640cd8b056cdc703e84b9d122ce3703.tar.xz
android_bootable_recovery-6a7e4af7c640cd8b056cdc703e84b9d122ce3703.tar.zst
android_bootable_recovery-6a7e4af7c640cd8b056cdc703e84b9d122ce3703.zip
Diffstat (limited to 'updater')
-rw-r--r--updater/Android.mk1
-rw-r--r--updater/commands.cpp247
-rw-r--r--updater/include/private/commands.h290
3 files changed, 521 insertions, 17 deletions
diff --git a/updater/Android.mk b/updater/Android.mk
index 46c56f4a0..ac9aecb04 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -35,6 +35,7 @@ updater_common_static_libraries := \
libfec \
libfec_rs \
libfs_mgr \
+ libgtest_prod \
liblog \
libselinux \
libsparse \
diff --git a/updater/commands.cpp b/updater/commands.cpp
index f798c6a73..6d4b5310b 100644
--- a/updater/commands.cpp
+++ b/updater/commands.cpp
@@ -16,28 +16,253 @@
#include "private/commands.h"
+#include <ostream>
#include <string>
+#include <vector>
#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "otautil/rangeset.h"
+
+using namespace std::string_literals;
Command::Type Command::ParseType(const std::string& type_str) {
- if (type_str == "zero") {
- return Type::ZERO;
- } else if (type_str == "new") {
- return Type::NEW;
+ if (type_str == "bsdiff") {
+ return Type::BSDIFF;
} else if (type_str == "erase") {
return Type::ERASE;
- } else if (type_str == "move") {
- return Type::MOVE;
- } else if (type_str == "bsdiff") {
- return Type::BSDIFF;
+ } else if (type_str == "free") {
+ return Type::FREE;
} else if (type_str == "imgdiff") {
return Type::IMGDIFF;
+ } else if (type_str == "move") {
+ return Type::MOVE;
+ } else if (type_str == "new") {
+ return Type::NEW;
} else if (type_str == "stash") {
return Type::STASH;
- } else if (type_str == "free") {
- return Type::FREE;
+ } else if (type_str == "zero") {
+ return Type::ZERO;
}
- LOG(ERROR) << "Invalid type: " << type_str;
return Type::LAST;
};
+
+bool Command::ParseTargetInfoAndSourceInfo(const std::vector<std::string>& tokens,
+ const std::string& tgt_hash, TargetInfo* target,
+ const std::string& src_hash, SourceInfo* source,
+ std::string* err) {
+ // We expect the given tokens parameter in one of the following formats.
+ //
+ // <tgt_ranges> <src_block_count> - <[stash_id:location] ...>
+ // (loads data from stashes only)
+ //
+ // <tgt_ranges> <src_block_count> <src_ranges>
+ // (loads data from source image only)
+ //
+ // <tgt_ranges> <src_block_count> <src_ranges> <src_ranges_location> <[stash_id:location] ...>
+ // (loads data from both of source image and stashes)
+
+ // At least it needs to provide three parameters: <tgt_ranges>, <src_block_count> and
+ // "-"/<src_ranges>.
+ if (tokens.size() < 3) {
+ *err = "invalid number of parameters";
+ return false;
+ }
+
+ size_t pos = 0;
+ RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]);
+ if (!tgt_ranges) {
+ *err = "invalid target ranges";
+ return false;
+ }
+ *target = TargetInfo(tgt_hash, tgt_ranges);
+
+ // <src_block_count>
+ const std::string& token = tokens[pos++];
+ size_t src_blocks;
+ if (!android::base::ParseUint(token, &src_blocks)) {
+ *err = "invalid src_block_count \""s + token + "\"";
+ return false;
+ }
+
+ RangeSet src_ranges;
+ RangeSet src_ranges_location;
+ // "-" or <src_ranges> [<src_ranges_location>]
+ if (tokens[pos] == "-") {
+ // no source ranges, only stashes
+ pos++;
+ } else {
+ src_ranges = RangeSet::Parse(tokens[pos++]);
+ if (!src_ranges) {
+ *err = "invalid source ranges";
+ return false;
+ }
+
+ if (pos >= tokens.size()) {
+ // No stashes, only source ranges.
+ SourceInfo result(src_hash, src_ranges, {}, {});
+
+ // Sanity check the block count.
+ if (result.blocks() != src_blocks) {
+ *err =
+ android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(),
+ src_ranges.ToString().c_str(), src_blocks);
+ return false;
+ }
+
+ *source = result;
+ return true;
+ }
+
+ src_ranges_location = RangeSet::Parse(tokens[pos++]);
+ if (!src_ranges_location) {
+ *err = "invalid source ranges location";
+ return false;
+ }
+ }
+
+ // <[stash_id:stash_location]>
+ std::vector<StashInfo> stashes;
+ while (pos < tokens.size()) {
+ // Each word is a an index into the stash table, a colon, and then a RangeSet describing where
+ // in the source block that stashed data should go.
+ std::vector<std::string> pairs = android::base::Split(tokens[pos++], ":");
+ if (pairs.size() != 2) {
+ *err = "invalid stash info";
+ return false;
+ }
+ RangeSet stash_location = RangeSet::Parse(pairs[1]);
+ if (!stash_location) {
+ *err = "invalid stash location";
+ return false;
+ }
+ stashes.emplace_back(pairs[0], stash_location);
+ }
+
+ SourceInfo result(src_hash, src_ranges, src_ranges_location, stashes);
+ if (src_blocks != result.blocks()) {
+ *err = android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(),
+ src_ranges.ToString().c_str(), src_blocks);
+ return false;
+ }
+
+ *source = result;
+ return true;
+}
+
+Command Command::Parse(const std::string& line, size_t index, std::string* err) {
+ std::vector<std::string> tokens = android::base::Split(line, " ");
+ size_t pos = 0;
+ // tokens.size() will be 1 at least.
+ Type op = ParseType(tokens[pos++]);
+ if (op == Type::LAST) {
+ *err = "invalid type";
+ return {};
+ }
+
+ PatchInfo patch_info;
+ TargetInfo target_info;
+ SourceInfo source_info;
+ StashInfo stash_info;
+
+ if (op == Type::ZERO || op == Type::NEW || op == Type::ERASE) {
+ // zero/new/erase <rangeset>
+ RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]);
+ if (!tgt_ranges) {
+ return {};
+ }
+ static const std::string kUnknownHash{ "unknown-hash" };
+ target_info = TargetInfo(kUnknownHash, tgt_ranges);
+ } else if (op == Type::STASH) {
+ // stash <stash_id> <src_ranges>
+ if (pos + 2 > tokens.size()) {
+ *err = "missing stash id and/or source ranges";
+ return {};
+ }
+ const std::string& id = tokens[pos++];
+ RangeSet src_ranges = RangeSet::Parse(tokens[pos++]);
+ if (!src_ranges) {
+ *err = "invalid token";
+ return {};
+ }
+ stash_info = StashInfo(id, src_ranges);
+ } else if (op == Type::FREE) {
+ // free <stash_id>
+ if (pos + 1 > tokens.size()) {
+ *err = "missing stash id in free command";
+ return {};
+ }
+ stash_info = StashInfo(tokens[pos++], {});
+ } else if (op == Type::MOVE) {
+ // <hash>
+ if (pos + 1 > tokens.size()) {
+ *err = "missing hash";
+ return {};
+ }
+ std::string hash = tokens[pos++];
+ if (!ParseTargetInfoAndSourceInfo(
+ std::vector<std::string>(tokens.cbegin() + pos, tokens.cend()), hash, &target_info,
+ hash, &source_info, err)) {
+ return {};
+ }
+ } else if (op == Type::BSDIFF || op == Type::IMGDIFF) {
+ // <offset> <length> <srchash> <dsthash>
+ if (pos + 4 > tokens.size()) {
+ *err = "invalid number of tokens";
+ return {};
+ }
+ size_t offset;
+ size_t length;
+ if (!android::base::ParseUint(tokens[pos++], &offset) ||
+ !android::base::ParseUint(tokens[pos++], &length)) {
+ *err = "invalid patch offset/length";
+ return {};
+ }
+ patch_info = PatchInfo(offset, length);
+
+ std::string src_hash = tokens[pos++];
+ std::string dst_hash = tokens[pos++];
+ if (!ParseTargetInfoAndSourceInfo(
+ std::vector<std::string>(tokens.cbegin() + pos, tokens.cend()), dst_hash, &target_info,
+ src_hash, &source_info, err)) {
+ return {};
+ }
+ } else {
+ *err = "invalid op";
+ return {};
+ }
+
+ return Command(op, index, line, patch_info, target_info, source_info, stash_info);
+}
+
+std::ostream& operator<<(std::ostream& os, const Command& command) {
+ os << command.index() << ": " << command.cmdline();
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const TargetInfo& target) {
+ os << target.blocks() << " blocks (" << target.hash_ << "): " << target.ranges_.ToString();
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const StashInfo& stash) {
+ os << stash.blocks() << " blocks (" << stash.id_ << "): " << stash.ranges_.ToString();
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const SourceInfo& source) {
+ os << source.blocks_ << " blocks (" << source.hash_ << "): ";
+ if (source.ranges_) {
+ os << source.ranges_.ToString();
+ if (source.location_) {
+ os << " (location: " << source.location_.ToString() << ")";
+ }
+ }
+ if (!source.stashes_.empty()) {
+ os << " " << source.stashes_.size() << " stash(es)";
+ }
+ return os;
+}
diff --git a/updater/include/private/commands.h b/updater/include/private/commands.h
index b36000072..784892fb5 100644
--- a/updater/include/private/commands.h
+++ b/updater/include/private/commands.h
@@ -16,20 +16,298 @@
#pragma once
+#include <ostream>
#include <string>
+#include <vector>
-struct Command {
+#include <gtest/gtest_prod.h> // FRIEND_TEST
+
+#include "otautil/rangeset.h"
+
+// Represents the target info used in a Command. TargetInfo contains the ranges of the blocks and
+// the expected hash.
+class TargetInfo {
+ public:
+ TargetInfo() = default;
+
+ TargetInfo(std::string hash, RangeSet ranges)
+ : hash_(std::move(hash)), ranges_(std::move(ranges)) {}
+
+ const std::string& hash() const {
+ return hash_;
+ }
+
+ const RangeSet& ranges() const {
+ return ranges_;
+ }
+
+ size_t blocks() const {
+ return ranges_.blocks();
+ }
+
+ bool operator==(const TargetInfo& other) const {
+ return hash_ == other.hash_ && ranges_ == other.ranges_;
+ }
+
+ private:
+ friend std::ostream& operator<<(std::ostream& os, const TargetInfo& source);
+
+ // The hash of the data represented by the object.
+ std::string hash_;
+ // The block ranges that the data should be written to.
+ RangeSet ranges_;
+};
+
+std::ostream& operator<<(std::ostream& os, const TargetInfo& source);
+
+// Represents the stash info used in a Command.
+class StashInfo {
+ public:
+ StashInfo() = default;
+
+ StashInfo(std::string id, RangeSet ranges) : id_(std::move(id)), ranges_(std::move(ranges)) {}
+
+ size_t blocks() const {
+ return ranges_.blocks();
+ }
+
+ const std::string& id() const {
+ return id_;
+ }
+
+ const RangeSet& ranges() const {
+ return ranges_;
+ }
+
+ bool operator==(const StashInfo& other) const {
+ return id_ == other.id_ && ranges_ == other.ranges_;
+ }
+
+ private:
+ friend std::ostream& operator<<(std::ostream& os, const StashInfo& stash);
+
+ // The id (i.e. hash) of the stash.
+ std::string id_;
+ // The matching location of the stash.
+ RangeSet ranges_;
+};
+
+std::ostream& operator<<(std::ostream& os, const StashInfo& stash);
+
+// Represents the source info in a Command, whose data could come from source image, stashed blocks,
+// or both.
+class SourceInfo {
+ public:
+ SourceInfo() = default;
+
+ SourceInfo(std::string hash, RangeSet ranges, RangeSet location, std::vector<StashInfo> stashes)
+ : hash_(std::move(hash)),
+ ranges_(std::move(ranges)),
+ location_(std::move(location)),
+ stashes_(std::move(stashes)) {
+ blocks_ = ranges_.blocks();
+ for (const auto& stash : stashes_) {
+ blocks_ += stash.ranges().blocks();
+ }
+ }
+
+ const std::string& hash() const {
+ return hash_;
+ }
+
+ size_t blocks() const {
+ return blocks_;
+ }
+
+ bool operator==(const SourceInfo& other) const {
+ return hash_ == other.hash_ && ranges_ == other.ranges_ && location_ == other.location_ &&
+ stashes_ == other.stashes_;
+ }
+
+ private:
+ friend std::ostream& operator<<(std::ostream& os, const SourceInfo& source);
+
+ // The hash of the data represented by the object.
+ std::string hash_;
+ // The block ranges from the source image to read data from. This could be a subset of all the
+ // blocks represented by the object, or empty if all the data should be loaded from stash.
+ RangeSet ranges_;
+ // The location in the buffer to load ranges_ into. Empty if ranges_ alone covers all the blocks
+ // (i.e. nothing needs to be loaded from stash).
+ RangeSet location_;
+ // The info for the stashed blocks that are part of the source. Empty if there's none.
+ std::vector<StashInfo> stashes_;
+ // Total number of blocks represented by the object.
+ size_t blocks_{ 0 };
+};
+
+std::ostream& operator<<(std::ostream& os, const SourceInfo& source);
+
+class PatchInfo {
+ public:
+ PatchInfo() = default;
+
+ PatchInfo(size_t offset, size_t length) : offset_(offset), length_(length) {}
+
+ size_t offset() const {
+ return offset_;
+ }
+
+ size_t length() const {
+ return length_;
+ }
+
+ bool operator==(const PatchInfo& other) const {
+ return offset_ == other.offset_ && length_ == other.length_;
+ }
+
+ private:
+ size_t offset_{ 0 };
+ size_t length_{ 0 };
+};
+
+// Command class holds the info for an update command that performs block-based OTA (BBOTA). Each
+// command consists of one or several args, namely TargetInfo, SourceInfo, StashInfo and PatchInfo.
+// The currently used BBOTA version is v4.
+//
+// zero <tgt_ranges>
+// - Fill the indicated blocks with zeros.
+// - Meaningful args: TargetInfo
+//
+// new <tgt_ranges>
+// - Fill the blocks with data read from the new_data file.
+// - Meaningful args: TargetInfo
+//
+// erase <tgt_ranges>
+// - Mark the given blocks as empty.
+// - Meaningful args: TargetInfo
+//
+// move <hash> <...>
+// - Read the source blocks, write result to target blocks.
+// - Meaningful args: TargetInfo, SourceInfo
+//
+// See the note below for <...>.
+//
+// bsdiff <patchstart> <patchlen> <srchash> <dsthash> <...>
+// imgdiff <patchstart> <patchlen> <srchash> <dsthash> <...>
+// - Read the source blocks, apply a patch, and write result to target blocks.
+// - Meaningful args: PatchInfo, TargetInfo, SourceInfo
+//
+// It expects <...> in one of the following formats:
+//
+// <tgt_ranges> <src_block_count> - <[stash_id:stash_location] ...>
+// (loads data from stashes only)
+//
+// <tgt_ranges> <src_block_count> <src_ranges>
+// (loads data from source image only)
+//
+// <tgt_ranges> <src_block_count> <src_ranges> <src_ranges_location>
+// <[stash_id:stash_location] ...>
+// (loads data from both of source image and stashes)
+//
+// stash <stash_id> <src_ranges>
+// - Load the given source blocks and stash the data in the given slot of the stash table.
+// - Meaningful args: StashInfo
+//
+// free <stash_id>
+// - Free the given stash data.
+// - Meaningful args: StashInfo
+//
+class Command {
+ public:
enum class Type {
- ZERO,
- NEW,
- ERASE,
- MOVE,
BSDIFF,
+ ERASE,
+ FREE,
IMGDIFF,
+ MOVE,
+ NEW,
STASH,
- FREE,
+ ZERO,
LAST, // Not a valid type.
};
+ Command() = default;
+
+ Command(Type type, size_t index, std::string cmdline, PatchInfo patch, TargetInfo target,
+ SourceInfo source, StashInfo stash)
+ : type_(type),
+ index_(index),
+ cmdline_(std::move(cmdline)),
+ patch_(std::move(patch)),
+ target_(std::move(target)),
+ source_(std::move(source)),
+ stash_(std::move(stash)) {}
+
+ // Parses the given command 'line' into a Command object and returns it. The 'index' is specified
+ // by the caller to index the object. On parsing error, it returns an empty Command object that
+ // evaluates to false, and the specific error message will be set in 'err'.
+ static Command Parse(const std::string& line, size_t index, std::string* err);
+
+ // Parses the command type from the given string.
static Type ParseType(const std::string& type_str);
+
+ Type type() const {
+ return type_;
+ }
+
+ size_t index() const {
+ return index_;
+ }
+
+ const std::string& cmdline() const {
+ return cmdline_;
+ }
+
+ const PatchInfo& patch() const {
+ return patch_;
+ }
+
+ const TargetInfo& target() const {
+ return target_;
+ }
+
+ const SourceInfo& source() const {
+ return source_;
+ }
+
+ const StashInfo& stash() const {
+ return stash_;
+ }
+
+ constexpr explicit operator bool() const {
+ return type_ != Type::LAST;
+ }
+
+ private:
+ FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput);
+ FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly);
+ FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes);
+ FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly);
+
+ // Parses the target and source info from the given 'tokens' vector. Saves the parsed info into
+ // 'target' and 'source' objects. Returns the parsing result. Error message will be set in 'err'
+ // on parsing error, and the contents in 'target' and 'source' will be undefined.
+ static bool ParseTargetInfoAndSourceInfo(const std::vector<std::string>& tokens,
+ const std::string& tgt_hash, TargetInfo* target,
+ const std::string& src_hash, SourceInfo* source,
+ std::string* err);
+
+ // The type of the command.
+ Type type_{ Type::LAST };
+ // The index of the Command object, which is specified by the caller.
+ size_t index_{ 0 };
+ // The input string that the Command object is parsed from.
+ std::string cmdline_;
+ // The patch info. Only meaningful for BSDIFF and IMGDIFF commands.
+ PatchInfo patch_;
+ // The target info, where the command should be written to.
+ TargetInfo target_;
+ // The source info to load the source blocks for the command.
+ SourceInfo source_;
+ // The stash info. Only meaningful for STASH and FREE commands. Note that although SourceInfo may
+ // also load data from stash, such info will be owned and managed by SourceInfo (i.e. in source_).
+ StashInfo stash_;
};
+
+std::ostream& operator<<(std::ostream& os, const Command& command);