Merge "adb: fix obsolete documentation."
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/Android.bp b/fs_mgr/libdm/Android.bp
index 672d401..f1b18dd 100644
--- a/fs_mgr/libdm/Android.bp
+++ b/fs_mgr/libdm/Android.bp
@@ -16,6 +16,7 @@
 
 cc_library_static {
     name: "libdm",
+    defaults: ["fs_mgr_defaults"],
     recovery_available: true,
 
     export_include_dirs: ["include"],
@@ -35,3 +36,16 @@
         "liblog_headers",
     ],
 }
+
+cc_test {
+    name: "libdm_test",
+    defaults: ["fs_mgr_defaults"],
+    static_libs: [
+        "libdm",
+        "libbase",
+        "liblog",
+    ],
+    srcs: [
+        "dm_test.cpp",
+    ]
+}
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index 57c1270..b96f4c1 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -14,23 +14,13 @@
  * limitations under the License.
  */
 
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/dm-ioctl.h>
-#include <stdint.h>
+#include "libdm/dm.h"
+
 #include <sys/ioctl.h>
+#include <sys/sysmacros.h>
 #include <sys/types.h>
-#include <unistd.h>
 
-#include <memory>
-#include <string>
-#include <vector>
-
-#include <android-base/logging.h>
 #include <android-base/macros.h>
-#include <android-base/unique_fd.h>
-
-#include "dm.h"
 
 namespace android {
 namespace dm {
@@ -51,23 +41,18 @@
         return false;
     }
 
-    std::unique_ptr<struct dm_ioctl, decltype(&free)> io(
-            static_cast<struct dm_ioctl*>(malloc(sizeof(struct dm_ioctl))), free);
-    if (io == nullptr) {
-        LOG(ERROR) << "Failed to allocate dm_ioctl";
-        return false;
-    }
-    InitIo(io.get(), name);
+    struct dm_ioctl io;
+    InitIo(&io, name);
 
-    if (ioctl(fd_, DM_DEV_CREATE, io.get())) {
-        PLOG(ERROR) << "DM_DEV_CREATE failed to create [" << name << "]";
+    if (ioctl(fd_, DM_DEV_CREATE, &io)) {
+        PLOG(ERROR) << "DM_DEV_CREATE failed for [" << name << "]";
         return false;
     }
 
     // Check to make sure the newly created device doesn't already have targets
     // added or opened by someone
-    CHECK(io->target_count == 0) << "Unexpected targets for newly created [" << name << "] device";
-    CHECK(io->open_count == 0) << "Unexpected opens for newly created [" << name << "] device";
+    CHECK(io.target_count == 0) << "Unexpected targets for newly created [" << name << "] device";
+    CHECK(io.open_count == 0) << "Unexpected opens for newly created [" << name << "] device";
 
     // Creates a new device mapper device with the name passed in
     return true;
@@ -84,22 +69,17 @@
         return false;
     }
 
-    std::unique_ptr<struct dm_ioctl, decltype(&free)> io(
-            static_cast<struct dm_ioctl*>(malloc(sizeof(struct dm_ioctl))), free);
-    if (io == nullptr) {
-        LOG(ERROR) << "Failed to allocate dm_ioctl";
-        return false;
-    }
-    InitIo(io.get(), name);
+    struct dm_ioctl io;
+    InitIo(&io, name);
 
-    if (ioctl(fd_, DM_DEV_REMOVE, io.get())) {
-        PLOG(ERROR) << "DM_DEV_REMOVE failed to create [" << name << "]";
+    if (ioctl(fd_, DM_DEV_REMOVE, &io)) {
+        PLOG(ERROR) << "DM_DEV_REMOVE failed for [" << name << "]";
         return false;
     }
 
     // Check to make sure appropriate uevent is generated so ueventd will
     // do the right thing and remove the corresponding device node and symlinks.
-    CHECK(io->flags & DM_UEVENT_GENERATED_FLAG)
+    CHECK(io.flags & DM_UEVENT_GENERATED_FLAG)
             << "Didn't generate uevent for [" << name << "] removal";
 
     return true;
@@ -115,13 +95,45 @@
     return DmDeviceState::INVALID;
 }
 
-bool DeviceMapper::LoadTableAndActivate(const std::string& /* name */, const DmTable& /* table */) {
-    return false;
+bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table) {
+    if (!CreateDevice(name)) {
+        return false;
+    }
+    if (!LoadTableAndActivate(name, table)) {
+        DeleteDevice(name);
+        return false;
+    }
+    return true;
+}
+
+bool DeviceMapper::LoadTableAndActivate(const std::string& name, const DmTable& table) {
+    std::string ioctl_buffer(sizeof(struct dm_ioctl), 0);
+    ioctl_buffer += table.Serialize();
+
+    struct dm_ioctl* io = reinterpret_cast<struct dm_ioctl*>(&ioctl_buffer[0]);
+    InitIo(io, name);
+    io->data_size = ioctl_buffer.size();
+    io->data_start = sizeof(struct dm_ioctl);
+    io->target_count = static_cast<uint32_t>(table.num_targets());
+    if (table.readonly()) {
+        io->flags |= DM_READONLY_FLAG;
+    }
+    if (ioctl(fd_, DM_TABLE_LOAD, io)) {
+        PLOG(ERROR) << "DM_TABLE_LOAD failed";
+        return false;
+    }
+
+    InitIo(io, name);
+    if (ioctl(fd_, DM_DEV_SUSPEND, io)) {
+        PLOG(ERROR) << "DM_TABLE_SUSPEND resume failed";
+        return false;
+    }
+    return true;
 }
 
 // Reads all the available device mapper targets and their corresponding
 // versions from the kernel and returns in a vector
-bool DeviceMapper::GetAvailableTargets(std::vector<DmTarget>* targets) {
+bool DeviceMapper::GetAvailableTargets(std::vector<DmTargetTypeInfo>* targets) {
     targets->clear();
 
     // calculate the space needed to read a maximum of kMaxPossibleDmTargets
@@ -147,7 +159,7 @@
     io->data_start = sizeof(*io);
 
     if (ioctl(fd_, DM_LIST_VERSIONS, io)) {
-        PLOG(ERROR) << "Failed to get DM_LIST_VERSIONS from kernel";
+        PLOG(ERROR) << "DM_LIST_VERSIONS failed";
         return false;
     }
 
@@ -170,7 +182,7 @@
     struct dm_target_versions* vers =
             reinterpret_cast<struct dm_target_versions*>(static_cast<char*>(buffer.get()) + next);
     while (next && data_size) {
-        targets->emplace_back((vers));
+        targets->emplace_back(vers);
         if (vers->next == 0) {
             break;
         }
@@ -209,7 +221,7 @@
     io->data_start = sizeof(*io);
 
     if (ioctl(fd_, DM_LIST_DEVICES, io)) {
-        PLOG(ERROR) << "Failed to get DM_LIST_DEVICES from kernel";
+        PLOG(ERROR) << "DM_LIST_DEVICES failed";
         return false;
     }
 
@@ -247,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/dm_table.cpp b/fs_mgr/libdm/dm_table.cpp
index 14b3932..cb6f210 100644
--- a/fs_mgr/libdm/dm_table.cpp
+++ b/fs_mgr/libdm/dm_table.cpp
@@ -14,18 +14,16 @@
  * limitations under the License.
  */
 
+#include "libdm/dm_table.h"
+
 #include <android-base/logging.h>
 #include <android-base/macros.h>
 
-#include <string>
-#include <vector>
-
-#include "dm_table.h"
-
 namespace android {
 namespace dm {
 
-bool DmTable::AddTarget(std::unique_ptr<DmTarget>&& /* target */) {
+bool DmTable::AddTarget(std::unique_ptr<DmTarget>&& target) {
+    targets_.push_back(std::move(target));
     return true;
 }
 
@@ -34,21 +32,37 @@
 }
 
 bool DmTable::valid() const {
+    if (targets_.empty()) {
+        LOG(ERROR) << "Device-mapper table must have at least one target.";
+        return "";
+    }
+    if (targets_[0]->start() != 0) {
+        LOG(ERROR) << "Device-mapper table must start at logical sector 0.";
+        return "";
+    }
     return true;
 }
 
-uint64_t DmTable::size() const {
-    return valid() ? size_ : 0;
+uint64_t DmTable::num_sectors() const {
+    return valid() ? num_sectors_ : 0;
 }
 
-// Returns a string represnetation of the table that is ready to be passed
-// down to the kernel for loading
+// Returns a string representation of the table that is ready to be passed
+// down to the kernel for loading.
 //
 // Implementation must verify there are no gaps in the table, table starts
 // with sector == 0, and iterate over each target to get its table
 // serialized.
 std::string DmTable::Serialize() const {
-    return "";
+    if (!valid()) {
+        return "";
+    }
+
+    std::string table;
+    for (const auto& target : targets_) {
+        table += target->Serialize();
+    }
+    return table;
 }
 
 }  // namespace dm
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
index 8bcd526..5934416 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -14,16 +14,46 @@
  * limitations under the License.
  */
 
+#include "libdm/dm_target.h"
+
 #include <android-base/logging.h>
 #include <android-base/macros.h>
 
-#include <stdint.h>
-
-#include <string>
-#include <vector>
-
-#include "dm_target.h"
+#include <libdm/dm.h>
 
 namespace android {
-namespace dm {}  // namespace dm
+namespace dm {
+
+std::string DmTarget::Serialize() const {
+    // Create a string containing a dm_target_spec, parameter data, and an
+    // explicit null terminator.
+    std::string data(sizeof(dm_target_spec), '\0');
+    data += GetParameterString();
+    data.push_back('\0');
+
+    // The kernel expects each target to be 8-byte aligned.
+    size_t padding = DM_ALIGN(data.size()) - data.size();
+    for (size_t i = 0; i < padding; i++) {
+        data.push_back('\0');
+    }
+
+    // Finally fill in the dm_target_spec.
+    struct dm_target_spec* spec = reinterpret_cast<struct dm_target_spec*>(&data[0]);
+    spec->sector_start = start();
+    spec->length = size();
+    strlcpy(spec->target_type, name().c_str(), sizeof(spec->target_type));
+    spec->next = (uint32_t)data.size();
+    return data;
+}
+
+std::string DmTargetZero::GetParameterString() const {
+    // The zero target type has no additional parameters.
+    return "";
+}
+
+std::string DmTargetLinear::GetParameterString() const {
+    return block_device_ + " " + std::to_string(physical_sector_);
+}
+
+}  // namespace dm
 }  // namespace android
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
new file mode 100644
index 0000000..adbe820
--- /dev/null
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -0,0 +1,37 @@
+/*
+ * 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 <map>
+
+#include <gtest/gtest.h>
+#include <libdm/dm.h>
+
+using namespace std;
+using namespace android::dm;
+
+TEST(libdm, HasMinimumTargets) {
+    DeviceMapper& dm = DeviceMapper::Instance();
+    vector<DmTargetTypeInfo> targets;
+    ASSERT_TRUE(dm.GetAvailableTargets(&targets));
+
+    map<string, DmTargetTypeInfo> by_name;
+    for (const auto& target : targets) {
+        by_name[target.name()] = target;
+    }
+
+    auto iter = by_name.find("linear");
+    EXPECT_NE(iter, by_name.end());
+}
diff --git a/fs_mgr/libdm/include/dm_target.h b/fs_mgr/libdm/include/dm_target.h
deleted file mode 100644
index 31b0cb6..0000000
--- a/fs_mgr/libdm/include/dm_target.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- *  Copyright 2018 Google, Inc
- *
- *  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.
- */
-
-#ifndef _LIBDM_DMTARGET_H_
-#define _LIBDM_DMTARGET_H_
-
-#include <linux/dm-ioctl.h>
-#include <stdint.h>
-
-#include <android-base/logging.h>
-
-#include <string>
-
-namespace android {
-namespace dm {
-
-class DmTarget {
-  public:
-    DmTarget(const std::string& name, uint64_t start = 0, uint64_t length = 0)
-        : name_(name), v0_(0), v1_(0), v2_(0), start_(start), length_(length){};
-
-    // Creates a DmTarget object from dm_target_version as read from kernel
-    // with DM_LIST_VERSION ioctl.
-    DmTarget(const struct dm_target_versions* vers) : start_(0), length_(0) {
-        CHECK(vers != nullptr) << "Can't create DmTarget with dm_target_versions set to nullptr";
-        v0_ = vers->version[0];
-        v1_ = vers->version[1];
-        v2_ = vers->version[2];
-        name_ = vers->name;
-    }
-
-    virtual ~DmTarget() = default;
-
-    // Returns name of the target.
-    const std::string& name() const { return name_; }
-
-    // Returns size in number of sectors when this target is part of
-    // a DmTable, return 0 otherwise.
-    uint64_t size() const { return length_; }
-
-    // Return string representation of the device mapper target version.
-    std::string version() const {
-        return std::to_string(v0_) + "." + std::to_string(v1_) + "." + std::to_string(v2_);
-    }
-
-    // Function that converts this object to a string of arguments that can
-    // be passed to the kernel for adding this target in a table. Each target (e.g. verity, linear)
-    // must implement this, for it to be used on a device.
-    virtual std::string Serialize() const { return ""; }
-
-  private:
-    // Name of the target.
-    std::string name_;
-    // Target version.
-    uint32_t v0_, v1_, v2_;
-    // logical sector number start and total length (in terms of 512-byte sectors) represented
-    // by this target within a DmTable.
-    uint64_t start_, length_;
-};
-
-}  // namespace dm
-}  // namespace android
-
-#endif /* _LIBDM_DMTARGET_H_ */
diff --git a/fs_mgr/libdm/include/dm.h b/fs_mgr/libdm/include/libdm/dm.h
similarity index 87%
rename from fs_mgr/libdm/include/dm.h
rename to fs_mgr/libdm/include/libdm/dm.h
index 52a9a11..60bceed 100644
--- a/fs_mgr/libdm/include/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -17,18 +17,20 @@
 #ifndef _LIBDM_DM_H_
 #define _LIBDM_DM_H_
 
-#include <errno.h>
 #include <fcntl.h>
 #include <linux/dm-ioctl.h>
 #include <linux/kdev_t.h>
+#include <stdint.h>
 #include <sys/sysmacros.h>
 #include <unistd.h>
 
 #include <memory>
+#include <string>
+#include <vector>
 
 #include <android-base/logging.h>
 
-#include <dm_table.h>
+#include "dm_table.h"
 
 // The minimum expected device mapper major.minor version
 #define DM_VERSION0 (4)
@@ -67,14 +69,6 @@
         uint64_t dev_;
     };
 
-    // Creates a device mapper device with given name.
-    // Return 'true' on success and 'false' on failure to
-    // create OR if a device mapper device with the same name already
-    // exists.
-    // TODO(b/110035986): Make this method private and to be only
-    // called through LoadTableAndActivate() below.
-    bool CreateDevice(const std::string& name);
-
     // Removes a device mapper device with the given name.
     // Returns 'true' on success, false otherwise.
     bool DeleteDevice(const std::string& name);
@@ -88,15 +82,20 @@
     // One of INVALID, SUSPENDED or ACTIVE.
     DmDeviceState state(const std::string& name) const;
 
-    // Loads the device mapper table from parameter into the underlying
-    // device mapper device with given name and activate / resumes the device in the process.
-    // If a device mapper device with the 'name', doesn't exist, it will be created.
+    // Creates a device, loads the given table, and activates it. If the device
+    // is not able to be activated, it is destroyed, and false is returned.
+    bool CreateDevice(const std::string& name, const DmTable& table);
+
+    // Loads the device mapper table from parameter into the underlying device
+    // mapper device with given name and activate / resumes the device in the
+    // process. A device with the given name must already exist.
+    //
     // Returns 'true' on success, false otherwise.
     bool LoadTableAndActivate(const std::string& name, const DmTable& table);
 
     // Returns true if a list of available device mapper targets registered in the kernel was
     // successfully read and stored in 'targets'. Returns 'false' otherwise.
-    bool GetAvailableTargets(std::vector<DmTarget>* targets);
+    bool GetAvailableTargets(std::vector<DmTargetTypeInfo>* targets);
 
     // Return 'true' if it can successfully read the list of device mapper block devices
     // currently created. 'devices' will be empty if the kernel interactions
@@ -105,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();
@@ -138,6 +138,12 @@
         }
     }
 
+    // Creates a device mapper device with given name.
+    // Return 'true' on success and 'false' on failure to
+    // create OR if a device mapper device with the same name already
+    // exists.
+    bool CreateDevice(const std::string& name);
+
     int fd_;
     // Non-copyable & Non-movable
     DeviceMapper(const DeviceMapper&) = delete;
diff --git a/fs_mgr/libdm/include/dm_table.h b/fs_mgr/libdm/include/libdm/dm_table.h
similarity index 86%
rename from fs_mgr/libdm/include/dm_table.h
rename to fs_mgr/libdm/include/libdm/dm_table.h
index 0b1685d..5c639be 100644
--- a/fs_mgr/libdm/include/dm_table.h
+++ b/fs_mgr/libdm/include/libdm/dm_table.h
@@ -30,7 +30,7 @@
 
 class DmTable {
   public:
-    DmTable() : size_(0){};
+    DmTable() : num_sectors_(0), readonly_(false) {}
 
     // Adds a target to the device mapper table for a range specified in the target object.
     // The function will return 'true' if the target was successfully added and doesn't overlap with
@@ -48,14 +48,20 @@
     // table is malformed.
     bool valid() const;
 
+    // Returns the toatl number of targets.
+    size_t num_targets() const { return targets_.size(); }
+
     // Returns the total size represented by the table in terms of number of 512-byte sectors.
     // NOTE: This function will overlook if there are any gaps in the targets added in the table.
-    uint64_t size() const;
+    uint64_t num_sectors() const;
 
     // Returns the string represntation of the table that is ready to be passed into the kernel
     // as part of the DM_TABLE_LOAD ioctl.
     std::string Serialize() const;
 
+    void set_readonly(bool readonly) { readonly_ = readonly; }
+    bool readonly() const { return readonly_; }
+
     ~DmTable() = default;
 
   private:
@@ -66,7 +72,10 @@
 
     // Total size in terms of # of sectors, as calculated by looking at the last and the first
     // target in 'target_'.
-    uint64_t size_;
+    uint64_t num_sectors_;
+
+    // True if the device should be read-only; false otherwise.
+    bool readonly_;
 };
 
 }  // namespace dm
diff --git a/fs_mgr/libdm/include/libdm/dm_target.h b/fs_mgr/libdm/include/libdm/dm_target.h
new file mode 100644
index 0000000..31b6a70
--- /dev/null
+++ b/fs_mgr/libdm/include/libdm/dm_target.h
@@ -0,0 +1,108 @@
+/*
+ *  Copyright 2018 Google, Inc
+ *
+ *  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.
+ */
+
+#ifndef _LIBDM_DMTARGET_H_
+#define _LIBDM_DMTARGET_H_
+
+#include <linux/dm-ioctl.h>
+#include <stdint.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace dm {
+
+class DmTargetTypeInfo {
+  public:
+    DmTargetTypeInfo() : major_(0), minor_(0), patch_(0) {}
+    DmTargetTypeInfo(const struct dm_target_versions* info)
+        : name_(info->name),
+          major_(info->version[0]),
+          minor_(info->version[1]),
+          patch_(info->version[2]) {}
+
+    const std::string& name() const { return name_; }
+    std::string version() const {
+        return std::to_string(major_) + "." + std::to_string(minor_) + "." + std::to_string(patch_);
+    }
+
+  private:
+    std::string name_;
+    uint32_t major_;
+    uint32_t minor_;
+    uint32_t patch_;
+};
+
+class DmTarget {
+  public:
+    DmTarget(uint64_t start, uint64_t length) : start_(start), length_(length) {}
+
+    virtual ~DmTarget() = default;
+
+    // Returns name of the target.
+    virtual std::string name() const = 0;
+
+    // Return the first logical sector represented by this target.
+    uint64_t start() const { return start_; }
+
+    // Returns size in number of sectors when this target is part of
+    // a DmTable, return 0 otherwise.
+    uint64_t size() const { return length_; }
+
+    // Function that converts this object to a string of arguments that can
+    // be passed to the kernel for adding this target in a table. Each target (e.g. verity, linear)
+    // must implement this, for it to be used on a device.
+    std::string Serialize() const;
+
+  protected:
+    // Get the parameter string that is passed to the end of the dm_target_spec
+    // for this target type.
+    virtual std::string GetParameterString() const = 0;
+
+  private:
+    // logical sector number start and total length (in terms of 512-byte sectors) represented
+    // by this target within a DmTable.
+    uint64_t start_, length_;
+};
+
+class DmTargetZero final : public DmTarget {
+  public:
+    DmTargetZero(uint64_t start, uint64_t length) : DmTarget(start, length) {}
+
+    std::string name() const override { return "zero"; }
+    std::string GetParameterString() const override;
+};
+
+class DmTargetLinear final : public DmTarget {
+  public:
+    DmTargetLinear(uint64_t start, uint64_t length, const std::string& block_device,
+                   uint64_t physical_sector)
+        : DmTarget(start, length), block_device_(block_device), physical_sector_(physical_sector) {}
+
+    std::string name() const override { return "linear"; }
+    std::string GetParameterString() const override;
+
+  private:
+    std::string block_device_;
+    uint64_t physical_sector_;
+};
+
+}  // namespace dm
+}  // namespace android
+
+#endif /* _LIBDM_DMTARGET_H_ */
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index 0ca8938..f7086a8 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -42,3 +42,20 @@
     ],
     export_include_dirs: ["include"],
 }
+
+cc_test {
+    name: "liblp_test",
+    defaults: ["fs_mgr_defaults"],
+    static_libs: [
+        "libbase",
+        "liblog",
+        "libcrypto",
+        "libcrypto_utils",
+        "liblp",
+    ],
+    srcs: [
+        "builder_test.cpp",
+        "io_test.cpp",
+        "utility_test.cpp",
+    ],
+}
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index a084893..0e4838c 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -287,7 +287,7 @@
         DCHECK(first_sector <= geometry_.last_logical_sector);
 
         // Note: the last usable sector is inclusive.
-        if (first_sector + sectors_needed > geometry_.last_logical_sector) {
+        if (geometry_.last_logical_sector + 1 - first_sector < sectors_needed) {
             LERROR << "Not enough free space to expand partition: " << partition->name();
             return false;
         }
@@ -347,5 +347,9 @@
     return metadata;
 }
 
+uint64_t MetadataBuilder::AllocatableSpace() const {
+    return (geometry_.last_logical_sector - geometry_.first_logical_sector + 1) * LP_SECTOR_SIZE;
+}
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
new file mode 100644
index 0000000..2983f0f
--- /dev/null
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -0,0 +1,326 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <liblp/builder.h>
+
+using namespace std;
+using namespace android::fs_mgr;
+
+static const char* TEST_GUID = "A799D1D6-669F-41D8-A3F0-EBB7572D8302";
+static const char* TEST_GUID2 = "A799D1D6-669F-41D8-A3F0-EBB7572D8303";
+
+TEST(liblp, BuildBasic) {
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+
+    Partition* partition = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(partition, nullptr);
+    EXPECT_EQ(partition->name(), "system");
+    EXPECT_EQ(partition->guid(), TEST_GUID);
+    EXPECT_EQ(partition->attributes(), LP_PARTITION_ATTR_READONLY);
+    EXPECT_EQ(partition->size(), 0);
+    EXPECT_EQ(builder->FindPartition("system"), partition);
+
+    builder->RemovePartition("system");
+    EXPECT_EQ(builder->FindPartition("system"), nullptr);
+}
+
+TEST(liblp, ResizePartition) {
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+
+    Partition* system = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(system, nullptr);
+    EXPECT_EQ(builder->GrowPartition(system, 65536), true);
+    EXPECT_EQ(system->size(), 65536);
+    ASSERT_EQ(system->extents().size(), 1);
+
+    LinearExtent* extent = system->extents()[0]->AsLinearExtent();
+    ASSERT_NE(extent, nullptr);
+    EXPECT_EQ(extent->num_sectors(), 65536 / LP_SECTOR_SIZE);
+    // The first logical sector will be (4096+1024*2)/512 = 12.
+    EXPECT_EQ(extent->physical_sector(), 12);
+
+    // Test growing to the same size.
+    EXPECT_EQ(builder->GrowPartition(system, 65536), true);
+    EXPECT_EQ(system->size(), 65536);
+    EXPECT_EQ(system->extents().size(), 1);
+    EXPECT_EQ(system->extents()[0]->num_sectors(), 65536 / LP_SECTOR_SIZE);
+    // Test growing to a smaller size.
+    EXPECT_EQ(builder->GrowPartition(system, 0), true);
+    EXPECT_EQ(system->size(), 65536);
+    EXPECT_EQ(system->extents().size(), 1);
+    EXPECT_EQ(system->extents()[0]->num_sectors(), 65536 / LP_SECTOR_SIZE);
+    // Test shrinking to a greater size.
+    builder->ShrinkPartition(system, 131072);
+    EXPECT_EQ(system->size(), 65536);
+    EXPECT_EQ(system->extents().size(), 1);
+    EXPECT_EQ(system->extents()[0]->num_sectors(), 65536 / LP_SECTOR_SIZE);
+
+    // Test shrinking within the same extent.
+    builder->ShrinkPartition(system, 32768);
+    EXPECT_EQ(system->size(), 32768);
+    EXPECT_EQ(system->extents().size(), 1);
+    extent = system->extents()[0]->AsLinearExtent();
+    ASSERT_NE(extent, nullptr);
+    EXPECT_EQ(extent->num_sectors(), 32768 / LP_SECTOR_SIZE);
+    EXPECT_EQ(extent->physical_sector(), 12);
+
+    // Test shrinking to 0.
+    builder->ShrinkPartition(system, 0);
+    EXPECT_EQ(system->size(), 0);
+    EXPECT_EQ(system->extents().size(), 0);
+}
+
+TEST(liblp, PartitionAlignment) {
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+
+    // Test that we align up to one sector.
+    Partition* system = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(system, nullptr);
+    EXPECT_EQ(builder->GrowPartition(system, 10000), true);
+    EXPECT_EQ(system->size(), 10240);
+    EXPECT_EQ(system->extents().size(), 1);
+
+    builder->ShrinkPartition(system, 9000);
+    EXPECT_EQ(system->size(), 9216);
+    EXPECT_EQ(system->extents().size(), 1);
+}
+
+TEST(liblp, DiskAlignment) {
+    static const uint64_t kDiskSize = 1000000;
+    static const uint32_t kMetadataSize = 1024;
+    static const uint32_t kMetadataSlots = 2;
+
+    // If the disk size is not aligned to 512 bytes, make sure it still leaves
+    // space at the end for backup metadata, and that it doesn't overlap with
+    // the space for logical partitions.
+    unique_ptr<MetadataBuilder> builder =
+            MetadataBuilder::New(kDiskSize, kMetadataSize, kMetadataSlots);
+    unique_ptr<LpMetadata> exported = builder->Export();
+    ASSERT_NE(exported, nullptr);
+
+    static const size_t kMetadataSpace =
+            (kMetadataSize * kMetadataSlots) + LP_METADATA_GEOMETRY_SIZE;
+    uint64_t space_at_end =
+            kDiskSize - (exported->geometry.last_logical_sector + 1) * LP_SECTOR_SIZE;
+    EXPECT_GE(space_at_end, kMetadataSpace);
+}
+
+TEST(liblp, MetadataAlignment) {
+    // Make sure metadata sizes get aligned up.
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1000, 2);
+    unique_ptr<LpMetadata> exported = builder->Export();
+    ASSERT_NE(exported, nullptr);
+    EXPECT_EQ(exported->geometry.metadata_max_size, 1024);
+}
+
+TEST(liblp, UseAllDiskSpace) {
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+    EXPECT_EQ(builder->AllocatableSpace(), 1036288);
+
+    Partition* system = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(system, nullptr);
+    EXPECT_EQ(builder->GrowPartition(system, 1036288), true);
+    EXPECT_EQ(system->size(), 1036288);
+    EXPECT_EQ(builder->GrowPartition(system, 1036289), false);
+}
+
+TEST(liblp, BuildComplex) {
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+
+    Partition* system = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_READONLY);
+    Partition* vendor = builder->AddPartition("vendor", TEST_GUID2, LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(system, nullptr);
+    ASSERT_NE(vendor, nullptr);
+    EXPECT_EQ(builder->GrowPartition(system, 65536), true);
+    EXPECT_EQ(builder->GrowPartition(vendor, 32768), true);
+    EXPECT_EQ(builder->GrowPartition(system, 98304), true);
+    EXPECT_EQ(system->size(), 98304);
+    EXPECT_EQ(vendor->size(), 32768);
+
+    // We now expect to have 3 extents total: 2 for system, 1 for vendor, since
+    // our allocation strategy is greedy/first-fit.
+    ASSERT_EQ(system->extents().size(), 2);
+    ASSERT_EQ(vendor->extents().size(), 1);
+
+    LinearExtent* system1 = system->extents()[0]->AsLinearExtent();
+    LinearExtent* system2 = system->extents()[1]->AsLinearExtent();
+    LinearExtent* vendor1 = vendor->extents()[0]->AsLinearExtent();
+    ASSERT_NE(system1, nullptr);
+    ASSERT_NE(system2, nullptr);
+    ASSERT_NE(vendor1, nullptr);
+    EXPECT_EQ(system1->num_sectors(), 65536 / LP_SECTOR_SIZE);
+    EXPECT_EQ(system1->physical_sector(), 12);
+    EXPECT_EQ(system2->num_sectors(), 32768 / LP_SECTOR_SIZE);
+    EXPECT_EQ(system2->physical_sector(), 204);
+    EXPECT_EQ(vendor1->num_sectors(), 32768 / LP_SECTOR_SIZE);
+    EXPECT_EQ(vendor1->physical_sector(), 140);
+    EXPECT_EQ(system1->physical_sector() + system1->num_sectors(), vendor1->physical_sector());
+    EXPECT_EQ(vendor1->physical_sector() + vendor1->num_sectors(), system2->physical_sector());
+}
+
+TEST(liblp, AddInvalidPartition) {
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+
+    Partition* partition = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(partition, nullptr);
+
+    // Duplicate name.
+    partition = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_READONLY);
+    EXPECT_EQ(partition, nullptr);
+
+    // Empty name.
+    partition = builder->AddPartition("", TEST_GUID, LP_PARTITION_ATTR_READONLY);
+    EXPECT_EQ(partition, nullptr);
+}
+
+TEST(liblp, BuilderExport) {
+    static const uint64_t kDiskSize = 1024 * 1024;
+    static const uint32_t kMetadataSize = 1024;
+    static const uint32_t kMetadataSlots = 2;
+    unique_ptr<MetadataBuilder> builder =
+            MetadataBuilder::New(kDiskSize, kMetadataSize, kMetadataSlots);
+
+    Partition* system = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_READONLY);
+    Partition* vendor = builder->AddPartition("vendor", TEST_GUID2, LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(system, nullptr);
+    ASSERT_NE(vendor, nullptr);
+    EXPECT_EQ(builder->GrowPartition(system, 65536), true);
+    EXPECT_EQ(builder->GrowPartition(vendor, 32768), true);
+    EXPECT_EQ(builder->GrowPartition(system, 98304), true);
+
+    unique_ptr<LpMetadata> exported = builder->Export();
+    EXPECT_NE(exported, nullptr);
+
+    // Verify geometry. Some details of this may change if we change the
+    // metadata structures. So in addition to checking the exact values, we
+    // also check that they are internally consistent after.
+    const LpMetadataGeometry& geometry = exported->geometry;
+    EXPECT_EQ(geometry.magic, LP_METADATA_GEOMETRY_MAGIC);
+    EXPECT_EQ(geometry.struct_size, sizeof(geometry));
+    EXPECT_EQ(geometry.metadata_max_size, 1024);
+    EXPECT_EQ(geometry.metadata_slot_count, 2);
+    EXPECT_EQ(geometry.first_logical_sector, 12);
+    EXPECT_EQ(geometry.last_logical_sector, 2035);
+
+    static const size_t kMetadataSpace =
+            (kMetadataSize * kMetadataSlots) + LP_METADATA_GEOMETRY_SIZE;
+    uint64_t space_at_end = kDiskSize - (geometry.last_logical_sector + 1) * LP_SECTOR_SIZE;
+    EXPECT_GE(space_at_end, kMetadataSpace);
+    EXPECT_GE(geometry.first_logical_sector * LP_SECTOR_SIZE, kMetadataSpace);
+
+    // Verify header.
+    const LpMetadataHeader& header = exported->header;
+    EXPECT_EQ(header.magic, LP_METADATA_HEADER_MAGIC);
+    EXPECT_EQ(header.major_version, LP_METADATA_MAJOR_VERSION);
+    EXPECT_EQ(header.minor_version, LP_METADATA_MINOR_VERSION);
+
+    ASSERT_EQ(exported->partitions.size(), 2);
+    ASSERT_EQ(exported->extents.size(), 3);
+
+    for (const auto& partition : exported->partitions) {
+        Partition* original = builder->FindPartition(GetPartitionName(partition));
+        ASSERT_NE(original, nullptr);
+        EXPECT_EQ(original->guid(), GetPartitionGuid(partition));
+        for (size_t i = 0; i < partition.num_extents; i++) {
+            const auto& extent = exported->extents[partition.first_extent_index + i];
+            LinearExtent* original_extent = original->extents()[i]->AsLinearExtent();
+            EXPECT_EQ(extent.num_sectors, original_extent->num_sectors());
+            EXPECT_EQ(extent.target_type, LP_TARGET_TYPE_LINEAR);
+            EXPECT_EQ(extent.target_data, original_extent->physical_sector());
+        }
+        EXPECT_EQ(partition.attributes, original->attributes());
+    }
+}
+
+TEST(liblp, BuilderImport) {
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+
+    Partition* system = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_READONLY);
+    Partition* vendor = builder->AddPartition("vendor", TEST_GUID2, LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(system, nullptr);
+    ASSERT_NE(vendor, nullptr);
+    EXPECT_EQ(builder->GrowPartition(system, 65536), true);
+    EXPECT_EQ(builder->GrowPartition(vendor, 32768), true);
+    EXPECT_EQ(builder->GrowPartition(system, 98304), true);
+
+    unique_ptr<LpMetadata> exported = builder->Export();
+    ASSERT_NE(exported, nullptr);
+
+    builder = MetadataBuilder::New(*exported.get());
+    ASSERT_NE(builder, nullptr);
+    system = builder->FindPartition("system");
+    ASSERT_NE(system, nullptr);
+    vendor = builder->FindPartition("vendor");
+    ASSERT_NE(vendor, nullptr);
+
+    EXPECT_EQ(system->size(), 98304);
+    ASSERT_EQ(system->extents().size(), 2);
+    EXPECT_EQ(system->guid(), TEST_GUID);
+    EXPECT_EQ(system->attributes(), LP_PARTITION_ATTR_READONLY);
+    EXPECT_EQ(vendor->size(), 32768);
+    ASSERT_EQ(vendor->extents().size(), 1);
+    EXPECT_EQ(vendor->guid(), TEST_GUID2);
+    EXPECT_EQ(vendor->attributes(), LP_PARTITION_ATTR_READONLY);
+
+    LinearExtent* system1 = system->extents()[0]->AsLinearExtent();
+    LinearExtent* system2 = system->extents()[1]->AsLinearExtent();
+    LinearExtent* vendor1 = vendor->extents()[0]->AsLinearExtent();
+    EXPECT_EQ(system1->num_sectors(), 65536 / LP_SECTOR_SIZE);
+    EXPECT_EQ(system1->physical_sector(), 12);
+    EXPECT_EQ(system2->num_sectors(), 32768 / LP_SECTOR_SIZE);
+    EXPECT_EQ(system2->physical_sector(), 204);
+    EXPECT_EQ(vendor1->num_sectors(), 32768 / LP_SECTOR_SIZE);
+}
+
+TEST(liblp, ExportNameTooLong) {
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+
+    std::string name = "abcdefghijklmnopqrstuvwxyz0123456789";
+    Partition* system = builder->AddPartition(name + name, TEST_GUID, LP_PARTITION_ATTR_READONLY);
+    EXPECT_NE(system, nullptr);
+
+    unique_ptr<LpMetadata> exported = builder->Export();
+    EXPECT_EQ(exported, nullptr);
+}
+
+TEST(liblp, ExportInvalidGuid) {
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+
+    Partition* system = builder->AddPartition("system", "bad", LP_PARTITION_ATTR_READONLY);
+    EXPECT_NE(system, nullptr);
+
+    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/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index fb982e2..671a3bd 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -153,6 +153,9 @@
     // underlying filesystem or contents of the partition on disk.
     void ShrinkPartition(Partition* partition, uint64_t requested_size);
 
+    // Amount of space that can be allocated to logical partitions.
+    uint64_t AllocatableSpace() const;
+
   private:
     MetadataBuilder();
     bool Init(uint64_t blockdevice_size, uint32_t metadata_max_size, uint32_t metadata_slot_count);
diff --git a/fs_mgr/liblp/include/liblp/metadata_format.h b/fs_mgr/liblp/include/liblp/metadata_format.h
index 6a2c655..8522435 100644
--- a/fs_mgr/liblp/include/liblp/metadata_format.h
+++ b/fs_mgr/liblp/include/liblp/metadata_format.h
@@ -46,6 +46,7 @@
  * READONLY - The partition should not be considered writable. When used with
  * device mapper, the block device will be created as read-only.
  */
+#define LP_PARTITION_ATTR_NONE 0x0
 #define LP_PARTITION_ATTR_READONLY 0x1
 
 /* Mask that defines all valid attributes. */
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/utility.cpp b/fs_mgr/liblp/utility.cpp
index 217d802..5310cab 100644
--- a/fs_mgr/liblp/utility.cpp
+++ b/fs_mgr/liblp/utility.cpp
@@ -84,7 +84,7 @@
     // macro to assist with buffer sizing.
     static const size_t kGuidLen = 36;
     char buffer[kGuidLen + 1];
-    uuid_unparse(partition.guid, buffer);
+    uuid_unparse_upper(partition.guid, buffer);
     return buffer;
 }
 
diff --git a/fs_mgr/liblp/utility_test.cpp b/fs_mgr/liblp/utility_test.cpp
new file mode 100644
index 0000000..25e8a25
--- /dev/null
+++ b/fs_mgr/liblp/utility_test.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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 "utility.h"
+#include <gtest/gtest.h>
+
+using namespace android;
+using namespace android::fs_mgr;
+
+TEST(liblp, SlotNumberForSlotSuffix) {
+    EXPECT_EQ(SlotNumberForSlotSuffix(""), 0);
+    EXPECT_EQ(SlotNumberForSlotSuffix("_a"), 0);
+    EXPECT_EQ(SlotNumberForSlotSuffix("_b"), 1);
+    EXPECT_EQ(SlotNumberForSlotSuffix("_c"), 2);
+    EXPECT_EQ(SlotNumberForSlotSuffix("_d"), 3);
+}
+
+TEST(liblp, GetMetadataOffset) {
+    LpMetadataGeometry geometry = {
+            LP_METADATA_GEOMETRY_MAGIC, sizeof(geometry), {0}, 16384, 4, 10000, 80000};
+    EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 0), 4096);
+    EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 1), 4096 + 16384);
+    EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 2), 4096 + 16384 * 2);
+    EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 3), 4096 + 16384 * 3);
+
+    EXPECT_EQ(GetBackupMetadataOffset(geometry, 3), -4096 - 16384 * 1);
+    EXPECT_EQ(GetBackupMetadataOffset(geometry, 2), -4096 - 16384 * 2);
+    EXPECT_EQ(GetBackupMetadataOffset(geometry, 1), -4096 - 16384 * 3);
+    EXPECT_EQ(GetBackupMetadataOffset(geometry, 0), -4096 - 16384 * 4);
+}
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 b40b83a..9d48b8c 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -22,63 +22,154 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <android-base/parseint.h>
 #include <android-base/unique_fd.h>
-#include <dm.h>
+#include <libdm/dm.h>
 
 #include <functional>
 #include <iomanip>
 #include <ios>
 #include <iostream>
 #include <map>
+#include <sstream>
 #include <string>
 #include <vector>
 
 using DeviceMapper = ::android::dm::DeviceMapper;
+using DmTable = ::android::dm::DmTable;
 using DmTarget = ::android::dm::DmTarget;
+using DmTargetLinear = ::android::dm::DmTargetLinear;
+using DmTargetZero = ::android::dm::DmTargetZero;
+using DmTargetTypeInfo = ::android::dm::DmTargetTypeInfo;
 using DmBlockDevice = ::android::dm::DeviceMapper::DmBlockDevice;
 
 static int Usage(void) {
     std::cerr << "usage: dmctl <command> [command options]" << std::endl;
     std::cerr << "commands:" << std::endl;
-    std::cerr << "  create <dm-name> [<dm-target> [-lo <filename>] <dm-target-args>]" << std::endl;
+    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;
+    std::cerr << "  <target_type> <start_sector> <num_sectors> [target_data]" << std::endl;
     return -EINVAL;
 }
 
+class TargetParser final {
+  public:
+    TargetParser(int argc, char** argv) : arg_index_(0), argc_(argc), argv_(argv) {}
+
+    bool More() const { return arg_index_ < argc_; }
+    std::unique_ptr<DmTarget> Next() {
+        if (!HasArgs(3)) {
+            std::cerr << "Expected <target_type> <start_sector> <num_sectors>" << std::endl;
+            return nullptr;
+        }
+
+        std::string target_type = NextArg();
+        uint64_t start_sector, num_sectors;
+        if (!android::base::ParseUint(NextArg(), &start_sector)) {
+            std::cerr << "Expected start sector, got: " << PreviousArg() << std::endl;
+            return nullptr;
+        }
+        if (!android::base::ParseUint(NextArg(), &num_sectors) || !num_sectors) {
+            std::cerr << "Expected non-zero sector count, got: " << PreviousArg() << std::endl;
+            return nullptr;
+        }
+
+        if (target_type == "zero") {
+            return std::make_unique<DmTargetZero>(start_sector, num_sectors);
+        } else if (target_type == "linear") {
+            if (!HasArgs(2)) {
+                std::cerr << "Expected \"linear\" <block_device> <sector>" << std::endl;
+                return nullptr;
+            }
+
+            std::string block_device = NextArg();
+            uint64_t physical_sector;
+            if (!android::base::ParseUint(NextArg(), &physical_sector)) {
+                std::cerr << "Expected sector, got: \"" << PreviousArg() << "\"" << std::endl;
+                return nullptr;
+            }
+            return std::make_unique<DmTargetLinear>(start_sector, num_sectors, block_device,
+                                                    physical_sector);
+        } else {
+            std::cerr << "Unrecognized target type: " << target_type << std::endl;
+            return nullptr;
+        }
+    }
+
+  private:
+    bool HasArgs(int count) { return arg_index_ + count <= argc_; }
+    const char* NextArg() {
+        CHECK(arg_index_ < argc_);
+        return argv_[arg_index_++];
+    }
+    const char* PreviousArg() {
+        CHECK(arg_index_ >= 0);
+        return argv_[arg_index_ - 1];
+    }
+
+  private:
+    int arg_index_;
+    int argc_;
+    char** argv_;
+};
+
 static int DmCreateCmdHandler(int argc, char** argv) {
     if (argc < 1) {
-        std::cerr << "DmCreateCmdHandler: atleast 'name' MUST be provided for target device";
+        std::cerr << "Usage: dmctl create <dm-name> [-ro] <targets...>" << std::endl;
+        return -EINVAL;
+    }
+    std::string name = argv[0];
+
+    // Parse extended options first.
+    DmTable table;
+    int arg_index = 1;
+    while (arg_index < argc && argv[arg_index][0] == '-') {
+        if (strcmp(argv[arg_index], "-ro") == 0) {
+            table.set_readonly(true);
+        } else {
+            std::cerr << "Unrecognized option: " << argv[arg_index] << std::endl;
+            return -EINVAL;
+        }
+        arg_index++;
+    }
+
+    // Parse everything else as target information.
+    TargetParser parser(argc - arg_index, argv + arg_index);
+    while (parser.More()) {
+        std::unique_ptr<DmTarget> target = parser.Next();
+        if (!target || !table.AddTarget(std::move(target))) {
+            return -EINVAL;
+        }
+    }
+
+    if (table.num_targets() == 0) {
+        std::cerr << "Must define at least one target." << std::endl;
         return -EINVAL;
     }
 
-    std::string name = argv[0];
     DeviceMapper& dm = DeviceMapper::Instance();
-    if (!dm.CreateDevice(name)) {
-        std::cerr << "DmCreateCmdHandler: Failed to create " << name << " device";
+    if (!dm.CreateDevice(name, table)) {
+        std::cerr << "Failed to create device-mapper device with name: " << name << std::endl;
         return -EIO;
     }
-
-    // if we also have target specified
-    if (argc > 1) {
-        // fall through for now. This will eventually create a DmTarget() based on the target name
-        // passing it the table that is specified at the command line
-    }
-
     return 0;
 }
 
 static int DmDeleteCmdHandler(int argc, char** argv) {
     if (argc < 1) {
-        std::cerr << "DmCreateCmdHandler: atleast 'name' MUST be provided for target device";
+        std::cerr << "Usage: dmctl delete <name>" << std::endl;
         return -EINVAL;
     }
 
     std::string name = argv[0];
     DeviceMapper& dm = DeviceMapper::Instance();
     if (!dm.DeleteDevice(name)) {
-        std::cerr << "DmCreateCmdHandler: Failed to create " << name << " device";
+        std::cerr << "Failed to delete [" << name << "]" << std::endl;
         return -EIO;
     }
 
@@ -86,7 +177,7 @@
 }
 
 static int DmListTargets(DeviceMapper& dm) {
-    std::vector<DmTarget> targets;
+    std::vector<DmTargetTypeInfo> targets;
     if (!dm.GetAvailableTargets(&targets)) {
         std::cerr << "Failed to read available device mapper targets" << std::endl;
         return -errno;
@@ -151,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) {
diff --git a/init/README.md b/init/README.md
index 550ef05..b0a73b9 100644
--- a/init/README.md
+++ b/init/README.md
@@ -752,3 +752,22 @@
     kill -SIGCONT 4343
 
     > strace runs
+
+Host Init Script Verification
+-----------------------------
+
+Init scripts are checked for correctness during build time. Specifically the below is checked.
+
+1) Well formatted action, service and import sections, e.g. no actions without a preceding 'on'
+line, and no extraneous lines after an 'import' statement.
+2) All commands map to a valid keyword and the argument count is within the correct range.
+3) All service options are valid. This is stricter than how commands are checked as the service
+options' arguments are fully parsed, e.g. UIDs and GIDs must resolve.
+
+There are other parts of init scripts that are only parsed at runtime and therefore not checked
+during build time, among them are the below.
+
+1) The validity of the arguments of commands, e.g. no checking if file paths actually exist, if
+SELinux would permit the operation, or if the UIDs and GIDs resolve.
+2) No checking if a service exists or has a valid SELinux domain defined
+3) No checking if a service has not been previously defined in a different init script.
diff --git a/init/init.cpp b/init/init.cpp
index b494bcc..77c4fc4 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -352,21 +352,23 @@
 }
 
 static void export_kernel_boot_props() {
+    constexpr const char* UNSET = "";
     struct {
         const char *src_prop;
         const char *dst_prop;
         const char *default_value;
     } prop_map[] = {
-        { "ro.boot.serialno",   "ro.serialno",   "", },
+        { "ro.boot.serialno",   "ro.serialno",   UNSET, },
         { "ro.boot.mode",       "ro.bootmode",   "unknown", },
         { "ro.boot.baseband",   "ro.baseband",   "unknown", },
         { "ro.boot.bootloader", "ro.bootloader", "unknown", },
         { "ro.boot.hardware",   "ro.hardware",   "unknown", },
         { "ro.boot.revision",   "ro.revision",   "0", },
     };
-    for (size_t i = 0; i < arraysize(prop_map); i++) {
-        std::string value = GetProperty(prop_map[i].src_prop, "");
-        property_set(prop_map[i].dst_prop, (!value.empty()) ? value : prop_map[i].default_value);
+    for (const auto& prop : prop_map) {
+        std::string value = GetProperty(prop.src_prop, prop.default_value);
+        if (value != UNSET)
+            property_set(prop.dst_prop, value);
     }
 }
 
diff --git a/init/service.cpp b/init/service.cpp
index 565cae7..95b37ab 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -787,9 +787,9 @@
     flags_ |= SVC_EXEC;
     is_exec_service_running_ = true;
 
-    LOG(INFO) << "SVC_EXEC pid " << pid_ << " (uid " << uid_ << " gid " << gid_ << "+"
-              << supp_gids_.size() << " context " << (!seclabel_.empty() ? seclabel_ : "default")
-              << ") started; waiting...";
+    LOG(INFO) << "SVC_EXEC service '" << name_ << "' pid " << pid_ << " (uid " << uid_ << " gid "
+              << gid_ << "+" << supp_gids_.size() << " context "
+              << (!seclabel_.empty() ? seclabel_ : "default") << ") started; waiting...";
 
     return Success();
 }
diff --git a/liblog/tests/libc_test.cpp b/liblog/tests/libc_test.cpp
index 329ba85..6d78ed6 100644
--- a/liblog/tests/libc_test.cpp
+++ b/liblog/tests/libc_test.cpp
@@ -21,6 +21,7 @@
 
 TEST(libc, __pstore_append) {
 #ifdef __ANDROID__
+#ifndef NO_PSTORE
   FILE* fp;
   ASSERT_TRUE(NULL != (fp = fopen("/dev/pmsg0", "a")));
   static const char message[] = "libc.__pstore_append\n";
@@ -43,6 +44,9 @@
             "Reboot, ensure string libc.__pstore_append is in "
             "/sys/fs/pstore/pmsg-ramoops-0\n");
   }
+#else  /* NO_PSTORE */
+  GTEST_LOG_(INFO) << "This test does nothing because of NO_PSTORE.\n";
+#endif /* NO_PSTORE */
 #else
   GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index dfb88f7..7834dd5 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -1,25 +1,46 @@
 phony {
     name: "shell_and_utilities",
     required: [
+        "shell_and_utilities_system",
+        "shell_and_utilities_recovery",
+        "shell_and_utilities_vendor",
+    ],
+}
+
+phony {
+    name: "shell_and_utilities_system",
+    required: [
         "awk",
-        "awk_vendor",
         "bzip2",
         "grep",
-        "grep_vendor",
         "logwrapper",
-        "logwrapper_vendor",
         "mkshrc",
-        "mkshrc_vendor",
         "newfs_msdos",
         "reboot",
         "sh",
-        "sh.recovery",
-        "sh_vendor",
         "toolbox",
-        "toolbox.recovery",
-        "toolbox_vendor",
         "toybox",
+    ],
+}
+
+phony {
+    name: "shell_and_utilities_recovery",
+    required: [
+        "sh.recovery",
+        "toolbox.recovery",
         "toybox.recovery",
+    ],
+}
+
+phony {
+    name: "shell_and_utilities_vendor",
+    required: [
+        "awk_vendor",
+        "grep_vendor",
+        "logwrapper_vendor",
+        "mkshrc_vendor",
+        "sh_vendor",
+        "toolbox_vendor",
         "toybox_vendor",
     ],
 }
diff --git a/shell_and_utilities/README.md b/shell_and_utilities/README.md
index b15be1f..e310e6b 100644
--- a/shell_and_utilities/README.md
+++ b/shell_and_utilities/README.md
@@ -193,13 +193,15 @@
 Android Q
 ---------
 
+BSD: fsck\_msdos newfs\_msdos
+
 bzip2: bzcat bzip2 bunzip2
 
 one-true-awk: awk
 
 PCRE: egrep fgrep grep
 
-toolbox: getevent getprop newfs\_msdos
+toolbox: getevent getprop
 
 toybox: acpi base64 basename blockdev cal cat chcon chgrp chmod chown
 chroot chrt cksum clear cmp comm cp cpio cut date dd df diff dirname