Merge "storaged: use correct accumulate variable for publishing disk stats"
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 6d50fa4..3b786e8 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -294,6 +294,7 @@
         "udp.cpp",
         "util.cpp",
         "vendor_boot_img_utils.cpp",
+        "task.cpp",
     ],
 
     // Only version the final binaries
@@ -375,14 +376,19 @@
     defaults: ["fastboot_host_defaults"],
 
     srcs: [
+        "fastboot_driver_test.cpp",
         "fastboot_test.cpp",
         "socket_mock.cpp",
         "socket_test.cpp",
+        "super_flash_helper_test.cpp",
         "tcp_test.cpp",
         "udp_test.cpp",
     ],
 
-    static_libs: ["libfastboot"],
+    static_libs: [
+        "libfastboot",
+        "libgmock",
+    ],
 
     target: {
         windows: {
@@ -395,6 +401,12 @@
     },
 
     test_suites: ["general-tests"],
+
+    data: [
+        "testdata/super.img",
+        "testdata/super_empty.img",
+        "testdata/system.img",
+    ],
 }
 
 cc_test_host {
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 0c8747c..c9cb228 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -44,8 +44,12 @@
 #include <unistd.h>
 
 #include <chrono>
+#include <fstream>
 #include <functional>
+#include <iostream>
+#include <memory>
 #include <regex>
+#include <sstream>
 #include <string>
 #include <thread>
 #include <utility>
@@ -216,7 +220,7 @@
     return std::string(dir) + "/" + img_name;
 }
 
-static std::string find_item(const std::string& item) {
+std::string find_item(const std::string& item) {
     for (size_t i = 0; i < images.size(); ++i) {
         if (!images[i].nickname.empty() && item == images[i].nickname) {
             return find_item_given_name(images[i].img_name);
@@ -546,8 +550,7 @@
     usb_open(list_devices_callback);
     NetworkDeviceConnected(/* print */ true);
 }
-
-static void syntax_error(const char* fmt, ...) {
+void syntax_error(const char* fmt, ...) {
     fprintf(stderr, "fastboot: usage: ");
 
     va_list ap;
@@ -1410,9 +1413,8 @@
  * partition names. If force_slot is true, it will fail if a slot is specified, and the given
  * partition does not support slots.
  */
-static void do_for_partitions(const std::string& part, const std::string& slot,
-                              const std::function<void(const std::string&)>& func,
-                              bool force_slot) {
+void do_for_partitions(const std::string& part, const std::string& slot,
+                       const std::function<void(const std::string&)>& func, bool force_slot) {
     std::string has_slot;
     // |part| can be vendor_boot:default. Query has-slot on the first token only.
     auto part_tokens = android::base::Split(part, ":");
@@ -1508,7 +1510,7 @@
     return partition;
 }
 
-static void do_flash(const char* pname, const char* fname) {
+void do_flash(const char* pname, const char* fname) {
     verbose("Do flash %s %s", pname, fname);
     struct fastboot_buffer buf;
 
@@ -1537,12 +1539,12 @@
     }
 }
 
-static bool is_userspace_fastboot() {
+bool is_userspace_fastboot() {
     std::string value;
     return fb->GetVar("is-userspace", &value) == fastboot::SUCCESS && value == "yes";
 }
 
-static void reboot_to_userspace_fastboot() {
+void reboot_to_userspace_fastboot() {
     fb->RebootTo("fastboot");
 
     auto* old_transport = fb->set_transport(nullptr);
@@ -2029,7 +2031,7 @@
     }
 }
 
-static bool should_flash_in_userspace(const std::string& partition_name) {
+bool should_flash_in_userspace(const std::string& partition_name) {
     if (!get_android_product_out()) {
         return false;
     }
@@ -2416,7 +2418,6 @@
             fb->Boot();
         } else if (command == FB_CMD_FLASH) {
             std::string pname = next_arg(&args);
-
             std::string fname;
             if (!args.empty()) {
                 fname = next_arg(&args);
@@ -2424,21 +2425,8 @@
                 fname = find_item(pname);
             }
             if (fname.empty()) die("cannot determine image filename for '%s'", pname.c_str());
-
-            auto flash = [&](const std::string& partition) {
-                if (should_flash_in_userspace(partition) && !is_userspace_fastboot() &&
-                    !force_flash) {
-                    die("The partition you are trying to flash is dynamic, and "
-                        "should be flashed via fastbootd. Please run:\n"
-                        "\n"
-                        "    fastboot reboot fastboot\n"
-                        "\n"
-                        "And try again. If you are intentionally trying to "
-                        "overwrite a fixed partition, use --force.");
-                }
-                do_flash(partition.c_str(), fname.c_str());
-            };
-            do_for_partitions(pname, slot_override, flash, true);
+            FlashTask task(slot_override, force_flash, pname, fname);
+            task.Run();
         } else if (command == "flash:raw") {
             std::string partition = next_arg(&args);
             std::string kernel = next_arg(&args);
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index d7ad5df..b5fb8c0 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -27,6 +27,8 @@
  */
 #pragma once
 
+#include <string>
+
 #include <bootimg.h>
 
 class FastBootTool {
@@ -37,3 +39,12 @@
     void ParseOsVersion(boot_img_hdr_v1*, const char*);
     unsigned ParseFsOption(const char*);
 };
+
+bool should_flash_in_userspace(const std::string& partition_name);
+bool is_userspace_fastboot();
+void do_flash(const char* pname, const char* fname);
+void do_for_partitions(const std::string& part, const std::string& slot,
+                       const std::function<void(const std::string&)>& func, bool force_slot);
+std::string find_item(const std::string& item);
+void reboot_to_userspace_fastboot();
+void syntax_error(const char* fmt, ...);
diff --git a/fastboot/fastboot_driver_test.cpp b/fastboot/fastboot_driver_test.cpp
new file mode 100644
index 0000000..e874c3a
--- /dev/null
+++ b/fastboot/fastboot_driver_test.cpp
@@ -0,0 +1,60 @@
+//
+// Copyright (C) 2023 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 "fastboot_driver.h"
+
+#include <optional>
+
+#include <gtest/gtest.h>
+#include "mock_transport.h"
+
+using namespace ::testing;
+using namespace fastboot;
+
+class DriverTest : public ::testing::Test {
+  protected:
+    InSequence s_;
+};
+
+TEST_F(DriverTest, GetVar) {
+    MockTransport transport;
+    FastBootDriver driver(&transport);
+
+    EXPECT_CALL(transport, Write(_, _))
+            .With(AllArgs(RawData("getvar:version")))
+            .WillOnce(ReturnArg<1>());
+    EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("OKAY0.4")));
+
+    std::string output;
+    ASSERT_EQ(driver.GetVar("version", &output), SUCCESS) << driver.Error();
+    ASSERT_EQ(output, "0.4");
+}
+
+TEST_F(DriverTest, InfoMessage) {
+    MockTransport transport;
+    FastBootDriver driver(&transport);
+
+    EXPECT_CALL(transport, Write(_, _))
+            .With(AllArgs(RawData("oem dmesg")))
+            .WillOnce(ReturnArg<1>());
+    EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("INFOthis is an info line")));
+    EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("OKAY")));
+
+    std::vector<std::string> info;
+    ASSERT_EQ(driver.RawCommand("oem dmesg", "", nullptr, &info), SUCCESS) << driver.Error();
+    ASSERT_EQ(info.size(), size_t(1));
+    ASSERT_EQ(info[0], "this is an info line");
+}
diff --git a/fastboot/fastboot_test.cpp b/fastboot/fastboot_test.cpp
index 9c3ab6e..79f37fd 100644
--- a/fastboot/fastboot_test.cpp
+++ b/fastboot/fastboot_test.cpp
@@ -16,6 +16,7 @@
 
 #include "fastboot.h"
 
+#include <android-base/logging.h>
 #include <gtest/gtest.h>
 
 TEST(FastBoot, ParseOsPatchLevel) {
@@ -201,3 +202,11 @@
     // No spaces allowed before between require-for-product and :.
     ParseRequirementLineTestMalformed("require-for-product :");
 }
+
+int main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    android::base::InitLogging(argv);
+    android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+
+    return RUN_ALL_TESTS();
+}
diff --git a/fastboot/mock_transport.h b/fastboot/mock_transport.h
new file mode 100644
index 0000000..cc3840c
--- /dev/null
+++ b/fastboot/mock_transport.h
@@ -0,0 +1,67 @@
+//
+// Copyright (C) 2023 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.
+//
+
+#pragma once
+
+#include <string.h>
+
+#include <algorithm>
+#include <string_view>
+
+#include <gmock/gmock.h>
+#include "transport.h"
+
+class MockTransport : public Transport {
+  public:
+    MOCK_METHOD(ssize_t, Read, (void* data, size_t len), (override));
+    MOCK_METHOD(ssize_t, Write, (const void* data, size_t len), (override));
+    MOCK_METHOD(int, Close, (), (override));
+    MOCK_METHOD(int, Reset, (), (override));
+};
+
+class RawDataMatcher {
+  public:
+    explicit RawDataMatcher(const char* data) : data_(data) {}
+    explicit RawDataMatcher(std::string_view data) : data_(data) {}
+
+    bool MatchAndExplain(std::tuple<const void*, size_t> args,
+                         ::testing::MatchResultListener*) const {
+        const void* expected_data = std::get<0>(args);
+        size_t expected_len = std::get<1>(args);
+        if (expected_len != data_.size()) {
+            return false;
+        }
+        return memcmp(expected_data, data_.data(), expected_len) == 0;
+    }
+    void DescribeTo(std::ostream* os) const { *os << "raw data is"; }
+    void DescribeNegationTo(std::ostream* os) const { *os << "raw data is not"; }
+
+  private:
+    std::string_view data_;
+};
+
+template <typename T>
+static inline ::testing::PolymorphicMatcher<RawDataMatcher> RawData(T data) {
+    return ::testing::MakePolymorphicMatcher(RawDataMatcher(data));
+}
+
+static inline auto CopyData(const char* source) {
+    return [source](void* buffer, size_t size) -> ssize_t {
+        size_t to_copy = std::min(size, strlen(source));
+        memcpy(buffer, source, to_copy);
+        return to_copy;
+    };
+};
diff --git a/fastboot/super_flash_helper_test.cpp b/fastboot/super_flash_helper_test.cpp
new file mode 100644
index 0000000..82b8aa5
--- /dev/null
+++ b/fastboot/super_flash_helper_test.cpp
@@ -0,0 +1,88 @@
+//
+// Copyright (C) 2023 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 "super_flash_helper.h"
+
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <sparse/sparse.h>
+
+using android::base::unique_fd;
+
+unique_fd OpenTestFile(const std::string& file, int flags) {
+    std::string path = "testdata/" + file;
+
+    unique_fd fd(open(path.c_str(), flags));
+    if (fd >= 0) {
+        return fd;
+    }
+
+    path = android::base::GetExecutableDirectory() + "/" + path;
+    return unique_fd{open(path.c_str(), flags)};
+}
+
+class TestImageSource final : public ImageSource {
+  public:
+    bool ReadFile(const std::string&, std::vector<char>*) const override {
+        // Not used here.
+        return false;
+    }
+    unique_fd OpenFile(const std::string& name) const override {
+        return OpenTestFile(name, O_RDONLY | O_CLOEXEC);
+    }
+};
+
+TEST(SuperFlashHelper, ImageEquality) {
+    auto super_empty_fd = OpenTestFile("super_empty.img", O_RDONLY);
+    ASSERT_GE(super_empty_fd, 0);
+
+    TestImageSource source;
+    SuperFlashHelper helper(source);
+    ASSERT_TRUE(helper.Open(super_empty_fd));
+    ASSERT_TRUE(helper.AddPartition("system_a", "system.img", false));
+
+    auto sparse_file = helper.GetSparseLayout();
+    ASSERT_NE(sparse_file, nullptr);
+
+    TemporaryFile fb_super;
+    ASSERT_GE(fb_super.fd, 0);
+    ASSERT_EQ(sparse_file_write(sparse_file.get(), fb_super.fd, false, false, false), 0);
+
+    auto real_super_fd = OpenTestFile("super.img", O_RDONLY);
+    ASSERT_GE(real_super_fd, 0);
+
+    std::string expected(get_file_size(real_super_fd), '\0');
+    ASSERT_FALSE(expected.empty());
+    ASSERT_TRUE(android::base::ReadFully(real_super_fd, expected.data(), expected.size()));
+
+    std::string actual(get_file_size(fb_super.fd), '\0');
+    ASSERT_FALSE(actual.empty());
+    ASSERT_EQ(lseek(fb_super.fd, 0, SEEK_SET), 0);
+    ASSERT_TRUE(android::base::ReadFully(fb_super.fd, actual.data(), actual.size()));
+
+    // The helper doesn't add any extra zeroes to the image, whereas lpmake does, to
+    // pad to the entire super size.
+    ASSERT_LE(actual.size(), expected.size());
+    for (size_t i = 0; i < actual.size(); i++) {
+        ASSERT_EQ(actual[i], expected[i]) << "byte mismatch at position " << i;
+    }
+    for (size_t i = actual.size(); i < expected.size(); i++) {
+        ASSERT_EQ(expected[i], 0) << "byte mismatch at position " << i;
+    }
+}
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
new file mode 100644
index 0000000..3f33c76
--- /dev/null
+++ b/fastboot/task.cpp
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2023 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 "task.h"
+
+#include "fastboot.h"
+#include "util.h"
+
+FlashTask::FlashTask(const std::string& _slot) : slot_(_slot){};
+FlashTask::FlashTask(const std::string& _slot, bool _force_flash)
+    : slot_(_slot), force_flash_(_force_flash) {}
+FlashTask::FlashTask(const std::string& _slot, bool _force_flash, const std::string& _pname)
+    : pname_(_pname), fname_(find_item(_pname)), slot_(_slot), force_flash_(_force_flash) {
+    if (fname_.empty()) die("cannot determine image filename for '%s'", pname_.c_str());
+}
+FlashTask::FlashTask(const std::string& _slot, bool _force_flash, const std::string& _pname,
+                     const std::string& _fname)
+    : pname_(_pname), fname_(_fname), slot_(_slot), force_flash_(_force_flash) {}
+
+void FlashTask::Run() {
+    auto flash = [&](const std::string& partition) {
+        if (should_flash_in_userspace(partition) && !is_userspace_fastboot() && !force_flash_) {
+            die("The partition you are trying to flash is dynamic, and "
+                "should be flashed via fastbootd. Please run:\n"
+                "\n"
+                "    fastboot reboot fastboot\n"
+                "\n"
+                "And try again. If you are intentionally trying to "
+                "overwrite a fixed partition, use --force.");
+        }
+        do_flash(partition.c_str(), fname_.c_str());
+    };
+    do_for_partitions(pname_, slot_, flash, true);
+}
diff --git a/fastboot/task.h b/fastboot/task.h
index 8b3fce9..216e658 100644
--- a/fastboot/task.h
+++ b/fastboot/task.h
@@ -26,6 +26,23 @@
   public:
     Task() = default;
     virtual void Run() = 0;
-    virtual bool Parse(const std::string& text) = 0;
     virtual ~Task() = default;
 };
+
+class FlashTask : public Task {
+  public:
+    FlashTask(const std::string& _slot);
+    FlashTask(const std::string& _slot, bool _force_flash);
+    FlashTask(const std::string& _slot, bool _force_flash, const std::string& _pname);
+    FlashTask(const std::string& _slot, bool _force_flash, const std::string& _pname,
+              const std::string& _fname);
+
+    void Run() override;
+    ~FlashTask() {}
+
+  private:
+    const std::string pname_;
+    const std::string fname_;
+    const std::string slot_;
+    bool force_flash_ = false;
+};
diff --git a/fastboot/testdata/make_super_images.sh b/fastboot/testdata/make_super_images.sh
new file mode 100644
index 0000000..71c54ee
--- /dev/null
+++ b/fastboot/testdata/make_super_images.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+set -e
+set -x
+
+lpmake \
+    --device-size=auto \
+    --metadata-size=4096 \
+    --metadata-slots=3 \
+    --partition=system_a:readonly:0 \
+    --alignment=16384 \
+    --output=super_empty.img
+
+lpmake \
+    --device-size=auto \
+    --metadata-size=4096 \
+    --metadata-slots=3 \
+    --partition=system_a:readonly:0 \
+    --alignment=16384 \
+    --output=super.img \
+    --image=system_a=system.img
diff --git a/fastboot/testdata/super.img b/fastboot/testdata/super.img
new file mode 100644
index 0000000..be13d36
--- /dev/null
+++ b/fastboot/testdata/super.img
Binary files differ
diff --git a/fastboot/testdata/super_empty.img b/fastboot/testdata/super_empty.img
new file mode 100644
index 0000000..4b25869
--- /dev/null
+++ b/fastboot/testdata/super_empty.img
Binary files differ
diff --git a/fastboot/testdata/system.img b/fastboot/testdata/system.img
new file mode 100644
index 0000000..b360610
--- /dev/null
+++ b/fastboot/testdata/system.img
Binary files differ
diff --git a/fs_mgr/liblp/images.cpp b/fs_mgr/liblp/images.cpp
index 0b1e522..02b64ac 100644
--- a/fs_mgr/liblp/images.cpp
+++ b/fs_mgr/liblp/images.cpp
@@ -312,6 +312,11 @@
 
 bool ImageBuilder::AddPartitionImage(const LpMetadataPartition& partition,
                                      const std::string& file) {
+    if (partition.num_extents == 0) {
+        LERROR << "Partition size is zero: " << GetPartitionName(partition);
+        return false;
+    }
+
     // Track which extent we're processing.
     uint32_t extent_index = partition.first_extent_index;