blob: 4a90ea873ff101b024894fdeb7c8e33c55f2754b [file] [log] [blame]
Tao Baoc3901232018-05-21 16:05:56 -07001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "private/commands.h"
18
Tao Bao6a7e4af2018-06-14 21:57:43 -070019#include <ostream>
Tao Baoc3901232018-05-21 16:05:56 -070020#include <string>
Tao Bao6a7e4af2018-06-14 21:57:43 -070021#include <vector>
Tao Baoc3901232018-05-21 16:05:56 -070022
23#include <android-base/logging.h>
Tao Bao6a7e4af2018-06-14 21:57:43 -070024#include <android-base/parseint.h>
25#include <android-base/stringprintf.h>
26#include <android-base/strings.h>
27
28#include "otautil/rangeset.h"
29
30using namespace std::string_literals;
Tao Baoc3901232018-05-21 16:05:56 -070031
Tao Bao91a649a2018-05-21 16:05:56 -070032bool Command::abort_allowed_ = false;
33
Tianjie Xu8f64bf62018-08-07 00:22:19 -070034Command::Command(Type type, size_t index, std::string cmdline, HashTreeInfo hash_tree_info)
35 : type_(type),
36 index_(index),
37 cmdline_(std::move(cmdline)),
38 hash_tree_info_(std::move(hash_tree_info)) {
39 CHECK(type == Type::COMPUTE_HASH_TREE);
40}
41
Tao Baoc3901232018-05-21 16:05:56 -070042Command::Type Command::ParseType(const std::string& type_str) {
Tao Bao91a649a2018-05-21 16:05:56 -070043 if (type_str == "abort") {
44 if (!abort_allowed_) {
45 LOG(ERROR) << "ABORT disallowed";
46 return Type::LAST;
47 }
48 return Type::ABORT;
49 } else if (type_str == "bsdiff") {
Tao Bao6a7e4af2018-06-14 21:57:43 -070050 return Type::BSDIFF;
Tianjie Xu69ffa152018-08-01 16:40:00 -070051 } else if (type_str == "compute_hash_tree") {
52 return Type::COMPUTE_HASH_TREE;
Tao Baoc3901232018-05-21 16:05:56 -070053 } else if (type_str == "erase") {
54 return Type::ERASE;
Tao Baoc3901232018-05-21 16:05:56 -070055 } else if (type_str == "free") {
56 return Type::FREE;
Tao Bao6a7e4af2018-06-14 21:57:43 -070057 } else if (type_str == "imgdiff") {
58 return Type::IMGDIFF;
59 } else if (type_str == "move") {
60 return Type::MOVE;
61 } else if (type_str == "new") {
62 return Type::NEW;
63 } else if (type_str == "stash") {
64 return Type::STASH;
65 } else if (type_str == "zero") {
66 return Type::ZERO;
Tao Baoc3901232018-05-21 16:05:56 -070067 }
Tao Baoc3901232018-05-21 16:05:56 -070068 return Type::LAST;
69};
Tao Bao6a7e4af2018-06-14 21:57:43 -070070
71bool Command::ParseTargetInfoAndSourceInfo(const std::vector<std::string>& tokens,
72 const std::string& tgt_hash, TargetInfo* target,
73 const std::string& src_hash, SourceInfo* source,
74 std::string* err) {
Tao Bao92f33932018-06-25 12:11:53 -070075 // We expect the given args (in 'tokens' vector) in one of the following formats.
Tao Bao6a7e4af2018-06-14 21:57:43 -070076 //
77 // <tgt_ranges> <src_block_count> - <[stash_id:location] ...>
78 // (loads data from stashes only)
79 //
80 // <tgt_ranges> <src_block_count> <src_ranges>
81 // (loads data from source image only)
82 //
83 // <tgt_ranges> <src_block_count> <src_ranges> <src_ranges_location> <[stash_id:location] ...>
84 // (loads data from both of source image and stashes)
85
Tao Bao92f33932018-06-25 12:11:53 -070086 // At least it needs to provide three args: <tgt_ranges>, <src_block_count> and "-"/<src_ranges>.
Tao Bao6a7e4af2018-06-14 21:57:43 -070087 if (tokens.size() < 3) {
Tao Bao92f33932018-06-25 12:11:53 -070088 *err = "invalid number of args";
Tao Bao6a7e4af2018-06-14 21:57:43 -070089 return false;
90 }
91
92 size_t pos = 0;
93 RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]);
94 if (!tgt_ranges) {
95 *err = "invalid target ranges";
96 return false;
97 }
98 *target = TargetInfo(tgt_hash, tgt_ranges);
99
100 // <src_block_count>
101 const std::string& token = tokens[pos++];
102 size_t src_blocks;
103 if (!android::base::ParseUint(token, &src_blocks)) {
104 *err = "invalid src_block_count \""s + token + "\"";
105 return false;
106 }
107
108 RangeSet src_ranges;
109 RangeSet src_ranges_location;
110 // "-" or <src_ranges> [<src_ranges_location>]
111 if (tokens[pos] == "-") {
112 // no source ranges, only stashes
113 pos++;
114 } else {
115 src_ranges = RangeSet::Parse(tokens[pos++]);
116 if (!src_ranges) {
117 *err = "invalid source ranges";
118 return false;
119 }
120
121 if (pos >= tokens.size()) {
122 // No stashes, only source ranges.
123 SourceInfo result(src_hash, src_ranges, {}, {});
124
125 // Sanity check the block count.
126 if (result.blocks() != src_blocks) {
127 *err =
128 android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(),
129 src_ranges.ToString().c_str(), src_blocks);
130 return false;
131 }
132
133 *source = result;
134 return true;
135 }
136
137 src_ranges_location = RangeSet::Parse(tokens[pos++]);
138 if (!src_ranges_location) {
139 *err = "invalid source ranges location";
140 return false;
141 }
142 }
143
144 // <[stash_id:stash_location]>
145 std::vector<StashInfo> stashes;
146 while (pos < tokens.size()) {
147 // Each word is a an index into the stash table, a colon, and then a RangeSet describing where
148 // in the source block that stashed data should go.
149 std::vector<std::string> pairs = android::base::Split(tokens[pos++], ":");
150 if (pairs.size() != 2) {
151 *err = "invalid stash info";
152 return false;
153 }
154 RangeSet stash_location = RangeSet::Parse(pairs[1]);
155 if (!stash_location) {
156 *err = "invalid stash location";
157 return false;
158 }
159 stashes.emplace_back(pairs[0], stash_location);
160 }
161
162 SourceInfo result(src_hash, src_ranges, src_ranges_location, stashes);
163 if (src_blocks != result.blocks()) {
164 *err = android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(),
165 src_ranges.ToString().c_str(), src_blocks);
166 return false;
167 }
168
169 *source = result;
170 return true;
171}
172
173Command Command::Parse(const std::string& line, size_t index, std::string* err) {
174 std::vector<std::string> tokens = android::base::Split(line, " ");
175 size_t pos = 0;
176 // tokens.size() will be 1 at least.
177 Type op = ParseType(tokens[pos++]);
178 if (op == Type::LAST) {
179 *err = "invalid type";
180 return {};
181 }
182
183 PatchInfo patch_info;
184 TargetInfo target_info;
185 SourceInfo source_info;
186 StashInfo stash_info;
187
188 if (op == Type::ZERO || op == Type::NEW || op == Type::ERASE) {
189 // zero/new/erase <rangeset>
Tao Bao92f33932018-06-25 12:11:53 -0700190 if (pos + 1 != tokens.size()) {
191 *err = android::base::StringPrintf("invalid number of args: %zu (expected 1)",
192 tokens.size() - pos);
193 return {};
194 }
Tao Bao6a7e4af2018-06-14 21:57:43 -0700195 RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]);
196 if (!tgt_ranges) {
197 return {};
198 }
199 static const std::string kUnknownHash{ "unknown-hash" };
200 target_info = TargetInfo(kUnknownHash, tgt_ranges);
201 } else if (op == Type::STASH) {
202 // stash <stash_id> <src_ranges>
Tao Bao92f33932018-06-25 12:11:53 -0700203 if (pos + 2 != tokens.size()) {
204 *err = android::base::StringPrintf("invalid number of args: %zu (expected 2)",
205 tokens.size() - pos);
Tao Bao6a7e4af2018-06-14 21:57:43 -0700206 return {};
207 }
208 const std::string& id = tokens[pos++];
209 RangeSet src_ranges = RangeSet::Parse(tokens[pos++]);
210 if (!src_ranges) {
211 *err = "invalid token";
212 return {};
213 }
214 stash_info = StashInfo(id, src_ranges);
215 } else if (op == Type::FREE) {
216 // free <stash_id>
Tao Bao92f33932018-06-25 12:11:53 -0700217 if (pos + 1 != tokens.size()) {
218 *err = android::base::StringPrintf("invalid number of args: %zu (expected 1)",
219 tokens.size() - pos);
Tao Bao6a7e4af2018-06-14 21:57:43 -0700220 return {};
221 }
222 stash_info = StashInfo(tokens[pos++], {});
223 } else if (op == Type::MOVE) {
224 // <hash>
225 if (pos + 1 > tokens.size()) {
226 *err = "missing hash";
227 return {};
228 }
229 std::string hash = tokens[pos++];
230 if (!ParseTargetInfoAndSourceInfo(
231 std::vector<std::string>(tokens.cbegin() + pos, tokens.cend()), hash, &target_info,
232 hash, &source_info, err)) {
233 return {};
234 }
235 } else if (op == Type::BSDIFF || op == Type::IMGDIFF) {
236 // <offset> <length> <srchash> <dsthash>
237 if (pos + 4 > tokens.size()) {
Tao Bao92f33932018-06-25 12:11:53 -0700238 *err = android::base::StringPrintf("invalid number of args: %zu (expected 4+)",
239 tokens.size() - pos);
Tao Bao6a7e4af2018-06-14 21:57:43 -0700240 return {};
241 }
242 size_t offset;
243 size_t length;
244 if (!android::base::ParseUint(tokens[pos++], &offset) ||
245 !android::base::ParseUint(tokens[pos++], &length)) {
246 *err = "invalid patch offset/length";
247 return {};
248 }
249 patch_info = PatchInfo(offset, length);
250
251 std::string src_hash = tokens[pos++];
252 std::string dst_hash = tokens[pos++];
253 if (!ParseTargetInfoAndSourceInfo(
254 std::vector<std::string>(tokens.cbegin() + pos, tokens.cend()), dst_hash, &target_info,
255 src_hash, &source_info, err)) {
256 return {};
257 }
Tao Bao91a649a2018-05-21 16:05:56 -0700258 } else if (op == Type::ABORT) {
259 // No-op, other than sanity checking the input args.
260 if (pos != tokens.size()) {
261 *err = android::base::StringPrintf("invalid number of args: %zu (expected 0)",
262 tokens.size() - pos);
263 return {};
264 }
Tianjie Xu8f64bf62018-08-07 00:22:19 -0700265 } else if (op == Type::COMPUTE_HASH_TREE) {
266 // <hash_tree_ranges> <source_ranges> <hash_algorithm> <salt_hex> <root_hash>
267 if (pos + 5 != tokens.size()) {
268 *err = android::base::StringPrintf("invalid number of args: %zu (expected 5)",
269 tokens.size() - pos);
270 return {};
271 }
272
273 // Expects the hash_tree data to be contiguous.
274 RangeSet hash_tree_ranges = RangeSet::Parse(tokens[pos++]);
275 if (!hash_tree_ranges || hash_tree_ranges.size() != 1) {
276 *err = "invalid hash tree ranges in: " + line;
277 return {};
278 }
279
280 RangeSet source_ranges = RangeSet::Parse(tokens[pos++]);
281 if (!source_ranges) {
282 *err = "invalid source ranges in: " + line;
283 return {};
284 }
285
286 std::string hash_algorithm = tokens[pos++];
287 std::string salt_hex = tokens[pos++];
288 std::string root_hash = tokens[pos++];
289 if (hash_algorithm.empty() || salt_hex.empty() || root_hash.empty()) {
290 *err = "invalid hash tree arguments in " + line;
291 return {};
292 }
293
294 HashTreeInfo hash_tree_info(std::move(hash_tree_ranges), std::move(source_ranges),
295 std::move(hash_algorithm), std::move(salt_hex),
296 std::move(root_hash));
297 return Command(op, index, line, std::move(hash_tree_info));
Tao Bao6a7e4af2018-06-14 21:57:43 -0700298 } else {
299 *err = "invalid op";
300 return {};
301 }
302
303 return Command(op, index, line, patch_info, target_info, source_info, stash_info);
304}
305
306std::ostream& operator<<(std::ostream& os, const Command& command) {
307 os << command.index() << ": " << command.cmdline();
308 return os;
309}
310
311std::ostream& operator<<(std::ostream& os, const TargetInfo& target) {
312 os << target.blocks() << " blocks (" << target.hash_ << "): " << target.ranges_.ToString();
313 return os;
314}
315
316std::ostream& operator<<(std::ostream& os, const StashInfo& stash) {
317 os << stash.blocks() << " blocks (" << stash.id_ << "): " << stash.ranges_.ToString();
318 return os;
319}
320
321std::ostream& operator<<(std::ostream& os, const SourceInfo& source) {
322 os << source.blocks_ << " blocks (" << source.hash_ << "): ";
323 if (source.ranges_) {
324 os << source.ranges_.ToString();
325 if (source.location_) {
326 os << " (location: " << source.location_.ToString() << ")";
327 }
328 }
329 if (!source.stashes_.empty()) {
330 os << " " << source.stashes_.size() << " stash(es)";
331 }
332 return os;
333}