From 6a7e4af7c640cd8b056cdc703e84b9d122ce3703 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Thu, 14 Jun 2018 21:57:43 -0700 Subject: updater: Add Command parsing codes. The added codes are not used in the updater yet. The switch will happen in subsequent CLs. Test: Run recovery_unit_test and recovery_component_test on marlin. Change-Id: I1ae8a233280f02c2171b43ef028bdccdacb39c59 --- updater/Android.mk | 1 + updater/commands.cpp | 247 +++++++++++++++++++++++++++++-- updater/include/private/commands.h | 290 ++++++++++++++++++++++++++++++++++++- 3 files changed, 521 insertions(+), 17 deletions(-) (limited to 'updater') 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 #include +#include #include +#include +#include +#include + +#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& 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. + // + // - <[stash_id:location] ...> + // (loads data from stashes only) + // + // + // (loads data from source image only) + // + // <[stash_id:location] ...> + // (loads data from both of source image and stashes) + + // At least it needs to provide three parameters: , and + // "-"/. + 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); + + // + 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 [] + 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 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 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 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 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 + 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 + if (pos + 1 > tokens.size()) { + *err = "missing stash id in free command"; + return {}; + } + stash_info = StashInfo(tokens[pos++], {}); + } else if (op == Type::MOVE) { + // + if (pos + 1 > tokens.size()) { + *err = "missing hash"; + return {}; + } + std::string hash = tokens[pos++]; + if (!ParseTargetInfoAndSourceInfo( + std::vector(tokens.cbegin() + pos, tokens.cend()), hash, &target_info, + hash, &source_info, err)) { + return {}; + } + } else if (op == Type::BSDIFF || op == Type::IMGDIFF) { + // + 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(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 #include +#include -struct Command { +#include // 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 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 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 +// - Fill the indicated blocks with zeros. +// - Meaningful args: TargetInfo +// +// new +// - Fill the blocks with data read from the new_data file. +// - Meaningful args: TargetInfo +// +// erase +// - Mark the given blocks as empty. +// - Meaningful args: TargetInfo +// +// move <...> +// - Read the source blocks, write result to target blocks. +// - Meaningful args: TargetInfo, SourceInfo +// +// See the note below for <...>. +// +// bsdiff <...> +// imgdiff <...> +// - 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: +// +// - <[stash_id:stash_location] ...> +// (loads data from stashes only) +// +// +// (loads data from source image only) +// +// +// <[stash_id:stash_location] ...> +// (loads data from both of source image and stashes) +// +// stash +// - Load the given source blocks and stash the data in the given slot of the stash table. +// - Meaningful args: StashInfo +// +// free +// - 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& 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); -- cgit v1.2.3 From 92f339372c236d3b84188489417df2d65550b1c4 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Mon, 25 Jun 2018 12:11:53 -0700 Subject: updater: Check the number of args in Command::Parse. Additionally checks for excess args when parsing ERASE, FREE, NEW, STASH and ZERO. Note that the check for MOVE, BSDIFF, IMGDIFF has been covered in Command::ParseTargetInfoAndSourceInfo. Test: Run recovery_unit_test on marlin. Change-Id: Ic8bc9b7a8dcf98f1f8db2e259607564508726857 --- updater/commands.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'updater') diff --git a/updater/commands.cpp b/updater/commands.cpp index 6d4b5310b..fb19ebc9a 100644 --- a/updater/commands.cpp +++ b/updater/commands.cpp @@ -54,7 +54,7 @@ bool Command::ParseTargetInfoAndSourceInfo(const std::vector& token 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. + // We expect the given args (in 'tokens' vector) in one of the following formats. // // - <[stash_id:location] ...> // (loads data from stashes only) @@ -65,10 +65,9 @@ bool Command::ParseTargetInfoAndSourceInfo(const std::vector& token // <[stash_id:location] ...> // (loads data from both of source image and stashes) - // At least it needs to provide three parameters: , and - // "-"/. + // At least it needs to provide three args: , and "-"/. if (tokens.size() < 3) { - *err = "invalid number of parameters"; + *err = "invalid number of args"; return false; } @@ -170,6 +169,11 @@ Command Command::Parse(const std::string& line, size_t index, std::string* err) if (op == Type::ZERO || op == Type::NEW || op == Type::ERASE) { // zero/new/erase + if (pos + 1 != tokens.size()) { + *err = android::base::StringPrintf("invalid number of args: %zu (expected 1)", + tokens.size() - pos); + return {}; + } RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]); if (!tgt_ranges) { return {}; @@ -178,8 +182,9 @@ Command Command::Parse(const std::string& line, size_t index, std::string* err) target_info = TargetInfo(kUnknownHash, tgt_ranges); } else if (op == Type::STASH) { // stash - if (pos + 2 > tokens.size()) { - *err = "missing stash id and/or source ranges"; + if (pos + 2 != tokens.size()) { + *err = android::base::StringPrintf("invalid number of args: %zu (expected 2)", + tokens.size() - pos); return {}; } const std::string& id = tokens[pos++]; @@ -191,8 +196,9 @@ Command Command::Parse(const std::string& line, size_t index, std::string* err) stash_info = StashInfo(id, src_ranges); } else if (op == Type::FREE) { // free - if (pos + 1 > tokens.size()) { - *err = "missing stash id in free command"; + if (pos + 1 != tokens.size()) { + *err = android::base::StringPrintf("invalid number of args: %zu (expected 1)", + tokens.size() - pos); return {}; } stash_info = StashInfo(tokens[pos++], {}); @@ -211,7 +217,8 @@ Command Command::Parse(const std::string& line, size_t index, std::string* err) } else if (op == Type::BSDIFF || op == Type::IMGDIFF) { // if (pos + 4 > tokens.size()) { - *err = "invalid number of tokens"; + *err = android::base::StringPrintf("invalid number of args: %zu (expected 4+)", + tokens.size() - pos); return {}; } size_t offset; -- cgit v1.2.3