Merge "first_stage_init: add support to skip module load failures" into rvc-dev
diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp
index 092a866..b273c77 100644
--- a/adb/client/adb_install.cpp
+++ b/adb/client/adb_install.cpp
@@ -155,6 +155,14 @@
     *buf = '\0';
 }
 
+static unique_fd send_command(const std::vector<std::string>& cmd_args, std::string* error) {
+    if (is_abb_exec_supported()) {
+        return send_abb_exec_command(cmd_args, error);
+    } else {
+        return unique_fd(adb_connect(android::base::Join(cmd_args, " "), error));
+    }
+}
+
 static int install_app_streamed(int argc, const char** argv, bool use_fastdeploy) {
     printf("Performing Streamed Install\n");
 
@@ -227,12 +235,7 @@
         cmd_args.push_back("--apex");
     }
 
-    unique_fd remote_fd;
-    if (use_abb_exec) {
-        remote_fd = send_abb_exec_command(cmd_args, &error);
-    } else {
-        remote_fd.reset(adb_connect(android::base::Join(cmd_args, " "), &error));
-    }
+    unique_fd remote_fd = send_command(cmd_args, &error);
     if (remote_fd < 0) {
         fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
         return 1;
@@ -548,24 +551,28 @@
 
     if (first_apk == -1) error_exit("need APK file on command line");
 
-    std::string install_cmd;
-    if (best_install_mode() == INSTALL_PUSH) {
-        install_cmd = "exec:pm";
-    } else {
-        install_cmd = "exec:cmd package";
-    }
+    const bool use_abb_exec = is_abb_exec_supported();
 
-    std::string cmd = android::base::StringPrintf("%s install-create -S %" PRIu64,
-                                                  install_cmd.c_str(), total_size);
+    const std::string install_cmd =
+            use_abb_exec ? "package"
+                         : best_install_mode() == INSTALL_PUSH ? "exec:pm" : "exec:cmd package";
+
+    std::vector<std::string> cmd_args = {install_cmd, "install-create", "-S",
+                                         std::to_string(total_size)};
+    cmd_args.reserve(first_apk + 4);
     for (int i = 1; i < first_apk; i++) {
-        cmd += " " + escape_arg(argv[i]);
+        if (use_abb_exec) {
+            cmd_args.push_back(argv[i]);
+        } else {
+            cmd_args.push_back(escape_arg(argv[i]));
+        }
     }
 
     // Create install session
     std::string error;
     char buf[BUFSIZ];
     {
-        unique_fd fd(adb_connect(cmd, &error));
+        unique_fd fd = send_command(cmd_args, &error);
         if (fd < 0) {
             fprintf(stderr, "adb: connect error for create: %s\n", error.c_str());
             return EXIT_FAILURE;
@@ -587,6 +594,7 @@
         fputs(buf, stderr);
         return EXIT_FAILURE;
     }
+    const auto session_id_str = std::to_string(session_id);
 
     // Valid session, now stream the APKs
     bool success = true;
@@ -599,10 +607,15 @@
             goto finalize_session;
         }
 
-        std::string cmd =
-                android::base::StringPrintf("%s install-write -S %" PRIu64 " %d %s -",
-                                            install_cmd.c_str(), static_cast<uint64_t>(sb.st_size),
-                                            session_id, android::base::Basename(file).c_str());
+        std::vector<std::string> cmd_args = {
+                install_cmd,
+                "install-write",
+                "-S",
+                std::to_string(sb.st_size),
+                session_id_str,
+                android::base::Basename(file),
+                "-",
+        };
 
         unique_fd local_fd(adb_open(file, O_RDONLY | O_CLOEXEC));
         if (local_fd < 0) {
@@ -612,7 +625,7 @@
         }
 
         std::string error;
-        unique_fd remote_fd(adb_connect(cmd, &error));
+        unique_fd remote_fd = send_command(cmd_args, &error);
         if (remote_fd < 0) {
             fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
             success = false;
@@ -637,10 +650,13 @@
 
 finalize_session:
     // Commit session if we streamed everything okay; otherwise abandon.
-    std::string service = android::base::StringPrintf("%s install-%s %d", install_cmd.c_str(),
-                                                      success ? "commit" : "abandon", session_id);
+    std::vector<std::string> service_args = {
+            install_cmd,
+            success ? "install-commit" : "install-abandon",
+            session_id_str,
+    };
     {
-        unique_fd fd(adb_connect(service, &error));
+        unique_fd fd = send_command(service_args, &error);
         if (fd < 0) {
             fprintf(stderr, "adb: connect error for finalize: %s\n", error.c_str());
             return EXIT_FAILURE;
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index b8eee4a..2553353 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -625,7 +625,7 @@
         if (!sm) {
             return device->WriteFail("Unable to create SnapshotManager");
         }
-        if (!sm->HandleImminentDataWipe()) {
+        if (!sm->FinishMergeInRecovery()) {
             return device->WriteFail("Unable to finish snapshot merge");
         }
     } else {
diff --git a/fs_mgr/libfiemap/image_test.cpp b/fs_mgr/libfiemap/image_test.cpp
index 5388b44..6663391 100644
--- a/fs_mgr/libfiemap/image_test.cpp
+++ b/fs_mgr/libfiemap/image_test.cpp
@@ -131,132 +131,6 @@
     ASSERT_TRUE(manager_->UnmapImageDevice(base_name_));
 }
 
-// This fixture is for tests against a simulated device environment. Rather
-// than use /data, we create an image and then layer a new filesystem within
-// it. Each test then decides how to mount and create layered images. This
-// allows us to test FBE vs FDE configurations.
-class ImageTest : public ::testing::Test {
-  public:
-    ImageTest() : dm_(DeviceMapper::Instance()) {}
-
-    void SetUp() override {
-        manager_ = ImageManager::Open(kMetadataPath, gDataPath);
-        ASSERT_NE(manager_, nullptr);
-
-        manager_->set_partition_opener(std::make_unique<TestPartitionOpener>());
-
-        submanager_ = ImageManager::Open(kMetadataPath + "/mnt"s, gDataPath + "/mnt"s);
-        ASSERT_NE(submanager_, nullptr);
-
-        submanager_->set_partition_opener(std::make_unique<TestPartitionOpener>());
-
-        // Ensure that metadata is cleared in between runs.
-        submanager_->RemoveAllImages();
-        manager_->RemoveAllImages();
-
-        const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
-        base_name_ = tinfo->name();
-        test_image_name_ = base_name_ + "-base";
-        wrapper_device_name_ = base_name_ + "-wrapper";
-
-        ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize * 16, false, nullptr));
-        ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &base_device_));
-    }
-
-    void TearDown() override {
-        submanager_->UnmapImageDevice(test_image_name_);
-        umount(gDataMountPath.c_str());
-        dm_.DeleteDeviceIfExists(wrapper_device_name_);
-        manager_->UnmapImageDevice(base_name_);
-        manager_->DeleteBackingImage(base_name_);
-    }
-
-  protected:
-    bool DoFormat(const std::string& device) {
-        // clang-format off
-        std::vector<std::string> mkfs_args = {
-            "/system/bin/mke2fs",
-            "-F",
-            "-b 4096",
-            "-t ext4",
-            "-m 0",
-            "-O has_journal",
-            device,
-            ">/dev/null",
-            "2>/dev/null",
-            "</dev/null",
-        };
-        // clang-format on
-        auto command = android::base::Join(mkfs_args, " ");
-        return system(command.c_str()) == 0;
-    }
-
-    std::unique_ptr<ImageManager> manager_;
-    std::unique_ptr<ImageManager> submanager_;
-
-    DeviceMapper& dm_;
-    std::string base_name_;
-    std::string base_device_;
-    std::string test_image_name_;
-    std::string wrapper_device_name_;
-};
-
-TEST_F(ImageTest, DirectMount) {
-    ASSERT_TRUE(DoFormat(base_device_));
-    ASSERT_EQ(mount(base_device_.c_str(), gDataMountPath.c_str(), "ext4", 0, nullptr), 0);
-    ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr));
-
-    std::string path;
-    ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path));
-    ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/loop"));
-}
-
-TEST_F(ImageTest, IndirectMount) {
-#ifdef SKIP_TEST_IN_PRESUBMIT
-    GTEST_SKIP() << "WIP failure b/148874852";
-#endif
-    // Create a simple wrapper around the base device that we'll mount from
-    // instead. This will simulate the code paths for dm-crypt/default-key/bow
-    // and force us to use device-mapper rather than loop devices.
-    uint64_t device_size = 0;
-    {
-        unique_fd fd(open(base_device_.c_str(), O_RDWR | O_CLOEXEC));
-        ASSERT_GE(fd, 0);
-        device_size = get_block_device_size(fd);
-        ASSERT_EQ(device_size, kTestImageSize * 16);
-    }
-    uint64_t num_sectors = device_size / 512;
-
-    auto& dm = DeviceMapper::Instance();
-
-    DmTable table;
-    table.Emplace<DmTargetLinear>(0, num_sectors, base_device_, 0);
-    ASSERT_TRUE(dm.CreateDevice(wrapper_device_name_, table));
-
-    // Format and mount.
-    std::string wrapper_device;
-    ASSERT_TRUE(dm.GetDmDevicePathByName(wrapper_device_name_, &wrapper_device));
-    ASSERT_TRUE(WaitForFile(wrapper_device, 5s));
-    ASSERT_TRUE(DoFormat(wrapper_device));
-    ASSERT_EQ(mount(wrapper_device.c_str(), gDataMountPath.c_str(), "ext4", 0, nullptr), 0);
-
-    ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr));
-
-    std::set<std::string> backing_devices;
-    auto init = [&](std::set<std::string> devices) -> bool {
-        backing_devices = std::move(devices);
-        return true;
-    };
-
-    std::string path;
-    ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path));
-    ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/dm-"));
-    ASSERT_TRUE(submanager_->UnmapImageDevice(test_image_name_));
-    ASSERT_TRUE(submanager_->MapAllImages(init));
-    ASSERT_FALSE(backing_devices.empty());
-    ASSERT_TRUE(submanager_->UnmapImageDevice(test_image_name_));
-}
-
 bool Mkdir(const std::string& path) {
     if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
         std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 1daa83b..5065508 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -223,6 +223,10 @@
     // optional callback fires periodically to query progress via GetUpdateState.
     bool HandleImminentDataWipe(const std::function<void()>& callback = {});
 
+    // Force a merge to complete in recovery. This is similar to HandleImminentDataWipe
+    // but does not expect a data wipe after.
+    bool FinishMergeInRecovery();
+
     // This method is only allowed in recovery and is used as a helper to
     // initialize the snapshot devices as a requirement to mount a snapshotted
     // /system in recovery.
@@ -541,6 +545,7 @@
     std::unique_ptr<IDeviceInfo> device_;
     std::unique_ptr<IImageManager> images_;
     bool has_local_image_manager_ = false;
+    bool in_factory_data_reset_ = false;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index efdb59f..0df5664 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -181,6 +181,13 @@
     ret.snapshot_status.set_device_size(target_partition->size());
     ret.snapshot_status.set_snapshot_size(target_partition->size());
 
+    if (ret.snapshot_status.snapshot_size() == 0) {
+        LOG(INFO) << "Not creating snapshot for partition " << ret.snapshot_status.name();
+        ret.snapshot_status.set_cow_partition_size(0);
+        ret.snapshot_status.set_cow_file_size(0);
+        return ret;
+    }
+
     // Being the COW partition virtual, its size doesn't affect the storage
     // memory that will be occupied by the target.
     // The actual storage space is affected by the COW file, whose size depends
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index c9fa28e..73efcfd 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -577,8 +577,16 @@
         return false;
     }
 
+    auto other_suffix = device_->GetOtherSlotSuffix();
+
     auto& dm = DeviceMapper::Instance();
     for (const auto& snapshot : snapshots) {
+        if (android::base::EndsWith(snapshot, other_suffix)) {
+            // Allow the merge to continue, but log this unexpected case.
+            LOG(ERROR) << "Unexpected snapshot found during merge: " << snapshot;
+            continue;
+        }
+
         // The device has to be mapped, since everything should be merged at
         // the same time. This is a fairly serious error. We could forcefully
         // map everything here, but it should have been mapped during first-
@@ -1008,6 +1016,15 @@
 }
 
 void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) {
+    // It's not possible to remove update state in recovery, so write an
+    // indicator that cleanup is needed on reboot. If a factory data reset
+    // was requested, it doesn't matter, everything will get wiped anyway.
+    // To make testing easier we consider a /data wipe as cleaned up.
+    if (device_->IsRecovery() && !in_factory_data_reset_) {
+        WriteUpdateState(lock, UpdateState::MergeCompleted);
+        return;
+    }
+
     RemoveAllUpdateState(lock);
 }
 
@@ -2528,7 +2545,43 @@
         }
         return true;
     };
-    if (!ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback)) {
+
+    in_factory_data_reset_ = true;
+    bool ok = ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback);
+    in_factory_data_reset_ = false;
+
+    if (!ok) {
+        return false;
+    }
+
+    // Nothing should be depending on partitions now, so unmap them all.
+    if (!UnmapAllPartitions()) {
+        LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash.";
+    }
+    return true;
+}
+
+bool SnapshotManager::FinishMergeInRecovery() {
+    if (!device_->IsRecovery()) {
+        LOG(ERROR) << "Data wipes are only allowed in recovery.";
+        return false;
+    }
+
+    auto mount = EnsureMetadataMounted();
+    if (!mount || !mount->HasDevice()) {
+        return false;
+    }
+
+    auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
+    auto super_path = device_->GetSuperDevice(slot_number);
+    if (!CreateLogicalAndSnapshotPartitions(super_path)) {
+        LOG(ERROR) << "Unable to map partitions to complete merge.";
+        return false;
+    }
+
+    UpdateState state = ProcessUpdateState();
+    if (state != UpdateState::MergeCompleted) {
+        LOG(ERROR) << "Merge returned unexpected status: " << state;
         return false;
     }
 
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index f82c082..9ca2412 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -1454,6 +1454,52 @@
     ASSERT_EQ(new_sm->GetUpdateState(), UpdateState::None);
 }
 
+// Test that a merge does not clear the snapshot state in fastboot.
+TEST_F(SnapshotUpdateTest, MergeInFastboot) {
+    // Execute the first update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+    ASSERT_TRUE(MapUpdateSnapshots());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // After reboot, init does first stage mount.
+    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    ASSERT_NE(init, nullptr);
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+    init = nullptr;
+
+    // Initiate the merge and then immediately stop it to simulate a reboot.
+    auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
+    ASSERT_TRUE(new_sm->InitiateMerge());
+    ASSERT_TRUE(UnmapAll());
+
+    // Simulate a reboot into recovery.
+    auto test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
+    test_device->set_recovery(true);
+    new_sm = SnapshotManager::NewForFirstStageMount(test_device.release());
+
+    ASSERT_TRUE(new_sm->FinishMergeInRecovery());
+
+    auto mount = new_sm->EnsureMetadataMounted();
+    ASSERT_TRUE(mount && mount->HasDevice());
+    ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted);
+
+    // Finish the merge in a normal boot.
+    test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
+    init = SnapshotManager::NewForFirstStageMount(test_device.release());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+    init = nullptr;
+
+    test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
+    new_sm = SnapshotManager::NewForFirstStageMount(test_device.release());
+    ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted);
+    ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::None);
+}
+
 // Test that after an OTA, before a merge, we can wipe data in recovery.
 TEST_F(SnapshotUpdateTest, DataWipeRollbackInRecovery) {
     // Execute the first update.
diff --git a/libcutils/trace-dev.inc b/libcutils/trace-dev.inc
index 3ec98b3..6543426 100644
--- a/libcutils/trace-dev.inc
+++ b/libcutils/trace-dev.inc
@@ -198,7 +198,7 @@
 }
 
 #define WRITE_MSG(format_begin, format_end, name, value) { \
-    char buf[ATRACE_MESSAGE_LENGTH]; \
+    char buf[ATRACE_MESSAGE_LENGTH] __attribute__((uninitialized));     \
     int pid = getpid(); \
     int len = snprintf(buf, sizeof(buf), format_begin "%s" format_end, pid, \
         name, value); \
diff --git a/libstats/push_compat/Android.bp b/libstats/push_compat/Android.bp
index 2f7212f..a63a5b6 100644
--- a/libstats/push_compat/Android.bp
+++ b/libstats/push_compat/Android.bp
@@ -50,6 +50,7 @@
     ],
     static_libs: ["libgtest_prod"],
     apex_available: ["com.android.resolv"],
+    min_sdk_version: "29",
 }
 
 cc_test {
diff --git a/libstats/socket/Android.bp b/libstats/socket/Android.bp
index e40a432..4e89b94 100644
--- a/libstats/socket/Android.bp
+++ b/libstats/socket/Android.bp
@@ -86,6 +86,7 @@
     export_include_dirs: ["include"],
     host_supported: true,
     apex_available: ["com.android.resolv"],
+    min_sdk_version: "29",
 }
 
 cc_benchmark {
diff --git a/libunwindstack/Maps.cpp b/libunwindstack/Maps.cpp
index 8f49ad9..670d904 100644
--- a/libunwindstack/Maps.cpp
+++ b/libunwindstack/Maps.cpp
@@ -159,6 +159,8 @@
         search_map_idx = old_map_idx + 1;
         if (new_map_idx + 1 < maps_.size()) {
           maps_[new_map_idx + 1]->prev_map = info.get();
+          maps_[new_map_idx + 1]->prev_real_map =
+              info->IsBlank() ? info->prev_real_map : info.get();
         }
         maps_[new_map_idx] = nullptr;
         total_entries--;
diff --git a/libunwindstack/tests/LocalUpdatableMapsTest.cpp b/libunwindstack/tests/LocalUpdatableMapsTest.cpp
index b816b9a..99afb0b 100644
--- a/libunwindstack/tests/LocalUpdatableMapsTest.cpp
+++ b/libunwindstack/tests/LocalUpdatableMapsTest.cpp
@@ -271,4 +271,103 @@
   EXPECT_TRUE(map_info->name.empty());
 }
 
+TEST_F(LocalUpdatableMapsTest, add_map_prev_name_updated) {
+  TemporaryFile tf;
+  ASSERT_TRUE(
+      android::base::WriteStringToFile("3000-4000 rwxp 00000 00:00 0\n"
+                                       "8000-9000 r-xp 00000 00:00 0\n"
+                                       "9000-a000 r-xp 00000 00:00 0\n",
+                                       tf.path));
+
+  maps_.TestSetMapsFile(tf.path);
+  ASSERT_TRUE(maps_.Reparse());
+  ASSERT_EQ(3U, maps_.Total());
+
+  MapInfo* map_info = maps_.Get(2);
+  ASSERT_TRUE(map_info != nullptr);
+  EXPECT_EQ(0x9000U, map_info->start);
+  EXPECT_EQ(0xA000U, map_info->end);
+  EXPECT_EQ(0U, map_info->offset);
+  EXPECT_EQ(PROT_READ | PROT_EXEC, map_info->flags);
+  EXPECT_TRUE(map_info->name.empty());
+  EXPECT_EQ(maps_.Get(1), map_info->prev_map);
+}
+
+TEST_F(LocalUpdatableMapsTest, add_map_prev_real_name_updated) {
+  TemporaryFile tf;
+  ASSERT_TRUE(
+      android::base::WriteStringToFile("3000-4000 r-xp 00000 00:00 0 /fake/lib.so\n"
+                                       "4000-5000 ---p 00000 00:00 0\n"
+                                       "7000-8000 r-xp 00000 00:00 0 /fake/lib1.so\n"
+                                       "8000-9000 ---p 00000 00:00 0\n",
+                                       tf.path));
+
+  maps_.TestSetMapsFile(tf.path);
+  ASSERT_TRUE(maps_.Reparse());
+  ASSERT_EQ(4U, maps_.Total());
+
+  MapInfo* map_info = maps_.Get(2);
+  ASSERT_TRUE(map_info != nullptr);
+  EXPECT_EQ(0x7000U, map_info->start);
+  EXPECT_EQ(0x8000U, map_info->end);
+  EXPECT_EQ(0U, map_info->offset);
+  EXPECT_EQ(PROT_READ | PROT_EXEC, map_info->flags);
+  EXPECT_EQ(maps_.Get(0), map_info->prev_real_map);
+  EXPECT_EQ(maps_.Get(1), map_info->prev_map);
+  EXPECT_EQ("/fake/lib1.so", map_info->name);
+
+  map_info = maps_.Get(3);
+  ASSERT_TRUE(map_info != nullptr);
+  EXPECT_EQ(0x8000U, map_info->start);
+  EXPECT_EQ(0x9000U, map_info->end);
+  EXPECT_EQ(0U, map_info->offset);
+  EXPECT_TRUE(map_info->IsBlank());
+  EXPECT_EQ(maps_.Get(2), map_info->prev_real_map);
+  EXPECT_EQ(maps_.Get(2), map_info->prev_map);
+  EXPECT_TRUE(map_info->name.empty());
+
+  ASSERT_TRUE(
+      android::base::WriteStringToFile("3000-4000 r-xp 00000 00:00 0 /fake/lib.so\n"
+                                       "4000-5000 ---p 00000 00:00 0\n"
+                                       "7000-8000 r-xp 00000 00:00 0 /fake/lib1.so\n"
+                                       "8000-9000 ---p 00000 00:00 0\n"
+                                       "9000-a000 r-xp 00000 00:00 0 /fake/lib2.so\n"
+                                       "a000-b000 r-xp 00000 00:00 0 /fake/lib3.so\n",
+                                       tf.path));
+
+  maps_.TestSetMapsFile(tf.path);
+  ASSERT_TRUE(maps_.Reparse());
+  ASSERT_EQ(6U, maps_.Total());
+
+  map_info = maps_.Get(2);
+  ASSERT_TRUE(map_info != nullptr);
+  EXPECT_EQ(0x7000U, map_info->start);
+  EXPECT_EQ(0x8000U, map_info->end);
+  EXPECT_EQ(0U, map_info->offset);
+  EXPECT_EQ(PROT_READ | PROT_EXEC, map_info->flags);
+  EXPECT_EQ("/fake/lib1.so", map_info->name);
+  EXPECT_EQ(maps_.Get(1), map_info->prev_map);
+  EXPECT_EQ(maps_.Get(0), map_info->prev_real_map);
+
+  map_info = maps_.Get(4);
+  ASSERT_TRUE(map_info != nullptr);
+  EXPECT_EQ(0x9000U, map_info->start);
+  EXPECT_EQ(0xA000U, map_info->end);
+  EXPECT_EQ(0U, map_info->offset);
+  EXPECT_EQ(PROT_READ | PROT_EXEC, map_info->flags);
+  EXPECT_EQ("/fake/lib2.so", map_info->name);
+  EXPECT_EQ(maps_.Get(3), map_info->prev_map);
+  EXPECT_EQ(maps_.Get(2), map_info->prev_real_map);
+
+  map_info = maps_.Get(5);
+  ASSERT_TRUE(map_info != nullptr);
+  EXPECT_EQ(0xA000U, map_info->start);
+  EXPECT_EQ(0xB000U, map_info->end);
+  EXPECT_EQ(0U, map_info->offset);
+  EXPECT_EQ(PROT_READ | PROT_EXEC, map_info->flags);
+  EXPECT_EQ("/fake/lib3.so", map_info->name);
+  EXPECT_EQ(maps_.Get(4), map_info->prev_map);
+  EXPECT_EQ(maps_.Get(4), map_info->prev_real_map);
+}
+
 }  // namespace unwindstack