Merge "Fix ARM program header values used for exidx."
diff --git a/adb/OVERVIEW.TXT b/adb/OVERVIEW.TXT
index 29a6992..f0b184c 100644
--- a/adb/OVERVIEW.TXT
+++ b/adb/OVERVIEW.TXT
@@ -103,9 +103,6 @@
            4-byte hex length, followed by a string giving the reason
            for failure.
 
-        3. As a special exception, for 'host:version', a 4-byte
-           hex string corresponding to the server's internal version number
-
     Note that the connection is still alive after an OKAY, which allows the
     client to make other requests. But in certain cases, an OKAY will even
     change the state of the connection.
diff --git a/adb/SERVICES.TXT b/adb/SERVICES.TXT
index 30c21f7..3e18a54 100644
--- a/adb/SERVICES.TXT
+++ b/adb/SERVICES.TXT
@@ -7,10 +7,6 @@
 host:version
     Ask the ADB server for its internal version number.
 
-    As a special exception, the server will respond with a 4-byte
-    hex string corresponding to its internal version number, without
-    any OKAY or FAIL.
-
 host:kill
     Ask the ADB server to quit immediately. This is used when the
     ADB client detects that an obsolete server is running after an
diff --git a/adb/fdevent.cpp b/adb/fdevent.cpp
index f98c11a..098a39d 100644
--- a/adb/fdevent.cpp
+++ b/adb/fdevent.cpp
@@ -35,7 +35,6 @@
 #include <unordered_map>
 #include <vector>
 
-#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/thread_annotations.h>
@@ -358,56 +357,10 @@
     }
 }
 
-static void fdevent_check_spin(uint64_t cycle) {
-    // Check to see if we're spinning because we forgot about an fdevent
-    // by keeping track of how long fdevents have been continuously pending.
-    struct SpinCheck {
-        fdevent* fde;
-        std::chrono::steady_clock::time_point timestamp;
-        uint64_t cycle;
-    };
-    static auto& g_continuously_pending = *new std::unordered_map<uint64_t, SpinCheck>();
-
-    auto now = std::chrono::steady_clock::now();
-    for (auto* fde : g_pending_list) {
-        auto it = g_continuously_pending.find(fde->id);
-        if (it == g_continuously_pending.end()) {
-            g_continuously_pending[fde->id] =
-                    SpinCheck{.fde = fde, .timestamp = now, .cycle = cycle};
-        } else {
-            it->second.cycle = cycle;
-        }
-    }
-
-    for (auto it = g_continuously_pending.begin(); it != g_continuously_pending.end();) {
-        if (it->second.cycle != cycle) {
-            it = g_continuously_pending.erase(it);
-        } else {
-            // Use an absurdly long window, since all we really care about is
-            // getting a bugreport eventually.
-            if (now - it->second.timestamp > std::chrono::minutes(5)) {
-                LOG(FATAL_WITHOUT_ABORT) << "detected spin in fdevent: " << dump_fde(it->second.fde);
-#if defined(__linux__)
-                int fd = it->second.fde->fd.get();
-                std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd);
-                std::string path;
-                if (!android::base::Readlink(fd_path, &path)) {
-                    PLOG(FATAL_WITHOUT_ABORT) << "readlink of fd " << fd << " failed";
-                }
-                LOG(FATAL_WITHOUT_ABORT) << "fd " << fd << " = " << path;
-#endif
-                abort();
-            }
-            ++it;
-        }
-    }
-}
-
 void fdevent_loop() {
     set_main_thread();
     fdevent_run_setup();
 
-    uint64_t cycle = 0;
     while (true) {
         if (terminate_loop) {
             return;
@@ -417,8 +370,6 @@
 
         fdevent_process();
 
-        fdevent_check_spin(cycle++);
-
         while (!g_pending_list.empty()) {
             fdevent* fde = g_pending_list.front();
             g_pending_list.pop_front();
diff --git a/adb/services.cpp b/adb/services.cpp
index b613d83..a757d90 100644
--- a/adb/services.cpp
+++ b/adb/services.cpp
@@ -181,29 +181,6 @@
     kick_transport(t);
 }
 
-static void spin_service(int fd, void*) {
-    unique_fd sfd(fd);
-
-    if (!__android_log_is_debuggable()) {
-        WriteFdExactly(sfd.get(), "refusing to spin on non-debuggable build\n");
-        return;
-    }
-
-    // A service that creates an fdevent that's always pending, and then ignores it.
-    unique_fd pipe_read, pipe_write;
-    if (!Pipe(&pipe_read, &pipe_write)) {
-        WriteFdExactly(sfd.get(), "failed to create pipe\n");
-        return;
-    }
-
-    fdevent_run_on_main_thread([fd = pipe_read.release()]() {
-        fdevent* fde = fdevent_create(fd, [](int, unsigned, void*) {}, nullptr);
-        fdevent_add(fde, FDE_READ);
-    });
-
-    WriteFdExactly(sfd.get(), "spinning\n");
-}
-
 int reverse_service(const char* command, atransport* transport) {
     int s[2];
     if (adb_socketpair(s)) {
@@ -351,8 +328,6 @@
                                     reinterpret_cast<void*>(1));
     } else if (!strcmp(name, "reconnect")) {
         ret = create_service_thread("reconnect", reconnect_service, transport);
-    } else if (!strcmp(name, "spin")) {
-        ret = create_service_thread("spin", spin_service, nullptr);
 #endif
     }
     if (ret >= 0) {
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index 8bd3b9d..b96f4c1 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -17,6 +17,7 @@
 #include "libdm/dm.h"
 
 #include <sys/ioctl.h>
+#include <sys/sysmacros.h>
 #include <sys/types.h>
 
 #include <android-base/macros.h>
@@ -258,8 +259,17 @@
 
 // Accepts a device mapper device name (like system_a, vendor_b etc) and
 // returns the path to it's device node (or symlink to the device node)
-std::string DeviceMapper::GetDmDevicePathByName(const std::string& /* name */) {
-    return "";
+bool DeviceMapper::GetDmDevicePathByName(const std::string& name, std::string* path) {
+    struct dm_ioctl io;
+    InitIo(&io, name);
+    if (ioctl(fd_, DM_DEV_STATUS, &io) < 0) {
+        PLOG(ERROR) << "DM_DEV_STATUS failed for " << name;
+        return false;
+    }
+
+    uint32_t dev_num = minor(io.dev);
+    *path = "/dev/block/dm-" + std::to_string(dev_num);
+    return true;
 }
 
 // private methods of DeviceMapper
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 8d23b2c..60bceed 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -104,8 +104,9 @@
     bool GetAvailableDevices(std::vector<DmBlockDevice>* devices);
 
     // Returns the path to the device mapper device node in '/dev' corresponding to
-    // 'name'.
-    std::string GetDmDevicePathByName(const std::string& name);
+    // 'name'. If the device does not exist, false is returned, and the path
+    // parameter is not set.
+    bool GetDmDevicePathByName(const std::string& name, std::string* path);
 
     // The only way to create a DeviceMapper object.
     static DeviceMapper& Instance();
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index 98f364c..f7086a8 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -55,6 +55,7 @@
     ],
     srcs: [
         "builder_test.cpp",
+        "io_test.cpp",
         "utility_test.cpp",
     ],
 }
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index c6e70e1..2983f0f 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -306,3 +306,21 @@
     unique_ptr<LpMetadata> exported = builder->Export();
     EXPECT_EQ(exported, nullptr);
 }
+
+TEST(liblp, MetadataTooLarge) {
+    static const size_t kDiskSize = 128 * 1024;
+    static const size_t kMetadataSize = 64 * 1024;
+
+    // No space to store metadata + geometry.
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize, kMetadataSize, 1);
+    EXPECT_EQ(builder, nullptr);
+
+    // No space to store metadata + geometry + one free sector.
+    builder = MetadataBuilder::New(kDiskSize + LP_METADATA_GEOMETRY_SIZE * 2, kMetadataSize, 1);
+    EXPECT_EQ(builder, nullptr);
+
+    // Space for metadata + geometry + one free sector.
+    builder = MetadataBuilder::New(kDiskSize + LP_METADATA_GEOMETRY_SIZE * 2 + LP_SECTOR_SIZE,
+                                   kMetadataSize, 1);
+    EXPECT_NE(builder, nullptr);
+}
diff --git a/fs_mgr/liblp/include/liblp/reader.h b/fs_mgr/liblp/include/liblp/reader.h
index e7fa46d..982fe65 100644
--- a/fs_mgr/liblp/include/liblp/reader.h
+++ b/fs_mgr/liblp/include/liblp/reader.h
@@ -29,13 +29,16 @@
 // Read logical partition metadata from its predetermined location on a block
 // device. If readback fails, we also attempt to load from a backup copy.
 std::unique_ptr<LpMetadata> ReadMetadata(const char* block_device, uint32_t slot_number);
+std::unique_ptr<LpMetadata> ReadMetadata(int fd, uint32_t slot_number);
 
 // Read and validate the logical partition geometry from a block device.
 bool ReadLogicalPartitionGeometry(const char* block_device, LpMetadataGeometry* geometry);
+bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry);
 
 // Read logical partition metadata from an image file that was created with
 // WriteToImageFile().
 std::unique_ptr<LpMetadata> ReadFromImageFile(const char* file);
+std::unique_ptr<LpMetadata> ReadFromImageFile(int fd);
 
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/liblp/include/liblp/writer.h b/fs_mgr/liblp/include/liblp/writer.h
index 02fb21f..efa409d 100644
--- a/fs_mgr/liblp/include/liblp/writer.h
+++ b/fs_mgr/liblp/include/liblp/writer.h
@@ -42,10 +42,13 @@
 // The slot number indicates which metadata slot to use.
 bool WritePartitionTable(const char* block_device, const LpMetadata& metadata, SyncMode sync_mode,
                          uint32_t slot_number);
+bool WritePartitionTable(int fd, const LpMetadata& metadata, SyncMode sync_mode,
+                         uint32_t slot_number);
 
 // Helper function to serialize geometry and metadata to a normal file, for
 // flashing or debugging.
 bool WriteToImageFile(const char* file, const LpMetadata& metadata);
+bool WriteToImageFile(int fd, const LpMetadata& metadata);
 
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp
new file mode 100644
index 0000000..2595654
--- /dev/null
+++ b/fs_mgr/liblp/io_test.cpp
@@ -0,0 +1,395 @@
+/*
+ * 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 <fcntl.h>
+#include <linux/memfd.h>
+#include <stdio.h>
+#include <sys/syscall.h>
+
+#include <android-base/file.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <liblp/builder.h>
+#include <liblp/reader.h>
+#include <liblp/writer.h>
+
+#include "utility.h"
+
+using namespace std;
+using namespace android::fs_mgr;
+using unique_fd = android::base::unique_fd;
+
+// Our tests assume a 128KiB disk with two 512 byte metadata slots.
+static const size_t kDiskSize = 131072;
+static const size_t kMetadataSize = 512;
+static const size_t kMetadataSlots = 2;
+static const char* TEST_GUID_BASE = "A799D1D6-669F-41D8-A3F0-EBB7572D830";
+static const char* TEST_GUID = "A799D1D6-669F-41D8-A3F0-EBB7572D8302";
+
+// Helper function for creating an in-memory file descriptor. This lets us
+// simulate read/writing logical partition metadata as if we had a block device
+// for a physical partition.
+static unique_fd CreateFakeDisk(off_t size) {
+    unique_fd fd(syscall(__NR_memfd_create, "fake_disk", MFD_ALLOW_SEALING));
+    if (fd < 0) {
+        perror("memfd_create");
+        return {};
+    }
+    if (ftruncate(fd, size) < 0) {
+        perror("ftruncate");
+        return {};
+    }
+    // Prevent anything from accidentally growing/shrinking the file, as it
+    // would not be allowed on an actual partition.
+    if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) {
+        perror("fcntl");
+        return {};
+    }
+    // Write garbage to the "disk" so we can tell what has been zeroed or not.
+    unique_ptr<uint8_t[]> buffer = make_unique<uint8_t[]>(size);
+    memset(buffer.get(), 0xcc, size);
+    if (!android::base::WriteFully(fd, buffer.get(), size)) {
+        return {};
+    }
+    return fd;
+}
+
+// Create a disk of the default size.
+static unique_fd CreateFakeDisk() {
+    return CreateFakeDisk(kDiskSize);
+}
+
+// Create a MetadataBuilder around some default sizes.
+static unique_ptr<MetadataBuilder> CreateDefaultBuilder() {
+    unique_ptr<MetadataBuilder> builder =
+            MetadataBuilder::New(kDiskSize, kMetadataSize, kMetadataSlots);
+    return builder;
+}
+
+static bool AddDefaultPartitions(MetadataBuilder* builder) {
+    Partition* system = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_NONE);
+    if (!system) {
+        return false;
+    }
+    return builder->GrowPartition(system, 24 * 1024);
+}
+
+// Create a temporary disk and flash it with the default partition setup.
+static unique_fd CreateFlashedDisk() {
+    unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
+    if (!builder || !AddDefaultPartitions(builder.get())) {
+        return {};
+    }
+    unique_fd fd = CreateFakeDisk();
+    if (fd < 0) {
+        return {};
+    }
+    // Export and flash.
+    unique_ptr<LpMetadata> exported = builder->Export();
+    if (!exported) {
+        return {};
+    }
+    if (!WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0)) {
+        return {};
+    }
+    return fd;
+}
+
+// Test that our CreateFakeDisk() function works.
+TEST(liblp, CreateFakeDisk) {
+    unique_fd fd = CreateFakeDisk();
+    ASSERT_GE(fd, 0);
+
+    uint64_t size;
+    ASSERT_TRUE(GetDescriptorSize(fd, &size));
+    ASSERT_EQ(size, kDiskSize);
+}
+
+// Flashing metadata should not work if the metadata was created for a larger
+// disk than the destination disk.
+TEST(liblp, ExportDiskTooSmall) {
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize + 1024, 512, 2);
+    ASSERT_NE(builder, nullptr);
+    unique_ptr<LpMetadata> exported = builder->Export();
+    ASSERT_NE(exported, nullptr);
+
+    // A larger geometry should fail to flash, since there won't be enough
+    // space to store the logical partition range that was specified.
+    unique_fd fd = CreateFakeDisk();
+    ASSERT_GE(fd, 0);
+
+    EXPECT_FALSE(WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0));
+}
+
+// Test the basics of flashing a partition and reading it back.
+TEST(liblp, FlashAndReadback) {
+    unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
+    ASSERT_NE(builder, nullptr);
+    ASSERT_TRUE(AddDefaultPartitions(builder.get()));
+
+    unique_fd fd = CreateFakeDisk();
+    ASSERT_GE(fd, 0);
+
+    // Export and flash.
+    unique_ptr<LpMetadata> exported = builder->Export();
+    ASSERT_NE(exported, nullptr);
+    ASSERT_TRUE(WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0));
+
+    // Read back. Note that some fields are only filled in during
+    // serialization, so exported and imported will not be identical. For
+    // example, table sizes and checksums are computed in WritePartitionTable.
+    // Therefore we check on a field-by-field basis.
+    unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0);
+    ASSERT_NE(imported, nullptr);
+
+    // Check geometry and header.
+    EXPECT_EQ(exported->geometry.metadata_max_size, imported->geometry.metadata_max_size);
+    EXPECT_EQ(exported->geometry.metadata_slot_count, imported->geometry.metadata_slot_count);
+    EXPECT_EQ(exported->geometry.first_logical_sector, imported->geometry.first_logical_sector);
+    EXPECT_EQ(exported->geometry.last_logical_sector, imported->geometry.last_logical_sector);
+    EXPECT_EQ(exported->header.major_version, imported->header.major_version);
+    EXPECT_EQ(exported->header.minor_version, imported->header.minor_version);
+    EXPECT_EQ(exported->header.header_size, imported->header.header_size);
+
+    // Check partition tables.
+    ASSERT_EQ(exported->partitions.size(), imported->partitions.size());
+    EXPECT_EQ(GetPartitionName(exported->partitions[0]), GetPartitionName(imported->partitions[0]));
+    EXPECT_EQ(GetPartitionGuid(exported->partitions[0]), GetPartitionGuid(imported->partitions[0]));
+    EXPECT_EQ(exported->partitions[0].attributes, imported->partitions[0].attributes);
+    EXPECT_EQ(exported->partitions[0].first_extent_index,
+              imported->partitions[0].first_extent_index);
+    EXPECT_EQ(exported->partitions[0].num_extents, imported->partitions[0].num_extents);
+
+    // Check extent tables.
+    ASSERT_EQ(exported->extents.size(), imported->extents.size());
+    EXPECT_EQ(exported->extents[0].num_sectors, imported->extents[0].num_sectors);
+    EXPECT_EQ(exported->extents[0].target_type, imported->extents[0].target_type);
+    EXPECT_EQ(exported->extents[0].target_data, imported->extents[0].target_data);
+}
+
+// Test that we can update metadata slots without disturbing others.
+TEST(liblp, UpdateAnyMetadataSlot) {
+    unique_fd fd = CreateFlashedDisk();
+    ASSERT_GE(fd, 0);
+
+    unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0);
+    ASSERT_NE(imported, nullptr);
+    ASSERT_EQ(imported->partitions.size(), 1);
+    EXPECT_EQ(GetPartitionName(imported->partitions[0]), "system");
+
+    // Verify that we can't read unwritten metadata.
+    ASSERT_EQ(ReadMetadata(fd, 1), nullptr);
+
+    // Change the name before writing to the next slot.
+    strncpy(imported->partitions[0].name, "vendor", sizeof(imported->partitions[0].name));
+    ASSERT_TRUE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1));
+
+    // Read back the original slot, make sure it hasn't changed.
+    imported = ReadMetadata(fd, 0);
+    ASSERT_NE(imported, nullptr);
+    ASSERT_EQ(imported->partitions.size(), 1);
+    EXPECT_EQ(GetPartitionName(imported->partitions[0]), "system");
+
+    // Now read back the new slot, and verify that it has a different name.
+    imported = ReadMetadata(fd, 1);
+    ASSERT_NE(imported, nullptr);
+    ASSERT_EQ(imported->partitions.size(), 1);
+    EXPECT_EQ(GetPartitionName(imported->partitions[0]), "vendor");
+
+    // Verify that we didn't overwrite anything in the logical paritition area.
+    // We expect the disk to be filled with 0xcc on creation so we can read
+    // this back and compare it.
+    char expected[LP_SECTOR_SIZE];
+    memset(expected, 0xcc, sizeof(expected));
+    for (uint64_t i = imported->geometry.first_logical_sector;
+         i <= imported->geometry.last_logical_sector; i++) {
+        char buffer[LP_SECTOR_SIZE];
+        ASSERT_GE(lseek(fd, i * LP_SECTOR_SIZE, SEEK_SET), 0);
+        ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer)));
+        ASSERT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0);
+    }
+}
+
+TEST(liblp, InvalidMetadataSlot) {
+    unique_fd fd = CreateFlashedDisk();
+    ASSERT_GE(fd, 0);
+
+    // Make sure all slots are filled.
+    unique_ptr<LpMetadata> metadata = ReadMetadata(fd, 0);
+    ASSERT_NE(metadata, nullptr);
+    for (uint32_t i = 1; i < kMetadataSlots; i++) {
+        ASSERT_TRUE(WritePartitionTable(fd, *metadata.get(), SyncMode::Update, i));
+    }
+
+    // Verify that we can't read unavailable slots.
+    EXPECT_EQ(ReadMetadata(fd, kMetadataSlots), nullptr);
+}
+
+// Test that updating a metadata slot does not allow it to be computed based
+// on mismatching geometry.
+TEST(liblp, NoChangingGeometry) {
+    unique_fd fd = CreateFlashedDisk();
+    ASSERT_GE(fd, 0);
+
+    unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0);
+    ASSERT_NE(imported, nullptr);
+    ASSERT_TRUE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1));
+
+    imported->geometry.metadata_max_size += LP_SECTOR_SIZE;
+    ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1));
+
+    imported = ReadMetadata(fd, 0);
+    ASSERT_NE(imported, nullptr);
+    imported->geometry.metadata_slot_count++;
+    ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1));
+
+    imported = ReadMetadata(fd, 0);
+    ASSERT_NE(imported, nullptr);
+    imported->geometry.first_logical_sector++;
+    ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1));
+
+    imported = ReadMetadata(fd, 0);
+    ASSERT_NE(imported, nullptr);
+    imported->geometry.last_logical_sector--;
+    ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1));
+}
+
+// Test that changing one bit of metadata is enough to break the checksum.
+TEST(liblp, BitFlipGeometry) {
+    unique_fd fd = CreateFlashedDisk();
+    ASSERT_GE(fd, 0);
+
+    LpMetadataGeometry geometry;
+    ASSERT_GE(lseek(fd, 0, SEEK_SET), 0);
+    ASSERT_TRUE(android::base::ReadFully(fd, &geometry, sizeof(geometry)));
+
+    LpMetadataGeometry bad_geometry = geometry;
+    bad_geometry.metadata_slot_count++;
+    ASSERT_TRUE(android::base::WriteFully(fd, &bad_geometry, sizeof(bad_geometry)));
+
+    unique_ptr<LpMetadata> metadata = ReadMetadata(fd, 0);
+    ASSERT_NE(metadata, nullptr);
+    EXPECT_EQ(metadata->geometry.metadata_slot_count, 2);
+}
+
+TEST(liblp, ReadBackupGeometry) {
+    unique_fd fd = CreateFlashedDisk();
+    ASSERT_GE(fd, 0);
+
+    char corruption[LP_METADATA_GEOMETRY_SIZE];
+    memset(corruption, 0xff, sizeof(corruption));
+
+    // Corrupt the first 4096 bytes of the disk.
+    ASSERT_GE(lseek(fd, 0, SEEK_SET), 0);
+    ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption)));
+    EXPECT_NE(ReadMetadata(fd, 0), nullptr);
+
+    // Corrupt the last 4096 bytes too.
+    ASSERT_GE(lseek(fd, -LP_METADATA_GEOMETRY_SIZE, SEEK_END), 0);
+    ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption)));
+    EXPECT_EQ(ReadMetadata(fd, 0), nullptr);
+}
+
+TEST(liblp, ReadBackupMetadata) {
+    unique_fd fd = CreateFlashedDisk();
+    ASSERT_GE(fd, 0);
+
+    unique_ptr<LpMetadata> metadata = ReadMetadata(fd, 0);
+
+    char corruption[kMetadataSize];
+    memset(corruption, 0xff, sizeof(corruption));
+
+    ASSERT_GE(lseek(fd, LP_METADATA_GEOMETRY_SIZE, SEEK_SET), 0);
+    ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption)));
+    EXPECT_NE(ReadMetadata(fd, 0), nullptr);
+
+    off_t offset = LP_METADATA_GEOMETRY_SIZE + kMetadataSize * 2;
+
+    // Corrupt the backup metadata.
+    ASSERT_GE(lseek(fd, -offset, SEEK_END), 0);
+    ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption)));
+    EXPECT_EQ(ReadMetadata(fd, 0), nullptr);
+}
+
+// Test that we don't attempt to write metadata if it would overflow its
+// reserved space.
+TEST(liblp, TooManyPartitions) {
+    unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
+    ASSERT_NE(builder, nullptr);
+
+    // Compute the maximum number of partitions we can fit in 1024 bytes of metadata.
+    size_t max_partitions = (kMetadataSize - sizeof(LpMetadataHeader)) / sizeof(LpMetadataPartition);
+    EXPECT_LT(max_partitions, 10);
+
+    // Add this number of partitions.
+    Partition* partition = nullptr;
+    for (size_t i = 0; i < max_partitions; i++) {
+        std::string guid = std::string(TEST_GUID) + to_string(i);
+        partition = builder->AddPartition(to_string(i), TEST_GUID, LP_PARTITION_ATTR_NONE);
+        ASSERT_NE(partition, nullptr);
+    }
+    ASSERT_NE(partition, nullptr);
+    // Add one extent to any partition to fill up more space - we're at 508
+    // bytes after this, out of 512.
+    ASSERT_TRUE(builder->GrowPartition(partition, 1024));
+
+    unique_ptr<LpMetadata> exported = builder->Export();
+    ASSERT_NE(exported, nullptr);
+
+    unique_fd fd = CreateFakeDisk();
+    ASSERT_GE(fd, 0);
+
+    // Check that we are able to write our table.
+    ASSERT_TRUE(WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0));
+    ASSERT_TRUE(WritePartitionTable(fd, *exported.get(), SyncMode::Update, 1));
+
+    // Check that adding one more partition overflows the metadata allotment.
+    partition = builder->AddPartition("final", TEST_GUID, LP_PARTITION_ATTR_NONE);
+    EXPECT_NE(partition, nullptr);
+
+    exported = builder->Export();
+    ASSERT_NE(exported, nullptr);
+
+    // The new table should be too large to be written.
+    ASSERT_FALSE(WritePartitionTable(fd, *exported.get(), SyncMode::Update, 1));
+
+    // Check that the first and last logical sectors weren't touched when we
+    // wrote this almost-full metadata.
+    char expected[LP_SECTOR_SIZE];
+    memset(expected, 0xcc, sizeof(expected));
+    char buffer[LP_SECTOR_SIZE];
+    ASSERT_GE(lseek(fd, exported->geometry.first_logical_sector * LP_SECTOR_SIZE, SEEK_SET), 0);
+    ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer)));
+    EXPECT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0);
+    ASSERT_GE(lseek(fd, exported->geometry.last_logical_sector * LP_SECTOR_SIZE, SEEK_SET), 0);
+    ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer)));
+    EXPECT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0);
+}
+
+// Test that we can read and write image files.
+TEST(liblp, ImageFiles) {
+    unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
+    ASSERT_NE(builder, nullptr);
+    ASSERT_TRUE(AddDefaultPartitions(builder.get()));
+    unique_ptr<LpMetadata> exported = builder->Export();
+
+    unique_fd fd(syscall(__NR_memfd_create, "image_file", 0));
+    ASSERT_GE(fd, 0);
+    ASSERT_TRUE(WriteToImageFile(fd, *exported.get()));
+
+    unique_ptr<LpMetadata> imported = ReadFromImageFile(fd);
+    ASSERT_NE(imported, nullptr);
+}
diff --git a/fs_mgr/liblp/reader.cpp b/fs_mgr/liblp/reader.cpp
index 328fe37..7938186 100644
--- a/fs_mgr/liblp/reader.cpp
+++ b/fs_mgr/liblp/reader.cpp
@@ -76,7 +76,7 @@
 // Read and validate geometry information from a block device that holds
 // logical partitions. If the information is corrupted, this will attempt
 // to read it from a secondary backup location.
-static bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry) {
+bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry) {
     // Read the first 4096 bytes.
     std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(LP_METADATA_GEOMETRY_SIZE);
     if (SeekFile64(fd, 0, SEEK_SET) < 0) {
@@ -236,43 +236,51 @@
     return metadata;
 }
 
-std::unique_ptr<LpMetadata> ReadMetadata(const char* block_device, uint32_t slot_number) {
-    android::base::unique_fd fd(open(block_device, O_RDONLY));
-    if (fd < 0) {
-        PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device;
-        return nullptr;
-    }
+std::unique_ptr<LpMetadata> ReadMetadata(int fd, uint32_t slot_number) {
     LpMetadataGeometry geometry;
     if (!ReadLogicalPartitionGeometry(fd, &geometry)) {
         return nullptr;
     }
 
+    if (slot_number >= geometry.metadata_slot_count) {
+        LERROR << __PRETTY_FUNCTION__ << "invalid metadata slot number";
+        return nullptr;
+    }
+
     // First try the primary copy.
     int64_t offset = GetPrimaryMetadataOffset(geometry, slot_number);
     if (SeekFile64(fd, offset, SEEK_SET) < 0) {
         PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << offset;
         return nullptr;
     }
-    if (std::unique_ptr<LpMetadata> metadata = ParseMetadata(fd)) {
-        return metadata;
+    std::unique_ptr<LpMetadata> metadata = ParseMetadata(fd);
+
+    // If the primary copy failed, try the backup copy.
+    if (!metadata) {
+        offset = GetBackupMetadataOffset(geometry, slot_number);
+        if (SeekFile64(fd, offset, SEEK_END) < 0) {
+            PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << offset;
+            return nullptr;
+        }
+        metadata = ParseMetadata(fd);
     }
 
-    // Next try the backup copy.
-    offset = GetBackupMetadataOffset(geometry, slot_number);
-    if (SeekFile64(fd, offset, SEEK_END) < 0) {
-        PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << offset;
-        return nullptr;
+    if (metadata) {
+        metadata->geometry = geometry;
     }
-    return ParseMetadata(fd);
+    return metadata;
 }
 
-std::unique_ptr<LpMetadata> ReadFromImageFile(const char* file) {
-    android::base::unique_fd fd(open(file, O_RDONLY));
+std::unique_ptr<LpMetadata> ReadMetadata(const char* block_device, uint32_t slot_number) {
+    android::base::unique_fd fd(open(block_device, O_RDONLY));
     if (fd < 0) {
-        PERROR << __PRETTY_FUNCTION__ << "open failed: " << file;
+        PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device;
         return nullptr;
     }
+    return ReadMetadata(fd, slot_number);
+}
 
+std::unique_ptr<LpMetadata> ReadFromImageFile(int fd) {
     LpMetadataGeometry geometry;
     if (!ReadLogicalPartitionGeometry(fd, &geometry)) {
         return nullptr;
@@ -289,6 +297,15 @@
     return metadata;
 }
 
+std::unique_ptr<LpMetadata> ReadFromImageFile(const char* file) {
+    android::base::unique_fd fd(open(file, O_RDONLY));
+    if (fd < 0) {
+        PERROR << __PRETTY_FUNCTION__ << "open failed: " << file;
+        return nullptr;
+    }
+    return ReadFromImageFile(fd);
+}
+
 static std::string NameFromFixedArray(const char* name, size_t buffer_size) {
     // If the end of the buffer has a null character, it's safe to assume the
     // buffer is null terminated. Otherwise, we cap the string to the input
diff --git a/fs_mgr/liblp/writer.cpp b/fs_mgr/liblp/writer.cpp
index 6a9c124..89cbabd 100644
--- a/fs_mgr/liblp/writer.cpp
+++ b/fs_mgr/liblp/writer.cpp
@@ -124,14 +124,8 @@
     return true;
 }
 
-bool WritePartitionTable(const char* block_device, const LpMetadata& metadata, SyncMode sync_mode,
+bool WritePartitionTable(int fd, const LpMetadata& metadata, SyncMode sync_mode,
                          uint32_t slot_number) {
-    android::base::unique_fd fd(open(block_device, O_RDWR | O_SYNC));
-    if (fd < 0) {
-        PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device;
-        return false;
-    }
-
     uint64_t size;
     if (!GetDescriptorSize(fd, &size)) {
         return false;
@@ -142,7 +136,7 @@
         // Verify that the old geometry is identical. If it's not, then we've
         // based this new metadata on invalid assumptions.
         LpMetadataGeometry old_geometry;
-        if (!ReadLogicalPartitionGeometry(block_device, &old_geometry)) {
+        if (!ReadLogicalPartitionGeometry(fd, &old_geometry)) {
             return false;
         }
         if (!CompareGeometry(geometry, old_geometry)) {
@@ -174,8 +168,7 @@
             return false;
         }
         if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
-            PERROR << __PRETTY_FUNCTION__ << "write " << blob.size()
-                   << " bytes failed: " << block_device;
+            PERROR << __PRETTY_FUNCTION__ << "write " << blob.size() << " bytes failed";
             return false;
         }
         if (SeekFile64(fd, -LP_METADATA_GEOMETRY_SIZE, SEEK_END) < 0) {
@@ -183,8 +176,7 @@
             return false;
         }
         if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
-            PERROR << __PRETTY_FUNCTION__ << "backup write " << blob.size()
-                   << " bytes failed: " << block_device;
+            PERROR << __PRETTY_FUNCTION__ << "backup write " << blob.size() << " bytes failed";
             return false;
         }
     }
@@ -196,8 +188,7 @@
         return false;
     }
     if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
-        PERROR << __PRETTY_FUNCTION__ << "write " << blob.size()
-               << " bytes failed: " << block_device;
+        PERROR << __PRETTY_FUNCTION__ << "write " << blob.size() << " bytes failed";
         return false;
     }
 
@@ -214,30 +205,43 @@
         return false;
     }
     if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
-        PERROR << __PRETTY_FUNCTION__ << "backup write " << blob.size()
-               << " bytes failed: " << block_device;
+        PERROR << __PRETTY_FUNCTION__ << "backup write " << blob.size() << " bytes failed";
         return false;
     }
     return true;
 }
 
-bool WriteToImageFile(const char* file, const LpMetadata& input) {
+bool WritePartitionTable(const char* block_device, const LpMetadata& metadata, SyncMode sync_mode,
+                         uint32_t slot_number) {
+    android::base::unique_fd fd(open(block_device, O_RDWR | O_SYNC));
+    if (fd < 0) {
+        PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device;
+        return false;
+    }
+    return WritePartitionTable(fd, metadata, sync_mode, slot_number);
+}
+
+bool WriteToImageFile(int fd, const LpMetadata& input) {
     std::string geometry = SerializeGeometry(input.geometry);
     std::string padding(LP_METADATA_GEOMETRY_SIZE - geometry.size(), '\0');
     std::string metadata = SerializeMetadata(input);
 
     std::string everything = geometry + padding + metadata;
 
+    if (!android::base::WriteFully(fd, everything.data(), everything.size())) {
+        PERROR << __PRETTY_FUNCTION__ << "write " << everything.size() << " bytes failed";
+        return false;
+    }
+    return true;
+}
+
+bool WriteToImageFile(const char* file, const LpMetadata& input) {
     android::base::unique_fd fd(open(file, O_CREAT | O_RDWR | O_TRUNC, 0644));
     if (fd < 0) {
         PERROR << __PRETTY_FUNCTION__ << "open failed: " << file;
         return false;
     }
-    if (!android::base::WriteFully(fd, everything.data(), everything.size())) {
-        PERROR << __PRETTY_FUNCTION__ << "write " << everything.size() << " bytes failed: " << file;
-        return false;
-    }
-    return true;
+    return WriteToImageFile(fd, input);
 }
 
 }  // namespace fs_mgr
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
index c15173f..9d48b8c 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -49,6 +49,7 @@
     std::cerr << "  create <dm-name> [-ro] <targets...>" << std::endl;
     std::cerr << "  delete <dm-name>" << std::endl;
     std::cerr << "  list <devices | targets>" << std::endl;
+    std::cerr << "  getpath <dm-name>" << std::endl;
     std::cerr << "  help" << std::endl;
     std::cerr << std::endl;
     std::cerr << "Target syntax:" << std::endl;
@@ -241,11 +242,30 @@
     return 0;
 }
 
+static int GetPathCmdHandler(int argc, char** argv) {
+    if (argc != 1) {
+        std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+        return -EINVAL;
+    }
+
+    DeviceMapper& dm = DeviceMapper::Instance();
+    std::string path;
+    if (!dm.GetDmDevicePathByName(argv[0], &path)) {
+        std::cerr << "Could not query path of device \"" << argv[0] << "\"." << std::endl;
+        return -EINVAL;
+    }
+    std::cout << path << std::endl;
+    return 0;
+}
+
 static std::map<std::string, std::function<int(int, char**)>> cmdmap = {
+        // clang-format off
         {"create", DmCreateCmdHandler},
         {"delete", DmDeleteCmdHandler},
         {"list", DmListCmdHandler},
         {"help", HelpCmdHandler},
+        {"getpath", GetPathCmdHandler},
+        // clang-format on
 };
 
 int main(int argc, char** argv) {