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
diff --git a/tests/unit/commands_test.cpp b/tests/unit/commands_test.cpp
index bbc83b9..cb2be91 100644
--- a/tests/unit/commands_test.cpp
+++ b/tests/unit/commands_test.cpp
@@ -310,3 +310,28 @@
   ASSERT_EQ(StashInfo(), command.stash());
   ASSERT_EQ(PatchInfo(), command.patch());
 }
+
+TEST(CommandsTest, Parse_InvalidNumberOfArgs) {
+  // Note that the case of having excess args in BSDIFF, IMGDIFF and MOVE is covered by
+  // ParseTargetInfoAndSourceInfo_InvalidInput.
+  std::vector<std::string> inputs{
+    "bsdiff",
+    "erase",
+    "erase 4,3,5,10,12 hash1",
+    "free",
+    "free id1 id2",
+    "imgdiff",
+    "move",
+    "new",
+    "new 4,3,5,10,12 hash1",
+    "stash",
+    "stash id1",
+    "stash id1 4,3,5,10,12 id2",
+    "zero",
+    "zero 4,3,5,10,12 hash2",
+  };
+  for (const auto& input : inputs) {
+    std::string err;
+    ASSERT_FALSE(Command::Parse(input, 0, &err));
+  }
+}
diff --git a/updater/commands.cpp b/updater/commands.cpp
index 6d4b531..fb19ebc 100644
--- a/updater/commands.cpp
+++ b/updater/commands.cpp
@@ -54,7 +54,7 @@
                                            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.
   //
   //    <tgt_ranges> <src_block_count> - <[stash_id:location] ...>
   //        (loads data from stashes only)
@@ -65,10 +65,9 @@
   //    <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>.
+  // At least it needs to provide three args: <tgt_ranges>, <src_block_count> and "-"/<src_ranges>.
   if (tokens.size() < 3) {
-    *err = "invalid number of parameters";
+    *err = "invalid number of args";
     return false;
   }
 
@@ -170,6 +169,11 @@
 
   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 {};
@@ -178,8 +182,9 @@
     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";
+    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 @@
     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";
+    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 @@
   } else if (op == Type::BSDIFF || op == Type::IMGDIFF) {
     // <offset> <length> <srchash> <dsthash>
     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;