Files
android_bootable_recovery/updater/commands.cpp
Tao Bao 91a649ab62 updater: Add ABORT command.
This will be used for testing purpose only, replacing the previously
used "fail", to intentionally abort an update.

As we're separating the logic between commands parsing and execution,
"abort" needs to be considered as a valid command during the parsing.

Test: recovery_unit_test and recovery_component_test on marlin.
Change-Id: I47c41c423e62c41cc8515fd92f3c5959be08da02
2018-07-07 04:12:19 +00:00

291 lines
9.2 KiB
C++

/*
* Copyright (C) 2018 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 "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;
bool Command::abort_allowed_ = false;
Command::Type Command::ParseType(const std::string& type_str) {
if (type_str == "abort") {
if (!abort_allowed_) {
LOG(ERROR) << "ABORT disallowed";
return Type::LAST;
}
return Type::ABORT;
} else if (type_str == "bsdiff") {
return Type::BSDIFF;
} else if (type_str == "erase") {
return Type::ERASE;
} 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 == "zero") {
return Type::ZERO;
}
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 args (in 'tokens' vector) 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 args: <tgt_ranges>, <src_block_count> and "-"/<src_ranges>.
if (tokens.size() < 3) {
*err = "invalid number of args";
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>
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 {};
}
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 = android::base::StringPrintf("invalid number of args: %zu (expected 2)",
tokens.size() - pos);
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 = android::base::StringPrintf("invalid number of args: %zu (expected 1)",
tokens.size() - pos);
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 = android::base::StringPrintf("invalid number of args: %zu (expected 4+)",
tokens.size() - pos);
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 if (op == Type::ABORT) {
// No-op, other than sanity checking the input args.
if (pos != tokens.size()) {
*err = android::base::StringPrintf("invalid number of args: %zu (expected 0)",
tokens.size() - pos);
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;
}